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:
Yiannis Stergiou
2021-07-11 18:01:34 +03:00
committed by GitHub
parent 060bb45725
commit aa6520daac
555 changed files with 201927 additions and 66002 deletions

View File

@@ -0,0 +1,26 @@
import React from 'react';
import { ApiUrlConsumer } from '../utils/contexts/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
import { Page } from './Page';
interface CategoriesPageProps {
id?: string;
title?: string;
}
export const CategoriesPage: React.FC<CategoriesPageProps> = ({ id = 'categories', title = 'Categories' }) => (
<Page id={id}>
<ApiUrlConsumer>
{(apiUrl) => (
<MediaListWrapper title={title} className="items-list-ver">
<LazyLoadItemListAsync
singleLinkContent={true}
inCategoriesList={true}
requestUrl={apiUrl.archive.categories}
/>
</MediaListWrapper>
)}
</ApiUrlConsumer>
</Page>
);

View File

@@ -0,0 +1,68 @@
import React, { useState, useEffect, CSSProperties } from 'react';
import { SiteConsumer } from '../utils/contexts/';
import { MediaPageStore } from '../utils/stores/';
import { MediaPageActions } from '../utils/actions/';
import VideoViewer from '../components/media-viewer/VideoViewer';
const wrapperStyles = {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
display: 'block',
} as CSSProperties;
const containerStyles = {
width: '100%',
height: '100%',
} as CSSProperties;
export const EmbedPage: React.FC = () => {
const [loadedVideo, setLoadedVideo] = useState(false);
const [failedMediaLoad, setFailedMediaLoad] = useState(false);
const onLoadedVideoData = () => {
setLoadedVideo(true);
};
const onMediaLoadError = () => {
setFailedMediaLoad(true);
};
useEffect(() => {
MediaPageStore.on('loaded_video_data', onLoadedVideoData);
MediaPageStore.on('loaded_media_error', onMediaLoadError);
MediaPageActions.loadMediaData();
return () => {
MediaPageStore.removeListener('loaded_video_data', onLoadedVideoData);
MediaPageStore.removeListener('loaded_media_error', onMediaLoadError);
};
}, []);
return (
<div className="embed-wrap" style={wrapperStyles}>
{failedMediaLoad && (
<div className="player-container player-container-error" style={containerStyles}>
<div className="player-container-inner" style={containerStyles}>
<div className="error-container">
<div className="error-container-inner">
<span className="icon-wrap">
<i className="material-icons">error_outline</i>
</span>
<span className="msg-wrap">{MediaPageStore.get('media-load-error-message')}</span>
</div>
</div>
</div>
</div>
)}
{loadedVideo && (
<SiteConsumer>
{(site) => (
<VideoViewer data={MediaPageStore.get('media-data')} siteUrl={site.url} containerStyles={containerStyles} />
)}
</SiteConsumer>
)}
</div>
);
};

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
import { Page } from './Page';
interface FeaturedMediaPageProps {
id?: string;
title?: string;
}
export const FeaturedMediaPage: React.FC<FeaturedMediaPageProps> = ({
id = 'featured-media',
title = PageStore.get('config-enabled').pages.featured.title,
}) => (
<Page id={id}>
<ApiUrlConsumer>
{(apiUrl) => (
<MediaListWrapper title={title} className="items-list-ver">
<LazyLoadItemListAsync
requestUrl={apiUrl.featured}
hideViews={!PageStore.get('config-media-item').displayViews}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
/>
</MediaListWrapper>
)}
</ApiUrlConsumer>
</Page>
);

View File

@@ -0,0 +1,62 @@
import React, { useState } from 'react';
import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/';
import { useUser } from '../utils/hooks/';
import { addClassname } from '../utils/helpers/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
import { ProfileHistoryPage } from './ProfileHistoryPage';
import { Page } from './Page';
declare global {
interface Window {
MediaCMS: any;
}
}
interface AnonymousHistoryPageProps {
id?: string;
title?: string;
}
export const AnonymousHistoryPage: React.FC<AnonymousHistoryPageProps> = ({
id = 'history-media',
title = PageStore.get('config-enabled').pages.history.title,
}) => {
const [resultsCount, setResultsCount] = useState<number | null>(null);
return (
<Page id={id}>
<ApiUrlConsumer>
{(apiUrl) => (
<MediaListWrapper
title={title + (null !== resultsCount ? ' (' + resultsCount + ')' : '')}
className="search-results-wrap items-list-hor"
>
<LazyLoadItemListAsync
singleLinkContent={false}
horizontalItemsOrientation={true}
itemsCountCallback={setResultsCount}
requestUrl={apiUrl.user.history}
hideViews={!PageStore.get('config-media-item').displayViews}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
/>
</MediaListWrapper>
)}
</ApiUrlConsumer>
</Page>
);
};
export const HistoryPage: React.FC = () => {
const { username, isAnonymous } = useUser();
const anonymousPage = isAnonymous || !PageStore.get('config-options').pages.profile.includeHistory;
if (!anonymousPage) {
addClassname(document.getElementById('page-history'), 'profile-page-history');
window.MediaCMS.profileId = username;
}
return anonymousPage ? <AnonymousHistoryPage /> : <ProfileHistoryPage />;
};

View File

@@ -0,0 +1,127 @@
import React, { useState } from 'react';
import { ApiUrlConsumer, LinksConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/';
import { MediaListRow } from '../components/MediaListRow';
import { MediaMultiListWrapper } from '../components/MediaMultiListWrapper';
import { ItemListAsync } from '../components/item-list/ItemListAsync.jsx';
import { InlineSliderItemListAsync } from '../components/item-list/InlineSliderItemListAsync.jsx';
import { Page } from './Page';
const EmptyMedia: React.FC = ({}) => {
return (
<LinksConsumer>
{(links) => (
<div className="empty-media">
<div className="welcome-title">Welcome to MediaCMS!</div>
<div className="start-uploading">Start uploading media and sharing your work!</div>
<a href={links.user.addMedia} title="Upload media" className="button-link">
<i className="material-icons" data-icon="video_call"></i>UPLOAD MEDIA
</a>
</div>
)}
</LinksConsumer>
);
};
interface HomePageProps {
id?: string;
latest_title: string;
featured_title: string;
recommended_title: string;
latest_view_all_link: boolean;
featured_view_all_link: boolean;
recommended_view_all_link: boolean;
}
export const HomePage: React.FC<HomePageProps> = ({
id = 'home',
featured_title = PageStore.get('config-options').pages.home.sections.featured.title,
recommended_title = PageStore.get('config-options').pages.home.sections.recommended.title,
latest_title = PageStore.get('config-options').pages.home.sections.latest.title,
latest_view_all_link = false,
featured_view_all_link = true,
recommended_view_all_link = true,
}) => {
const [zeroMedia, setZeroMedia] = useState(false);
const [visibleLatest, setVisibleLatest] = useState(false);
const [visibleFeatured, setVisibleFeatured] = useState(false);
const [visibleRecommended, setVisibleRecommended] = useState(false);
const onLoadLatest = (length: number) => {
setVisibleLatest(0 < length);
setZeroMedia(0 === length);
};
const onLoadFeatured = (length: number) => {
setVisibleFeatured(0 < length);
};
const onLoadRecommended = (length: number) => {
setVisibleRecommended(0 < length);
};
return (
<Page id={id}>
<LinksConsumer>
{(links) => (
<ApiUrlConsumer>
{(apiUrl) => (
<MediaMultiListWrapper className="items-list-ver">
{PageStore.get('config-enabled').pages.featured &&
PageStore.get('config-enabled').pages.featured.enabled && (
<MediaListRow
title={featured_title}
style={!visibleFeatured ? { display: 'none' } : undefined}
viewAllLink={featured_view_all_link ? links.featured : null}
>
<InlineSliderItemListAsync
requestUrl={apiUrl.featured}
itemsCountCallback={onLoadFeatured}
hideViews={!PageStore.get('config-media-item').displayViews}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
/>
</MediaListRow>
)}
{PageStore.get('config-enabled').pages.recommended &&
PageStore.get('config-enabled').pages.recommended.enabled && (
<MediaListRow
title={recommended_title}
style={!visibleRecommended ? { display: 'none' } : undefined}
viewAllLink={recommended_view_all_link ? links.recommended : null}
>
<InlineSliderItemListAsync
requestUrl={apiUrl.recommended}
itemsCountCallback={onLoadRecommended}
hideViews={!PageStore.get('config-media-item').displayViews}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
/>
</MediaListRow>
)}
<MediaListRow
title={latest_title}
style={!visibleLatest ? { display: 'none' } : undefined}
viewAllLink={latest_view_all_link ? links.latest : null}
>
<ItemListAsync
pageItems={30}
requestUrl={apiUrl.media}
itemsCountCallback={onLoadLatest}
hideViews={!PageStore.get('config-media-item').displayViews}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
/>
</MediaListRow>
{zeroMedia && <EmptyMedia />}
</MediaMultiListWrapper>
)}
</ApiUrlConsumer>
)}
</LinksConsumer>
</Page>
);
};

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync';
import { Page } from './Page';
interface LatestMediaPageProps {
id?: string;
title?: string;
}
export const LatestMediaPage: React.FC<LatestMediaPageProps> = ({
id = 'latest-media',
title = PageStore.get('config-enabled').pages.latest.title,
}) => (
<Page id={id}>
<ApiUrlConsumer>
{(apiUrl) => (
<MediaListWrapper title={title} className="items-list-ver">
<LazyLoadItemListAsync
requestUrl={apiUrl.media}
hideViews={!PageStore.get('config-media-item').displayViews}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
/>
</MediaListWrapper>
)}
</ApiUrlConsumer>
</Page>
);

View File

@@ -0,0 +1,62 @@
import React, { useState } from 'react';
import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/';
import { useUser } from '../utils/hooks/';
import { addClassname } from '../utils/helpers/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync';
import { ProfileLikedPage } from './ProfileLikedPage';
import { Page } from './Page';
declare global {
interface Window {
MediaCMS: any;
}
}
interface AnonymousLikedMediaPageProps {
id?: string;
title?: string;
}
export const AnonymousLikedMediaPage: React.FC<AnonymousLikedMediaPageProps> = ({
id = 'liked-media',
title = PageStore.get('config-enabled').pages.liked.title,
}) => {
const [resultsCount, setResultsCount] = useState<number | null>(null);
return (
<Page id={id}>
<ApiUrlConsumer>
{(apiUrl) => (
<MediaListWrapper
title={title + (null !== resultsCount ? ' (' + resultsCount + ')' : '')}
className="search-results-wrap items-list-hor"
>
<LazyLoadItemListAsync
singleLinkContent={false}
horizontalItemsOrientation={true}
itemsCountCallback={setResultsCount}
requestUrl={apiUrl.user.liked}
hideViews={!PageStore.get('config-media-item').displayViews}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
/>
</MediaListWrapper>
)}
</ApiUrlConsumer>
</Page>
);
};
export const LikedMediaPage: React.FC = () => {
const { username, isAnonymous } = useUser();
const anonymousPage = isAnonymous || !PageStore.get('config-options').pages.profile.includeLikedMedia;
if (!anonymousPage) {
addClassname(document.getElementById('page-liked'), 'profile-page-liked');
window.MediaCMS.profileId = username;
}
return anonymousPage ? <AnonymousLikedMediaPage /> : <ProfileLikedPage />;
};

View File

@@ -0,0 +1,113 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ApiUrlContext } from '../utils/contexts/';
import { PageActions } from '../utils/actions/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { ManageItemList } from '../components/management-table/ManageItemList/ManageItemList';
import { Page } from './_Page';
function genReqUrl(url, sort, page) {
const ret = url + '?' + sort + ('' === sort ? '' : '&') + 'page=' + page;
return ret;
}
export class ManageCommentsPage extends Page {
constructor(props) {
super(props, 'manage-comments');
this.state = {
resultsCount: null,
requestUrl: ApiUrlContext._currentValue.manage.comments,
currentPage: 1,
sortingArgs: '',
sortBy: 'add_date',
ordering: 'desc',
refresh: 0,
};
this.getCountFunc = this.getCountFunc.bind(this);
this.onTablePageChange = this.onTablePageChange.bind(this);
this.onColumnSortClick = this.onColumnSortClick.bind(this);
this.onItemsRemoval = this.onItemsRemoval.bind(this);
this.onItemsRemovalFail = this.onItemsRemovalFail.bind(this);
}
onTablePageChange(newPageUrl, updatedPage) {
this.setState({
currentPage: updatedPage,
requestUrl: genReqUrl(ApiUrlContext._currentValue.manage.comments, this.state.sortingArgs, updatedPage),
});
}
getCountFunc(resultsCount) {
this.setState({
resultsCount: resultsCount,
});
}
onColumnSortClick(sort, order) {
const newArgs = 'sort_by=' + sort + '&ordering=' + order;
this.setState({
sortBy: sort,
ordering: order,
sortingArgs: newArgs,
requestUrl: genReqUrl(ApiUrlContext._currentValue.manage.comments, newArgs, this.state.currentPage),
});
}
onItemsRemoval(multipleItems) {
this.setState(
{
resultsCount: null,
refresh: this.state.refresh + 1,
requestUrl: ApiUrlContext._currentValue.manage.comments,
},
function () {
if (multipleItems) {
PageActions.addNotification('The comments deleted successfully.', 'commentsRemovalSucceed');
} else {
PageActions.addNotification('The comment deleted successfully.', 'commentRemovalSucceed');
}
}
);
}
onItemsRemovalFail(multipleItems) {
if (multipleItems) {
PageActions.addNotification('The comments removal failed. Please try again.', 'commentsRemovalFailed');
} else {
PageActions.addNotification('The comment removal failed. Please try again.', 'commentRemovalFailed');
}
}
pageContent() {
return (
<MediaListWrapper
title={this.props.title + (null === this.state.resultsCount ? '' : ' (' + this.state.resultsCount + ')')}
className="search-results-wrap items-list-hor"
>
<ManageItemList
pageItems={50}
manageType={'comments'}
key={this.state.requestUrl + '[' + this.state.refresh + ']'}
itemsCountCallback={this.getCountFunc}
requestUrl={this.state.requestUrl}
onPageChange={this.onTablePageChange}
sortBy={this.state.sortBy}
ordering={this.state.ordering}
onRowsDelete={this.onItemsRemoval}
onRowsDeleteFail={this.onItemsRemovalFail}
onClickColumnSort={this.onColumnSortClick}
/>
</MediaListWrapper>
);
}
}
ManageCommentsPage.propTypes = {
title: PropTypes.string.isRequired,
};
ManageCommentsPage.defaultProps = {
title: 'Manage comments',
};

View File

@@ -0,0 +1,151 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ApiUrlContext } from '../utils/contexts/';
import { PageActions } from '../utils/actions/';
import { FiltersToggleButton } from '../components/_shared';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { ManageMediaFilters } from '../components/management-table/ManageMediaFilters.jsx';
import { ManageItemList } from '../components/management-table/ManageItemList/ManageItemList';
import { Page } from './_Page';
function genReqUrl(url, filters, sort, page) {
const ret = url + '?' + filters + ('' === filters ? '' : '&') + sort + ('' === sort ? '' : '&') + 'page=' + page;
return ret;
}
export class ManageMediaPage extends Page {
constructor(props) {
super(props, 'manage-media');
this.state = {
resultsCount: null,
currentPage: 1,
requestUrl: ApiUrlContext._currentValue.manage.media,
pageTitle: props.title,
hiddenFilters: true,
filterArgs: '',
sortingArgs: '',
sortBy: 'add_date',
ordering: 'desc',
refresh: 0,
};
this.getCountFunc = this.getCountFunc.bind(this);
this.onTablePageChange = this.onTablePageChange.bind(this);
this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this);
this.onFiltersUpdate = this.onFiltersUpdate.bind(this);
this.onColumnSortClick = this.onColumnSortClick.bind(this);
this.onItemsRemoval = this.onItemsRemoval.bind(this);
this.onItemsRemovalFail = this.onItemsRemovalFail.bind(this);
}
onTablePageChange(newPageUrl, updatedPage) {
this.setState({
currentPage: updatedPage,
requestUrl: genReqUrl(
ApiUrlContext._currentValue.manage.media,
this.state.filterArgs,
this.state.sortingArgs,
updatedPage
),
});
}
onToggleFiltersClick() {
this.setState({
hiddenFilters: !this.state.hiddenFilters,
});
}
getCountFunc(resultsCount) {
this.setState({
resultsCount: resultsCount,
pageTitle: this.state.pageTitle,
});
}
onFiltersUpdate(updatedArgs) {
const newArgs = [];
for (let arg in updatedArgs) {
if (null !== updatedArgs[arg] && 'all' !== updatedArgs[arg]) {
newArgs.push(arg + '=' + updatedArgs[arg]);
}
}
this.setState({
filterArgs: newArgs.join('&'),
requestUrl: genReqUrl(
ApiUrlContext._currentValue.manage.media,
newArgs.join('&'),
this.state.sortingArgs,
this.state.currentPage
),
});
}
onColumnSortClick(sort, order) {
const newArgs = 'sort_by=' + sort + '&ordering=' + order;
this.setState({
sortBy: sort,
ordering: order,
sortingArgs: newArgs,
requestUrl: genReqUrl(
ApiUrlContext._currentValue.manage.media,
this.state.filterArgs,
newArgs,
this.state.currentPage
),
});
}
onItemsRemoval(multipleItems) {
this.setState(
{
resultsCount: null,
refresh: this.state.refresh + 1,
requestUrl: ApiUrlContext._currentValue.manage.media,
},
function () {
PageActions.addNotification('The media deleted successfully.', 'mediaRemovalSucceed');
}
);
}
onItemsRemovalFail(multipleItems) {
PageActions.addNotification('The media removal failed. Please try again.', 'mediaRemovalFailed');
}
pageContent() {
return (
<MediaListWrapper
title={this.state.pageTitle + (null === this.state.resultsCount ? '' : ' (' + this.state.resultsCount + ')')}
className=""
>
<FiltersToggleButton onClick={this.onToggleFiltersClick} />
<ManageMediaFilters hidden={this.state.hiddenFilters} onFiltersUpdate={this.onFiltersUpdate} />
<ManageItemList
pageItems={50}
manageType={'media'}
key={this.state.requestUrl + '[' + this.state.refresh + ']'}
requestUrl={this.state.requestUrl}
itemsCountCallback={this.getCountFunc}
onPageChange={this.onTablePageChange}
sortBy={this.state.sortBy}
ordering={this.state.ordering}
onRowsDelete={this.onItemsRemoval}
onRowsDeleteFail={this.onItemsRemovalFail}
onClickColumnSort={this.onColumnSortClick}
/>
</MediaListWrapper>
);
}
}
ManageMediaPage.propTypes = {
title: PropTypes.string.isRequired,
};
ManageMediaPage.defaultProps = {
title: 'Manage media',
};

View File

@@ -0,0 +1,169 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ApiUrlContext } from '../utils/contexts/';
import { PageActions } from '../utils/actions/';
import { FiltersToggleButton } from '../components/_shared';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { ManageUsersFilters } from '../components/management-table/ManageUsersFilters.jsx';
import { ManageItemList } from '../components/management-table/ManageItemList/ManageItemList';
import { Page } from './_Page';
function genReqUrl(url, filters, sort, page) {
const ret = url + '?' + filters + ('' === filters ? '' : '&') + sort + ('' === sort ? '' : '&') + 'page=' + page;
return ret;
}
export class ManageUsersPage extends Page {
constructor(props) {
super(props, 'manage-users');
this.state = {
resultsCount: null,
currentPage: 1,
requestUrl: ApiUrlContext._currentValue.manage.users,
hiddenFilters: true,
filterArgs: '',
sortingArgs: '',
sortBy: 'add_date',
ordering: 'desc',
refresh: 0,
};
this.getCountFunc = this.getCountFunc.bind(this);
this.onTablePageChange = this.onTablePageChange.bind(this);
this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this);
this.onFiltersUpdate = this.onFiltersUpdate.bind(this);
this.onColumnSortClick = this.onColumnSortClick.bind(this);
this.onItemsRemoval = this.onItemsRemoval.bind(this);
this.onItemsRemovalFail = this.onItemsRemovalFail.bind(this);
}
onTablePageChange(newPageUrl, updatedPage) {
this.setState({
currentPage: updatedPage,
requestUrl: genReqUrl(
ApiUrlContext._currentValue.manage.users,
this.state.filterArgs,
this.state.sortingArgs,
updatedPage
),
});
}
onToggleFiltersClick() {
this.setState({
hiddenFilters: !this.state.hiddenFilters,
});
}
getCountFunc(resultsCount) {
this.setState({
resultsCount: resultsCount,
});
}
onFiltersUpdate(updatedArgs) {
// console.log( "==>", updatedArgs );
const newArgs = [];
for (let arg in updatedArgs) {
if (null !== updatedArgs[arg] && 'all' !== updatedArgs[arg]) {
newArgs.push(arg + '=' + updatedArgs[arg]);
}
}
// console.log( ApiUrlContext._currentValue.manage.users + ( newArgs.length ? '?' + newArgs.join('&') : '' ) );
/*if( 1 === this.state.currentPage ){*/
this.setState({
filterArgs: newArgs.join('&'),
requestUrl: genReqUrl(
ApiUrlContext._currentValue.manage.users,
newArgs.join('&'),
this.state.sortingArgs,
this.state.currentPage
),
});
/*}
else{
this.setState({
filterArgs: newArgs.join('&'),
requestUrl: ApiUrlContext._currentValue.manage.users + ( newArgs.length ? '?' + newArgs.join('&') : '' ) + '&page=' + this.state.currentPage,
});
}*/
}
onColumnSortClick(sort, order) {
const newArgs = 'sort_by=' + sort + '&ordering=' + order;
this.setState({
sortBy: sort,
ordering: order,
sortingArgs: newArgs,
requestUrl: genReqUrl(
ApiUrlContext._currentValue.manage.users,
this.state.filterArgs,
newArgs,
this.state.currentPage
),
});
}
onItemsRemoval(multipleItems) {
this.setState(
{
resultsCount: null,
refresh: this.state.refresh + 1,
requestUrl: ApiUrlContext._currentValue.manage.users,
},
function () {
if (multipleItems) {
PageActions.addNotification('The users deleted successfully.', 'usersRemovalSucceed');
} else {
PageActions.addNotification('The user deleted successfully.', 'userRemovalSucceed');
}
}
);
}
onItemsRemovalFail(multipleItems) {
if (multipleItems) {
PageActions.addNotification('The users removal failed. Please try again.', 'usersRemovalFailed');
} else {
PageActions.addNotification('The user removal failed. Please try again.', 'userRemovalFailed');
}
}
pageContent() {
return [
<MediaListWrapper
key="2"
title={this.props.title + (null === this.state.resultsCount ? '' : ' (' + this.state.resultsCount + ')')}
>
<FiltersToggleButton onClick={this.onToggleFiltersClick} />
<ManageUsersFilters hidden={this.state.hiddenFilters} onFiltersUpdate={this.onFiltersUpdate} />
<ManageItemList
pageItems={50}
manageType={'users'}
key={this.state.requestUrl + '[' + this.state.refresh + ']'}
itemsCountCallback={this.getCountFunc}
requestUrl={this.state.requestUrl}
onPageChange={this.onTablePageChange}
sortBy={this.state.sortBy}
ordering={this.state.ordering}
onRowsDelete={this.onItemsRemoval}
onRowsDeleteFail={this.onItemsRemovalFail}
onClickColumnSort={this.onColumnSortClick}
/>
</MediaListWrapper>,
];
}
}
ManageUsersPage.propTypes = {
title: PropTypes.string.isRequired,
};
ManageUsersPage.defaultProps = {
title: 'Manage users',
};

View File

@@ -0,0 +1,13 @@
import React from 'react';
import AudioViewer from '../components/media-viewer/AudioViewer';
import { _MediaPage } from './_MediaPage';
export class MediaAudioPage extends _MediaPage {
viewerContainerContent() {
return <AudioViewer />;
}
mediaType() {
return 'audio';
}
}

View File

@@ -0,0 +1,13 @@
import React from 'react';
import ImageViewer from '../components/media-viewer/ImageViewer';
import { _MediaPage } from './_MediaPage';
export class MediaImagePage extends _MediaPage {
viewerContainerContent() {
return <ImageViewer />;
}
mediaType() {
return 'image';
}
}

View File

@@ -0,0 +1,60 @@
import React from 'react';
import { SiteConsumer } from '../utils/contexts/';
import { MediaPageStore } from '../utils/stores/';
import AttachmentViewer from '../components/media-viewer/AttachmentViewer';
import AudioViewer from '../components/media-viewer/AudioViewer';
import ImageViewer from '../components/media-viewer/ImageViewer';
import PdfViewer from '../components/media-viewer/PdfViewer';
import VideoViewer from '../components/media-viewer/VideoViewer';
import { _VideoMediaPage } from './_VideoMediaPage';
if (window.MediaCMS.site.devEnv) {
const extractUrlParams = () => {
let mediaId = null;
let playlistId = null;
const query = window.location.search.split('?')[1];
if (query) {
const params = query.split('&');
params.forEach((param) => {
if (0 === param.indexOf('m=')) {
mediaId = param.split('m=')[1];
} else if (0 === param.indexOf('pl=')) {
playlistId = param.split('pl=')[1];
}
});
}
return { mediaId, playlistId };
};
const { mediaId, playlistId } = extractUrlParams();
if (mediaId) {
window.MediaCMS.mediaId = mediaId;
}
if (playlistId) {
window.MediaCMS.playlistId = playlistId;
}
}
export class MediaPage extends _VideoMediaPage {
viewerContainerContent(mediaData) {
switch (MediaPageStore.get('media-type')) {
case 'video':
return (
<SiteConsumer>{(site) => <VideoViewer data={mediaData} siteUrl={site.url} inEmbed={!1} />}</SiteConsumer>
);
case 'audio':
return <AudioViewer />;
case 'image':
return <ImageViewer />;
case 'pdf':
return <PdfViewer />;
}
return <AttachmentViewer />;
}
}

View File

@@ -0,0 +1,9 @@
import React from 'react';
import PdfViewer from '../components/media-viewer/PdfViewer';
import { _MediaPage } from './_MediaPage';
export class MediaPdfPage extends _MediaPage {
viewerContainerContent() {
return <PdfViewer />;
}
}

View File

@@ -0,0 +1,14 @@
import React from 'react';
import { SiteConsumer } from '../utils/contexts/';
import VideoViewer from '../components/media-viewer/VideoViewer';
import { _VideoMediaPage } from './_VideoMediaPage';
export class MediaVideoPage extends _VideoMediaPage {
viewerContainerContent(mediaData) {
return <SiteConsumer>{(site) => <VideoViewer data={mediaData} siteUrl={site.url} inEmbed={!1} />}</SiteConsumer>;
}
mediaType() {
return 'video';
}
}

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { ApiUrlConsumer } from '../utils/contexts/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
import { Page } from './Page';
interface MembersPageProps {
id?: string;
title?: string;
}
export const MembersPage: React.FC<MembersPageProps> = ({ id = 'members', title = 'Members' }) => (
<Page id={id}>
<ApiUrlConsumer>
{(apiUrl) => (
<MediaListWrapper title={title} className="items-list-ver">
<LazyLoadItemListAsync requestUrl={apiUrl.users} />
</MediaListWrapper>
)}
</ApiUrlConsumer>
</Page>
);

View File

@@ -0,0 +1,22 @@
import React, { useEffect } from 'react';
import { PageActions } from '../utils/actions/';
import { Notifications } from '../components/_shared';
import { PageMain } from '../components/page-layout/';
interface PageProps {
id: string;
children?: any;
}
export const Page: React.FC<PageProps> = ({ id, children = null }) => {
useEffect(() => {
PageActions.initPage(id);
}, []);
return (
<>
<PageMain key="page-main">{children}</PageMain>
<Notifications key="notifications" />
</>
);
};

View File

@@ -0,0 +1,446 @@
import React, { useState, useEffect } from 'react';
import { PageActions, PlaylistPageActions } from '../utils/actions/';
import { MemberContext } from '../utils/contexts/';
import { usePopup } from '../utils/hooks/';
import { PlaylistPageStore } from '../utils/stores/';
import {
CircleIconButton,
MaterialIcon,
NavigationContentApp,
NavigationMenuList,
PopupMain,
} from '../components/_shared/';
import { PlaylistCreationForm } from '../components/playlist-form/PlaylistCreationForm';
import { PlaylistMediaList } from '../components/playlist-page/PlaylistMediaList';
import { Page } from './_Page';
import '../components/playlist-page/PlaylistPage.scss';
if (window.MediaCMS.site.devEnv) {
const extractUrlParams = () => {
let mediaId = null;
let playlistId = null;
const query = window.location.search.split('?')[1];
if (query) {
const params = query.split('&');
params.forEach((param) => {
if (0 === param.indexOf('m=')) {
mediaId = param.split('m=')[1];
} else if (0 === param.indexOf('pl=')) {
playlistId = param.split('pl=')[1];
}
});
}
return { mediaId, playlistId };
};
const { playlistId } = extractUrlParams();
if (playlistId) {
window.MediaCMS.playlistId = playlistId;
}
}
function PlayAllLink(props) {
let playAllUrl = props.media[0].url;
if (window.MediaCMS.site.devEnv && -1 < playAllUrl.indexOf('view?')) {
playAllUrl = '/media.html?' + playAllUrl.split('view?')[1];
}
playAllUrl += '&pl=' + props.id;
return !props.media || !props.media.length ? (
<span>{props.children}</span>
) : (
<a href={playAllUrl} title="">
{props.children}
</a>
);
}
function PlaylistThumb(props) {
const [thumb, setThumb] = useState(null);
useEffect(() => {
if (!props.thumb || 'string' !== typeof props.thumb) {
setThumb(null);
} else {
const tb = props.thumb.trim();
setThumb('' !== tb ? tb : null);
}
}, [props.thumb]);
return (
<div className={'playlist-thumb' + (thumb ? '' : ' no-thumb')} style={{ backgroundImage: 'url("' + thumb + '")' }}>
<PlayAllLink id={props.id} media={props.media}>
<span>
{thumb ? <img src={thumb} alt="" /> : null}
<span className="play-all">
<span>
<span>
<i className="material-icons">play_arrow</i>
<span className="play-all-label">PLAY ALL</span>
</span>
</span>
</span>
</span>
</PlayAllLink>
</div>
);
}
function PlaylistTitle(props) {
return (
<div className="playlist-title">
<h1>{props.title}</h1>
</div>
);
}
function PlaylistMeta(props) {
return (
<div className="playlist-meta">
<div className="playlist-videos-number">{props.totalItems} media</div>
{/*<div className="playlist-views">{ props.viewsCount } { 1 < formatViewsNumber( props.viewsCount ) ? 'views' : 'view' }</div>*/}
{!props.dateLabel ? null : <div className="playlist-last-update">{props.dateLabel}</div>}
</div>
);
}
function playlistOptionsList() {
const items = {
deleteMedia: {
itemType: 'open-subpage',
text: 'Delete',
icon: 'delete',
buttonAttr: {
className: 'change-page',
'data-page-id': 'proceedPlaylistRemovalPopup',
},
},
};
return items;
}
function playlistOptionsPopupPages(proceedPlaylistRemoval, cancelPlaylistRemoval) {
const optionsList = playlistOptionsList();
return {
main: (
<PopupMain>
<NavigationMenuList items={[optionsList.deleteMedia]} />
</PopupMain>
),
proceedPlaylistRemovalPopup: (
<PopupMain>
<div className="popup-message">
<span className="popup-message-title">Playlist removal</span>
<span className="popup-message-main">You're willing to remove playlist permanently?</span>
</div>
<hr />
<span className="popup-message-bottom">
<button className="button-link cancel-playlist-removal" onClick={cancelPlaylistRemoval}>
CANCEL
</button>
<button className="button-link proceed-playlist-removal" onClick={proceedPlaylistRemoval}>
PROCEED
</button>
</span>
</PopupMain>
),
};
}
function PlaylistOptions(props) {
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
const [popupCurrentPage, setPopupCurrentPage] = useState('main');
function proceedPlaylistRemoval() {
PlaylistPageActions.removePlaylist();
popupContentRef.current.toggle();
}
function cancelPlaylistRemoval() {
popupContentRef.current.toggle();
}
return (
<div className={'playlist-options-wrap' + ('main' === popupCurrentPage ? ' playlist-options-main' : '')}>
<PopupTrigger contentRef={popupContentRef}>
<CircleIconButton>
<MaterialIcon type="more_horiz" />
</CircleIconButton>
</PopupTrigger>
<PopupContent contentRef={popupContentRef}>
<NavigationContentApp
pageChangeCallback={setPopupCurrentPage}
initPage="main"
focusFirstItemOnPageChange={false}
pages={playlistOptionsPopupPages(proceedPlaylistRemoval, cancelPlaylistRemoval)}
pageChangeSelector={'.change-page'}
pageIdSelectorAttr={'data-page-id'}
/>
</PopupContent>
</div>
);
}
function PlaylistEdit(props) {
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
function onPlaylistSave() {
// Empty for now...
}
function onClickExit() {
popupContentRef.current.toggle();
}
function playlistUpdateCompleted(new_playlist_data) {
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
setTimeout(function () {
PageActions.addNotification('Playlist updated', 'playlistUpdateCompleted');
onClickExit();
}, 100);
}
function playlistUpdateFailed() {
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
setTimeout(function () {
PageActions.addNotification('Playlist update failed', 'playlistUpdateFailed');
onClickExit();
}, 100);
}
function playlistRemovalCompleted(playlistId) {
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
setTimeout(function () {
PageActions.addNotification('Playlist removed. Redirecting...', 'playlistDelete');
setTimeout(function () {
window.location.href = MemberContext._currentValue.pages.playlists;
}, 2000);
}, 100);
}
function playlistRemovalFailed(playlistId) {
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
setTimeout(function () {
PageActions.addNotification('Playlist removal failed', 'playlistDeleteFail');
}, 100);
}
useEffect(() => {
PlaylistPageStore.on('playlist_update_completed', playlistUpdateCompleted);
PlaylistPageStore.on('playlist_update_failed', playlistUpdateFailed);
PlaylistPageStore.on('playlist_removal_completed', playlistRemovalCompleted);
PlaylistPageStore.on('playlist_removal_failed', playlistRemovalFailed);
return () => {
PlaylistPageStore.removeListener('playlist_update_completed', playlistUpdateCompleted);
PlaylistPageStore.removeListener('playlist_update_failed', playlistUpdateFailed);
PlaylistPageStore.removeListener('playlist_removal_completed', playlistRemovalCompleted);
PlaylistPageStore.removeListener('playlist_removal_failed', playlistRemovalFailed);
};
}, []);
return (
<div className="edit-playlist">
<PopupTrigger contentRef={popupContentRef}>
<CircleIconButton>
<MaterialIcon type="edit" />
<span>EDIT</span>
</CircleIconButton>
</PopupTrigger>
<PopupContent contentRef={popupContentRef}>
<div className="popup-fullscreen">
<PopupMain>
<span className="popup-fullscreen-overlay"></span>
<div className="edit-playlist-form-wrap">
<div className="edit-playlist-popup-title">
Edit playlist
<CircleIconButton type="button" onClick={onClickExit}>
<MaterialIcon type="close" />
</CircleIconButton>
</div>
<PlaylistCreationForm
date={new Date().getTime()}
id={PlaylistPageStore.get('playlistId')}
onCancel={onClickExit}
onPlaylistSave={onPlaylistSave}
/>
</div>
</PopupMain>
</div>
</PopupContent>
</div>
);
}
function PlaylistActions(props) {
return props.loggedinUserPlaylist ? (
<div className="playlist-actions">{props.loggedinUserPlaylist ? <PlaylistOptions /> : null}</div>
) : null;
}
function PlaylistAuthor(props) {
return (
<div className="playlist-author">
<div>
<div className="playlist-author-thumb">
<a href={props.link} title={props.name}>
{props.thumb ? (
<span style={{ backgroundImage: 'url(' + props.thumb + ')' }}>
<img src={props.thumb} alt="" />
</span>
) : (
<span>
<MaterialIcon type="person" />
</span>
)}
</a>
</div>
<div className="playlist-author-name">
<a href={props.link} title={props.name}>
{props.name}
</a>
</div>
{props.loggedinUserPlaylist ? <PlaylistEdit /> : null}
</div>
</div>
);
}
export class PlaylistPage extends Page {
constructor(props) {
super(props, 'playlist-page');
this.state = {
thumb: PlaylistPageStore.get('thumb'),
media: PlaylistPageStore.get('playlist-media'),
savedPlaylist: PlaylistPageStore.get('saved-playlist'),
loggedinUserPlaylist: PlaylistPageStore.get('logged-in-user-playlist'),
title: PlaylistPageStore.get('title'),
description: PlaylistPageStore.get('description'),
};
this.onLoadPlaylistData = this.onLoadPlaylistData.bind(this);
PlaylistPageStore.on('loaded_playlist_data', this.onLoadPlaylistData);
/*this.onPlaylistSaveUpdate = this.onPlaylistSaveUpdate.bind(this);
PlaylistPageStore.on('saved-updated', this.onPlaylistSaveUpdate);*/
this.onMediaRemovedFromPlaylist = this.onMediaRemovedFromPlaylist.bind(this);
PlaylistPageStore.on('removed_media_from_playlist', this.onMediaRemovedFromPlaylist);
this.onMediaReorderedInPlaylist = this.onMediaReorderedInPlaylist.bind(this);
PlaylistPageStore.on('reordered_media_in_playlist', this.onMediaReorderedInPlaylist);
this.onCompletePlaylistUpdate = this.onCompletePlaylistUpdate.bind(this);
PlaylistPageStore.on('playlist_update_completed', this.onCompletePlaylistUpdate);
}
onCompletePlaylistUpdate() {
this.setState({
thumb: PlaylistPageStore.get('thumb'),
title: PlaylistPageStore.get('title'),
description: PlaylistPageStore.get('description'),
});
}
onLoadPlaylistData() {
this.setState({
thumb: PlaylistPageStore.get('thumb'),
title: PlaylistPageStore.get('title'),
description: PlaylistPageStore.get('description'),
media: PlaylistPageStore.get('playlist-media'),
savedPlaylist: PlaylistPageStore.get('saved-playlist'),
loggedinUserPlaylist: PlaylistPageStore.get('logged-in-user-playlist'),
});
}
componentDidMount() {
PlaylistPageActions.loadPlaylistData();
}
/*onPlaylistSaveUpdate(){
this.setState({
savedPlaylist: PlaylistPageStore.get('saved-playlist'),
}, () => {
if( this.state.savedPlaylist ){
PageActions.addNotification('Added to playlists library', 'added-to-playlists-lib');
}
else{
PageActions.addNotification('Removed from playlists library', 'removed-from-playlists-lib');
}
});
}*/
onMediaRemovedFromPlaylist() {
this.setState({
media: PlaylistPageStore.get('playlist-media'),
thumb: PlaylistPageStore.get('thumb'),
});
}
onMediaReorderedInPlaylist() {
this.setState({
media: PlaylistPageStore.get('playlist-media'),
thumb: PlaylistPageStore.get('thumb'),
});
}
pageContent() {
const playlistId = PlaylistPageStore.get('playlistId');
if (!playlistId) {
return null;
}
return [
<div key="playlistDetails" className="playlist-details">
<PlaylistThumb id={playlistId} thumb={this.state.thumb} media={this.state.media} />
<PlaylistTitle title={this.state.title} />
<PlaylistMeta
totalItems={PlaylistPageStore.get('total-items')}
dateLabel={PlaylistPageStore.get('date-label')}
viewsCount={PlaylistPageStore.get('views-count')}
/>
{/*'public' === PlaylistPageStore.get('visibility') ? null :
<div className="playlist-status">
<span>{ PlaylistPageStore.get('visibility-icon') }</span>
<div>{ PlaylistPageStore.get('visibility') }</div>
</div>*/}
<PlaylistActions
loggedinUserPlaylist={this.state.loggedinUserPlaylist}
savedPlaylist={this.state.savedPlaylist}
/>
{this.state.description ? <div className="playlist-description">{this.state.description}</div> : null}
<PlaylistAuthor
name={PlaylistPageStore.get('author-name')}
link={PlaylistPageStore.get('author-link')}
thumb={PlaylistPageStore.get('author-thumb')}
loggedinUserPlaylist={this.state.loggedinUserPlaylist}
/>
</div>,
<PlaylistMediaList
key={'playlistMediaList_' + this.state.media.length}
id={playlistId}
media={this.state.media}
loggedinUserPlaylist={this.state.loggedinUserPlaylist}
/>,
];
}
}

View File

@@ -0,0 +1,305 @@
import React from 'react';
import PropTypes from 'prop-types';
import UrlParse from 'url-parse';
import { ApiUrlContext, MemberContext, SiteContext } from '../utils/contexts/';
import { formatInnerLink, csrfToken, postRequest } from '../utils/helpers/';
import { PageActions } from '../utils/actions/';
import { PageStore, ProfilePageStore } from '../utils/stores/';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
import { MediaListRow } from '../components/MediaListRow';
import { ProfileMediaPage } from './ProfileMediaPage';
class ChannelContactForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
subject: '',
body: '',
isSending: false,
};
this.onUpdateSubject = this.onUpdateSubject.bind(this);
this.onUpdateBody = this.onUpdateBody.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.onSubmitSuccess = this.onSubmitSuccess.bind(this);
this.onSubmitFail = this.onSubmitFail.bind(this);
}
onUpdateSubject() {
this.setState({
subject: this.refs.msgSubject.value.trim(),
});
}
onUpdateBody() {
this.setState({
body: this.refs.msgBody.value.trim(),
});
}
onSubmitSuccess(response) {
this.setState(
{
subject: '',
body: '',
isSending: false,
},
function () {
setTimeout(
function () {
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
PageActions.addNotification(
'Your message was successfully submitted to ' + this.props.author.name,
'messageSubmitSucceed'
);
}.bind(this),
100
);
}
);
}
onSubmitFail(response) {
this.setState(
{
isSending: false,
},
function () {
console.log(response);
setTimeout(
function () {
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
PageActions.addNotification('Your message failed to submit. Please try again', 'messageSubmitFailed');
}.bind(this),
100
);
}
);
}
onSubmit(ev) {
if (this.state.isSending || '' === this.state.subject || '' === this.state.body) {
return;
}
ev.preventDefault();
ev.stopPropagation();
this.setState(
{
isSending: true,
},
function () {
const url = ApiUrlContext._currentValue.users + '/' + this.props.author.username + '/contact';
postRequest(
url,
{
subject: this.state.subject,
body: this.state.body,
},
{
headers: {
'X-CSRFToken': csrfToken(),
},
},
false,
this.onSubmitSuccess,
this.onSubmitFail
);
}
);
}
render() {
return (
<div className="media-list-row profile-contact">
<div className="media-list-header">
<h2>Contact</h2>
</div>
<form method="post" className={'user-contact-form' + (this.state.isSending ? ' pending-response' : '')}>
<span>
<label>Subject</label>
<input
ref="msgSubject"
type="text"
required={true}
onChange={this.onUpdateSubject}
value={this.state.subject}
/>
</span>
<span>
<label>Message</label>
<textarea
ref="msgBody"
required={true}
cols="40"
rows="10"
onChange={this.onUpdateBody}
value={this.state.body}
></textarea>
</span>
<button onClick={this.onSubmit}>SUBMIT</button>
</form>
</div>
);
}
}
export class ProfileAboutPage extends ProfileMediaPage {
constructor(props) {
super(props, 'author-about');
this.userIsAuthor = null;
this.enabledContactForm = false;
}
pageContent() {
let description = null;
let details = [];
let socialMedia = [];
if (this.state.author) {
if (null === this.userIsAuthor) {
if (MemberContext._currentValue.is.anonymous) {
this.userIsAuthor = false;
this.enabledContactForm = false;
} else {
this.userIsAuthor = ProfilePageStore.get('author-data').username === MemberContext._currentValue.username;
this.enabledContactForm = !this.userIsAuthor && MemberContext._currentValue.can.contactUser;
}
}
let i;
if (
void 0 !== this.state.author.description &&
!!this.state.author.description &&
'' !== this.state.author.description
) {
description = this.state.author.description;
}
if (void 0 !== this.state.author.location_info && this.state.author.location_info.length) {
let locations = [];
i = 0;
while (i < this.state.author.location_info.length) {
if (
void 0 !== this.state.author.location_info[i].title &&
void 0 !== this.state.author.location_info[i].url
) {
locations.push(
<a
key={i}
href={formatInnerLink(this.state.author.location_info[i].url, SiteContext._currentValue.url)}
title={this.state.author.location_info[i].title}
>
{this.state.author.location_info[i].title}
</a>
);
}
i += 1;
}
details.push(
<li key={'location'}>
<span>Location:</span>
<span>{locations}</span>
</li>
);
} else if (
void 0 !== this.state.author.location &&
!!this.state.author.location &&
'' !== this.state.author.location
) {
// TODO: Remove it, doesn't really need. Remains for backward compatibility.
details.push(
<li key={'location'}>
<span>Location:</span>
<span>{this.state.author.location}</span>
</li>
);
}
let lnk;
if (
void 0 !== this.state.author.home_page &&
!!this.state.author.home_page &&
'' !== this.state.author.home_page
) {
lnk = UrlParse(this.state.author.home_page.trim()).toString();
if ('' !== lnk) {
details.push(
<li key={'website'}>
<span>Website:</span>
<span>{lnk}</span>
</li>
);
}
}
if (
void 0 !== this.state.author.social_media_links &&
!!this.state.author.social_media_links &&
'' !== this.state.author.social_media_links
) {
let socialMediaLinks = this.state.author.social_media_links.split(',');
if (socialMediaLinks.length) {
i = 0;
while (i < socialMediaLinks.length) {
lnk = socialMediaLinks[i].trim();
if ('' !== lnk) {
socialMedia.push(<span key={i}>{lnk}</span>);
}
i += 1;
}
details.push(
<li key={'social_media'}>
<span>Social media:</span>
<span className="author-social-media">{socialMedia}</span>
</li>
);
}
}
}
return [
this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="about" />
) : null,
this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent" enabledContactForm={this.enabledContactForm}>
<div className="media-list-wrapper items-list-ver profile-about-content ">
{null === description && 0 < details.length ? null : PageStore.get('config-options').pages.profile
.htmlInDescription ? (
<MediaListRow title={this.props.title}>
<span dangerouslySetInnerHTML={{ __html: description || null }}></span>
</MediaListRow>
) : (
<MediaListRow title={this.props.title}>{description}</MediaListRow>
)}
{!details.length ? null : (
<MediaListRow title={'Details'}>
<ul className="profile-details">{details}</ul>
</MediaListRow>
)}
{this.enabledContactForm ? <ChannelContactForm author={this.state.author} /> : null}
</div>
</ProfilePagesContent>
) : null,
];
}
}
ProfileAboutPage.propTypes = {
title: PropTypes.string.isRequired,
};
ProfileAboutPage.defaultProps = {
title: 'Biography',
};

View File

@@ -0,0 +1,66 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
import { ProfileMediaPage } from './ProfileMediaPage';
export class ProfileHistoryPage extends ProfileMediaPage {
constructor(props) {
super(props, 'author-history');
this.state = {
resultsCount: null,
};
this.getCountFunc = this.getCountFunc.bind(this);
}
getCountFunc(resultsCount) {
this.setState({
resultsCount: resultsCount,
});
}
pageContent() {
return [
this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="history" />
) : null,
this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent">
<ApiUrlConsumer>
{(apiUrl) => (
<MediaListWrapper
title={
this.props.title + (null !== this.state.resultsCount ? ' (' + this.state.resultsCount + ')' : '')
}
className="items-list-ver"
>
<LazyLoadItemListAsync
itemsCountCallback={this.getCountFunc}
requestUrl={apiUrl.user.history}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideViews={!PageStore.get('config-media-item').displayViews}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
canEdit={false}
/>
</MediaListWrapper>
)}
</ApiUrlConsumer>
</ProfilePagesContent>
) : null,
];
}
}
ProfileHistoryPage.propTypes = {
title: PropTypes.string.isRequired,
};
ProfileHistoryPage.defaultProps = {
title: 'History',
};

View File

@@ -0,0 +1,66 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync';
import { ProfileMediaPage } from './ProfileMediaPage';
export class ProfileLikedPage extends ProfileMediaPage {
constructor(props) {
super(props, 'author-liked');
this.state = {
resultsCount: null,
};
this.getCountFunc = this.getCountFunc.bind(this);
}
getCountFunc(resultsCount) {
this.setState({
resultsCount: resultsCount,
});
}
pageContent() {
return [
this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="liked" />
) : null,
this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent">
<ApiUrlConsumer>
{(apiUrl) => (
<MediaListWrapper
title={
this.props.title + (null !== this.state.resultsCount ? ' (' + this.state.resultsCount + ')' : '')
}
className="items-list-ver"
>
<LazyLoadItemListAsync
itemsCountCallback={this.getCountFunc}
requestUrl={apiUrl.user.liked}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideViews={!PageStore.get('config-media-item').displayViews}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
canEdit={false}
/>
</MediaListWrapper>
)}
</ApiUrlConsumer>
</ProfilePagesContent>
) : null,
];
}
}
ProfileLikedPage.propTypes = {
title: PropTypes.string.isRequired,
};
ProfileLikedPage.defaultProps = {
title: 'Liked media',
};

View File

@@ -0,0 +1,180 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ApiUrlContext, LinksConsumer, MemberContext } from '../utils/contexts';
import { PageStore, ProfilePageStore } from '../utils/stores';
import { ProfilePageActions } from '../utils/actions';
import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync';
import { Page } from './_Page';
import '../components/profile-page/ProfilePage.scss';
function EmptyChannelMedia(props) {
return (
<LinksConsumer>
{(links) => (
<div className="empty-media empty-channel-media">
<div className="welcome-title">Welcome {props.name}</div>
<div className="start-uploading">
Start uploading media and sharing your work. Media that you upload will show up here.
</div>
<a href={links.user.addMedia} title="Upload media" className="button-link">
<i className="material-icons" data-icon="video_call"></i>UPLOAD MEDIA
</a>
</div>
)}
</LinksConsumer>
);
}
export class ProfileMediaPage extends Page {
constructor(props, pageSlug) {
super(props, 'string' === typeof pageSlug ? pageSlug : 'author-home');
this.profilePageSlug = 'string' === typeof pageSlug ? pageSlug : 'author-home';
this.state = {
channelMediaCount: -1,
author: ProfilePageStore.get('author-data'),
uploadsPreviewItemsCount: 0,
title: this.props.title,
query: ProfilePageStore.get('author-query'),
requestUrl: null,
};
this.authorDataLoad = this.authorDataLoad.bind(this);
this.onAuthorPreviewItemsCountCallback = this.onAuthorPreviewItemsCountCallback.bind(this);
this.getCountFunc = this.getCountFunc.bind(this);
this.changeRequestQuery = this.changeRequestQuery.bind(this);
ProfilePageStore.on('load-author-data', this.authorDataLoad);
}
componentDidMount() {
ProfilePageActions.load_author_data();
}
authorDataLoad() {
const author = ProfilePageStore.get('author-data');
let requestUrl = this.state.requestUrl;
if (author) {
if (this.state.query) {
requestUrl = ApiUrlContext._currentValue.search.query + this.state.query + '&author=' + author.id;
} else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id;
}
}
this.setState({
author: author,
requestUrl: requestUrl,
});
}
onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) {
this.setState({
uploadsPreviewItemsCount: totalAuthorPreviewItems,
});
}
getCountFunc(count) {
this.setState(
{
channelMediaCount: count,
},
() => {
if (this.state.query) {
let title = '';
if (!count) {
title = 'No results for "' + this.state.query + '"';
} else if (1 === count) {
title = '1 result for "' + this.state.query + '"';
} else {
title = count + ' results for "' + this.state.query + '"';
}
this.setState({
title: title,
});
}
}
);
}
changeRequestQuery(newQuery) {
if (!this.state.author) {
return;
}
let requestUrl;
if (newQuery) {
requestUrl = ApiUrlContext._currentValue.search.query + newQuery + '&author=' + this.state.author.id;
} else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id;
}
let title = this.state.title;
if ('' === newQuery) {
title = this.props.title;
}
this.setState({
requestUrl: requestUrl,
query: newQuery,
title: title,
});
}
pageContent() {
const authorData = ProfilePageStore.get('author-data');
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
return [
this.state.author ? (
<ProfilePagesHeader
key="ProfilePagesHeader"
author={this.state.author}
onQueryChange={this.changeRequestQuery}
/>
) : null,
this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent">
<MediaListWrapper
title={!isMediaAuthor || 0 < this.state.channelMediaCount ? this.state.title : null}
className="items-list-ver"
>
<LazyLoadItemListAsync
key={this.state.requestUrl}
requestUrl={this.state.requestUrl}
hideAuthor={true}
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null}
hideViews={!PageStore.get('config-media-item').displayViews}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
canEdit={isMediaAuthor}
/>
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
<EmptyChannelMedia name={this.state.author.name} />
) : null}
</MediaListWrapper>
</ProfilePagesContent>
) : null,
];
}
}
ProfileMediaPage.propTypes = {
title: PropTypes.string.isRequired,
};
ProfileMediaPage.defaultProps = {
title: 'Uploads',
};

View File

@@ -0,0 +1,57 @@
import React from 'react';
import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
import { ProfileMediaPage } from './ProfileMediaPage';
export class ProfilePlaylistsPage extends ProfileMediaPage {
constructor(props) {
super(props, 'author-playlists');
this.state = {
loadedAuthor: false,
loadedPlaylists: false,
playlistsCount: -1,
};
this.getPlaylistsCountFunc = this.getPlaylistsCountFunc.bind(this);
}
getPlaylistsCountFunc(resultsCount) {
this.setState({
loadedPlaylists: true,
playlistsCount: resultsCount,
});
}
pageContent() {
return [
this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="playlists" />
) : null,
this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent">
<ApiUrlConsumer>
{(apiUrl) => (
<MediaListWrapper
title={-1 < this.state.playlistsCount ? 'Created playlists' : void 0}
className="profile-playlists-content items-list-ver"
>
<LazyLoadItemListAsync
requestUrl={apiUrl.user.playlists + this.state.author.username}
itemsCountCallback={this.getPlaylistsCountFunc}
hideViews={!PageStore.get('config-media-item').displayViews}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
/>
</MediaListWrapper>
)}
</ApiUrlConsumer>
</ProfilePagesContent>
) : null,
];
}
}

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
import { Page } from './Page';
interface RecommendedMediaPageProps {
id?: string;
title?: string;
}
export const RecommendedMediaPage: React.FC<RecommendedMediaPageProps> = ({
id = 'recommended-media',
title = PageStore.get('config-enabled').pages.recommended.title,
}) => (
<Page id={id}>
<ApiUrlConsumer>
{(apiUrl) => (
<MediaListWrapper title={title} className="items-list-ver">
<LazyLoadItemListAsync
requestUrl={apiUrl.recommended}
hideViews={!PageStore.get('config-media-item').displayViews}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
/>
</MediaListWrapper>
)}
</ApiUrlConsumer>
</Page>
);

View File

@@ -0,0 +1,218 @@
import React from 'react';
import { ApiUrlContext } from '../utils/contexts/';
import { PageStore, SearchFieldStore } from '../utils/stores/';
import { FiltersToggleButton } from '../components/_shared/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync';
import { SearchMediaFiltersRow } from '../components/search-filters/SearchMediaFiltersRow';
import { SearchResultsFilters } from '../components/search-filters/SearchResultsFilters';
import { Page } from './_Page';
export class SearchPage extends Page {
constructor(props) {
super(props, 'search-results');
this.state = {
validQuery: false,
requestUrl: null,
filterArgs: '',
resultsTitle: null,
resultsCount: null,
searchQuery: SearchFieldStore.get('search-query'),
searchCategories: SearchFieldStore.get('search-categories'),
searchTags: SearchFieldStore.get('search-tags'),
hiddenFilters: true,
};
this.getCountFunc = this.getCountFunc.bind(this);
this.updateRequestUrl = this.updateRequestUrl.bind(this);
this.onFilterArgsUpdate = this.onFilterArgsUpdate.bind(this);
this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this);
this.onFiltersUpdate = this.onFiltersUpdate.bind(this);
this.didMount = false;
this.updateRequestUrl();
}
componentDidMount() {
this.didMount = true;
}
onToggleFiltersClick() {
this.setState({
hiddenFilters: !this.state.hiddenFilters,
});
}
onFiltersUpdate(updatedArgs) {
const args = {
media_type: null,
upload_date: null,
sort_by: null,
ordering: null,
};
switch (updatedArgs.media_type) {
case 'video':
case 'audio':
case 'image':
case 'pdf':
args.media_type = updatedArgs.media_type;
break;
}
switch (updatedArgs.upload_date) {
case 'today':
case 'this_week':
case 'this_month':
case 'this_year':
args.upload_date = updatedArgs.upload_date;
break;
}
switch (updatedArgs.sort_by) {
case 'most_views':
args.sort_by = 'views';
break;
case 'most_likes':
args.sort_by = 'likes';
break;
case 'date_added_asc':
args.ordering = 'asc';
break;
}
const newArgs = [];
for (let arg in args) {
if (null !== args[arg]) {
newArgs.push(arg + '=' + args[arg]);
}
}
this.setState(
{
filterArgs: newArgs.length ? '&' + newArgs.join('&') : '',
},
function () {
this.updateRequestUrl();
}
);
}
updateRequestUrl() {
const validQuery = this.state.searchQuery || this.state.searchCategories || this.state.searchTags;
let title = null;
if (null !== this.state.resultsCount) {
if (!validQuery) {
title = 'No results for "' + this.state.searchQuery + '"';
} else {
if (this.state.searchCategories) {
title = null === this.state.resultsCount || 0 === this.state.resultsCount ? 'No' : this.state.resultsCount;
title += ' media in category "' + this.state.searchCategories + '"';
} else if (this.state.searchTags) {
title = null === this.state.resultsCount || 0 === this.state.resultsCount ? 'No' : this.state.resultsCount;
title += ' media in tag "' + this.state.searchTags + '"';
} else {
if (null === this.state.resultsCount || 0 === this.state.resultsCount) {
title = 'No results for "' + this.state.searchQuery + '"';
} else {
title =
this.state.resultsCount +
' result' +
(1 < this.state.resultsCount ? 's' : '') +
' for "' +
this.state.searchQuery +
'"';
}
}
}
}
const api_url_postfix =
(this.state.searchQuery || '') +
(this.state.searchTags ? '&t=' + this.state.searchTags : '') +
(this.state.searchCategories ? '&c=' + this.state.searchCategories : '');
const url = ApiUrlContext._currentValue.search.query + api_url_postfix + this.state.filterArgs;
if (this.didMount) {
this.setState({
validQuery: validQuery,
requestUrl: url,
resultsTitle: title,
});
} else {
this.state.validQuery = validQuery;
this.state.requestUrl = url;
this.state.resultsTitle = title;
}
}
onFilterArgsUpdate(updatedArgs) {
const newArgs = [];
for (let arg in updatedArgs) {
if (null !== updatedArgs[arg]) {
newArgs.push(arg + '=' + updatedArgs[arg]);
}
}
this.setState(
{
filterArgs: newArgs.length ? '&' + newArgs.join('&') : '',
},
function () {
this.updateRequestUrl();
}
);
}
getCountFunc(resultsCount) {
this.setState(
{
resultsCount: resultsCount,
},
function () {
this.updateRequestUrl();
}
);
}
pageContent() {
const advancedFilters = PageStore.get('config-options').pages.search.advancedFilters;
return (
<MediaListWrapper
className="search-results-wrap items-list-hor"
title={null === this.state.resultsTitle ? null : this.state.resultsTitle}
>
{advancedFilters ? <FiltersToggleButton onClick={this.onToggleFiltersClick} /> : null}
{advancedFilters ? (
<SearchResultsFilters hidden={this.state.hiddenFilters} onFiltersUpdate={this.onFiltersUpdate} />
) : null}
{advancedFilters ? null : <SearchMediaFiltersRow onFiltersUpdate={this.onFilterArgsUpdate} />}
{!this.state.validQuery ? null : (
<LazyLoadItemListAsync
key={this.state.requestUrl}
singleLinkContent={false}
horizontalItemsOrientation={true}
itemsCountCallback={this.getCountFunc}
requestUrl={this.state.requestUrl}
preferSummary={true}
hideViews={!PageStore.get('config-media-item').displayViews}
hideAuthor={!PageStore.get('config-media-item').displayAuthor}
hideDate={!PageStore.get('config-media-item').displayPublishDate}
/>
)}
</MediaListWrapper>
);
}
}

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { ApiUrlConsumer } from '../utils/contexts/';
import { MediaListWrapper } from '../components/MediaListWrapper';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
import { Page } from './Page';
interface TagsPageProps {
id?: string;
title?: string;
}
export const TagsPage: React.FC<TagsPageProps> = ({ id = 'tags', title = 'Tags' }) => (
<Page id={id}>
<ApiUrlConsumer>
{(apiUrl) => (
<MediaListWrapper title={title} className="items-list-ver">
<LazyLoadItemListAsync singleLinkContent={true} inTagsList={true} requestUrl={apiUrl.archive.tags} />
</MediaListWrapper>
)}
</ApiUrlConsumer>
</Page>
);

View File

@@ -0,0 +1,111 @@
import React from 'react';
import { PageStore, MediaPageStore } from '../utils/stores/';
import { MediaPageActions } from '../utils/actions/';
import ViewerError from '../components/media-page/ViewerError';
import ViewerInfo from '../components/media-page/ViewerInfo';
import ViewerSidebar from '../components/media-page/ViewerSidebar';
import { Page } from './_Page';
import '../components/media-page/MediaPage.scss';
const wideLayoutBreakpoint = 1216;
export class _MediaPage extends Page {
constructor(props) {
super(props, 'media');
const isWideLayout = wideLayoutBreakpoint <= window.innerWidth;
this.state = {
mediaLoaded: false,
mediaLoadFailed: false,
wideLayout: isWideLayout,
infoAndSidebarViewType: !isWideLayout ? 0 : 1,
viewerClassname: 'cf viewer-section viewer-wide',
viewerNestedClassname: 'viewer-section-nested',
pagePlaylistLoaded: false,
};
this.onWindowResize = this.onWindowResize.bind(this);
this.onMediaLoad = this.onMediaLoad.bind(this);
this.onMediaLoadError = this.onMediaLoadError.bind(this);
this.onPagePlaylistLoad = this.onPagePlaylistLoad.bind(this);
MediaPageStore.on('loaded_media_data', this.onMediaLoad);
MediaPageStore.on('loaded_media_error', this.onMediaLoadError);
MediaPageStore.on('loaded_page_playlist_data', this.onPagePlaylistLoad);
}
componentDidMount() {
MediaPageActions.loadMediaData();
// FIXME: Is not neccessary to check on every window dimension for changes...
PageStore.on('window_resize', this.onWindowResize);
}
onPagePlaylistLoad() {
this.setState({
pagePlaylistLoaded: true,
});
}
onWindowResize() {
const isWideLayout = wideLayoutBreakpoint <= window.innerWidth;
this.setState({
wideLayout: isWideLayout,
infoAndSidebarViewType: !isWideLayout || (MediaPageStore.isVideo() && this.state.theaterMode) ? 0 : 1,
});
}
onMediaLoad() {
this.setState({ mediaLoaded: true });
}
onMediaLoadError() {
this.setState({ mediaLoadFailed: true });
}
viewerContainerContent() {
return null;
}
mediaType() {
return null;
}
pageContent() {
return this.state.mediaLoadFailed ? (
<div className={this.state.viewerClassname}>
<ViewerError />
</div>
) : (
<div className={this.state.viewerClassname}>
<div className="viewer-container" key="viewer-container">
{this.state.mediaLoaded ? this.viewerContainerContent() : null}
</div>
<div key="viewer-section-nested" className={this.state.viewerNestedClassname}>
{!this.state.infoAndSidebarViewType
? [
<ViewerInfo key="viewer-info" />,
this.state.pagePlaylistLoaded ? (
<ViewerSidebar
key="viewer-sidebar"
mediaId={MediaPageStore.get('media-id')}
playlistData={MediaPageStore.get('playlist-data')}
/>
) : null,
]
: [
this.state.pagePlaylistLoaded ? (
<ViewerSidebar
key="viewer-sidebar"
mediaId={MediaPageStore.get('media-id')}
playlistData={MediaPageStore.get('playlist-data')}
/>
) : null,
<ViewerInfo key="viewer-info" />,
]}
</div>
</div>
);
}
}

View File

@@ -0,0 +1,23 @@
import React from 'react';
import { PageActions } from '../utils/actions/';
import { PageMain } from '../components/page-layout/';
import { Notifications } from '../components/_shared';
export class Page extends React.PureComponent {
constructor(props, pageId) {
super(props);
if (void 0 !== pageId) {
PageActions.initPage(pageId);
}
}
render() {
return (
<>
<PageMain>{this.pageContent()}</PageMain>
<Notifications />
</>
);
}
}

View File

@@ -0,0 +1,128 @@
import React from 'react';
// FIXME: 'VideoViewerStore' is used only in case of video media, but is included in every media page code.
import { PageStore, MediaPageStore, VideoViewerStore } from '../utils/stores/';
import { MediaPageActions } from '../utils/actions/';
import ViewerInfoVideo from '../components/media-page/ViewerInfoVideo';
import ViewerError from '../components/media-page/ViewerError';
import ViewerSidebar from '../components/media-page/ViewerSidebar';
import { Page } from './_Page';
import _MediaPage from './_MediaPage';
const wideLayoutBreakpoint = 1216;
export class _VideoMediaPage extends Page {
constructor(props) {
super(props, 'media');
this.state = {
wideLayout: wideLayoutBreakpoint <= window.innerWidth,
mediaLoaded: false,
mediaLoadFailed: false,
isVideoMedia: false,
theaterMode: false, // FIXME: Used only in case of video media, but is included in every media page code.
pagePlaylistLoaded: false,
pagePlaylistData: MediaPageStore.get('playlist-data'),
};
this.onWindowResize = this.onWindowResize.bind(this);
this.onMediaLoad = this.onMediaLoad.bind(this);
this.onMediaLoadError = this.onMediaLoadError.bind(this);
this.onPagePlaylistLoad = this.onPagePlaylistLoad.bind(this);
MediaPageStore.on('loaded_media_data', this.onMediaLoad);
MediaPageStore.on('loaded_media_error', this.onMediaLoadError);
MediaPageStore.on('loaded_page_playlist_data', this.onPagePlaylistLoad);
}
componentDidMount() {
MediaPageActions.loadMediaData();
// FIXME: Is not neccessary to check on every window dimension for changes...
PageStore.on('window_resize', this.onWindowResize);
}
onWindowResize() {
this.setState({
wideLayout: wideLayoutBreakpoint <= window.innerWidth,
});
}
onPagePlaylistLoad() {
this.setState({
pagePlaylistLoaded: true,
pagePlaylistData: MediaPageStore.get('playlist-data'),
});
}
onMediaLoad() {
const isVideoMedia = 'video' === MediaPageStore.get('media-type');
if (isVideoMedia) {
this.onViewerModeChange = this.onViewerModeChange.bind(this);
VideoViewerStore.on('changed_viewer_mode', this.onViewerModeChange);
this.setState({
mediaLoaded: true,
isVideoMedia: isVideoMedia,
theaterMode: VideoViewerStore.get('in-theater-mode'),
});
} else {
this.setState({
mediaLoaded: true,
isVideoMedia: isVideoMedia,
});
}
}
onViewerModeChange() {
this.setState({ theaterMode: VideoViewerStore.get('in-theater-mode') });
}
onMediaLoadError(a) {
this.setState({ mediaLoadFailed: true });
}
pageContent() {
const viewerClassname = 'cf viewer-section' + (this.state.theaterMode ? ' theater-mode' : ' viewer-wide');
const viewerNestedClassname = 'viewer-section-nested' + (this.state.theaterMode ? ' viewer-section' : '');
return this.state.mediaLoadFailed ? (
<div className={viewerClassname}>
<ViewerError />
</div>
) : (
<div className={viewerClassname}>
{[
<div className="viewer-container" key="viewer-container">
{this.state.mediaLoaded && this.state.pagePlaylistLoaded
? this.viewerContainerContent(MediaPageStore.get('media-data'))
: null}
</div>,
<div key="viewer-section-nested" className={viewerNestedClassname}>
{!this.state.wideLayout || (this.state.isVideoMedia && this.state.theaterMode)
? [
<ViewerInfoVideo key="viewer-info" />,
this.state.pagePlaylistLoaded ? (
<ViewerSidebar
key="viewer-sidebar"
mediaId={MediaPageStore.get('media-id')}
playlistData={MediaPageStore.get('playlist-data')}
/>
) : null,
]
: [
this.state.pagePlaylistLoaded ? (
<ViewerSidebar
key="viewer-sidebar"
mediaId={MediaPageStore.get('media-id')}
playlistData={MediaPageStore.get('playlist-data')}
/>
) : null,
<ViewerInfoVideo key="viewer-info" />,
]}
</div>,
]}
</div>
);
}
}

View File

@@ -0,0 +1,19 @@
export * from './CategoriesPage';
export * from './EmbedPage';
export * from './FeaturedMediaPage';
export * from './HistoryPage';
export * from './HomePage';
export * from './LatestMediaPage';
export * from './LikedMediaPage';
export * from './ManageCommentsPage';
export * from './ManageMediaPage';
export * from './ManageUsersPage';
export * from './MediaPage';
export * from './MembersPage';
export * from './PlaylistPage';
export * from './ProfileAboutPage';
export * from './ProfileMediaPage';
export * from './ProfilePlaylistsPage';
export * from './RecommendedMediaPage';
export * from './SearchPage';
export * from './TagsPage';