mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-03-19 03:08:30 -04:00
Frontent dev env (#247)
* Added frontend development files/environment * More items-categories related removals * Improvements in pages templates (inc. static pages) * Improvements in video player * Added empty home page message + cta * Updates in media, playlist and management pages * Improvements in material icons font loading * Replaced media & playlists links in frontend dev-env * frontend package version update * chnaged frontend dev url port * static files update * Changed default position of theme switcher * enabled frontend docker container
This commit is contained in:
731
frontend/src/static/js/components/profile-page/ProfilePage.scss
Normal file
731
frontend/src/static/js/components/profile-page/ProfilePage.scss
Normal file
@@ -0,0 +1,731 @@
|
||||
@import '../../../css/includes/_variables.scss';
|
||||
@import '../../../css/includes/_variables_dimensions.scss';
|
||||
|
||||
#page-profile-media,
|
||||
#page-profile-playlists,
|
||||
#page-profile-about,
|
||||
#page-liked.profile-page-liked,
|
||||
#page-history.profile-page-history {
|
||||
.page-main {
|
||||
background-color: var(--profile-page-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.profile-page-header {
|
||||
background-color: var(--profile-page-header-bg-color);
|
||||
|
||||
.profile-info-nav-wrap,
|
||||
.profile-info-nav-wrap:before {
|
||||
background-color: var(--profile-page-header-bg-color);
|
||||
}
|
||||
|
||||
.profile-info {
|
||||
.profile-videos-number {
|
||||
color: var(--profile-page-info-videos-number-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.profile-nav {
|
||||
background-color: var(--profile-page-header-bg-color);
|
||||
border-bottom-color: var(--profile-page-header-bg-color);
|
||||
|
||||
ul {
|
||||
li {
|
||||
a {
|
||||
color: var(--profile-page-nav-link-text-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--profile-page-nav-link-hover-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
a {
|
||||
color: var(--profile-page-nav-link-active-text-color);
|
||||
}
|
||||
|
||||
&:after {
|
||||
background-color: var(--profile-page-nav-link-active-after-bg-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-page-content {
|
||||
background-color: var(--profile-page-bg-color);
|
||||
|
||||
.item-content h3 {
|
||||
span {
|
||||
background-color: var(--profile-page-bg-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-page-header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
float: left;
|
||||
|
||||
button {
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-banner-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
float: left;
|
||||
|
||||
&.no-banner-img {
|
||||
padding-bottom: 0;
|
||||
|
||||
.profile-banner {
|
||||
position: relative;
|
||||
top: auto;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
padding: 0 1rem;
|
||||
margin: 24px auto;
|
||||
text-align: right;
|
||||
transform: none;
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: 492px) {
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
padding: 0;
|
||||
width: 654px;
|
||||
}
|
||||
|
||||
@media (min-width: 928px) {
|
||||
width: 872px;
|
||||
}
|
||||
|
||||
@media (min-width: 1146px) {
|
||||
width: 1090px;
|
||||
}
|
||||
|
||||
@media (min-width: 1582px) {
|
||||
width: 1308px;
|
||||
}
|
||||
}
|
||||
|
||||
a.edit-channel {
|
||||
position: relative;
|
||||
top: auto;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(#000, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.popup-message-bottom {
|
||||
button {
|
||||
position: relative;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
font-size: 14px;
|
||||
|
||||
&.proceed-profile-removal {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&.cancel-profile-removal {
|
||||
float: left;
|
||||
color: var(--body-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-banner {
|
||||
position: fixed;
|
||||
top: var(--header-height);
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: block;
|
||||
background-attachment: scroll;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.profile-banner-wrap {
|
||||
padding-bottom: 18%;
|
||||
|
||||
@media screen and (min-width: 492px) {
|
||||
padding-bottom: calc(100% / 6.2);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 339px) {
|
||||
}
|
||||
}
|
||||
|
||||
.profile-banner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
a.edit-channel,
|
||||
a.edit-profile {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
a.edit-channel,
|
||||
a.edit-profile,
|
||||
.delete-profile-wrap > button {
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
line-height: inherit;
|
||||
|
||||
padding: 6px 12px;
|
||||
border-radius: 1px;
|
||||
|
||||
background-color: var(--brand-color, var(--default-brand-color));
|
||||
|
||||
@media screen and (min-width: 710px) {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
a.edit-channel,
|
||||
a.edit-profile {
|
||||
}
|
||||
|
||||
a.edit-channel {
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
|
||||
@media screen and (min-width: 710px) {
|
||||
right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
a.edit-profile {
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.delete-profile-wrap {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
|
||||
@media screen and (min-width: 710px) {
|
||||
left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-info-nav-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
float: left;
|
||||
padding-top: 16px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
|
||||
@media screen and (min-width: 710px) {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-info {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
clear: both;
|
||||
|
||||
.sliding-sidebar & {
|
||||
transition-property: width;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
|
||||
@media screen and (max-width: 709px) {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: inline-block;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
line-height: 1.25;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.profile-info-inner {
|
||||
display: table;
|
||||
width: 100%;
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
|
||||
&:first-child {
|
||||
width: 80px + 24px;
|
||||
|
||||
@media screen and (max-width: 709px) {
|
||||
width: 64px + 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-videos-number {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-nav {
|
||||
position: relative;
|
||||
z-index: +1;
|
||||
height: $_authorPage-navHeight;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
|
||||
.items-list-wrap {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.profile-nav-inner {
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
clear: both;
|
||||
|
||||
.sliding-sidebar & {
|
||||
transition-property: width;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
&.items-list-outer .previous-slide,
|
||||
&.items-list-outer .next-slide {
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
padding: 0 !important;
|
||||
margin: 0;
|
||||
background-color: var(--profile-page-header-bg-color);
|
||||
|
||||
.circle-icon-button {
|
||||
margin: 0;
|
||||
background-color: var(--profile-page-header-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.items-list-outer .previous-slide {
|
||||
left: -0.75em;
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
&.items-list-outer .next-slide {
|
||||
right: -0.75em;
|
||||
right: -1px;
|
||||
}
|
||||
|
||||
ul {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
float: left;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
vertical-align: bottom;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
line-height: $_authorPage-navHeight;
|
||||
width: 109px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.007px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
bottom: 1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&.media-search {
|
||||
> * {
|
||||
position: relative;
|
||||
display: table;
|
||||
float: left;
|
||||
width: auto;
|
||||
height: 3rem;
|
||||
|
||||
> span {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: transparent;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
max-width: 178px;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
font-weight: 500;
|
||||
border-width: 0 0 2px;
|
||||
background-color: transparent;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
box-shadow: none;
|
||||
|
||||
&:focus {
|
||||
border-bottom-color: var(--profile-page-nav-link-active-after-bg-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.fixed-nav {
|
||||
.profile-info-nav-wrap {
|
||||
padding-bottom: $_authorPage-navHeight;
|
||||
}
|
||||
|
||||
.profile-nav {
|
||||
z-index: +2;
|
||||
position: fixed;
|
||||
top: var(--header-height);
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.visible-sidebar & {
|
||||
padding-left: var(--sidebar-width);
|
||||
}
|
||||
|
||||
.sliding-sidebar & {
|
||||
transition-property: padding-left;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-main {
|
||||
overflow: visible;
|
||||
|
||||
#page-profile-media &,
|
||||
#page-profile-about &,
|
||||
#page-profile-playlists &,
|
||||
#page-liked.profile-page-liked &,
|
||||
#page-history.profile-page-history & {
|
||||
padding-bottom: 0;
|
||||
|
||||
.profile-page-content {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-page-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
background-color: var(--profile-page-bg-color);
|
||||
}
|
||||
|
||||
.item-content h3 {
|
||||
span {
|
||||
&:after,
|
||||
&:before {
|
||||
background: var(--profile-page-item-content-title-bg-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#page-profile-about {
|
||||
.items-list-ver.media-list-wrapper {
|
||||
&:first-child {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.media-list-row {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
+ .items-list-ver.media-list-wrapper {
|
||||
.media-list-row {
|
||||
margin-top: 32px;
|
||||
border-top: 1px solid var(--media-list-row-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-details {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
display: block;
|
||||
line-height: 2;
|
||||
margin-bottom: 1em;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 100%;
|
||||
|
||||
&:first-child {
|
||||
width: 160px;
|
||||
line-height: 2.2;
|
||||
font-size: 0.928571429em;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
font-weight: 500;
|
||||
|
||||
> * {
|
||||
display: block;
|
||||
}
|
||||
|
||||
> * + * {
|
||||
&::before {
|
||||
display: block;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.author-social-media {
|
||||
span {
|
||||
display: block;
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-page-content {
|
||||
> * {
|
||||
padding-left: 16px !important;
|
||||
padding-right: 16px !important;
|
||||
|
||||
@media (min-width: 580px) {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
> * {
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-page-content.with-cform {
|
||||
.media-list-row {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-contact {
|
||||
}
|
||||
|
||||
.user-contact-form {
|
||||
position: relative;
|
||||
width: 780px;
|
||||
max-width: 100%;
|
||||
|
||||
&.pending-response {
|
||||
opacity: 0.7;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
textarea {
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0 0 24px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 80px;
|
||||
max-height: 50vh;
|
||||
}
|
||||
|
||||
button {
|
||||
line-height: 1;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 1px;
|
||||
background-color: var(--default-theme-color);
|
||||
}
|
||||
}
|
||||
|
||||
.empty-profile-page-msg {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
$item-width: 218px;
|
||||
$side-empty-space: 40px;
|
||||
|
||||
$-hor-spaces: 2 * $side-empty-space;
|
||||
$-max-width: $-hor-spaces + ( 2 * $item-width ) - 1;
|
||||
|
||||
@media (max-width: $-max-width) {
|
||||
#page-profile-about {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ( ( 2 * $side-empty-space ) + ( 2 * $item-width ) )) and (max-width: 599px) {
|
||||
#page-profile-about {
|
||||
.media-list-wrapper.items-list-ver .media-list-row {
|
||||
width: calc(2 * var(--default-item-width));
|
||||
max-width: calc(2 * var(--default-item-width));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 2 through 6 {
|
||||
$-hor-spaces: 2 * $side-empty-space;
|
||||
$-min-width: $-hor-spaces + ($i * $item-width);
|
||||
@media (min-width: $-min-width) {
|
||||
.profile-page-header .profile-info,
|
||||
.profile-page-header .profile-nav .profile-nav-inner {
|
||||
width: calc(#{$i} * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 3 through 7 {
|
||||
$-hor-spaces: 2 * $side-empty-space;
|
||||
$-min-width: $-hor-spaces + ($i * $item-width);
|
||||
$j: $i - 1;
|
||||
@media (min-width: $-min-width) and (min-width: 768px) {
|
||||
.visible-sidebar .profile-page-header .profile-info,
|
||||
.visible-sidebar .profile-page-header .profile-nav .profile-nav-inner {
|
||||
width: calc( #{$j} * var(--item-width, var(--default-item-width)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$-hor-spaces: 2 * $side-empty-space;
|
||||
$-max-width: $-hor-spaces + ( 2 * $item-width ) - 1;
|
||||
|
||||
@media (max-width: $-max-width ) and (max-width: 709px) {
|
||||
.profile-page-header .profile-nav.fixed-nav .profile-nav {
|
||||
padding-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $-max-width ) and (max-width: 768px) {
|
||||
.profile-page-header .profile-nav.fixed-nav .profile-nav {
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
}
|
||||
11
frontend/src/static/js/components/profile-page/ProfilePagesContent.js
Executable file
11
frontend/src/static/js/components/profile-page/ProfilePagesContent.js
Executable file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
export default class ProfilePagesContent extends React.PureComponent {
|
||||
render() {
|
||||
return this.props.children ? (
|
||||
<div className={'profile-page-content' + (this.props.enabledContactForm ? ' with-cform' : '')}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { usePopup } from '../../utils/hooks/';
|
||||
import { LinksContext, MemberContext, SiteContext } from '../../utils/contexts/';
|
||||
import { PageStore, ProfilePageStore } from '../../utils/stores/';
|
||||
import { PageActions, ProfilePageActions } from '../../utils/actions/';
|
||||
import { CircleIconButton, PopupMain } from '../_shared';
|
||||
import ItemsInlineSlider from '../item-list/includes/itemLists/ItemsInlineSlider';
|
||||
|
||||
class ProfileSearchBar extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
visibleForm: false,
|
||||
queryVal: ProfilePageStore.get('author-query') || '',
|
||||
};
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onInputFocus = this.onInputFocus.bind(this);
|
||||
this.onInputBlur = this.onInputBlur.bind(this);
|
||||
this.showForm = this.showForm.bind(this);
|
||||
this.hideForm = this.hideForm.bind(this);
|
||||
this.onFormSubmit = this.onFormSubmit.bind(this);
|
||||
|
||||
this.updateTimeout = null;
|
||||
this.pendingUpdate = false;
|
||||
}
|
||||
|
||||
updateQuery(value) {
|
||||
this.pendingUpdateValue = null;
|
||||
|
||||
this.setState(
|
||||
{
|
||||
queryVal: value,
|
||||
},
|
||||
function () {
|
||||
if ('function' === typeof this.props.onQueryChange) {
|
||||
this.props.onQueryChange(this.state.queryVal);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onChange(ev) {
|
||||
this.pendingEvent = ev;
|
||||
|
||||
this.setState(
|
||||
{
|
||||
queryVal: ev.target.value || '',
|
||||
},
|
||||
function () {
|
||||
if (this.updateTimeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingEvent = null;
|
||||
|
||||
if ('function' === typeof this.props.onQueryChange) {
|
||||
this.props.onQueryChange(this.state.queryVal);
|
||||
}
|
||||
|
||||
this.updateTimeout = setTimeout(
|
||||
function () {
|
||||
this.updateTimeout = null;
|
||||
|
||||
if (this.pendingEvent) {
|
||||
this.onChange(this.pendingEvent);
|
||||
}
|
||||
}.bind(this),
|
||||
100
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/*onKeydown(e){
|
||||
let found = false, key = e.keyCode || e.charCode;
|
||||
switch( key ){
|
||||
case 38: // Arrow Up.
|
||||
found = this.getItemsArr(this.state.predictionItems.length-1);
|
||||
break;
|
||||
case 40: // Arrow Down.
|
||||
found = this.getItemsArr(0);
|
||||
break;
|
||||
}
|
||||
if( found ){
|
||||
found.focus();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}*/
|
||||
|
||||
onInputFocus() {
|
||||
// console.log('FOCUS');
|
||||
/*if( this.state.predictionItems.length ){
|
||||
this.refs.SearchInput.onkeydown = this.refs.SearchInput.onkeydown || this.onKeydown;
|
||||
}*/
|
||||
}
|
||||
|
||||
onInputBlur() {
|
||||
this.hideForm();
|
||||
}
|
||||
|
||||
showForm() {
|
||||
this.setState(
|
||||
{
|
||||
visibleForm: true,
|
||||
},
|
||||
function () {
|
||||
if ('function' === typeof this.props.toggleSearchField) {
|
||||
this.props.toggleSearchField();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
hideForm() {
|
||||
this.setState(
|
||||
{
|
||||
visibleForm: false,
|
||||
},
|
||||
function () {
|
||||
if ('function' === typeof this.props.toggleSearchField) {
|
||||
this.props.toggleSearchField();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onFormSubmit(ev) {
|
||||
if ('' === this.refs.SearchInput.value.trim()) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.visibleForm) {
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
<CircleIconButton buttonShadow={false} onClick={this.showForm}>
|
||||
<i className="material-icons">search</i>
|
||||
</CircleIconButton>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form method="get" action={LinksContext._currentValue.profile.media} onSubmit={this.onFormSubmit}>
|
||||
<span>
|
||||
<CircleIconButton buttonShadow={false}>
|
||||
<i className="material-icons">search</i>
|
||||
</CircleIconButton>
|
||||
</span>
|
||||
<span>
|
||||
<input
|
||||
autoFocus={true}
|
||||
ref="SearchInput"
|
||||
type="text"
|
||||
name="aq"
|
||||
placeholder="Search"
|
||||
aria-label="Search"
|
||||
value={this.state.queryVal}
|
||||
onChange={this.onChange}
|
||||
onFocus={this.onInputFocus}
|
||||
onBlur={this.onInputBlur}
|
||||
/>
|
||||
</span>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProfileSearchBar.propTypes = {
|
||||
onQueryChange: PropTypes.func,
|
||||
};
|
||||
|
||||
ProfileSearchBar.defaultProps = {};
|
||||
|
||||
function InlineTab(props) {
|
||||
return (
|
||||
<li className={props.isActive ? 'active' : null}>
|
||||
<a href={props.link} title={props.label}>
|
||||
{props.label}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
InlineTab.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
link: PropTypes.string.isRequired,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
class NavMenuInlineTabs extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
displayNext: false,
|
||||
displayPrev: false,
|
||||
};
|
||||
|
||||
this.inlineSlider = null;
|
||||
|
||||
this.nextSlide = this.nextSlide.bind(this);
|
||||
this.prevSlide = this.prevSlide.bind(this);
|
||||
|
||||
this.updateSlider = this.updateSlider.bind(this, false);
|
||||
|
||||
this.onToggleSearchField = this.onToggleSearchField.bind(this);
|
||||
|
||||
PageStore.on('window_resize', this.updateSlider);
|
||||
|
||||
this.sliderRecalTimeout = null;
|
||||
|
||||
PageStore.on(
|
||||
'changed_page_sidebar_visibility',
|
||||
function () {
|
||||
clearTimeout(this.sliderRecalTimeout);
|
||||
|
||||
// NOTE: 200ms is transition duration, set in CSS.
|
||||
this.sliderRecalTimeout = setTimeout(
|
||||
function () {
|
||||
this.updateSliderButtonsView();
|
||||
|
||||
this.sliderRecalTimeout = setTimeout(
|
||||
function () {
|
||||
this.sliderRecalTimeout = null;
|
||||
|
||||
this.updateSlider();
|
||||
}.bind(this),
|
||||
50
|
||||
);
|
||||
}.bind(this),
|
||||
150
|
||||
);
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.previousBtn = (
|
||||
<span className="previous-slide">
|
||||
<CircleIconButton buttonShadow={false} onClick={this.prevSlide}>
|
||||
<i className="material-icons">keyboard_arrow_left</i>
|
||||
</CircleIconButton>
|
||||
</span>
|
||||
);
|
||||
this.nextBtn = (
|
||||
<span className="next-slide">
|
||||
<CircleIconButton buttonShadow={false} onClick={this.nextSlide}>
|
||||
<i className="material-icons">keyboard_arrow_right</i>
|
||||
</CircleIconButton>
|
||||
</span>
|
||||
);
|
||||
|
||||
this.userIsAuthor =
|
||||
!MemberContext._currentValue.is.anonymous &&
|
||||
ProfilePageStore.get('author-data').username === MemberContext._currentValue.username;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateSlider();
|
||||
}
|
||||
|
||||
nextSlide() {
|
||||
this.inlineSlider.nextSlide();
|
||||
this.updateSliderButtonsView();
|
||||
this.inlineSlider.scrollToCurrentSlide();
|
||||
}
|
||||
|
||||
prevSlide() {
|
||||
this.inlineSlider.previousSlide();
|
||||
this.updateSliderButtonsView();
|
||||
this.inlineSlider.scrollToCurrentSlide();
|
||||
}
|
||||
|
||||
updateSlider(afterItemsUpdate) {
|
||||
if (!this.inlineSlider) {
|
||||
this.inlineSlider = new ItemsInlineSlider(this.refs.itemsListWrap, '.profile-nav ul li');
|
||||
}
|
||||
|
||||
this.inlineSlider.updateDataState(document.querySelectorAll('.profile-nav ul li').length, true, !afterItemsUpdate);
|
||||
|
||||
this.updateSliderButtonsView();
|
||||
|
||||
if (this.pendingChangeSlide) {
|
||||
this.pendingChangeSlide = false;
|
||||
this.inlineSlider.scrollToCurrentSlide();
|
||||
}
|
||||
}
|
||||
|
||||
updateSliderButtonsView() {
|
||||
this.setState({
|
||||
displayPrev: this.inlineSlider.hasPreviousSlide(),
|
||||
displayNext: this.inlineSlider.hasNextSlide(),
|
||||
});
|
||||
}
|
||||
|
||||
onToggleSearchField() {
|
||||
this.updateSlider();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<nav ref="tabsNav" className="profile-nav items-list-outer list-inline list-slider">
|
||||
<div className="profile-nav-inner items-list-outer">
|
||||
{this.state.displayPrev ? this.previousBtn : null}
|
||||
|
||||
<ul className="items-list-wrap" ref="itemsListWrap">
|
||||
<InlineTab
|
||||
id="about"
|
||||
isActive={'about' === this.props.type}
|
||||
label={'About' + (this.userIsAuthor ? ' Me' : '')}
|
||||
link={LinksContext._currentValue.profile.about}
|
||||
/>
|
||||
<InlineTab
|
||||
id="media"
|
||||
isActive={'media' === this.props.type}
|
||||
label={(this.userIsAuthor ? 'My ' : '') + 'Media'}
|
||||
link={LinksContext._currentValue.profile.media}
|
||||
/>
|
||||
|
||||
{MemberContext._currentValue.can.saveMedia ? (
|
||||
<InlineTab
|
||||
id="playlists"
|
||||
isActive={'playlists' === this.props.type}
|
||||
label={(this.userIsAuthor ? 'My ' : '') + 'Playlists'}
|
||||
link={LinksContext._currentValue.profile.playlists}
|
||||
/>
|
||||
) : null}
|
||||
{PageStore.get('config-options').pages.profile.includeHistory && this.userIsAuthor ? (
|
||||
<InlineTab
|
||||
id="history"
|
||||
isActive={'history' === this.props.type}
|
||||
label={PageStore.get('config-enabled').pages.history.title}
|
||||
link={LinksContext._currentValue.user.history}
|
||||
/>
|
||||
) : null}
|
||||
{PageStore.get('config-options').pages.profile.includeLikedMedia && this.userIsAuthor ? (
|
||||
<InlineTab
|
||||
id="liked"
|
||||
isActive={'liked' === this.props.type}
|
||||
label={PageStore.get('config-enabled').pages.liked.title}
|
||||
link={LinksContext._currentValue.user.liked}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<li className="media-search">
|
||||
<ProfileSearchBar onQueryChange={this.props.onQueryChange} toggleSearchField={this.onToggleSearchField} />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{this.state.displayNext ? this.nextBtn : null}
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NavMenuInlineTabs.propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
onQueryChange: PropTypes.func,
|
||||
};
|
||||
|
||||
function AddBannerButton(props) {
|
||||
let link = props.link;
|
||||
|
||||
if (window.MediaCMS.site.devEnv) {
|
||||
link = '/edit-channel.html';
|
||||
}
|
||||
return (
|
||||
<a href={link} className="edit-channel" title="Add banner">
|
||||
ADD BANNER
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function EditBannerButton(props) {
|
||||
let link = props.link;
|
||||
|
||||
if (window.MediaCMS.site.devEnv) {
|
||||
link = '/edit-channel.html';
|
||||
}
|
||||
return (
|
||||
<a href={link} className="edit-channel" title="Edit banner">
|
||||
EDIT BANNER
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function EditProfileButton(props) {
|
||||
let link = props.link;
|
||||
|
||||
if (window.MediaCMS.site.devEnv) {
|
||||
link = '/edit-profile.html';
|
||||
}
|
||||
|
||||
return (
|
||||
<a href={link} className="edit-profile" title="Edit profile">
|
||||
EDIT PROFILE
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProfilePagesHeader(props) {
|
||||
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
||||
|
||||
const profilePageHeaderRef = useRef(null);
|
||||
const profileNavRef = useRef(null);
|
||||
|
||||
const [fixedNav, setFixedNav] = useState(false);
|
||||
|
||||
const positions = {
|
||||
profileNavTop: 0,
|
||||
};
|
||||
|
||||
const userIsAdmin = !MemberContext._currentValue.is.anonymous && MemberContext._currentValue.is.admin;
|
||||
const userIsAuthor =
|
||||
!MemberContext._currentValue.is.anonymous &&
|
||||
ProfilePageStore.get('author-data').username === MemberContext._currentValue.username;
|
||||
const userCanEditProfile =
|
||||
userIsAuthor || (!MemberContext._currentValue.is.anonymous && MemberContext._currentValue.can.editProfile);
|
||||
const userCanDeleteProfile =
|
||||
userIsAdmin ||
|
||||
userIsAuthor ||
|
||||
(!MemberContext._currentValue.is.anonymous && MemberContext._currentValue.can.deleteProfile);
|
||||
|
||||
function updateProfileNavTopPosition() {
|
||||
positions.profileHeaderTop = profilePageHeaderRef.current.offsetTop;
|
||||
positions.profileNavTop =
|
||||
positions.profileHeaderTop +
|
||||
profilePageHeaderRef.current.offsetHeight -
|
||||
profileNavRef.current.refs.tabsNav.offsetHeight;
|
||||
}
|
||||
|
||||
function updateFixedNavPosition() {
|
||||
setFixedNav(positions.profileHeaderTop + window.scrollY > positions.profileNavTop);
|
||||
}
|
||||
|
||||
function cancelProfileRemoval() {
|
||||
popupContentRef.current.toggle();
|
||||
}
|
||||
|
||||
function proceedMediaRemoval() {
|
||||
ProfilePageActions.remove_profile();
|
||||
popupContentRef.current.toggle();
|
||||
}
|
||||
|
||||
function onProfileDelete(username) {
|
||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||
setTimeout(function () {
|
||||
PageActions.addNotification('Profile removed. Redirecting...', 'profileDelete');
|
||||
setTimeout(function () {
|
||||
window.location.href = SiteContext._currentValue.url;
|
||||
}, 2000);
|
||||
}, 100);
|
||||
|
||||
if (void 0 !== username) {
|
||||
console.info("Removed user's profile '" + username + '"');
|
||||
}
|
||||
}
|
||||
|
||||
function onProfileDeleteFail(username) {
|
||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||
setTimeout(function () {
|
||||
PageActions.addNotification('Profile removal failed', 'profileDeleteFail');
|
||||
}, 100);
|
||||
|
||||
if (void 0 !== username) {
|
||||
console.info('Profile "' + username + '"' + ' removal failed');
|
||||
}
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
updateProfileNavTopPosition();
|
||||
updateFixedNavPosition();
|
||||
}
|
||||
|
||||
function onWindowScroll() {
|
||||
updateFixedNavPosition();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (userCanDeleteProfile) {
|
||||
ProfilePageStore.on('profile_delete', onProfileDelete);
|
||||
ProfilePageStore.on('profile_delete_fail', onProfileDeleteFail);
|
||||
}
|
||||
|
||||
PageStore.on('resize', onWindowResize);
|
||||
PageStore.on('changed_page_sidebar_visibility', onWindowResize);
|
||||
PageStore.on('window_scroll', onWindowScroll);
|
||||
|
||||
updateProfileNavTopPosition();
|
||||
updateFixedNavPosition();
|
||||
|
||||
return () => {
|
||||
if (userCanDeleteProfile) {
|
||||
ProfilePageStore.removeListener('profile_delete', onProfileDelete);
|
||||
ProfilePageStore.removeListener('profile_delete_fail', onProfileDeleteFail);
|
||||
}
|
||||
|
||||
PageStore.removeListener('resize', onWindowResize);
|
||||
PageStore.removeListener('changed_page_sidebar_visibility', onWindowResize);
|
||||
PageStore.removeListener('window_scroll', onWindowScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={profilePageHeaderRef} className={'profile-page-header' + (fixedNav ? ' fixed-nav' : '')}>
|
||||
<span className="profile-banner-wrap">
|
||||
{props.author.banner_thumbnail_url ? (
|
||||
<span
|
||||
className="profile-banner"
|
||||
style={{
|
||||
backgroundImage:
|
||||
'url(' +
|
||||
SiteContext._currentValue.url +
|
||||
'/' +
|
||||
props.author.banner_thumbnail_url.replace(/^\//g, '') +
|
||||
')',
|
||||
}}
|
||||
></span>
|
||||
) : null}
|
||||
|
||||
{userCanDeleteProfile ? (
|
||||
<span className="delete-profile-wrap">
|
||||
<PopupTrigger contentRef={popupContentRef}>
|
||||
<button className="delete-profile" title="">
|
||||
REMOVE PROFILE
|
||||
</button>
|
||||
</PopupTrigger>
|
||||
|
||||
<PopupContent contentRef={popupContentRef}>
|
||||
<PopupMain>
|
||||
<div className="popup-message">
|
||||
<span className="popup-message-title">Profile removal</span>
|
||||
<span className="popup-message-main">You're willing to remove profile permanently?</span>
|
||||
</div>
|
||||
<hr />
|
||||
<span className="popup-message-bottom">
|
||||
<button className="button-link cancel-profile-removal" onClick={cancelProfileRemoval}>
|
||||
CANCEL
|
||||
</button>
|
||||
<button className="button-link proceed-profile-removal" onClick={proceedMediaRemoval}>
|
||||
PROCEED
|
||||
</button>
|
||||
</span>
|
||||
</PopupMain>
|
||||
</PopupContent>
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{userCanEditProfile ? (
|
||||
props.author.banner_thumbnail_url ? (
|
||||
<EditBannerButton link={ProfilePageStore.get('author-data').default_channel_edit_url} />
|
||||
) : (
|
||||
<AddBannerButton link={ProfilePageStore.get('author-data').default_channel_edit_url} />
|
||||
)
|
||||
) : null}
|
||||
</span>
|
||||
|
||||
<div className="profile-info-nav-wrap">
|
||||
{props.author.thumbnail_url || props.author.name ? (
|
||||
<div className="profile-info">
|
||||
<div className="profile-info-inner">
|
||||
<div>{props.author.thumbnail_url ? <img src={props.author.thumbnail_url} alt="" /> : null}</div>
|
||||
<div>
|
||||
{props.author.name ? <h1>{props.author.name}</h1> : null}
|
||||
{userCanEditProfile ? <EditProfileButton link={ProfilePageStore.get('author-data').edit_url} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<NavMenuInlineTabs ref={profileNavRef} type={props.type} onQueryChange={props.onQueryChange} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ProfilePagesHeader.propTypes = {
|
||||
author: PropTypes.object.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
onQueryChange: PropTypes.func,
|
||||
};
|
||||
|
||||
ProfilePagesHeader.defaultProps = {
|
||||
type: 'media',
|
||||
};
|
||||
Reference in New Issue
Block a user