mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-21 22:07:59 -05: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:
26
frontend/src/static/js/pages/CategoriesPage.tsx
Normal file
26
frontend/src/static/js/pages/CategoriesPage.tsx
Normal 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>
|
||||
);
|
||||
68
frontend/src/static/js/pages/EmbedPage.tsx
Normal file
68
frontend/src/static/js/pages/EmbedPage.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
31
frontend/src/static/js/pages/FeaturedMediaPage.tsx
Normal file
31
frontend/src/static/js/pages/FeaturedMediaPage.tsx
Normal 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>
|
||||
);
|
||||
62
frontend/src/static/js/pages/HistoryPage.tsx
Normal file
62
frontend/src/static/js/pages/HistoryPage.tsx
Normal 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 />;
|
||||
};
|
||||
127
frontend/src/static/js/pages/HomePage.tsx
Executable file
127
frontend/src/static/js/pages/HomePage.tsx
Executable 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>
|
||||
);
|
||||
};
|
||||
31
frontend/src/static/js/pages/LatestMediaPage.tsx
Normal file
31
frontend/src/static/js/pages/LatestMediaPage.tsx
Normal 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>
|
||||
);
|
||||
62
frontend/src/static/js/pages/LikedMediaPage.tsx
Normal file
62
frontend/src/static/js/pages/LikedMediaPage.tsx
Normal 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 />;
|
||||
};
|
||||
113
frontend/src/static/js/pages/ManageCommentsPage.js
Executable file
113
frontend/src/static/js/pages/ManageCommentsPage.js
Executable 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',
|
||||
};
|
||||
151
frontend/src/static/js/pages/ManageMediaPage.js
Normal file
151
frontend/src/static/js/pages/ManageMediaPage.js
Normal 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',
|
||||
};
|
||||
169
frontend/src/static/js/pages/ManageUsersPage.js
Normal file
169
frontend/src/static/js/pages/ManageUsersPage.js
Normal 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',
|
||||
};
|
||||
13
frontend/src/static/js/pages/MediaAudioPage.js
Executable file
13
frontend/src/static/js/pages/MediaAudioPage.js
Executable 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';
|
||||
}
|
||||
}
|
||||
13
frontend/src/static/js/pages/MediaImagePage.js
Executable file
13
frontend/src/static/js/pages/MediaImagePage.js
Executable 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';
|
||||
}
|
||||
}
|
||||
60
frontend/src/static/js/pages/MediaPage.js
Executable file
60
frontend/src/static/js/pages/MediaPage.js
Executable 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 />;
|
||||
}
|
||||
}
|
||||
9
frontend/src/static/js/pages/MediaPdfPage.js
Executable file
9
frontend/src/static/js/pages/MediaPdfPage.js
Executable 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 />;
|
||||
}
|
||||
}
|
||||
14
frontend/src/static/js/pages/MediaVideoPage.js
Executable file
14
frontend/src/static/js/pages/MediaVideoPage.js
Executable 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';
|
||||
}
|
||||
}
|
||||
22
frontend/src/static/js/pages/MembersPage.tsx
Executable file
22
frontend/src/static/js/pages/MembersPage.tsx
Executable 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>
|
||||
);
|
||||
22
frontend/src/static/js/pages/Page.tsx
Normal file
22
frontend/src/static/js/pages/Page.tsx
Normal 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" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
446
frontend/src/static/js/pages/PlaylistPage.js
Normal file
446
frontend/src/static/js/pages/PlaylistPage.js
Normal 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}
|
||||
/>,
|
||||
];
|
||||
}
|
||||
}
|
||||
305
frontend/src/static/js/pages/ProfileAboutPage.js
Normal file
305
frontend/src/static/js/pages/ProfileAboutPage.js
Normal 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',
|
||||
};
|
||||
66
frontend/src/static/js/pages/ProfileHistoryPage.js
Executable file
66
frontend/src/static/js/pages/ProfileHistoryPage.js
Executable 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',
|
||||
};
|
||||
66
frontend/src/static/js/pages/ProfileLikedPage.js
Executable file
66
frontend/src/static/js/pages/ProfileLikedPage.js
Executable 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',
|
||||
};
|
||||
180
frontend/src/static/js/pages/ProfileMediaPage.js
Executable file
180
frontend/src/static/js/pages/ProfileMediaPage.js
Executable 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',
|
||||
};
|
||||
57
frontend/src/static/js/pages/ProfilePlaylistsPage.js
Executable file
57
frontend/src/static/js/pages/ProfilePlaylistsPage.js
Executable 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
31
frontend/src/static/js/pages/RecommendedMediaPage.tsx
Normal file
31
frontend/src/static/js/pages/RecommendedMediaPage.tsx
Normal 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>
|
||||
);
|
||||
218
frontend/src/static/js/pages/SearchPage.js
Normal file
218
frontend/src/static/js/pages/SearchPage.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
22
frontend/src/static/js/pages/TagsPage.tsx
Normal file
22
frontend/src/static/js/pages/TagsPage.tsx
Normal 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>
|
||||
);
|
||||
111
frontend/src/static/js/pages/_MediaPage.js
Executable file
111
frontend/src/static/js/pages/_MediaPage.js
Executable 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
23
frontend/src/static/js/pages/_Page.js
Normal file
23
frontend/src/static/js/pages/_Page.js
Normal 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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
128
frontend/src/static/js/pages/_VideoMediaPage.js
Normal file
128
frontend/src/static/js/pages/_VideoMediaPage.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
19
frontend/src/static/js/pages/index.ts
Normal file
19
frontend/src/static/js/pages/index.ts
Normal 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';
|
||||
Reference in New Issue
Block a user