Compare commits
3 Commits
main
...
frontend-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d391c29e9 | ||
|
|
fbc3817efd | ||
|
|
08189191b5 |
@ -1 +1 @@
|
|||||||
VERSION = "7.7"
|
VERSION = "7.6"
|
||||||
|
|||||||
5
frontend/.vscode/settings.json
vendored
5
frontend/.vscode/settings.json
vendored
@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true,
|
||||||
}
|
"prettier.configPath": "../.prettierrc"
|
||||||
|
}
|
||||||
|
|||||||
@ -21,6 +21,9 @@
|
|||||||
"@babel/core": "^7.26.9",
|
"@babel/core": "^7.26.9",
|
||||||
"@babel/preset-env": "^7.26.9",
|
"@babel/preset-env": "^7.26.9",
|
||||||
"@babel/preset-react": "^7.26.3",
|
"@babel/preset-react": "^7.26.3",
|
||||||
|
"@testing-library/dom": "^8.20.1",
|
||||||
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
|
"@testing-library/react": "^12.1.5",
|
||||||
"@types/flux": "^3.1.15",
|
"@types/flux": "^3.1.15",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/minimatch": "^5.1.2",
|
"@types/minimatch": "^5.1.2",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -3,278 +3,257 @@ 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, inEmbeddedApp, publishedOnDate } from '../../utils/helpers/';
|
import { formatInnerLink, publishedOnDate } from '../../utils/helpers/';
|
||||||
import { PopupMain } from '../_shared/';
|
import { PopupMain } from '../_shared/';
|
||||||
import CommentsList from '../comments/Comments';
|
import CommentsList from '../comments/Comments';
|
||||||
import { replaceString } from '../../utils/helpers/';
|
import { replaceString } from '../../utils/helpers/';
|
||||||
import { translateString } from '../../utils/helpers/';
|
import { translateString } from '../../utils/helpers/';
|
||||||
|
|
||||||
function metafield(arr) {
|
function metafield(arr) {
|
||||||
let i;
|
let i;
|
||||||
let sep;
|
let sep;
|
||||||
let ret = [];
|
let ret = [];
|
||||||
|
|
||||||
if (arr && arr.length) {
|
if (arr && arr.length) {
|
||||||
i = 0;
|
i = 0;
|
||||||
sep = 1 < arr.length ? ', ' : '';
|
sep = 1 < arr.length ? ', ' : '';
|
||||||
while (i < arr.length) {
|
while (i < arr.length) {
|
||||||
ret[i] = (
|
ret[i] = (
|
||||||
<div key={i}>
|
<div key={i}>
|
||||||
<a href={arr[i].url} title={arr[i].title}>
|
<a href={arr[i].url} title={arr[i].title}>
|
||||||
{arr[i].title}
|
{arr[i].title}
|
||||||
</a>
|
</a>
|
||||||
{i < arr.length - 1 ? sep : ''}
|
{i < arr.length - 1 ? sep : ''}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MediaAuthorBanner(props) {
|
function MediaAuthorBanner(props) {
|
||||||
return (
|
return (
|
||||||
<div className="media-author-banner">
|
<div className="media-author-banner">
|
||||||
<div>
|
<div>
|
||||||
<a className="author-banner-thumb" href={props.link || null} title={props.name}>
|
<a className="author-banner-thumb" href={props.link || null} title={props.name}>
|
||||||
<span style={{ backgroundImage: 'url(' + props.thumb + ')' }}>
|
<span style={{ backgroundImage: 'url(' + props.thumb + ')' }}>
|
||||||
<img src={props.thumb} loading="lazy" alt={props.name} title={props.name} />
|
<img src={props.thumb} loading="lazy" alt={props.name} title={props.name} />
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
<a href={props.link} className="author-banner-name" title={props.name}>
|
<a href={props.link} className="author-banner-name" title={props.name}>
|
||||||
<span>{props.name}</span>
|
<span>{props.name}</span>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
{PageStore.get('config-media-item').displayPublishDate && props.published ? (
|
{PageStore.get('config-media-item').displayPublishDate && props.published ? (
|
||||||
<span className="author-banner-date">
|
<span className="author-banner-date">
|
||||||
{translateString('Published on')} {replaceString(publishedOnDate(new Date(props.published)))}
|
{translateString('Published on')} {replaceString(publishedOnDate(new Date(props.published)))}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MediaMetaField(props) {
|
function MediaMetaField(props) {
|
||||||
return (
|
return (
|
||||||
<div className={props.id.trim() ? 'media-content-' + props.id.trim() : null}>
|
<div className={props.id.trim() ? 'media-content-' + props.id.trim() : null}>
|
||||||
<div className="media-content-field">
|
<div className="media-content-field">
|
||||||
<div className="media-content-field-label">
|
<div className="media-content-field-label">
|
||||||
<h4>{props.title}</h4>
|
<h4>{props.title}</h4>
|
||||||
</div>
|
|
||||||
<div className="media-content-field-content">{props.value}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="media-content-field-content">{props.value}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditMediaButton(props) {
|
function EditMediaButton(props) {
|
||||||
let link = props.link;
|
let link = props.link;
|
||||||
|
|
||||||
if (window.MediaCMS.site.devEnv) {
|
if (window.MediaCMS.site.devEnv) {
|
||||||
link = '/edit-media.html';
|
link = '/edit-media.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href={link} rel="nofollow" title={translateString('Edit media')} className="edit-media-icon">
|
<a href={link} rel="nofollow" title={translateString('Edit media')} className="edit-media-icon">
|
||||||
<i className="material-icons">edit</i>
|
<i className="material-icons">edit</i>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ViewerInfoContent(props) {
|
export default function ViewerInfoContent(props) {
|
||||||
const { userCan } = useUser();
|
const { userCan } = useUser();
|
||||||
|
|
||||||
const description = props.description.trim();
|
const description = props.description.trim();
|
||||||
const tagsContent =
|
const tagsContent =
|
||||||
!PageStore.get('config-enabled').taxonomies.tags || PageStore.get('config-enabled').taxonomies.tags.enabled
|
!PageStore.get('config-enabled').taxonomies.tags || PageStore.get('config-enabled').taxonomies.tags.enabled
|
||||||
? metafield(MediaPageStore.get('media-tags'))
|
? metafield(MediaPageStore.get('media-tags'))
|
||||||
: [];
|
: [];
|
||||||
const categoriesContent = PageStore.get('config-options').pages.media.categoriesWithTitle
|
const categoriesContent = PageStore.get('config-options').pages.media.categoriesWithTitle
|
||||||
? []
|
? []
|
||||||
: !PageStore.get('config-enabled').taxonomies.categories ||
|
: !PageStore.get('config-enabled').taxonomies.categories ||
|
||||||
PageStore.get('config-enabled').taxonomies.categories.enabled
|
PageStore.get('config-enabled').taxonomies.categories.enabled
|
||||||
? metafield(MediaPageStore.get('media-categories'))
|
? metafield(MediaPageStore.get('media-categories'))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
let summary = MediaPageStore.get('media-summary');
|
let summary = MediaPageStore.get('media-summary');
|
||||||
|
|
||||||
summary = summary ? summary.trim() : '';
|
summary = summary ? summary.trim() : '';
|
||||||
|
|
||||||
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
||||||
|
|
||||||
const [hasSummary, setHasSummary] = useState('' !== summary);
|
const [hasSummary, setHasSummary] = useState('' !== summary);
|
||||||
const [isContentVisible, setIsContentVisible] = useState('' == summary);
|
const [isContentVisible, setIsContentVisible] = useState('' == summary);
|
||||||
|
|
||||||
function proceedMediaRemoval() {
|
function proceedMediaRemoval() {
|
||||||
MediaPageActions.removeMedia();
|
MediaPageActions.removeMedia();
|
||||||
popupContentRef.current.toggle();
|
popupContentRef.current.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelMediaRemoval() {
|
||||||
|
popupContentRef.current.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMediaDelete(mediaId) {
|
||||||
|
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||||
|
setTimeout(function () {
|
||||||
|
PageActions.addNotification('Media removed. Redirecting...', 'mediaDelete');
|
||||||
|
setTimeout(function () {
|
||||||
|
window.location.href =
|
||||||
|
SiteContext._currentValue.url + '/' + MediaPageStore.get('media-data').author_profile.replace(/^\//g, '');
|
||||||
|
}, 2000);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
if (void 0 !== mediaId) {
|
||||||
|
console.info("Removed media '" + mediaId + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMediaDeleteFail(mediaId) {
|
||||||
|
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||||
|
setTimeout(function () {
|
||||||
|
PageActions.addNotification('Media removal failed', 'mediaDeleteFail');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
if (void 0 !== mediaId) {
|
||||||
|
console.info('Media "' + mediaId + '"' + ' removal failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClickLoadMore() {
|
||||||
|
setIsContentVisible(!isContentVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
MediaPageStore.on('media_delete', onMediaDelete);
|
||||||
|
MediaPageStore.on('media_delete_fail', onMediaDeleteFail);
|
||||||
|
return () => {
|
||||||
|
MediaPageStore.removeListener('media_delete', onMediaDelete);
|
||||||
|
MediaPageStore.removeListener('media_delete_fail', onMediaDeleteFail);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const authorLink = formatInnerLink(props.author.url, SiteContext._currentValue.url);
|
||||||
|
const authorThumb = formatInnerLink(props.author.thumb, SiteContext._currentValue.url);
|
||||||
|
|
||||||
|
function setTimestampAnchors(text) {
|
||||||
|
function wrapTimestampWithAnchor(match, string) {
|
||||||
|
let split = match.split(':'),
|
||||||
|
s = 0,
|
||||||
|
m = 1;
|
||||||
|
|
||||||
|
while (split.length > 0) {
|
||||||
|
s += m * parseInt(split.pop(), 10);
|
||||||
|
m *= 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = `<a href="#" data-timestamp="${s}" class="video-timestamp">${match}</a>`;
|
||||||
|
return wrapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelMediaRemoval() {
|
const timeRegex = new RegExp('((\\d)?\\d:)?(\\d)?\\d:\\d\\d', 'g');
|
||||||
popupContentRef.current.toggle();
|
return text.replace(timeRegex, wrapTimestampWithAnchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMediaDelete(mediaId) {
|
return (
|
||||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
<div className="media-info-content">
|
||||||
setTimeout(function () {
|
{void 0 === PageStore.get('config-media-item').displayAuthor ||
|
||||||
PageActions.addNotification('Media removed. Redirecting...', 'mediaDelete');
|
null === PageStore.get('config-media-item').displayAuthor ||
|
||||||
setTimeout(function () {
|
!!PageStore.get('config-media-item').displayAuthor ? (
|
||||||
window.location.href =
|
<MediaAuthorBanner link={authorLink} thumb={authorThumb} name={props.author.name} published={props.published} />
|
||||||
SiteContext._currentValue.url +
|
) : null}
|
||||||
'/' +
|
|
||||||
MediaPageStore.get('media-data').author_profile.replace(/^\//g, '');
|
|
||||||
}, 2000);
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
if (void 0 !== mediaId) {
|
<div className="media-content-banner">
|
||||||
console.info("Removed media '" + mediaId + '"');
|
<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}
|
||||||
|
|
||||||
function onMediaDeleteFail(mediaId) {
|
{userCan.editMedia ? (
|
||||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
<div className="media-author-actions">
|
||||||
setTimeout(function () {
|
{userCan.editMedia ? <EditMediaButton link={MediaPageStore.get('media-data').edit_url} /> : null}
|
||||||
PageActions.addNotification('Media removal failed', 'mediaDeleteFail');
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
if (void 0 !== mediaId) {
|
{userCan.deleteMedia ? (
|
||||||
console.info('Media "' + mediaId + '"' + ' removal failed');
|
<PopupTrigger contentRef={popupContentRef}>
|
||||||
}
|
<button className="remove-media-icon" title={translateString('Delete media')}>
|
||||||
}
|
<i className="material-icons">delete</i>
|
||||||
|
</button>
|
||||||
|
</PopupTrigger>
|
||||||
|
) : null}
|
||||||
|
|
||||||
function onClickLoadMore() {
|
{userCan.deleteMedia ? (
|
||||||
setIsContentVisible(!isContentVisible);
|
<PopupContent contentRef={popupContentRef}>
|
||||||
}
|
<PopupMain>
|
||||||
|
<div className="popup-message">
|
||||||
useEffect(() => {
|
<span className="popup-message-title">Media removal</span>
|
||||||
MediaPageStore.on('media_delete', onMediaDelete);
|
<span className="popup-message-main">You're willing to remove media permanently?</span>
|
||||||
MediaPageStore.on('media_delete_fail', onMediaDeleteFail);
|
</div>
|
||||||
return () => {
|
<hr />
|
||||||
MediaPageStore.removeListener('media_delete', onMediaDelete);
|
<span className="popup-message-bottom">
|
||||||
MediaPageStore.removeListener('media_delete_fail', onMediaDeleteFail);
|
<button className="button-link cancel-comment-removal" onClick={cancelMediaRemoval}>
|
||||||
};
|
CANCEL
|
||||||
}, []);
|
</button>
|
||||||
|
<button className="button-link proceed-comment-removal" onClick={proceedMediaRemoval}>
|
||||||
const authorLink = formatInnerLink(props.author.url, SiteContext._currentValue.url);
|
PROCEED
|
||||||
const authorThumb = formatInnerLink(props.author.thumb, SiteContext._currentValue.url);
|
</button>
|
||||||
|
</span>
|
||||||
function setTimestampAnchors(text) {
|
</PopupMain>
|
||||||
function wrapTimestampWithAnchor(match, string) {
|
</PopupContent>
|
||||||
let split = match.split(':'),
|
) : null}
|
||||||
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}
|
||||||
{!inEmbeddedApp() && <CommentsList />}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
|
||||||
|
<CommentsList />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,119 +1,107 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { formatViewsNumber, inEmbeddedApp } from '../../utils/helpers/';
|
import { formatViewsNumber } 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 {
|
import { MediaLikeIcon, MediaDislikeIcon, OtherMediaDownloadLink, VideoMediaDownloadLink, MediaSaveButton, MediaShareButton, MediaMoreOptionsIcon } from '../media-actions/';
|
||||||
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,28 @@
|
|||||||
.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 & {
|
||||||
#page-media {
|
padding-left: var(--sidebar-width);
|
||||||
padding-left: 0;
|
opacity: 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body.sliding-sidebar & {
|
.visible-sidebar #page-media & {
|
||||||
transition-property: padding-left;
|
padding-left: 0;
|
||||||
transition-duration: 0.2s;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.embedded-app & {
|
.visible-sidebar & {
|
||||||
padding-top: 0;
|
#page-media {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.sliding-sidebar & {
|
||||||
|
transition-property: padding-left;
|
||||||
|
transition-duration: 0.2s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#page-profile-media,
|
#page-profile-media,
|
||||||
@ -35,20 +30,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,
|
||||||
@ -56,7 +51,7 @@
|
|||||||
#page-profile-about,
|
#page-profile-about,
|
||||||
#page-liked.profile-page-liked,
|
#page-liked.profile-page-liked,
|
||||||
#page-history.profile-page-history {
|
#page-history.profile-page-history {
|
||||||
.page-main-wrap {
|
.page-main-wrap {
|
||||||
background-color: var(--body-bg-color);
|
background-color: var(--body-bg-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -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, inEmbeddedApp } from '../utils/helpers/';
|
import { formatInnerLink, csrfToken, postRequest } 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" hideChannelBanner={inEmbeddedApp()} />
|
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="about" />
|
||||||
) : null,
|
) : null,
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesContent key="ProfilePagesContent" enabledContactForm={this.enabledContactForm}>
|
<ProfilePagesContent key="ProfilePagesContent" enabledContactForm={this.enabledContactForm}>
|
||||||
|
|||||||
@ -2,7 +2,6 @@ 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';
|
||||||
@ -29,7 +28,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" hideChannelBanner={inEmbeddedApp()} />
|
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="history" />
|
||||||
) : null,
|
) : null,
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesContent key="ProfilePagesContent">
|
<ProfilePagesContent key="ProfilePagesContent">
|
||||||
|
|||||||
@ -2,7 +2,6 @@ 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';
|
||||||
@ -29,7 +28,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" hideChannelBanner={inEmbeddedApp()} />
|
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="liked" />
|
||||||
) : null,
|
) : null,
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesContent key="ProfilePagesContent">
|
<ProfilePagesContent key="ProfilePagesContent">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
|||||||
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';
|
||||||
@ -31,7 +30,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" hideChannelBanner={inEmbeddedApp()} />
|
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="playlists" />
|
||||||
) : null,
|
) : null,
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesContent key="ProfilePagesContent">
|
<ProfilePagesContent key="ProfilePagesContent">
|
||||||
|
|||||||
@ -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 { inEmbeddedApp, translateString } from '../utils/helpers';
|
import { 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,443 +19,400 @@ 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">Media that you have shared with others will show up here.</div>
|
<div className="start-uploading">
|
||||||
</div>
|
Media that you have shared with others will show up here.
|
||||||
)}
|
</div>
|
||||||
</LinksConsumer>
|
</div>
|
||||||
);
|
)}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
this.setState({
|
||||||
ProfilePageActions.load_author_data();
|
author: author,
|
||||||
}
|
requestUrl: requestUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
authorDataLoad() {
|
onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) {
|
||||||
const author = ProfilePageStore.get('author-data');
|
this.setState({
|
||||||
|
uploadsPreviewItemsCount: totalAuthorPreviewItems,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let requestUrl = this.state.requestUrl;
|
getCountFunc(count) {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
channelMediaCount: count,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (this.state.query) {
|
||||||
|
let title = '';
|
||||||
|
|
||||||
if (author) {
|
if (!count) {
|
||||||
if (this.state.query) {
|
title = 'No results for "' + this.state.query + '"';
|
||||||
requestUrl =
|
} else if (1 === count) {
|
||||||
ApiUrlContext._currentValue.media +
|
title = '1 result for "' + this.state.query + '"';
|
||||||
'?author=' +
|
} else {
|
||||||
author.id +
|
title = count + ' results for "' + this.state.query + '"';
|
||||||
'&show=shared_by_me&q=' +
|
}
|
||||||
encodeURIComponent(this.state.query) +
|
|
||||||
this.state.filterArgs;
|
this.setState({
|
||||||
} else {
|
title: title,
|
||||||
requestUrl =
|
});
|
||||||
ApiUrlContext._currentValue.media +
|
|
||||||
'?author=' +
|
|
||||||
author.id +
|
|
||||||
'&show=shared_by_me' +
|
|
||||||
this.state.filterArgs;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
changeRequestQuery(newQuery) {
|
||||||
author: author,
|
if (!this.state.author) {
|
||||||
requestUrl: requestUrl,
|
return;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) {
|
let requestUrl;
|
||||||
this.setState({
|
|
||||||
uploadsPreviewItemsCount: totalAuthorPreviewItems,
|
if (newQuery) {
|
||||||
});
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCountFunc(count) {
|
let title = this.state.title;
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
channelMediaCount: count,
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (this.state.query) {
|
|
||||||
let title = '';
|
|
||||||
|
|
||||||
if (!count) {
|
if ('' === newQuery) {
|
||||||
title = 'No results for "' + this.state.query + '"';
|
title = this.props.title;
|
||||||
} else if (1 === count) {
|
|
||||||
title = '1 result for "' + this.state.query + '"';
|
|
||||||
} else {
|
|
||||||
title = count + ' results for "' + this.state.query + '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
title: title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changeRequestQuery(newQuery) {
|
this.setState({
|
||||||
|
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 (newQuery) {
|
if (this.state.query) {
|
||||||
requestUrl =
|
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
|
||||||
ApiUrlContext._currentValue.media +
|
|
||||||
'?author=' +
|
|
||||||
this.state.author.id +
|
|
||||||
'&show=shared_by_me&q=' +
|
|
||||||
encodeURIComponent(newQuery) +
|
|
||||||
this.state.filterArgs;
|
|
||||||
} else {
|
} else {
|
||||||
requestUrl =
|
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me' + this.state.filterArgs;
|
||||||
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 });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onToggleFiltersClick() {
|
pageContent() {
|
||||||
this.setState({
|
const authorData = ProfilePageStore.get('author-data');
|
||||||
hiddenFilters: !this.state.hiddenFilters,
|
|
||||||
hiddenTags: true,
|
|
||||||
hiddenSorting: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleTagsClick() {
|
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
||||||
this.setState({
|
|
||||||
hiddenFilters: true,
|
|
||||||
hiddenTags: !this.state.hiddenTags,
|
|
||||||
hiddenSorting: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleSortingClick() {
|
// Check if any filters are active
|
||||||
this.setState({
|
const hasActiveFilters = this.state.filterArgs && (
|
||||||
hiddenFilters: true,
|
this.state.filterArgs.includes('media_type=') ||
|
||||||
hiddenTags: true,
|
this.state.filterArgs.includes('upload_date=') ||
|
||||||
hiddenSorting: !this.state.hiddenSorting,
|
this.state.filterArgs.includes('duration=') ||
|
||||||
});
|
this.state.filterArgs.includes('publish_state=')
|
||||||
}
|
);
|
||||||
|
|
||||||
onTagSelect(tag) {
|
return [
|
||||||
this.setState({ selectedTag: tag }, () => {
|
this.state.author ? (
|
||||||
this.onFiltersUpdate({
|
<ProfilePagesHeader
|
||||||
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
key="ProfilePagesHeader"
|
||||||
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
author={this.state.author}
|
||||||
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
type="shared_by_me"
|
||||||
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
onQueryChange={this.changeRequestQuery}
|
||||||
sort_by: this.state.selectedSort,
|
onToggleFiltersClick={this.onToggleFiltersClick}
|
||||||
tag: tag,
|
onToggleTagsClick={this.onToggleTagsClick}
|
||||||
});
|
onToggleSortingClick={this.onToggleSortingClick}
|
||||||
});
|
hasActiveFilters={hasActiveFilters}
|
||||||
}
|
hasActiveTags={this.state.selectedTag !== 'all'}
|
||||||
|
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
|
||||||
onSortSelect(sortBy) {
|
/>
|
||||||
this.setState({ selectedSort: sortBy }, () => {
|
) : null,
|
||||||
this.onFiltersUpdate({
|
this.state.author ? (
|
||||||
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
<ProfilePagesContent key="ProfilePagesContent">
|
||||||
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
<MediaListWrapper
|
||||||
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
title={this.state.title}
|
||||||
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
className="items-list-ver"
|
||||||
sort_by: sortBy,
|
showBulkActions={isMediaAuthor}
|
||||||
tag: this.state.selectedTag,
|
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}
|
||||||
onFiltersUpdate(updatedArgs) {
|
>
|
||||||
const args = {
|
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} />
|
||||||
media_type: null,
|
<ProfileMediaTags hidden={this.state.hiddenTags} tags={this.state.availableTags} onTagSelect={this.onTagSelect} />
|
||||||
upload_date: null,
|
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
|
||||||
duration: null,
|
<LazyLoadItemListAsync
|
||||||
publish_state: null,
|
key={`${this.state.requestUrl}-${this.props.bulkActions.listKey}`}
|
||||||
sort_by: null,
|
requestUrl={this.state.requestUrl}
|
||||||
ordering: null,
|
hideAuthor={true}
|
||||||
t: null,
|
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null}
|
||||||
};
|
hideViews={!PageStore.get('config-media-item').displayViews}
|
||||||
|
hideDate={!PageStore.get('config-media-item').displayPublishDate}
|
||||||
switch (updatedArgs.media_type) {
|
canEdit={isMediaAuthor}
|
||||||
case 'video':
|
onResponseDataLoaded={this.onResponseDataLoaded}
|
||||||
case 'audio':
|
showSelection={isMediaAuthor}
|
||||||
case 'image':
|
hasAnySelection={this.props.bulkActions.selectedMedia.size > 0}
|
||||||
case 'pdf':
|
selectedMedia={this.props.bulkActions.selectedMedia}
|
||||||
args.media_type = updatedArgs.media_type;
|
onMediaSelection={this.props.bulkActions.handleMediaSelection}
|
||||||
break;
|
onItemsUpdate={this.props.bulkActions.handleItemsUpdate}
|
||||||
}
|
/>
|
||||||
|
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
|
||||||
switch (updatedArgs.upload_date) {
|
<EmptySharedByMe name={this.state.author.name} />
|
||||||
case 'today':
|
) : null}
|
||||||
case 'this_week':
|
</MediaListWrapper>
|
||||||
case 'this_month':
|
</ProfilePagesContent>
|
||||||
case 'this_year':
|
) : null,
|
||||||
args.upload_date = updatedArgs.upload_date;
|
this.state.author && isMediaAuthor ? (
|
||||||
break;
|
<BulkActionsModals
|
||||||
}
|
key="BulkActionsModals"
|
||||||
|
{...this.props.bulkActions}
|
||||||
// Handle duration filter
|
selectedMediaIds={Array.from(this.props.bulkActions.selectedMedia)}
|
||||||
if (updatedArgs.duration && updatedArgs.duration !== 'all') {
|
csrfToken={this.props.bulkActions.getCsrfToken()}
|
||||||
args.duration = updatedArgs.duration;
|
username={this.state.author.username}
|
||||||
}
|
onConfirmCancel={this.props.bulkActions.handleConfirmCancel}
|
||||||
|
onConfirmProceed={this.props.bulkActions.handleConfirmProceed}
|
||||||
// Handle publish state filter
|
onPermissionModalCancel={this.props.bulkActions.handlePermissionModalCancel}
|
||||||
if (updatedArgs.publish_state && updatedArgs.publish_state !== 'all') {
|
onPermissionModalSuccess={this.props.bulkActions.handlePermissionModalSuccess}
|
||||||
args.publish_state = updatedArgs.publish_state;
|
onPermissionModalError={this.props.bulkActions.handlePermissionModalError}
|
||||||
}
|
onPlaylistModalCancel={this.props.bulkActions.handlePlaylistModalCancel}
|
||||||
|
onPlaylistModalSuccess={this.props.bulkActions.handlePlaylistModalSuccess}
|
||||||
switch (updatedArgs.sort_by) {
|
onPlaylistModalError={this.props.bulkActions.handlePlaylistModalError}
|
||||||
case 'date_added_desc':
|
onChangeOwnerModalCancel={this.props.bulkActions.handleChangeOwnerModalCancel}
|
||||||
// Default sorting, no need to add parameters
|
onChangeOwnerModalSuccess={this.props.bulkActions.handleChangeOwnerModalSuccess}
|
||||||
break;
|
onChangeOwnerModalError={this.props.bulkActions.handleChangeOwnerModalError}
|
||||||
case 'date_added_asc':
|
onPublishStateModalCancel={this.props.bulkActions.handlePublishStateModalCancel}
|
||||||
args.ordering = 'asc';
|
onPublishStateModalSuccess={this.props.bulkActions.handlePublishStateModalSuccess}
|
||||||
break;
|
onPublishStateModalError={this.props.bulkActions.handlePublishStateModalError}
|
||||||
case 'alphabetically_asc':
|
onCategoryModalCancel={this.props.bulkActions.handleCategoryModalCancel}
|
||||||
args.sort_by = 'title_asc';
|
onCategoryModalSuccess={this.props.bulkActions.handleCategoryModalSuccess}
|
||||||
break;
|
onCategoryModalError={this.props.bulkActions.handleCategoryModalError}
|
||||||
case 'alphabetically_desc':
|
onTagModalCancel={this.props.bulkActions.handleTagModalCancel}
|
||||||
args.sort_by = 'title_desc';
|
onTagModalSuccess={this.props.bulkActions.handleTagModalSuccess}
|
||||||
break;
|
onTagModalError={this.props.bulkActions.handleTagModalError}
|
||||||
case 'plays_least':
|
/>
|
||||||
args.sort_by = 'views_asc';
|
) : null,
|
||||||
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
|
||||||
|
|||||||
@ -10,404 +10,364 @@ 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 { inEmbeddedApp, translateString } from '../utils/helpers';
|
import { 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">Media that others have shared with you will show up here.</div>
|
<div className="start-uploading">
|
||||||
</div>
|
Media that others have shared with you will show up here.
|
||||||
)}
|
</div>
|
||||||
</LinksConsumer>
|
</div>
|
||||||
);
|
)}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
this.setState({
|
||||||
ProfilePageActions.load_author_data();
|
author: author,
|
||||||
}
|
requestUrl: requestUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
authorDataLoad() {
|
onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) {
|
||||||
const author = ProfilePageStore.get('author-data');
|
this.setState({
|
||||||
|
uploadsPreviewItemsCount: totalAuthorPreviewItems,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let requestUrl = this.state.requestUrl;
|
getCountFunc(count) {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
channelMediaCount: count,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (this.state.query) {
|
||||||
|
let title = '';
|
||||||
|
|
||||||
if (author) {
|
if (!count) {
|
||||||
if (this.state.query) {
|
title = 'No results for "' + this.state.query + '"';
|
||||||
requestUrl =
|
} else if (1 === count) {
|
||||||
ApiUrlContext._currentValue.media +
|
title = '1 result for "' + this.state.query + '"';
|
||||||
'?author=' +
|
} else {
|
||||||
author.id +
|
title = count + ' results for "' + this.state.query + '"';
|
||||||
'&show=shared_with_me&q=' +
|
}
|
||||||
encodeURIComponent(this.state.query) +
|
|
||||||
this.state.filterArgs;
|
this.setState({
|
||||||
} else {
|
title: title,
|
||||||
requestUrl =
|
});
|
||||||
ApiUrlContext._currentValue.media +
|
|
||||||
'?author=' +
|
|
||||||
author.id +
|
|
||||||
'&show=shared_with_me' +
|
|
||||||
this.state.filterArgs;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
changeRequestQuery(newQuery) {
|
||||||
author: author,
|
if (!this.state.author) {
|
||||||
requestUrl: requestUrl,
|
return;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) {
|
let requestUrl;
|
||||||
this.setState({
|
|
||||||
uploadsPreviewItemsCount: totalAuthorPreviewItems,
|
if (newQuery) {
|
||||||
});
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCountFunc(count) {
|
let title = this.state.title;
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
channelMediaCount: count,
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (this.state.query) {
|
|
||||||
let title = '';
|
|
||||||
|
|
||||||
if (!count) {
|
if ('' === newQuery) {
|
||||||
title = 'No results for "' + this.state.query + '"';
|
title = this.props.title;
|
||||||
} else if (1 === count) {
|
|
||||||
title = '1 result for "' + this.state.query + '"';
|
|
||||||
} else {
|
|
||||||
title = count + ' results for "' + this.state.query + '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
title: title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changeRequestQuery(newQuery) {
|
this.setState({
|
||||||
|
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 (newQuery) {
|
if (this.state.query) {
|
||||||
requestUrl =
|
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
|
||||||
ApiUrlContext._currentValue.media +
|
|
||||||
'?author=' +
|
|
||||||
this.state.author.id +
|
|
||||||
'&show=shared_with_me&q=' +
|
|
||||||
encodeURIComponent(newQuery) +
|
|
||||||
this.state.filterArgs;
|
|
||||||
} else {
|
} else {
|
||||||
requestUrl =
|
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me' + this.state.filterArgs;
|
||||||
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 });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onToggleFiltersClick() {
|
pageContent() {
|
||||||
this.setState({
|
const authorData = ProfilePageStore.get('author-data');
|
||||||
hiddenFilters: !this.state.hiddenFilters,
|
|
||||||
hiddenTags: true,
|
|
||||||
hiddenSorting: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleTagsClick() {
|
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
||||||
this.setState({
|
|
||||||
hiddenFilters: true,
|
|
||||||
hiddenTags: !this.state.hiddenTags,
|
|
||||||
hiddenSorting: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleSortingClick() {
|
// Check if any filters are active
|
||||||
this.setState({
|
const hasActiveFilters = this.state.filterArgs && (
|
||||||
hiddenFilters: true,
|
this.state.filterArgs.includes('media_type=') ||
|
||||||
hiddenTags: true,
|
this.state.filterArgs.includes('upload_date=') ||
|
||||||
hiddenSorting: !this.state.hiddenSorting,
|
this.state.filterArgs.includes('duration=') ||
|
||||||
});
|
this.state.filterArgs.includes('publish_state=')
|
||||||
}
|
);
|
||||||
|
|
||||||
onTagSelect(tag) {
|
return [
|
||||||
this.setState({ selectedTag: tag }, () => {
|
this.state.author ? (
|
||||||
this.onFiltersUpdate({
|
<ProfilePagesHeader
|
||||||
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
key="ProfilePagesHeader"
|
||||||
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
author={this.state.author}
|
||||||
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
type="shared_with_me"
|
||||||
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
onQueryChange={this.changeRequestQuery}
|
||||||
sort_by: this.state.selectedSort,
|
onToggleFiltersClick={this.onToggleFiltersClick}
|
||||||
tag: tag,
|
onToggleTagsClick={this.onToggleTagsClick}
|
||||||
});
|
onToggleSortingClick={this.onToggleSortingClick}
|
||||||
});
|
hasActiveFilters={hasActiveFilters}
|
||||||
}
|
hasActiveTags={this.state.selectedTag !== 'all'}
|
||||||
|
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
|
||||||
onSortSelect(sortBy) {
|
/>
|
||||||
this.setState({ selectedSort: sortBy }, () => {
|
) : null,
|
||||||
this.onFiltersUpdate({
|
this.state.author ? (
|
||||||
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
<ProfilePagesContent key="ProfilePagesContent">
|
||||||
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
<MediaListWrapper
|
||||||
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
title={this.state.title}
|
||||||
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
className="items-list-ver"
|
||||||
sort_by: sortBy,
|
>
|
||||||
tag: this.state.selectedTag,
|
<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}
|
||||||
onFiltersUpdate(updatedArgs) {
|
requestUrl={this.state.requestUrl}
|
||||||
const args = {
|
hideAuthor={true}
|
||||||
media_type: null,
|
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null}
|
||||||
upload_date: null,
|
hideViews={!PageStore.get('config-media-item').displayViews}
|
||||||
duration: null,
|
hideDate={!PageStore.get('config-media-item').displayPublishDate}
|
||||||
publish_state: null,
|
canEdit={false}
|
||||||
sort_by: null,
|
onResponseDataLoaded={this.onResponseDataLoaded}
|
||||||
ordering: null,
|
/>
|
||||||
t: null,
|
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
|
||||||
};
|
<EmptySharedWithMe name={this.state.author.name} />
|
||||||
|
) : null}
|
||||||
switch (updatedArgs.media_type) {
|
</MediaListWrapper>
|
||||||
case 'video':
|
</ProfilePagesContent>
|
||||||
case 'audio':
|
) : null,
|
||||||
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',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
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';
|
||||||
@ -11,102 +10,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" />,
|
||||||
!inEmbeddedApp() && this.state.pagePlaylistLoaded ? (
|
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,
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
!inEmbeddedApp() && this.state.pagePlaylistLoaded ? (
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ 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';
|
||||||
@ -12,119 +11,118 @@ 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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
onViewerModeChange() {
|
||||||
MediaPageActions.loadMediaData();
|
this.setState({ theaterMode: VideoViewerStore.get('in-theater-mode') });
|
||||||
// FIXME: Is not neccessary to check on every window dimension for changes...
|
}
|
||||||
PageStore.on('window_resize', this.onWindowResize);
|
|
||||||
}
|
|
||||||
|
|
||||||
onWindowResize() {
|
onMediaLoadError(a) {
|
||||||
this.setState({
|
this.setState({ mediaLoadFailed: true });
|
||||||
wideLayout: wideLayoutBreakpoint <= window.innerWidth,
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onPagePlaylistLoad() {
|
pageContent() {
|
||||||
this.setState({
|
const viewerClassname = 'cf viewer-section' + (this.state.theaterMode ? ' theater-mode' : ' viewer-wide');
|
||||||
pagePlaylistLoaded: true,
|
const viewerNestedClassname = 'viewer-section-nested' + (this.state.theaterMode ? ' viewer-section' : '');
|
||||||
pagePlaylistData: MediaPageStore.get('playlist-data'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMediaLoad() {
|
return this.state.mediaLoadFailed ? (
|
||||||
const isVideoMedia =
|
<div className={viewerClassname}>
|
||||||
'video' === MediaPageStore.get('media-type') || 'audio' === MediaPageStore.get('media-type');
|
<ViewerError />
|
||||||
|
</div>
|
||||||
if (isVideoMedia) {
|
) : (
|
||||||
this.onViewerModeChange = this.onViewerModeChange.bind(this);
|
<div className={viewerClassname}>
|
||||||
|
{[
|
||||||
VideoViewerStore.on('changed_viewer_mode', this.onViewerModeChange);
|
<div className="viewer-container" key="viewer-container">
|
||||||
|
{this.state.mediaLoaded && this.state.pagePlaylistLoaded
|
||||||
this.setState({
|
? this.viewerContainerContent(MediaPageStore.get('media-data'))
|
||||||
mediaLoaded: true,
|
: null}
|
||||||
isVideoMedia: isVideoMedia,
|
</div>,
|
||||||
theaterMode: VideoViewerStore.get('in-theater-mode'),
|
<div key="viewer-section-nested" className={viewerNestedClassname}>
|
||||||
});
|
{!this.state.wideLayout || (this.state.isVideoMedia && this.state.theaterMode)
|
||||||
} else {
|
? [
|
||||||
this.setState({
|
<ViewerInfoVideo key="viewer-info" />,
|
||||||
mediaLoaded: true,
|
this.state.pagePlaylistLoaded ? (
|
||||||
isVideoMedia: isVideoMedia,
|
<ViewerSidebar
|
||||||
});
|
key="viewer-sidebar"
|
||||||
}
|
mediaId={MediaPageStore.get('media-id')}
|
||||||
}
|
playlistData={MediaPageStore.get('playlist-data')}
|
||||||
|
/>
|
||||||
onViewerModeChange() {
|
) : null,
|
||||||
this.setState({ theaterMode: VideoViewerStore.get('in-theater-mode') });
|
]
|
||||||
}
|
: [
|
||||||
|
this.state.pagePlaylistLoaded ? (
|
||||||
onMediaLoadError(a) {
|
<ViewerSidebar
|
||||||
this.setState({ mediaLoadFailed: true });
|
key="viewer-sidebar"
|
||||||
}
|
mediaId={MediaPageStore.get('media-id')}
|
||||||
|
playlistData={MediaPageStore.get('playlist-data')}
|
||||||
pageContent() {
|
/>
|
||||||
const viewerClassname = 'cf viewer-section' + (this.state.theaterMode ? ' theater-mode' : ' viewer-wide');
|
) : null,
|
||||||
const viewerNestedClassname = 'viewer-section-nested' + (this.state.theaterMode ? ' viewer-section' : '');
|
<ViewerInfoVideo key="viewer-info" />,
|
||||||
|
|
||||||
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,103 +1,101 @@
|
|||||||
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
import { BrowserCache } from '../classes/';
|
import { BrowserCache } from '../classes/';
|
||||||
import { PageStore } from '../stores/';
|
import { PageStore } from '../stores/';
|
||||||
import { addClassname, removeClassname, inEmbeddedApp } from '../helpers/';
|
import { addClassname, removeClassname } 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 () {
|
||||||
if ('media' === PageStore.get('current-page')) {
|
slidingSidebarTimeout = null;
|
||||||
if (visibleSidebar) {
|
removeClassname(document.body, 'sliding-sidebar');
|
||||||
addClassname(document.body, 'overflow-hidden');
|
}, 220);
|
||||||
} else {
|
}, 20);
|
||||||
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 isMediaPage = useMemo(() => PageStore.get('current-page') === 'media', []);
|
const enabledSidebar = !!(document.getElementById('app-sidebar') || document.querySelector('.page-sidebar'));
|
||||||
const isEmbeddedApp = useMemo(() => inEmbeddedApp(), []);
|
|
||||||
|
|
||||||
const enabledSidebar = Boolean(document.getElementById('app-sidebar') || document.querySelector('.page-sidebar'));
|
const [visibleSidebar, setVisibleSidebar] = useState(cache.get('visible-sidebar'));
|
||||||
|
const [visibleMobileSearch, setVisibleMobileSearch] = useState(false);
|
||||||
|
|
||||||
const [visibleSidebar, setVisibleSidebar] = useState(cache.get('visible-sidebar'));
|
const toggleMobileSearch = () => {
|
||||||
const [visibleMobileSearch, setVisibleMobileSearch] = useState(false);
|
setVisibleMobileSearch(!visibleMobileSearch);
|
||||||
|
};
|
||||||
|
|
||||||
const toggleMobileSearch = () => {
|
const toggleSidebar = () => {
|
||||||
setVisibleMobileSearch(!visibleMobileSearch);
|
const newval = !visibleSidebar;
|
||||||
};
|
onSidebarVisibilityChange(newval);
|
||||||
|
setVisibleSidebar(newval);
|
||||||
|
};
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
useEffect(() => {
|
||||||
const newval = !visibleSidebar;
|
if (visibleSidebar) {
|
||||||
onSidebarVisibilityChange(newval);
|
addClassname(document.body, 'visible-sidebar');
|
||||||
setVisibleSidebar(newval);
|
} else {
|
||||||
};
|
removeClassname(document.body, 'visible-sidebar');
|
||||||
|
}
|
||||||
|
if ('media' !== PageStore.get('current-page') && 1023 < window.innerWidth) {
|
||||||
|
cache.set('visible-sidebar', visibleSidebar);
|
||||||
|
}
|
||||||
|
}, [visibleSidebar]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isEmbeddedApp && visibleSidebar) {
|
PageStore.once('page_init', () => {
|
||||||
addClassname(document.body, 'visible-sidebar');
|
if ('media' === PageStore.get('current-page')) {
|
||||||
} else {
|
setVisibleSidebar(false);
|
||||||
removeClassname(document.body, 'visible-sidebar');
|
removeClassname(document.body, 'visible-sidebar');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (!isEmbeddedApp && !isMediaPage && 1023 < window.innerWidth) {
|
setVisibleSidebar(
|
||||||
cache.set('visible-sidebar', visibleSidebar);
|
'media' !== PageStore.get('current-page') &&
|
||||||
}
|
1023 < window.innerWidth &&
|
||||||
}, [isEmbeddedApp, isMediaPage, visibleSidebar]);
|
(null === visibleSidebar || visibleSidebar)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const value = {
|
||||||
PageStore.once('page_init', () => {
|
enabledSidebar,
|
||||||
if (isEmbeddedApp || isMediaPage) {
|
visibleSidebar,
|
||||||
setVisibleSidebar(false);
|
setVisibleSidebar,
|
||||||
removeClassname(document.body, 'visible-sidebar');
|
visibleMobileSearch,
|
||||||
}
|
toggleMobileSearch,
|
||||||
});
|
toggleSidebar,
|
||||||
|
};
|
||||||
|
|
||||||
setVisibleSidebar(
|
return <LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>;
|
||||||
!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;
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -14,4 +14,3 @@ 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';
|
|
||||||
|
|||||||
@ -3,83 +3,64 @@ 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) {
|
||||||
if (inEmbeddedApp()) {
|
const appHeader = document.getElementById('app-header');
|
||||||
globalThis.document.body.classList.add('embedded-app');
|
const appSidebar = document.getElementById('app-sidebar');
|
||||||
globalThis.document.body.classList.remove('visible-sidebar');
|
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
|
||||||
|
|
||||||
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
|
if (appContent && PageComponent) {
|
||||||
|
ReactDOM.render(
|
||||||
if (appContent && PageComponent) {
|
<AppProviders>
|
||||||
ReactDOM.render(
|
{appHeader ? ReactDOM.createPortal(<PageHeader />, appHeader) : null}
|
||||||
<AppProviders>
|
{appSidebar ? ReactDOM.createPortal(<PageSidebar />, appSidebar) : null}
|
||||||
<PageComponent />
|
<PageComponent />
|
||||||
</AppProviders>,
|
</AppProviders>,
|
||||||
appContent
|
appContent
|
||||||
);
|
);
|
||||||
}
|
} else if (appHeader && appSidebar) {
|
||||||
|
ReactDOM.render(
|
||||||
return;
|
<AppProviders>
|
||||||
}
|
{ReactDOM.createPortal(<PageHeader />, appHeader)}
|
||||||
|
<PageSidebar />
|
||||||
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
|
</AppProviders>,
|
||||||
const appHeader = document.getElementById('app-header');
|
appSidebar
|
||||||
const appSidebar = document.getElementById('app-sidebar');
|
);
|
||||||
|
} else if (appHeader) {
|
||||||
if (appContent && PageComponent) {
|
ReactDOM.render(
|
||||||
ReactDOM.render(
|
<LayoutProvider>
|
||||||
<AppProviders>
|
<ThemeProvider>
|
||||||
{appHeader ? ReactDOM.createPortal(<PageHeader />, appHeader) : null}
|
<UserProvider>
|
||||||
{appSidebar ? ReactDOM.createPortal(<PageSidebar />, appSidebar) : null}
|
<PageHeader />
|
||||||
<PageComponent />
|
</UserProvider>
|
||||||
</AppProviders>,
|
</ThemeProvider>
|
||||||
appContent
|
</LayoutProvider>,
|
||||||
);
|
appSidebar
|
||||||
} else if (appHeader && appSidebar) {
|
);
|
||||||
ReactDOM.render(
|
} else if (appSidebar) {
|
||||||
<AppProviders>
|
ReactDOM.render(
|
||||||
{ReactDOM.createPortal(<PageHeader />, appHeader)}
|
<AppProviders>
|
||||||
<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 it is too large
Load Diff
@ -9,88 +9,89 @@ let browserCache;
|
|||||||
const _StoreData = {};
|
const _StoreData = {};
|
||||||
|
|
||||||
class VideoPlayerStore extends EventEmitter {
|
class VideoPlayerStore extends EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.mediacms_config = mediacmsConfig(window.MediaCMS);
|
this.mediacms_config = mediacmsConfig(window.MediaCMS);
|
||||||
|
|
||||||
browserCache = new BrowserCache(this.mediacms_config.site.id, 86400); // Keep cache data "fresh" for one day.
|
browserCache = new BrowserCache(this.mediacms_config.site.id, 86400); // Keep cache data "fresh" for one day.
|
||||||
|
|
||||||
_StoreData.inTheaterMode = browserCache.get('in-theater-mode');
|
_StoreData.inTheaterMode = browserCache.get('in-theater-mode');
|
||||||
_StoreData.inTheaterMode = null !== _StoreData.inTheaterMode ? _StoreData.inTheaterMode : !1;
|
_StoreData.inTheaterMode = null !== _StoreData.inTheaterMode ? _StoreData.inTheaterMode : !1;
|
||||||
|
|
||||||
_StoreData.playerVolume = browserCache.get('player-volume');
|
_StoreData.playerVolume = browserCache.get('player-volume');
|
||||||
_StoreData.playerVolume =
|
_StoreData.playerVolume =
|
||||||
null === _StoreData.playerVolume ? 1 : Math.max(Math.min(Number(_StoreData.playerVolume), 1), 0);
|
null === _StoreData.playerVolume ? 1 : Math.max(Math.min(Number(_StoreData.playerVolume), 1), 0);
|
||||||
|
|
||||||
_StoreData.playerSoundMuted = browserCache.get('player-sound-muted');
|
_StoreData.playerSoundMuted = browserCache.get('player-sound-muted');
|
||||||
_StoreData.playerSoundMuted = null !== _StoreData.playerSoundMuted ? _StoreData.playerSoundMuted : !1;
|
_StoreData.playerSoundMuted = null !== _StoreData.playerSoundMuted ? _StoreData.playerSoundMuted : !1;
|
||||||
|
|
||||||
_StoreData.videoQuality = browserCache.get('video-quality');
|
_StoreData.videoQuality = browserCache.get('video-quality');
|
||||||
_StoreData.videoQuality = null !== _StoreData.videoQuality ? _StoreData.videoQuality : 'Auto';
|
_StoreData.videoQuality = null !== _StoreData.videoQuality ? _StoreData.videoQuality : 'Auto';
|
||||||
|
|
||||||
_StoreData.videoPlaybackSpeed = browserCache.get('video-playback-speed');
|
_StoreData.videoPlaybackSpeed = browserCache.get('video-playback-speed');
|
||||||
_StoreData.videoPlaybackSpeed = null !== _StoreData.videoPlaybackSpeed ? _StoreData.videoPlaybackSpeed : !1;
|
_StoreData.videoPlaybackSpeed = null !== _StoreData.videoPlaybackSpeed ? _StoreData.videoPlaybackSpeed : !1;
|
||||||
}
|
|
||||||
|
|
||||||
get(type) {
|
|
||||||
let r = null;
|
|
||||||
switch (type) {
|
|
||||||
case 'player-volume':
|
|
||||||
r = _StoreData.playerVolume;
|
|
||||||
break;
|
|
||||||
case 'player-sound-muted':
|
|
||||||
r = _StoreData.playerSoundMuted;
|
|
||||||
break;
|
|
||||||
case 'in-theater-mode':
|
|
||||||
r = _StoreData.inTheaterMode;
|
|
||||||
break;
|
|
||||||
case 'video-data':
|
|
||||||
r = _StoreData.videoData;
|
|
||||||
break;
|
|
||||||
case 'video-quality':
|
|
||||||
r = _StoreData.videoQuality;
|
|
||||||
break;
|
|
||||||
case 'video-playback-speed':
|
|
||||||
r = _StoreData.videoPlaybackSpeed;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
actions_handler(action) {
|
get(type) {
|
||||||
switch (action.type) {
|
let r = null;
|
||||||
case 'TOGGLE_VIEWER_MODE':
|
switch (type) {
|
||||||
_StoreData.inTheaterMode = !_StoreData.inTheaterMode;
|
case 'player-volume':
|
||||||
this.emit('changed_viewer_mode');
|
r = _StoreData.playerVolume;
|
||||||
break;
|
break;
|
||||||
case 'SET_VIEWER_MODE':
|
case 'player-sound-muted':
|
||||||
_StoreData.inTheaterMode = action.inTheaterMode;
|
r = _StoreData.playerSoundMuted;
|
||||||
browserCache.set('in-theater-mode', _StoreData.inTheaterMode);
|
break;
|
||||||
this.emit('changed_viewer_mode');
|
case 'in-theater-mode':
|
||||||
break;
|
r = _StoreData.inTheaterMode;
|
||||||
case 'SET_PLAYER_VOLUME':
|
break;
|
||||||
_StoreData.playerVolume = action.playerVolume;
|
case 'video-data':
|
||||||
browserCache.set('player-volume', action.playerVolume);
|
r = _StoreData.videoData;
|
||||||
this.emit('changed_player_volume');
|
break;
|
||||||
break;
|
case 'video-quality':
|
||||||
case 'SET_PLAYER_SOUND_MUTED':
|
r = _StoreData.videoQuality;
|
||||||
_StoreData.playerSoundMuted = action.playerSoundMuted;
|
break;
|
||||||
browserCache.set('player-sound-muted', action.playerSoundMuted);
|
case 'video-playback-speed':
|
||||||
this.emit('changed_player_sound_muted');
|
r = _StoreData.videoPlaybackSpeed;
|
||||||
break;
|
break;
|
||||||
case 'SET_VIDEO_QUALITY':
|
}
|
||||||
_StoreData.videoQuality = action.quality;
|
return r;
|
||||||
browserCache.set('video-quality', action.quality);
|
}
|
||||||
this.emit('changed_video_quality');
|
|
||||||
break;
|
actions_handler(action) {
|
||||||
case 'SET_VIDEO_PLAYBACK_SPEED':
|
switch (action.type) {
|
||||||
_StoreData.videoPlaybackSpeed = action.playbackSpeed;
|
case 'TOGGLE_VIEWER_MODE':
|
||||||
browserCache.set('video-playback-speed', action.playbackSpeed);
|
_StoreData.inTheaterMode = !_StoreData.inTheaterMode;
|
||||||
this.emit('changed_video_playback_speed');
|
browserCache.set('in-theater-mode', _StoreData.inTheaterMode);
|
||||||
break;
|
this.emit('changed_viewer_mode');
|
||||||
|
break;
|
||||||
|
case 'SET_VIEWER_MODE':
|
||||||
|
_StoreData.inTheaterMode = action.inTheaterMode;
|
||||||
|
browserCache.set('in-theater-mode', _StoreData.inTheaterMode);
|
||||||
|
this.emit('changed_viewer_mode');
|
||||||
|
break;
|
||||||
|
case 'SET_PLAYER_VOLUME':
|
||||||
|
_StoreData.playerVolume = action.playerVolume;
|
||||||
|
browserCache.set('player-volume', action.playerVolume);
|
||||||
|
this.emit('changed_player_volume');
|
||||||
|
break;
|
||||||
|
case 'SET_PLAYER_SOUND_MUTED':
|
||||||
|
_StoreData.playerSoundMuted = action.playerSoundMuted;
|
||||||
|
browserCache.set('player-sound-muted', action.playerSoundMuted);
|
||||||
|
this.emit('changed_player_sound_muted');
|
||||||
|
break;
|
||||||
|
case 'SET_VIDEO_QUALITY':
|
||||||
|
_StoreData.videoQuality = action.quality;
|
||||||
|
browserCache.set('video-quality', action.quality);
|
||||||
|
this.emit('changed_video_quality');
|
||||||
|
break;
|
||||||
|
case 'SET_VIDEO_PLAYBACK_SPEED':
|
||||||
|
_StoreData.videoPlaybackSpeed = action.playbackSpeed;
|
||||||
|
browserCache.set('video-playback-speed', action.playbackSpeed);
|
||||||
|
this.emit('changed_video_playback_speed');
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default exportStore(new VideoPlayerStore(), 'actions_handler');
|
export default exportStore(new VideoPlayerStore(), 'actions_handler');
|
||||||
|
|||||||
385
frontend/tests/tests-constants.ts
Normal file
385
frontend/tests/tests-constants.ts
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
export const sampleGlobalMediaCMS = {
|
||||||
|
profileId: 'john',
|
||||||
|
site: {
|
||||||
|
id: 'my-site',
|
||||||
|
url: 'https://example.com/',
|
||||||
|
api: 'https://example.com/api/',
|
||||||
|
title: 'Example',
|
||||||
|
theme: { mode: 'dark', switch: { enabled: true, position: 'sidebar' } },
|
||||||
|
logo: {
|
||||||
|
lightMode: { img: '/img/light.png', svg: '/img/light.svg' },
|
||||||
|
darkMode: { img: '/img/dark.png', svg: '/img/dark.svg' },
|
||||||
|
},
|
||||||
|
devEnv: false,
|
||||||
|
useRoundedCorners: true,
|
||||||
|
version: '1.0.0',
|
||||||
|
taxonomies: {
|
||||||
|
tags: { enabled: true, title: 'Topic Tags' },
|
||||||
|
categories: { enabled: false, title: 'Kinds' },
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
featured: { enabled: true, title: 'Featured picks' },
|
||||||
|
latest: { enabled: true, title: 'Recent uploads' },
|
||||||
|
members: { enabled: true, title: 'People' },
|
||||||
|
recommended: { enabled: false, title: 'You may like' },
|
||||||
|
},
|
||||||
|
userPages: {
|
||||||
|
liked: { enabled: true, title: 'Favorites' },
|
||||||
|
history: { enabled: true, title: 'Watched' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
home: '/',
|
||||||
|
admin: '/admin',
|
||||||
|
error404: '/404',
|
||||||
|
latestMedia: '/latest',
|
||||||
|
featuredMedia: '/featured',
|
||||||
|
recommendedMedia: '/recommended',
|
||||||
|
signin: '/signin',
|
||||||
|
signout: '/signout',
|
||||||
|
register: '/register',
|
||||||
|
changePassword: '/password',
|
||||||
|
members: '/members',
|
||||||
|
search: '/search',
|
||||||
|
likedMedia: '/liked',
|
||||||
|
history: '/history',
|
||||||
|
addMedia: '/add',
|
||||||
|
editChannel: '/edit/channel',
|
||||||
|
editProfile: '/edit/profile',
|
||||||
|
tags: '/tags',
|
||||||
|
categories: '/categories',
|
||||||
|
manageMedia: '/manage/media',
|
||||||
|
manageUsers: '/manage/users',
|
||||||
|
manageComments: '/manage/comments',
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
media: 'v1/media/',
|
||||||
|
playlists: 'v1/playlists',
|
||||||
|
members: 'v1/users',
|
||||||
|
liked: 'v1/user/liked',
|
||||||
|
history: 'v1/user/history',
|
||||||
|
tags: 'v1/tags',
|
||||||
|
categories: 'v1/categories',
|
||||||
|
manage_media: 'v1/manage/media',
|
||||||
|
manage_users: 'v1/manage/users',
|
||||||
|
manage_comments: 'v1/manage/comments',
|
||||||
|
search: 'v1/search',
|
||||||
|
actions: 'v1/actions',
|
||||||
|
comments: 'v1/comments',
|
||||||
|
},
|
||||||
|
contents: {
|
||||||
|
header: {
|
||||||
|
right: '',
|
||||||
|
onLogoRight: '',
|
||||||
|
},
|
||||||
|
notifications: {
|
||||||
|
messages: { addToLiked: 'Yay', removeFromLiked: 'Oops', addToDisliked: 'nay', removeFromDisliked: 'ok' },
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
belowNavMenu: '__belowNavMenu__',
|
||||||
|
belowThemeSwitcher: '__belowThemeSwitcher__',
|
||||||
|
footer: '__footer__',
|
||||||
|
mainMenuExtraItems: [
|
||||||
|
{ text: '__text_1__', link: '__link_1__', icon: '__icon_1__', className: '__className_1__' },
|
||||||
|
],
|
||||||
|
navMenuItems: [
|
||||||
|
{ text: '__text_2__', link: '__link_2__', icon: '__icon_2__', className: '__className_2__' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
uploader: {
|
||||||
|
belowUploadArea: '__belowUploadArea__',
|
||||||
|
postUploadMessage: '__postUploadMessage__',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
home: {
|
||||||
|
sections: {
|
||||||
|
latest: { title: 'Latest T' },
|
||||||
|
featured: { title: 'Featured T' },
|
||||||
|
recommended: { title: 'Recommended T' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
media: { categoriesWithTitle: true, htmlInDescription: true, hideViews: true, related: { initialSize: 5 } },
|
||||||
|
profile: { htmlInDescription: true, includeHistory: true, includeLikedMedia: true },
|
||||||
|
search: { advancedFilters: true },
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
mediaItem: { hideAuthor: true, hideViews: false, hideDate: true },
|
||||||
|
media: {
|
||||||
|
actions: {
|
||||||
|
like: true,
|
||||||
|
dislike: true,
|
||||||
|
report: true,
|
||||||
|
comment: true,
|
||||||
|
comment_mention: true,
|
||||||
|
download: true,
|
||||||
|
save: true,
|
||||||
|
share: true,
|
||||||
|
},
|
||||||
|
shareOptions: ['embed', 'email'],
|
||||||
|
},
|
||||||
|
playlists: { mediaTypes: ['audio'] },
|
||||||
|
sideBar: { hideHomeLink: false, hideTagsLink: true, hideCategoriesLink: false },
|
||||||
|
embeddedVideo: { initialDimensions: { width: 640, height: 360 } },
|
||||||
|
headerBar: { hideLogin: false, hideRegister: true },
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
is: { anonymous: false, admin: true },
|
||||||
|
name: ' John ',
|
||||||
|
username: ' john ',
|
||||||
|
thumbnail: ' /img/j.png ',
|
||||||
|
can: {
|
||||||
|
changePassword: true,
|
||||||
|
deleteProfile: true,
|
||||||
|
addComment: true,
|
||||||
|
mentionComment: true,
|
||||||
|
deleteComment: true,
|
||||||
|
editMedia: true,
|
||||||
|
deleteMedia: true,
|
||||||
|
editSubtitle: true,
|
||||||
|
manageMedia: true,
|
||||||
|
manageUsers: true,
|
||||||
|
manageComments: true,
|
||||||
|
contactUser: true,
|
||||||
|
canSeeMembersPage: true,
|
||||||
|
usersNeedsToBeApproved: false,
|
||||||
|
addMedia: true,
|
||||||
|
editProfile: true,
|
||||||
|
readComment: true,
|
||||||
|
},
|
||||||
|
pages: { about: '/u/john/about ', media: '/u/john ', playlists: '/u/john/playlists ' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sampleMediaCMSConfig = {
|
||||||
|
api: {
|
||||||
|
archive: {
|
||||||
|
tags: '',
|
||||||
|
categories: '',
|
||||||
|
},
|
||||||
|
featured: '',
|
||||||
|
manage: {
|
||||||
|
media: '',
|
||||||
|
users: '',
|
||||||
|
comments: '',
|
||||||
|
},
|
||||||
|
media: '',
|
||||||
|
playlists: '/v1/playlists',
|
||||||
|
recommended: '',
|
||||||
|
search: {
|
||||||
|
query: '',
|
||||||
|
titles: './search.html?titles=',
|
||||||
|
tag: '',
|
||||||
|
category: '',
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
liked: '',
|
||||||
|
history: '',
|
||||||
|
playlists: '/playlists/?author=',
|
||||||
|
},
|
||||||
|
users: '/users',
|
||||||
|
},
|
||||||
|
contents: {
|
||||||
|
header: {
|
||||||
|
right: '',
|
||||||
|
onLogoRight: '',
|
||||||
|
},
|
||||||
|
uploader: {
|
||||||
|
belowUploadArea: '',
|
||||||
|
postUploadMessage: '',
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
belowNavMenu: '__belowNavMenu__',
|
||||||
|
belowThemeSwitcher: '__belowThemeSwitcher__',
|
||||||
|
footer: '__footer__',
|
||||||
|
mainMenuExtra: {
|
||||||
|
items: [{ text: '__text_1__', link: '__link_1__', icon: '__icon_1__', className: '__className_1__' }],
|
||||||
|
},
|
||||||
|
navMenu: {
|
||||||
|
items: [{ text: '__text_2__', link: '__link_2__', icon: '__icon_2__', className: '__className_2__' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
taxonomies: sampleGlobalMediaCMS.site.taxonomies,
|
||||||
|
pages: {
|
||||||
|
featured: { enabled: true, title: 'Featured picks' },
|
||||||
|
latest: { enabled: true, title: 'Recent uploads' },
|
||||||
|
members: { enabled: true, title: 'People' },
|
||||||
|
recommended: { enabled: true, title: 'You may like' },
|
||||||
|
liked: { enabled: true, title: 'Favorites' },
|
||||||
|
history: { enabled: true, title: 'Watched' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
member: {
|
||||||
|
name: null,
|
||||||
|
username: 'john',
|
||||||
|
thumbnail: null,
|
||||||
|
is: {
|
||||||
|
admin: false,
|
||||||
|
anonymous: false,
|
||||||
|
},
|
||||||
|
can: {
|
||||||
|
addComment: false,
|
||||||
|
addMedia: false,
|
||||||
|
canSeeMembersPage: false,
|
||||||
|
changePassword: false,
|
||||||
|
contactUser: false,
|
||||||
|
deleteComment: false,
|
||||||
|
deleteMedia: false,
|
||||||
|
deleteProfile: false,
|
||||||
|
dislikeMedia: false,
|
||||||
|
downloadMedia: false,
|
||||||
|
editMedia: false,
|
||||||
|
editProfile: false,
|
||||||
|
editSubtitle: false,
|
||||||
|
likeMedia: false,
|
||||||
|
login: false,
|
||||||
|
manageComments: false,
|
||||||
|
manageMedia: false,
|
||||||
|
manageUsers: false,
|
||||||
|
mentionComment: false,
|
||||||
|
readComment: true,
|
||||||
|
register: false,
|
||||||
|
reportMedia: false,
|
||||||
|
saveMedia: true,
|
||||||
|
shareMedia: false,
|
||||||
|
usersNeedsToBeApproved: false,
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
home: null,
|
||||||
|
about: null,
|
||||||
|
media: null,
|
||||||
|
playlists: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
item: {
|
||||||
|
displayAuthor: false,
|
||||||
|
displayViews: false,
|
||||||
|
displayPublishDate: false,
|
||||||
|
},
|
||||||
|
share: {
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
notifications: {
|
||||||
|
messages: {
|
||||||
|
addToLiked: '',
|
||||||
|
removeFromLiked: '',
|
||||||
|
addToDisliked: '',
|
||||||
|
removeFromDisliked: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
pages: {
|
||||||
|
home: {
|
||||||
|
sections: {
|
||||||
|
latest: {
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
featured: {
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
recommended: {
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
advancedFilters: false,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
categoriesWithTitle: true,
|
||||||
|
htmlInDescription: true,
|
||||||
|
related: { initialSize: 5 },
|
||||||
|
displayViews: true,
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
htmlInDescription: false,
|
||||||
|
includeHistory: false,
|
||||||
|
includeLikedMedia: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
embedded: {
|
||||||
|
video: {
|
||||||
|
dimensions: {
|
||||||
|
width: 0,
|
||||||
|
widthUnit: 'px',
|
||||||
|
height: 0,
|
||||||
|
heightUnit: 'px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
playlists: {
|
||||||
|
mediaTypes: [],
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
hideHomeLink: false,
|
||||||
|
hideTagsLink: false,
|
||||||
|
hideCategoriesLink: false,
|
||||||
|
},
|
||||||
|
site: {
|
||||||
|
api: '',
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
url: '',
|
||||||
|
useRoundedCorners: false,
|
||||||
|
version: '',
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
logo: {
|
||||||
|
lightMode: { img: '/img/light.png', svg: '/img/light.svg' },
|
||||||
|
darkMode: { img: '/img/dark.png', svg: '/img/dark.svg' },
|
||||||
|
},
|
||||||
|
mode: 'dark',
|
||||||
|
switch: {
|
||||||
|
enabled: true,
|
||||||
|
position: 'sidebar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
admin: '',
|
||||||
|
archive: {
|
||||||
|
categories: '',
|
||||||
|
tags: '',
|
||||||
|
},
|
||||||
|
changePassword: '',
|
||||||
|
embed: '',
|
||||||
|
error404: '',
|
||||||
|
featured: '',
|
||||||
|
home: '',
|
||||||
|
latest: '',
|
||||||
|
manage: {
|
||||||
|
comments: '',
|
||||||
|
media: '',
|
||||||
|
users: '',
|
||||||
|
},
|
||||||
|
members: '',
|
||||||
|
profile: {
|
||||||
|
about: '',
|
||||||
|
media: '',
|
||||||
|
playlists: '',
|
||||||
|
shared_by_me: '',
|
||||||
|
shared_with_me: '',
|
||||||
|
},
|
||||||
|
recommended: '',
|
||||||
|
register: '',
|
||||||
|
search: {
|
||||||
|
base: '',
|
||||||
|
category: '',
|
||||||
|
query: '',
|
||||||
|
tag: '',
|
||||||
|
},
|
||||||
|
signin: '',
|
||||||
|
signout: '',
|
||||||
|
user: {
|
||||||
|
addMedia: '',
|
||||||
|
editChannel: '',
|
||||||
|
editProfile: '',
|
||||||
|
history: '',
|
||||||
|
liked: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
145
frontend/tests/utils/actions/MediaPageActions.test.ts
Normal file
145
frontend/tests/utils/actions/MediaPageActions.test.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import * as MediaPageActions from '../../../src/static/js/utils/actions/MediaPageActions';
|
||||||
|
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||||
|
|
||||||
|
// Mock the dispatcher module used by MediaPageActions
|
||||||
|
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||||
|
|
||||||
|
describe('utils/actions', () => {
|
||||||
|
describe('MediaPageActions', () => {
|
||||||
|
const dispatch = dispatcher.dispatch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loadMediaData', () => {
|
||||||
|
it('Should dispatch LOAD_MEDIA_DATA action', () => {
|
||||||
|
MediaPageActions.loadMediaData();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'LOAD_MEDIA_DATA' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('likeMedia / dislikeMedia', () => {
|
||||||
|
it('Should dispatch LIKE_MEDIA action', () => {
|
||||||
|
MediaPageActions.likeMedia();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'LIKE_MEDIA' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should dispatch DISLIKE_MEDIA action', () => {
|
||||||
|
MediaPageActions.dislikeMedia();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'DISLIKE_MEDIA' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('reportMedia', () => {
|
||||||
|
it('Should dispatch REPORT_MEDIA with empty string when description is undefined', () => {
|
||||||
|
MediaPageActions.reportMedia();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'REPORT_MEDIA', reportDescription: '' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// @todo: Revisit this behavior
|
||||||
|
it('Should dispatch REPORT_MEDIA with stripped description when provided', () => {
|
||||||
|
MediaPageActions.reportMedia(' some text ');
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'REPORT_MEDIA', reportDescription: 'sometext' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// @todo: Revisit this behavior
|
||||||
|
it('Should remove all whitespace characters including newlines and tabs', () => {
|
||||||
|
MediaPageActions.reportMedia('\n\t spaced\ntext \t');
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'REPORT_MEDIA', reportDescription: 'spacedtext' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('copyShareLink / copyEmbedMediaCode', () => {
|
||||||
|
it('Should dispatch COPY_SHARE_LINK carrying the provided input element', () => {
|
||||||
|
const inputElem = document.createElement('input');
|
||||||
|
MediaPageActions.copyShareLink(inputElem);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'COPY_SHARE_LINK', inputElement: inputElem });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should dispatch COPY_EMBED_MEDIA_CODE carrying the provided textarea element', () => {
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
MediaPageActions.copyEmbedMediaCode(textarea);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'COPY_EMBED_MEDIA_CODE', inputElement: textarea });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removeMedia', () => {
|
||||||
|
it('Should dispatch REMOVE_MEDIA action', () => {
|
||||||
|
MediaPageActions.removeMedia();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_MEDIA' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('comments', () => {
|
||||||
|
it('Should dispatch SUBMIT_COMMENT with provided text', () => {
|
||||||
|
const commentText = 'Nice one';
|
||||||
|
MediaPageActions.submitComment(commentText);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'SUBMIT_COMMENT', commentText });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should dispatch DELETE_COMMENT with provided comment id', () => {
|
||||||
|
const commentId = 'c-123';
|
||||||
|
MediaPageActions.deleteComment(commentId);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'DELETE_COMMENT', commentId });
|
||||||
|
});
|
||||||
|
|
||||||
|
// @todo: Revisit this behavior
|
||||||
|
it('Should dispatch DELETE_COMMENT with numeric comment id', () => {
|
||||||
|
const commentId = 42;
|
||||||
|
MediaPageActions.deleteComment(commentId);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'DELETE_COMMENT', commentId });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('playlists', () => {
|
||||||
|
it('Should dispatch CREATE_PLAYLIST with provided data', () => {
|
||||||
|
const payload = { title: 'My list', description: 'Desc' };
|
||||||
|
MediaPageActions.createPlaylist(payload);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'CREATE_PLAYLIST', playlist_data: payload });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should dispatch ADD_MEDIA_TO_PLAYLIST with ids', () => {
|
||||||
|
const playlist_id = 'pl-1';
|
||||||
|
const media_id = 'm-1';
|
||||||
|
MediaPageActions.addMediaToPlaylist(playlist_id, media_id);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'ADD_MEDIA_TO_PLAYLIST', playlist_id, media_id });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should dispatch REMOVE_MEDIA_FROM_PLAYLIST with ids', () => {
|
||||||
|
const playlist_id = 'pl-1';
|
||||||
|
const media_id = 'm-1';
|
||||||
|
MediaPageActions.removeMediaFromPlaylist(playlist_id, media_id);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_MEDIA_FROM_PLAYLIST', playlist_id, media_id });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should dispatch APPEND_NEW_PLAYLIST with provided playlist data', () => {
|
||||||
|
const playlist_data = {
|
||||||
|
playlist_id: 'pl-2',
|
||||||
|
add_date: new Date('2020-01-01T00:00:00Z'),
|
||||||
|
description: 'Cool',
|
||||||
|
title: 'T',
|
||||||
|
media_list: ['a', 'b'],
|
||||||
|
};
|
||||||
|
MediaPageActions.addNewPlaylist(playlist_data);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'APPEND_NEW_PLAYLIST', playlist_data });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
55
frontend/tests/utils/actions/PageActions.test.ts
Normal file
55
frontend/tests/utils/actions/PageActions.test.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import * as PageActions from '../../../src/static/js/utils/actions/PageActions';
|
||||||
|
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||||
|
|
||||||
|
// Mock the dispatcher module used by PageActions
|
||||||
|
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||||
|
|
||||||
|
describe('utils/actions', () => {
|
||||||
|
describe('PageActions', () => {
|
||||||
|
const dispatch = dispatcher.dispatch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('initPage', () => {
|
||||||
|
it('Should dispatch INIT_PAGE with provided page string', () => {
|
||||||
|
PageActions.initPage('home');
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'INIT_PAGE', page: 'home' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// @todo: Revisit this behavior
|
||||||
|
it('Should dispatch INIT_PAGE with empty string', () => {
|
||||||
|
PageActions.initPage('');
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'INIT_PAGE', page: '' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleMediaAutoPlay', () => {
|
||||||
|
it('Should dispatch TOGGLE_AUTO_PLAY action', () => {
|
||||||
|
PageActions.toggleMediaAutoPlay();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_AUTO_PLAY' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addNotification', () => {
|
||||||
|
it('Should dispatch ADD_NOTIFICATION with message and id', () => {
|
||||||
|
const notification = 'Saved!';
|
||||||
|
const notificationId = 'notif-1';
|
||||||
|
PageActions.addNotification(notification, notificationId);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'ADD_NOTIFICATION', notification, notificationId });
|
||||||
|
});
|
||||||
|
|
||||||
|
// @todo: Revisit this behavior
|
||||||
|
it('Should dispatch ADD_NOTIFICATION with empty notification message', () => {
|
||||||
|
const notification = '';
|
||||||
|
const notificationId = 'id-empty';
|
||||||
|
PageActions.addNotification(notification, notificationId);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'ADD_NOTIFICATION', notification, notificationId });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
96
frontend/tests/utils/actions/PlaylistPageActions.test.ts
Normal file
96
frontend/tests/utils/actions/PlaylistPageActions.test.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { PlaylistPageActions } from '../../../src/static/js/utils/actions';
|
||||||
|
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||||
|
|
||||||
|
// Mock the dispatcher module used by PlaylistPageActions
|
||||||
|
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||||
|
|
||||||
|
describe('utils/actions', () => {
|
||||||
|
describe('PlaylistPageActions', () => {
|
||||||
|
const dispatch = dispatcher.dispatch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loadPlaylistData', () => {
|
||||||
|
it('Should dispatch LOAD_PLAYLIST_DATA action', () => {
|
||||||
|
PlaylistPageActions.loadPlaylistData();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'LOAD_PLAYLIST_DATA' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleSave', () => {
|
||||||
|
it('Should dispatch TOGGLE_SAVE action', () => {
|
||||||
|
PlaylistPageActions.toggleSave();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_SAVE' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updatePlaylist', () => {
|
||||||
|
it('Should dispatch UPDATE_PLAYLIST with provided title and description', () => {
|
||||||
|
const payload = { title: 'My Playlist', description: 'A description' };
|
||||||
|
PlaylistPageActions.updatePlaylist(payload);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_PLAYLIST', playlist_data: payload });
|
||||||
|
});
|
||||||
|
|
||||||
|
// @todo: Revisit this behavior
|
||||||
|
it('Should dispatch UPDATE_PLAYLIST with empty strings for title and description', () => {
|
||||||
|
const payload = { title: '', description: '' };
|
||||||
|
PlaylistPageActions.updatePlaylist(payload);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_PLAYLIST', playlist_data: payload });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removePlaylist', () => {
|
||||||
|
it('Should dispatch REMOVE_PLAYLIST action', () => {
|
||||||
|
PlaylistPageActions.removePlaylist();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_PLAYLIST' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removedMediaFromPlaylist', () => {
|
||||||
|
it('Should dispatch MEDIA_REMOVED_FROM_PLAYLIST with media and playlist ids', () => {
|
||||||
|
PlaylistPageActions.removedMediaFromPlaylist('m1', 'p1');
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
type: 'MEDIA_REMOVED_FROM_PLAYLIST',
|
||||||
|
media_id: 'm1',
|
||||||
|
playlist_id: 'p1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// @todo: Revisit this behavior
|
||||||
|
it('Should dispatch MEDIA_REMOVED_FROM_PLAYLIST with empty ids as strings', () => {
|
||||||
|
PlaylistPageActions.removedMediaFromPlaylist('', '');
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
type: 'MEDIA_REMOVED_FROM_PLAYLIST',
|
||||||
|
media_id: '',
|
||||||
|
playlist_id: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('reorderedMediaInPlaylist', () => {
|
||||||
|
it('Should dispatch PLAYLIST_MEDIA_REORDERED with provided array', () => {
|
||||||
|
const items = [
|
||||||
|
{ id: '1', url: '/1', thumbnail_url: '/t1' },
|
||||||
|
{ id: '2', url: '/2', thumbnail_url: '/t2' },
|
||||||
|
];
|
||||||
|
PlaylistPageActions.reorderedMediaInPlaylist(items);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: items });
|
||||||
|
});
|
||||||
|
|
||||||
|
// @todo: Revisit this behavior
|
||||||
|
it('Should dispatch PLAYLIST_MEDIA_REORDERED with empty array for playlist media', () => {
|
||||||
|
const items: any[] = [];
|
||||||
|
PlaylistPageActions.reorderedMediaInPlaylist(items);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: items });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
39
frontend/tests/utils/actions/PlaylistViewActions.test.ts
Normal file
39
frontend/tests/utils/actions/PlaylistViewActions.test.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { PlaylistViewActions } from '../../../src/static/js/utils/actions';
|
||||||
|
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||||
|
|
||||||
|
// Mock the dispatcher module used by PlaylistViewActions
|
||||||
|
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||||
|
|
||||||
|
describe('utils/actions', () => {
|
||||||
|
describe('PlaylistViewActions', () => {
|
||||||
|
const dispatch = dispatcher.dispatch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleLoop', () => {
|
||||||
|
it('Should dispatch TOGGLE_LOOP action', () => {
|
||||||
|
PlaylistViewActions.toggleLoop();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_LOOP' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleShuffle', () => {
|
||||||
|
it('Should dispatch TOGGLE_SHUFFLE action', () => {
|
||||||
|
PlaylistViewActions.toggleShuffle();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_SHUFFLE' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleSave', () => {
|
||||||
|
it('Should dispatch TOGGLE_SAVE action', () => {
|
||||||
|
PlaylistViewActions.toggleSave();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_SAVE' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
27
frontend/tests/utils/actions/ProfilePageActions.test.ts
Normal file
27
frontend/tests/utils/actions/ProfilePageActions.test.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { ProfilePageActions } from '../../../src/static/js/utils/actions';
|
||||||
|
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||||
|
|
||||||
|
// Mock the dispatcher module used by ProfilePageActions
|
||||||
|
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||||
|
|
||||||
|
describe('utils/actions', () => {
|
||||||
|
describe('ProfilePageActions', () => {
|
||||||
|
const dispatch = dispatcher.dispatch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should dispatch LOAD_AUTHOR_DATA ', () => {
|
||||||
|
ProfilePageActions.load_author_data();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'LOAD_AUTHOR_DATA' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should dispatch REMOVE_PROFILE ', () => {
|
||||||
|
ProfilePageActions.remove_profile();
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_PROFILE' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
25
frontend/tests/utils/actions/SearchFieldActions.test.ts
Normal file
25
frontend/tests/utils/actions/SearchFieldActions.test.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { SearchFieldActions } from '../../../src/static/js/utils/actions';
|
||||||
|
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||||
|
|
||||||
|
// Mock the dispatcher module used by SearchFieldActions
|
||||||
|
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||||
|
|
||||||
|
describe('utils/actions', () => {
|
||||||
|
describe('SearchFieldActions', () => {
|
||||||
|
const dispatch = dispatcher.dispatch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('requestPredictions', () => {
|
||||||
|
it('Should dispatch REQUEST_PREDICTIONS with provided query strings', () => {
|
||||||
|
SearchFieldActions.requestPredictions('cats');
|
||||||
|
SearchFieldActions.requestPredictions('');
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'REQUEST_PREDICTIONS', query: 'cats' });
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'REQUEST_PREDICTIONS', query: '' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
72
frontend/tests/utils/actions/VideoViewerActions.test.ts
Normal file
72
frontend/tests/utils/actions/VideoViewerActions.test.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { VideoViewerActions } from '../../../src/static/js/utils/actions';
|
||||||
|
import dispatcher from '../../../src/static/js/utils/dispatcher';
|
||||||
|
|
||||||
|
// Mock the dispatcher module used by VideoViewerActions
|
||||||
|
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
|
||||||
|
|
||||||
|
describe('utils/actions', () => {
|
||||||
|
describe('VideoViewerActions', () => {
|
||||||
|
const dispatch = dispatcher.dispatch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(dispatcher.dispatch as jest.Mock).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('set_viewer_mode', () => {
|
||||||
|
it('Should dispatch SET_VIEWER_MODE with "true" and "false" for enabling and disabling theater mode', () => {
|
||||||
|
VideoViewerActions.set_viewer_mode(true);
|
||||||
|
VideoViewerActions.set_viewer_mode(false);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_VIEWER_MODE', inTheaterMode: true });
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_VIEWER_MODE', inTheaterMode: false });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('set_player_volume', () => {
|
||||||
|
it('Should dispatch SET_PLAYER_VOLUME with provided volume numbers', () => {
|
||||||
|
VideoViewerActions.set_player_volume(0);
|
||||||
|
VideoViewerActions.set_player_volume(0.75);
|
||||||
|
VideoViewerActions.set_player_volume(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(3);
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_PLAYER_VOLUME', playerVolume: 0 });
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_PLAYER_VOLUME', playerVolume: 0.75 });
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(3, { type: 'SET_PLAYER_VOLUME', playerVolume: 1 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('set_player_sound_muted', () => {
|
||||||
|
it('Should dispatch SET_PLAYER_SOUND_MUTED with "true" and "false"', () => {
|
||||||
|
VideoViewerActions.set_player_sound_muted(true);
|
||||||
|
VideoViewerActions.set_player_sound_muted(false);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_PLAYER_SOUND_MUTED', playerSoundMuted: true });
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(2, {
|
||||||
|
type: 'SET_PLAYER_SOUND_MUTED',
|
||||||
|
playerSoundMuted: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('set_video_quality', () => {
|
||||||
|
it('Should dispatch SET_VIDEO_QUALITY with "auto" and numeric quality', () => {
|
||||||
|
VideoViewerActions.set_video_quality('auto');
|
||||||
|
VideoViewerActions.set_video_quality(720);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_VIDEO_QUALITY', quality: 'auto' });
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_VIDEO_QUALITY', quality: 720 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('set_video_playback_speed', () => {
|
||||||
|
it('Should dispatch SET_VIDEO_PLAYBACK_SPEED with different speeds', () => {
|
||||||
|
VideoViewerActions.set_video_playback_speed(1.5);
|
||||||
|
VideoViewerActions.set_video_playback_speed(0.5);
|
||||||
|
VideoViewerActions.set_video_playback_speed(2);
|
||||||
|
expect(dispatch).toHaveBeenCalledTimes(3);
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: 1.5 });
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: 0.5 });
|
||||||
|
expect(dispatch).toHaveBeenNthCalledWith(3, { type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: 2 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
92
frontend/tests/utils/classes/BrowserCache.test.ts
Normal file
92
frontend/tests/utils/classes/BrowserCache.test.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { BrowserCache } from '../../../src/static/js/utils/classes/BrowserCache';
|
||||||
|
|
||||||
|
// Mocks for helpers used by BrowserCache
|
||||||
|
jest.mock('../../../src/static/js/utils/helpers/', () => ({
|
||||||
|
logErrorAndReturnError: jest.fn((args: any[]) => ({ error: true, args })),
|
||||||
|
logWarningAndReturnError: jest.fn((args: any[]) => ({ warning: true, args })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { logErrorAndReturnError } = jest.requireMock('../../../src/static/js/utils/helpers/');
|
||||||
|
|
||||||
|
describe('utils/classes', () => {
|
||||||
|
describe('BrowserCache', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Returns error when prefix is missing', () => {
|
||||||
|
const cache = BrowserCache(undefined, 3600);
|
||||||
|
expect(cache).toEqual(expect.objectContaining({ error: true }));
|
||||||
|
expect(logErrorAndReturnError).toHaveBeenCalledWith(['Cache object prefix is required']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Set and get returns stored primitive value before expiration', () => {
|
||||||
|
const cache = BrowserCache('prefix', 3600);
|
||||||
|
|
||||||
|
if (cache instanceof Error) {
|
||||||
|
expect(cache instanceof Error).toBe(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(cache.set('foo', 'bar')).toBe(true);
|
||||||
|
expect(cache.get('foo')).toBe('bar');
|
||||||
|
|
||||||
|
// Ensure value serialized in localStorage with namespaced key
|
||||||
|
const raw = localStorage.getItem('prefix[foo]') as string;
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
expect(parsed.value).toBe('bar');
|
||||||
|
expect(typeof parsed.expire).toBe('number');
|
||||||
|
expect(parsed.expire).toBeGreaterThan(Date.now());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Get returns null when expired', () => {
|
||||||
|
const cache = BrowserCache('prefix', 1);
|
||||||
|
|
||||||
|
if (cache instanceof Error) {
|
||||||
|
expect(cache instanceof Error).toBe(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.set('exp', { a: 1 });
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.advanceTimersByTime(1_000);
|
||||||
|
|
||||||
|
expect(cache.get('exp')).toBeNull();
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Clear removes only keys for its prefix', () => {
|
||||||
|
const cacheA = BrowserCache('A', 3600);
|
||||||
|
const cacheB = BrowserCache('B', 3600);
|
||||||
|
|
||||||
|
if (cacheA instanceof Error) {
|
||||||
|
expect(cacheA instanceof Error).toBe(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cacheB instanceof Error) {
|
||||||
|
expect(cacheB instanceof Error).toBe(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheA.set('x', 1);
|
||||||
|
cacheB.set('x', 2);
|
||||||
|
|
||||||
|
expect(localStorage.getItem('A[x]')).toBeTruthy();
|
||||||
|
expect(localStorage.getItem('B[x]')).toBeTruthy();
|
||||||
|
|
||||||
|
cacheA.clear();
|
||||||
|
|
||||||
|
expect(localStorage.getItem('A[x]')).toBeNull();
|
||||||
|
expect(localStorage.getItem('B[x]')).toBeTruthy();
|
||||||
|
|
||||||
|
cacheB.clear();
|
||||||
|
|
||||||
|
expect(localStorage.getItem('A[x]')).toBeNull();
|
||||||
|
expect(localStorage.getItem('B[x]')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
101
frontend/tests/utils/classes/MediaDurationInfo.test.ts
Normal file
101
frontend/tests/utils/classes/MediaDurationInfo.test.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { MediaDurationInfo } from '../../../src/static/js/utils/classes/MediaDurationInfo';
|
||||||
|
|
||||||
|
describe('utils/classes', () => {
|
||||||
|
describe('MediaDurationInfo', () => {
|
||||||
|
test('Initializes via constructor when seconds is a positive integer (<= 59)', () => {
|
||||||
|
const mdi = new MediaDurationInfo(42);
|
||||||
|
expect(mdi.toString()).toBe('0:42');
|
||||||
|
expect(mdi.ariaLabel()).toBe('42 seconds');
|
||||||
|
expect(mdi.ISO8601()).toBe('P0Y0M0DT0H0M42S');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Formats minutes and zero-pads seconds; no hours prefix under 60 minutes', () => {
|
||||||
|
const mdi = new MediaDurationInfo();
|
||||||
|
mdi.update(5 * 60 + 7);
|
||||||
|
expect(mdi.toString()).toBe('5:07');
|
||||||
|
expect(mdi.ariaLabel()).toBe('5 minutes, 7 seconds');
|
||||||
|
expect(mdi.ISO8601()).toBe('P0Y0M0DT0H5M7S');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Includes hours when duration >= 1 hour and zero-pads minutes when needed', () => {
|
||||||
|
const mdi = new MediaDurationInfo();
|
||||||
|
mdi.update(1 * 3600 + 2 * 60 + 3);
|
||||||
|
expect(mdi.toString()).toBe('1:02:03');
|
||||||
|
expect(mdi.ariaLabel()).toBe('1 hours, 2 minutes, 3 seconds');
|
||||||
|
expect(mdi.ISO8601()).toBe('P0Y0M0DT1H2M3S');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Accumulates hours when days are present (e.g., 1 day + 2:03:04 => 26:03:04)', () => {
|
||||||
|
const mdi = new MediaDurationInfo();
|
||||||
|
const seconds = 1 * 86400 + 2 * 3600 + 3 * 60 + 4; // 1d 2:03:04 => 26:03:04
|
||||||
|
mdi.update(seconds);
|
||||||
|
expect(mdi.toString()).toBe('26:03:04');
|
||||||
|
expect(mdi.ariaLabel()).toBe('26 hours, 3 minutes, 4 seconds');
|
||||||
|
expect(mdi.ISO8601()).toBe('P0Y0M0DT26H3M4S');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Large durations: multiple days correctly mapped into hours', () => {
|
||||||
|
const mdi = new MediaDurationInfo();
|
||||||
|
const seconds = 3 * 86400 + 10 * 3600 + 15 * 60 + 9; // 3d 10:15:09 => 82:15:09
|
||||||
|
mdi.update(seconds);
|
||||||
|
expect(mdi.toString()).toBe('82:15:09');
|
||||||
|
expect(mdi.ariaLabel()).toBe('82 hours, 15 minutes, 9 seconds');
|
||||||
|
expect(mdi.ISO8601()).toBe('P0Y0M0DT82H15M9S');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Caching: toString and ariaLabel recompute only after update()', () => {
|
||||||
|
const mdi = new MediaDurationInfo(59);
|
||||||
|
const firstToString = mdi.toString();
|
||||||
|
const firstAria = mdi.ariaLabel();
|
||||||
|
expect(firstToString).toBe('0:59');
|
||||||
|
expect(firstAria).toBe('59 seconds');
|
||||||
|
|
||||||
|
// Call again to hit cached path
|
||||||
|
expect(mdi.toString()).toBe(firstToString);
|
||||||
|
expect(mdi.ariaLabel()).toBe(firstAria);
|
||||||
|
|
||||||
|
// Update and ensure cache invalidates
|
||||||
|
mdi.update(60);
|
||||||
|
expect(mdi.toString()).toBe('1:00');
|
||||||
|
expect(mdi.ariaLabel()).toBe('1 minutes');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Ignores invalid (non-positive integer or zero) updates, retaining previous value', () => {
|
||||||
|
const mdi = new MediaDurationInfo(10);
|
||||||
|
expect(mdi.toString()).toBe('0:10');
|
||||||
|
|
||||||
|
mdi.update(1.23);
|
||||||
|
expect(mdi.toString()).toBe('0:10');
|
||||||
|
|
||||||
|
mdi.update(-5);
|
||||||
|
expect(mdi.toString()).toBe('0:10');
|
||||||
|
|
||||||
|
mdi.update('x');
|
||||||
|
expect(mdi.toString()).toBe('0:10');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Boundary conditions around a minute and an hour', () => {
|
||||||
|
const mdi = new MediaDurationInfo();
|
||||||
|
|
||||||
|
mdi.update(59);
|
||||||
|
expect(mdi.toString()).toBe('0:59');
|
||||||
|
|
||||||
|
mdi.update(60);
|
||||||
|
expect(mdi.toString()).toBe('1:00');
|
||||||
|
|
||||||
|
mdi.update(3599);
|
||||||
|
expect(mdi.toString()).toBe('59:59');
|
||||||
|
|
||||||
|
mdi.update(3600);
|
||||||
|
expect(mdi.toString()).toBe('1:00:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
// @todo: Revisit this behavior
|
||||||
|
test('Constructs without initial seconds', () => {
|
||||||
|
const mdi = new MediaDurationInfo();
|
||||||
|
expect(typeof mdi.toString()).toBe('function');
|
||||||
|
expect(mdi.ariaLabel()).toBe('');
|
||||||
|
expect(mdi.ISO8601()).toBe('P0Y0M0DTundefinedHundefinedMundefinedS');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
102
frontend/tests/utils/classes/UpNextLoaderView.test.ts
Normal file
102
frontend/tests/utils/classes/UpNextLoaderView.test.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { UpNextLoaderView } from '../../../src/static/js/utils/classes/UpNextLoaderView';
|
||||||
|
|
||||||
|
// Minimal helpers mocks used by UpNextLoaderView
|
||||||
|
jest.mock('../../../src/static/js/utils/helpers/', () => ({
|
||||||
|
addClassname: jest.fn((el: any, cn: string) => el && el.classList && el.classList.add(cn)),
|
||||||
|
removeClassname: jest.fn((el: any, cn: string) => el && el.classList && el.classList.remove(cn)),
|
||||||
|
translateString: (s: string) => s,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { addClassname, removeClassname } = jest.requireMock('../../../src/static/js/utils/helpers/');
|
||||||
|
|
||||||
|
const makeNextItem = () => ({
|
||||||
|
url: '/next-url',
|
||||||
|
title: 'Next title',
|
||||||
|
author_name: 'Jane Doe',
|
||||||
|
thumbnail_url: 'https://example.com/thumb.jpg',
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('utils/classes', () => {
|
||||||
|
describe('UpNextLoaderView', () => {
|
||||||
|
test('html() builds structure with expected classes and content', () => {
|
||||||
|
const v = new UpNextLoaderView(makeNextItem());
|
||||||
|
|
||||||
|
const root = v.html();
|
||||||
|
|
||||||
|
expect(root).toBeInstanceOf(HTMLElement);
|
||||||
|
expect(root.querySelector('.up-next-loader-inner')).not.toBeNull();
|
||||||
|
expect(root.querySelector('.up-next-label')!.textContent).toBe('Up Next');
|
||||||
|
expect(root.querySelector('.next-media-title')!.textContent).toBe('Next title');
|
||||||
|
expect(root.querySelector('.next-media-author')!.textContent).toBe('Jane Doe');
|
||||||
|
|
||||||
|
// poster background
|
||||||
|
const poster = root.querySelector('.next-media-poster') as HTMLElement;
|
||||||
|
expect(poster.style.backgroundImage).toContain('thumb.jpg');
|
||||||
|
|
||||||
|
// go-next link points to next url
|
||||||
|
const link = root.querySelector('.go-next a') as HTMLAnchorElement;
|
||||||
|
expect(link.getAttribute('href')).toBe('/next-url');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setVideoJsPlayerElem marks player with vjs-mediacms-has-up-next-view class', () => {
|
||||||
|
const v = new UpNextLoaderView(makeNextItem());
|
||||||
|
const player = document.createElement('div');
|
||||||
|
|
||||||
|
v.setVideoJsPlayerElem(player);
|
||||||
|
|
||||||
|
expect(addClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-has-up-next-view');
|
||||||
|
expect(v.vjsPlayerElem).toBe(player);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('startTimer shows view, registers scroll, and navigates after 10s', () => {
|
||||||
|
const next = makeNextItem();
|
||||||
|
const v = new UpNextLoaderView(next);
|
||||||
|
const player = document.createElement('div');
|
||||||
|
|
||||||
|
v.setVideoJsPlayerElem(player);
|
||||||
|
v.startTimer();
|
||||||
|
|
||||||
|
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-up-next-hidden');
|
||||||
|
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cancelTimer clears timeout, stops scroll, and marks canceled', () => {
|
||||||
|
const v = new UpNextLoaderView(makeNextItem());
|
||||||
|
const player = document.createElement('div');
|
||||||
|
|
||||||
|
v.setVideoJsPlayerElem(player);
|
||||||
|
|
||||||
|
v.startTimer();
|
||||||
|
v.cancelTimer();
|
||||||
|
|
||||||
|
expect(addClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Cancel button click hides the view and cancels timer', () => {
|
||||||
|
const v = new UpNextLoaderView(makeNextItem());
|
||||||
|
const player = document.createElement('div');
|
||||||
|
v.setVideoJsPlayerElem(player);
|
||||||
|
|
||||||
|
v.startTimer();
|
||||||
|
const root = v.html();
|
||||||
|
const cancelBtn = root.querySelector('.up-next-cancel button') as HTMLButtonElement;
|
||||||
|
cancelBtn.click();
|
||||||
|
|
||||||
|
expect(addClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('showTimerView shows or starts timer based on flag', () => {
|
||||||
|
const v = new UpNextLoaderView(makeNextItem());
|
||||||
|
const player = document.createElement('div');
|
||||||
|
v.setVideoJsPlayerElem(player);
|
||||||
|
|
||||||
|
// beginTimer=false -> just show view
|
||||||
|
v.showTimerView(false);
|
||||||
|
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-up-next-hidden');
|
||||||
|
|
||||||
|
// beginTimer=true -> starts timer
|
||||||
|
v.showTimerView(true);
|
||||||
|
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
739
frontend/tests/utils/stores/MediaPageStore.test.ts
Normal file
739
frontend/tests/utils/stores/MediaPageStore.test.ts
Normal file
@ -0,0 +1,739 @@
|
|||||||
|
import { csrfToken, deleteRequest, getRequest, postRequest, putRequest } from '../../../src/static/js/utils/helpers';
|
||||||
|
|
||||||
|
const MEDIA_ID = 'MEDIA_ID';
|
||||||
|
const PLAYLIST_ID = 'PLAYLIST_ID';
|
||||||
|
|
||||||
|
window.history.pushState({}, '', `/?m=${MEDIA_ID}&pl=${PLAYLIST_ID}`);
|
||||||
|
|
||||||
|
import store from '../../../src/static/js/utils/stores/MediaPageStore';
|
||||||
|
|
||||||
|
import { sampleGlobalMediaCMS, sampleMediaCMSConfig } from '../../tests-constants';
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/classes/', () => ({
|
||||||
|
BrowserCache: jest.fn().mockImplementation(() => ({
|
||||||
|
get: jest.fn(),
|
||||||
|
set: jest.fn(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||||
|
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/helpers', () => ({
|
||||||
|
BrowserEvents: jest.fn().mockImplementation(() => ({
|
||||||
|
doc: jest.fn(),
|
||||||
|
win: jest.fn(),
|
||||||
|
})),
|
||||||
|
csrfToken: jest.fn(),
|
||||||
|
deleteRequest: jest.fn(),
|
||||||
|
exportStore: jest.fn((store) => store),
|
||||||
|
getRequest: jest.fn(),
|
||||||
|
postRequest: jest.fn(),
|
||||||
|
putRequest: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('utils/store', () => {
|
||||||
|
describe('MediaPageStore', () => {
|
||||||
|
const handler = store.actions_handler.bind(store);
|
||||||
|
|
||||||
|
const onLoadedViewerPlaylistData = jest.fn();
|
||||||
|
const onLoadedPagePlaylistData = jest.fn();
|
||||||
|
const onLoadedViewerPlaylistError = jest.fn();
|
||||||
|
const onLoadedVideoData = jest.fn();
|
||||||
|
const onLoadedImageData = jest.fn();
|
||||||
|
const onLoadedMediaData = jest.fn();
|
||||||
|
const onLoadedMediaError = jest.fn();
|
||||||
|
const onCommentsLoad = jest.fn();
|
||||||
|
const onUsersLoad = jest.fn();
|
||||||
|
const onPlaylistsLoad = jest.fn();
|
||||||
|
const onLikedMediaFailedRequest = jest.fn();
|
||||||
|
const onLikedMedia = jest.fn();
|
||||||
|
const onDislikedMediaFailedRequest = jest.fn();
|
||||||
|
const onDislikedMedia = jest.fn();
|
||||||
|
const onReportedMedia = jest.fn();
|
||||||
|
const onPlaylistCreationCompleted = jest.fn();
|
||||||
|
const onPlaylistCreationFailed = jest.fn();
|
||||||
|
const onMediaPlaylistAdditionCompleted = jest.fn();
|
||||||
|
const onMediaPlaylistAdditionFailed = jest.fn();
|
||||||
|
const onMediaPlaylistRemovalCompleted = jest.fn();
|
||||||
|
const onMediaPlaylistRemovalFailed = jest.fn();
|
||||||
|
const onCopiedMediaLink = jest.fn();
|
||||||
|
const onCopiedEmbedMediaCode = jest.fn();
|
||||||
|
const onMediaDelete = jest.fn();
|
||||||
|
const onMediaDeleteFail = jest.fn();
|
||||||
|
const onCommentDeleteFail = jest.fn();
|
||||||
|
const onCommentDelete = jest.fn();
|
||||||
|
const onCommentSubmitFail = jest.fn();
|
||||||
|
const onCommentSubmit = jest.fn();
|
||||||
|
|
||||||
|
store.on('loaded_viewer_playlist_data', onLoadedViewerPlaylistData);
|
||||||
|
store.on('loaded_page_playlist_data', onLoadedPagePlaylistData);
|
||||||
|
store.on('loaded_viewer_playlist_error', onLoadedViewerPlaylistError);
|
||||||
|
store.on('loaded_video_data', onLoadedVideoData);
|
||||||
|
store.on('loaded_image_data', onLoadedImageData);
|
||||||
|
store.on('loaded_media_data', onLoadedMediaData);
|
||||||
|
store.on('loaded_media_error', onLoadedMediaError);
|
||||||
|
store.on('comments_load', onCommentsLoad);
|
||||||
|
store.on('users_load', onUsersLoad);
|
||||||
|
store.on('playlists_load', onPlaylistsLoad);
|
||||||
|
store.on('liked_media_failed_request', onLikedMediaFailedRequest);
|
||||||
|
store.on('liked_media', onLikedMedia);
|
||||||
|
store.on('disliked_media_failed_request', onDislikedMediaFailedRequest);
|
||||||
|
store.on('disliked_media', onDislikedMedia);
|
||||||
|
store.on('reported_media', onReportedMedia);
|
||||||
|
store.on('playlist_creation_completed', onPlaylistCreationCompleted);
|
||||||
|
store.on('playlist_creation_failed', onPlaylistCreationFailed);
|
||||||
|
store.on('media_playlist_addition_completed', onMediaPlaylistAdditionCompleted);
|
||||||
|
store.on('media_playlist_addition_failed', onMediaPlaylistAdditionFailed);
|
||||||
|
store.on('media_playlist_removal_completed', onMediaPlaylistRemovalCompleted);
|
||||||
|
store.on('media_playlist_removal_failed', onMediaPlaylistRemovalFailed);
|
||||||
|
store.on('copied_media_link', onCopiedMediaLink);
|
||||||
|
store.on('copied_embed_media_code', onCopiedEmbedMediaCode);
|
||||||
|
store.on('media_delete', onMediaDelete);
|
||||||
|
store.on('media_delete_fail', onMediaDeleteFail);
|
||||||
|
store.on('comment_delete_fail', onCommentDeleteFail);
|
||||||
|
store.on('comment_delete', onCommentDelete);
|
||||||
|
store.on('comment_submit_fail', onCommentSubmitFail);
|
||||||
|
store.on('comment_submit', onCommentSubmit);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
(globalThis as any).window.MediaCMS = {
|
||||||
|
// mediaId: MEDIA_ID, // @note: It doesn't belong in 'sampleGlobalMediaCMS, but it could be used
|
||||||
|
features: sampleGlobalMediaCMS.features,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
delete (globalThis as any).window.MediaCMS;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Validate initial values', () => {
|
||||||
|
expect(store.get('users')).toStrictEqual([]);
|
||||||
|
expect(store.get('playlists')).toStrictEqual([]);
|
||||||
|
expect(store.get('media-load-error-type')).toBe(null);
|
||||||
|
expect(store.get('media-load-error-message')).toBe(null);
|
||||||
|
expect(store.get('media-comments')).toStrictEqual([]);
|
||||||
|
expect(store.get('media-data')).toBe(null);
|
||||||
|
expect(store.get('media-id')).toBe(MEDIA_ID);
|
||||||
|
expect(store.get('media-url')).toBe('N/A');
|
||||||
|
expect(store.get('media-edit-subtitle-url')).toBe(null);
|
||||||
|
expect(store.get('media-likes')).toBe('N/A');
|
||||||
|
expect(store.get('media-dislikes')).toBe('N/A');
|
||||||
|
expect(store.get('media-summary')).toBe(null);
|
||||||
|
expect(store.get('media-categories')).toStrictEqual([]);
|
||||||
|
expect(store.get('media-tags')).toStrictEqual([]);
|
||||||
|
expect(store.get('media-type')).toBe(null);
|
||||||
|
expect(store.get('media-original-url')).toBe(null);
|
||||||
|
expect(store.get('media-thumbnail-url')).toBe(null);
|
||||||
|
expect(store.get('user-liked-media')).toBe(false);
|
||||||
|
expect(store.get('user-disliked-media')).toBe(false);
|
||||||
|
expect(store.get('media-author-thumbnail-url')).toBe(null);
|
||||||
|
expect(store.get('playlist-data')).toBe(null);
|
||||||
|
expect(store.get('playlist-id')).toBe(null);
|
||||||
|
expect(store.get('playlist-next-media-url')).toBe(null);
|
||||||
|
expect(store.get('playlist-previous-media-url')).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Trigger and validate actions behavior', () => {
|
||||||
|
const MEDIA_DATA = {
|
||||||
|
add_subtitle_url: '/MEDIA_DATA_ADD_SUBTITLE_URL',
|
||||||
|
author_thumbnail: 'MEDIA_DATA_AUTHOR_THUMBNAIL',
|
||||||
|
categories_info: [
|
||||||
|
{ title: 'Art', url: '/search?c=Art' },
|
||||||
|
{ title: 'Documentary', url: '/search?c=Documentary' },
|
||||||
|
],
|
||||||
|
likes: 12,
|
||||||
|
dislikes: 4,
|
||||||
|
media_type: 'video',
|
||||||
|
original_media_url: 'MEDIA_DATA_ORIGINAL_MEDIA_URL',
|
||||||
|
reported_times: 0,
|
||||||
|
summary: 'MEDIA_DATA_SUMMARY',
|
||||||
|
tags_info: [
|
||||||
|
{ title: 'and', url: '/search?t=and' },
|
||||||
|
{ title: 'behavior', url: '/search?t=behavior' },
|
||||||
|
],
|
||||||
|
thumbnail_url: 'MEDIA_DATA_THUMBNAIL_URL',
|
||||||
|
url: '/MEDIA_DATA_URL',
|
||||||
|
};
|
||||||
|
const PLAYLIST_DATA = {
|
||||||
|
playlist_media: [
|
||||||
|
{ friendly_token: `${MEDIA_ID}_2`, url: '/PLAYLIT_MEDIA_URL_2' },
|
||||||
|
{ friendly_token: MEDIA_ID, url: '/PLAYLIT_MEDIA_URL_1' },
|
||||||
|
{ friendly_token: `${MEDIA_ID}_3`, url: '/PLAYLIT_MEDIA_URL_3' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const USER_PLAYLIST_DATA = { playlist_media: [{ url: 'm=PLAYLIST_MEDIA_ID' }] };
|
||||||
|
|
||||||
|
test('Action type: "LOAD_MEDIA_DATA"', () => {
|
||||||
|
const MEDIA_API_URL = `${sampleMediaCMSConfig.api.media}/${MEDIA_ID}`;
|
||||||
|
const MEDIA_COMMENTS_API_URL = `${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/comments`;
|
||||||
|
const PLAYLIST_API_URL = `${sampleMediaCMSConfig.api.playlists}/${PLAYLIST_ID}`;
|
||||||
|
const USERS_API_URL = sampleMediaCMSConfig.api.users;
|
||||||
|
const USER_PLAYLISTS_API_URL = `${sampleMediaCMSConfig.api.user.playlists}${sampleMediaCMSConfig.member.username}`;
|
||||||
|
const USER_PLAYLIST_API_URL = `${sampleMediaCMSConfig.site.url}/${'PLAYLIST_API_URL'.replace(/^\//g, '')}`;
|
||||||
|
|
||||||
|
const MEDIA_COMMENTS_RESULTS = ['COMMENT_ID_1'];
|
||||||
|
const USERS_RESULTS = ['USER_ID_1'];
|
||||||
|
const USER_PLAYLISTS_RESULTS = [
|
||||||
|
{
|
||||||
|
url: `/${PLAYLIST_ID}`,
|
||||||
|
user: sampleMediaCMSConfig.member.username,
|
||||||
|
title: 'PLAYLIST_TITLE',
|
||||||
|
description: 'PLAYLIST_DECRIPTION',
|
||||||
|
add_date: 'PLAYLIST_ADD_DATE',
|
||||||
|
api_url: 'PLAYLIST_API_URL',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
(getRequest as jest.Mock).mockImplementation((url, _cache, successCallback, _failCallback) => {
|
||||||
|
if (url === PLAYLIST_API_URL) {
|
||||||
|
return successCallback({ data: PLAYLIST_DATA });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url === USER_PLAYLIST_API_URL) {
|
||||||
|
return successCallback({ data: USER_PLAYLIST_DATA });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url === MEDIA_API_URL) {
|
||||||
|
return successCallback({ data: MEDIA_DATA });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url === USERS_API_URL) {
|
||||||
|
return successCallback({ data: { count: USERS_RESULTS.length, results: USERS_RESULTS } });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url === MEDIA_COMMENTS_API_URL) {
|
||||||
|
return successCallback({
|
||||||
|
data: { count: MEDIA_COMMENTS_RESULTS.length, results: MEDIA_COMMENTS_RESULTS },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url === USER_PLAYLISTS_API_URL) {
|
||||||
|
return successCallback({
|
||||||
|
data: { count: USER_PLAYLISTS_RESULTS.length, results: USER_PLAYLISTS_RESULTS },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
handler({ type: 'LOAD_MEDIA_DATA' });
|
||||||
|
|
||||||
|
expect(getRequest).toHaveBeenCalledTimes(6);
|
||||||
|
|
||||||
|
expect(getRequest).toHaveBeenCalledWith(
|
||||||
|
PLAYLIST_API_URL,
|
||||||
|
false,
|
||||||
|
store.playlistDataResponse,
|
||||||
|
store.playlistDataErrorResponse
|
||||||
|
);
|
||||||
|
expect(getRequest).toHaveBeenCalledWith(
|
||||||
|
MEDIA_API_URL,
|
||||||
|
false,
|
||||||
|
store.dataResponse,
|
||||||
|
store.dataErrorResponse
|
||||||
|
);
|
||||||
|
expect(getRequest).toHaveBeenCalledWith(MEDIA_COMMENTS_API_URL, false, store.commentsResponse);
|
||||||
|
expect(getRequest).toHaveBeenCalledWith(USERS_API_URL, false, store.usersResponse);
|
||||||
|
expect(getRequest).toHaveBeenCalledWith(USER_PLAYLISTS_API_URL, false, store.playlistsResponse);
|
||||||
|
expect(getRequest).toHaveBeenCalledWith(USER_PLAYLIST_API_URL, false, expect.any(Function));
|
||||||
|
|
||||||
|
expect(onLoadedViewerPlaylistData).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onLoadedPagePlaylistData).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onLoadedViewerPlaylistError).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onLoadedVideoData).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onLoadedImageData).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onLoadedMediaData).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onLoadedMediaError).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onCommentsLoad).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onUsersLoad).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onPlaylistsLoad).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onLikedMediaFailedRequest).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onLikedMedia).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onDislikedMediaFailedRequest).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onDislikedMedia).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onReportedMedia).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onPlaylistCreationCompleted).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onPlaylistCreationFailed).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onMediaPlaylistAdditionCompleted).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onMediaPlaylistAdditionFailed).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onMediaPlaylistRemovalCompleted).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onMediaPlaylistRemovalFailed).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onCopiedMediaLink).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onCopiedEmbedMediaCode).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onMediaDelete).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onMediaDeleteFail).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onCommentDeleteFail).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onCommentDelete).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onCommentSubmitFail).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onCommentSubmit).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
|
expect(store.isVideo()).toBeTruthy();
|
||||||
|
|
||||||
|
expect(store.get('users')).toStrictEqual(USERS_RESULTS);
|
||||||
|
expect(store.get('playlists')).toStrictEqual([
|
||||||
|
{
|
||||||
|
playlist_id: PLAYLIST_ID,
|
||||||
|
title: 'PLAYLIST_TITLE',
|
||||||
|
description: 'PLAYLIST_DECRIPTION',
|
||||||
|
add_date: 'PLAYLIST_ADD_DATE',
|
||||||
|
media_list: ['PLAYLIST_MEDIA_ID'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(store.get('media-load-error-type')).toBe(null);
|
||||||
|
expect(store.get('media-load-error-message')).toBe(null);
|
||||||
|
expect(store.get('media-comments')).toStrictEqual(MEDIA_COMMENTS_RESULTS);
|
||||||
|
expect(store.get('media-data')).toBe(MEDIA_DATA);
|
||||||
|
expect(store.get('media-id')).toBe(MEDIA_ID);
|
||||||
|
expect(store.get('media-url')).toBe(MEDIA_DATA.url);
|
||||||
|
expect(store.get('media-edit-subtitle-url')).toBe(MEDIA_DATA.add_subtitle_url);
|
||||||
|
expect(store.get('media-likes')).toBe(MEDIA_DATA.likes);
|
||||||
|
expect(store.get('media-dislikes')).toBe(MEDIA_DATA.dislikes);
|
||||||
|
expect(store.get('media-summary')).toBe(MEDIA_DATA.summary);
|
||||||
|
expect(store.get('media-categories')).toStrictEqual(MEDIA_DATA.categories_info);
|
||||||
|
expect(store.get('media-tags')).toStrictEqual(MEDIA_DATA.tags_info);
|
||||||
|
expect(store.get('media-type')).toBe(MEDIA_DATA.media_type);
|
||||||
|
expect(store.get('media-original-url')).toBe(MEDIA_DATA.original_media_url);
|
||||||
|
expect(store.get('media-thumbnail-url')).toBe(MEDIA_DATA.thumbnail_url);
|
||||||
|
expect(store.get('user-liked-media')).toBe(false);
|
||||||
|
expect(store.get('user-disliked-media')).toBe(false);
|
||||||
|
expect(store.get('media-author-thumbnail-url')).toBe(`/${MEDIA_DATA.author_thumbnail}`);
|
||||||
|
expect(store.get('playlist-data')).toBe(PLAYLIST_DATA);
|
||||||
|
expect(store.get('playlist-id')).toBe(PLAYLIST_ID);
|
||||||
|
expect(store.get('playlist-next-media-url')).toBe(
|
||||||
|
`${PLAYLIST_DATA.playlist_media[2].url}&pl=${PLAYLIST_ID}`
|
||||||
|
);
|
||||||
|
expect(store.get('playlist-previous-media-url')).toBe(
|
||||||
|
`${PLAYLIST_DATA.playlist_media[0].url}&pl=${PLAYLIST_ID}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "LIKE_MEDIA"', () => {
|
||||||
|
// Mock the CSRF token
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
// Mock post request
|
||||||
|
(postRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _postData, _configData, _sync, successCallback, _failCallback) =>
|
||||||
|
successCallback({ data: {} })
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'LIKE_MEDIA' });
|
||||||
|
|
||||||
|
// Verify postRequest was called with correct parameters
|
||||||
|
expect(postRequest).toHaveBeenCalledWith(
|
||||||
|
`${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/actions`,
|
||||||
|
{ type: 'like' },
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
store.likeActionResponse,
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onLikedMedia).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(store.get('media-likes')).toBe(MEDIA_DATA.likes + 1);
|
||||||
|
expect(store.get('media-dislikes')).toBe(MEDIA_DATA.dislikes);
|
||||||
|
expect(store.get('user-liked-media')).toBe(true);
|
||||||
|
expect(store.get('user-disliked-media')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "DISLIKE_MEDIA"', () => {
|
||||||
|
handler({ type: 'DISLIKE_MEDIA' });
|
||||||
|
|
||||||
|
expect(postRequest).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onDislikedMedia).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
|
expect(store.get('media-likes')).toBe(MEDIA_DATA.likes + 1);
|
||||||
|
expect(store.get('media-dislikes')).toBe(MEDIA_DATA.dislikes);
|
||||||
|
expect(store.get('user-liked-media')).toBe(true);
|
||||||
|
expect(store.get('user-disliked-media')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "REPORT_MEDIA"', () => {
|
||||||
|
const REPORT_DESCRIPTION = 'REPORT_DESCRIPTION';
|
||||||
|
|
||||||
|
// Mock the CSRF token
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
// Mock post request
|
||||||
|
(postRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _postData, _configData, _sync, successCallback, _failCallback) =>
|
||||||
|
successCallback({ data: {} })
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'REPORT_MEDIA', reportDescription: REPORT_DESCRIPTION });
|
||||||
|
|
||||||
|
// Verify postRequest was called with correct parameters
|
||||||
|
expect(postRequest).toHaveBeenCalledWith(
|
||||||
|
`${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/actions`,
|
||||||
|
{ type: 'report', extra_info: REPORT_DESCRIPTION },
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
store.reportActionResponse,
|
||||||
|
store.reportActionResponse
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onReportedMedia).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "COPY_SHARE_LINK"', () => {
|
||||||
|
document.execCommand = jest.fn(); // @deprecated
|
||||||
|
const inputElement = document.createElement('input');
|
||||||
|
handler({ type: 'COPY_SHARE_LINK', inputElement });
|
||||||
|
expect(onCopiedMediaLink).toHaveBeenCalledTimes(1);
|
||||||
|
expect(document.execCommand).toHaveBeenCalledWith('copy');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "COPY_EMBED_MEDIA_CODE"', () => {
|
||||||
|
document.execCommand = jest.fn(); // @deprecated
|
||||||
|
const inputElement = document.createElement('input');
|
||||||
|
handler({ type: 'COPY_EMBED_MEDIA_CODE', inputElement });
|
||||||
|
expect(onCopiedEmbedMediaCode).toHaveBeenCalledTimes(1);
|
||||||
|
expect(document.execCommand).toHaveBeenCalledWith('copy');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Action type: "REMOVE_MEDIA"', () => {
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock the CSRF token
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Verify deleteRequest was called with correct parameters
|
||||||
|
expect(deleteRequest).toHaveBeenCalledWith(
|
||||||
|
`${sampleMediaCMSConfig.api.media}/${MEDIA_ID}`,
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
store.removeMediaResponse,
|
||||||
|
store.removeMediaFail
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fast-forward time
|
||||||
|
jest.advanceTimersByTime(100);
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Successful', () => {
|
||||||
|
// Mock delete request
|
||||||
|
(deleteRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _configData, _sync, successCallback, _failCallback) => successCallback({ status: 204 })
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'REMOVE_MEDIA' });
|
||||||
|
|
||||||
|
expect(onMediaDelete).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onMediaDelete).toHaveBeenCalledWith(MEDIA_ID);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Failed', () => {
|
||||||
|
// Mock delete request
|
||||||
|
(deleteRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _configData, _sync, _successCallback, failCallback) => failCallback({})
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'REMOVE_MEDIA' });
|
||||||
|
|
||||||
|
expect(onMediaDeleteFail).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Action type: "SUBMIT_COMMENT"', () => {
|
||||||
|
const COMMENT_TEXT = 'COMMENT_TEXT';
|
||||||
|
const COMMENT_UID = 'COMMENT_UID';
|
||||||
|
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock the CSRF token
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Verify postRequest was called with correct parameters
|
||||||
|
expect(postRequest).toHaveBeenCalledWith(
|
||||||
|
`${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/comments`,
|
||||||
|
{ text: COMMENT_TEXT },
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
store.submitCommentResponse,
|
||||||
|
store.submitCommentFail
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fast-forward time
|
||||||
|
jest.advanceTimersByTime(100);
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Successful', () => {
|
||||||
|
// Mock post request
|
||||||
|
(postRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _postData, _configData, _sync, successCallback, _failCallback) =>
|
||||||
|
successCallback({ data: { uid: COMMENT_UID }, status: 201 })
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'SUBMIT_COMMENT', commentText: COMMENT_TEXT });
|
||||||
|
|
||||||
|
expect(onCommentSubmit).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onCommentSubmit).toHaveBeenCalledWith(COMMENT_UID);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Failed', () => {
|
||||||
|
// Mock post request
|
||||||
|
(postRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _postData, _configData, _sync, _successCallback, failCallback) => failCallback()
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'SUBMIT_COMMENT', commentText: COMMENT_TEXT });
|
||||||
|
|
||||||
|
expect(onCommentSubmitFail).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Action type: "DELETE_COMMENT"', () => {
|
||||||
|
const COMMENT_ID = 'COMMENT_ID';
|
||||||
|
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock the CSRF token
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Verify deleteRequest was called with correct parameters
|
||||||
|
expect(deleteRequest).toHaveBeenCalledWith(
|
||||||
|
`${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/comments/${COMMENT_ID}`,
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
store.removeCommentResponse,
|
||||||
|
store.removeCommentFail
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fast-forward time
|
||||||
|
jest.advanceTimersByTime(100);
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Successful', () => {
|
||||||
|
// Mock delete request
|
||||||
|
(deleteRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _configData, _sync, successCallback, _failCallback) => successCallback({ status: 204 })
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'DELETE_COMMENT', commentId: COMMENT_ID });
|
||||||
|
|
||||||
|
expect(onCommentDelete).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Failed', () => {
|
||||||
|
// Mock delete request
|
||||||
|
(deleteRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _configData, _sync, _successCallback, failCallback) => failCallback()
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'DELETE_COMMENT', commentId: COMMENT_ID });
|
||||||
|
|
||||||
|
expect(onCommentDeleteFail).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Action type: "CREATE_PLAYLIST"', () => {
|
||||||
|
const NEW_PLAYLIST_DATA = {
|
||||||
|
title: 'NEW_PLAYLIST_DATA_TITLE',
|
||||||
|
description: 'NEW_PLAYLIST_DATA_DESCRIPTION',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock the CSRF token
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Verify postRequest was called with correct parameters
|
||||||
|
expect(postRequest).toHaveBeenCalledWith(
|
||||||
|
sampleMediaCMSConfig.api.playlists,
|
||||||
|
NEW_PLAYLIST_DATA,
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
expect.any(Function),
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Successful', () => {
|
||||||
|
const NEW_PLAYLIST_RESPONSE_DATA = { uid: 'COMMENT_UID' };
|
||||||
|
|
||||||
|
// Mock post request
|
||||||
|
(postRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _postData, _configData, _sync, successCallback, _failCallback) =>
|
||||||
|
successCallback({ data: NEW_PLAYLIST_RESPONSE_DATA, status: 201 })
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'CREATE_PLAYLIST', playlist_data: NEW_PLAYLIST_DATA });
|
||||||
|
|
||||||
|
// Verify postRequest was called with correct parameters
|
||||||
|
expect(postRequest).toHaveBeenCalledWith(
|
||||||
|
sampleMediaCMSConfig.api.playlists,
|
||||||
|
NEW_PLAYLIST_DATA,
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
expect.any(Function),
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onPlaylistCreationCompleted).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onPlaylistCreationCompleted).toHaveBeenCalledWith(NEW_PLAYLIST_RESPONSE_DATA);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Failed', () => {
|
||||||
|
// Mock post request
|
||||||
|
(postRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _postData, _configData, _sync, _successCallback, failCallback) => failCallback()
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'CREATE_PLAYLIST', playlist_data: NEW_PLAYLIST_DATA });
|
||||||
|
|
||||||
|
expect(onPlaylistCreationFailed).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Action type: "ADD_MEDIA_TO_PLAYLIST"', () => {
|
||||||
|
const NEW_PLAYLIST_MEDIA_ID = 'NEW_PLAYLIST_MEDIA_ID';
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock the CSRF token
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Verify postRequest was called with correct parameters
|
||||||
|
expect(putRequest).toHaveBeenCalledWith(
|
||||||
|
`${sampleMediaCMSConfig.api.playlists}/${PLAYLIST_ID}`,
|
||||||
|
{ type: 'add', media_friendly_token: NEW_PLAYLIST_MEDIA_ID },
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
expect.any(Function),
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Successful', () => {
|
||||||
|
// Mock put request
|
||||||
|
(putRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _putData, _configData, _sync, successCallback, _failCallback) =>
|
||||||
|
successCallback({ data: {} })
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({
|
||||||
|
type: 'ADD_MEDIA_TO_PLAYLIST',
|
||||||
|
playlist_id: PLAYLIST_ID,
|
||||||
|
media_id: NEW_PLAYLIST_MEDIA_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onMediaPlaylistAdditionCompleted).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Failed', () => {
|
||||||
|
// Mock put request
|
||||||
|
(putRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _putData, _configData, _sync, _successCallback, failCallback) => failCallback()
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({
|
||||||
|
type: 'ADD_MEDIA_TO_PLAYLIST',
|
||||||
|
playlist_id: PLAYLIST_ID,
|
||||||
|
media_id: NEW_PLAYLIST_MEDIA_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onMediaPlaylistAdditionFailed).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Action type: "REMOVE_MEDIA_FROM_PLAYLIST"', () => {
|
||||||
|
// Mock the CSRF token
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Verify postRequest was called with correct parameters
|
||||||
|
expect(putRequest).toHaveBeenCalledWith(
|
||||||
|
`${sampleMediaCMSConfig.api.playlists}/${PLAYLIST_ID}`,
|
||||||
|
{ type: 'remove', media_friendly_token: MEDIA_ID },
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
expect.any(Function),
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Successful', () => {
|
||||||
|
// Mock put request
|
||||||
|
(putRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _putData, _configData, _sync, successCallback, _failCallback) =>
|
||||||
|
successCallback({ data: {} })
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'REMOVE_MEDIA_FROM_PLAYLIST', playlist_id: PLAYLIST_ID, media_id: MEDIA_ID });
|
||||||
|
|
||||||
|
expect(onMediaPlaylistRemovalCompleted).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Failed', () => {
|
||||||
|
// Mock put request
|
||||||
|
(putRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _putData, _configData, _sync, _successCallback, failCallback) => failCallback()
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'REMOVE_MEDIA_FROM_PLAYLIST', playlist_id: PLAYLIST_ID, media_id: MEDIA_ID });
|
||||||
|
|
||||||
|
expect(onMediaPlaylistRemovalFailed).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "APPEND_NEW_PLAYLIST"', () => {
|
||||||
|
const NEW_USER_PLAYLIST = {
|
||||||
|
add_date: 'PLAYLIST_ADD_DATE_2',
|
||||||
|
description: 'PLAYLIST_DECRIPTION_2',
|
||||||
|
media_list: ['PLAYLIST_MEDIA_ID'],
|
||||||
|
playlist_id: 'PLAYLIST_ID',
|
||||||
|
title: 'PLAYLIST_TITLE_2',
|
||||||
|
};
|
||||||
|
|
||||||
|
handler({ type: 'APPEND_NEW_PLAYLIST', playlist_data: NEW_USER_PLAYLIST });
|
||||||
|
|
||||||
|
expect(onPlaylistsLoad).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(store.get('playlists')).toStrictEqual([
|
||||||
|
{
|
||||||
|
add_date: 'PLAYLIST_ADD_DATE',
|
||||||
|
description: 'PLAYLIST_DECRIPTION',
|
||||||
|
media_list: ['PLAYLIST_MEDIA_ID'],
|
||||||
|
playlist_id: PLAYLIST_ID,
|
||||||
|
title: 'PLAYLIST_TITLE',
|
||||||
|
},
|
||||||
|
NEW_USER_PLAYLIST,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
162
frontend/tests/utils/stores/PageStore.test.ts
Normal file
162
frontend/tests/utils/stores/PageStore.test.ts
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import { BrowserCache } from '../../../src/static/js/utils/classes';
|
||||||
|
import store from '../../../src/static/js/utils/stores/PageStore';
|
||||||
|
|
||||||
|
import { sampleMediaCMSConfig } from '../../tests-constants';
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/classes/', () => ({
|
||||||
|
BrowserCache: jest.fn().mockImplementation(() => ({
|
||||||
|
get: (key: string) => (key === 'media-auto-play' ? false : undefined),
|
||||||
|
set: jest.fn(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||||
|
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/helpers', () => ({
|
||||||
|
BrowserEvents: jest.fn().mockImplementation(() => ({
|
||||||
|
doc: jest.fn(),
|
||||||
|
win: jest.fn(),
|
||||||
|
})),
|
||||||
|
exportStore: jest.fn((store) => store),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('utils/store', () => {
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PageStore', () => {
|
||||||
|
const handler = store.actions_handler.bind(store);
|
||||||
|
|
||||||
|
const onInit = jest.fn();
|
||||||
|
const onToggleAutoPlay = jest.fn();
|
||||||
|
const onAddNotification = jest.fn();
|
||||||
|
|
||||||
|
store.on('page_init', onInit);
|
||||||
|
store.on('switched_media_auto_play', onToggleAutoPlay);
|
||||||
|
store.on('added_notification', onAddNotification);
|
||||||
|
|
||||||
|
test('Validate initial values', () => {
|
||||||
|
// BrowserCache mock
|
||||||
|
expect(store.get('browser-cache').get('media-auto-play')).toBe(false);
|
||||||
|
expect(store.get('browser-cache').get('ANY')).toBe(undefined);
|
||||||
|
|
||||||
|
// Autoplay media files
|
||||||
|
expect(store.get('media-auto-play')).toBe(false);
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
expect(store.get('config-contents')).toStrictEqual(sampleMediaCMSConfig.contents);
|
||||||
|
expect(store.get('config-enabled')).toStrictEqual(sampleMediaCMSConfig.enabled);
|
||||||
|
expect(store.get('config-media-item')).toStrictEqual(sampleMediaCMSConfig.media.item);
|
||||||
|
expect(store.get('config-options')).toStrictEqual(sampleMediaCMSConfig.options);
|
||||||
|
expect(store.get('config-site')).toStrictEqual(sampleMediaCMSConfig.site);
|
||||||
|
|
||||||
|
// Playlists API path
|
||||||
|
expect(store.get('api-playlists')).toStrictEqual(sampleMediaCMSConfig.api.playlists);
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
expect(store.get('notifications')).toStrictEqual([]);
|
||||||
|
expect(store.get('notifications-size')).toBe(0);
|
||||||
|
|
||||||
|
expect(store.get('current-page')).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Trigger and validate browser events behavior', () => {
|
||||||
|
const docVisChange = jest.fn();
|
||||||
|
const winScroll = jest.fn();
|
||||||
|
const winResize = jest.fn();
|
||||||
|
|
||||||
|
store.on('document_visibility_change', docVisChange);
|
||||||
|
store.on('window_scroll', winScroll);
|
||||||
|
store.on('window_resize', winResize);
|
||||||
|
|
||||||
|
store.onDocumentVisibilityChange();
|
||||||
|
store.onWindowScroll();
|
||||||
|
store.onWindowResize();
|
||||||
|
|
||||||
|
expect(docVisChange).toHaveBeenCalled();
|
||||||
|
expect(winScroll).toHaveBeenCalled();
|
||||||
|
expect(winResize).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Trigger and validate actions behavior', () => {
|
||||||
|
test('Action type: "INIT_PAGE"', () => {
|
||||||
|
handler({ type: 'INIT_PAGE', page: 'home' });
|
||||||
|
expect(onInit).toHaveBeenCalledTimes(1);
|
||||||
|
expect(store.get('current-page')).toBe('home');
|
||||||
|
|
||||||
|
handler({ type: 'INIT_PAGE', page: 'about' });
|
||||||
|
expect(onInit).toHaveBeenCalledTimes(2);
|
||||||
|
expect(store.get('current-page')).toBe('about');
|
||||||
|
|
||||||
|
handler({ type: 'INIT_PAGE', page: 'profile' });
|
||||||
|
expect(onInit).toHaveBeenCalledTimes(3);
|
||||||
|
expect(store.get('current-page')).toBe('profile');
|
||||||
|
|
||||||
|
expect(onInit).toHaveBeenCalledWith();
|
||||||
|
|
||||||
|
expect(onToggleAutoPlay).toHaveBeenCalledTimes(0);
|
||||||
|
expect(onAddNotification).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "TOGGLE_AUTO_PLAY"', () => {
|
||||||
|
const browserCacheInstance = (BrowserCache as jest.Mock).mock.results[0].value;
|
||||||
|
const browserCacheSetSpy = browserCacheInstance.set;
|
||||||
|
|
||||||
|
const initialValue = store.get('media-auto-play');
|
||||||
|
|
||||||
|
handler({ type: 'TOGGLE_AUTO_PLAY' });
|
||||||
|
|
||||||
|
expect(onToggleAutoPlay).toHaveBeenCalledWith();
|
||||||
|
expect(onToggleAutoPlay).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(store.get('media-auto-play')).toBe(!initialValue);
|
||||||
|
expect(browserCacheSetSpy).toHaveBeenCalledWith('media-auto-play', !initialValue);
|
||||||
|
|
||||||
|
browserCacheSetSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "ADD_NOTIFICATION"', () => {
|
||||||
|
const notificationMsg1 = 'NOTIFICATION_MSG_1';
|
||||||
|
const notificationMsg2 = 'NOTIFICATION_MSG_2';
|
||||||
|
const invalidNotification = 44;
|
||||||
|
|
||||||
|
// Add notification
|
||||||
|
handler({ type: 'ADD_NOTIFICATION', notification: notificationMsg1 });
|
||||||
|
expect(onAddNotification).toHaveBeenCalledWith();
|
||||||
|
expect(onAddNotification).toHaveBeenCalledTimes(1);
|
||||||
|
expect(store.get('notifications-size')).toBe(1);
|
||||||
|
|
||||||
|
const currentNotifications = store.get('notifications');
|
||||||
|
expect(currentNotifications.length).toBe(1);
|
||||||
|
expect(typeof currentNotifications[0][0]).toBe('string');
|
||||||
|
expect(currentNotifications[0][1]).toBe(notificationMsg1);
|
||||||
|
|
||||||
|
expect(store.get('notifications-size')).toBe(0);
|
||||||
|
expect(store.get('notifications')).toStrictEqual([]);
|
||||||
|
|
||||||
|
// Add another notification
|
||||||
|
handler({ type: 'ADD_NOTIFICATION', notification: notificationMsg2 });
|
||||||
|
|
||||||
|
expect(onAddNotification).toHaveBeenCalledWith();
|
||||||
|
expect(onAddNotification).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
expect(store.get('notifications-size')).toBe(1);
|
||||||
|
expect(store.get('notifications')[0][1]).toBe(notificationMsg2);
|
||||||
|
|
||||||
|
expect(store.get('notifications-size')).toBe(0);
|
||||||
|
expect(store.get('notifications')).toStrictEqual([]);
|
||||||
|
|
||||||
|
// Add invalid notification
|
||||||
|
handler({ type: 'ADD_NOTIFICATION', notification: invalidNotification });
|
||||||
|
expect(onAddNotification).toHaveBeenCalledWith();
|
||||||
|
expect(onAddNotification).toHaveBeenCalledTimes(3);
|
||||||
|
|
||||||
|
expect(store.get('notifications-size')).toBe(0);
|
||||||
|
expect(store.get('notifications')).toStrictEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
390
frontend/tests/utils/stores/PlaylistPageStore.test.ts
Normal file
390
frontend/tests/utils/stores/PlaylistPageStore.test.ts
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
import {
|
||||||
|
publishedOnDate,
|
||||||
|
getRequest,
|
||||||
|
postRequest,
|
||||||
|
deleteRequest,
|
||||||
|
csrfToken,
|
||||||
|
} from '../../../src/static/js/utils/helpers';
|
||||||
|
import store from '../../../src/static/js/utils/stores/PlaylistPageStore';
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||||
|
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/helpers', () => ({
|
||||||
|
publishedOnDate: jest.fn(),
|
||||||
|
exportStore: jest.fn((store) => store),
|
||||||
|
getRequest: jest.fn(),
|
||||||
|
postRequest: jest.fn(),
|
||||||
|
deleteRequest: jest.fn(),
|
||||||
|
csrfToken: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('utils/store', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
(globalThis as any).window.MediaCMS = { playlistId: null };
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
delete (globalThis as any).window.MediaCMS;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PlaylistPageStore', () => {
|
||||||
|
const handler = store.actions_handler.bind(store);
|
||||||
|
|
||||||
|
const onLoadedPlaylistData = jest.fn();
|
||||||
|
const onLoadedPlaylistEerror = jest.fn();
|
||||||
|
const onLoadedMediaError = jest.fn();
|
||||||
|
const onPlaylistUpdateCompleted = jest.fn();
|
||||||
|
const onPlaylistUpdateFailed = jest.fn();
|
||||||
|
const onPlaylistRemovalCompleted = jest.fn();
|
||||||
|
const onPlaylistRemovalFailed = jest.fn();
|
||||||
|
const onSavedUpdated = jest.fn();
|
||||||
|
const onReorderedMediaInPlaylist = jest.fn();
|
||||||
|
const onRemovedMediaFromPlaylist = jest.fn();
|
||||||
|
|
||||||
|
store.on('loaded_playlist_data', onLoadedPlaylistData);
|
||||||
|
store.on('loaded_playlist_error', onLoadedPlaylistEerror);
|
||||||
|
store.on('loaded_media_error', onLoadedMediaError); // @todo: It doesn't get called
|
||||||
|
store.on('playlist_update_completed', onPlaylistUpdateCompleted);
|
||||||
|
store.on('playlist_update_failed', onPlaylistUpdateFailed);
|
||||||
|
store.on('playlist_removal_completed', onPlaylistRemovalCompleted);
|
||||||
|
store.on('playlist_removal_failed', onPlaylistRemovalFailed);
|
||||||
|
store.on('saved-updated', onSavedUpdated);
|
||||||
|
store.on('reordered_media_in_playlist', onReorderedMediaInPlaylist);
|
||||||
|
store.on('removed_media_from_playlist', onRemovedMediaFromPlaylist);
|
||||||
|
|
||||||
|
test('Validate initial values', () => {
|
||||||
|
expect(store.get('INVALID_TYPE')).toBe(null);
|
||||||
|
expect(store.get('playlistId')).toBe(null);
|
||||||
|
expect(store.get('logged-in-user-playlist')).toBe(false);
|
||||||
|
expect(store.get('playlist-media')).toStrictEqual([]);
|
||||||
|
expect(store.get('visibility')).toBe('public');
|
||||||
|
expect(store.get('visibility-icon')).toBe(null);
|
||||||
|
// // expect(store.get('total-items')).toBe(0); // @todo: It throws error
|
||||||
|
expect(store.get('views-count')).toBe('N/A');
|
||||||
|
expect(store.get('title')).toBe(null);
|
||||||
|
expect(store.get('edit-link')).toBe('#');
|
||||||
|
expect(store.get('thumb')).toBe(null);
|
||||||
|
expect(store.get('description')).toBe(null);
|
||||||
|
expect(store.get('author-username')).toBe(null);
|
||||||
|
expect(store.get('author-name')).toBe(null);
|
||||||
|
expect(store.get('author-link')).toBe(null);
|
||||||
|
expect(store.get('author-thumb')).toBe(null);
|
||||||
|
expect(store.get('saved-playlist')).toBe(false);
|
||||||
|
expect(store.get('date-label')).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Trigger and validate actions behavior', () => {
|
||||||
|
test('Action type: "LOAD_PLAYLIST_DATA" - failed', () => {
|
||||||
|
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
const loadDataSpy = jest.spyOn(store, 'loadData');
|
||||||
|
|
||||||
|
handler({ type: 'LOAD_PLAYLIST_DATA' });
|
||||||
|
|
||||||
|
expect(loadDataSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(loadDataSpy).toHaveReturnedWith(false);
|
||||||
|
|
||||||
|
expect(warnSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(warnSpy).toHaveBeenCalledWith('Invalid playlist id:', '');
|
||||||
|
|
||||||
|
expect(store.get('playlistId')).toBe(null);
|
||||||
|
|
||||||
|
loadDataSpy.mockRestore();
|
||||||
|
warnSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "LOAD_PLAYLIST_DATA" - completed successful', () => {
|
||||||
|
const playlistId = 'PLAYLIST_ID_1';
|
||||||
|
window.history.pushState({}, '', `/playlists/${playlistId}`);
|
||||||
|
|
||||||
|
// Mock get request
|
||||||
|
const mockGetRequestResponse = {
|
||||||
|
data: {
|
||||||
|
add_date: Date.now(),
|
||||||
|
description: 'DESCRIPTION',
|
||||||
|
playlist_media: [],
|
||||||
|
title: 'TITLE',
|
||||||
|
user: 'USER',
|
||||||
|
user_thumbnail_url: 'USER_THUMB_URL',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(getRequest as jest.Mock).mockImplementation((_url, _cache, successCallback, _failCallback) =>
|
||||||
|
successCallback(mockGetRequestResponse)
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadDataSpy = jest.spyOn(store, 'loadData');
|
||||||
|
const dataResponseSpy = jest.spyOn(store, 'dataResponse');
|
||||||
|
|
||||||
|
handler({ type: 'LOAD_PLAYLIST_DATA' });
|
||||||
|
|
||||||
|
expect(store.get('playlistId')).toBe(playlistId);
|
||||||
|
expect(store.get('author-name')).toBe(mockGetRequestResponse.data.user);
|
||||||
|
expect(store.get('author-link')).toBe(`/user/${mockGetRequestResponse.data.user}`);
|
||||||
|
expect(store.get('author-thumb')).toBe(`/${mockGetRequestResponse.data.user_thumbnail_url}`);
|
||||||
|
|
||||||
|
expect(store.get('date-label')).toBe('Created on undefined');
|
||||||
|
expect(publishedOnDate).toHaveBeenCalledWith(new Date(mockGetRequestResponse.data.add_date), 3);
|
||||||
|
|
||||||
|
expect(loadDataSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(loadDataSpy).toHaveReturnedWith(undefined);
|
||||||
|
|
||||||
|
expect(dataResponseSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dataResponseSpy).toHaveBeenCalledWith(mockGetRequestResponse);
|
||||||
|
|
||||||
|
// Verify getRequest was called with correct parameters
|
||||||
|
expect(getRequest).toHaveBeenCalledWith(
|
||||||
|
store.playlistAPIUrl,
|
||||||
|
false,
|
||||||
|
store.dataResponse,
|
||||||
|
store.dataErrorResponse
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onLoadedPlaylistData).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onLoadedPlaylistData).toHaveBeenCalledWith();
|
||||||
|
|
||||||
|
loadDataSpy.mockRestore();
|
||||||
|
dataResponseSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "LOAD_PLAYLIST_DATA" - completed with error', () => {
|
||||||
|
const playlistId = 'PLAYLIST_ID_2';
|
||||||
|
window.history.pushState({}, '', `/playlists/${playlistId}`);
|
||||||
|
|
||||||
|
// Mock get request
|
||||||
|
const mockGetRequestResponse = { type: 'private' };
|
||||||
|
(getRequest as jest.Mock).mockImplementation((_url, _cache, _successCallback, failCallback) =>
|
||||||
|
failCallback(mockGetRequestResponse)
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadDataSpy = jest.spyOn(store, 'loadData');
|
||||||
|
const dataErrorResponseSpy = jest.spyOn(store, 'dataErrorResponse');
|
||||||
|
|
||||||
|
handler({ type: 'LOAD_PLAYLIST_DATA' });
|
||||||
|
|
||||||
|
expect(store.get('playlistId')).toBe(playlistId);
|
||||||
|
|
||||||
|
expect(loadDataSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(loadDataSpy).toHaveReturnedWith(undefined);
|
||||||
|
|
||||||
|
expect(dataErrorResponseSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dataErrorResponseSpy).toHaveBeenCalledWith(mockGetRequestResponse);
|
||||||
|
|
||||||
|
// Verify getRequest was called with correct parameters
|
||||||
|
expect(getRequest).toHaveBeenCalledWith(
|
||||||
|
store.playlistAPIUrl,
|
||||||
|
false,
|
||||||
|
store.dataResponse,
|
||||||
|
store.dataErrorResponse
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onLoadedPlaylistEerror).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onLoadedPlaylistEerror).toHaveBeenCalledWith();
|
||||||
|
|
||||||
|
loadDataSpy.mockRestore();
|
||||||
|
dataErrorResponseSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "TOGGLE_SAVE"', () => {
|
||||||
|
const initialValue = store.get('saved-playlist');
|
||||||
|
|
||||||
|
handler({ type: 'TOGGLE_SAVE' });
|
||||||
|
|
||||||
|
expect(onSavedUpdated).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onSavedUpdated).toHaveBeenCalledWith();
|
||||||
|
|
||||||
|
expect(store.get('saved-playlist')).toBe(!initialValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "UPDATE_PLAYLIST" - failed', () => {
|
||||||
|
// Mock (updated) playlist data
|
||||||
|
const mockPlaylistData = { title: 'PLAYLIST_TITLE', description: 'PLAYLIST_DESCRIPTION' };
|
||||||
|
|
||||||
|
// Mock the CSRF token
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
// Mock post request
|
||||||
|
(postRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _postData, _configData, _sync, _successCallback, failCallback) => failCallback()
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialStoreData = {
|
||||||
|
title: store.get('title'),
|
||||||
|
description: store.get('description'),
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(store.get('title')).toBe(initialStoreData.title);
|
||||||
|
expect(store.get('description')).toBe(initialStoreData.description);
|
||||||
|
|
||||||
|
handler({ type: 'UPDATE_PLAYLIST', playlist_data: mockPlaylistData });
|
||||||
|
|
||||||
|
expect(store.get('title')).toBe(initialStoreData.title);
|
||||||
|
expect(store.get('description')).toBe(initialStoreData.description);
|
||||||
|
|
||||||
|
// Verify postRequest was called with correct parameters
|
||||||
|
expect(postRequest).toHaveBeenCalledWith(
|
||||||
|
store.playlistAPIUrl,
|
||||||
|
mockPlaylistData,
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
store.onPlaylistUpdateCompleted,
|
||||||
|
store.onPlaylistUpdateFailed
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onPlaylistUpdateFailed).toHaveBeenCalledWith();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "UPDATE_PLAYLIST" - successful', () => {
|
||||||
|
// Mock (updated) playlist data
|
||||||
|
const mockPlaylistData = { title: 'PLAYLIST_TITLE', description: 'PLAYLIST_DESCRIPTION' };
|
||||||
|
|
||||||
|
// Mock the CSRF token
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
// Mock post request
|
||||||
|
(postRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _postData, _configData, _sync, successCallback, _failCallback) =>
|
||||||
|
successCallback({ data: mockPlaylistData })
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialStoreData = {
|
||||||
|
title: store.get('title'),
|
||||||
|
description: store.get('description'),
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(store.get('title')).toBe(initialStoreData.title);
|
||||||
|
expect(store.get('description')).toBe(initialStoreData.description);
|
||||||
|
|
||||||
|
handler({ type: 'UPDATE_PLAYLIST', playlist_data: mockPlaylistData });
|
||||||
|
|
||||||
|
expect(store.get('title')).toBe(mockPlaylistData.title);
|
||||||
|
expect(store.get('description')).toBe(mockPlaylistData.description);
|
||||||
|
|
||||||
|
// Verify postRequest was called with correct parameters
|
||||||
|
expect(postRequest).toHaveBeenCalledWith(
|
||||||
|
store.playlistAPIUrl,
|
||||||
|
mockPlaylistData,
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
store.onPlaylistUpdateCompleted,
|
||||||
|
store.onPlaylistUpdateFailed
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onPlaylistUpdateCompleted).toHaveBeenCalledWith(mockPlaylistData);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "REMOVE_PLAYLIST" - failed', () => {
|
||||||
|
// Mock the CSRF token
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
// Mock delete request
|
||||||
|
(deleteRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _config, _sync, _successCallback, failCallback) => failCallback()
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'REMOVE_PLAYLIST' });
|
||||||
|
|
||||||
|
// Verify deleteRequest was called with correct parameters
|
||||||
|
expect(deleteRequest).toHaveBeenCalledWith(
|
||||||
|
store.playlistAPIUrl,
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
store.onPlaylistRemovalCompleted,
|
||||||
|
store.onPlaylistRemovalFailed
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onPlaylistRemovalFailed).toHaveBeenCalledWith();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "REMOVE_PLAYLIST" - completed successful', () => {
|
||||||
|
// Mock the CSRF token
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
// Mock delete request
|
||||||
|
const deleteRequestResponse = { status: 204 };
|
||||||
|
(deleteRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _config, _sync, successCallback, _failCallback) => successCallback(deleteRequestResponse)
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'REMOVE_PLAYLIST' });
|
||||||
|
|
||||||
|
// Verify deleteRequest was called with correct parameters
|
||||||
|
expect(deleteRequest).toHaveBeenCalledWith(
|
||||||
|
store.playlistAPIUrl,
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
store.onPlaylistRemovalCompleted,
|
||||||
|
store.onPlaylistRemovalFailed
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onPlaylistRemovalCompleted).toHaveBeenCalledWith(deleteRequestResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "REMOVE_PLAYLIST" - completed with invalid status code', () => {
|
||||||
|
// Mock the CSRF token
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
// Mock delete request
|
||||||
|
const deleteRequestResponse = { status: 403 };
|
||||||
|
(deleteRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _config, _sync, successCallback, _failCallback) => successCallback(deleteRequestResponse)
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'REMOVE_PLAYLIST' });
|
||||||
|
|
||||||
|
// Verify deleteRequest was called with correct parameters
|
||||||
|
expect(deleteRequest).toHaveBeenCalledWith(
|
||||||
|
store.playlistAPIUrl,
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
store.onPlaylistRemovalCompleted,
|
||||||
|
store.onPlaylistRemovalFailed
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onPlaylistRemovalFailed).toHaveBeenCalledWith();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "PLAYLIST_MEDIA_REORDERED"', () => {
|
||||||
|
// Mock playlist media data
|
||||||
|
const mockPlaylistMedia = [
|
||||||
|
{ thumbnail_url: 'THUMB_URL_1', url: '?id=MEDIA_ID_1' },
|
||||||
|
{ thumbnail_url: 'THUMB_URL_2', url: '?id=MEDIA_ID_2' },
|
||||||
|
];
|
||||||
|
|
||||||
|
handler({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: mockPlaylistMedia });
|
||||||
|
|
||||||
|
expect(onReorderedMediaInPlaylist).toHaveBeenCalledWith();
|
||||||
|
|
||||||
|
expect(store.get('playlist-media')).toStrictEqual(mockPlaylistMedia);
|
||||||
|
expect(store.get('thumb')).toBe(mockPlaylistMedia[0].thumbnail_url);
|
||||||
|
expect(store.get('total-items')).toBe(mockPlaylistMedia.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "MEDIA_REMOVED_FROM_PLAYLIST"', () => {
|
||||||
|
// Mock playlist media data
|
||||||
|
const mockPlaylistMedia = [
|
||||||
|
{ thumbnail_url: 'THUMB_URL_1', url: '?id=MEDIA_ID_1' },
|
||||||
|
{ thumbnail_url: 'THUMB_URL_2', url: '?id=MEDIA_ID_2' },
|
||||||
|
];
|
||||||
|
|
||||||
|
handler({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: mockPlaylistMedia });
|
||||||
|
|
||||||
|
handler({ type: 'MEDIA_REMOVED_FROM_PLAYLIST', media_id: 'MEDIA_ID_2' });
|
||||||
|
|
||||||
|
expect(store.get('playlist-media')).toStrictEqual([mockPlaylistMedia[0]]);
|
||||||
|
expect(store.get('thumb')).toBe(mockPlaylistMedia[0].thumbnail_url);
|
||||||
|
expect(store.get('total-items')).toBe(mockPlaylistMedia.length - 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
83
frontend/tests/utils/stores/PlaylistViewStore.test.ts
Normal file
83
frontend/tests/utils/stores/PlaylistViewStore.test.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { BrowserCache } from '../../../src/static/js/utils/classes/';
|
||||||
|
import store from '../../../src/static/js/utils/stores/PlaylistViewStore';
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/classes/', () => ({
|
||||||
|
BrowserCache: jest.fn().mockImplementation(() => ({
|
||||||
|
get: jest.fn(),
|
||||||
|
set: jest.fn(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||||
|
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/helpers', () => ({
|
||||||
|
BrowserEvents: jest.fn().mockImplementation(() => ({
|
||||||
|
doc: jest.fn(),
|
||||||
|
win: jest.fn(),
|
||||||
|
})),
|
||||||
|
exportStore: jest.fn((store) => store),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('utils/store', () => {
|
||||||
|
describe('PlaylistViewStore', () => {
|
||||||
|
const browserCacheInstance = (BrowserCache as jest.Mock).mock.results[0].value;
|
||||||
|
const browserCacheSetSpy = browserCacheInstance.set;
|
||||||
|
|
||||||
|
const handler = store.actions_handler.bind(store);
|
||||||
|
|
||||||
|
const onLoopRepeatUpdated = jest.fn();
|
||||||
|
const onShuffleUpdated = jest.fn();
|
||||||
|
const onSavedUpdated = jest.fn();
|
||||||
|
|
||||||
|
store.on('loop-repeat-updated', onLoopRepeatUpdated);
|
||||||
|
store.on('shuffle-updated', onShuffleUpdated);
|
||||||
|
store.on('saved-updated', onSavedUpdated);
|
||||||
|
|
||||||
|
test('Validate initial values', () => {
|
||||||
|
expect(store.get('INVALID_TYPE')).toBe(null);
|
||||||
|
expect(store.get('logged-in-user-playlist')).toBe(false);
|
||||||
|
expect(store.get('enabled-loop')).toBe(undefined);
|
||||||
|
expect(store.get('enabled-shuffle')).toBe(undefined);
|
||||||
|
expect(store.get('saved-playlist')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Trigger and validate actions behavior', () => {
|
||||||
|
// @todo: Revisit the behavior of this action
|
||||||
|
test('Action type: "TOGGLE_LOOP"', () => {
|
||||||
|
handler({ type: 'TOGGLE_LOOP' });
|
||||||
|
|
||||||
|
expect(onLoopRepeatUpdated).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onLoopRepeatUpdated).toHaveBeenCalledWith();
|
||||||
|
|
||||||
|
expect(store.get('enabled-loop')).toBe(undefined);
|
||||||
|
|
||||||
|
expect(browserCacheSetSpy).toHaveBeenCalledWith('loopPlaylist[null]', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// @todo: Revisit the behavior of this action
|
||||||
|
test('Action type: "TOGGLE_SHUFFLE"', () => {
|
||||||
|
handler({ type: 'TOGGLE_SHUFFLE' });
|
||||||
|
|
||||||
|
expect(onShuffleUpdated).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onShuffleUpdated).toHaveBeenCalledWith();
|
||||||
|
|
||||||
|
expect(store.get('enabled-shuffle')).toBe(undefined);
|
||||||
|
|
||||||
|
expect(browserCacheSetSpy).toHaveBeenCalledWith('shufflePlaylist[null]', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "TOGGLE_SAVE"', () => {
|
||||||
|
const initialValue = store.get('saved-playlist');
|
||||||
|
|
||||||
|
handler({ type: 'TOGGLE_SAVE' });
|
||||||
|
|
||||||
|
expect(onSavedUpdated).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onSavedUpdated).toHaveBeenCalledWith();
|
||||||
|
|
||||||
|
expect(store.get('saved-playlist')).toBe(!initialValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
168
frontend/tests/utils/stores/ProfilePageStore.test.ts
Normal file
168
frontend/tests/utils/stores/ProfilePageStore.test.ts
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import { getRequest, deleteRequest, csrfToken } from '../../../src/static/js/utils/helpers';
|
||||||
|
import store from '../../../src/static/js/utils/stores/ProfilePageStore';
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||||
|
config: jest.fn(() => ({
|
||||||
|
...jest.requireActual('../../tests-constants').sampleMediaCMSConfig,
|
||||||
|
api: { ...jest.requireActual('../../tests-constants').sampleMediaCMSConfig.api, users: '' },
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/helpers', () => ({
|
||||||
|
getRequest: jest.fn(),
|
||||||
|
deleteRequest: jest.fn(),
|
||||||
|
csrfToken: jest.fn(),
|
||||||
|
exportStore: jest.fn((store) => store),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('utils/store', () => {
|
||||||
|
const mockAuthorData = { username: 'testuser', name: 'Test User' };
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
(globalThis as any).window.MediaCMS = { profileId: mockAuthorData.username };
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
delete (globalThis as any).window.MediaCMS;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ProfilePageStore', () => {
|
||||||
|
const handler = store.actions_handler.bind(store);
|
||||||
|
|
||||||
|
const onProfileDelete = jest.fn();
|
||||||
|
const onProfileDeleteFail = jest.fn();
|
||||||
|
const onLoadAuthorData = jest.fn();
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
store.on('profile_delete', onProfileDelete);
|
||||||
|
store.on('profile_delete_fail', onProfileDeleteFail);
|
||||||
|
store.on('load-author-data', onLoadAuthorData);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset store state
|
||||||
|
store.authorData = null;
|
||||||
|
store.removingProfile = false;
|
||||||
|
store.authorQuery = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Trigger and validate actions behavior', () => {
|
||||||
|
test('Action type: "REMOVE_PROFILE" - successful deletion', async () => {
|
||||||
|
// Set up author data
|
||||||
|
store.authorData = mockAuthorData;
|
||||||
|
|
||||||
|
// Mock the CSRF token
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
// Mock delete request
|
||||||
|
(deleteRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _config, _sync, successCallback, _failCallback) => successCallback({ status: 204 })
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'REMOVE_PROFILE' });
|
||||||
|
|
||||||
|
// Verify deleteRequest was called with correct parameters
|
||||||
|
expect(deleteRequest).toHaveBeenCalledWith(
|
||||||
|
'/testuser', // API URL constructed from config + username
|
||||||
|
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
|
||||||
|
false,
|
||||||
|
store.removeProfileResponse,
|
||||||
|
store.removeProfileFail
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify event was emitted
|
||||||
|
expect(onProfileDelete).toHaveBeenCalledWith(mockAuthorData.username);
|
||||||
|
expect(onProfileDelete).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "REMOVE_PROFILE" - deletion failure', async () => {
|
||||||
|
// Set up author data
|
||||||
|
store.authorData = mockAuthorData;
|
||||||
|
|
||||||
|
// Mock the CSRF token
|
||||||
|
const mockCSRFtoken = 'test-csrf-token';
|
||||||
|
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
|
||||||
|
|
||||||
|
// Mock delete request
|
||||||
|
(deleteRequest as jest.Mock).mockImplementation(
|
||||||
|
(_url, _config, _sync, _successCallback, failCallback) => failCallback.call(store)
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'REMOVE_PROFILE' });
|
||||||
|
|
||||||
|
// Wait for the setTimeout in removeProfileFail
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||||
|
|
||||||
|
// Verify event was emitted
|
||||||
|
expect(onProfileDeleteFail).toHaveBeenCalledWith(mockAuthorData.username);
|
||||||
|
expect(onProfileDeleteFail).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "REMOVE_PROFILE" - prevents duplicate calls while removing', () => {
|
||||||
|
// Set up author data
|
||||||
|
store.authorData = mockAuthorData;
|
||||||
|
|
||||||
|
handler({ type: 'REMOVE_PROFILE' });
|
||||||
|
expect(deleteRequest).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
store.removingProfile = true;
|
||||||
|
handler({ type: 'REMOVE_PROFILE' });
|
||||||
|
expect(deleteRequest).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
store.removingProfile = false;
|
||||||
|
handler({ type: 'REMOVE_PROFILE' });
|
||||||
|
expect(deleteRequest).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "LOAD_AUTHOR_DATA"', async () => {
|
||||||
|
(getRequest as jest.Mock).mockImplementation((_url, _cache, successCallback, _failCallback) =>
|
||||||
|
successCallback({ data: mockAuthorData })
|
||||||
|
);
|
||||||
|
|
||||||
|
handler({ type: 'LOAD_AUTHOR_DATA' });
|
||||||
|
|
||||||
|
// Verify getRequest was called with correct parameters
|
||||||
|
expect(getRequest).toHaveBeenCalledWith('/testuser', false, store.onDataLoad, store.onDataLoadFail);
|
||||||
|
|
||||||
|
// Verify event was emitted
|
||||||
|
expect(onLoadAuthorData).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Verify author data was processed correctly
|
||||||
|
expect(store.get('author-data')).toStrictEqual(mockAuthorData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Getter methods', () => {
|
||||||
|
test('Validate initial values', () => {
|
||||||
|
expect(store.get('INVALID_TYPE')).toBe(undefined);
|
||||||
|
expect(store.get('author-data')).toBe(null);
|
||||||
|
expect(store.get('author-query')).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('get("author-data") returns authorData', () => {
|
||||||
|
store.authorData = mockAuthorData;
|
||||||
|
expect(store.get('author-data')).toBe(mockAuthorData);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('get("author-query") - without "aq" parameter in URL', () => {
|
||||||
|
window.history.pushState({}, '', '/path');
|
||||||
|
expect(store.get('author-query')).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('get("author-query") - with "aq" parameter in URL', () => {
|
||||||
|
window.history.pushState({}, '', '/path?aq=AUTHOR_QUERY');
|
||||||
|
expect(store.get('author-query')).toBe('AUTHOR_QUERY');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('get("author-query") - empty search string', () => {
|
||||||
|
window.history.pushState({}, '', '/path?aq');
|
||||||
|
expect(store.get('author-query')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
64
frontend/tests/utils/stores/SearchFieldStore.test.ts
Normal file
64
frontend/tests/utils/stores/SearchFieldStore.test.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
const urlParams = { q: 'search_query', c: 'category_1', t: 'tag_1' };
|
||||||
|
window.history.pushState({}, '', `/?q=${urlParams.q}&c=${urlParams.c}&t=${urlParams.t}`);
|
||||||
|
|
||||||
|
import store from '../../../src/static/js/utils/stores/SearchFieldStore';
|
||||||
|
import { getRequest } from '../../../src/static/js/utils/helpers';
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||||
|
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/helpers', () => ({
|
||||||
|
exportStore: jest.fn((store) => store),
|
||||||
|
getRequest: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('utils/store', () => {
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SearchFieldStore', () => {
|
||||||
|
const handler = store.actions_handler.bind(store);
|
||||||
|
|
||||||
|
const onLoadPredictions = jest.fn();
|
||||||
|
|
||||||
|
store.on('load_predictions', onLoadPredictions);
|
||||||
|
|
||||||
|
test('Validate initial values based on URL params', async () => {
|
||||||
|
expect(store.get('INVALID_TYPE')).toBe(null);
|
||||||
|
expect(store.get('search-query')).toBe(urlParams.q);
|
||||||
|
expect(store.get('search-categories')).toBe(urlParams.c);
|
||||||
|
expect(store.get('search-tags')).toBe(urlParams.t);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "Action type: "TOGGLE_VIEWER_MODE"', async () => {
|
||||||
|
const predictionsQuery_1 = 'predictions_query_1';
|
||||||
|
const predictionsQuery_2 = 'predictions_query_2';
|
||||||
|
|
||||||
|
const response_1 = { data: [{ title: 'Prediction 1' }, { title: 'Prediction 2' }] };
|
||||||
|
const response_2 = { data: [{ title: 'Prediction 3' }, { title: 'Prediction 4' }] };
|
||||||
|
|
||||||
|
(getRequest as jest.Mock)
|
||||||
|
.mockImplementationOnce((_url, _cache, successCallback, _failCallback) => successCallback(response_1))
|
||||||
|
.mockImplementationOnce((_url, _cache, successCallback, _failCallback) => successCallback(response_2));
|
||||||
|
|
||||||
|
handler({ type: 'REQUEST_PREDICTIONS', query: predictionsQuery_1 });
|
||||||
|
handler({ type: 'REQUEST_PREDICTIONS', query: predictionsQuery_2 });
|
||||||
|
|
||||||
|
expect(onLoadPredictions).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
expect(onLoadPredictions).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
predictionsQuery_1,
|
||||||
|
response_1.data.map(({ title }) => title)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(onLoadPredictions).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
predictionsQuery_2,
|
||||||
|
response_2.data.map(({ title }) => title)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
147
frontend/tests/utils/stores/VideoViewerStore.test.ts
Normal file
147
frontend/tests/utils/stores/VideoViewerStore.test.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { BrowserCache } from '../../../src/static/js/utils/classes/';
|
||||||
|
import store from '../../../src/static/js/utils/stores/VideoViewerStore';
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/classes/', () => ({
|
||||||
|
BrowserCache: jest.fn().mockImplementation(() => ({
|
||||||
|
get: (key: string) => {
|
||||||
|
let result: any = undefined;
|
||||||
|
switch (key) {
|
||||||
|
case 'player-volume':
|
||||||
|
result = 0.6;
|
||||||
|
break;
|
||||||
|
case 'player-sound-muted':
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
case 'in-theater-mode':
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
case 'video-quality':
|
||||||
|
result = 720;
|
||||||
|
break;
|
||||||
|
case 'video-playback-speed':
|
||||||
|
result = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
set: jest.fn(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/settings/config', () => ({
|
||||||
|
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../src/static/js/utils/helpers', () => ({
|
||||||
|
BrowserEvents: jest.fn().mockImplementation(() => ({
|
||||||
|
doc: jest.fn(),
|
||||||
|
win: jest.fn(),
|
||||||
|
})),
|
||||||
|
exportStore: jest.fn((store) => store),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('utils/store', () => {
|
||||||
|
describe('VideoViewerStore', () => {
|
||||||
|
const browserCacheInstance = (BrowserCache as jest.Mock).mock.results[0].value;
|
||||||
|
const browserCacheSetSpy = browserCacheInstance.set;
|
||||||
|
|
||||||
|
const handler = store.actions_handler.bind(store);
|
||||||
|
|
||||||
|
const onChangedViewerMode = jest.fn();
|
||||||
|
const onChangedPlayerVolume = jest.fn();
|
||||||
|
const onChangedPlayerSoundMuted = jest.fn();
|
||||||
|
const onChangedVideoQuality = jest.fn();
|
||||||
|
const onChangedVideoPlaybackSpeed = jest.fn();
|
||||||
|
|
||||||
|
store.on('changed_viewer_mode', onChangedViewerMode);
|
||||||
|
store.on('changed_player_volume', onChangedPlayerVolume);
|
||||||
|
store.on('changed_player_sound_muted', onChangedPlayerSoundMuted);
|
||||||
|
store.on('changed_video_quality', onChangedVideoQuality);
|
||||||
|
store.on('changed_video_playback_speed', onChangedVideoPlaybackSpeed);
|
||||||
|
|
||||||
|
test('Validate initial values', () => {
|
||||||
|
expect(store.get('player-volume')).toBe(0.6);
|
||||||
|
expect(store.get('player-sound-muted')).toBe(false);
|
||||||
|
expect(store.get('in-theater-mode')).toBe(true);
|
||||||
|
expect(store.get('video-data')).toBe(undefined); // @todo: Revisit this behavior
|
||||||
|
expect(store.get('video-quality')).toBe(720);
|
||||||
|
expect(store.get('video-playback-speed')).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Trigger and validate actions behavior', () => {
|
||||||
|
test('Action type: "TOGGLE_VIEWER_MODE"', () => {
|
||||||
|
const initialValue = store.get('in-theater-mode');
|
||||||
|
|
||||||
|
handler({ type: 'TOGGLE_VIEWER_MODE' });
|
||||||
|
|
||||||
|
expect(onChangedViewerMode).toHaveBeenCalledWith();
|
||||||
|
expect(onChangedViewerMode).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(store.get('in-theater-mode')).toBe(!initialValue);
|
||||||
|
expect(browserCacheSetSpy).toHaveBeenCalledWith('in-theater-mode', !initialValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "SET_VIEWER_MODE"', () => {
|
||||||
|
const initialValue = store.get('in-theater-mode');
|
||||||
|
const newValue = !initialValue;
|
||||||
|
|
||||||
|
handler({ type: 'SET_VIEWER_MODE', inTheaterMode: newValue });
|
||||||
|
|
||||||
|
expect(onChangedViewerMode).toHaveBeenCalledWith();
|
||||||
|
expect(onChangedViewerMode).toHaveBeenCalledTimes(2); // The first time called by 'TOGGLE_VIEWER_MODE' action.
|
||||||
|
|
||||||
|
expect(store.get('in-theater-mode')).toBe(newValue);
|
||||||
|
expect(browserCacheSetSpy).toHaveBeenCalledWith('in-theater-mode', newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "SET_PLAYER_VOLUME"', () => {
|
||||||
|
const newValue = 0.3;
|
||||||
|
|
||||||
|
handler({ type: 'SET_PLAYER_VOLUME', playerVolume: newValue });
|
||||||
|
|
||||||
|
expect(onChangedPlayerVolume).toHaveBeenCalledWith();
|
||||||
|
expect(onChangedPlayerVolume).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(store.get('player-volume')).toBe(newValue);
|
||||||
|
expect(browserCacheSetSpy).toHaveBeenCalledWith('player-volume', newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "SET_PLAYER_SOUND_MUTED"', () => {
|
||||||
|
const initialValue = store.get('player-sound-muted');
|
||||||
|
const newValue = !initialValue;
|
||||||
|
|
||||||
|
handler({ type: 'SET_PLAYER_SOUND_MUTED', playerSoundMuted: newValue });
|
||||||
|
|
||||||
|
expect(onChangedPlayerSoundMuted).toHaveBeenCalledWith();
|
||||||
|
expect(onChangedPlayerSoundMuted).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(store.get('player-sound-muted')).toBe(newValue);
|
||||||
|
expect(browserCacheSetSpy).toHaveBeenCalledWith('player-sound-muted', newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "SET_VIDEO_QUALITY"', () => {
|
||||||
|
const newValue = 1080;
|
||||||
|
|
||||||
|
handler({ type: 'SET_VIDEO_QUALITY', quality: newValue });
|
||||||
|
|
||||||
|
expect(onChangedVideoQuality).toHaveBeenCalledWith();
|
||||||
|
expect(onChangedVideoQuality).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(store.get('video-quality')).toBe(newValue);
|
||||||
|
expect(browserCacheSetSpy).toHaveBeenCalledWith('video-quality', newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Action type: "SET_VIDEO_PLAYBACK_SPEED"', () => {
|
||||||
|
const newValue = 1.5;
|
||||||
|
|
||||||
|
handler({ type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: newValue });
|
||||||
|
|
||||||
|
expect(onChangedVideoPlaybackSpeed).toHaveBeenCalledWith();
|
||||||
|
expect(onChangedVideoPlaybackSpeed).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(store.get('video-playback-speed')).toBe(newValue);
|
||||||
|
expect(browserCacheSetSpy).toHaveBeenCalledWith('video-playback-speed', newValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -7,6 +7,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.30.tgz"
|
resolved "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.30.tgz"
|
||||||
integrity sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==
|
integrity sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==
|
||||||
|
|
||||||
|
"@adobe/css-tools@^4.0.1":
|
||||||
|
version "4.4.4"
|
||||||
|
resolved "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz"
|
||||||
|
integrity sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==
|
||||||
|
|
||||||
"@asamuzakjp/css-color@^3.2.0":
|
"@asamuzakjp/css-color@^3.2.0":
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz"
|
resolved "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz"
|
||||||
@ -45,7 +50,7 @@
|
|||||||
resolved "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz"
|
resolved "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz"
|
||||||
integrity sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==
|
integrity sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==
|
||||||
|
|
||||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1":
|
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1":
|
||||||
version "7.27.1"
|
version "7.27.1"
|
||||||
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz"
|
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz"
|
||||||
integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
|
integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
|
||||||
@ -1002,6 +1007,11 @@
|
|||||||
"@babel/plugin-transform-modules-commonjs" "^7.27.1"
|
"@babel/plugin-transform-modules-commonjs" "^7.27.1"
|
||||||
"@babel/plugin-transform-typescript" "^7.28.5"
|
"@babel/plugin-transform-typescript" "^7.28.5"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.12.5":
|
||||||
|
version "7.28.6"
|
||||||
|
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz"
|
||||||
|
integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==
|
||||||
|
|
||||||
"@babel/runtime@^7.3.4", "@babel/runtime@7.4.5":
|
"@babel/runtime@^7.3.4", "@babel/runtime@7.4.5":
|
||||||
version "7.4.5"
|
version "7.4.5"
|
||||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz"
|
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz"
|
||||||
@ -1009,6 +1019,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.2"
|
regenerator-runtime "^0.13.2"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.9.2":
|
||||||
|
version "7.28.6"
|
||||||
|
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz"
|
||||||
|
integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==
|
||||||
|
|
||||||
"@babel/template@^7.27.1", "@babel/template@^7.27.2", "@babel/template@^7.3.3":
|
"@babel/template@^7.27.1", "@babel/template@^7.27.2", "@babel/template@^7.3.3":
|
||||||
version "7.27.2"
|
version "7.27.2"
|
||||||
resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz"
|
resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz"
|
||||||
@ -1807,11 +1822,54 @@
|
|||||||
"@svgr/plugin-svgo" "^5.5.0"
|
"@svgr/plugin-svgo" "^5.5.0"
|
||||||
loader-utils "^2.0.0"
|
loader-utils "^2.0.0"
|
||||||
|
|
||||||
|
"@testing-library/dom@^8.0.0", "@testing-library/dom@^8.20.1":
|
||||||
|
version "8.20.1"
|
||||||
|
resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz"
|
||||||
|
integrity sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==
|
||||||
|
dependencies:
|
||||||
|
"@babel/code-frame" "^7.10.4"
|
||||||
|
"@babel/runtime" "^7.12.5"
|
||||||
|
"@types/aria-query" "^5.0.1"
|
||||||
|
aria-query "5.1.3"
|
||||||
|
chalk "^4.1.0"
|
||||||
|
dom-accessibility-api "^0.5.9"
|
||||||
|
lz-string "^1.5.0"
|
||||||
|
pretty-format "^27.0.2"
|
||||||
|
|
||||||
|
"@testing-library/jest-dom@^5.17.0":
|
||||||
|
version "5.17.0"
|
||||||
|
resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz"
|
||||||
|
integrity sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==
|
||||||
|
dependencies:
|
||||||
|
"@adobe/css-tools" "^4.0.1"
|
||||||
|
"@babel/runtime" "^7.9.2"
|
||||||
|
"@types/testing-library__jest-dom" "^5.9.1"
|
||||||
|
aria-query "^5.0.0"
|
||||||
|
chalk "^3.0.0"
|
||||||
|
css.escape "^1.5.1"
|
||||||
|
dom-accessibility-api "^0.5.6"
|
||||||
|
lodash "^4.17.15"
|
||||||
|
redent "^3.0.0"
|
||||||
|
|
||||||
|
"@testing-library/react@^12.1.5":
|
||||||
|
version "12.1.5"
|
||||||
|
resolved "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz"
|
||||||
|
integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.5"
|
||||||
|
"@testing-library/dom" "^8.0.0"
|
||||||
|
"@types/react-dom" "<18.0.0"
|
||||||
|
|
||||||
"@trysound/sax@0.2.0":
|
"@trysound/sax@0.2.0":
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz"
|
resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz"
|
||||||
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
|
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
|
||||||
|
|
||||||
|
"@types/aria-query@^5.0.1":
|
||||||
|
version "5.0.4"
|
||||||
|
resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz"
|
||||||
|
integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==
|
||||||
|
|
||||||
"@types/babel__core@^7.1.14", "@types/babel__core@^7.20.5":
|
"@types/babel__core@^7.1.14", "@types/babel__core@^7.20.5":
|
||||||
version "7.20.5"
|
version "7.20.5"
|
||||||
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz"
|
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz"
|
||||||
@ -1977,7 +2035,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/istanbul-lib-report" "*"
|
"@types/istanbul-lib-report" "*"
|
||||||
|
|
||||||
"@types/jest@^29.5.12":
|
"@types/jest@*", "@types/jest@^29.5.12":
|
||||||
version "29.5.14"
|
version "29.5.14"
|
||||||
resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz"
|
resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz"
|
||||||
integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==
|
integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==
|
||||||
@ -2046,6 +2104,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz"
|
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz"
|
||||||
integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
|
integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
|
||||||
|
|
||||||
|
"@types/react-dom@<18.0.0":
|
||||||
|
version "17.0.26"
|
||||||
|
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.26.tgz"
|
||||||
|
integrity sha512-Z+2VcYXJwOqQ79HreLU/1fyQ88eXSSFh6I3JdrEHQIfYSI0kCQpTGvOrbE6jFGGYXKsHuwY9tBa/w5Uo6KzrEg==
|
||||||
|
|
||||||
"@types/react@*", "@types/react@^19.0.10", "@types/react@^19.2.0":
|
"@types/react@*", "@types/react@^19.0.10", "@types/react@^19.2.0":
|
||||||
version "19.2.7"
|
version "19.2.7"
|
||||||
resolved "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz"
|
resolved "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz"
|
||||||
@ -2104,6 +2167,13 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.12.tgz"
|
resolved "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.12.tgz"
|
||||||
integrity sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q==
|
integrity sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q==
|
||||||
|
|
||||||
|
"@types/testing-library__jest-dom@^5.9.1":
|
||||||
|
version "5.14.9"
|
||||||
|
resolved "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz"
|
||||||
|
integrity sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==
|
||||||
|
dependencies:
|
||||||
|
"@types/jest" "*"
|
||||||
|
|
||||||
"@types/tough-cookie@*":
|
"@types/tough-cookie@*":
|
||||||
version "4.0.5"
|
version "4.0.5"
|
||||||
resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz"
|
resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz"
|
||||||
@ -2531,6 +2601,13 @@ argparse@^2.0.1:
|
|||||||
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
|
||||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||||
|
|
||||||
|
aria-query@^5.0.0, aria-query@5.1.3:
|
||||||
|
version "5.1.3"
|
||||||
|
resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz"
|
||||||
|
integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==
|
||||||
|
dependencies:
|
||||||
|
deep-equal "^2.0.5"
|
||||||
|
|
||||||
arr-diff@^4.0.0:
|
arr-diff@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz"
|
resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz"
|
||||||
@ -2546,7 +2623,7 @@ arr-union@^3.1.0:
|
|||||||
resolved "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz"
|
resolved "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz"
|
||||||
integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==
|
integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==
|
||||||
|
|
||||||
array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2:
|
array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz"
|
||||||
integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==
|
integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==
|
||||||
@ -3273,7 +3350,7 @@ call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-
|
|||||||
es-errors "^1.3.0"
|
es-errors "^1.3.0"
|
||||||
function-bind "^1.1.2"
|
function-bind "^1.1.2"
|
||||||
|
|
||||||
call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.7, call-bind@^1.0.8:
|
call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.7, call-bind@^1.0.8:
|
||||||
version "1.0.8"
|
version "1.0.8"
|
||||||
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz"
|
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz"
|
||||||
integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
|
integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
|
||||||
@ -3972,6 +4049,11 @@ css-what@^6.0.1:
|
|||||||
resolved "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz"
|
resolved "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz"
|
||||||
integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==
|
integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==
|
||||||
|
|
||||||
|
css.escape@^1.5.1:
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz"
|
||||||
|
integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==
|
||||||
|
|
||||||
cssesc@^3.0.0:
|
cssesc@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz"
|
||||||
@ -4191,6 +4273,30 @@ deep-equal@^1.0.1:
|
|||||||
object-keys "^1.1.1"
|
object-keys "^1.1.1"
|
||||||
regexp.prototype.flags "^1.5.1"
|
regexp.prototype.flags "^1.5.1"
|
||||||
|
|
||||||
|
deep-equal@^2.0.5:
|
||||||
|
version "2.2.3"
|
||||||
|
resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz"
|
||||||
|
integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==
|
||||||
|
dependencies:
|
||||||
|
array-buffer-byte-length "^1.0.0"
|
||||||
|
call-bind "^1.0.5"
|
||||||
|
es-get-iterator "^1.1.3"
|
||||||
|
get-intrinsic "^1.2.2"
|
||||||
|
is-arguments "^1.1.1"
|
||||||
|
is-array-buffer "^3.0.2"
|
||||||
|
is-date-object "^1.0.5"
|
||||||
|
is-regex "^1.1.4"
|
||||||
|
is-shared-array-buffer "^1.0.2"
|
||||||
|
isarray "^2.0.5"
|
||||||
|
object-is "^1.1.5"
|
||||||
|
object-keys "^1.1.1"
|
||||||
|
object.assign "^4.1.4"
|
||||||
|
regexp.prototype.flags "^1.5.1"
|
||||||
|
side-channel "^1.0.4"
|
||||||
|
which-boxed-primitive "^1.0.2"
|
||||||
|
which-collection "^1.0.1"
|
||||||
|
which-typed-array "^1.1.13"
|
||||||
|
|
||||||
deepmerge@^4.2.2:
|
deepmerge@^4.2.2:
|
||||||
version "4.3.1"
|
version "4.3.1"
|
||||||
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz"
|
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz"
|
||||||
@ -4363,6 +4469,11 @@ dns-txt@^2.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
buffer-indexof "^1.0.0"
|
buffer-indexof "^1.0.0"
|
||||||
|
|
||||||
|
dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9:
|
||||||
|
version "0.5.16"
|
||||||
|
resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz"
|
||||||
|
integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
|
||||||
|
|
||||||
dom-converter@^0.2.0:
|
dom-converter@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz"
|
resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz"
|
||||||
@ -4661,6 +4772,21 @@ es-errors@^1.3.0:
|
|||||||
resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz"
|
resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz"
|
||||||
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
||||||
|
|
||||||
|
es-get-iterator@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz"
|
||||||
|
integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.2"
|
||||||
|
get-intrinsic "^1.1.3"
|
||||||
|
has-symbols "^1.0.3"
|
||||||
|
is-arguments "^1.1.1"
|
||||||
|
is-map "^2.0.2"
|
||||||
|
is-set "^2.0.2"
|
||||||
|
is-string "^1.0.7"
|
||||||
|
isarray "^2.0.5"
|
||||||
|
stop-iteration-iterator "^1.0.0"
|
||||||
|
|
||||||
es-module-lexer@^1.2.1:
|
es-module-lexer@^1.2.1:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz"
|
resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz"
|
||||||
@ -5226,7 +5352,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5:
|
|||||||
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
|
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
|
||||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||||
|
|
||||||
get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0:
|
get-intrinsic@^1.1.3, get-intrinsic@^1.2.2, get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
|
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
|
||||||
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
|
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
|
||||||
@ -5778,6 +5904,11 @@ imurmurhash@^0.1.4:
|
|||||||
resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
|
resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
|
||||||
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
|
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
|
||||||
|
|
||||||
|
indent-string@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz"
|
||||||
|
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
|
||||||
|
|
||||||
inflight@^1.0.4:
|
inflight@^1.0.4:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
|
resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
|
||||||
@ -5855,7 +5986,7 @@ is-arguments@^1.0.4, is-arguments@^1.1.1:
|
|||||||
call-bound "^1.0.2"
|
call-bound "^1.0.2"
|
||||||
has-tostringtag "^1.0.2"
|
has-tostringtag "^1.0.2"
|
||||||
|
|
||||||
is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
|
is-array-buffer@^3.0.2, is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
|
||||||
version "3.0.5"
|
version "3.0.5"
|
||||||
resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz"
|
resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz"
|
||||||
integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==
|
integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==
|
||||||
@ -6047,7 +6178,7 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-extglob "^2.1.1"
|
is-extglob "^2.1.1"
|
||||||
|
|
||||||
is-map@^2.0.3:
|
is-map@^2.0.2, is-map@^2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz"
|
resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz"
|
||||||
integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==
|
integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==
|
||||||
@ -6136,12 +6267,12 @@ is-regex@^1.1.4, is-regex@^1.2.1:
|
|||||||
has-tostringtag "^1.0.2"
|
has-tostringtag "^1.0.2"
|
||||||
hasown "^2.0.2"
|
hasown "^2.0.2"
|
||||||
|
|
||||||
is-set@^2.0.3:
|
is-set@^2.0.2, is-set@^2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz"
|
resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz"
|
||||||
integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==
|
integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==
|
||||||
|
|
||||||
is-shared-array-buffer@^1.0.4:
|
is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz"
|
resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz"
|
||||||
integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==
|
integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==
|
||||||
@ -6158,7 +6289,7 @@ is-stream@^2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz"
|
||||||
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
|
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
|
||||||
|
|
||||||
is-string@^1.1.1:
|
is-string@^1.0.7, is-string@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz"
|
resolved "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz"
|
||||||
integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==
|
integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==
|
||||||
@ -7077,6 +7208,11 @@ lru-cache@^5.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist "^3.0.2"
|
yallist "^3.0.2"
|
||||||
|
|
||||||
|
lz-string@^1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz"
|
||||||
|
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
||||||
|
|
||||||
magic-string@^0.25.7:
|
magic-string@^0.25.7:
|
||||||
version "0.25.9"
|
version "0.25.9"
|
||||||
resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz"
|
resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz"
|
||||||
@ -7302,6 +7438,11 @@ mimic-response@^2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz"
|
resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz"
|
||||||
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
|
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
|
||||||
|
|
||||||
|
min-indent@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz"
|
||||||
|
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
|
||||||
|
|
||||||
mini-css-extract-plugin@^1.6.0:
|
mini-css-extract-plugin@^1.6.0:
|
||||||
version "1.6.2"
|
version "1.6.2"
|
||||||
resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz"
|
resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz"
|
||||||
@ -8400,16 +8541,16 @@ pretty-error@^4.0.0:
|
|||||||
lodash "^4.17.20"
|
lodash "^4.17.20"
|
||||||
renderkid "^3.0.0"
|
renderkid "^3.0.0"
|
||||||
|
|
||||||
pretty-format@^29.0.0:
|
pretty-format@^27.0.2:
|
||||||
version "29.7.0"
|
version "27.5.1"
|
||||||
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz"
|
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz"
|
||||||
integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
|
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@jest/schemas" "^29.6.3"
|
ansi-regex "^5.0.1"
|
||||||
ansi-styles "^5.0.0"
|
ansi-styles "^5.0.0"
|
||||||
react-is "^18.0.0"
|
react-is "^17.0.1"
|
||||||
|
|
||||||
pretty-format@^29.7.0:
|
pretty-format@^29.0.0, pretty-format@^29.7.0:
|
||||||
version "29.7.0"
|
version "29.7.0"
|
||||||
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz"
|
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz"
|
||||||
integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
|
integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
|
||||||
@ -8596,7 +8737,7 @@ raw-body@2.5.2:
|
|||||||
iconv-lite "0.4.24"
|
iconv-lite "0.4.24"
|
||||||
unpipe "1.0.0"
|
unpipe "1.0.0"
|
||||||
|
|
||||||
react-dom@^17.0.2, react-dom@>=16.8.0, react-dom@>=16.8.3:
|
react-dom@^17.0.2, react-dom@<18.0.0, react-dom@>=16.8.0, react-dom@>=16.8.3:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz"
|
resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz"
|
||||||
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
|
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
|
||||||
@ -8610,6 +8751,11 @@ react-is@^16.13.1:
|
|||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
||||||
|
react-is@^17.0.1:
|
||||||
|
version "17.0.2"
|
||||||
|
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
|
||||||
|
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||||
|
|
||||||
react-is@^18.0.0, react-is@^18.3.1:
|
react-is@^18.0.0, react-is@^18.3.1:
|
||||||
version "18.3.1"
|
version "18.3.1"
|
||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
|
||||||
@ -8625,7 +8771,7 @@ react-mentions@^4.3.1:
|
|||||||
prop-types "^15.5.8"
|
prop-types "^15.5.8"
|
||||||
substyle "^9.1.0"
|
substyle "^9.1.0"
|
||||||
|
|
||||||
"react@^15.0.2 || ^16.0.0 || ^17.0.0", react@^17.0.2, react@>=16.8.0, react@>=16.8.3, react@17.0.2:
|
"react@^15.0.2 || ^16.0.0 || ^17.0.0", react@^17.0.2, react@<18.0.0, react@>=16.8.0, react@>=16.8.3, react@17.0.2:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz"
|
resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz"
|
||||||
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
|
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
|
||||||
@ -8702,6 +8848,14 @@ readdirp@^4.0.1:
|
|||||||
resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz"
|
resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz"
|
||||||
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
|
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
|
||||||
|
|
||||||
|
redent@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz"
|
||||||
|
integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
|
||||||
|
dependencies:
|
||||||
|
indent-string "^4.0.0"
|
||||||
|
strip-indent "^3.0.0"
|
||||||
|
|
||||||
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
|
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz"
|
resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz"
|
||||||
@ -9360,7 +9514,7 @@ side-channel-weakmap@^1.0.2:
|
|||||||
object-inspect "^1.13.3"
|
object-inspect "^1.13.3"
|
||||||
side-channel-map "^1.0.1"
|
side-channel-map "^1.0.1"
|
||||||
|
|
||||||
side-channel@^1.0.6, side-channel@^1.1.0:
|
side-channel@^1.0.4, side-channel@^1.0.6, side-channel@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz"
|
resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz"
|
||||||
integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
|
integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
|
||||||
@ -9645,7 +9799,7 @@ statuses@2.0.1:
|
|||||||
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
|
||||||
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
|
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
|
||||||
|
|
||||||
stop-iteration-iterator@^1.1.0:
|
stop-iteration-iterator@^1.0.0, stop-iteration-iterator@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz"
|
resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz"
|
||||||
integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==
|
integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==
|
||||||
@ -9805,6 +9959,13 @@ strip-final-newline@^2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz"
|
resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz"
|
||||||
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
||||||
|
|
||||||
|
strip-indent@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz"
|
||||||
|
integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
|
||||||
|
dependencies:
|
||||||
|
min-indent "^1.0.0"
|
||||||
|
|
||||||
strip-json-comments@^3.1.1:
|
strip-json-comments@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
|
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
|
||||||
@ -10670,15 +10831,7 @@ whatwg-url@^14.0.0, whatwg-url@^14.1.1:
|
|||||||
tr46 "^5.1.0"
|
tr46 "^5.1.0"
|
||||||
webidl-conversions "^7.0.0"
|
webidl-conversions "^7.0.0"
|
||||||
|
|
||||||
whatwg-url@^15.0.0:
|
whatwg-url@^15.0.0, whatwg-url@^15.1.0:
|
||||||
version "15.1.0"
|
|
||||||
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz"
|
|
||||||
integrity sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==
|
|
||||||
dependencies:
|
|
||||||
tr46 "^6.0.0"
|
|
||||||
webidl-conversions "^8.0.0"
|
|
||||||
|
|
||||||
whatwg-url@^15.1.0:
|
|
||||||
version "15.1.0"
|
version "15.1.0"
|
||||||
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz"
|
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz"
|
||||||
integrity sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==
|
integrity sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==
|
||||||
@ -10694,7 +10847,7 @@ whatwg-url@^5.0.0:
|
|||||||
tr46 "~0.0.3"
|
tr46 "~0.0.3"
|
||||||
webidl-conversions "^3.0.0"
|
webidl-conversions "^3.0.0"
|
||||||
|
|
||||||
which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1:
|
which-boxed-primitive@^1.0.2, which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz"
|
resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz"
|
||||||
integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==
|
integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==
|
||||||
@ -10724,7 +10877,7 @@ which-builtin-type@^1.2.1:
|
|||||||
which-collection "^1.0.2"
|
which-collection "^1.0.2"
|
||||||
which-typed-array "^1.1.16"
|
which-typed-array "^1.1.16"
|
||||||
|
|
||||||
which-collection@^1.0.2:
|
which-collection@^1.0.1, which-collection@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz"
|
||||||
integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==
|
integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==
|
||||||
@ -10739,7 +10892,7 @@ which-module@^2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz"
|
||||||
integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
|
integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
|
||||||
|
|
||||||
which-typed-array@^1.1.16, which-typed-array@^1.1.19, which-typed-array@^1.1.2:
|
which-typed-array@^1.1.13, which-typed-array@^1.1.16, which-typed-array@^1.1.19, which-typed-array@^1.1.2:
|
||||||
version "1.1.19"
|
version "1.1.19"
|
||||||
resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz"
|
resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz"
|
||||||
integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==
|
integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==
|
||||||
@ -10829,12 +10982,7 @@ ws@^7.3.1:
|
|||||||
resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz"
|
resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz"
|
||||||
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
|
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
|
||||||
|
|
||||||
ws@^8.18.0:
|
ws@^8.18.0, ws@^8.18.3:
|
||||||
version "8.18.3"
|
|
||||||
resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz"
|
|
||||||
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
|
|
||||||
|
|
||||||
ws@^8.18.3:
|
|
||||||
version "8.18.3"
|
version "8.18.3"
|
||||||
resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz"
|
resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz"
|
||||||
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
|
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
|
||||||
|
|||||||
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
@ -1 +1 @@
|
|||||||
!function(){"use strict";var n,e={4256:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every((function(n){return t.O[n](r[c])}))?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=594,function(){var n={594:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some((function(e){return 0!==n[e]}))){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],(function(){return t(4256)}));o=t.O(o)}();
|
!function(){"use strict";var n,e={4256:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every(function(n){return t.O[n](r[c])})?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=594,function(){var n={594:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some(function(e){return 0!==n[e]})){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],function(){return t(4256)});o=t.O(o)}();
|
||||||
@ -1 +1 @@
|
|||||||
!function(){"use strict";var n,e={5879:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every((function(n){return t.O[n](r[c])}))?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=543,function(){var n={543:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some((function(e){return 0!==n[e]}))){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],(function(){return t(5879)}));o=t.O(o)}();
|
!function(){"use strict";var n,e={5879:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every(function(n){return t.O[n](r[c])})?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=543,function(){var n={543:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some(function(e){return 0!==n[e]})){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],function(){return t(5879)});o=t.O(o)}();
|
||||||
@ -1 +1 @@
|
|||||||
!function(){"use strict";var n,e={1684:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every((function(n){return t.O[n](r[c])}))?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=152,function(){var n={152:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some((function(e){return 0!==n[e]}))){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],(function(){return t(1684)}));o=t.O(o)}();
|
!function(){"use strict";var n,e={1684:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every(function(n){return t.O[n](r[c])})?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=152,function(){var n={152:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some(function(e){return 0!==n[e]})){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],function(){return t(1684)});o=t.O(o)}();
|
||||||
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
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user