Playlistorder #7442

Merged
jessopb merged 3 commits from playlistorder into master 2022-01-27 16:20:21 +01:00
40 changed files with 1114 additions and 544 deletions

View file

@ -2,7 +2,8 @@
.*\.typeface\.json .*\.typeface\.json
.*/node_modules/findup/.* .*/node_modules/findup/.*
.*/node_modules/react-plastic/.* .*/node_modules/react-plastic/.*
.*/node_modules/raf-schd/.*
.*/node_modules/react-beautiful-dnd/.*
[include] [include]

View file

@ -24,9 +24,8 @@ declare type CollectionGroup = {
} }
declare type CollectionEditParams = { declare type CollectionEditParams = {
claims?: Array<Claim>, uris?: Array<string>,
remove?: boolean, remove?: boolean,
claimIds?: Array<string>,
replace?: boolean, replace?: boolean,
order?: { from: number, to: number }, order?: { from: number, to: number },
type?: string, type?: string,

View file

@ -62,7 +62,8 @@
"parse-duration": "^1.0.0", "parse-duration": "^1.0.0",
"proxy-polyfill": "0.1.6", "proxy-polyfill": "0.1.6",
"re-reselect": "^4.0.0", "re-reselect": "^4.0.0",
"react-datetime-picker": "^3.2.1", "react-beautiful-dnd": "^13.1.0",
"react-datetime-picker": "^3.4.3",
"remove-markdown": "^0.3.0", "remove-markdown": "^0.3.0",
"rss": "^1.2.2", "rss": "^1.2.2",
"source-map-explorer": "^2.5.2", "source-map-explorer": "^2.5.2",

View file

@ -2249,5 +2249,11 @@
"Amount of $%input_amount% LBC in USB is lower than price of $%price_amount%": "Amount of $%input_amount% LBC in USB is lower than price of $%price_amount%", "Amount of $%input_amount% LBC in USB is lower than price of $%price_amount%": "Amount of $%input_amount% LBC in USB is lower than price of $%price_amount%",
"Hosting for content you have downloaded": "Hosting for content you have downloaded", "Hosting for content you have downloaded": "Hosting for content you have downloaded",
"Hosting content selected by the network": "Hosting content selected by the network", "Hosting content selected by the network": "Hosting content selected by the network",
"Remove all unavailable claims": "Remove all unavailable claims",
"Drag": "Drag",
"Move Top": "Move Top",
"Move Bottom": "Move Bottom",
"Move Up": "Move Up",
"Move Down": "Move Down",
"--end--": "--end--" "--end--": "--end--"
} }

View file

@ -79,15 +79,7 @@ const ClaimCollectionAdd = (props: Props) => {
.filter((list) => (isChannel ? list.type === 'collection' : list.type === 'playlist')) .filter((list) => (isChannel ? list.type === 'collection' : list.type === 'playlist'))
.map((l) => { .map((l) => {
const { id } = l; const { id } = l;
return ( return <CollectionSelectItem collectionId={id} uri={permanentUrl} key={id} category={'builtin'} />;
<CollectionSelectItem
claim={claim}
collectionId={id}
uri={permanentUrl}
key={id}
category={'builtin'}
/>
);
})} })}
{unpublished && {unpublished &&
(Object.values(unpublished): any) (Object.values(unpublished): any)
@ -96,13 +88,7 @@ const ClaimCollectionAdd = (props: Props) => {
.map((l) => { .map((l) => {
const { id } = l; const { id } = l;
return ( return (
<CollectionSelectItem <CollectionSelectItem collectionId={id} uri={permanentUrl} key={id} category={'unpublished'} />
claim={claim}
collectionId={id}
uri={permanentUrl}
key={id}
category={'unpublished'}
/>
); );
})} })}
{published && {published &&
@ -110,13 +96,7 @@ const ClaimCollectionAdd = (props: Props) => {
// $FlowFixMe // $FlowFixMe
const { id } = l; const { id } = l;
return ( return (
<CollectionSelectItem <CollectionSelectItem collectionId={id} uri={permanentUrl} key={id} category={'published'} />
claim={claim}
collectionId={id}
uri={permanentUrl}
key={id}
category={'published'}
/>
); );
})} })}
</div> </div>

View file

@ -1,4 +1,7 @@
// @flow // @flow
// $FlowFixMe
import { Draggable } from 'react-beautiful-dnd';
import { MAIN_CLASS } from 'constants/classnames'; import { MAIN_CLASS } from 'constants/classnames';
import type { Node } from 'react'; import type { Node } from 'react';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
@ -44,7 +47,13 @@ type Props = {
collectionId?: string, collectionId?: string,
showNoSourceClaims?: boolean, showNoSourceClaims?: boolean,
onClick?: (e: any, claim?: ?Claim, index?: number) => void, onClick?: (e: any, claim?: ?Claim, index?: number) => void,
noEmpty: boolean, maxClaimRender?: number,
excludeUris?: Array<string>,
loadedCallback?: (number) => void,
swipeLayout: boolean,
showEdit?: boolean,
droppableProvided?: any,
unavailableUris?: Array<string>,
}; };
export default function ClaimList(props: Props) { export default function ClaimList(props: Props) {
@ -75,7 +84,13 @@ export default function ClaimList(props: Props) {
collectionId, collectionId,
showNoSourceClaims, showNoSourceClaims,
onClick, onClick,
noEmpty, maxClaimRender,
excludeUris = [],
loadedCallback,
swipeLayout = false,
showEdit,
droppableProvided,
unavailableUris,
} = props; } = props;
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW); const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
@ -85,8 +100,18 @@ export default function ClaimList(props: Props) {
const timedOut = uris === null; const timedOut = uris === null;
const urisLength = (uris && uris.length) || 0; const urisLength = (uris && uris.length) || 0;
const tileUris = (prefixUris || []).concat(uris); let tileUris = (prefixUris || []).concat(uris || []);
const sortedUris = (urisLength > 0 && (currentSort === SORT_NEW ? tileUris : tileUris.slice().reverse())) || []; tileUris = tileUris.filter((uri) => !excludeUris.includes(uri));
const totalLength = tileUris.length;
if (maxClaimRender) tileUris = tileUris.slice(0, maxClaimRender);
let sortedUris = (urisLength > 0 && (currentSort === SORT_NEW ? tileUris : tileUris.slice().reverse())) || [];
React.useEffect(() => {
if (typeof loadedCallback === 'function') loadedCallback(totalLength);
}, [totalLength]); // eslint-disable-line react-hooks/exhaustive-deps
const noResultMsg = searchInLanguage const noResultMsg = searchInLanguage
? __('No results. Contents may be hidden by the Language filter.') ? __('No results. Contents may be hidden by the Language filter.')
@ -96,11 +121,21 @@ export default function ClaimList(props: Props) {
setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW); setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW);
} }
function handleClaimClicked(e, claim, index) { const handleClaimClicked = React.useCallback(
(e, claim, index) => {
if (onClick) { if (onClick) {
onClick(e, claim, index); onClick(e, claim, index);
} }
} },
[onClick]
);
const customShouldHide = React.useCallback((claim: StreamClaim) => {
// Hack to hide spee.ch thumbnail publishes
// If it meets these requirements, it was probably uploaded here:
// https://github.com/lbryio/lbry-redux/blob/master/src/redux/actions/publish.js#L74-L79
return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch';
}, []);
useEffect(() => { useEffect(() => {
const handleScroll = debounce((e) => { const handleScroll = debounce((e) => {
@ -123,8 +158,32 @@ export default function ClaimList(props: Props) {
} }
}, [loading, onScrollBottom, urisLength, pageSize, page]); }, [loading, onScrollBottom, urisLength, pageSize, page]);
const getClaimPreview = (uri: string, index: number, draggableProvided?: any) => (
<ClaimPreview
uri={uri}
indexInContainer={index}
type={type}
active={activeUri && uri === activeUri}
hideMenu={hideMenu}
includeSupportAction={includeSupportAction}
showUnresolvedClaim={showUnresolvedClaims}
properties={renderProperties || (type !== 'small' ? undefined : false)}
renderActions={renderActions}
showUserBlocked={showHiddenByUser}
showHiddenByUser={showHiddenByUser}
collectionId={collectionId}
showNoSourceClaims={showNoSourceClaims}
customShouldHide={customShouldHide}
onClick={handleClaimClicked}
swipeLayout={swipeLayout}
showEdit={showEdit}
dragHandleProps={draggableProvided && draggableProvided.dragHandleProps}
unavailableUris={unavailableUris}
/>
);
return tileLayout && !header ? ( return tileLayout && !header ? (
<section className="claim-grid"> <section className={classnames('claim-grid', { 'swipe-list': swipeLayout })}>
{urisLength > 0 && {urisLength > 0 &&
tileUris.map((uri) => ( tileUris.map((uri) => (
<ClaimPreviewTile <ClaimPreviewTile
@ -134,11 +193,10 @@ export default function ClaimList(props: Props) {
properties={renderProperties} properties={renderProperties}
collectionId={collectionId} collectionId={collectionId}
showNoSourceClaims={showNoSourceClaims} showNoSourceClaims={showNoSourceClaims}
swipeLayout={swipeLayout}
/> />
))} ))}
{!timedOut && urisLength === 0 && !loading && !noEmpty && ( {!timedOut && urisLength === 0 && !loading && <div className="empty main--empty">{empty || noResultMsg}</div>}
<div className="empty main--empty">{empty || noResultMsg}</div>
)}
{timedOut && timedOutMessage && <div className="empty main--empty">{timedOutMessage}</div>} {timedOut && timedOutMessage && <div className="empty main--empty">{timedOutMessage}</div>}
</section> </section>
) : ( ) : (
@ -178,43 +236,52 @@ export default function ClaimList(props: Props) {
{urisLength > 0 && ( {urisLength > 0 && (
<ul <ul
className={classnames('ul--no-style', { className={classnames('ul--no-style', {
card: !(tileLayout || type === 'small'), card: !(tileLayout || swipeLayout || type === 'small'),
'claim-list--card-body': tileLayout, 'claim-list--card-body': tileLayout,
'swipe-list': swipeLayout,
})} })}
{...(droppableProvided && droppableProvided.droppableProps)}
ref={droppableProvided && droppableProvided.innerRef}
> >
{sortedUris.map((uri, index) => ( {injectedItem && sortedUris.some((uri, index) => index === 4) && <li>{injectedItem}</li>}
<React.Fragment key={uri}>
{injectedItem && index === 4 && <li>{injectedItem}</li>} {sortedUris.map((uri, index) =>
<ClaimPreview droppableProvided ? (
uri={uri} <Draggable key={uri} draggableId={uri} index={index}>
indexInContainer={index} {(draggableProvided, draggableSnapshot) => {
type={type} // Restrict dragging to vertical axis
active={activeUri && uri === activeUri} // https://github.com/atlassian/react-beautiful-dnd/issues/958#issuecomment-980548919
hideMenu={hideMenu} let transform = draggableProvided.draggableProps.style.transform;
includeSupportAction={includeSupportAction}
showUnresolvedClaim={showUnresolvedClaims} if (draggableSnapshot.isDragging && transform) {
properties={renderProperties || (type !== 'small' ? undefined : false)} transform = transform.replace(/\(.+,/, '(0,');
renderActions={renderActions} }
showUserBlocked={showHiddenByUser}
showHiddenByUser={showHiddenByUser} const style = {
collectionId={collectionId} ...draggableProvided.draggableProps.style,
showNoSourceClaims={showNoSourceClaims} transform,
customShouldHide={(claim: StreamClaim) => { };
// Hack to hide spee.ch thumbnail publishes
// If it meets these requirements, it was probably uploaded here: return (
// https://github.com/lbryio/lbry-redux/blob/master/src/redux/actions/publish.js#L74-L79 <li ref={draggableProvided.innerRef} {...draggableProvided.draggableProps} style={style}>
return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch'; {/* https://github.com/atlassian/react-beautiful-dnd/issues/1756 */}
<div style={{ display: 'none' }} {...draggableProvided.dragHandleProps} />
{getClaimPreview(uri, index, draggableProvided)}
</li>
);
}} }}
onClick={(e, claim, index) => handleClaimClicked(e, claim, index)} </Draggable>
/> ) : (
</React.Fragment> getClaimPreview(uri, index)
))} )
)}
{droppableProvided && droppableProvided.placeholder}
</ul> </ul>
)} )}
{!timedOut && urisLength === 0 && !loading && !noEmpty && ( {!timedOut && urisLength === 0 && !loading && <div className="empty empty--centered">{empty || noResultMsg}</div>}
<div className="empty empty--centered">{empty || noResultMsg}</div>
)}
{!loading && timedOut && timedOutMessage && <div className="empty empty--centered">{timedOutMessage}</div>} {!loading && timedOut && timedOutMessage && <div className="empty empty--centered">{timedOutMessage}</div>}
</section> </section>
); );

View file

@ -94,6 +94,13 @@ type Props = {
doClaimSearch: ({}) => void, doClaimSearch: ({}) => void,
doToggleTagFollowDesktop: (string) => void, doToggleTagFollowDesktop: (string) => void,
doFetchViewCount: (claimIdCsv: string) => void, doFetchViewCount: (claimIdCsv: string) => void,
loadedCallback?: (number) => void,
maxClaimRender?: number,
useSkeletonScreen?: boolean,
excludeUris?: Array<string>,
swipeLayout: boolean,
}; };
function ClaimListDiscover(props: Props) { function ClaimListDiscover(props: Props) {
@ -157,6 +164,11 @@ function ClaimListDiscover(props: Props) {
empty, empty,
claimsByUri, claimsByUri,
doFetchViewCount, doFetchViewCount,
loadedCallback,
maxClaimRender,
useSkeletonScreen = true,
excludeUris = [],
swipeLayout = false,
} = props; } = props;
const didNavigateForward = history.action === 'PUSH'; const didNavigateForward = history.action === 'PUSH';
const { search } = location; const { search } = location;
@ -493,6 +505,21 @@ function ClaimListDiscover(props: Props) {
} }
function resolveOrderByOption(orderBy: string | Array<string>, sortBy: string | Array<string>) { function resolveOrderByOption(orderBy: string | Array<string>, sortBy: string | Array<string>) {
// let order_by; // peterson 038692cafc793616cceaf10b88909fecde07ad0b
//
// switch (orderBy) {
// case CS.ORDER_BY_TRENDING:
// order_by = CS.ORDER_BY_TRENDING_VALUE;
// break;
// case CS.ORDER_BY_NEW:
// order_by = CS.ORDER_BY_NEW_VALUE;
// break;
// case CS.ORDER_BY_NEW_ASC:
// order_by = CS.ORDER_BY_NEW_ASC_VALUE;
// break;
// default:
// order_by = CS.ORDER_BY_TOP_VALUE;
// }
const order_by = const order_by =
orderBy === CS.ORDER_BY_TRENDING orderBy === CS.ORDER_BY_TRENDING
? CS.ORDER_BY_TRENDING_VALUE ? CS.ORDER_BY_TRENDING_VALUE
@ -569,8 +596,12 @@ function ClaimListDiscover(props: Props) {
searchOptions={options} searchOptions={options}
showNoSourceClaims={showNoSourceClaims} showNoSourceClaims={showNoSourceClaims}
empty={empty} empty={empty}
maxClaimRender={maxClaimRender}
excludeUris={excludeUris}
loadedCallback={loadedCallback}
swipeLayout={swipeLayout}
/> />
{loading && ( {loading && useSkeletonScreen && (
<div className="claim-grid"> <div className="claim-grid">
{new Array(dynamicPageSize).fill(1).map((x, i) => ( {new Array(dynamicPageSize).fill(1).map((x, i) => (
<ClaimPreviewTile key={i} placeholder="loading" /> <ClaimPreviewTile key={i} placeholder="loading" />
@ -602,8 +633,13 @@ function ClaimListDiscover(props: Props) {
searchOptions={options} searchOptions={options}
showNoSourceClaims={hasNoSource || showNoSourceClaims} showNoSourceClaims={hasNoSource || showNoSourceClaims}
empty={empty} empty={empty}
maxClaimRender={maxClaimRender}
excludeUris={excludeUris}
loadedCallback={loadedCallback}
swipeLayout={swipeLayout}
/> />
{loading && {loading &&
useSkeletonScreen &&
new Array(dynamicPageSize) new Array(dynamicPageSize)
.fill(1) .fill(1)
.map((x, i) => ( .map((x, i) => (

View file

@ -157,11 +157,9 @@ function ClaimMenuList(props: Props) {
doToast({ doToast({
message: source ? __('Item removed from %name%', { name }) : __('Item added to %name%', { name }), message: source ? __('Item removed from %name%', { name }) : __('Item added to %name%', { name }),
}); });
doCollectionEdit(collectionId, { if (contentClaim) {
claims: [contentClaim], doCollectionEdit(collectionId, { uris: [contentClaim.permanent_url], remove: source, type: 'playlist' });
remove: source, }
type: 'playlist',
});
} }
function handleFollow() { function handleFollow() {

View file

@ -10,17 +10,12 @@ import {
selectDateForUri, selectDateForUri,
} from 'redux/selectors/claims'; } from 'redux/selectors/claims';
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info'; import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
import { import { makeSelectCollectionIsMine } from 'redux/selectors/collections';
makeSelectCollectionIsMine,
makeSelectUrlsForCollectionId,
makeSelectIndexForUrlInCollection,
} from 'redux/selectors/collections';
import { doResolveUri } from 'redux/actions/claims'; import { doResolveUri } from 'redux/actions/claims';
import { doCollectionEdit } from 'redux/actions/collections';
import { doFileGet } from 'redux/actions/file'; import { doFileGet } from 'redux/actions/file';
import { selectBanStateForUri } from 'lbryinc'; import { selectBanStateForUri } from 'lbryinc';
import { selectShowMatureContent } from 'redux/selectors/settings'; import { selectLanguage, selectShowMatureContent } from 'redux/selectors/settings';
import { makeSelectHasVisitedUri } from 'redux/selectors/content'; import { makeSelectHasVisitedUri } from 'redux/selectors/content';
import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions'; import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
import { isClaimNsfw } from 'util/claim'; import { isClaimNsfw } from 'util/claim';
@ -50,15 +45,13 @@ const select = (state, props) => {
streamingUrl: props.uri && makeSelectStreamingUrlForUri(props.uri)(state), streamingUrl: props.uri && makeSelectStreamingUrlForUri(props.uri)(state),
wasPurchased: props.uri && makeSelectClaimWasPurchased(props.uri)(state), wasPurchased: props.uri && makeSelectClaimWasPurchased(props.uri)(state),
isCollectionMine: makeSelectCollectionIsMine(props.collectionId)(state), isCollectionMine: makeSelectCollectionIsMine(props.collectionId)(state),
collectionUris: makeSelectUrlsForCollectionId(props.collectionId)(state), lang: selectLanguage(state),
collectionIndex: makeSelectIndexForUrlInCollection(props.uri, props.collectionId)(state),
}; };
}; };
const perform = (dispatch) => ({ const perform = (dispatch) => ({
resolveUri: (uri) => dispatch(doResolveUri(uri)), resolveUri: (uri) => dispatch(doResolveUri(uri)),
getFile: (uri) => dispatch(doFileGet(uri, false)), getFile: (uri) => dispatch(doFileGet(uri, false)),
editCollection: (id, params) => dispatch(doCollectionEdit(id, params)),
}); });
export default connect(select, perform)(ClaimPreview); export default connect(select, perform)(ClaimPreview);

View file

@ -6,10 +6,14 @@ import { isEmpty } from 'util/object';
import classnames from 'classnames'; import classnames from 'classnames';
import { isURIValid } from 'util/lbryURI'; import { isURIValid } from 'util/lbryURI';
import * as COLLECTIONS_CONSTS from 'constants/collections'; import * as COLLECTIONS_CONSTS from 'constants/collections';
import { isChannelClaim } from 'util/claim';
import { formatLbryUrlForWeb } from 'util/url'; import { formatLbryUrlForWeb } from 'util/url';
import { formatClaimPreviewTitle } from 'util/formatAriaLabel'; import { formatClaimPreviewTitle } from 'util/formatAriaLabel';
import { toCompactNotation } from 'util/string';
import Tooltip from 'component/common/tooltip';
import FileThumbnail from 'component/fileThumbnail'; import FileThumbnail from 'component/fileThumbnail';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
import PreviewOverlayProperties from 'component/previewOverlayProperties';
import ClaimTags from 'component/claimTags'; import ClaimTags from 'component/claimTags';
import SubscribeButton from 'component/subscribeButton'; import SubscribeButton from 'component/subscribeButton';
import ChannelThumbnail from 'component/channelThumbnail'; import ChannelThumbnail from 'component/channelThumbnail';
@ -25,10 +29,8 @@ import ClaimMenuList from 'component/claimMenuList';
import ClaimPreviewLoading from './claim-preview-loading'; import ClaimPreviewLoading from './claim-preview-loading';
import ClaimPreviewHidden from './claim-preview-no-mature'; import ClaimPreviewHidden from './claim-preview-no-mature';
import ClaimPreviewNoContent from './claim-preview-no-content'; import ClaimPreviewNoContent from './claim-preview-no-content';
import CollectionEditButtons from './collection-buttons'; import CollectionEditButtons from 'component/collectionEditButtons';
import AbandonedChannelPreview from 'component/abandonedChannelPreview'; import AbandonedChannelPreview from 'component/abandonedChannelPreview';
import PreviewOverlayProperties from 'component/previewOverlayProperties';
// preview images used on the landing page and on the channel page // preview images used on the landing page and on the channel page
type Props = { type Props = {
@ -49,7 +51,7 @@ type Props = {
type: string, type: string,
banState: { blacklisted?: boolean, filtered?: boolean, muted?: boolean, blocked?: boolean }, banState: { blacklisted?: boolean, filtered?: boolean, muted?: boolean, blocked?: boolean },
hasVisitedUri: boolean, hasVisitedUri: boolean,
channelIsBlocked: boolean, blockedUris: Array<string>,
actions: boolean | Node | string | number, actions: boolean | Node | string | number,
properties: boolean | Node | string | number | ((Claim) => Node), properties: boolean | Node | string | number | ((Claim) => Node),
empty?: Node, empty?: Node,
@ -68,15 +70,17 @@ type Props = {
repostUrl?: string, repostUrl?: string,
hideMenu?: boolean, hideMenu?: boolean,
collectionId?: string, collectionId?: string,
editCollection: (string, CollectionEditParams) => void,
isCollectionMine: boolean, isCollectionMine: boolean,
collectionUris: Array<Collection>,
collectionIndex?: number,
disableNavigation?: boolean, disableNavigation?: boolean,
mediaDuration?: string, mediaDuration?: string,
date?: any, date?: any,
indexInContainer?: number, // The index order of this component within 'containerId'. indexInContainer?: number, // The index order of this component within 'containerId'.
channelSubCount?: number, channelSubCount?: number,
swipeLayout: boolean,
lang: string,
showEdit?: boolean,
dragHandleProps?: any,
unavailableUris?: Array<string>,
}; };
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => { const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
@ -97,7 +101,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
streamingUrl, streamingUrl,
mediaDuration, mediaDuration,
// user properties // user properties
channelIsBlocked,
hasVisitedUri, hasVisitedUri,
// component // component
history, history,
@ -129,34 +132,40 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
hideMenu = false, hideMenu = false,
// repostUrl, // repostUrl,
collectionId, collectionId,
collectionIndex,
editCollection,
isCollectionMine, isCollectionMine,
collectionUris,
disableNavigation, disableNavigation,
indexInContainer, indexInContainer,
channelSubCount, channelSubCount,
swipeLayout = false,
lang,
showEdit,
dragHandleProps,
unavailableUris,
} = props; } = props;
const isCollection = claim && claim.value_type === 'collection'; const isCollection = claim && claim.value_type === 'collection';
const collectionClaimId = isCollection && claim && claim.claim_id; const collectionClaimId = isCollection && claim && claim.claim_id;
const listId = collectionId || collectionClaimId || null; const listId = collectionId || collectionClaimId;
const WrapperElement = wrapperElement || 'li'; const WrapperElement = wrapperElement || 'li';
const shouldFetch = const shouldFetch =
claim === undefined || (claim !== null && claim.value_type === 'channel' && isEmpty(claim.meta) && !pending); claim === undefined || (claim !== null && claim.value_type === 'channel' && isEmpty(claim.meta) && !pending);
const abandoned = !isResolvingUri && !claim; const abandoned = !isResolvingUri && !claim;
const isMyCollection = listId && (isCollectionMine || listId.includes('-')); const isMyCollection = listId && (isCollectionMine || listId.includes('-'));
if (isMyCollection && claim === null && unavailableUris) unavailableUris.push(uri);
const shouldHideActions = hideActions || isMyCollection || type === 'small' || type === 'tooltip'; const shouldHideActions = hideActions || isMyCollection || type === 'small' || type === 'tooltip';
const canonicalUrl = claim && claim.canonical_url; const canonicalUrl = claim && claim.canonical_url;
const lastCollectionIndex = collectionUris ? collectionUris.length - 1 : 0;
const channelSubscribers = React.useMemo(() => { const channelSubscribers = React.useMemo(() => {
if (channelSubCount === undefined) { if (channelSubCount === undefined) {
return <span />; return <span />;
} }
const formattedSubCount = Number(channelSubCount).toLocaleString(); const formattedSubCount = toCompactNotation(channelSubCount, lang, 10000);
return ( return (
<Tooltip title={channelSubCount} followCursor placement="top">
<span className="claim-preview__channel-sub-count"> <span className="claim-preview__channel-sub-count">
{channelSubCount === 1 ? __('1 Follower') : __('%formattedSubCount% Followers', { formattedSubCount })} {channelSubCount === 1 ? __('1 Follower') : __('%formattedSubCount% Followers', { formattedSubCount })}
</span> </span>
</Tooltip>
); );
}, [channelSubCount]); }, [channelSubCount]);
const isValid = uri && isURIValid(uri); const isValid = uri && isURIValid(uri);
@ -170,12 +179,13 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
claim.value.stream_type && claim.value.stream_type &&
// $FlowFixMe // $FlowFixMe
(claim.value.stream_type === 'audio' || claim.value.stream_type === 'video'); (claim.value.stream_type === 'audio' || claim.value.stream_type === 'video');
const isChannelUri = claim ? claim.value_type === 'channel' : false; const isChannelUri = isChannelClaim(claim, uri);
const signingChannel = claim && claim.signing_channel; const signingChannel = claim && claim.signing_channel;
const repostedChannelUri = const repostedChannelUri =
claim && claim.repost_channel_url && claim.value_type === 'channel' claim && claim.repost_channel_url && claim.value_type === 'channel'
? claim.permanent_url || claim.canonical_url ? claim.permanent_url || claim.canonical_url
: undefined; : undefined;
const repostedContentUri = claim && (claim.reposted_claim ? claim.reposted_claim.permanent_url : claim.permanent_url);
// Get channel title ( use name as fallback ) // Get channel title ( use name as fallback )
let channelTitle = null; let channelTitle = null;
@ -225,7 +235,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
((abandoned && !showUnresolvedClaim) || (!claimIsMine && obscureNsfw && nsfw)); ((abandoned && !showUnresolvedClaim) || (!claimIsMine && obscureNsfw && nsfw));
// This will be replaced once blocking is done at the wallet server level // This will be replaced once blocking is done at the wallet server level
if (claim && !claimIsMine && (banState.blacklisted || banState.filtered)) { if (!shouldHide && !claimIsMine && (banState.blacklisted || banState.filtered)) {
shouldHide = true; shouldHide = true;
} }
@ -320,18 +330,14 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
'claim-preview--channel': isChannelUri, 'claim-preview--channel': isChannelUri,
'claim-preview--visited': !isChannelUri && !claimIsMine && hasVisitedUri, 'claim-preview--visited': !isChannelUri && !claimIsMine && hasVisitedUri,
'claim-preview--pending': pending, 'claim-preview--pending': pending,
'claim-preview--collection-mine': isMyCollection && listId && type === 'listview', 'claim-preview--collection-mine': isMyCollection && showEdit,
'swipe-list__item': swipeLayout,
})} })}
> >
{isMyCollection && listId && type === 'listview' && ( {isMyCollection && showEdit && (
<CollectionEditButtons <CollectionEditButtons uri={uri} collectionId={listId} dragHandleProps={dragHandleProps} />
collectionIndex={collectionIndex}
editCollection={editCollection}
listId={listId}
lastCollectionIndex={lastCollectionIndex}
claim={claim}
/>
)} )}
{isChannelUri && claim ? ( {isChannelUri && claim ? (
<UriIndicator focusable={false} uri={uri} link> <UriIndicator focusable={false} uri={uri} link>
<ChannelThumbnail uri={uri} small={type === 'inline'} /> <ChannelThumbnail uri={uri} small={type === 'inline'} />
@ -342,7 +348,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
<NavLink aria-hidden tabIndex={-1} {...navLinkProps}> <NavLink aria-hidden tabIndex={-1} {...navLinkProps}>
<FileThumbnail thumbnail={thumbnailUrl}> <FileThumbnail thumbnail={thumbnailUrl}>
<div className="claim-preview__hover-actions"> <div className="claim-preview__hover-actions">
{isPlayable && <FileWatchLaterLink focusable={false} uri={uri} />} {isPlayable && <FileWatchLaterLink focusable={false} uri={repostedContentUri} />}
</div> </div>
{/* @if TARGET='app' */} {/* @if TARGET='app' */}
<div className="claim-preview__hover-actions"> <div className="claim-preview__hover-actions">
@ -352,7 +358,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
</div> </div>
{/* @endif */} {/* @endif */}
<div className="claim-preview__file-property-overlay"> <div className="claim-preview__file-property-overlay">
<PreviewOverlayProperties uri={uri} properties={properties} /> <PreviewOverlayProperties uri={uri} />
</div> </div>
</FileThumbnail> </FileThumbnail>
</NavLink> </NavLink>
@ -391,8 +397,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
<ChannelThumbnail uri={signingChannel.permanent_url} xsmall /> <ChannelThumbnail uri={signingChannel.permanent_url} xsmall />
</div> </div>
)} )}
{isChannelUri && !banState.muted && !claimIsMine && (
{isChannelUri && !channelIsBlocked && !claimIsMine && (
<SubscribeButton <SubscribeButton
uri={repostedChannelUri || (uri.startsWith('lbry://') ? uri : `lbry://${uri}`)} uri={repostedChannelUri || (uri.startsWith('lbry://') ? uri : `lbry://${uri}`)}
/> />

View file

@ -42,6 +42,7 @@ type Props = {
properties?: (Claim) => void, properties?: (Claim) => void,
collectionId?: string, collectionId?: string,
viewCount: string, viewCount: string,
swipeLayout: boolean,
}; };
// preview image cards used in related video functionality, channel overview page and homepage // preview image cards used in related video functionality, channel overview page and homepage
@ -66,6 +67,7 @@ function ClaimPreviewTile(props: Props) {
collectionId, collectionId,
mediaDuration, mediaDuration,
viewCount, viewCount,
swipeLayout = false,
} = props; } = props;
const isRepost = claim && claim.repost_channel_url; const isRepost = claim && claim.repost_channel_url;
const isCollection = claim && claim.value_type === 'collection'; const isCollection = claim && claim.value_type === 'collection';
@ -84,6 +86,7 @@ function ClaimPreviewTile(props: Props) {
const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail; const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail;
const canonicalUrl = claim && claim.canonical_url; const canonicalUrl = claim && claim.canonical_url;
const permanentUrl = claim && claim.permanent_url; const permanentUrl = claim && claim.permanent_url;
const repostedContentUri = claim && (claim.reposted_claim ? claim.reposted_claim.permanent_url : claim.permanent_url);
const listId = collectionId || collectionClaimId; const listId = collectionId || collectionClaimId;
const navigateUrl = const navigateUrl =
formatLbryUrlForWeb(canonicalUrl || uri || '/') + (listId ? generateListSearchUrlParams(listId) : ''); formatLbryUrlForWeb(canonicalUrl || uri || '/') + (listId ? generateListSearchUrlParams(listId) : '');
@ -168,6 +171,7 @@ function ClaimPreviewTile(props: Props) {
onClick={handleClick} onClick={handleClick}
className={classnames('card claim-preview--tile', { className={classnames('card claim-preview--tile', {
'claim-preview__wrapper--channel': isChannel, 'claim-preview__wrapper--channel': isChannel,
'swipe-list__item claim-preview--horizontal-tile': swipeLayout,
})} })}
> >
<NavLink {...navLinkProps} role="none" tabIndex={-1} aria-hidden> <NavLink {...navLinkProps} role="none" tabIndex={-1} aria-hidden>
@ -175,7 +179,7 @@ function ClaimPreviewTile(props: Props) {
{!isChannel && ( {!isChannel && (
<React.Fragment> <React.Fragment>
<div className="claim-preview__hover-actions"> <div className="claim-preview__hover-actions">
{isPlayable && <FileWatchLaterLink focusable={false} uri={uri} />} {isPlayable && <FileWatchLaterLink focusable={false} uri={repostedContentUri} />}
</div> </div>
{/* @if TARGET='app' */} {/* @if TARGET='app' */}
<div className="claim-preview__hover-actions"> <div className="claim-preview__hover-actions">

View file

@ -55,20 +55,6 @@ function CollectionActions(props: Props) {
const claimId = claim && claim.claim_id; const claimId = claim && claim.claim_id;
const webShareable = true; // collections have cost? const webShareable = true; // collections have cost?
/*
A bit too much dependency with both ordering and shuffling depending on a single list item index selector
For now when they click edit, we'll toggle shuffle off for them.
*/
const handleSetShowEdit = (setting) => {
doToggleShuffleList(collectionId, false);
setShowEdit(setting);
};
const handlePublishMode = () => {
doToggleShuffleList(collectionId, false);
push(`?${PAGE_VIEW_QUERY}=${EDIT_PAGE}`);
};
const doPlay = React.useCallback( const doPlay = React.useCallback(
(playUri) => { (playUri) => {
const navigateUrl = formatLbryUrlForWeb(playUri); const navigateUrl = formatLbryUrlForWeb(playUri);
@ -138,7 +124,7 @@ function CollectionActions(props: Props) {
title={uri ? __('Update') : __('Publish')} title={uri ? __('Update') : __('Publish')}
label={uri ? __('Update') : __('Publish')} label={uri ? __('Update') : __('Publish')}
className={classnames('button--file-action')} className={classnames('button--file-action')}
onClick={() => handlePublishMode()} onClick={() => push(`?${PAGE_VIEW_QUERY}=${EDIT_PAGE}`)}
icon={ICONS.PUBLISH} icon={ICONS.PUBLISH}
iconColor={collectionHasEdits && 'red'} iconColor={collectionHasEdits && 'red'}
iconSize={18} iconSize={18}
@ -165,7 +151,9 @@ function CollectionActions(props: Props) {
</> </>
); );
const infoButton = ( const infoButtons = (
<div className="section">
{uri && (
<Button <Button
title={__('Info')} title={__('Info')}
className={classnames('button-toggle', { className={classnames('button-toggle', {
@ -174,17 +162,17 @@ function CollectionActions(props: Props) {
icon={ICONS.MORE} icon={ICONS.MORE}
onClick={() => setShowInfo(!showInfo)} onClick={() => setShowInfo(!showInfo)}
/> />
); )}
const showEditButton = ( {isMyCollection && (
<Button <Button
title={__('Edit')} title={__('Edit')}
className={classnames('button-toggle', { className={classnames('button-toggle', { 'button-toggle--active': showEdit })}
'button-toggle--active': showEdit,
})}
icon={ICONS.EDIT} icon={ICONS.EDIT}
onClick={() => handleSetShowEdit(!showEdit)} onClick={() => setShowEdit(!showEdit)}
/> />
)}
</div>
); );
if (isMobile) { if (isMobile) {
@ -192,7 +180,7 @@ function CollectionActions(props: Props) {
<div className="media__actions"> <div className="media__actions">
{lhsSection} {lhsSection}
{rhsSection} {rhsSection}
{uri && <span>{infoButton}</span>} {infoButtons}
</div> </div>
); );
} else { } else {
@ -202,10 +190,8 @@ function CollectionActions(props: Props) {
{lhsSection} {lhsSection}
{rhsSection} {rhsSection}
</div> </div>
<div className="section">
{uri && infoButton} {infoButtons}
{showEditButton}
</div>
</div> </div>
); );
} }

View file

@ -1,13 +1,15 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import CollectionContent from './view'; import CollectionContent from './view';
import { selectClaimForUri, selectClaimIsMine } from 'redux/selectors/claims'; import { selectClaimForUri } from 'redux/selectors/claims';
import { import {
makeSelectUrlsForCollectionId, makeSelectUrlsForCollectionId,
makeSelectNameForCollectionId, makeSelectNameForCollectionId,
makeSelectCollectionForId, makeSelectCollectionForId,
makeSelectCollectionIsMine,
} from 'redux/selectors/collections'; } from 'redux/selectors/collections';
import { selectPlayingUri, selectListLoop, selectListShuffle } from 'redux/selectors/content'; import { selectPlayingUri, selectListLoop, selectListShuffle } from 'redux/selectors/content';
import { doToggleLoopList, doToggleShuffleList } from 'redux/actions/content'; import { doToggleLoopList, doToggleShuffleList } from 'redux/actions/content';
import { doCollectionEdit } from 'redux/actions/collections';
const select = (state, props) => { const select = (state, props) => {
const playingUri = selectPlayingUri(state); const playingUri = selectPlayingUri(state);
@ -24,7 +26,7 @@ const select = (state, props) => {
collection: makeSelectCollectionForId(props.id)(state), collection: makeSelectCollectionForId(props.id)(state),
collectionUrls: makeSelectUrlsForCollectionId(props.id)(state), collectionUrls: makeSelectUrlsForCollectionId(props.id)(state),
collectionName: makeSelectNameForCollectionId(props.id)(state), collectionName: makeSelectNameForCollectionId(props.id)(state),
isMine: selectClaimIsMine(state, claim), isMyCollection: makeSelectCollectionIsMine(props.id)(state),
loop, loop,
shuffle, shuffle,
}; };
@ -33,4 +35,5 @@ const select = (state, props) => {
export default connect(select, { export default connect(select, {
doToggleLoopList, doToggleLoopList,
doToggleShuffleList, doToggleShuffleList,
doCollectionEdit,
})(CollectionContent); })(CollectionContent);

View file

@ -1,5 +1,10 @@
// @flow // @flow
// $FlowFixMe
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import React from 'react'; import React from 'react';
import classnames from 'classnames';
import ClaimList from 'component/claimList'; import ClaimList from 'component/claimList';
import Card from 'component/common/card'; import Card from 'component/common/card';
import Button from 'component/button'; import Button from 'component/button';
@ -11,7 +16,7 @@ import * as ICONS from 'constants/icons';
type Props = { type Props = {
id: string, id: string,
url: string, url: string,
isMine: boolean, isMyCollection: boolean,
collectionUrls: Array<Claim>, collectionUrls: Array<Claim>,
collectionName: string, collectionName: string,
collection: any, collection: any,
@ -20,10 +25,35 @@ type Props = {
doToggleLoopList: (string, boolean) => void, doToggleLoopList: (string, boolean) => void,
doToggleShuffleList: (string, string, boolean) => void, doToggleShuffleList: (string, string, boolean) => void,
createUnpublishedCollection: (string, Array<any>, ?string) => void, createUnpublishedCollection: (string, Array<any>, ?string) => void,
doCollectionEdit: (string, CollectionEditParams) => void,
}; };
export default function CollectionContent(props: Props) { export default function CollectionContent(props: Props) {
const { collectionUrls, collectionName, id, url, loop, shuffle, doToggleLoopList, doToggleShuffleList } = props; const {
isMyCollection,
collectionUrls,
collectionName,
id,
url,
loop,
shuffle,
doToggleLoopList,
doToggleShuffleList,
doCollectionEdit,
} = props;
const [showEdit, setShowEdit] = React.useState(false);
function handleOnDragEnd(result) {
const { source, destination } = result;
if (!destination) return;
const { index: from } = source;
const { index: to } = destination;
doCollectionEdit(id, { order: { from, to } });
}
return ( return (
<Card <Card
@ -63,12 +93,26 @@ export default function CollectionContent(props: Props) {
</> </>
} }
titleActions={ titleActions={
<>
<div className="card__title-actions--link"> <div className="card__title-actions--link">
{/* TODO: BUTTON TO SAVE COLLECTION - Probably save/copy modal */} {/* TODO: BUTTON TO SAVE COLLECTION - Probably save/copy modal */}
<Button label={__('View List')} button="link" navigate={`/$/${PAGES.LIST}/${id}`} /> <Button label={__('View List')} button="link" navigate={`/$/${PAGES.LIST}/${id}`} />
</div> </div>
{isMyCollection && (
<Button
title={__('Edit')}
className={classnames('button-toggle', { 'button-toggle--active': showEdit })}
icon={ICONS.EDIT}
onClick={() => setShowEdit(!showEdit)}
/>
)}
</>
} }
body={ body={
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="list__ordering">
{(DroppableProvided) => (
<ClaimList <ClaimList
isCardBody isCardBody
type="small" type="small"
@ -76,7 +120,12 @@ export default function CollectionContent(props: Props) {
uris={collectionUrls} uris={collectionUrls}
collectionId={id} collectionId={id}
empty={__('List is Empty')} empty={__('List is Empty')}
showEdit={showEdit}
droppableProvided={DroppableProvided}
/> />
)}
</Droppable>
</DragDropContext>
} }
/> />
); );

View file

@ -22,6 +22,7 @@ import * as ACTIONS from 'constants/action_types';
import CollectionForm from './view'; import CollectionForm from './view';
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app'; import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
import { doSetActiveChannel, doSetIncognito } from 'redux/actions/app'; import { doSetActiveChannel, doSetIncognito } from 'redux/actions/app';
import { doCollectionEdit } from 'redux/actions/collections';
const select = (state, props) => ({ const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),
@ -44,12 +45,13 @@ const select = (state, props) => ({
collectionClaimIds: makeSelectClaimIdsForCollectionId(props.collectionId)(state), collectionClaimIds: makeSelectClaimIdsForCollectionId(props.collectionId)(state),
}); });
const perform = (dispatch) => ({ const perform = (dispatch, ownProps) => ({
publishCollectionUpdate: (params) => dispatch(doCollectionPublishUpdate(params)), publishCollectionUpdate: (params) => dispatch(doCollectionPublishUpdate(params)),
publishCollection: (params, collectionId) => dispatch(doCollectionPublish(params, collectionId)), publishCollection: (params, collectionId) => dispatch(doCollectionPublish(params, collectionId)),
clearCollectionErrors: () => dispatch({ type: ACTIONS.CLEAR_COLLECTION_ERRORS }), clearCollectionErrors: () => dispatch({ type: ACTIONS.CLEAR_COLLECTION_ERRORS }),
setActiveChannel: (claimId) => dispatch(doSetActiveChannel(claimId)), setActiveChannel: (claimId) => dispatch(doSetActiveChannel(claimId)),
setIncognito: (incognito) => dispatch(doSetIncognito(incognito)), setIncognito: (incognito) => dispatch(doSetIncognito(incognito)),
doCollectionEdit: (params) => dispatch(doCollectionEdit(ownProps.collectionId, params)),
}); });
export default connect(select, perform)(CollectionForm); export default connect(select, perform)(CollectionForm);

View file

@ -1,4 +1,7 @@
// @flow // @flow
// $FlowFixMe
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import Button from 'component/button'; import Button from 'component/button';
@ -54,6 +57,7 @@ type Props = {
onDone: (string) => void, onDone: (string) => void,
setActiveChannel: (string) => void, setActiveChannel: (string) => void,
setIncognito: (boolean) => void, setIncognito: (boolean) => void,
doCollectionEdit: (CollectionEditParams) => void,
}; };
function CollectionForm(props: Props) { function CollectionForm(props: Props) {
@ -87,6 +91,7 @@ function CollectionForm(props: Props) {
setActiveChannel, setActiveChannel,
setIncognito, setIncognito,
onDone, onDone,
doCollectionEdit,
} = props; } = props;
const activeChannelName = activeChannelClaim && activeChannelClaim.name; const activeChannelName = activeChannelClaim && activeChannelClaim.name;
let prefix = 'lbry://'; let prefix = 'lbry://';
@ -196,6 +201,17 @@ function CollectionForm(props: Props) {
return collectionParams; return collectionParams;
} }
function handleOnDragEnd(result) {
const { source, destination } = result;
if (!destination) return;
const { index: from } = source;
const { index: to } = destination;
doCollectionEdit({ order: { from, to } });
}
function handleLanguageChange(index, code) { function handleLanguageChange(index, code) {
let langs = [...languageParam]; let langs = [...languageParam];
if (index === 0) { if (index === 0) {
@ -286,7 +302,6 @@ function CollectionForm(props: Props) {
} }
}, [uri, hasClaim]); }, [uri, hasClaim]);
console.log('params', params);
return ( return (
<> <>
<div className={classnames('main--contained', { 'card--disabled': disabled })}> <div className={classnames('main--contained', { 'card--disabled': disabled })}>
@ -357,12 +372,19 @@ function CollectionForm(props: Props) {
</div> </div>
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="list__ordering">
{(DroppableProvided) => (
<ClaimList <ClaimList
uris={collectionUrls} uris={collectionUrls}
collectionId={collectionId} collectionId={collectionId}
empty={__('This list has no items.')} empty={__('This list has no items.')}
type={'listview'} showEdit
droppableProvided={DroppableProvided}
/> />
)}
</Droppable>
</DragDropContext>
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<Card <Card

View file

@ -0,0 +1,23 @@
import { connect } from 'react-redux';
import { doCollectionEdit } from 'redux/actions/collections';
import { makeSelectIndexForUrlInCollection, makeSelectUrlsForCollectionId } from 'redux/selectors/collections';
import CollectionButtons from './view';
const select = (state, props) => {
const { uri, collectionId } = props;
return {
collectionIndex: makeSelectIndexForUrlInCollection(uri, collectionId, true)(state),
collectionUris: makeSelectUrlsForCollectionId(collectionId)(state),
};
};
const perform = (dispatch, ownProps) => {
const { collectionId } = ownProps;
return {
editCollection: (params) => dispatch(doCollectionEdit(collectionId, params)),
};
};
export default connect(select, perform)(CollectionButtons);

View file

@ -0,0 +1,92 @@
// @flow
import * as ICONS from 'constants/icons';
import Button from 'component/button';
import Icon from 'component/common/icon';
import React from 'react';
type Props = {
collectionIndex?: number,
collectionUris: Array<Collection>,
dragHandleProps?: any,
uri: string,
editCollection: (CollectionEditParams) => void,
};
export default function CollectionButtons(props: Props) {
const { collectionIndex: foundIndex, collectionUris, dragHandleProps, uri, editCollection } = props;
const [confirmDelete, setConfirmDelete] = React.useState(false);
const lastCollectionIndex = collectionUris ? collectionUris.length - 1 : 0;
const collectionIndex = Number(foundIndex);
const orderButton = (className: string, title: string, icon: string, disabled: boolean, handleClick?: () => void) => (
<Button
className={`button-collection-manage ${className}`}
icon={icon}
title={title}
disabled={disabled}
onClick={() => handleClick && handleClick()}
/>
);
return (
<div
className="collection-preview__edit-buttons"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<div className="collection-preview__edit-group" {...dragHandleProps}>
<div className="button-collection-manage button-collection-drag top-left bottom-left">
<Icon icon={ICONS.DRAG} title={__('Drag')} size={20} />
</div>
</div>
<div className="collection-preview__edit-group">
{orderButton('', __('Move Top'), ICONS.UP_TOP, collectionIndex === 0, () =>
editCollection({ order: { from: collectionIndex, to: 0 } })
)}
{orderButton('', __('Move Bottom'), ICONS.DOWN_BOTTOM, collectionIndex === lastCollectionIndex, () =>
editCollection({ order: { from: collectionIndex, to: lastCollectionIndex } })
)}
</div>
<div className="collection-preview__edit-group">
{orderButton('', __('Move Up'), ICONS.UP, collectionIndex === 0, () =>
editCollection({ order: { from: collectionIndex, to: collectionIndex - 1 } })
)}
{orderButton('', __('Move Down'), ICONS.DOWN, collectionIndex === lastCollectionIndex, () =>
editCollection({ order: { from: collectionIndex, to: collectionIndex + 1 } })
)}
</div>
{!confirmDelete ? (
<div className="collection-preview__edit-group collection-preview__delete ">
<Button
className="button-collection-manage button-collection-delete top-right bottom-right"
icon={ICONS.DELETE}
title={__('Remove')}
onClick={() => setConfirmDelete(true)}
/>
</div>
) : (
<div className="collection-preview__edit-group collection-preview__delete">
<Button
className="button-collection-manage button-collection-delete-cancel top-right"
icon={ICONS.REMOVE}
title={__('Cancel')}
onClick={() => setConfirmDelete(false)}
/>
{orderButton('button-collection-delete-confirm bottom-right', __('Remove'), ICONS.DELETE, false, () =>
editCollection({ uris: [uri], remove: true })
)}
</div>
)}
</div>
);
}

View file

@ -5,10 +5,12 @@ import { doCollectionEdit } from 'redux/actions/collections';
import CollectionSelectItem from './view'; import CollectionSelectItem from './view';
const select = (state, props) => { const select = (state, props) => {
const { collectionId, uri } = props;
return { return {
collection: makeSelectCollectionForId(props.collectionId)(state), collection: makeSelectCollectionForId(collectionId)(state),
hasClaim: makeSelectCollectionForIdHasClaimUrl(props.collectionId, props.uri)(state), hasClaim: makeSelectCollectionForIdHasClaimUrl(collectionId, uri)(state),
collectionPending: makeSelectClaimIsPending(props.collectionId)(state), collectionPending: makeSelectClaimIsPending(collectionId)(state),
}; };
}; };

View file

@ -11,15 +11,15 @@ type Props = {
category: string, category: string,
edited: boolean, edited: boolean,
editCollection: (string, CollectionEditParams) => void, editCollection: (string, CollectionEditParams) => void,
claim: Claim, uri: string,
collectionPending: Collection, collectionPending: Collection,
}; };
function CollectionSelectItem(props: Props) { function CollectionSelectItem(props: Props) {
const { collection, hasClaim, category, editCollection, claim, collectionPending } = props; const { collection, hasClaim, category, editCollection, uri, collectionPending } = props;
const { name, id } = collection; const { name, id } = collection;
const handleChange = (e) => { const handleChange = (e) => {
editCollection(id, { claims: [claim], remove: hasClaim }); editCollection(id, { uris: [uri], remove: hasClaim });
}; };
let icon; let icon;

View file

@ -382,21 +382,10 @@ export const icons = {
[ICONS.NO]: buildIcon( [ICONS.NO]: buildIcon(
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" /> <path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" />
), ),
[ICONS.UP]: buildIcon( [ICONS.UP]: buildIcon(<polyline transform="matrix(1,0,0,-1,0,24.707107)" points="6 9 12 15 18 9" />),
<g> [ICONS.UP_TOP]: buildIcon(<path d="m6 16 6-6 6 6M6 8h12" />),
<polyline transform="matrix(1,0,0,-1,0,24.707107)" points="6 9 12 15 18 9" /> [ICONS.DOWN_BOTTOM]: buildIcon(<path d="m6 8 6 6 6-6M6 16h12" />),
</g> [ICONS.DRAG]: buildIcon(<path d="m8 18 4 4 4-4M4 14h16M4 10h16M8 6l4-4 4 4" />),
),
[ICONS.UP_TOP]: buildIcon(
<g>
<path d="m6 16 6-6 6 6M6 8h12" />
</g>
),
[ICONS.DOWN_BOTTOM]: buildIcon(
<g>
<path d="m6 8 6 6 6-6M6 16h12" />
</g>
),
[ICONS.DOWN]: buildIcon(<polyline points="6 9 12 15 18 9" />), [ICONS.DOWN]: buildIcon(<polyline points="6 9 12 15 18 9" />),
[ICONS.FULLSCREEN]: buildIcon( [ICONS.FULLSCREEN]: buildIcon(
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" /> <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" />

View file

@ -1,18 +1,15 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doCollectionEdit } from 'redux/actions/collections'; import { doCollectionEdit } from 'redux/actions/collections';
import { makeSelectCollectionForIdHasClaimUrl } from 'redux/selectors/collections'; import { makeSelectCollectionForIdHasClaimUrl } from 'redux/selectors/collections';
import { makeSelectClaimForUri } from 'redux/selectors/claims';
import * as COLLECTIONS_CONSTS from 'constants/collections'; import * as COLLECTIONS_CONSTS from 'constants/collections';
import FileWatchLaterLink from './view'; import FileWatchLaterLink from './view';
import { doToast } from 'redux/actions/notifications'; import { doToast } from 'redux/actions/notifications';
const select = (state, props) => { const select = (state, props) => {
const claim = makeSelectClaimForUri(props.uri)(state); const { uri } = props;
const permanentUri = claim && claim.permanent_url;
return { return {
claim, hasClaimInWatchLater: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.WATCH_LATER_ID, uri)(state),
hasClaimInWatchLater: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.WATCH_LATER_ID, permanentUri)(state),
}; };
}; };

View file

@ -7,7 +7,6 @@ import * as COLLECTIONS_CONSTS from 'constants/collections';
type Props = { type Props = {
uri: string, uri: string,
claim: StreamClaim,
focusable: boolean, focusable: boolean,
hasClaimInWatchLater: boolean, hasClaimInWatchLater: boolean,
doToast: ({ message: string }) => void, doToast: ({ message: string }) => void,
@ -15,14 +14,10 @@ type Props = {
}; };
function FileWatchLaterLink(props: Props) { function FileWatchLaterLink(props: Props) {
const { claim, hasClaimInWatchLater, doToast, doCollectionEdit, focusable = true } = props; const { uri, hasClaimInWatchLater, doToast, doCollectionEdit, focusable = true } = props;
const buttonRef = useRef(); const buttonRef = useRef();
let isHovering = useHover(buttonRef); let isHovering = useHover(buttonRef);
if (!claim) {
return null;
}
function handleWatchLater(e) { function handleWatchLater(e) {
e.preventDefault(); e.preventDefault();
doToast({ doToast({
@ -31,7 +26,7 @@ function FileWatchLaterLink(props: Props) {
linkTarget: !hasClaimInWatchLater && '/list/watchlater', linkTarget: !hasClaimInWatchLater && '/list/watchlater',
}); });
doCollectionEdit(COLLECTIONS_CONSTS.WATCH_LATER_ID, { doCollectionEdit(COLLECTIONS_CONSTS.WATCH_LATER_ID, {
claims: [claim], uris: [uri],
remove: hasClaimInWatchLater, remove: hasClaimInWatchLater,
type: 'playlist', type: 'playlist',
}); });

View file

@ -8,6 +8,7 @@ import { LINKED_COMMENT_QUERY_PARAM } from 'constants/comment';
import { parseURI, isURIValid } from 'util/lbryURI'; import { parseURI, isURIValid } from 'util/lbryURI';
import { WELCOME_VERSION } from 'config'; import { WELCOME_VERSION } from 'config';
import { GetLinksData } from 'util/buildHomepage'; import { GetLinksData } from 'util/buildHomepage';
import { useIsLargeScreen } from 'effects/use-screensize';
import HomePage from 'page/home'; import HomePage from 'page/home';
import BackupPage from 'page/backup'; import BackupPage from 'page/backup';
@ -125,8 +126,8 @@ function AppRouter(props: Props) {
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
const resetScroll = urlParams.get('reset_scroll'); const resetScroll = urlParams.get('reset_scroll');
const hasLinkedCommentInUrl = urlParams.get(LINKED_COMMENT_QUERY_PARAM); const hasLinkedCommentInUrl = urlParams.get(LINKED_COMMENT_QUERY_PARAM);
const isLargeScreen = useIsLargeScreen();
const dynamicRoutes = GetLinksData(homepageData).filter( const dynamicRoutes = GetLinksData(homepageData, isLargeScreen).filter(
(potentialRoute: any) => potentialRoute && potentialRoute.route (potentialRoute: any) => potentialRoute && potentialRoute.route
); );

View file

@ -2,7 +2,7 @@
// https://github.com/carmelopullara/react-feather // https://github.com/carmelopullara/react-feather
// Note: Icons should be named for their purpose, rather than the actual icon. // Note: Icons should be named for their purpose, rather than the actual icon.
// The goal being to reduce multiple uses of the same icon for different purposes. // The goal being to reduce multiple uses of the same icon for different purposes.
//
export const REWARDS = 'Award'; export const REWARDS = 'Award';
export const LOCAL = 'Folder'; export const LOCAL = 'Folder';
export const ALERT = 'AlertCircle'; export const ALERT = 'AlertCircle';
@ -68,6 +68,7 @@ export const UP = 'ChevronUp';
export const UP_TOP = 'ChevronUpTop'; export const UP_TOP = 'ChevronUpTop';
export const DOWN = 'ChevronDown'; export const DOWN = 'ChevronDown';
export const DOWN_BOTTOM = 'ChevronDownBottom'; export const DOWN_BOTTOM = 'ChevronDownBottom';
export const DRAG = 'DragAndDrop';
export const SECURE = 'Lock'; export const SECURE = 'Lock';
export const MENU = 'Menu'; export const MENU = 'Menu';
export const BACKUP = 'Database'; export const BACKUP = 'Database';

View file

@ -1,4 +1,8 @@
// @flow // @flow
// $FlowFixMe
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import React from 'react'; import React from 'react';
import ClaimList from 'component/claimList'; import ClaimList from 'component/claimList';
import Page from 'component/page'; import Page from 'component/page';
@ -51,6 +55,7 @@ export default function CollectionPage(props: Props) {
collectionHasEdits, collectionHasEdits,
claimIsPending, claimIsPending,
isResolvingCollection, isResolvingCollection,
editCollection,
fetchCollectionItems, fetchCollectionItems,
deleteCollection, deleteCollection,
} = props; } = props;
@ -63,9 +68,22 @@ export default function CollectionPage(props: Props) {
const [didTryResolve, setDidTryResolve] = React.useState(false); const [didTryResolve, setDidTryResolve] = React.useState(false);
const [showInfo, setShowInfo] = React.useState(false); const [showInfo, setShowInfo] = React.useState(false);
const [showEdit, setShowEdit] = React.useState(false); const [showEdit, setShowEdit] = React.useState(false);
const [unavailableUris, setUnavailable] = React.useState([]);
const { name, totalItems } = collection || {}; const { name, totalItems } = collection || {};
const isBuiltin = COLLECTIONS_CONSTS.BUILTIN_LISTS.includes(collectionId); const isBuiltin = COLLECTIONS_CONSTS.BUILTIN_LISTS.includes(collectionId);
function handleOnDragEnd(result) {
const { source, destination } = result;
if (!destination) return;
const { index: from } = source;
const { index: to } = destination;
editCollection(collectionId, { order: { from, to } });
}
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
const editing = urlParams.get(PAGE_VIEW_QUERY) === EDIT_PAGE; const editing = urlParams.get(PAGE_VIEW_QUERY) === EDIT_PAGE;
@ -94,6 +112,18 @@ export default function CollectionPage(props: Props) {
/> />
); );
const removeUnavailable = (
<Button
button="close"
icon={ICONS.DELETE}
label={__('Remove all unavailable claims')}
onClick={() => {
editCollection(collectionId, { uris: unavailableUris, remove: true });
setUnavailable([]);
}}
/>
);
let titleActions; let titleActions;
if (collectionHasEdits) { if (collectionHasEdits) {
titleActions = unpublished; titleActions = unpublished;
@ -124,7 +154,7 @@ export default function CollectionPage(props: Props) {
{claim ? claim.value.title || claim.name : collection && collection.name} {claim ? claim.value.title || claim.name : collection && collection.name}
</span> </span>
} }
titleActions={titleActions} titleActions={unavailableUris.length > 0 ? removeUnavailable : titleActions}
subtitle={subTitle} subtitle={subTitle}
body={ body={
<CollectionActions <CollectionActions
@ -191,7 +221,20 @@ export default function CollectionPage(props: Props) {
<Page> <Page>
<div className={classnames('section card-stack')}> <div className={classnames('section card-stack')}>
{info} {info}
<ClaimList uris={collectionUrls} collectionId={collectionId} type={showEdit ? 'listview' : undefined} />
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="list__ordering">
{(DroppableProvided) => (
<ClaimList
uris={collectionUrls}
collectionId={collectionId}
showEdit={showEdit}
droppableProvided={DroppableProvided}
unavailableUris={unavailableUris}
/>
)}
</Droppable>
</DragDropContext>
</div> </div>
</Page> </Page>
); );

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import { SITE_NAME, ENABLE_NO_SOURCE_CLAIMS } from 'config'; import { SITE_NAME } from 'config';
import React from 'react'; import React from 'react';
import Page from 'component/page'; import Page from 'component/page';
import Button from 'component/button'; import Button from 'component/button';
@ -9,6 +9,7 @@ import ClaimTilesDiscover from 'component/claimTilesDiscover';
import ClaimPreviewTile from 'component/claimPreviewTile'; import ClaimPreviewTile from 'component/claimPreviewTile';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import WaitUntilOnPage from 'component/common/wait-until-on-page'; import WaitUntilOnPage from 'component/common/wait-until-on-page';
import { useIsLargeScreen } from 'effects/use-screensize'; // have this?
import { GetLinksData } from 'util/buildHomepage'; import { GetLinksData } from 'util/buildHomepage';
type Props = { type Props = {
@ -24,9 +25,11 @@ function HomePage(props: Props) {
const showPersonalizedChannels = subscribedChannels && subscribedChannels.length > 0; const showPersonalizedChannels = subscribedChannels && subscribedChannels.length > 0;
const showPersonalizedTags = followedTags && followedTags.length > 0; const showPersonalizedTags = followedTags && followedTags.length > 0;
const showIndividualTags = showPersonalizedTags && followedTags.length < 5; const showIndividualTags = showPersonalizedTags && followedTags.length < 5;
const isLargeScreen = useIsLargeScreen();
const rowData: Array<RowDataItem> = GetLinksData( const rowData: Array<RowDataItem> = GetLinksData(
homepageData, homepageData,
isLargeScreen,
true, true,
authenticated, authenticated,
showPersonalizedChannels, showPersonalizedChannels,
@ -37,29 +40,40 @@ function HomePage(props: Props) {
showNsfw showNsfw
); );
function getRowElements(title, route, link, icon, help, options, index, pinUrls) { type SectionHeaderProps = {
const tilePlaceholder = ( title: string,
<ul className="claim-grid"> navigate?: string,
{new Array(options.pageSize || 8).fill(1).map((x, i) => ( icon?: string,
<ClaimPreviewTile showNoSourceClaims={ENABLE_NO_SOURCE_CLAIMS} key={i} placeholder /> help?: string,
))} };
</ul> const SectionHeader = ({ title, navigate = '/', icon = '', help }: SectionHeaderProps) => {
);
const claimTiles = (
<ClaimTilesDiscover {...options} showNoSourceClaims={ENABLE_NO_SOURCE_CLAIMS} hasSource pinUrls={pinUrls} />
);
return ( return (
<div key={title} className="claim-grid__wrapper">
{index !== 0 && title && typeof title === 'string' && (
<h1 className="claim-grid__header"> <h1 className="claim-grid__header">
<Button navigate={route || link} button="link"> <Button navigate={navigate} button="link">
{icon && <Icon className="claim-grid__header-icon" sectionIcon icon={icon} size={20} />} <Icon className="claim-grid__header-icon" sectionIcon icon={icon} size={20} />
<span className="claim-grid__title">{title}</span> <span className="claim-grid__title">{title}</span>
{help} {help}
</Button> </Button>
</h1> </h1>
);
};
function getRowElements(title, route, link, icon, help, options, index, pinUrls) {
const tilePlaceholder = (
<ul className="claim-grid">
{new Array(options.pageSize || 8).fill(1).map((x, i) => (
<ClaimPreviewTile key={i} placeholder />
))}
</ul>
);
const claimTiles = <ClaimTilesDiscover {...options} hasSource pinUrls={pinUrls} />;
return (
<div key={title} className="claim-grid__wrapper">
{/* category header */}
{index !== 0 && title && typeof title === 'string' && (
<SectionHeader title={__(title)} navigate={route || link} icon={icon} help={help} />
)} )}
{index === 0 && <>{claimTiles}</>} {index === 0 && <>{claimTiles}</>}
@ -69,6 +83,7 @@ function HomePage(props: Props) {
</WaitUntilOnPage> </WaitUntilOnPage>
)} )}
{/* view more button */}
{(route || link) && ( {(route || link) && (
<Button <Button
className="claim-grid__title--secondary" className="claim-grid__title--secondary"

View file

@ -318,160 +318,49 @@ export const doCollectionEdit = (collectionId: string, params: CollectionEditPar
) => { ) => {
const state = getState(); const state = getState();
const collection: Collection = makeSelectCollectionForId(collectionId)(state); const collection: Collection = makeSelectCollectionForId(collectionId)(state);
if (!collection) return dispatch({ type: ACTIONS.COLLECTION_ERROR, data: { message: 'collection does not exist' } });
const editedCollection: Collection = makeSelectEditedCollectionForId(collectionId)(state); const editedCollection: Collection = makeSelectEditedCollectionForId(collectionId)(state);
const unpublishedCollection: Collection = makeSelectUnpublishedCollectionForId(collectionId)(state); const unpublishedCollection: Collection = makeSelectUnpublishedCollectionForId(collectionId)(state);
const publishedCollection: Collection = makeSelectPublishedCollectionForId(collectionId)(state); // needs to be published only const publishedCollection: Collection = makeSelectPublishedCollectionForId(collectionId)(state); // needs to be published only
const generateCollectionItemsFromSearchResult = (results) => { const { uris, order, remove, type } = params;
return (
Object.values(results)
// $FlowFixMe
.reduce(
(
acc,
cur: {
stream: ?StreamClaim,
channel: ?ChannelClaim,
claimsInChannel: ?number,
collection: ?CollectionClaim,
}
) => {
let url;
if (cur.stream) {
url = cur.stream.permanent_url;
} else if (cur.channel) {
url = cur.channel.permanent_url;
} else if (cur.collection) {
url = cur.collection.permanent_url;
} else {
return acc;
}
acc.push(url);
return acc;
},
[]
)
);
};
if (!collection) {
return dispatch({
type: ACTIONS.COLLECTION_ERROR,
data: {
message: 'collection does not exist',
},
});
}
let currentItems = collection.items ? collection.items.concat() : [];
const { claims: passedClaims, order, claimIds, replace, remove, type } = params;
const collectionType = type || collection.type; const collectionType = type || collection.type;
let newItems: Array<?string> = currentItems; const currentUrls = collection.items ? collection.items.concat() : [];
let newItems = currentUrls;
if (passedClaims) { // Passed uris to add/remove:
if (uris) {
if (remove) { if (remove) {
const passedUrls = passedClaims.map((claim) => claim.permanent_url); // Filters (removes) the passed uris from the current list items
// $FlowFixMe // need this? newItems = currentUrls.filter((url) => url && !uris.includes(url));
newItems = currentItems.filter((item: string) => !passedUrls.includes(item));
} else { } else {
passedClaims.forEach((claim) => newItems.push(claim.permanent_url)); // Pushes (adds to the end) the passed uris to the current list items
} uris.forEach((url) => newItems.push(url));
}
if (claimIds) {
const batches = [];
if (claimIds.length > 50) {
for (let i = 0; i < Math.ceil(claimIds.length / 50); i++) {
batches[i] = claimIds.slice(i * 50, (i + 1) * 50);
}
} else {
batches[0] = claimIds;
}
const resultArray = await Promise.all(
batches.map((batch) => {
let options = { claim_ids: batch, page: 1, page_size: 50 };
return dispatch(doClaimSearch(options));
})
);
const searchResults = Object.assign({}, ...resultArray);
if (replace) {
newItems = generateCollectionItemsFromSearchResult(searchResults);
} else {
newItems = currentItems.concat(generateCollectionItemsFromSearchResult(searchResults));
} }
} }
// Passed an ordering to change: (doesn't need the uris here since
// the items are already on the list)
if (order) { if (order) {
const [movedItem] = currentItems.splice(order.from, 1); const [movedItem] = currentUrls.splice(order.from, 1);
currentItems.splice(order.to, 0, movedItem); currentUrls.splice(order.to, 0, movedItem);
} }
// console.log('p&e', publishedCollection.items, newItems, publishedCollection.items.join(','), newItems.join(',')) // Delete 'edited' if newItems are the same as publishedItems
if (editedCollection) { if (editedCollection && newItems && publishedCollection.items.join(',') === newItems.join(',')) {
// delete edited if newItems are the same as publishedItems dispatch({ type: ACTIONS.COLLECTION_DELETE, data: { id: collectionId, collectionKey: 'edited' } });
if (publishedCollection.items.join(',') === newItems.join(',')) {
dispatch({
type: ACTIONS.COLLECTION_DELETE,
data: {
id: collectionId,
collectionKey: 'edited',
},
});
} else { } else {
dispatch({ dispatch({
type: ACTIONS.COLLECTION_EDIT, type: ACTIONS.COLLECTION_EDIT,
data: { data: {
id: collectionId, id: collectionId,
collectionKey: 'edited', collectionKey:
collection: { ((editedCollection || publishedCollection) && 'edited') ||
items: newItems, (COLS.BUILTIN_LISTS.includes(collectionId) && 'builtin') ||
id: collectionId, (unpublishedCollection && 'unpublished'),
name: params.name || collection.name,
updatedAt: getTimestamp(),
type: collectionType,
},
},
});
}
} else if (publishedCollection) {
dispatch({
type: ACTIONS.COLLECTION_EDIT,
data: {
id: collectionId,
collectionKey: 'edited',
collection: {
items: newItems,
id: collectionId,
name: params.name || collection.name,
updatedAt: getTimestamp(),
type: collectionType,
},
},
});
} else if (COLS.BUILTIN_LISTS.includes(collectionId)) {
dispatch({
type: ACTIONS.COLLECTION_EDIT,
data: {
id: collectionId,
collectionKey: 'builtin',
collection: {
items: newItems,
id: collectionId,
name: params.name || collection.name,
updatedAt: getTimestamp(),
type: collectionType,
},
},
});
} else if (unpublishedCollection) {
dispatch({
type: ACTIONS.COLLECTION_EDIT,
data: {
id: collectionId,
collectionKey: 'unpublished',
collection: { collection: {
items: newItems, items: newItems,
id: collectionId, id: collectionId,
@ -482,5 +371,6 @@ export const doCollectionEdit = (collectionId: string, params: CollectionEditPar
}, },
}); });
} }
return true; return true;
}; };

View file

@ -156,13 +156,13 @@ export const makeSelectClaimIdsForCollectionId = (id: string) =>
return ids; return ids;
}); });
export const makeSelectIndexForUrlInCollection = (url: string, id: string) => export const makeSelectIndexForUrlInCollection = (url: string, id: string, ignoreShuffle?: boolean) =>
createSelector( createSelector(
(state) => state.content.shuffleList, (state) => state.content.shuffleList,
makeSelectUrlsForCollectionId(id), makeSelectUrlsForCollectionId(id),
makeSelectClaimForUri(url), makeSelectClaimForUri(url),
(shuffleState, urls, claim) => { (shuffleState, urls, claim) => {
const shuffleUrls = shuffleState && shuffleState.collectionId === id && shuffleState.newUrls; const shuffleUrls = !ignoreShuffle && shuffleState && shuffleState.collectionId === id && shuffleState.newUrls;
const listUrls = shuffleUrls || urls; const listUrls = shuffleUrls || urls;
const index = listUrls && listUrls.findIndex((u) => u === url); const index = listUrls && listUrls.findIndex((u) => u === url);

View file

@ -66,3 +66,5 @@
@import 'component/empty'; @import 'component/empty';
@import 'component/stripe-card'; @import 'component/stripe-card';
@import 'component/wallet-tip-send'; @import 'component/wallet-tip-send';
@import 'component/swipe-list';
@import 'component/utils';

View file

@ -120,7 +120,7 @@
order: 1; order: 1;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='14' viewBox='0 -2 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-monitor'%3E%3Crect x='2' y='3' width='20' height='14' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='8' y1='21' x2='16' y2='21'%3E%3C/line%3E%3Cline x1='12' y1='17' x2='12' y2='21'%3E%3C/line%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='19' height='19' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-monitor'%3E%3Crect x='2' y='3' width='20' height='14' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='8' y1='21' x2='16' y2='21'%3E%3C/line%3E%3Cline x1='12' y1='17' x2='12' y2='21'%3E%3C/line%3E%3C/svg%3E");
} }
&:focus:not(:focus-visible) { &:focus:not(:focus-visible) {
@ -128,7 +128,7 @@
// see: https://github.com/lbryio/lbry-desktop/pull/5549#discussion_r580406932 // see: https://github.com/lbryio/lbry-desktop/pull/5549#discussion_r580406932
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='14' viewBox='0 -2 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-monitor'%3E%3Crect x='2' y='3' width='20' height='14' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='8' y1='21' x2='16' y2='21'%3E%3C/line%3E%3Cline x1='12' y1='17' x2='12' y2='21'%3E%3C/line%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='19' height='19' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-monitor'%3E%3Crect x='2' y='3' width='20' height='14' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='8' y1='21' x2='16' y2='21'%3E%3C/line%3E%3Cline x1='12' y1='17' x2='12' y2='21'%3E%3C/line%3E%3C/svg%3E");
} }
} }
@ -148,26 +148,35 @@
.vjs-button--play-next.vjs-button { .vjs-button--play-next.vjs-button {
order: 0; order: 0;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='18' height='14' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-skip-forward'%3e%3cpolygon points='5 4 15 12 5 20 5 4'%3e%3c/polygon%3e%3cline x1='19' y1='5' x2='19' y2='19'%3e%3c/line%3e%3c/svg%3e"); background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='19' height='19' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-skip-forward'%3e%3cpolygon points='5 4 15 12 5 20 5 4'%3e%3c/polygon%3e%3cline x1='19' y1='5' x2='19' y2='19'%3e%3c/line%3e%3c/svg%3e");
&:focus:not(:focus-visible) { &:focus:not(:focus-visible) {
order: 0; order: 0;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='18' height='14' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-skip-forward'%3e%3cpolygon points='5 4 15 12 5 20 5 4'%3e%3c/polygon%3e%3cline x1='19' y1='5' x2='19' y2='19'%3e%3c/line%3e%3c/svg%3e"); background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='19' height='19' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-skip-forward'%3e%3cpolygon points='5 4 15 12 5 20 5 4'%3e%3c/polygon%3e%3cline x1='19' y1='5' x2='19' y2='19'%3e%3c/line%3e%3c/svg%3e");
} }
} }
.vjs-button--play-previous.vjs-button { .vjs-button--play-previous.vjs-button {
order: 0; order: 0;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='18' height='14' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-skip-back'%3e%3cpolygon points='19 20 9 12 19 4 19 20'%3e%3c/polygon%3e%3cline x1='5' y1='19' x2='5' y2='5'%3e%3c/line%3e%3c/svg%3e"); background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='19' height='19' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-skip-back'%3e%3cpolygon points='19 20 9 12 19 4 19 20'%3e%3c/polygon%3e%3cline x1='5' y1='19' x2='5' y2='5'%3e%3c/line%3e%3c/svg%3e");
&:focus:not(:focus-visible) { &:focus:not(:focus-visible) {
order: 0; order: 0;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='18' height='14' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round' class='feather feather-skip-back'%3e%3cpolygon points='19 20 9 12 19 4 19 20'%3e%3c/polygon%3e%3cline x1='5' y1='19' x2='5' y2='5'%3e%3c/line%3e%3c/svg%3e"); background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='19' height='19' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-skip-back'%3e%3cpolygon points='19 20 9 12 19 4 19 20'%3e%3c/polygon%3e%3cline x1='5' y1='19' x2='5' y2='5'%3e%3c/line%3e%3c/svg%3e");
} }
} }
.vjs-button--autoplay-next.vjs-button { .vjs-button--autoplay-next.vjs-button {
margin: auto; // TODO: the width and height of a vjs-button should probably retain
// its default "100%" value, so that each control-bar button have the same
// touch area size. Anything inside (like this on/off switch, or icons)
// should be a child (e.g. vjs-icon-placeholder), so that inner margins will
// also be consistent.
//
// TEMP: for now, just hardcode the left-right margin so that it looks
// equally spaced compared to its siblings.
margin: auto 0.5rem auto 0.7rem;
order: 1; order: 1;
width: 24px; width: 24px;
height: 14px; height: 14px;
@ -616,16 +625,17 @@ svg + .button__label {
background-color: var(--color-button-alt-bg); background-color: var(--color-button-alt-bg);
color: var(--color-button-alt-text); color: var(--color-button-alt-text);
text-align: center; text-align: center;
&:hover { &:hover {
background-color: var(--color-button-alt-bg-hover); background-color: var(--color-button-alt-bg-hover);
} }
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
font-size: var(--font-small); font-size: var(--font-small);
}
@media (max-width: $breakpoint-small) { &:focus {
padding: var(--spacing-s) var(--spacing-s); background-color: var(--color-button-alt-bg);
}
} }
&.top-right { &.top-right {
@ -648,6 +658,15 @@ svg + .button__label {
&.button-collection-delete-cancel { &.button-collection-delete-cancel {
background-color: red; background-color: red;
} }
&.button-collection-drag {
cursor: grab;
display: flex;
align-content: center;
justify-content: center;
align-items: center;
padding-bottom: var(--spacing-xs);
}
} }
.button-toggle--expandformobile { .button-toggle--expandformobile {

View file

@ -216,8 +216,12 @@
} }
.claim-preview--collection-mine { .claim-preview--collection-mine {
padding-left: 7rem; padding-left: 9rem;
position: relative; position: relative;
@media (max-width: $breakpoint-small) {
padding-left: 6.5rem;
}
} }
.claim-preview--pending { .claim-preview--pending {
@ -240,7 +244,6 @@
.claim-preview__actions { .claim-preview__actions {
align-self: flex-end; align-self: flex-end;
margin-bottom: auto; margin-bottom: auto;
justify-content: flex-end;
width: auto; width: auto;
} }
@ -250,10 +253,6 @@
justify-content: space-between; justify-content: space-between;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
.claim-preview__actions {
margin-left: var(--spacing-m);
}
} }
@media (max-width: $breakpoint-xsmall) { @media (max-width: $breakpoint-xsmall) {
@ -541,6 +540,12 @@
} }
} }
.claim-preview--horizontal-tile {
&:not(:first-child) {
margin-top: 0;
}
}
.claim-tile__title { .claim-tile__title {
position: relative; position: relative;
padding: var(--spacing-s); padding: var(--spacing-s);
@ -551,7 +556,7 @@
font-weight: 600; font-weight: 600;
color: var(--color-text); color: var(--color-text);
font-size: var(--font-small); font-size: var(--font-small);
min-height: 2rem; min-height: 3.2rem;
@media (min-width: $breakpoint-small) { @media (min-width: $breakpoint-small) {
min-height: 2.5rem; min-height: 2.5rem;

View file

@ -94,7 +94,7 @@
padding-top: var(--spacing-m); padding-top: var(--spacing-m);
padding-bottom: var(--spacing-m); padding-bottom: var(--spacing-m);
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr;
align-items: center; align-items: center;
} }
@ -107,6 +107,10 @@
justify-content: center; justify-content: center;
align-items: stretch; align-items: stretch;
flex-grow: 4; flex-grow: 4;
@media (max-width: $breakpoint-small) {
width: 1.5rem;
}
} }
.collection-edit__header { .collection-edit__header {

View file

@ -12,7 +12,7 @@
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
min-height: calc(100vh - var(--header-height)); min-height: 100vh;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-top: var(--header-height); margin-top: var(--header-height);
@ -26,7 +26,7 @@
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
padding: var(--spacing-xs); padding: var(--spacing-xs);
padding-top: var(--spacing-m); margin-top: var(--header-height-mobile);
} }
@media (min-width: $breakpoint-large) { @media (min-width: $breakpoint-large) {
@ -42,20 +42,51 @@
padding-top: 0; padding-top: 0;
} }
.main-wrapper__inner--auth {
min-height: unset;
padding: 0;
}
.sidebar--pusher {
animation-timing-function: var(--resizing-animation-function);
transition: transform var(--resizing-animation-timing);
transform-origin: left;
position: absolute;
@media (max-width: $breakpoint-small) {
transform: translateX(0);
width: calc(100% - var(--spacing-m));
}
@media (min-width: $breakpoint-small) {
transform: translateX(var(--side-nav-width--micro));
width: calc(100% - ((var(--side-nav-width--micro))));
}
}
.sidebar--pusher--filepage {
width: 100%;
}
.sidebar--pusher--open {
@media (min-width: $breakpoint-medium) {
transform: translateX(var(--side-nav-width));
width: calc(100% - var(--side-nav-width));
}
}
.main { .main {
position: relative; position: relative;
width: calc(100% - var(--side-nav-width) - var(--spacing-l)); width: calc(100% - 2 * var(--spacing-l));
max-width: var(--page-max-width); max-width: var(--page-max-width);
z-index: 0; z-index: 0;
margin-right: auto; margin-right: auto;
margin-left: auto; margin-left: auto;
min-height: calc(100vh - var(--header-height));
@media (max-width: $breakpoint-medium) and (min-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
margin: 0 var(--spacing-l);
}
@media (max-width: $breakpoint-medium) {
width: 100%; width: 100%;
min-height: calc(100vh - var(--header-height-mobile));
} }
} }
@ -127,13 +158,7 @@
margin-left: var(--spacing-m); margin-left: var(--spacing-m);
.card__header--between { .card__header--between {
padding: var(--spacing-s) var(--spacing-s) var(--spacing-s) var(--spacing-s);
display: flex;
align-items: center; align-items: center;
.card__title-actions {
padding: 0 0 0 0;
align-items: center;
}
} }
@media (max-width: $breakpoint-medium) { @media (max-width: $breakpoint-medium) {
width: 100%; width: 100%;
@ -148,7 +173,18 @@
overflow-wrap: break-word; overflow-wrap: break-word;
.card__header--between { .card__header--between {
padding: var(--spacing-s) var(--spacing-s) var(--spacing-s) var(--spacing-s); align-items: flex-start !important;
.card__title-actions {
display: flex;
flex-direction: column;
align-items: flex-end;
padding: var(--spacing-s);
button {
margin-top: var(--spacing-xxs);
}
}
} }
.file-page__recommended-collection__row { .file-page__recommended-collection__row {
@ -162,6 +198,15 @@
max-width: 50rem; max-width: 50rem;
} }
} }
.collection-preview__edit-group {
width: 1.5rem;
}
.claim-preview--collection-mine {
padding-left: 6.5rem;
position: relative;
}
} }
@media (max-width: $breakpoint-medium) { @media (max-width: $breakpoint-medium) {
@ -221,12 +266,7 @@
.main--full-width { .main--full-width {
@extend .main; @extend .main;
@media (min-width: $breakpoint-large) {
max-width: none; max-width: none;
width: 100%;
margin: 0 var(--spacing-l);
}
} }
.main--auth-page { .main--auth-page {
@ -250,6 +290,10 @@
margin-top: var(--spacing-m); margin-top: var(--spacing-m);
padding: 0 var(--spacing-m); padding: 0 var(--spacing-m);
@media (max-width: $breakpoint-small) {
padding: 0 0;
}
.card__subtitle { .card__subtitle {
margin: 0 0 var(--spacing-s) 0; margin: 0 0 var(--spacing-s) 0;
font-size: var(--font-small); font-size: var(--font-small);
@ -393,18 +437,25 @@
.main__sign-up--graphic { .main__sign-up--graphic {
max-width: 47rem; max-width: 47rem;
.card__first-pane {
width: 50%;
@media (max-width: $breakpoint-small) {
width: 100%;
}
}
.card__second-pane { .card__second-pane {
width: 50%; width: 50%;
@media (max-width: $breakpoint-small) {
width: 100%;
}
border: none; border: none;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: var(--color-login-graphic-background); background: var(--color-login-graphic-background);
@media (max-width: $breakpoint-small) {
width: 100%;
}
.signup-image { .signup-image {
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
width: 50%; width: 50%;
@ -413,7 +464,7 @@
} }
.card__title { .card__title {
font-size: var(--font-heading); font-size: var(--font-title);
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
} }

View file

@ -0,0 +1,13 @@
.swipe-list {
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.swipe-list__item {
width: 80vw;
margin-right: var(--spacing-s);
flex-shrink: 0;
scroll-snap-align: start;
}

View file

@ -0,0 +1,92 @@
.flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.justify-between {
justify-content: space-between;
}
.justify-center {
justify-content: center;
}
.items-center {
align-items: center;
}
.w-full {
width: 100%;
}
.opacity-40 {
opacity: 0.4;
}
.h-12 {
height: 3rem;
}
.mt-0 {
margin-top: 0px;
}
.mt-s {
margin-top: var(--spacing-s);
}
.mt-m {
margin-top: var(--spacing-m);
}
.mb-m {
margin-bottom: var(--spacing-m);
}
.mb-xl {
margin-bottom: var(--spacing-xl);
}
.ml-s {
margin-left: var(--spacing-s);
}
.ml-m {
margin-left: var(--spacing-m);
}
.mr-m {
margin-right: var(--spacing-m);
}
.mb-0 {
margin-bottom: 0;
}
.text-s {
font-size: var(--font-small);
}
@media (min-width: $breakpoint-small) {
.md\:items-center {
align-items: center;
}
.md\:flex-col {
flex-direction: column;
}
.md\:ml-m {
margin-left: var(--spacing-m);
}
.md\:flex-row {
flex-direction: row;
}
.md\:w-auto {
width: auto;
}
.md\:mt-0 {
margin-top: 0;
}
.md\:mb-xl {
margin-bottom: var(--spacing-xl);
}
.md\:h-12 {
height: 3rem;
}
}

View file

@ -5,7 +5,6 @@ import * as CS from 'constants/claim_search';
import { parseURI } from 'util/lbryURI'; import { parseURI } from 'util/lbryURI';
import moment from 'moment'; import moment from 'moment';
import { toCapitalCase } from 'util/string'; import { toCapitalCase } from 'util/string';
import { useIsLargeScreen } from 'effects/use-screensize';
import { CUSTOM_HOMEPAGE } from 'config'; import { CUSTOM_HOMEPAGE } from 'config';
export type RowDataItem = { export type RowDataItem = {
@ -125,6 +124,7 @@ export const getHomepageRowForCat = (cat: HomepageCat) => {
export function GetLinksData( export function GetLinksData(
all: any, // HomepageData type? all: any, // HomepageData type?
isLargeScreen: boolean,
isHomepage?: boolean = false, isHomepage?: boolean = false,
authenticated?: boolean, authenticated?: boolean,
showPersonalizedChannels?: boolean, showPersonalizedChannels?: boolean,
@ -134,8 +134,6 @@ export function GetLinksData(
showIndividualTags?: boolean, showIndividualTags?: boolean,
showNsfw?: boolean showNsfw?: boolean
) { ) {
const isLargeScreen = useIsLargeScreen();
function getPageSize(originalSize) { function getPageSize(originalSize) {
return isLargeScreen ? originalSize * (3 / 2) : originalSize; return isLargeScreen ? originalSize * (3 / 2) : originalSize;
} }

View file

@ -1,5 +1,6 @@
// @flow // @flow
import { MATURE_TAGS } from 'constants/tags'; import { MATURE_TAGS } from 'constants/tags';
import { parseURI } from 'util/lbryURI';
const matureTagMap = MATURE_TAGS.reduce((acc, tag) => ({ ...acc, [tag]: true }), {}); const matureTagMap = MATURE_TAGS.reduce((acc, tag) => ({ ...acc, [tag]: true }), {});
@ -66,6 +67,28 @@ export function filterClaims(claims: Array<Claim>, query: ?string): Array<Claim>
return claims; return claims;
} }
/**
* Determines if the claim is a channel.
*
* @param claim
* @param uri An abandoned claim will be null, so provide the `uri` as a fallback to parse.
*/
export function isChannelClaim(claim: ?Claim, uri?: string) {
// 1. parseURI can't resolve a repost's channel, so a `claim` will be needed.
// 2. parseURI is still needed to cover the case of abandoned claims.
if (claim) {
return claim.value_type === 'channel';
} else if (uri) {
try {
return Boolean(parseURI(uri).isChannel);
} catch (err) {
return false;
}
} else {
return false;
}
}
export function getChannelIdFromClaim(claim: ?Claim) { export function getChannelIdFromClaim(claim: ?Claim) {
if (claim) { if (claim) {
if (claim.value_type === 'channel') { if (claim.value_type === 'channel') {

View file

@ -4,14 +4,20 @@ export function toCapitalCase(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1);
} }
export function toCompactNotation(number: string | number, lang: ?string) { export function toCompactNotation(number: string | number, lang: ?string, minThresholdToApply?: string | number) {
const locale = lang || 'en';
if (minThresholdToApply && Number(number) >= Number(minThresholdToApply)) {
try { try {
return Number(number).toLocaleString(lang || 'en', { return Number(number).toLocaleString(locale, {
compactDisplay: 'short', compactDisplay: 'short',
notation: 'compact', notation: 'compact',
}); });
} catch (err) { } catch (err) {
// Not all browsers support the addition options. // Not all browsers support the additional options.
return Number(number).toLocaleString(); return Number(number).toLocaleString(locale);
}
} else {
return Number(number).toLocaleString(locale);
} }
} }

395
yarn.lock
View file

@ -1167,7 +1167,14 @@
core-js-pure "^3.0.0" core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4": "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4":
version "7.10.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839"
integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5":
version "7.14.6" version "7.14.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg== integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
@ -1181,6 +1188,13 @@
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.15.4", "@babel/runtime@^7.9.2":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.10.1", "@babel/template@^7.8.3", "@babel/template@^7.8.6": "@babel/template@^7.10.1", "@babel/template@^7.8.3", "@babel/template@^7.8.6":
version "7.10.1" version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
@ -1287,9 +1301,9 @@
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@datapunt/matomo-tracker-js@^0.1.4": "@datapunt/matomo-tracker-js@^0.1.4":
version "0.1.4" version "0.1.5"
resolved "https://registry.yarnpkg.com/@datapunt/matomo-tracker-js/-/matomo-tracker-js-0.1.4.tgz#1226f0964d2c062bf9392e9c2fd89838262b10df" resolved "https://registry.yarnpkg.com/@datapunt/matomo-tracker-js/-/matomo-tracker-js-0.1.5.tgz#650396e2d8d9d905c04c94e637cbc21c07648d02"
integrity sha512-bi8/guszgciSNLJQIFgph27AzkiCF1DmLBxtmJE3CsLxfc0aTgI2vMg3EFoLv13Mu8HLaCs27Z7vbttlD6jp5w== integrity sha512-4L4y5XlVVfJYeyUY1F3iuG55tWBdM5xODtOJSc4LmxTIeTVMKPdI8VbPG3h/2BzO1xxk/p71XakGlemYzn0inw==
"@develar/schema-utils@~2.6.5": "@develar/schema-utils@~2.6.5":
version "2.6.5" version "2.6.5"
@ -1552,9 +1566,9 @@
fastq "^1.6.0" fastq "^1.6.0"
"@npmcli/fs@^1.0.0": "@npmcli/fs@^1.0.0":
version "1.0.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.0.0.tgz#589612cfad3a6ea0feafcb901d29c63fd52db09f" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.0.tgz#bec1d1b89c170d40e1b73ad6c943b0b75e7d2951"
integrity sha512-8ltnOpRR/oJbOp8vaGUnipOi3bqkcW+sLHFlyXIr08OGHmVJLB1Hn7QtGXbYcpVtH1gAYZTlmDXtE4YV0+AMMQ== integrity sha512-VhP1qZLXcrXRIaPoqb4YA55JQxLNF3jNR4T55IdOJa3+IFJKNYHtPvtXx8slmeMavj37vCzCfrqQM1vWLsYKLA==
dependencies: dependencies:
"@gar/promisify" "^1.0.1" "@gar/promisify" "^1.0.1"
semver "^7.3.5" semver "^7.3.5"
@ -1964,6 +1978,14 @@
"@types/minimatch" "*" "@types/minimatch" "*"
"@types/node" "*" "@types/node" "*"
"@types/hoist-non-react-statics@^3.3.0":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/html-minifier-terser@^5.0.0": "@types/html-minifier-terser@^5.0.0":
version "5.1.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50"
@ -2000,9 +2022,9 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.0.tgz#5b6ee7a77faacddd7de719017d0bc12f52f81589" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.0.tgz#5b6ee7a77faacddd7de719017d0bc12f52f81589"
"@types/node@^14.6.2": "@types/node@^14.6.2":
version "14.18.1" version "14.18.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.1.tgz#459886b51f52aa923dc06b9ea81cb8b1d733e9d3" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.9.tgz#0e5944eefe2b287391279a19b407aa98bd14436d"
integrity sha512-fTFWOFrgAkj737w1o0HLTIgisgYHnsZfeiqhG1Ltrf/iJjudEbUwetQAsfrtVE49JGwvpEzQR+EbMkIqG4227g== integrity sha512-j11XSuRuAlft6vLDEX4RvhqC0KxNxx6QIyMXNb0vHHSNPXTPeiy3algESWmOOIzEtiEL0qiowPU3ewW9hHVa7Q==
"@types/normalize-package-data@^2.4.0": "@types/normalize-package-data@^2.4.0":
version "2.4.0" version "2.4.0"
@ -2034,6 +2056,16 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-redux@^7.1.20":
version "7.1.21"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.21.tgz#f32bbaf7cbc4b93f51e10d340aa54035c084b186"
integrity sha512-bLdglUiBSQNzWVVbmNPKGYYjrzp3/YDPwfOH3nLEz99I4awLlaRAPWjo6bZ2POpxztFWtDDXIPxBLVykXqBt+w==
dependencies:
"@types/hoist-non-react-statics" "^3.3.0"
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react-transition-group@^4.4.4": "@types/react-transition-group@^4.4.4":
version "4.4.4" version "4.4.4"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e"
@ -2143,20 +2175,52 @@
resolved "https://registry.yarnpkg.com/@ungap/from-entries/-/from-entries-0.2.1.tgz#7e86196b8b2e99d73106a8f25c2a068326346354" resolved "https://registry.yarnpkg.com/@ungap/from-entries/-/from-entries-0.2.1.tgz#7e86196b8b2e99d73106a8f25c2a068326346354"
integrity sha512-CAqefTFAfnUPwYqsWHXpOxHaq1Zo5UQ3m9Zm2p09LggGe57rqHoBn3c++xcoomzXKynAUuiBMDUCQvKMnXjUpA== integrity sha512-CAqefTFAfnUPwYqsWHXpOxHaq1Zo5UQ3m9Zm2p09LggGe57rqHoBn3c++xcoomzXKynAUuiBMDUCQvKMnXjUpA==
"@videojs/http-streaming@2.9.2": "@videojs/http-streaming@2.10.2":
version "2.9.2" version "2.10.2"
resolved "https://registry.yarnpkg.com/@videojs/http-streaming/-/http-streaming-2.9.2.tgz#47d33bb02bd9c1287200398b1e85d213dee814d0" resolved "https://registry.yarnpkg.com/@videojs/http-streaming/-/http-streaming-2.10.2.tgz#02e6fcfa74f7850b5f9eb40a8e5c85c9d7d33eaf"
integrity sha512-2ZsxJn4/nZZ6k6jIhic2l9ynGmKwprtuI5b3+M6JgqOSLvQQ/ah+heVs/0g2Ze7qJxodqR+aSY948JwJIz1gCw== integrity sha512-JTAlAUHzj0sTsge2WBh4DWKM2I5BDFEZYOvzxmsK/ySILmI0GRyjAHx9uid68ZECQ2qbEAIRmZW5lWp0R5PeNA==
dependencies: dependencies:
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
"@videojs/vhs-utils" "^3.0.2" "@videojs/vhs-utils" "3.0.3"
aes-decrypter "3.1.2" aes-decrypter "3.1.2"
global "^4.4.0" global "^4.4.0"
m3u8-parser "4.7.0" m3u8-parser "4.7.0"
mpd-parser "0.17.0" mpd-parser "0.19.0"
mux.js "5.12.2" mux.js "5.13.0"
video.js "^6 || ^7" video.js "^6 || ^7"
"@videojs/http-streaming@2.12.0":
version "2.12.0"
resolved "https://registry.yarnpkg.com/@videojs/http-streaming/-/http-streaming-2.12.0.tgz#850069e063e26cf2fa5ed9bb3addfc92fa899f78"
integrity sha512-vdQA0lDYBXGJqV2T02AGqg1w4dcgyRoN+bYG+G8uF4DpCEMhEtUI0BA4tRu4/Njar8w/9D5k0a1KX40pcvM3fA==
dependencies:
"@babel/runtime" "^7.12.5"
"@videojs/vhs-utils" "3.0.4"
aes-decrypter "3.1.2"
global "^4.4.0"
m3u8-parser "4.7.0"
mpd-parser "0.19.2"
mux.js "5.14.1"
video.js "^6 || ^7"
"@videojs/vhs-utils@3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@videojs/vhs-utils/-/vhs-utils-3.0.3.tgz#708bc50742e9481712039695299b32da6582ef92"
integrity sha512-bU7daxDHhzcTDbmty1cXjzsTYvx2cBGbA8hG5H2Gvpuk4sdfuvkZtMCwtCqL59p6dsleMPspyaNS+7tWXx2Y0A==
dependencies:
"@babel/runtime" "^7.12.5"
global "^4.4.0"
url-toolkit "^2.2.1"
"@videojs/vhs-utils@3.0.4", "@videojs/vhs-utils@^3.0.3":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@videojs/vhs-utils/-/vhs-utils-3.0.4.tgz#e253eecd8e9318f767e752010d213587f94bb03a"
integrity sha512-hui4zOj2I1kLzDgf8QDVxD3IzrwjS/43KiS8IHQO0OeeSsb4pB/lgNt1NG7Dv0wMQfCccUpMVLGcK618s890Yg==
dependencies:
"@babel/runtime" "^7.12.5"
global "^4.4.0"
url-toolkit "^2.2.1"
"@videojs/vhs-utils@^3.0.0", "@videojs/vhs-utils@^3.0.2": "@videojs/vhs-utils@^3.0.0", "@videojs/vhs-utils@^3.0.2":
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/@videojs/vhs-utils/-/vhs-utils-3.0.2.tgz#0203418ecaaff29bc33c69b6ad707787347b7614" resolved "https://registry.yarnpkg.com/@videojs/vhs-utils/-/vhs-utils-3.0.2.tgz#0203418ecaaff29bc33c69b6ad707787347b7614"
@ -2166,10 +2230,10 @@
global "^4.4.0" global "^4.4.0"
url-toolkit "^2.2.1" url-toolkit "^2.2.1"
"@videojs/xhr@2.5.1": "@videojs/xhr@2.6.0":
version "2.5.1" version "2.6.0"
resolved "https://registry.yarnpkg.com/@videojs/xhr/-/xhr-2.5.1.tgz#26bc5a79dbb3b03bfb13742c6ce559f89e90719e" resolved "https://registry.yarnpkg.com/@videojs/xhr/-/xhr-2.6.0.tgz#cd897e0ad54faf497961bcce3fa16dc15a26bb80"
integrity sha512-wV9nGESHseSK+S9ePEru2+OJZ1jq/ZbbzniGQ4weAmTIepuBMSYPx5zrxxQA0E786T5ykpO8ts+LayV+3/oI2w== integrity sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==
dependencies: dependencies:
"@babel/runtime" "^7.5.5" "@babel/runtime" "^7.5.5"
global "~4.4.0" global "~4.4.0"
@ -2325,6 +2389,11 @@
resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.0.3.tgz#2dcfd92881425c5923e429c2aec86fb3609032a1" resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.0.3.tgz#2dcfd92881425c5923e429c2aec86fb3609032a1"
integrity sha512-1VPkkTBk07gMR1fjpBtse4G+oJqpmE+0gUFB0dg3VIL7qJmUVaBoD/vlzMm/jNeOPfvlmerl1lpnsZyBUFIRuw== integrity sha512-1VPkkTBk07gMR1fjpBtse4G+oJqpmE+0gUFB0dg3VIL7qJmUVaBoD/vlzMm/jNeOPfvlmerl1lpnsZyBUFIRuw==
"@xmldom/xmldom@^0.7.2":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d"
integrity sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==
"@xtuc/ieee754@^1.2.0": "@xtuc/ieee754@^1.2.0":
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@ -3719,14 +3788,10 @@ codemirror-spell-checker@1.1.2:
dependencies: dependencies:
typo-js "*" typo-js "*"
codemirror@^5.39.2: codemirror@^5.39.2, codemirror@^5.58.2:
version "5.52.0" version "5.63.3"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.52.0.tgz#4dbd6aef7f0e63db826b9a23922f0c03ac75c0a7" resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.63.3.tgz#97042a242027fe0c87c09b36bc01931d37b76527"
integrity sha512-1C+LELr+5grgJYqwZKqxrcbPsHFHapVaVAloBsFBASbpLnQqLw1U8yXJ3gT5D+rhxIiSpo+kTqN+hQ+9ialIXw==
codemirror@^5.58.2:
version "5.59.2"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.59.2.tgz#ee674d3a4a8d241af38d52afc482625ba7393922"
integrity sha512-/D5PcsKyzthtSy2NNKCyJi3b+htRkoKv3idswR/tR6UAvMNKA7SrmyZy6fOONJxSRs1JlUWEDAbxqfdArbK8iA==
collapse-white-space@^1.0.2: collapse-white-space@^1.0.2:
version "1.0.6" version "1.0.6"
@ -4177,6 +4242,13 @@ crypto-random-string@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
css-box-model@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
dependencies:
tiny-invariant "^1.0.6"
css-color-names@0.0.4, css-color-names@^0.0.4: css-color-names@0.0.4, css-color-names@^0.0.4:
version "0.0.4" version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@ -4258,15 +4330,15 @@ css-select@^2.0.0:
nth-check "^1.0.2" nth-check "^1.0.2"
css-select@^4.1.3: css-select@^4.1.3:
version "4.2.0" version "4.1.3"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.0.tgz#ab28276d3afb00cc05e818bd33eb030f14f57895" resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067"
integrity sha512-6YVG6hsH9yIb/si3Th/is8Pex7qnVHO6t7q7U6TIUnkQASGbS8tnUDBftnPynLNnuUl/r2+PTd0ekiiq7R0zJw== integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==
dependencies: dependencies:
boolbase "^1.0.0" boolbase "^1.0.0"
css-what "^5.1.0" css-what "^5.0.0"
domhandler "^4.3.0" domhandler "^4.2.0"
domutils "^2.8.0" domutils "^2.6.0"
nth-check "^2.0.1" nth-check "^2.0.0"
css-tree@1.0.0-alpha.37: css-tree@1.0.0-alpha.37:
version "1.0.0-alpha.37" version "1.0.0-alpha.37"
@ -4283,10 +4355,10 @@ css-what@^3.2.1:
version "3.2.1" version "3.2.1"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1"
css-what@^5.1.0: css-what@^5.0.0:
version "5.1.0" version "5.0.1"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.1.tgz#3efa820131f4669a8ac2408f9c32e7c7de9f4cad"
integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== integrity sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==
cssesc@^3.0.0: cssesc@^3.0.0:
version "3.0.0" version "3.0.0"
@ -4745,10 +4817,10 @@ domhandler@^3.0.0, domhandler@^3.2.0:
dependencies: dependencies:
domelementtype "^2.0.1" domelementtype "^2.0.1"
domhandler@^4.2.0, domhandler@^4.3.0: domhandler@^4.2.0:
version "4.3.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059"
integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==
dependencies: dependencies:
domelementtype "^2.2.0" domelementtype "^2.2.0"
@ -4775,10 +4847,10 @@ domutils@^2.0.0:
domelementtype "^2.0.1" domelementtype "^2.0.1"
domhandler "^3.2.0" domhandler "^3.2.0"
domutils@^2.8.0: domutils@^2.6.0:
version "2.8.0" version "2.7.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.7.0.tgz#8ebaf0c41ebafcf55b0b72ec31c56323712c5442"
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== integrity sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==
dependencies: dependencies:
dom-serializer "^1.0.1" dom-serializer "^1.0.1"
domelementtype "^2.2.0" domelementtype "^2.2.0"
@ -5722,10 +5794,10 @@ fast-diff@^1.1.1:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
fast-glob@^3.1.1, fast-glob@^3.2.4: fast-glob@^3.2.4, fast-glob@^3.2.9:
version "3.2.7" version "3.2.11"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
dependencies: dependencies:
"@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3" "@nodelib/fs.walk" "^1.2.3"
@ -5998,9 +6070,9 @@ flush-write-stream@^1.0.0:
readable-stream "^2.3.6" readable-stream "^2.3.6"
follow-redirects@^1.0.0: follow-redirects@^1.0.0:
version "1.14.7" version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
for-in@^1.0.2: for-in@^1.0.2:
version "1.0.2" version "1.0.2"
@ -6348,15 +6420,15 @@ globalthis@^1.0.1:
define-properties "^1.1.3" define-properties "^1.1.3"
globby@^11.0.1: globby@^11.0.1:
version "11.0.4" version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
dependencies: dependencies:
array-union "^2.1.0" array-union "^2.1.0"
dir-glob "^3.0.1" dir-glob "^3.0.1"
fast-glob "^3.1.1" fast-glob "^3.2.9"
ignore "^5.1.4" ignore "^5.2.0"
merge2 "^1.3.0" merge2 "^1.4.1"
slash "^3.0.0" slash "^3.0.0"
globby@^6.1.0: globby@^6.1.0:
@ -6912,10 +6984,10 @@ ignore@^5.0.2:
version "5.1.4" version "5.1.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
ignore@^5.1.4: ignore@^5.2.0:
version "5.1.9" version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
imagesloaded@^4.1.4: imagesloaded@^4.1.4:
version "4.1.4" version "4.1.4"
@ -8360,12 +8432,17 @@ merge-descriptors@1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
merge-refs@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/merge-refs/-/merge-refs-1.0.0.tgz#388348bce22e623782c6df9d3c4fc55888276120"
integrity sha512-WZ4S5wqD9FCR9hxkLgvcHJCBxzXzy3VVE6p8W2OzxRzB+hLRlcadGE2bW9xp2KSzk10rvp4y+pwwKO6JQVguMg==
merge-stream@^2.0.0: merge-stream@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
merge2@^1.3.0: merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
@ -8631,15 +8708,25 @@ move-concurrently@^1.0.1:
rimraf "^2.5.4" rimraf "^2.5.4"
run-queue "^1.0.3" run-queue "^1.0.3"
mpd-parser@0.17.0: mpd-parser@0.19.0:
version "0.17.0" version "0.19.0"
resolved "https://registry.yarnpkg.com/mpd-parser/-/mpd-parser-0.17.0.tgz#d7f3002edcb706f98993ef75846a713d056d3332" resolved "https://registry.yarnpkg.com/mpd-parser/-/mpd-parser-0.19.0.tgz#8937044040ca85e20398ecf5d94552655e2c6728"
integrity sha512-oKS5G0jCcHHJ3sHYlcLeM9Xcbuixl08eAx7QW0Th7ChlZiI0YvLtGaHE/L0aKUBJFNvtkeksIr8XgJgSBBsS4g== integrity sha512-FDLIXtZMZs99fv5iXNFg94quNFT26tobo0NUgHu7L3XgZvEq1NBarf5yxDFFJ1zzfbcmtj+NRaml6nYIxoPWvw==
dependencies: dependencies:
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
"@videojs/vhs-utils" "^3.0.2" "@videojs/vhs-utils" "^3.0.2"
"@xmldom/xmldom" "^0.7.2"
global "^4.4.0"
mpd-parser@0.19.2:
version "0.19.2"
resolved "https://registry.yarnpkg.com/mpd-parser/-/mpd-parser-0.19.2.tgz#68611e653cdf2cc1e90688825c4a129b7f9007e0"
integrity sha512-M5tAIdtBM2TN+OSTz/37T7V+h9ZLvhyNqq4TNIdtjAQ/Hg8UnMRf5nJQDjffcXag3POXi31yUJQEKOXdcAM/nw==
dependencies:
"@babel/runtime" "^7.12.5"
"@videojs/vhs-utils" "^3.0.2"
"@xmldom/xmldom" "^0.7.2"
global "^4.4.0" global "^4.4.0"
xmldom "^0.5.0"
ms@2.0.0: ms@2.0.0:
version "2.0.0" version "2.0.0"
@ -8669,10 +8756,17 @@ mute-stream@0.0.7:
version "0.0.7" version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
mux.js@5.12.2: mux.js@5.13.0:
version "5.12.2" version "5.13.0"
resolved "https://registry.yarnpkg.com/mux.js/-/mux.js-5.12.2.tgz#cd823312f4bb69adb8b9c5f45635b4451066d6e6" resolved "https://registry.yarnpkg.com/mux.js/-/mux.js-5.13.0.tgz#99c3da21f6c0362a1529729d1c5e5b51f34f606d"
integrity sha512-9OY1lrFIo7FxMeIC6aLUftiNv97AztufDfi30N7qDll1Pcy7bCxlHztyHp1Ce0KQwy2XqchGeENPS4v1NJngHQ== integrity sha512-PkmnzHcTQjUBEHp3KRPQAFoNkJtKlpCEvsYtXDfDrC+/WqbMuxHvoYfmAbHVAH7Sa/KliPVU0dT1ureO8wilog==
dependencies:
"@babel/runtime" "^7.11.2"
mux.js@5.14.1:
version "5.14.1"
resolved "https://registry.yarnpkg.com/mux.js/-/mux.js-5.14.1.tgz#209583f454255d9ba2ff1bb61ad5a6867cf61878"
integrity sha512-38kA/xjWRDzMbcpHQfhKbJAME8eTZVsb9U2Puk890oGvGqnyu8B/AkKdICKPHkigfqYX9MY20vje88TP14nhog==
dependencies: dependencies:
"@babel/runtime" "^7.11.2" "@babel/runtime" "^7.11.2"
@ -8742,9 +8836,9 @@ node-forge@0.9.0:
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
node-html-parser@^5.1.0: node-html-parser@^5.1.0:
version "5.1.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-5.1.0.tgz#753f5a60cdfe6d027c15857cb817df592c18c998" resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-5.2.0.tgz#6f29fd00d79f65334e7e20200964644207925607"
integrity sha512-l6C1Gf1o7YuxeMGa17PypEez/rj+ii3q4/NZG37nRmWSLDjHyB0WNrlE4h2UW92D0JSfUSfu+lOvxThttVe7Jw== integrity sha512-fmiwLfQu+J2A0zjwSEkztSHexAf5qq/WoiL/Hgo1K7JpfEP+OGWY5maG0kGaM+IFVdixF/1QbyXaQ3h4cGfeLw==
dependencies: dependencies:
css-select "^4.1.3" css-select "^4.1.3"
he "1.2.0" he "1.2.0"
@ -8916,10 +9010,10 @@ nth-check@^1.0.2, nth-check@~1.0.1:
dependencies: dependencies:
boolbase "~1.0.0" boolbase "~1.0.0"
nth-check@^2.0.1: nth-check@^2.0.0:
version "2.0.1" version "2.0.0"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125"
integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==
dependencies: dependencies:
boolbase "^1.0.0" boolbase "^1.0.0"
@ -9437,9 +9531,9 @@ picomatch@^2.0.4, picomatch@^2.2.1:
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
picomatch@^2.2.3: picomatch@^2.2.3:
version "2.3.0" version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pify@^2.0.0, pify@^2.3.0: pify@^2.0.0, pify@^2.3.0:
version "2.3.0" version "2.3.0"
@ -10134,6 +10228,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
raf-schd@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@ -10201,6 +10300,19 @@ react-awesome-lightbox@^1.7.3:
resolved "https://registry.yarnpkg.com/react-awesome-lightbox/-/react-awesome-lightbox-1.7.3.tgz#ee1c00fd4197e0e65bf996aa219eac4d8b6db5a0" resolved "https://registry.yarnpkg.com/react-awesome-lightbox/-/react-awesome-lightbox-1.7.3.tgz#ee1c00fd4197e0e65bf996aa219eac4d8b6db5a0"
integrity sha512-mSxdL3KGzuh2eR8I00nv9njiolmMoXITuCvfd71DBXK13JW3e+Z/sCMENS9+dngBJU8/m7dR1Ix0W6afS5cFsA== integrity sha512-mSxdL3KGzuh2eR8I00nv9njiolmMoXITuCvfd71DBXK13JW3e+Z/sCMENS9+dngBJU8/m7dR1Ix0W6afS5cFsA==
react-beautiful-dnd@^13.1.0:
version "13.1.0"
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz#ec97c81093593526454b0de69852ae433783844d"
integrity sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==
dependencies:
"@babel/runtime" "^7.9.2"
css-box-model "^1.2.0"
memoize-one "^5.1.1"
raf-schd "^4.0.2"
react-redux "^7.2.0"
redux "^4.0.4"
use-memo-one "^1.1.1"
react-calendar@^3.3.1: react-calendar@^3.3.1:
version "3.3.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/react-calendar/-/react-calendar-3.3.1.tgz#da691a5d59c88f178695fd8b33909a71d698021f" resolved "https://registry.yarnpkg.com/react-calendar/-/react-calendar-3.3.1.tgz#da691a5d59c88f178695fd8b33909a71d698021f"
@ -10236,25 +10348,26 @@ react-confetti@^4.0.1:
dependencies: dependencies:
tween-functions "^1.2.0" tween-functions "^1.2.0"
react-date-picker@^8.1.0: react-date-picker@^8.3.3:
version "8.1.1" version "8.3.6"
resolved "https://registry.yarnpkg.com/react-date-picker/-/react-date-picker-8.1.1.tgz#1959608cd042c9bfcf2faa6d63a56e9ef6b17e2b" resolved "https://registry.yarnpkg.com/react-date-picker/-/react-date-picker-8.3.6.tgz#446142bee5691aea66a2bac53313357aca561cd4"
integrity sha512-kFhn+uSJML+EuROvR6qLYU5G3wsxrdB2K1ugh1t6HjJCjphE6ot85jb8THWebqWEcQi07pLseU7ZFpzKDD3A6A== integrity sha512-c1rThf0jSKROoSGLpUEPtcC8VE+XoVgqxh+ng9aLYQvjDMGWQBgoat6Qrj8nRVzvCPpdXV4jqiCB3z2vVVuseA==
dependencies: dependencies:
"@types/react-calendar" "^3.0.0" "@types/react-calendar" "^3.0.0"
"@wojtekmaj/date-utils" "^1.0.3" "@wojtekmaj/date-utils" "^1.0.3"
get-user-locale "^1.2.0" get-user-locale "^1.2.0"
make-event-props "^1.1.0" make-event-props "^1.1.0"
merge-class-names "^1.1.1" merge-class-names "^1.1.1"
merge-refs "^1.0.0"
prop-types "^15.6.0" prop-types "^15.6.0"
react-calendar "^3.3.1" react-calendar "^3.3.1"
react-fit "^1.0.3" react-fit "^1.0.3"
update-input-width "^1.1.1" update-input-width "^1.2.2"
react-datetime-picker@^3.2.1: react-datetime-picker@^3.4.3:
version "3.2.1" version "3.4.3"
resolved "https://registry.yarnpkg.com/react-datetime-picker/-/react-datetime-picker-3.2.1.tgz#d3a9631bcba17bd0047e6424cff0dfe242d9cf0e" resolved "https://registry.yarnpkg.com/react-datetime-picker/-/react-datetime-picker-3.4.3.tgz#9163471f72b708185482b6b72cd259da03462f79"
integrity sha512-elybaAL7RJG7r0elYZze5/zQo1ds0v+v89tyZkzEShw+6I1EcveXwYPOMj3aq0k7D5kY/K+dC5dWYw0w4d9kmw== integrity sha512-yuFmh3TJwDo3VnyQF6auRJoeYfFTUtyLsR292lWXieigp0ugKkQefUEzVybZQidiiUlCNK9UQgc37/igl7uBYA==
dependencies: dependencies:
"@wojtekmaj/date-utils" "^1.0.3" "@wojtekmaj/date-utils" "^1.0.3"
get-user-locale "^1.2.0" get-user-locale "^1.2.0"
@ -10263,9 +10376,9 @@ react-datetime-picker@^3.2.1:
prop-types "^15.6.0" prop-types "^15.6.0"
react-calendar "^3.3.1" react-calendar "^3.3.1"
react-clock "^3.0.0" react-clock "^3.0.0"
react-date-picker "^8.1.0" react-date-picker "^8.3.3"
react-fit "^1.0.3" react-fit "^1.0.3"
react-time-picker "^4.2.0" react-time-picker "^4.4.2"
react-dom@^16.8.2: react-dom@^16.8.2:
version "16.13.0" version "16.13.0"
@ -10356,6 +10469,18 @@ react-redux@^6.0.1:
prop-types "^15.7.2" prop-types "^15.7.2"
react-is "^16.8.2" react-is "^16.8.2"
react-redux@^7.2.0:
version "7.2.6"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.6.tgz#49633a24fe552b5f9caf58feb8a138936ddfe9aa"
integrity sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ==
dependencies:
"@babel/runtime" "^7.15.4"
"@types/react-redux" "^7.1.20"
hoist-non-react-statics "^3.3.2"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-is "^17.0.2"
react-router-dom@^5.1.0: react-router-dom@^5.1.0:
version "5.1.2" version "5.1.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18"
@ -10399,19 +10524,20 @@ react-spring@^8.0.20, react-spring@^8.0.27:
"@babel/runtime" "^7.3.1" "@babel/runtime" "^7.3.1"
prop-types "^15.5.8" prop-types "^15.5.8"
react-time-picker@^4.2.0: react-time-picker@^4.4.2:
version "4.2.1" version "4.4.4"
resolved "https://registry.yarnpkg.com/react-time-picker/-/react-time-picker-4.2.1.tgz#b27f0bbc2e58534f20dbf10b14d0b8f3334fcb07" resolved "https://registry.yarnpkg.com/react-time-picker/-/react-time-picker-4.4.4.tgz#a67ca5fd88f51eac0919df802e416d9a25ad726a"
integrity sha512-T0aEabJ3bz54l8LV3pdpB5lOZuO3pRIbry5STcUV58UndlrWLcHpdpvS1IC8JLNXhbLxzGs1MmpASb5k1ddlsg== integrity sha512-WMdrpGnegug0871Do+SU1Fe91uZGmS6JUo1Yw7eLfU3VHMXCFj9sL9FAT6BuXe7lfILBbXq4tQQOqa/rLDASQg==
dependencies: dependencies:
"@wojtekmaj/date-utils" "^1.0.0" "@wojtekmaj/date-utils" "^1.0.0"
get-user-locale "^1.2.0" get-user-locale "^1.2.0"
make-event-props "^1.1.0" make-event-props "^1.1.0"
merge-class-names "^1.1.1" merge-class-names "^1.1.1"
merge-refs "^1.0.0"
prop-types "^15.6.0" prop-types "^15.6.0"
react-clock "^3.0.0" react-clock "^3.0.0"
react-fit "^1.0.3" react-fit "^1.0.3"
update-input-width "^1.1.1" update-input-width "^1.2.2"
react-transition-group@^4.4.2: react-transition-group@^4.4.2:
version "4.4.2" version "4.4.2"
@ -10606,6 +10732,13 @@ redux@^3.6.0:
loose-envify "^1.1.0" loose-envify "^1.1.0"
symbol-observable "^1.0.3" symbol-observable "^1.0.3"
redux@^4.0.0, redux@^4.0.4:
version "4.1.2"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104"
integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==
dependencies:
"@babel/runtime" "^7.9.2"
regenerate-unicode-properties@^8.2.0: regenerate-unicode-properties@^8.2.0:
version "8.2.0" version "8.2.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
@ -12172,6 +12305,11 @@ tiny-invariant@^1.0.2:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
tiny-invariant@^1.0.6:
version "1.2.0"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
tiny-relative-date@^1.3.0: tiny-relative-date@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07"
@ -12646,10 +12784,10 @@ upath@^1.1.1:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
update-input-width@^1.1.1: update-input-width@^1.2.2:
version "1.2.1" version "1.2.2"
resolved "https://registry.yarnpkg.com/update-input-width/-/update-input-width-1.2.1.tgz#769d6182413590c3b50b52ffa9c65d79e2c17f95" resolved "https://registry.yarnpkg.com/update-input-width/-/update-input-width-1.2.2.tgz#9a6a35858ae8e66fbfe0304437b23a4934fc7d37"
integrity sha512-zygDshqDb2C2/kgfoD423n5htv/3OBF7aTaz2u2zZy998EJki8njOHOeZjKEd8XSYeDziIX1JXfMsKaIRJeJ/Q== integrity sha512-6QwD9ZVSXb96PxOZ01DU0DJTPwQGY7qBYgdniZKJN02Xzom2m+9J6EPxMbefskqtj4x78qbe5psDSALq9iNEYg==
update-notifier@^2.5.0: update-notifier@^2.5.0:
version "2.5.0" version "2.5.0"
@ -12741,6 +12879,11 @@ url@^0.11.0:
punycode "1.3.2" punycode "1.3.2"
querystring "0.2.0" querystring "0.2.0"
use-memo-one@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
use@^3.1.0: use@^3.1.0:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
@ -12862,21 +13005,40 @@ vfile@^2.0.0:
unist-util-stringify-position "^1.0.0" unist-util-stringify-position "^1.0.0"
vfile-message "^1.0.0" vfile-message "^1.0.0"
"video.js@^6 || ^7", video.js@^7.0.0, video.js@^7.14.3: "video.js@^6 || ^7", video.js@^7.0.0:
version "7.14.3" version "7.15.4"
resolved "https://registry.yarnpkg.com/video.js/-/video.js-7.14.3.tgz#0b612c09a0a81ef9bce65c710e73291cb06dc32c" resolved "https://registry.yarnpkg.com/video.js/-/video.js-7.15.4.tgz#0f96ef138035138cb30bf00a989b6174f0d16bac"
integrity sha512-6avCdSIfn5ss5NOgoQfY/xEfPNcz9DXSw+ZN80NwPguCdRd4VL4y40b/d7osYJwyCdF+YkvhqAW7dw4s0vBigg== integrity sha512-hghxkgptLUvfkpktB4wxcIVF3VpY/hVsMkrjHSv0jpj1bW9Jplzdt8IgpTm9YhlB1KYAp07syVQeZcBFUBwhkw==
dependencies: dependencies:
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
"@videojs/http-streaming" "2.9.2" "@videojs/http-streaming" "2.10.2"
"@videojs/vhs-utils" "^3.0.2" "@videojs/vhs-utils" "^3.0.3"
"@videojs/xhr" "2.5.1" "@videojs/xhr" "2.6.0"
aes-decrypter "3.1.2" aes-decrypter "3.1.2"
global "^4.4.0" global "^4.4.0"
keycode "^2.2.0" keycode "^2.2.0"
m3u8-parser "4.7.0" m3u8-parser "4.7.0"
mpd-parser "0.17.0" mpd-parser "0.19.0"
mux.js "5.12.2" mux.js "5.13.0"
safe-json-parse "4.0.0"
videojs-font "3.2.0"
videojs-vtt.js "^0.15.3"
video.js@^7.14.3:
version "7.17.0"
resolved "https://registry.yarnpkg.com/video.js/-/video.js-7.17.0.tgz#35918cc03748a5680f5d5f1da410e06eeea7786e"
integrity sha512-8RbLu9+Pdpep9OTPncUHIvZXFgn/7hKdPnSTE/lGSnlFSucXtTUBp41R7NDwncscMLQ0WgazUbmFlvr4MNWMbA==
dependencies:
"@babel/runtime" "^7.12.5"
"@videojs/http-streaming" "2.12.0"
"@videojs/vhs-utils" "^3.0.3"
"@videojs/xhr" "2.6.0"
aes-decrypter "3.1.2"
global "^4.4.0"
keycode "^2.2.0"
m3u8-parser "4.7.0"
mpd-parser "0.19.2"
mux.js "5.14.1"
safe-json-parse "4.0.0" safe-json-parse "4.0.0"
videojs-font "3.2.0" videojs-font "3.2.0"
videojs-vtt.js "^0.15.3" videojs-vtt.js "^0.15.3"
@ -13363,11 +13525,6 @@ xmldom@^0.3.0:
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.3.0.tgz#e625457f4300b5df9c2e1ecb776147ece47f3e5a" resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.3.0.tgz#e625457f4300b5df9c2e1ecb776147ece47f3e5a"
integrity sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g== integrity sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g==
xmldom@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e"
integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==
xpipe@*: xpipe@*:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/xpipe/-/xpipe-1.0.5.tgz#8dd8bf45fc3f7f55f0e054b878f43a62614dafdf" resolved "https://registry.yarnpkg.com/xpipe/-/xpipe-1.0.5.tgz#8dd8bf45fc3f7f55f0e054b878f43a62614dafdf"