Merge branch 'main' into feat-lti-integration-select

This commit is contained in:
Markos Gogoulos
2026-01-31 15:33:27 +02:00
41 changed files with 5081 additions and 4831 deletions

View File

@@ -1 +1 @@
VERSION = "7.8124" VERSION = "8"

File diff suppressed because it is too large Load Diff

View File

@@ -3,253 +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) {
const friendlyToken = window.MediaCMS.mediaId; let link = props.link;
return ( if (window.MediaCMS.site.devEnv) {
<a href={`/edit?m=${friendlyToken}`} className="edit-media-icon" title={translateString('Edit metadata')}> link = '/edit-media.html';
<i className="material-icons">edit</i> }
</a>
); return (
<a href={link} rel="nofollow" title={translateString('Edit media')} className="edit-media-icon">
<i className="material-icons">edit</i>
</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 /> : 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,107 +1,119 @@
import React from 'react'; import React from 'react';
import { formatViewsNumber } from '../../utils/helpers/'; import { formatViewsNumber, inEmbeddedApp } from '../../utils/helpers/';
import { PageStore, MediaPageStore } from '../../utils/stores/'; import { PageStore, MediaPageStore } from '../../utils/stores/';
import { MemberContext, PlaylistsContext } from '../../utils/contexts/'; import { MemberContext, PlaylistsContext } from '../../utils/contexts/';
import { MediaLikeIcon, MediaDislikeIcon, OtherMediaDownloadLink, VideoMediaDownloadLink, MediaSaveButton, MediaShareButton, MediaMoreOptionsIcon } from '../media-actions/'; import {
MediaLikeIcon,
MediaDislikeIcon,
OtherMediaDownloadLink,
VideoMediaDownloadLink,
MediaSaveButton,
MediaShareButton,
MediaMoreOptionsIcon,
} from '../media-actions/';
import ViewerInfoTitleBanner from './ViewerInfoTitleBanner'; import ViewerInfoTitleBanner from './ViewerInfoTitleBanner';
import { translateString } from '../../utils/helpers/'; import { translateString } from '../../utils/helpers/';
export default class ViewerInfoVideoTitleBanner extends ViewerInfoTitleBanner { export default class ViewerInfoVideoTitleBanner extends ViewerInfoTitleBanner {
render() { render() {
const displayViews = PageStore.get('config-options').pages.media.displayViews && void 0 !== this.props.views; const displayViews = PageStore.get('config-options').pages.media.displayViews && void 0 !== this.props.views;
const mediaData = MediaPageStore.get('media-data'); const mediaData = MediaPageStore.get('media-data');
const mediaState = mediaData.state; const mediaState = mediaData.state;
const isShared = mediaData.is_shared; const isShared = mediaData.is_shared;
let stateTooltip = ''; let stateTooltip = '';
switch (mediaState) { switch (mediaState) {
case 'private': case 'private':
stateTooltip = 'The site admins have to make its access public'; stateTooltip = 'The site admins have to make its access public';
break; break;
case 'unlisted': case 'unlisted':
stateTooltip = 'The site admins have to make it appear on listings'; stateTooltip = 'The site admins have to make it appear on listings';
break; break;
}
const sharedTooltip = 'This media is shared with specific users or categories';
return (
<div className="media-title-banner">
{displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
? this.mediaCategories(true)
: null}
{void 0 !== this.props.title ? <h1>{this.props.title}</h1> : null}
{isShared || 'public' !== mediaState ? (
<div className="media-labels-area">
<div className="media-labels-area-inner">
{isShared ? (
<>
<span className="media-label-state">
<span>shared</span>
</span>
<span className="helper-icon" data-tooltip={sharedTooltip}>
<i className="material-icons">help_outline</i>
</span>
</>
) : 'public' !== mediaState ? (
<>
<span className="media-label-state">
<span>{mediaState}</span>
</span>
<span className="helper-icon" data-tooltip={stateTooltip}>
<i className="material-icons">help_outline</i>
</span>
</>
) : null}
</div>
</div>
) : null}
<div
className={
'media-views-actions' +
(this.state.likedMedia ? ' liked-media' : '') +
(this.state.dislikedMedia ? ' disliked-media' : '')
}
>
{!displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
? this.mediaCategories()
: null}
{displayViews ? (
<div className="media-views">
{formatViewsNumber(this.props.views, true)}{' '}
{1 >= this.props.views ? translateString('view') : translateString('views')}
</div>
) : null}
<div className="media-actions">
<div>
{MemberContext._currentValue.can.likeMedia ? <MediaLikeIcon /> : null}
{MemberContext._currentValue.can.dislikeMedia ? <MediaDislikeIcon /> : null}
{!inEmbeddedApp() && MemberContext._currentValue.can.shareMedia ? (
<MediaShareButton isVideo={true} />
) : null}
{!inEmbeddedApp() &&
!MemberContext._currentValue.is.anonymous &&
MemberContext._currentValue.can.saveMedia &&
-1 < PlaylistsContext._currentValue.mediaTypes.indexOf(MediaPageStore.get('media-type')) ? (
<MediaSaveButton />
) : null}
{!this.props.allowDownload || !MemberContext._currentValue.can.downloadMedia ? null : !this
.downloadLink ? (
<VideoMediaDownloadLink />
) : (
<OtherMediaDownloadLink link={this.downloadLink} title={this.downloadFilename} />
)}
<MediaMoreOptionsIcon allowDownload={this.props.allowDownload} />
</div>
</div>
</div>
</div>
);
} }
const sharedTooltip = 'This media is shared with specific users or categories';
return (
<div className="media-title-banner">
{displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
? this.mediaCategories(true)
: null}
{void 0 !== this.props.title ? <h1>{this.props.title}</h1> : null}
{isShared || 'public' !== mediaState ? (
<div className="media-labels-area">
<div className="media-labels-area-inner">
{isShared ? (
<>
<span className="media-label-state">
<span>shared</span>
</span>
<span className="helper-icon" data-tooltip={sharedTooltip}>
<i className="material-icons">help_outline</i>
</span>
</>
) : 'public' !== mediaState ? (
<>
<span className="media-label-state">
<span>{mediaState}</span>
</span>
<span className="helper-icon" data-tooltip={stateTooltip}>
<i className="material-icons">help_outline</i>
</span>
</>
) : null}
</div>
</div>
) : null}
<div
className={
'media-views-actions' +
(this.state.likedMedia ? ' liked-media' : '') +
(this.state.dislikedMedia ? ' disliked-media' : '')
}
>
{!displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
? this.mediaCategories()
: null}
{displayViews ? (
<div className="media-views">
{formatViewsNumber(this.props.views, true)} {1 >= this.props.views ? translateString('view') : translateString('views')}
</div>
) : null}
<div className="media-actions">
<div>
{MemberContext._currentValue.can.likeMedia ? <MediaLikeIcon /> : null}
{MemberContext._currentValue.can.dislikeMedia ? <MediaDislikeIcon /> : null}
{MemberContext._currentValue.can.shareMedia ? <MediaShareButton isVideo={true} /> : null}
{!MemberContext._currentValue.is.anonymous &&
MemberContext._currentValue.can.saveMedia &&
-1 < PlaylistsContext._currentValue.mediaTypes.indexOf(MediaPageStore.get('media-type')) ? (
<MediaSaveButton />
) : null}
{!this.props.allowDownload || !MemberContext._currentValue.can.downloadMedia ? null : !this
.downloadLink ? (
<VideoMediaDownloadLink />
) : (
<OtherMediaDownloadLink link={this.downloadLink} title={this.downloadFilename} />
)}
<MediaMoreOptionsIcon allowDownload={this.props.allowDownload} />
</div>
</div>
</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);
} }
} }

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import UrlParse from 'url-parse'; import UrlParse from 'url-parse';
import { ApiUrlContext, MemberContext, SiteContext } from '../utils/contexts/'; import { ApiUrlContext, MemberContext, SiteContext } from '../utils/contexts/';
import { formatInnerLink, csrfToken, postRequest } from '../utils/helpers/'; import { formatInnerLink, csrfToken, postRequest, inEmbeddedApp } from '../utils/helpers/';
import { PageActions } from '../utils/actions/'; import { PageActions } from '../utils/actions/';
import { PageStore, ProfilePageStore } from '../utils/stores/'; import { PageStore, ProfilePageStore } from '../utils/stores/';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader'; import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
@@ -268,7 +268,7 @@ export class ProfileAboutPage extends ProfileMediaPage {
return [ return [
this.state.author ? ( this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="about" /> <ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="about" hideChannelBanner={inEmbeddedApp()} />
) : null, ) : null,
this.state.author ? ( this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent" enabledContactForm={this.enabledContactForm}> <ProfilePagesContent key="ProfilePagesContent" enabledContactForm={this.enabledContactForm}>

View File

@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ApiUrlConsumer } from '../utils/contexts/'; import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/'; import { PageStore } from '../utils/stores/';
import { inEmbeddedApp } from '../utils/helpers/';
import { MediaListWrapper } from '../components/MediaListWrapper'; import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader'; import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent'; import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
@@ -28,7 +29,7 @@ export class ProfileHistoryPage extends ProfileMediaPage {
pageContent() { pageContent() {
return [ return [
this.state.author ? ( this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="history" /> <ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="history" hideChannelBanner={inEmbeddedApp()} />
) : null, ) : null,
this.state.author ? ( this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent"> <ProfilePagesContent key="ProfilePagesContent">

View File

@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ApiUrlConsumer } from '../utils/contexts/'; import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/'; import { PageStore } from '../utils/stores/';
import { inEmbeddedApp } from '../utils/helpers/';
import { MediaListWrapper } from '../components/MediaListWrapper'; import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader'; import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent'; import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
@@ -28,7 +29,7 @@ export class ProfileLikedPage extends ProfileMediaPage {
pageContent() { pageContent() {
return [ return [
this.state.author ? ( this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="liked" /> <ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="liked" hideChannelBanner={inEmbeddedApp()} />
) : null, ) : null,
this.state.author ? ( this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent"> <ProfilePagesContent key="ProfilePagesContent">

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { ApiUrlConsumer } from '../utils/contexts/'; import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/'; import { PageStore } from '../utils/stores/';
import { inEmbeddedApp } from '../utils/helpers/';
import { MediaListWrapper } from '../components/MediaListWrapper'; import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader'; import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent'; import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
@@ -30,7 +31,7 @@ export class ProfilePlaylistsPage extends ProfileMediaPage {
pageContent() { pageContent() {
return [ return [
this.state.author ? ( this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="playlists" /> <ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="playlists" hideChannelBanner={inEmbeddedApp()} />
) : null, ) : null,
this.state.author ? ( this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent"> <ProfilePagesContent key="ProfilePagesContent">

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,20 @@
export function inEmbeddedApp() {
try {
const params = new URL(globalThis.location.href).searchParams;
const mode = params.get('mode');
if (mode === 'embed_mode') {
sessionStorage.setItem('media_cms_embed_mode', 'true');
return true;
}
if (mode === 'standard') {
sessionStorage.removeItem('media_cms_embed_mode');
return false;
}
return sessionStorage.getItem('media_cms_embed_mode') === 'true';
} catch (e) {
return false;
}
}

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,83 @@ 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'); if (inEmbeddedApp()) {
const appSidebar = document.getElementById('app-sidebar'); globalThis.document.body.classList.add('embedded-app');
const appContent = idSelector ? document.getElementById(idSelector) : undefined; globalThis.document.body.classList.remove('visible-sidebar');
if (appContent && PageComponent) { const appContent = idSelector ? document.getElementById(idSelector) : undefined;
ReactDOM.render(
<AppProviders> if (appContent && PageComponent) {
{appHeader ? ReactDOM.createPortal(<PageHeader />, appHeader) : null} ReactDOM.render(
{appSidebar ? ReactDOM.createPortal(<PageSidebar />, appSidebar) : null} <AppProviders>
<PageComponent /> <PageComponent />
</AppProviders>, </AppProviders>,
appContent appContent
); );
} else if (appHeader && appSidebar) { }
ReactDOM.render(
<AppProviders> return;
{ReactDOM.createPortal(<PageHeader />, appHeader)} }
<PageSidebar />
</AppProviders>, const appContent = idSelector ? document.getElementById(idSelector) : undefined;
appSidebar const appHeader = document.getElementById('app-header');
); const appSidebar = document.getElementById('app-sidebar');
} else if (appHeader) {
ReactDOM.render( if (appContent && PageComponent) {
<LayoutProvider> ReactDOM.render(
<ThemeProvider> <AppProviders>
<UserProvider> {appHeader ? ReactDOM.createPortal(<PageHeader />, appHeader) : null}
<PageHeader /> {appSidebar ? ReactDOM.createPortal(<PageSidebar />, appSidebar) : null}
</UserProvider> <PageComponent />
</ThemeProvider> </AppProviders>,
</LayoutProvider>, appContent
appSidebar );
); } else if (appHeader && appSidebar) {
} else if (appSidebar) { ReactDOM.render(
ReactDOM.render( <AppProviders>
<AppProviders> {ReactDOM.createPortal(<PageHeader />, appHeader)}
<PageSidebar /> <PageSidebar />
</AppProviders>, </AppProviders>,
appSidebar 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);
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long