initial implementation

This commit is contained in:
Yiannis
2026-01-16 01:47:14 +02:00
parent 1c15880ae3
commit 4a1fdd61e0
13 changed files with 4925 additions and 4639 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -3,257 +3,278 @@ import { SiteContext } from '../../utils/contexts/';
import { useUser, usePopup } from '../../utils/hooks/'; import { useUser, usePopup } from '../../utils/hooks/';
import { PageStore, MediaPageStore } from '../../utils/stores/'; import { PageStore, MediaPageStore } from '../../utils/stores/';
import { PageActions, MediaPageActions } from '../../utils/actions/'; import { PageActions, MediaPageActions } from '../../utils/actions/';
import { formatInnerLink, publishedOnDate } from '../../utils/helpers/'; import { formatInnerLink, inEmbeddedApp, publishedOnDate } from '../../utils/helpers/';
import { PopupMain } from '../_shared/'; import { PopupMain } from '../_shared/';
import CommentsList from '../comments/Comments'; import CommentsList from '../comments/Comments';
import { replaceString } from '../../utils/helpers/'; import { replaceString } from '../../utils/helpers/';
import { translateString } from '../../utils/helpers/'; import { translateString } from '../../utils/helpers/';
function metafield(arr) { function metafield(arr) {
let i; let i;
let sep; let sep;
let ret = []; let ret = [];
if (arr && arr.length) { if (arr && arr.length) {
i = 0; i = 0;
sep = 1 < arr.length ? ', ' : ''; sep = 1 < arr.length ? ', ' : '';
while (i < arr.length) { while (i < arr.length) {
ret[i] = ( ret[i] = (
<div key={i}> <div key={i}>
<a href={arr[i].url} title={arr[i].title}> <a href={arr[i].url} title={arr[i].title}>
{arr[i].title} {arr[i].title}
</a> </a>
{i < arr.length - 1 ? sep : ''} {i < arr.length - 1 ? sep : ''}
</div> </div>
); );
i += 1; i += 1;
}
} }
}
return ret; return ret;
} }
function MediaAuthorBanner(props) { function MediaAuthorBanner(props) {
return ( return (
<div className="media-author-banner"> <div className="media-author-banner">
<div> <div>
<a className="author-banner-thumb" href={props.link || null} title={props.name}> <a className="author-banner-thumb" href={props.link || null} title={props.name}>
<span style={{ backgroundImage: 'url(' + props.thumb + ')' }}> <span style={{ backgroundImage: 'url(' + props.thumb + ')' }}>
<img src={props.thumb} loading="lazy" alt={props.name} title={props.name} /> <img src={props.thumb} loading="lazy" alt={props.name} title={props.name} />
</span> </span>
</a> </a>
</div> </div>
<div> <div>
<span> <span>
<a href={props.link} className="author-banner-name" title={props.name}> <a href={props.link} className="author-banner-name" title={props.name}>
<span>{props.name}</span> <span>{props.name}</span>
</a> </a>
</span> </span>
{PageStore.get('config-media-item').displayPublishDate && props.published ? ( {PageStore.get('config-media-item').displayPublishDate && props.published ? (
<span className="author-banner-date"> <span className="author-banner-date">
{translateString('Published on')} {replaceString(publishedOnDate(new Date(props.published)))} {translateString('Published on')} {replaceString(publishedOnDate(new Date(props.published)))}
</span> </span>
) : null} ) : null}
</div> </div>
</div> </div>
); );
} }
function MediaMetaField(props) { function MediaMetaField(props) {
return ( return (
<div className={props.id.trim() ? 'media-content-' + props.id.trim() : null}> <div className={props.id.trim() ? 'media-content-' + props.id.trim() : null}>
<div className="media-content-field"> <div className="media-content-field">
<div className="media-content-field-label"> <div className="media-content-field-label">
<h4>{props.title}</h4> <h4>{props.title}</h4>
</div>
<div className="media-content-field-content">{props.value}</div>
</div>
</div> </div>
<div className="media-content-field-content">{props.value}</div> );
</div>
</div>
);
} }
function EditMediaButton(props) { function EditMediaButton(props) {
let link = props.link; let link = props.link;
if (window.MediaCMS.site.devEnv) { if (window.MediaCMS.site.devEnv) {
link = '/edit-media.html'; link = '/edit-media.html';
} }
return ( return (
<a href={link} rel="nofollow" title={translateString('Edit media')} className="edit-media-icon"> <a href={link} rel="nofollow" title={translateString('Edit media')} className="edit-media-icon">
<i className="material-icons">edit</i> <i className="material-icons">edit</i>
</a> </a>
); );
} }
export default function ViewerInfoContent(props) { export default function ViewerInfoContent(props) {
const { userCan } = useUser(); const { userCan } = useUser();
const description = props.description.trim(); const description = props.description.trim();
const tagsContent = const tagsContent =
!PageStore.get('config-enabled').taxonomies.tags || PageStore.get('config-enabled').taxonomies.tags.enabled !PageStore.get('config-enabled').taxonomies.tags || PageStore.get('config-enabled').taxonomies.tags.enabled
? metafield(MediaPageStore.get('media-tags')) ? metafield(MediaPageStore.get('media-tags'))
: []; : [];
const categoriesContent = PageStore.get('config-options').pages.media.categoriesWithTitle const categoriesContent = PageStore.get('config-options').pages.media.categoriesWithTitle
? [] ? []
: !PageStore.get('config-enabled').taxonomies.categories || : !PageStore.get('config-enabled').taxonomies.categories ||
PageStore.get('config-enabled').taxonomies.categories.enabled PageStore.get('config-enabled').taxonomies.categories.enabled
? metafield(MediaPageStore.get('media-categories')) ? metafield(MediaPageStore.get('media-categories'))
: []; : [];
let summary = MediaPageStore.get('media-summary'); let summary = MediaPageStore.get('media-summary');
summary = summary ? summary.trim() : ''; summary = summary ? summary.trim() : '';
const [popupContentRef, PopupContent, PopupTrigger] = usePopup(); const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
const [hasSummary, setHasSummary] = useState('' !== summary); const [hasSummary, setHasSummary] = useState('' !== summary);
const [isContentVisible, setIsContentVisible] = useState('' == summary); const [isContentVisible, setIsContentVisible] = useState('' == summary);
function proceedMediaRemoval() { function proceedMediaRemoval() {
MediaPageActions.removeMedia(); MediaPageActions.removeMedia();
popupContentRef.current.toggle(); popupContentRef.current.toggle();
}
function cancelMediaRemoval() {
popupContentRef.current.toggle();
}
function onMediaDelete(mediaId) {
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
setTimeout(function () {
PageActions.addNotification('Media removed. Redirecting...', 'mediaDelete');
setTimeout(function () {
window.location.href =
SiteContext._currentValue.url + '/' + MediaPageStore.get('media-data').author_profile.replace(/^\//g, '');
}, 2000);
}, 100);
if (void 0 !== mediaId) {
console.info("Removed media '" + mediaId + '"');
}
}
function onMediaDeleteFail(mediaId) {
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
setTimeout(function () {
PageActions.addNotification('Media removal failed', 'mediaDeleteFail');
}, 100);
if (void 0 !== mediaId) {
console.info('Media "' + mediaId + '"' + ' removal failed');
}
}
function onClickLoadMore() {
setIsContentVisible(!isContentVisible);
}
useEffect(() => {
MediaPageStore.on('media_delete', onMediaDelete);
MediaPageStore.on('media_delete_fail', onMediaDeleteFail);
return () => {
MediaPageStore.removeListener('media_delete', onMediaDelete);
MediaPageStore.removeListener('media_delete_fail', onMediaDeleteFail);
};
}, []);
const authorLink = formatInnerLink(props.author.url, SiteContext._currentValue.url);
const authorThumb = formatInnerLink(props.author.thumb, SiteContext._currentValue.url);
function setTimestampAnchors(text) {
function wrapTimestampWithAnchor(match, string) {
let split = match.split(':'),
s = 0,
m = 1;
while (split.length > 0) {
s += m * parseInt(split.pop(), 10);
m *= 60;
}
const wrapped = `<a href="#" data-timestamp="${s}" class="video-timestamp">${match}</a>`;
return wrapped;
} }
const timeRegex = new RegExp('((\\d)?\\d:)?(\\d)?\\d:\\d\\d', 'g'); function cancelMediaRemoval() {
return text.replace(timeRegex, wrapTimestampWithAnchor); popupContentRef.current.toggle();
} }
return ( function onMediaDelete(mediaId) {
<div className="media-info-content"> // FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
{void 0 === PageStore.get('config-media-item').displayAuthor || setTimeout(function () {
null === PageStore.get('config-media-item').displayAuthor || PageActions.addNotification('Media removed. Redirecting...', 'mediaDelete');
!!PageStore.get('config-media-item').displayAuthor ? ( setTimeout(function () {
<MediaAuthorBanner link={authorLink} thumb={authorThumb} name={props.author.name} published={props.published} /> window.location.href =
) : null} SiteContext._currentValue.url +
'/' +
MediaPageStore.get('media-data').author_profile.replace(/^\//g, '');
}, 2000);
}, 100);
<div className="media-content-banner"> if (void 0 !== mediaId) {
<div className="media-content-banner-inner"> console.info("Removed media '" + mediaId + '"');
{hasSummary ? <div className="media-content-summary">{summary}</div> : null} }
{(!hasSummary || isContentVisible) && description ? ( }
<div
className="media-content-description"
dangerouslySetInnerHTML={{ __html: setTimestampAnchors(description) }}
></div>
) : null}
{hasSummary ? (
<button className="load-more" onClick={onClickLoadMore}>
{isContentVisible ? 'SHOW LESS' : 'SHOW MORE'}
</button>
) : null}
{tagsContent.length ? (
<MediaMetaField
value={tagsContent}
title={1 < tagsContent.length ? translateString('Tags') : translateString('Tag')}
id="tags"
/>
) : null}
{categoriesContent.length ? (
<MediaMetaField
value={categoriesContent}
title={1 < categoriesContent.length ? translateString('Categories') : translateString('Category')}
id="categories"
/>
) : null}
{userCan.editMedia ? ( function onMediaDeleteFail(mediaId) {
<div className="media-author-actions"> // FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
{userCan.editMedia ? <EditMediaButton link={MediaPageStore.get('media-data').edit_url} /> : null} setTimeout(function () {
PageActions.addNotification('Media removal failed', 'mediaDeleteFail');
}, 100);
{userCan.deleteMedia ? ( if (void 0 !== mediaId) {
<PopupTrigger contentRef={popupContentRef}> console.info('Media "' + mediaId + '"' + ' removal failed');
<button className="remove-media-icon" title={translateString('Delete media')}> }
<i className="material-icons">delete</i> }
</button>
</PopupTrigger>
) : null}
{userCan.deleteMedia ? ( function onClickLoadMore() {
<PopupContent contentRef={popupContentRef}> setIsContentVisible(!isContentVisible);
<PopupMain> }
<div className="popup-message">
<span className="popup-message-title">Media removal</span> useEffect(() => {
<span className="popup-message-main">You're willing to remove media permanently?</span> MediaPageStore.on('media_delete', onMediaDelete);
</div> MediaPageStore.on('media_delete_fail', onMediaDeleteFail);
<hr /> return () => {
<span className="popup-message-bottom"> MediaPageStore.removeListener('media_delete', onMediaDelete);
<button className="button-link cancel-comment-removal" onClick={cancelMediaRemoval}> MediaPageStore.removeListener('media_delete_fail', onMediaDeleteFail);
CANCEL };
</button> }, []);
<button className="button-link proceed-comment-removal" onClick={proceedMediaRemoval}>
PROCEED const authorLink = formatInnerLink(props.author.url, SiteContext._currentValue.url);
</button> const authorThumb = formatInnerLink(props.author.thumb, SiteContext._currentValue.url);
</span>
</PopupMain> function setTimestampAnchors(text) {
</PopupContent> function wrapTimestampWithAnchor(match, string) {
) : null} let split = match.split(':'),
s = 0,
m = 1;
while (split.length > 0) {
s += m * parseInt(split.pop(), 10);
m *= 60;
}
const wrapped = `<a href="#" data-timestamp="${s}" class="video-timestamp">${match}</a>`;
return wrapped;
}
const timeRegex = new RegExp('((\\d)?\\d:)?(\\d)?\\d:\\d\\d', 'g');
return text.replace(timeRegex, wrapTimestampWithAnchor);
}
return (
<div className="media-info-content">
{void 0 === PageStore.get('config-media-item').displayAuthor ||
null === PageStore.get('config-media-item').displayAuthor ||
!!PageStore.get('config-media-item').displayAuthor ? (
<MediaAuthorBanner
link={authorLink}
thumb={authorThumb}
name={props.author.name}
published={props.published}
/>
) : null}
<div className="media-content-banner">
<div className="media-content-banner-inner">
{hasSummary ? <div className="media-content-summary">{summary}</div> : null}
{(!hasSummary || isContentVisible) && description ? (
<div
className="media-content-description"
dangerouslySetInnerHTML={{ __html: setTimestampAnchors(description) }}
></div>
) : null}
{hasSummary ? (
<button className="load-more" onClick={onClickLoadMore}>
{isContentVisible ? 'SHOW LESS' : 'SHOW MORE'}
</button>
) : null}
{tagsContent.length ? (
<MediaMetaField
value={tagsContent}
title={1 < tagsContent.length ? translateString('Tags') : translateString('Tag')}
id="tags"
/>
) : null}
{categoriesContent.length ? (
<MediaMetaField
value={categoriesContent}
title={
1 < categoriesContent.length
? translateString('Categories')
: translateString('Category')
}
id="categories"
/>
) : null}
{userCan.editMedia ? (
<div className="media-author-actions">
{userCan.editMedia ? (
<EditMediaButton link={MediaPageStore.get('media-data').edit_url} />
) : null}
{userCan.deleteMedia ? (
<PopupTrigger contentRef={popupContentRef}>
<button className="remove-media-icon" title={translateString('Delete media')}>
<i className="material-icons">delete</i>
</button>
</PopupTrigger>
) : null}
{userCan.deleteMedia ? (
<PopupContent contentRef={popupContentRef}>
<PopupMain>
<div className="popup-message">
<span className="popup-message-title">Media removal</span>
<span className="popup-message-main">
You're willing to remove media permanently?
</span>
</div>
<hr />
<span className="popup-message-bottom">
<button
className="button-link cancel-comment-removal"
onClick={cancelMediaRemoval}
>
CANCEL
</button>
<button
className="button-link proceed-comment-removal"
onClick={proceedMediaRemoval}
>
PROCEED
</button>
</span>
</PopupMain>
</PopupContent>
) : null}
</div>
) : null}
</div>
</div> </div>
) : null}
</div>
</div>
<CommentsList /> {!inEmbeddedApp() && <CommentsList />}
</div> </div>
); );
} }

View File

@@ -1,28 +1,33 @@
.page-main-wrap { .page-main-wrap {
padding-top: var(--header-height); padding-top: var(--header-height);
will-change: padding-left; will-change: padding-left;
@media (min-width: 768px) {
.visible-sidebar & {
padding-left: var(--sidebar-width);
opacity: 1;
}
}
.visible-sidebar #page-media & {
padding-left: 0;
}
@media (min-width: 768px) {
.visible-sidebar & { .visible-sidebar & {
padding-left: var(--sidebar-width); #page-media {
opacity: 1; padding-left: 0;
}
} }
}
.visible-sidebar #page-media & { body.sliding-sidebar & {
padding-left: 0; transition-property: padding-left;
} transition-duration: 0.2s;
.visible-sidebar & {
#page-media {
padding-left: 0;
} }
}
body.sliding-sidebar & { .embedded-app & {
transition-property: padding-left; padding-top: 0;
transition-duration: 0.2s; padding-left: 0;
} }
} }
#page-profile-media, #page-profile-media,
@@ -30,20 +35,20 @@
#page-profile-about, #page-profile-about,
#page-liked.profile-page-liked, #page-liked.profile-page-liked,
#page-history.profile-page-history { #page-history.profile-page-history {
.page-main { .page-main {
min-height: calc(100vh - var(--header-height)); min-height: calc(100vh - var(--header-height));
} }
} }
.page-main { .page-main {
position: relative; position: relative;
width: 100%; width: 100%;
padding-bottom: 16px; padding-bottom: 16px;
} }
.page-main-inner { .page-main-inner {
display: block; display: block;
margin: 1em 1em 0 1em; margin: 1em 1em 0 1em;
} }
#page-profile-media, #page-profile-media,
@@ -51,7 +56,7 @@
#page-profile-about, #page-profile-about,
#page-liked.profile-page-liked, #page-liked.profile-page-liked,
#page-history.profile-page-history { #page-history.profile-page-history {
.page-main-wrap { .page-main-wrap {
background-color: var(--body-bg-color); background-color: var(--body-bg-color);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFi
import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags'; import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags';
import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting'; import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting';
import { BulkActionsModals } from '../components/BulkActionsModals'; import { BulkActionsModals } from '../components/BulkActionsModals';
import { translateString } from '../utils/helpers'; import { inEmbeddedApp, translateString } from '../utils/helpers';
import { withBulkActions } from '../utils/hoc/withBulkActions'; import { withBulkActions } from '../utils/hoc/withBulkActions';
import { Page } from './_Page'; import { Page } from './_Page';
@@ -19,400 +19,443 @@ import { Page } from './_Page';
import '../components/profile-page/ProfilePage.scss'; import '../components/profile-page/ProfilePage.scss';
function EmptySharedByMe(props) { function EmptySharedByMe(props) {
return ( return (
<LinksConsumer> <LinksConsumer>
{(links) => ( {(links) => (
<div className="empty-media empty-channel-media"> <div className="empty-media empty-channel-media">
<div className="welcome-title">No shared media</div> <div className="welcome-title">No shared media</div>
<div className="start-uploading"> <div className="start-uploading">Media that you have shared with others will show up here.</div>
Media that you have shared with others will show up here. </div>
</div> )}
</div> </LinksConsumer>
)} );
</LinksConsumer>
);
} }
class ProfileSharedByMePage extends Page { class ProfileSharedByMePage extends Page {
constructor(props, pageSlug) { constructor(props, pageSlug) {
super(props, 'string' === typeof pageSlug ? pageSlug : 'author-shared-by-me'); super(props, 'string' === typeof pageSlug ? pageSlug : 'author-shared-by-me');
this.profilePageSlug = 'string' === typeof pageSlug ? pageSlug : 'author-shared-by-me'; this.profilePageSlug = 'string' === typeof pageSlug ? pageSlug : 'author-shared-by-me';
this.state = { this.state = {
channelMediaCount: -1, channelMediaCount: -1,
author: ProfilePageStore.get('author-data'), author: ProfilePageStore.get('author-data'),
uploadsPreviewItemsCount: 0, uploadsPreviewItemsCount: 0,
title: this.props.title, title: this.props.title,
query: ProfilePageStore.get('author-query'), query: ProfilePageStore.get('author-query'),
requestUrl: null, requestUrl: null,
hiddenFilters: true, hiddenFilters: true,
hiddenTags: true, hiddenTags: true,
hiddenSorting: true, hiddenSorting: true,
filterArgs: '', filterArgs: '',
availableTags: [], availableTags: [],
selectedTag: 'all', selectedTag: 'all',
selectedSort: 'date_added_desc', selectedSort: 'date_added_desc',
}; };
this.authorDataLoad = this.authorDataLoad.bind(this); this.authorDataLoad = this.authorDataLoad.bind(this);
this.onAuthorPreviewItemsCountCallback = this.onAuthorPreviewItemsCountCallback.bind(this); this.onAuthorPreviewItemsCountCallback = this.onAuthorPreviewItemsCountCallback.bind(this);
this.getCountFunc = this.getCountFunc.bind(this); this.getCountFunc = this.getCountFunc.bind(this);
this.changeRequestQuery = this.changeRequestQuery.bind(this); this.changeRequestQuery = this.changeRequestQuery.bind(this);
this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this); this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this);
this.onToggleTagsClick = this.onToggleTagsClick.bind(this); this.onToggleTagsClick = this.onToggleTagsClick.bind(this);
this.onToggleSortingClick = this.onToggleSortingClick.bind(this); this.onToggleSortingClick = this.onToggleSortingClick.bind(this);
this.onFiltersUpdate = this.onFiltersUpdate.bind(this); this.onFiltersUpdate = this.onFiltersUpdate.bind(this);
this.onTagSelect = this.onTagSelect.bind(this); this.onTagSelect = this.onTagSelect.bind(this);
this.onSortSelect = this.onSortSelect.bind(this); this.onSortSelect = this.onSortSelect.bind(this);
this.onResponseDataLoaded = this.onResponseDataLoaded.bind(this); this.onResponseDataLoaded = this.onResponseDataLoaded.bind(this);
ProfilePageStore.on('load-author-data', this.authorDataLoad); 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.media + '?author=' + author.id + '&show=shared_by_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
} else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_by_me' + this.state.filterArgs;
}
} }
this.setState({ componentDidMount() {
author: author, ProfilePageActions.load_author_data();
requestUrl: requestUrl, }
});
}
onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) { authorDataLoad() {
this.setState({ const author = ProfilePageStore.get('author-data');
uploadsPreviewItemsCount: totalAuthorPreviewItems,
});
}
getCountFunc(count) { let requestUrl = this.state.requestUrl;
this.setState(
{
channelMediaCount: count,
},
() => {
if (this.state.query) {
let title = '';
if (!count) { if (author) {
title = 'No results for "' + this.state.query + '"'; if (this.state.query) {
} else if (1 === count) { requestUrl =
title = '1 result for "' + this.state.query + '"'; ApiUrlContext._currentValue.media +
} else { '?author=' +
title = count + ' results for "' + this.state.query + '"'; author.id +
} '&show=shared_by_me&q=' +
encodeURIComponent(this.state.query) +
this.setState({ this.state.filterArgs;
title: title, } else {
}); requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
author.id +
'&show=shared_by_me' +
this.state.filterArgs;
}
} }
}
);
}
changeRequestQuery(newQuery) { this.setState({
if (!this.state.author) { author: author,
return; requestUrl: requestUrl,
});
} }
let requestUrl; onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) {
this.setState({
if (newQuery) { uploadsPreviewItemsCount: totalAuthorPreviewItems,
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me&q=' + encodeURIComponent(newQuery) + this.state.filterArgs; });
} else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me' + this.state.filterArgs;
} }
let title = this.state.title; getCountFunc(count) {
this.setState(
{
channelMediaCount: count,
},
() => {
if (this.state.query) {
let title = '';
if ('' === newQuery) { if (!count) {
title = this.props.title; 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,
});
}
}
);
} }
this.setState({ changeRequestQuery(newQuery) {
requestUrl: requestUrl,
query: newQuery,
title: title,
});
}
onToggleFiltersClick() {
this.setState({
hiddenFilters: !this.state.hiddenFilters,
hiddenTags: true,
hiddenSorting: true,
});
}
onToggleTagsClick() {
this.setState({
hiddenFilters: true,
hiddenTags: !this.state.hiddenTags,
hiddenSorting: true,
});
}
onToggleSortingClick() {
this.setState({
hiddenFilters: true,
hiddenTags: true,
hiddenSorting: !this.state.hiddenSorting,
});
}
onTagSelect(tag) {
this.setState({ selectedTag: tag }, () => {
this.onFiltersUpdate({
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
sort_by: this.state.selectedSort,
tag: tag,
});
});
}
onSortSelect(sortBy) {
this.setState({ selectedSort: sortBy }, () => {
this.onFiltersUpdate({
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
sort_by: sortBy,
tag: this.state.selectedTag,
});
});
}
onFiltersUpdate(updatedArgs) {
const args = {
media_type: null,
upload_date: null,
duration: null,
publish_state: null,
sort_by: null,
ordering: null,
t: 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;
}
// Handle duration filter
if (updatedArgs.duration && updatedArgs.duration !== 'all') {
args.duration = updatedArgs.duration;
}
// Handle publish state filter
if (updatedArgs.publish_state && updatedArgs.publish_state !== 'all') {
args.publish_state = updatedArgs.publish_state;
}
switch (updatedArgs.sort_by) {
case 'date_added_desc':
// Default sorting, no need to add parameters
break;
case 'date_added_asc':
args.ordering = 'asc';
break;
case 'alphabetically_asc':
args.sort_by = 'title_asc';
break;
case 'alphabetically_desc':
args.sort_by = 'title_desc';
break;
case 'plays_least':
args.sort_by = 'views_asc';
break;
case 'plays_most':
args.sort_by = 'views_desc';
break;
case 'likes_least':
args.sort_by = 'likes_asc';
break;
case 'likes_most':
args.sort_by = 'likes_desc';
break;
}
if (updatedArgs.tag && updatedArgs.tag !== 'all') {
args.t = updatedArgs.tag;
}
const newArgs = [];
for (let arg in args) {
if (null !== args[arg]) {
newArgs.push(arg + '=' + args[arg]);
}
}
this.setState(
{
filterArgs: newArgs.length ? '&' + newArgs.join('&') : '',
},
function () {
if (!this.state.author) { if (!this.state.author) {
return; return;
} }
let requestUrl; let requestUrl;
if (this.state.query) { if (newQuery) {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_by_me&q=' +
encodeURIComponent(newQuery) +
this.state.filterArgs;
} else { } else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me' + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_by_me' +
this.state.filterArgs;
}
let title = this.state.title;
if ('' === newQuery) {
title = this.props.title;
} }
this.setState({ this.setState({
requestUrl: requestUrl, requestUrl: requestUrl,
query: newQuery,
title: title,
}); });
}
);
}
onResponseDataLoaded(responseData) {
if (responseData && responseData.tags) {
const tags = responseData.tags.split(',').map((tag) => tag.trim()).filter((tag) => tag);
this.setState({ availableTags: tags });
} }
}
pageContent() { onToggleFiltersClick() {
const authorData = ProfilePageStore.get('author-data'); this.setState({
hiddenFilters: !this.state.hiddenFilters,
hiddenTags: true,
hiddenSorting: true,
});
}
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username; onToggleTagsClick() {
this.setState({
hiddenFilters: true,
hiddenTags: !this.state.hiddenTags,
hiddenSorting: true,
});
}
// Check if any filters are active onToggleSortingClick() {
const hasActiveFilters = this.state.filterArgs && ( this.setState({
this.state.filterArgs.includes('media_type=') || hiddenFilters: true,
this.state.filterArgs.includes('upload_date=') || hiddenTags: true,
this.state.filterArgs.includes('duration=') || hiddenSorting: !this.state.hiddenSorting,
this.state.filterArgs.includes('publish_state=') });
); }
return [ onTagSelect(tag) {
this.state.author ? ( this.setState({ selectedTag: tag }, () => {
<ProfilePagesHeader this.onFiltersUpdate({
key="ProfilePagesHeader" media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
author={this.state.author} upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
type="shared_by_me" duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
onQueryChange={this.changeRequestQuery} publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
onToggleFiltersClick={this.onToggleFiltersClick} sort_by: this.state.selectedSort,
onToggleTagsClick={this.onToggleTagsClick} tag: tag,
onToggleSortingClick={this.onToggleSortingClick} });
hasActiveFilters={hasActiveFilters} });
hasActiveTags={this.state.selectedTag !== 'all'} }
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
/> onSortSelect(sortBy) {
) : null, this.setState({ selectedSort: sortBy }, () => {
this.state.author ? ( this.onFiltersUpdate({
<ProfilePagesContent key="ProfilePagesContent"> media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
<MediaListWrapper upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
title={this.state.title} duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
className="items-list-ver" publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
showBulkActions={isMediaAuthor} sort_by: sortBy,
selectedCount={this.props.bulkActions.selectedMedia.size} tag: this.state.selectedTag,
totalCount={this.props.bulkActions.availableMediaIds.length} });
onBulkAction={this.props.bulkActions.handleBulkAction} });
onSelectAll={this.props.bulkActions.handleSelectAll} }
onDeselectAll={this.props.bulkActions.handleDeselectAll}
> onFiltersUpdate(updatedArgs) {
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} /> const args = {
<ProfileMediaTags hidden={this.state.hiddenTags} tags={this.state.availableTags} onTagSelect={this.onTagSelect} /> media_type: null,
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} /> upload_date: null,
<LazyLoadItemListAsync duration: null,
key={`${this.state.requestUrl}-${this.props.bulkActions.listKey}`} publish_state: null,
requestUrl={this.state.requestUrl} sort_by: null,
hideAuthor={true} ordering: null,
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null} t: null,
hideViews={!PageStore.get('config-media-item').displayViews} };
hideDate={!PageStore.get('config-media-item').displayPublishDate}
canEdit={isMediaAuthor} switch (updatedArgs.media_type) {
onResponseDataLoaded={this.onResponseDataLoaded} case 'video':
showSelection={isMediaAuthor} case 'audio':
hasAnySelection={this.props.bulkActions.selectedMedia.size > 0} case 'image':
selectedMedia={this.props.bulkActions.selectedMedia} case 'pdf':
onMediaSelection={this.props.bulkActions.handleMediaSelection} args.media_type = updatedArgs.media_type;
onItemsUpdate={this.props.bulkActions.handleItemsUpdate} break;
/> }
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
<EmptySharedByMe name={this.state.author.name} /> switch (updatedArgs.upload_date) {
) : null} case 'today':
</MediaListWrapper> case 'this_week':
</ProfilePagesContent> case 'this_month':
) : null, case 'this_year':
this.state.author && isMediaAuthor ? ( args.upload_date = updatedArgs.upload_date;
<BulkActionsModals break;
key="BulkActionsModals" }
{...this.props.bulkActions}
selectedMediaIds={Array.from(this.props.bulkActions.selectedMedia)} // Handle duration filter
csrfToken={this.props.bulkActions.getCsrfToken()} if (updatedArgs.duration && updatedArgs.duration !== 'all') {
username={this.state.author.username} args.duration = updatedArgs.duration;
onConfirmCancel={this.props.bulkActions.handleConfirmCancel} }
onConfirmProceed={this.props.bulkActions.handleConfirmProceed}
onPermissionModalCancel={this.props.bulkActions.handlePermissionModalCancel} // Handle publish state filter
onPermissionModalSuccess={this.props.bulkActions.handlePermissionModalSuccess} if (updatedArgs.publish_state && updatedArgs.publish_state !== 'all') {
onPermissionModalError={this.props.bulkActions.handlePermissionModalError} args.publish_state = updatedArgs.publish_state;
onPlaylistModalCancel={this.props.bulkActions.handlePlaylistModalCancel} }
onPlaylistModalSuccess={this.props.bulkActions.handlePlaylistModalSuccess}
onPlaylistModalError={this.props.bulkActions.handlePlaylistModalError} switch (updatedArgs.sort_by) {
onChangeOwnerModalCancel={this.props.bulkActions.handleChangeOwnerModalCancel} case 'date_added_desc':
onChangeOwnerModalSuccess={this.props.bulkActions.handleChangeOwnerModalSuccess} // Default sorting, no need to add parameters
onChangeOwnerModalError={this.props.bulkActions.handleChangeOwnerModalError} break;
onPublishStateModalCancel={this.props.bulkActions.handlePublishStateModalCancel} case 'date_added_asc':
onPublishStateModalSuccess={this.props.bulkActions.handlePublishStateModalSuccess} args.ordering = 'asc';
onPublishStateModalError={this.props.bulkActions.handlePublishStateModalError} break;
onCategoryModalCancel={this.props.bulkActions.handleCategoryModalCancel} case 'alphabetically_asc':
onCategoryModalSuccess={this.props.bulkActions.handleCategoryModalSuccess} args.sort_by = 'title_asc';
onCategoryModalError={this.props.bulkActions.handleCategoryModalError} break;
onTagModalCancel={this.props.bulkActions.handleTagModalCancel} case 'alphabetically_desc':
onTagModalSuccess={this.props.bulkActions.handleTagModalSuccess} args.sort_by = 'title_desc';
onTagModalError={this.props.bulkActions.handleTagModalError} break;
/> case 'plays_least':
) : null, args.sort_by = 'views_asc';
]; break;
} case 'plays_most':
args.sort_by = 'views_desc';
break;
case 'likes_least':
args.sort_by = 'likes_asc';
break;
case 'likes_most':
args.sort_by = 'likes_desc';
break;
}
if (updatedArgs.tag && updatedArgs.tag !== 'all') {
args.t = updatedArgs.tag;
}
const newArgs = [];
for (let arg in args) {
if (null !== args[arg]) {
newArgs.push(arg + '=' + args[arg]);
}
}
this.setState(
{
filterArgs: newArgs.length ? '&' + newArgs.join('&') : '',
},
function () {
if (!this.state.author) {
return;
}
let requestUrl;
if (this.state.query) {
requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_by_me&q=' +
encodeURIComponent(this.state.query) +
this.state.filterArgs;
} else {
requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_by_me' +
this.state.filterArgs;
}
this.setState({
requestUrl: requestUrl,
});
}
);
}
onResponseDataLoaded(responseData) {
if (responseData && responseData.tags) {
const tags = responseData.tags
.split(',')
.map((tag) => tag.trim())
.filter((tag) => tag);
this.setState({ availableTags: tags });
}
}
pageContent() {
const authorData = ProfilePageStore.get('author-data');
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
// Check if any filters are active
const hasActiveFilters =
this.state.filterArgs &&
(this.state.filterArgs.includes('media_type=') ||
this.state.filterArgs.includes('upload_date=') ||
this.state.filterArgs.includes('duration=') ||
this.state.filterArgs.includes('publish_state='));
return [
this.state.author ? (
<ProfilePagesHeader
key="ProfilePagesHeader"
author={this.state.author}
type="shared_by_me"
onQueryChange={this.changeRequestQuery}
onToggleFiltersClick={this.onToggleFiltersClick}
onToggleTagsClick={this.onToggleTagsClick}
onToggleSortingClick={this.onToggleSortingClick}
hasActiveFilters={hasActiveFilters}
hasActiveTags={this.state.selectedTag !== 'all'}
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
hideChannelBanner={inEmbeddedApp()}
/>
) : null,
this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent">
<MediaListWrapper
title={this.state.title}
className="items-list-ver"
showBulkActions={isMediaAuthor}
selectedCount={this.props.bulkActions.selectedMedia.size}
totalCount={this.props.bulkActions.availableMediaIds.length}
onBulkAction={this.props.bulkActions.handleBulkAction}
onSelectAll={this.props.bulkActions.handleSelectAll}
onDeselectAll={this.props.bulkActions.handleDeselectAll}
>
<ProfileMediaFilters
hidden={this.state.hiddenFilters}
tags={this.state.availableTags}
onFiltersUpdate={this.onFiltersUpdate}
/>
<ProfileMediaTags
hidden={this.state.hiddenTags}
tags={this.state.availableTags}
onTagSelect={this.onTagSelect}
/>
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
<LazyLoadItemListAsync
key={`${this.state.requestUrl}-${this.props.bulkActions.listKey}`}
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}
onResponseDataLoaded={this.onResponseDataLoaded}
showSelection={isMediaAuthor}
hasAnySelection={this.props.bulkActions.selectedMedia.size > 0}
selectedMedia={this.props.bulkActions.selectedMedia}
onMediaSelection={this.props.bulkActions.handleMediaSelection}
onItemsUpdate={this.props.bulkActions.handleItemsUpdate}
/>
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
<EmptySharedByMe name={this.state.author.name} />
) : null}
</MediaListWrapper>
</ProfilePagesContent>
) : null,
this.state.author && isMediaAuthor ? (
<BulkActionsModals
key="BulkActionsModals"
{...this.props.bulkActions}
selectedMediaIds={Array.from(this.props.bulkActions.selectedMedia)}
csrfToken={this.props.bulkActions.getCsrfToken()}
username={this.state.author.username}
onConfirmCancel={this.props.bulkActions.handleConfirmCancel}
onConfirmProceed={this.props.bulkActions.handleConfirmProceed}
onPermissionModalCancel={this.props.bulkActions.handlePermissionModalCancel}
onPermissionModalSuccess={this.props.bulkActions.handlePermissionModalSuccess}
onPermissionModalError={this.props.bulkActions.handlePermissionModalError}
onPlaylistModalCancel={this.props.bulkActions.handlePlaylistModalCancel}
onPlaylistModalSuccess={this.props.bulkActions.handlePlaylistModalSuccess}
onPlaylistModalError={this.props.bulkActions.handlePlaylistModalError}
onChangeOwnerModalCancel={this.props.bulkActions.handleChangeOwnerModalCancel}
onChangeOwnerModalSuccess={this.props.bulkActions.handleChangeOwnerModalSuccess}
onChangeOwnerModalError={this.props.bulkActions.handleChangeOwnerModalError}
onPublishStateModalCancel={this.props.bulkActions.handlePublishStateModalCancel}
onPublishStateModalSuccess={this.props.bulkActions.handlePublishStateModalSuccess}
onPublishStateModalError={this.props.bulkActions.handlePublishStateModalError}
onCategoryModalCancel={this.props.bulkActions.handleCategoryModalCancel}
onCategoryModalSuccess={this.props.bulkActions.handleCategoryModalSuccess}
onCategoryModalError={this.props.bulkActions.handleCategoryModalError}
onTagModalCancel={this.props.bulkActions.handleTagModalCancel}
onTagModalSuccess={this.props.bulkActions.handleTagModalSuccess}
onTagModalError={this.props.bulkActions.handleTagModalError}
/>
) : null,
];
}
} }
ProfileSharedByMePage.propTypes = { ProfileSharedByMePage.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
bulkActions: PropTypes.object.isRequired, bulkActions: PropTypes.object.isRequired,
}; };
ProfileSharedByMePage.defaultProps = { ProfileSharedByMePage.defaultProps = {
title: 'Shared by me', title: 'Shared by me',
}; };
// Wrap with HOC and export as named export for compatibility // Wrap with HOC and export as named export for compatibility

View File

@@ -10,364 +10,404 @@ import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListA
import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFilters'; import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFilters';
import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags'; import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags';
import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting'; import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting';
import { translateString } from '../utils/helpers'; import { inEmbeddedApp, translateString } from '../utils/helpers';
import { Page } from './_Page'; import { Page } from './_Page';
import '../components/profile-page/ProfilePage.scss'; import '../components/profile-page/ProfilePage.scss';
function EmptySharedWithMe(props) { function EmptySharedWithMe(props) {
return ( return (
<LinksConsumer> <LinksConsumer>
{(links) => ( {(links) => (
<div className="empty-media empty-channel-media"> <div className="empty-media empty-channel-media">
<div className="welcome-title">No shared media</div> <div className="welcome-title">No shared media</div>
<div className="start-uploading"> <div className="start-uploading">Media that others have shared with you will show up here.</div>
Media that others have shared with you will show up here. </div>
</div> )}
</div> </LinksConsumer>
)} );
</LinksConsumer>
);
} }
export class ProfileSharedWithMePage extends Page { export class ProfileSharedWithMePage extends Page {
constructor(props, pageSlug) { constructor(props, pageSlug) {
super(props, 'string' === typeof pageSlug ? pageSlug : 'author-shared-with-me'); super(props, 'string' === typeof pageSlug ? pageSlug : 'author-shared-with-me');
this.profilePageSlug = 'string' === typeof pageSlug ? pageSlug : 'author-shared-with-me'; this.profilePageSlug = 'string' === typeof pageSlug ? pageSlug : 'author-shared-with-me';
this.state = { this.state = {
channelMediaCount: -1, channelMediaCount: -1,
author: ProfilePageStore.get('author-data'), author: ProfilePageStore.get('author-data'),
uploadsPreviewItemsCount: 0, uploadsPreviewItemsCount: 0,
title: this.props.title, title: this.props.title,
query: ProfilePageStore.get('author-query'), query: ProfilePageStore.get('author-query'),
requestUrl: null, requestUrl: null,
hiddenFilters: true, hiddenFilters: true,
hiddenTags: true, hiddenTags: true,
hiddenSorting: true, hiddenSorting: true,
filterArgs: '', filterArgs: '',
availableTags: [], availableTags: [],
selectedTag: 'all', selectedTag: 'all',
selectedSort: 'date_added_desc', selectedSort: 'date_added_desc',
}; };
this.authorDataLoad = this.authorDataLoad.bind(this); this.authorDataLoad = this.authorDataLoad.bind(this);
this.onAuthorPreviewItemsCountCallback = this.onAuthorPreviewItemsCountCallback.bind(this); this.onAuthorPreviewItemsCountCallback = this.onAuthorPreviewItemsCountCallback.bind(this);
this.getCountFunc = this.getCountFunc.bind(this); this.getCountFunc = this.getCountFunc.bind(this);
this.changeRequestQuery = this.changeRequestQuery.bind(this); this.changeRequestQuery = this.changeRequestQuery.bind(this);
this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this); this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this);
this.onToggleTagsClick = this.onToggleTagsClick.bind(this); this.onToggleTagsClick = this.onToggleTagsClick.bind(this);
this.onToggleSortingClick = this.onToggleSortingClick.bind(this); this.onToggleSortingClick = this.onToggleSortingClick.bind(this);
this.onFiltersUpdate = this.onFiltersUpdate.bind(this); this.onFiltersUpdate = this.onFiltersUpdate.bind(this);
this.onTagSelect = this.onTagSelect.bind(this); this.onTagSelect = this.onTagSelect.bind(this);
this.onSortSelect = this.onSortSelect.bind(this); this.onSortSelect = this.onSortSelect.bind(this);
this.onResponseDataLoaded = this.onResponseDataLoaded.bind(this); this.onResponseDataLoaded = this.onResponseDataLoaded.bind(this);
ProfilePageStore.on('load-author-data', this.authorDataLoad); 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.media + '?author=' + author.id + '&show=shared_with_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
} else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_with_me' + this.state.filterArgs;
}
} }
this.setState({ componentDidMount() {
author: author, ProfilePageActions.load_author_data();
requestUrl: requestUrl, }
});
}
onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) { authorDataLoad() {
this.setState({ const author = ProfilePageStore.get('author-data');
uploadsPreviewItemsCount: totalAuthorPreviewItems,
});
}
getCountFunc(count) { let requestUrl = this.state.requestUrl;
this.setState(
{
channelMediaCount: count,
},
() => {
if (this.state.query) {
let title = '';
if (!count) { if (author) {
title = 'No results for "' + this.state.query + '"'; if (this.state.query) {
} else if (1 === count) { requestUrl =
title = '1 result for "' + this.state.query + '"'; ApiUrlContext._currentValue.media +
} else { '?author=' +
title = count + ' results for "' + this.state.query + '"'; author.id +
} '&show=shared_with_me&q=' +
encodeURIComponent(this.state.query) +
this.setState({ this.state.filterArgs;
title: title, } else {
}); requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
author.id +
'&show=shared_with_me' +
this.state.filterArgs;
}
} }
}
);
}
changeRequestQuery(newQuery) { this.setState({
if (!this.state.author) { author: author,
return; requestUrl: requestUrl,
});
} }
let requestUrl; onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) {
this.setState({
if (newQuery) { uploadsPreviewItemsCount: totalAuthorPreviewItems,
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me&q=' + encodeURIComponent(newQuery) + this.state.filterArgs; });
} else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me' + this.state.filterArgs;
} }
let title = this.state.title; getCountFunc(count) {
this.setState(
{
channelMediaCount: count,
},
() => {
if (this.state.query) {
let title = '';
if ('' === newQuery) { if (!count) {
title = this.props.title; 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,
});
}
}
);
} }
this.setState({ changeRequestQuery(newQuery) {
requestUrl: requestUrl,
query: newQuery,
title: title,
});
}
onToggleFiltersClick() {
this.setState({
hiddenFilters: !this.state.hiddenFilters,
hiddenTags: true,
hiddenSorting: true,
});
}
onToggleTagsClick() {
this.setState({
hiddenFilters: true,
hiddenTags: !this.state.hiddenTags,
hiddenSorting: true,
});
}
onToggleSortingClick() {
this.setState({
hiddenFilters: true,
hiddenTags: true,
hiddenSorting: !this.state.hiddenSorting,
});
}
onTagSelect(tag) {
this.setState({ selectedTag: tag }, () => {
this.onFiltersUpdate({
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
sort_by: this.state.selectedSort,
tag: tag,
});
});
}
onSortSelect(sortBy) {
this.setState({ selectedSort: sortBy }, () => {
this.onFiltersUpdate({
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
sort_by: sortBy,
tag: this.state.selectedTag,
});
});
}
onFiltersUpdate(updatedArgs) {
const args = {
media_type: null,
upload_date: null,
duration: null,
publish_state: null,
sort_by: null,
ordering: null,
t: 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;
}
// Handle duration filter
if (updatedArgs.duration && updatedArgs.duration !== 'all') {
args.duration = updatedArgs.duration;
}
// Handle publish state filter
if (updatedArgs.publish_state && updatedArgs.publish_state !== 'all') {
args.publish_state = updatedArgs.publish_state;
}
switch (updatedArgs.sort_by) {
case 'date_added_desc':
// Default sorting, no need to add parameters
break;
case 'date_added_asc':
args.ordering = 'asc';
break;
case 'alphabetically_asc':
args.sort_by = 'title_asc';
break;
case 'alphabetically_desc':
args.sort_by = 'title_desc';
break;
case 'plays_least':
args.sort_by = 'views_asc';
break;
case 'plays_most':
args.sort_by = 'views_desc';
break;
case 'likes_least':
args.sort_by = 'likes_asc';
break;
case 'likes_most':
args.sort_by = 'likes_desc';
break;
}
if (updatedArgs.tag && updatedArgs.tag !== 'all') {
args.t = updatedArgs.tag;
}
const newArgs = [];
for (let arg in args) {
if (null !== args[arg]) {
newArgs.push(arg + '=' + args[arg]);
}
}
this.setState(
{
filterArgs: newArgs.length ? '&' + newArgs.join('&') : '',
},
function () {
if (!this.state.author) { if (!this.state.author) {
return; return;
} }
let requestUrl; let requestUrl;
if (this.state.query) { if (newQuery) {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_with_me&q=' +
encodeURIComponent(newQuery) +
this.state.filterArgs;
} else { } else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me' + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_with_me' +
this.state.filterArgs;
}
let title = this.state.title;
if ('' === newQuery) {
title = this.props.title;
} }
this.setState({ this.setState({
requestUrl: requestUrl, requestUrl: requestUrl,
query: newQuery,
title: title,
}); });
}
);
}
onResponseDataLoaded(responseData) {
if (responseData && responseData.tags) {
const tags = responseData.tags.split(',').map((tag) => tag.trim()).filter((tag) => tag);
this.setState({ availableTags: tags });
} }
}
pageContent() { onToggleFiltersClick() {
const authorData = ProfilePageStore.get('author-data'); this.setState({
hiddenFilters: !this.state.hiddenFilters,
hiddenTags: true,
hiddenSorting: true,
});
}
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username; onToggleTagsClick() {
this.setState({
hiddenFilters: true,
hiddenTags: !this.state.hiddenTags,
hiddenSorting: true,
});
}
// Check if any filters are active onToggleSortingClick() {
const hasActiveFilters = this.state.filterArgs && ( this.setState({
this.state.filterArgs.includes('media_type=') || hiddenFilters: true,
this.state.filterArgs.includes('upload_date=') || hiddenTags: true,
this.state.filterArgs.includes('duration=') || hiddenSorting: !this.state.hiddenSorting,
this.state.filterArgs.includes('publish_state=') });
); }
return [ onTagSelect(tag) {
this.state.author ? ( this.setState({ selectedTag: tag }, () => {
<ProfilePagesHeader this.onFiltersUpdate({
key="ProfilePagesHeader" media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
author={this.state.author} upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
type="shared_with_me" duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
onQueryChange={this.changeRequestQuery} publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
onToggleFiltersClick={this.onToggleFiltersClick} sort_by: this.state.selectedSort,
onToggleTagsClick={this.onToggleTagsClick} tag: tag,
onToggleSortingClick={this.onToggleSortingClick} });
hasActiveFilters={hasActiveFilters} });
hasActiveTags={this.state.selectedTag !== 'all'} }
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
/> onSortSelect(sortBy) {
) : null, this.setState({ selectedSort: sortBy }, () => {
this.state.author ? ( this.onFiltersUpdate({
<ProfilePagesContent key="ProfilePagesContent"> media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
<MediaListWrapper upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
title={this.state.title} duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
className="items-list-ver" publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
> sort_by: sortBy,
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} /> tag: this.state.selectedTag,
<ProfileMediaTags hidden={this.state.hiddenTags} tags={this.state.availableTags} onTagSelect={this.onTagSelect} /> });
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} /> });
<LazyLoadItemListAsync }
key={this.state.requestUrl}
requestUrl={this.state.requestUrl} onFiltersUpdate(updatedArgs) {
hideAuthor={true} const args = {
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null} media_type: null,
hideViews={!PageStore.get('config-media-item').displayViews} upload_date: null,
hideDate={!PageStore.get('config-media-item').displayPublishDate} duration: null,
canEdit={false} publish_state: null,
onResponseDataLoaded={this.onResponseDataLoaded} sort_by: null,
/> ordering: null,
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? ( t: null,
<EmptySharedWithMe name={this.state.author.name} /> };
) : null}
</MediaListWrapper> switch (updatedArgs.media_type) {
</ProfilePagesContent> case 'video':
) : null, 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;
}
// Handle duration filter
if (updatedArgs.duration && updatedArgs.duration !== 'all') {
args.duration = updatedArgs.duration;
}
// Handle publish state filter
if (updatedArgs.publish_state && updatedArgs.publish_state !== 'all') {
args.publish_state = updatedArgs.publish_state;
}
switch (updatedArgs.sort_by) {
case 'date_added_desc':
// Default sorting, no need to add parameters
break;
case 'date_added_asc':
args.ordering = 'asc';
break;
case 'alphabetically_asc':
args.sort_by = 'title_asc';
break;
case 'alphabetically_desc':
args.sort_by = 'title_desc';
break;
case 'plays_least':
args.sort_by = 'views_asc';
break;
case 'plays_most':
args.sort_by = 'views_desc';
break;
case 'likes_least':
args.sort_by = 'likes_asc';
break;
case 'likes_most':
args.sort_by = 'likes_desc';
break;
}
if (updatedArgs.tag && updatedArgs.tag !== 'all') {
args.t = updatedArgs.tag;
}
const newArgs = [];
for (let arg in args) {
if (null !== args[arg]) {
newArgs.push(arg + '=' + args[arg]);
}
}
this.setState(
{
filterArgs: newArgs.length ? '&' + newArgs.join('&') : '',
},
function () {
if (!this.state.author) {
return;
}
let requestUrl;
if (this.state.query) {
requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_with_me&q=' +
encodeURIComponent(this.state.query) +
this.state.filterArgs;
} else {
requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_with_me' +
this.state.filterArgs;
}
this.setState({
requestUrl: requestUrl,
});
}
);
}
onResponseDataLoaded(responseData) {
if (responseData && responseData.tags) {
const tags = responseData.tags
.split(',')
.map((tag) => tag.trim())
.filter((tag) => tag);
this.setState({ availableTags: tags });
}
}
pageContent() {
const authorData = ProfilePageStore.get('author-data');
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
// Check if any filters are active
const hasActiveFilters =
this.state.filterArgs &&
(this.state.filterArgs.includes('media_type=') ||
this.state.filterArgs.includes('upload_date=') ||
this.state.filterArgs.includes('duration=') ||
this.state.filterArgs.includes('publish_state='));
return [
this.state.author ? (
<ProfilePagesHeader
key="ProfilePagesHeader"
author={this.state.author}
type="shared_with_me"
onQueryChange={this.changeRequestQuery}
onToggleFiltersClick={this.onToggleFiltersClick}
onToggleTagsClick={this.onToggleTagsClick}
onToggleSortingClick={this.onToggleSortingClick}
hasActiveFilters={hasActiveFilters}
hasActiveTags={this.state.selectedTag !== 'all'}
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
hideChannelBanner={inEmbeddedApp()}
/>
) : null,
this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent">
<MediaListWrapper title={this.state.title} className="items-list-ver">
<ProfileMediaFilters
hidden={this.state.hiddenFilters}
tags={this.state.availableTags}
onFiltersUpdate={this.onFiltersUpdate}
/>
<ProfileMediaTags
hidden={this.state.hiddenTags}
tags={this.state.availableTags}
onTagSelect={this.onTagSelect}
/>
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
<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={false}
onResponseDataLoaded={this.onResponseDataLoaded}
/>
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
<EmptySharedWithMe name={this.state.author.name} />
) : null}
</MediaListWrapper>
</ProfilePagesContent>
) : null,
];
}
} }
ProfileSharedWithMePage.propTypes = { ProfileSharedWithMePage.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
}; };
ProfileSharedWithMePage.defaultProps = { ProfileSharedWithMePage.defaultProps = {
title: 'Shared with me', title: 'Shared with me',
}; };

View File

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

View File

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

View File

@@ -1,101 +1,103 @@
import React, { createContext, useContext, useEffect, useState } from 'react'; import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { BrowserCache } from '../classes/'; import { BrowserCache } from '../classes/';
import { PageStore } from '../stores/'; import { PageStore } from '../stores/';
import { addClassname, removeClassname } from '../helpers/'; import { addClassname, removeClassname, inEmbeddedApp } from '../helpers/';
import SiteContext from './SiteContext'; import SiteContext from './SiteContext';
let slidingSidebarTimeout; let slidingSidebarTimeout;
function onSidebarVisibilityChange(visibleSidebar) { function onSidebarVisibilityChange(visibleSidebar) {
clearTimeout(slidingSidebarTimeout); clearTimeout(slidingSidebarTimeout);
addClassname(document.body, 'sliding-sidebar'); addClassname(document.body, 'sliding-sidebar');
slidingSidebarTimeout = setTimeout(function () {
if ('media' === PageStore.get('current-page')) {
if (visibleSidebar) {
addClassname(document.body, 'overflow-hidden');
} else {
removeClassname(document.body, 'overflow-hidden');
}
} else {
if (!visibleSidebar || 767 < window.innerWidth) {
removeClassname(document.body, 'overflow-hidden');
} else {
addClassname(document.body, 'overflow-hidden');
}
}
if (visibleSidebar) {
addClassname(document.body, 'visible-sidebar');
} else {
removeClassname(document.body, 'visible-sidebar');
}
slidingSidebarTimeout = setTimeout(function () { slidingSidebarTimeout = setTimeout(function () {
slidingSidebarTimeout = null; if ('media' === PageStore.get('current-page')) {
removeClassname(document.body, 'sliding-sidebar'); if (visibleSidebar) {
}, 220); addClassname(document.body, 'overflow-hidden');
}, 20); } else {
removeClassname(document.body, 'overflow-hidden');
}
} else {
if (!visibleSidebar || 767 < window.innerWidth) {
removeClassname(document.body, 'overflow-hidden');
} else {
addClassname(document.body, 'overflow-hidden');
}
}
if (visibleSidebar) {
addClassname(document.body, 'visible-sidebar');
} else {
removeClassname(document.body, 'visible-sidebar');
}
slidingSidebarTimeout = setTimeout(function () {
slidingSidebarTimeout = null;
removeClassname(document.body, 'sliding-sidebar');
}, 220);
}, 20);
} }
export const LayoutContext = createContext(); export const LayoutContext = createContext();
export const LayoutProvider = ({ children }) => { export const LayoutProvider = ({ children }) => {
const site = useContext(SiteContext); const site = useContext(SiteContext);
const cache = new BrowserCache('MediaCMS[' + site.id + '][layout]', 86400); const cache = new BrowserCache('MediaCMS[' + site.id + '][layout]', 86400);
const enabledSidebar = !!(document.getElementById('app-sidebar') || document.querySelector('.page-sidebar')); const isMediaPage = useMemo(() => PageStore.get('current-page') === 'media', []);
const isEmbeddedApp = useMemo(() => inEmbeddedApp(), []);
const [visibleSidebar, setVisibleSidebar] = useState(cache.get('visible-sidebar')); const enabledSidebar = Boolean(document.getElementById('app-sidebar') || document.querySelector('.page-sidebar'));
const [visibleMobileSearch, setVisibleMobileSearch] = useState(false);
const toggleMobileSearch = () => { const [visibleSidebar, setVisibleSidebar] = useState(cache.get('visible-sidebar'));
setVisibleMobileSearch(!visibleMobileSearch); const [visibleMobileSearch, setVisibleMobileSearch] = useState(false);
};
const toggleSidebar = () => { const toggleMobileSearch = () => {
const newval = !visibleSidebar; setVisibleMobileSearch(!visibleMobileSearch);
onSidebarVisibilityChange(newval); };
setVisibleSidebar(newval);
};
useEffect(() => { const toggleSidebar = () => {
if (visibleSidebar) { const newval = !visibleSidebar;
addClassname(document.body, 'visible-sidebar'); onSidebarVisibilityChange(newval);
} else { setVisibleSidebar(newval);
removeClassname(document.body, 'visible-sidebar'); };
}
if ('media' !== PageStore.get('current-page') && 1023 < window.innerWidth) {
cache.set('visible-sidebar', visibleSidebar);
}
}, [visibleSidebar]);
useEffect(() => { useEffect(() => {
PageStore.once('page_init', () => { if (!isEmbeddedApp && visibleSidebar) {
if ('media' === PageStore.get('current-page')) { addClassname(document.body, 'visible-sidebar');
setVisibleSidebar(false); } else {
removeClassname(document.body, 'visible-sidebar'); removeClassname(document.body, 'visible-sidebar');
} }
});
setVisibleSidebar( if (!isEmbeddedApp && !isMediaPage && 1023 < window.innerWidth) {
'media' !== PageStore.get('current-page') && cache.set('visible-sidebar', visibleSidebar);
1023 < window.innerWidth && }
(null === visibleSidebar || visibleSidebar) }, [isEmbeddedApp, isMediaPage, visibleSidebar]);
);
}, []);
const value = { useEffect(() => {
enabledSidebar, PageStore.once('page_init', () => {
visibleSidebar, if (isEmbeddedApp || isMediaPage) {
setVisibleSidebar, setVisibleSidebar(false);
visibleMobileSearch, removeClassname(document.body, 'visible-sidebar');
toggleMobileSearch, }
toggleSidebar, });
};
return <LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>; setVisibleSidebar(
!isEmbeddedApp && !isMediaPage && 1023 < window.innerWidth && (null === visibleSidebar || visibleSidebar)
);
}, []);
const value = {
enabledSidebar,
visibleSidebar,
setVisibleSidebar,
visibleMobileSearch,
toggleMobileSearch,
toggleSidebar,
};
return <LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>;
}; };
export const LayoutConsumer = LayoutContext.Consumer; export const LayoutConsumer = LayoutContext.Consumer;

View File

@@ -0,0 +1,4 @@
export function inEmbeddedApp() {
const url = new URL(globalThis.location.href);
return url.searchParams.get('mode') === 'embed_mode';
}

View File

@@ -14,3 +14,4 @@ export * from './quickSort';
export * from './requests'; export * from './requests';
export { translateString } from './translate'; export { translateString } from './translate';
export { replaceString } from './replacementStrings'; export { replaceString } from './replacementStrings';
export * from './embeddedApp';

View File

@@ -3,64 +3,82 @@ import ReactDOM from 'react-dom';
import { ThemeProvider } from './contexts/ThemeContext'; import { ThemeProvider } from './contexts/ThemeContext';
import { LayoutProvider } from './contexts/LayoutContext'; import { LayoutProvider } from './contexts/LayoutContext';
import { UserProvider } from './contexts/UserContext'; import { UserProvider } from './contexts/UserContext';
import { inEmbeddedApp } from './helpers';
const AppProviders = ({ children }) => ( const AppProviders = ({ children }) => (
<LayoutProvider> <LayoutProvider>
<ThemeProvider> <ThemeProvider>
<UserProvider>{children}</UserProvider> <UserProvider>{children}</UserProvider>
</ThemeProvider> </ThemeProvider>
</LayoutProvider> </LayoutProvider>
); );
import { PageHeader, PageSidebar } from '../components/page-layout'; import { PageHeader, PageSidebar } from '../components/page-layout';
export function renderPage(idSelector, PageComponent) { export function renderPage(idSelector, PageComponent) {
const appHeader = document.getElementById('app-header'); const appContent = idSelector ? document.getElementById(idSelector) : undefined;
const appSidebar = document.getElementById('app-sidebar');
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
if (appContent && PageComponent) { if (inEmbeddedApp() && appContent) {
ReactDOM.render( globalThis.document.body.classList.add('embedded-app');
<AppProviders> globalThis.document.body.classList.remove('visible-sidebar');
{appHeader ? ReactDOM.createPortal(<PageHeader />, appHeader) : null}
{appSidebar ? ReactDOM.createPortal(<PageSidebar />, appSidebar) : null} if (PageComponent) {
<PageComponent /> ReactDOM.render(
</AppProviders>, <AppProviders>
appContent <PageComponent />
); </AppProviders>,
} else if (appHeader && appSidebar) { appContent
ReactDOM.render( );
<AppProviders> }
{ReactDOM.createPortal(<PageHeader />, appHeader)}
<PageSidebar /> return;
</AppProviders>, }
appSidebar
); const appHeader = document.getElementById('app-header');
} else if (appHeader) { const appSidebar = document.getElementById('app-sidebar');
ReactDOM.render(
<LayoutProvider> if (appContent && PageComponent) {
<ThemeProvider> ReactDOM.render(
<UserProvider> <AppProviders>
<PageHeader /> {appHeader ? ReactDOM.createPortal(<PageHeader />, appHeader) : null}
</UserProvider> {appSidebar ? ReactDOM.createPortal(<PageSidebar />, appSidebar) : null}
</ThemeProvider> <PageComponent />
</LayoutProvider>, </AppProviders>,
appSidebar appContent
); );
} else if (appSidebar) { } else if (appHeader && appSidebar) {
ReactDOM.render( ReactDOM.render(
<AppProviders> <AppProviders>
<PageSidebar /> {ReactDOM.createPortal(<PageHeader />, appHeader)}
</AppProviders>, <PageSidebar />
appSidebar </AppProviders>,
); appSidebar
} );
} else if (appHeader) {
ReactDOM.render(
<LayoutProvider>
<ThemeProvider>
<UserProvider>
<PageHeader />
</UserProvider>
</ThemeProvider>
</LayoutProvider>,
appSidebar
);
} else if (appSidebar) {
ReactDOM.render(
<AppProviders>
<PageSidebar />
</AppProviders>,
appSidebar
);
}
} }
export function renderEmbedPage(idSelector, PageComponent) { export function renderEmbedPage(idSelector, PageComponent) {
const appContent = idSelector ? document.getElementById(idSelector) : undefined; const appContent = idSelector ? document.getElementById(idSelector) : undefined;
if (appContent && PageComponent) { if (appContent && PageComponent) {
ReactDOM.render(<PageComponent />, appContent); ReactDOM.render(<PageComponent />, appContent);
} }
} }