strip out livestreams
This commit is contained in:
parent
fca18c26d3
commit
ab9f70930d
78 changed files with 126 additions and 3504 deletions
ui
component
channelContent
channelMentionSuggestions
claimPreview
claimPreviewSubtitle
claimPreviewTile
claimType
commentCreate
common
fileActions
fileReactions
fileSubtitle
fileTitleSection
fileType
fileViewCount
fileViewCountInline
header
livestreamComment
livestreamComments
livestreamLayout
livestreamLink
livestreamList
notification
page
publishFile
publishForm
router
constants
modal
page
discover
file
livestream
livestreamCurrent
livestreamSetup
ownComments
settingsCreator
show
redux
actions
reducers
selectors
scss
util
|
@ -7,7 +7,6 @@ import { useHistory } from 'react-router-dom';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ClaimListDiscover from 'component/claimListDiscover';
|
import ClaimListDiscover from 'component/claimListDiscover';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import LivestreamLink from 'component/livestreamLink';
|
|
||||||
import { Form, FormField } from 'component/common/form';
|
import { Form, FormField } from 'component/common/form';
|
||||||
import { DEBOUNCE_WAIT_DURATION_MS } from 'constants/search';
|
import { DEBOUNCE_WAIT_DURATION_MS } from 'constants/search';
|
||||||
import { lighthouse } from 'redux/actions/search';
|
import { lighthouse } from 'redux/actions/search';
|
||||||
|
@ -115,9 +114,6 @@ function ChannelContent(props: Props) {
|
||||||
{!fetching && Boolean(claimsInChannel) && !channelIsBlocked && !channelIsBlackListed && (
|
{!fetching && Boolean(claimsInChannel) && !channelIsBlocked && !channelIsBlackListed && (
|
||||||
<HiddenNsfwClaims uri={uri} />
|
<HiddenNsfwClaims uri={uri} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<LivestreamLink uri={uri} />
|
|
||||||
|
|
||||||
{!fetching && channelIsBlackListed && (
|
{!fetching && channelIsBlackListed && (
|
||||||
<section className="card card--section">
|
<section className="card card--section">
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -19,11 +19,9 @@ type Props = {
|
||||||
mentionTerm: string,
|
mentionTerm: string,
|
||||||
noTopSuggestion?: boolean,
|
noTopSuggestion?: boolean,
|
||||||
showMature: boolean,
|
showMature: boolean,
|
||||||
isLivestream: boolean,
|
|
||||||
creatorUri: string,
|
creatorUri: string,
|
||||||
commentorUris: Array<string>,
|
commentorUris: Array<string>,
|
||||||
subscriptionUris: Array<string>,
|
subscriptionUris: Array<string>,
|
||||||
unresolvedCommentors: Array<string>,
|
|
||||||
unresolvedSubscriptions: Array<string>,
|
unresolvedSubscriptions: Array<string>,
|
||||||
canonicalCreator: string,
|
canonicalCreator: string,
|
||||||
canonicalCommentors: Array<string>,
|
canonicalCommentors: Array<string>,
|
||||||
|
@ -34,10 +32,8 @@ type Props = {
|
||||||
|
|
||||||
export default function ChannelMentionSuggestions(props: Props) {
|
export default function ChannelMentionSuggestions(props: Props) {
|
||||||
const {
|
const {
|
||||||
unresolvedCommentors,
|
|
||||||
unresolvedSubscriptions,
|
unresolvedSubscriptions,
|
||||||
canonicalCreator,
|
canonicalCreator,
|
||||||
isLivestream,
|
|
||||||
creatorUri,
|
creatorUri,
|
||||||
inputRef,
|
inputRef,
|
||||||
showMature,
|
showMature,
|
||||||
|
@ -184,11 +180,6 @@ export default function ChannelMentionSuggestions(props: Props) {
|
||||||
}
|
}
|
||||||
}, [doResolveUris, stringifiedResults]);
|
}, [doResolveUris, stringifiedResults]);
|
||||||
|
|
||||||
// Only resolve commentors on Livestreams when actually mentioning/looking for it
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (isLivestream && unresolvedCommentors && mentionTerm) doResolveUris(unresolvedCommentors);
|
|
||||||
}, [doResolveUris, isLivestream, mentionTerm, unresolvedCommentors]);
|
|
||||||
|
|
||||||
// Only resolve the subscriptions that match the mention term, instead of all
|
// Only resolve the subscriptions that match the mention term, instead of all
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isTyping) return;
|
if (isTyping) return;
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
makeSelectClaimIsNsfw,
|
makeSelectClaimIsNsfw,
|
||||||
makeSelectReflectingClaimForUri,
|
makeSelectReflectingClaimForUri,
|
||||||
makeSelectClaimWasPurchased,
|
makeSelectClaimWasPurchased,
|
||||||
makeSelectClaimIsStreamPlaceholder,
|
|
||||||
makeSelectTitleForUri,
|
makeSelectTitleForUri,
|
||||||
makeSelectDateForUri,
|
makeSelectDateForUri,
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
|
@ -23,7 +22,6 @@ import { doCollectionEdit } from 'redux/actions/collections';
|
||||||
import { doFileGet } from 'redux/actions/file';
|
import { doFileGet } from 'redux/actions/file';
|
||||||
import { selectMutedChannels, makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
import { selectMutedChannels, makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||||
import { makeSelectIsActiveLivestream } from 'redux/selectors/livestream';
|
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
|
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
|
||||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||||
|
@ -58,8 +56,6 @@ const select = (state, props) => {
|
||||||
isSubscribed: props.uri && makeSelectIsSubscribed(props.uri, true)(state),
|
isSubscribed: props.uri && makeSelectIsSubscribed(props.uri, true)(state),
|
||||||
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),
|
||||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
|
||||||
isLivestreamActive: makeSelectIsActiveLivestream(props.uri)(state),
|
|
||||||
isCollectionMine: makeSelectCollectionIsMine(props.collectionId)(state),
|
isCollectionMine: makeSelectCollectionIsMine(props.collectionId)(state),
|
||||||
collectionUris: makeSelectUrlsForCollectionId(props.collectionId)(state),
|
collectionUris: makeSelectUrlsForCollectionId(props.collectionId)(state),
|
||||||
collectionIndex: makeSelectIndexForUrlInCollection(props.uri, props.collectionId)(state),
|
collectionIndex: makeSelectIndexForUrlInCollection(props.uri, props.collectionId)(state),
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { formatLbryUrlForWeb } from 'util/url';
|
||||||
import { formatClaimPreviewTitle } from 'util/formatAriaLabel';
|
import { formatClaimPreviewTitle } from 'util/formatAriaLabel';
|
||||||
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';
|
||||||
|
@ -26,7 +25,6 @@ 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 { ENABLE_NO_SOURCE_CLAIMS } from 'config';
|
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
|
|
||||||
|
@ -78,8 +76,6 @@ type Props = {
|
||||||
hideRepostLabel?: boolean,
|
hideRepostLabel?: boolean,
|
||||||
repostUrl?: string,
|
repostUrl?: string,
|
||||||
hideMenu?: boolean,
|
hideMenu?: boolean,
|
||||||
isLivestream?: boolean,
|
|
||||||
isLivestreamActive: boolean,
|
|
||||||
collectionId?: string,
|
collectionId?: string,
|
||||||
editCollection: (string, CollectionEditParams) => void,
|
editCollection: (string, CollectionEditParams) => void,
|
||||||
isCollectionMine: boolean,
|
isCollectionMine: boolean,
|
||||||
|
@ -144,8 +140,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
renderActions,
|
renderActions,
|
||||||
hideMenu = false,
|
hideMenu = false,
|
||||||
// repostUrl,
|
// repostUrl,
|
||||||
isLivestream, // need both? CHECK
|
|
||||||
isLivestreamActive,
|
|
||||||
collectionId,
|
collectionId,
|
||||||
collectionIndex,
|
collectionIndex,
|
||||||
editCollection,
|
editCollection,
|
||||||
|
@ -296,7 +290,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
}
|
}
|
||||||
}, [isValid, uri, isResolvingUri, shouldFetch, resolveUri]);
|
}, [isValid, uri, isResolvingUri, shouldFetch, resolveUri]);
|
||||||
|
|
||||||
if ((shouldHide && !showNullPlaceholder) || (isLivestream && !ENABLE_NO_SOURCE_CLAIMS)) {
|
if (shouldHide && !showNullPlaceholder) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,11 +323,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let liveProperty = null;
|
|
||||||
if (isLivestreamActive === true) {
|
|
||||||
liveProperty = (claim) => <>LIVE</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WrapperElement
|
<WrapperElement
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
@ -343,7 +332,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
'claim-preview__wrapper--channel': isChannelUri && type !== 'inline',
|
'claim-preview__wrapper--channel': isChannelUri && type !== 'inline',
|
||||||
'claim-preview__wrapper--inline': type === 'inline',
|
'claim-preview__wrapper--inline': type === 'inline',
|
||||||
'claim-preview__wrapper--small': type === 'small',
|
'claim-preview__wrapper--small': type === 'small',
|
||||||
'claim-preview__live': isLivestreamActive,
|
|
||||||
'claim-preview__active': active,
|
'claim-preview__active': active,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
@ -380,11 +368,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* @endif */}
|
{/* @endif */}
|
||||||
{(!isLivestream || isLivestreamActive) && (
|
|
||||||
<div className="claim-preview__file-property-overlay">
|
|
||||||
<PreviewOverlayProperties uri={uri} small={type === 'small'} properties={liveProperty} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</FileThumbnail>
|
</FileThumbnail>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import { makeSelectClaimForUri, makeSelectClaimIsPending } from 'redux/selectors/claims';
|
||||||
makeSelectClaimForUri,
|
|
||||||
makeSelectClaimIsPending,
|
|
||||||
makeSelectClaimIsStreamPlaceholder,
|
|
||||||
} from 'redux/selectors/claims';
|
|
||||||
import { doClearPublish, doPrepareEdit } from 'redux/actions/publish';
|
import { doClearPublish, doPrepareEdit } from 'redux/actions/publish';
|
||||||
import { push } from 'connected-react-router';
|
import { push } from 'connected-react-router';
|
||||||
import ClaimPreviewSubtitle from './view';
|
import ClaimPreviewSubtitle from './view';
|
||||||
|
@ -12,7 +8,6 @@ import ClaimPreviewSubtitle from './view';
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
pending: makeSelectClaimIsPending(props.uri)(state),
|
pending: makeSelectClaimIsPending(props.uri)(state),
|
||||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { ENABLE_NO_SOURCE_CLAIMS } from 'config';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import UriIndicator from 'component/uriIndicator';
|
import UriIndicator from 'component/uriIndicator';
|
||||||
import DateTime from 'component/dateTime';
|
import DateTime from 'component/dateTime';
|
||||||
|
@ -13,12 +12,11 @@ type Props = {
|
||||||
pending?: boolean,
|
pending?: boolean,
|
||||||
type: string,
|
type: string,
|
||||||
beginPublish: (?string) => void,
|
beginPublish: (?string) => void,
|
||||||
isLivestream: boolean,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// previews used in channel overview and homepage (and other places?)
|
// previews used in channel overview and homepage (and other places?)
|
||||||
function ClaimPreviewSubtitle(props: Props) {
|
function ClaimPreviewSubtitle(props: Props) {
|
||||||
const { pending, uri, claim, type, beginPublish, isLivestream } = props;
|
const { pending, uri, claim, type, beginPublish } = props;
|
||||||
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
||||||
|
|
||||||
let isChannel;
|
let isChannel;
|
||||||
|
@ -38,15 +36,12 @@ function ClaimPreviewSubtitle(props: Props) {
|
||||||
type !== 'inline' &&
|
type !== 'inline' &&
|
||||||
`${claimsInChannel} ${claimsInChannel === 1 ? __('upload') : __('uploads')}`}
|
`${claimsInChannel} ${claimsInChannel === 1 ? __('upload') : __('uploads')}`}
|
||||||
|
|
||||||
{!isChannel &&
|
{!isChannel && (
|
||||||
(isLivestream && ENABLE_NO_SOURCE_CLAIMS ? (
|
|
||||||
__('Livestream')
|
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
<FileViewCountInline uri={uri} isLivestream={isLivestream} />
|
<FileViewCountInline uri={uri} />
|
||||||
<DateTime timeAgo uri={uri} />
|
<DateTime timeAgo uri={uri} />
|
||||||
</>
|
</>
|
||||||
))}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
@ -6,14 +6,12 @@ import {
|
||||||
makeSelectTitleForUri,
|
makeSelectTitleForUri,
|
||||||
makeSelectChannelForClaimUri,
|
makeSelectChannelForClaimUri,
|
||||||
makeSelectClaimIsNsfw,
|
makeSelectClaimIsNsfw,
|
||||||
makeSelectClaimIsStreamPlaceholder,
|
|
||||||
makeSelectDateForUri,
|
makeSelectDateForUri,
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
import { doFileGet } from 'redux/actions/file';
|
import { doFileGet } from 'redux/actions/file';
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { makeSelectViewCountForUri, selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
import { makeSelectViewCountForUri, selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||||
import { makeSelectIsActiveLivestream } from 'redux/selectors/livestream';
|
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import ClaimPreviewTile from './view';
|
import ClaimPreviewTile from './view';
|
||||||
import formatMediaDuration from 'util/formatMediaDuration';
|
import formatMediaDuration from 'util/formatMediaDuration';
|
||||||
|
@ -36,8 +34,6 @@ const select = (state, props) => {
|
||||||
blockedChannelUris: selectMutedChannels(state),
|
blockedChannelUris: selectMutedChannels(state),
|
||||||
showMature: selectShowMatureContent(state),
|
showMature: selectShowMatureContent(state),
|
||||||
isMature: makeSelectClaimIsNsfw(props.uri)(state),
|
isMature: makeSelectClaimIsNsfw(props.uri)(state),
|
||||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
|
||||||
isLivestreamActive: makeSelectIsActiveLivestream(props.uri)(state),
|
|
||||||
viewCount: makeSelectViewCountForUri(props.uri)(state),
|
viewCount: makeSelectViewCountForUri(props.uri)(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,10 +49,7 @@ type Props = {
|
||||||
showHiddenByUser?: boolean,
|
showHiddenByUser?: boolean,
|
||||||
properties?: (Claim) => void,
|
properties?: (Claim) => void,
|
||||||
collectionId?: string,
|
collectionId?: string,
|
||||||
showNoSourceClaims?: boolean,
|
|
||||||
isLivestream: boolean,
|
|
||||||
viewCount: string,
|
viewCount: string,
|
||||||
isLivestreamActive: 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
|
||||||
|
@ -76,9 +73,6 @@ function ClaimPreviewTile(props: Props) {
|
||||||
showMature,
|
showMature,
|
||||||
showHiddenByUser,
|
showHiddenByUser,
|
||||||
properties,
|
properties,
|
||||||
showNoSourceClaims,
|
|
||||||
isLivestream,
|
|
||||||
isLivestreamActive,
|
|
||||||
collectionId,
|
collectionId,
|
||||||
mediaDuration,
|
mediaDuration,
|
||||||
viewCount,
|
viewCount,
|
||||||
|
@ -175,13 +169,13 @@ function ClaimPreviewTile(props: Props) {
|
||||||
shouldHide = blockedChannelUris.some((blockedUri) => isURIEqual(blockedUri, signingChannel.permanent_url));
|
shouldHide = blockedChannelUris.some((blockedUri) => isURIEqual(blockedUri, signingChannel.permanent_url));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldHide || (isLivestream && !showNoSourceClaims)) {
|
if (shouldHide) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isChannelPage = window.location.pathname.startsWith('/@');
|
const isChannelPage = window.location.pathname.startsWith('/@');
|
||||||
|
|
||||||
const shouldShowViewCount = !(!viewCount || (claim && claim.repost_url) || isLivestream || !isChannelPage);
|
const shouldShowViewCount = !(!viewCount || (claim && claim.repost_url) || !isChannelPage);
|
||||||
|
|
||||||
if (placeholder || (!claim && isResolvingUri)) {
|
if (placeholder || (!claim && isResolvingUri)) {
|
||||||
return (
|
return (
|
||||||
|
@ -202,16 +196,12 @@ function ClaimPreviewTile(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let liveProperty = null;
|
let liveProperty = null;
|
||||||
if (isLivestreamActive === true) {
|
|
||||||
liveProperty = (claim) => <>LIVE</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
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,
|
||||||
'claim-preview__live': isLivestreamActive,
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<NavLink {...navLinkProps} role="none" tabIndex={-1} aria-hidden>
|
<NavLink {...navLinkProps} role="none" tabIndex={-1} aria-hidden>
|
||||||
|
@ -273,7 +263,7 @@ function ClaimPreviewTile(props: Props) {
|
||||||
<div className="claim-tile__about">
|
<div className="claim-tile__about">
|
||||||
<UriIndicator uri={uri} link />
|
<UriIndicator uri={uri} link />
|
||||||
<div className="claim-tile__about--counts">
|
<div className="claim-tile__about--counts">
|
||||||
<FileViewCountInline uri={uri} isLivestream={isLivestream} />
|
<FileViewCountInline uri={uri} />
|
||||||
<DateTime timeAgo uri={uri} />
|
<DateTime timeAgo uri={uri} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectClaimForUri, makeSelectClaimIsStreamPlaceholder } from 'redux/selectors/claims';
|
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||||
import FileType from './view';
|
import FileType from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select)(FileType);
|
export default connect(select)(FileType);
|
||||||
|
|
|
@ -26,18 +26,7 @@ const select = (state, props) => ({
|
||||||
|
|
||||||
const perform = (dispatch, ownProps) => ({
|
const perform = (dispatch, ownProps) => ({
|
||||||
createComment: (comment, claimId, parentId, txid, payment_intent_id, environment) =>
|
createComment: (comment, claimId, parentId, txid, payment_intent_id, environment) =>
|
||||||
dispatch(
|
dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri, txid, payment_intent_id, environment)),
|
||||||
doCommentCreate(
|
|
||||||
comment,
|
|
||||||
claimId,
|
|
||||||
parentId,
|
|
||||||
ownProps.uri,
|
|
||||||
ownProps.livestream,
|
|
||||||
txid,
|
|
||||||
payment_intent_id,
|
|
||||||
environment
|
|
||||||
)
|
|
||||||
),
|
|
||||||
sendTip: (params, callback, errorCallback) => dispatch(doSendTip(params, false, callback, errorCallback, false)),
|
sendTip: (params, callback, errorCallback) => dispatch(doSendTip(params, false, callback, errorCallback, false)),
|
||||||
doToast: (options) => dispatch(doToast(options)),
|
doToast: (options) => dispatch(doToast(options)),
|
||||||
doFetchCreatorSettings: (channelClaimId) => dispatch(doFetchCreatorSettings(channelClaimId)),
|
doFetchCreatorSettings: (channelClaimId) => dispatch(doFetchCreatorSettings(channelClaimId)),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { FF_MAX_CHARS_IN_COMMENT, FF_MAX_CHARS_IN_LIVESTREAM_COMMENT } from 'constants/form-field';
|
import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
|
||||||
import { FormField, Form } from 'component/common/form';
|
import { FormField, Form } from 'component/common/form';
|
||||||
import { getChannelIdFromClaim } from 'util/claim';
|
import { getChannelIdFromClaim } from 'util/claim';
|
||||||
import { Lbryio } from 'lbryinc';
|
import { Lbryio } from 'lbryinc';
|
||||||
|
@ -40,7 +40,6 @@ type Props = {
|
||||||
activeChannel: string,
|
activeChannel: string,
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
bottom: boolean,
|
bottom: boolean,
|
||||||
livestream?: boolean,
|
|
||||||
embed?: boolean,
|
embed?: boolean,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
supportDisabled: boolean,
|
supportDisabled: boolean,
|
||||||
|
@ -68,8 +67,6 @@ export function CommentCreate(props: Props) {
|
||||||
parentId,
|
parentId,
|
||||||
activeChannelClaim,
|
activeChannelClaim,
|
||||||
bottom,
|
bottom,
|
||||||
livestream,
|
|
||||||
embed,
|
|
||||||
claimIsMine,
|
claimIsMine,
|
||||||
settingsByChannelId,
|
settingsByChannelId,
|
||||||
supportDisabled,
|
supportDisabled,
|
||||||
|
@ -179,7 +176,6 @@ export function CommentCreate(props: Props) {
|
||||||
let newMentionValue = mentionValue.replace('lbry://', '');
|
let newMentionValue = mentionValue.replace('lbry://', '');
|
||||||
if (newMentionValue.includes('#')) newMentionValue = newMentionValue.replace('#', ':');
|
if (newMentionValue.includes('#')) newMentionValue = newMentionValue.replace('#', ':');
|
||||||
|
|
||||||
if (livestream && key !== KEYCODES.TAB) setPauseQuickSend(true);
|
|
||||||
setCommentValue(
|
setCommentValue(
|
||||||
commentValue.substring(0, selectedMentionIndex) +
|
commentValue.substring(0, selectedMentionIndex) +
|
||||||
`${newMentionValue}` +
|
`${newMentionValue}` +
|
||||||
|
@ -190,7 +186,7 @@ export function CommentCreate(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function altEnterListener(e: SyntheticKeyboardEvent<*>) {
|
function altEnterListener(e: SyntheticKeyboardEvent<*>) {
|
||||||
if ((livestream || e.ctrlKey || e.metaKey) && e.keyCode === KEYCODES.ENTER) {
|
if ((e.ctrlKey || e.metaKey) && e.keyCode === KEYCODES.ENTER) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
buttonRef.current.click();
|
buttonRef.current.click();
|
||||||
}
|
}
|
||||||
|
@ -442,17 +438,8 @@ export function CommentCreate(props: Props) {
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (embed) {
|
|
||||||
window.open(`https://odysee.com/$/${PAGES.AUTH}?redirect=/$/${PAGES.LIVESTREAM}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathPlusRedirect = `/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`;
|
const pathPlusRedirect = `/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`;
|
||||||
if (livestream) {
|
|
||||||
window.open(pathPlusRedirect);
|
|
||||||
} else {
|
|
||||||
push(pathPlusRedirect);
|
push(pathPlusRedirect);
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormField type="textarea" name={'comment_signup_prompt'} placeholder={__('Say something about this...')} />
|
<FormField type="textarea" name={'comment_signup_prompt'} placeholder={__('Say something about this...')} />
|
||||||
|
@ -518,7 +505,6 @@ export function CommentCreate(props: Props) {
|
||||||
{!advancedEditor && (
|
{!advancedEditor && (
|
||||||
<ChannelMentionSuggestions
|
<ChannelMentionSuggestions
|
||||||
uri={uri}
|
uri={uri}
|
||||||
isLivestream={livestream}
|
|
||||||
inputRef={formFieldInputRef}
|
inputRef={formFieldInputRef}
|
||||||
mentionTerm={channelMention}
|
mentionTerm={channelMention}
|
||||||
creatorUri={channelUri}
|
creatorUri={channelUri}
|
||||||
|
@ -533,9 +519,7 @@ export function CommentCreate(props: Props) {
|
||||||
className={isReply ? 'content_reply' : 'content_comment'}
|
className={isReply ? 'content_reply' : 'content_comment'}
|
||||||
label={
|
label={
|
||||||
<span className="comment-new__label-wrapper">
|
<span className="comment-new__label-wrapper">
|
||||||
{!livestream && (
|
|
||||||
<div className="comment-new__label">{isReply ? __('Replying as') + ' ' : __('Comment as') + ' '}</div>
|
<div className="comment-new__label">{isReply ? __('Replying as') + ' ' : __('Comment as') + ' '}</div>
|
||||||
)}
|
|
||||||
<SelectChannel tiny />
|
<SelectChannel tiny />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
@ -548,7 +532,7 @@ export function CommentCreate(props: Props) {
|
||||||
charCount={charCount}
|
charCount={charCount}
|
||||||
onChange={handleCommentChange}
|
onChange={handleCommentChange}
|
||||||
autoFocus={isReply}
|
autoFocus={isReply}
|
||||||
textAreaMaxLength={livestream ? FF_MAX_CHARS_IN_LIVESTREAM_COMMENT : FF_MAX_CHARS_IN_COMMENT}
|
textAreaMaxLength={FF_MAX_CHARS_IN_COMMENT}
|
||||||
/>
|
/>
|
||||||
{isSupportComment && (
|
{isSupportComment && (
|
||||||
<WalletTipAmountSelector
|
<WalletTipAmountSelector
|
||||||
|
|
|
@ -1767,532 +1767,6 @@ export const icons = {
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
[ICONS.LIVESTREAM]: (props: CustomProps) => (
|
|
||||||
<svg
|
|
||||||
version="1.1"
|
|
||||||
id="Layer_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
viewBox="0 0 36 36"
|
|
||||||
width={props.size || '18'}
|
|
||||||
height={props.size || '16'}
|
|
||||||
className={props.className}
|
|
||||||
>
|
|
||||||
<g id="XMLID_505_">
|
|
||||||
<linearGradient
|
|
||||||
id="XMLID_420_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-519.065"
|
|
||||||
y1="1525.4059"
|
|
||||||
x2="-508.6628"
|
|
||||||
y2="1525.4059"
|
|
||||||
>
|
|
||||||
<stop offset="1.970443e-002" stopColor="#FFC200" />
|
|
||||||
<stop offset="0.3866" stopColor="#FF31BD" />
|
|
||||||
<stop offset="0.6245" stopColor="#8E31BD" />
|
|
||||||
<stop offset="0.7758" stopColor="#6E8EDE" />
|
|
||||||
<stop offset="1" stopColor="#57EABA" />
|
|
||||||
</linearGradient>
|
|
||||||
<circle
|
|
||||||
id="XMLID_508_"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(XMLID_420_)"
|
|
||||||
strokeWidth="2.4678"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit="10"
|
|
||||||
cx="-513.9"
|
|
||||||
cy="1525.4"
|
|
||||||
r="4"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
id="XMLID_507_"
|
|
||||||
fill="#FF7B5B"
|
|
||||||
d="M-521,1518.3c-1.8,1.8-2.9,4.3-2.9,7.1c0,2.6,1,4.9,2.5,6.7L-521,1518.3z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
id="XMLID_506_"
|
|
||||||
fill="#FF7B5B"
|
|
||||||
d="M-506.9,1532.1c1.8-1.8,2.9-4.3,2.9-7.1c0-2.6-1-4.9-2.5-6.7L-506.9,1532.1z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<rect id="XMLID_125_" x="0" y="0" fill="none" width="36" height="36" stroke="none" />
|
|
||||||
{/* }//fill="#FFFFFF" */}
|
|
||||||
<linearGradient
|
|
||||||
id="XMLID_421_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-1625.151"
|
|
||||||
y1="-2518.4661"
|
|
||||||
x2="-1596.6696"
|
|
||||||
y2="-2518.4661"
|
|
||||||
gradientTransform="matrix(-1 0 0 -1 -1589.489 -2500.4661)"
|
|
||||||
>
|
|
||||||
<stop offset="1.970443e-002" stopColor="#FFC200" />
|
|
||||||
<stop offset="0.4731" stopColor="#FF31BD" />
|
|
||||||
<stop offset="0.6947" stopColor="#8E31BD" />
|
|
||||||
<stop offset="1" stopColor="#57EABA" />
|
|
||||||
</linearGradient>
|
|
||||||
<path
|
|
||||||
id="XMLID_124_"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#XMLID_421_)"
|
|
||||||
strokeWidth="2.94"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit="10"
|
|
||||||
d="M21.4,5.2L21.4,5.2c7.1,0,12.8,5.7,12.8,12.8v0c0,7.1-5.7,12.8-12.8,12.8H8.7V18C8.7,10.9,14.4,5.2,21.4,5.2z"
|
|
||||||
/>
|
|
||||||
<linearGradient id="XMLID_422_" gradientUnits="userSpaceOnUse" x1="18.041" y1="32.147" x2="38.7776" y2="-0.9289">
|
|
||||||
<stop offset="1.970443e-002" stopColor="#FFC200" />
|
|
||||||
<stop offset="0.4731" stopColor="#FF31BD" />
|
|
||||||
<stop offset="0.6947" stopColor="#8E31BD" />
|
|
||||||
<stop offset="1" stopColor="#57EABA" />
|
|
||||||
</linearGradient>
|
|
||||||
<rect id="XMLID_123_" x="26.9" y="13.8" fill="url(#XMLID_422_)" stroke="none" width="2.8" height="3.8" />
|
|
||||||
<linearGradient
|
|
||||||
id="XMLID_423_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="13.0856"
|
|
||||||
y1="29.0402"
|
|
||||||
x2="33.8223"
|
|
||||||
y2="-4.0356"
|
|
||||||
>
|
|
||||||
<stop offset="1.970443e-002" stopColor="#FFC200" />
|
|
||||||
<stop offset="0.4731" stopColor="#FF31BD" />
|
|
||||||
<stop offset="0.6947" stopColor="#8E31BD" />
|
|
||||||
<stop offset="1" stopColor="#57EABA" />
|
|
||||||
</linearGradient>
|
|
||||||
<rect id="XMLID_122_" x="20" y="13.8" fill="url(#XMLID_422_)" stroke="none" width="2.8" height="3.8" />
|
|
||||||
<linearGradient id="XMLID_424_" gradientUnits="userSpaceOnUse" x1="0.338" y1="17.7555" x2="17.2654" y2="17.7555">
|
|
||||||
<stop offset="1.970443e-002" stopColor="#FFC200" />
|
|
||||||
<stop offset="0.4731" stopColor="#FF31BD" />
|
|
||||||
<stop offset="0.6947" stopColor="#8E31BD" />
|
|
||||||
<stop offset="1" stopColor="#57EABA" />
|
|
||||||
</linearGradient>
|
|
||||||
<circle
|
|
||||||
id="XMLID_121_"
|
|
||||||
fill="none"
|
|
||||||
stroke="#6E8EDE"
|
|
||||||
strokeWidth="2.94"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit="10"
|
|
||||||
cx="8.8"
|
|
||||||
cy="17.8"
|
|
||||||
r="6"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
[ICONS.LIVESTREAM_SOLID]: (props: CustomProps) => (
|
|
||||||
<svg
|
|
||||||
id="prefix__Layer_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
fill="none"
|
|
||||||
width={props.size || '18'}
|
|
||||||
height={props.size || '16'}
|
|
||||||
viewBox="0 0 36 36"
|
|
||||||
xmlSpace="preserve"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<style>{'.prefix__st1{fill:#ff7b5b}.prefix__st3{fill:#79d1b6}'}</style>
|
|
||||||
<g id="prefix__XMLID_505_">
|
|
||||||
<linearGradient
|
|
||||||
id="prefix__XMLID_410_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1={-571.815}
|
|
||||||
y1={1525.406}
|
|
||||||
x2={-561.413}
|
|
||||||
y2={1525.406}
|
|
||||||
>
|
|
||||||
<stop offset={0.02} stopColor="#ffc200" />
|
|
||||||
<stop offset={0.387} stopColor="#ff31bd" />
|
|
||||||
<stop offset={0.625} stopColor="#8e31bd" />
|
|
||||||
<stop offset={0.776} stopColor="#6e8ede" />
|
|
||||||
<stop offset={1} stopColor="#57eaba" />
|
|
||||||
</linearGradient>
|
|
||||||
<circle
|
|
||||||
id="prefix__XMLID_508_"
|
|
||||||
cx={-566.6}
|
|
||||||
cy={1525.4}
|
|
||||||
r={4}
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#prefix__XMLID_410_)"
|
|
||||||
strokeWidth={2.468}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
id="prefix__XMLID_507_"
|
|
||||||
className="prefix__st1"
|
|
||||||
d="M-573.7 1518.3c-1.8 1.8-2.9 4.3-2.9 7.1 0 2.6 1 4.9 2.5 6.7l.4-13.8z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
id="prefix__XMLID_506_"
|
|
||||||
className="prefix__st1"
|
|
||||||
d="M-559.6 1532.1c1.8-1.8 2.9-4.3 2.9-7.1 0-2.6-1-4.9-2.5-6.7l-.4 13.8z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
id="prefix__XMLID_20_"
|
|
||||||
d="M21.4 5.2h0c7.1 0 12.8 5.7 12.8 12.8v0c0 7.1-5.7 12.8-12.8 12.8H8.7V18c0-7.1 5.7-12.8 12.7-12.8z"
|
|
||||||
fill="none"
|
|
||||||
stroke="#e729e1"
|
|
||||||
strokeWidth={2.94}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
<path id="prefix__XMLID_19_" className="prefix__st3" stroke="none" d="M26.9 13.8h2.8v3.8h-2.8z" />
|
|
||||||
<path id="prefix__XMLID_18_" className="prefix__st3" stroke="none" d="M20 13.8h2.8v3.8H20z" />
|
|
||||||
<circle
|
|
||||||
id="prefix__XMLID_17_"
|
|
||||||
cx={8.8}
|
|
||||||
cy={17.8}
|
|
||||||
r={6}
|
|
||||||
fill="none"
|
|
||||||
stroke="#ffa100"
|
|
||||||
strokeWidth={2.94}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
[ICONS.LIVESTREAM_MONOCHROME]: (props: CustomProps) => (
|
|
||||||
<svg
|
|
||||||
id="prefix__Layer_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
fill="currentColor"
|
|
||||||
stroke="currentColor"
|
|
||||||
width={props.size || '18'}
|
|
||||||
height={props.size || '16'}
|
|
||||||
viewBox="0 0 36 36"
|
|
||||||
xmlSpace="preserve"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<g id="prefix__XMLID_505_">
|
|
||||||
<linearGradient
|
|
||||||
id="prefix__XMLID_410_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1={-571.815}
|
|
||||||
y1={1525.406}
|
|
||||||
x2={-561.413}
|
|
||||||
y2={1525.406}
|
|
||||||
>
|
|
||||||
<stop offset={0.02} stopColor="#ffc200" />
|
|
||||||
<stop offset={0.387} stopColor="#ff31bd" />
|
|
||||||
<stop offset={0.625} stopColor="#8e31bd" />
|
|
||||||
<stop offset={0.776} stopColor="#6e8ede" />
|
|
||||||
<stop offset={1} stopColor="#57eaba" />
|
|
||||||
</linearGradient>
|
|
||||||
<circle
|
|
||||||
id="prefix__XMLID_508_"
|
|
||||||
cx={-566.6}
|
|
||||||
cy={1525.4}
|
|
||||||
r={4}
|
|
||||||
fill="none"
|
|
||||||
strokeWidth={2.468}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
<path id="prefix__XMLID_507_" d="M-573.7 1518.3c-1.8 1.8-2.9 4.3-2.9 7.1 0 2.6 1 4.9 2.5 6.7l.4-13.8z" />
|
|
||||||
<path id="prefix__XMLID_506_" d="M-559.6 1532.1c1.8-1.8 2.9-4.3 2.9-7.1 0-2.6-1-4.9-2.5-6.7l-.4 13.8z" />
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
id="prefix__XMLID_20_"
|
|
||||||
d="M21.4 5.2h0c7.1 0 12.8 5.7 12.8 12.8v0c0 7.1-5.7 12.8-12.8 12.8H8.7V18c0-7.1 5.7-12.8 12.7-12.8z"
|
|
||||||
fill="none"
|
|
||||||
strokeWidth={2.94}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
<path id="prefix__XMLID_19_" d="M26.9 13.8h2.8v3.8h-2.8z" />
|
|
||||||
<path id="prefix__XMLID_18_" d="M20 13.8h2.8v3.8H20z" />
|
|
||||||
<circle
|
|
||||||
id="prefix__XMLID_17_"
|
|
||||||
cx={8.8}
|
|
||||||
cy={17.8}
|
|
||||||
r={6}
|
|
||||||
fill="none"
|
|
||||||
strokeWidth={2.94}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
|
|
||||||
[ICONS.LIVESTREAM]: (props: CustomProps) => (
|
|
||||||
<svg
|
|
||||||
version="1.1"
|
|
||||||
id="Layer_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
viewBox="0 0 36 36"
|
|
||||||
width={props.size || '18'}
|
|
||||||
height={props.size || '16'}
|
|
||||||
>
|
|
||||||
<g id="XMLID_505_">
|
|
||||||
<linearGradient
|
|
||||||
id="XMLID_420_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-519.065"
|
|
||||||
y1="1525.4059"
|
|
||||||
x2="-508.6628"
|
|
||||||
y2="1525.4059"
|
|
||||||
>
|
|
||||||
<stop offset="1.970443e-002" stopColor="#FFC200" />
|
|
||||||
<stop offset="0.3866" stopColor="#FF31BD" />
|
|
||||||
<stop offset="0.6245" stopColor="#8E31BD" />
|
|
||||||
<stop offset="0.7758" stopColor="#6E8EDE" />
|
|
||||||
<stop offset="1" stopColor="#57EABA" />
|
|
||||||
</linearGradient>
|
|
||||||
<circle
|
|
||||||
id="XMLID_508_"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(XMLID_420_)"
|
|
||||||
strokeWidth="2.4678"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit="10"
|
|
||||||
cx="-513.9"
|
|
||||||
cy="1525.4"
|
|
||||||
r="4"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
id="XMLID_507_"
|
|
||||||
fill="#FF7B5B"
|
|
||||||
d="M-521,1518.3c-1.8,1.8-2.9,4.3-2.9,7.1c0,2.6,1,4.9,2.5,6.7L-521,1518.3z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
id="XMLID_506_"
|
|
||||||
fill="#FF7B5B"
|
|
||||||
d="M-506.9,1532.1c1.8-1.8,2.9-4.3,2.9-7.1c0-2.6-1-4.9-2.5-6.7L-506.9,1532.1z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<rect id="XMLID_125_" x="0" y="0" fill="none" width="36" height="36" stroke="none" />
|
|
||||||
{/* }//fill="#FFFFFF" */}
|
|
||||||
<linearGradient
|
|
||||||
id="XMLID_421_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="-1625.151"
|
|
||||||
y1="-2518.4661"
|
|
||||||
x2="-1596.6696"
|
|
||||||
y2="-2518.4661"
|
|
||||||
gradientTransform="matrix(-1 0 0 -1 -1589.489 -2500.4661)"
|
|
||||||
>
|
|
||||||
<stop offset="1.970443e-002" stopColor="#FFC200" />
|
|
||||||
<stop offset="0.4731" stopColor="#FF31BD" />
|
|
||||||
<stop offset="0.6947" stopColor="#8E31BD" />
|
|
||||||
<stop offset="1" stopColor="#57EABA" />
|
|
||||||
</linearGradient>
|
|
||||||
<path
|
|
||||||
id="XMLID_124_"
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#XMLID_421_)"
|
|
||||||
strokeWidth="2.94"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit="10"
|
|
||||||
d="M21.4,5.2L21.4,5.2c7.1,0,12.8,5.7,12.8,12.8v0c0,7.1-5.7,12.8-12.8,12.8H8.7V18C8.7,10.9,14.4,5.2,21.4,5.2z"
|
|
||||||
/>
|
|
||||||
<linearGradient id="XMLID_422_" gradientUnits="userSpaceOnUse" x1="18.041" y1="32.147" x2="38.7776" y2="-0.9289">
|
|
||||||
<stop offset="1.970443e-002" stopColor="#FFC200" />
|
|
||||||
<stop offset="0.4731" stopColor="#FF31BD" />
|
|
||||||
<stop offset="0.6947" stopColor="#8E31BD" />
|
|
||||||
<stop offset="1" stopColor="#57EABA" />
|
|
||||||
</linearGradient>
|
|
||||||
<rect id="XMLID_123_" x="26.9" y="13.8" fill="url(#XMLID_422_)" stroke="none" width="2.8" height="3.8" />
|
|
||||||
<linearGradient
|
|
||||||
id="XMLID_423_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1="13.0856"
|
|
||||||
y1="29.0402"
|
|
||||||
x2="33.8223"
|
|
||||||
y2="-4.0356"
|
|
||||||
>
|
|
||||||
<stop offset="1.970443e-002" stopColor="#FFC200" />
|
|
||||||
<stop offset="0.4731" stopColor="#FF31BD" />
|
|
||||||
<stop offset="0.6947" stopColor="#8E31BD" />
|
|
||||||
<stop offset="1" stopColor="#57EABA" />
|
|
||||||
</linearGradient>
|
|
||||||
<rect id="XMLID_122_" x="20" y="13.8" fill="url(#XMLID_422_)" stroke="none" width="2.8" height="3.8" />
|
|
||||||
<linearGradient id="XMLID_424_" gradientUnits="userSpaceOnUse" x1="0.338" y1="17.7555" x2="17.2654" y2="17.7555">
|
|
||||||
<stop offset="1.970443e-002" stopColor="#FFC200" />
|
|
||||||
<stop offset="0.4731" stopColor="#FF31BD" />
|
|
||||||
<stop offset="0.6947" stopColor="#8E31BD" />
|
|
||||||
<stop offset="1" stopColor="#57EABA" />
|
|
||||||
</linearGradient>
|
|
||||||
<circle
|
|
||||||
id="XMLID_121_"
|
|
||||||
fill="none"
|
|
||||||
stroke="#6E8EDE"
|
|
||||||
strokeWidth="2.94"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit="10"
|
|
||||||
cx="8.8"
|
|
||||||
cy="17.8"
|
|
||||||
r="6"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
[ICONS.LIVESTREAM_SOLID]: (props: CustomProps) => (
|
|
||||||
<svg
|
|
||||||
id="prefix__Layer_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
fill="none"
|
|
||||||
width={props.size || '18'}
|
|
||||||
height={props.size || '16'}
|
|
||||||
viewBox="0 0 36 36"
|
|
||||||
xmlSpace="preserve"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<style>{'.prefix__st1{fill:#ff7b5b}.prefix__st3{fill:#79d1b6}'}</style>
|
|
||||||
<g id="prefix__XMLID_505_">
|
|
||||||
<linearGradient
|
|
||||||
id="prefix__XMLID_410_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1={-571.815}
|
|
||||||
y1={1525.406}
|
|
||||||
x2={-561.413}
|
|
||||||
y2={1525.406}
|
|
||||||
>
|
|
||||||
<stop offset={0.02} stopColor="#ffc200" />
|
|
||||||
<stop offset={0.387} stopColor="#ff31bd" />
|
|
||||||
<stop offset={0.625} stopColor="#8e31bd" />
|
|
||||||
<stop offset={0.776} stopColor="#6e8ede" />
|
|
||||||
<stop offset={1} stopColor="#57eaba" />
|
|
||||||
</linearGradient>
|
|
||||||
<circle
|
|
||||||
id="prefix__XMLID_508_"
|
|
||||||
cx={-566.6}
|
|
||||||
cy={1525.4}
|
|
||||||
r={4}
|
|
||||||
fill="none"
|
|
||||||
stroke="url(#prefix__XMLID_410_)"
|
|
||||||
strokeWidth={2.468}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
id="prefix__XMLID_507_"
|
|
||||||
className="prefix__st1"
|
|
||||||
d="M-573.7 1518.3c-1.8 1.8-2.9 4.3-2.9 7.1 0 2.6 1 4.9 2.5 6.7l.4-13.8z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
id="prefix__XMLID_506_"
|
|
||||||
className="prefix__st1"
|
|
||||||
d="M-559.6 1532.1c1.8-1.8 2.9-4.3 2.9-7.1 0-2.6-1-4.9-2.5-6.7l-.4 13.8z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
id="prefix__XMLID_20_"
|
|
||||||
d="M21.4 5.2h0c7.1 0 12.8 5.7 12.8 12.8v0c0 7.1-5.7 12.8-12.8 12.8H8.7V18c0-7.1 5.7-12.8 12.7-12.8z"
|
|
||||||
fill="none"
|
|
||||||
stroke="#e729e1"
|
|
||||||
strokeWidth={2.94}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
<path id="prefix__XMLID_19_" className="prefix__st3" stroke="none" d="M26.9 13.8h2.8v3.8h-2.8z" />
|
|
||||||
<path id="prefix__XMLID_18_" className="prefix__st3" stroke="none" d="M20 13.8h2.8v3.8H20z" />
|
|
||||||
<circle
|
|
||||||
id="prefix__XMLID_17_"
|
|
||||||
cx={8.8}
|
|
||||||
cy={17.8}
|
|
||||||
r={6}
|
|
||||||
fill="none"
|
|
||||||
stroke="#ffa100"
|
|
||||||
strokeWidth={2.94}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
[ICONS.LIVESTREAM_MONOCHROME]: (props: CustomProps) => (
|
|
||||||
<svg
|
|
||||||
id="prefix__Layer_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
fill="currentColor"
|
|
||||||
stroke="currentColor"
|
|
||||||
width={props.size || '18'}
|
|
||||||
height={props.size || '16'}
|
|
||||||
viewBox="0 0 36 36"
|
|
||||||
xmlSpace="preserve"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<g id="prefix__XMLID_505_">
|
|
||||||
<linearGradient
|
|
||||||
id="prefix__XMLID_410_"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
x1={-571.815}
|
|
||||||
y1={1525.406}
|
|
||||||
x2={-561.413}
|
|
||||||
y2={1525.406}
|
|
||||||
>
|
|
||||||
<stop offset={0.02} stopColor="#ffc200" />
|
|
||||||
<stop offset={0.387} stopColor="#ff31bd" />
|
|
||||||
<stop offset={0.625} stopColor="#8e31bd" />
|
|
||||||
<stop offset={0.776} stopColor="#6e8ede" />
|
|
||||||
<stop offset={1} stopColor="#57eaba" />
|
|
||||||
</linearGradient>
|
|
||||||
<circle
|
|
||||||
id="prefix__XMLID_508_"
|
|
||||||
cx={-566.6}
|
|
||||||
cy={1525.4}
|
|
||||||
r={4}
|
|
||||||
fill="none"
|
|
||||||
strokeWidth={2.468}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
<path id="prefix__XMLID_507_" d="M-573.7 1518.3c-1.8 1.8-2.9 4.3-2.9 7.1 0 2.6 1 4.9 2.5 6.7l.4-13.8z" />
|
|
||||||
<path id="prefix__XMLID_506_" d="M-559.6 1532.1c1.8-1.8 2.9-4.3 2.9-7.1 0-2.6-1-4.9-2.5-6.7l-.4 13.8z" />
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
id="prefix__XMLID_20_"
|
|
||||||
d="M21.4 5.2h0c7.1 0 12.8 5.7 12.8 12.8v0c0 7.1-5.7 12.8-12.8 12.8H8.7V18c0-7.1 5.7-12.8 12.7-12.8z"
|
|
||||||
fill="none"
|
|
||||||
strokeWidth={2.94}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
<path id="prefix__XMLID_19_" d="M26.9 13.8h2.8v3.8h-2.8z" />
|
|
||||||
<path id="prefix__XMLID_18_" d="M20 13.8h2.8v3.8H20z" />
|
|
||||||
<circle
|
|
||||||
id="prefix__XMLID_17_"
|
|
||||||
cx={8.8}
|
|
||||||
cy={17.8}
|
|
||||||
r={6}
|
|
||||||
fill="none"
|
|
||||||
strokeWidth={2.94}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit={10}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
[ICONS.STACK]: (props: CustomProps) => (
|
[ICONS.STACK]: (props: CustomProps) => (
|
||||||
<svg
|
<svg
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import {
|
||||||
makeSelectClaimIsMine,
|
makeSelectClaimIsMine,
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
selectMyChannelClaims,
|
selectMyChannelClaims,
|
||||||
makeSelectClaimIsStreamPlaceholder,
|
|
||||||
makeSelectTagInClaimOrChannelForUri,
|
makeSelectTagInClaimOrChannelForUri,
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
import { makeSelectStreamingUrlForUri, makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
import { makeSelectStreamingUrlForUri, makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
||||||
|
@ -24,7 +23,6 @@ const select = (state, props) => ({
|
||||||
renderMode: makeSelectFileRenderModeForUri(props.uri)(state),
|
renderMode: makeSelectFileRenderModeForUri(props.uri)(state),
|
||||||
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||||
myChannels: selectMyChannelClaims(state),
|
myChannels: selectMyChannelClaims(state),
|
||||||
isLivestreamClaim: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
|
||||||
reactionsDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state),
|
reactionsDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state),
|
||||||
streamingUrl: makeSelectStreamingUrlForUri(props.uri)(state),
|
streamingUrl: makeSelectStreamingUrlForUri(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,7 +30,6 @@ type Props = {
|
||||||
doToast: ({ message: string }) => void,
|
doToast: ({ message: string }) => void,
|
||||||
clearPlayingUri: () => void,
|
clearPlayingUri: () => void,
|
||||||
hideRepost?: boolean,
|
hideRepost?: boolean,
|
||||||
isLivestreamClaim: boolean,
|
|
||||||
reactionsDisabled: boolean,
|
reactionsDisabled: boolean,
|
||||||
download: (string) => void,
|
download: (string) => void,
|
||||||
streamingUrl: ?string,
|
streamingUrl: ?string,
|
||||||
|
@ -50,7 +49,6 @@ function FileActions(props: Props) {
|
||||||
clearPlayingUri,
|
clearPlayingUri,
|
||||||
doToast,
|
doToast,
|
||||||
hideRepost,
|
hideRepost,
|
||||||
isLivestreamClaim,
|
|
||||||
reactionsDisabled,
|
reactionsDisabled,
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
|
@ -96,7 +94,7 @@ function FileActions(props: Props) {
|
||||||
|
|
||||||
const lhsSection = (
|
const lhsSection = (
|
||||||
<>
|
<>
|
||||||
{ENABLE_FILE_REACTIONS && !reactionsDisabled && <FileReactions uri={uri} livestream={isLivestreamClaim} />}
|
{ENABLE_FILE_REACTIONS && !reactionsDisabled && <FileReactions uri={uri} />}
|
||||||
<ClaimSupportButton uri={uri} fileAction />
|
<ClaimSupportButton uri={uri} fileAction />
|
||||||
<ClaimCollectionAddButton uri={uri} fileAction />
|
<ClaimCollectionAddButton uri={uri} fileAction />
|
||||||
{!hideRepost && (
|
{!hideRepost && (
|
||||||
|
@ -124,14 +122,12 @@ function FileActions(props: Props) {
|
||||||
|
|
||||||
const rhsSection = (
|
const rhsSection = (
|
||||||
<>
|
<>
|
||||||
{/* @if TARGET='app' */}
|
|
||||||
<FileDownloadLink uri={uri} />
|
<FileDownloadLink uri={uri} />
|
||||||
{/* @endif */}
|
|
||||||
{claimIsMine && (
|
{claimIsMine && (
|
||||||
<Button
|
<Button
|
||||||
className="button--file-action"
|
className="button--file-action"
|
||||||
icon={ICONS.EDIT}
|
icon={ICONS.EDIT}
|
||||||
label={isLivestreamClaim ? __('Update') : __('Edit')}
|
label={__('Edit')}
|
||||||
navigate={`/$/${PAGES.UPLOAD}`}
|
navigate={`/$/${PAGES.UPLOAD}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
prepareEdit(claim, editUri, fileInfo);
|
prepareEdit(claim, editUri, fileInfo);
|
||||||
|
@ -147,7 +143,7 @@ function FileActions(props: Props) {
|
||||||
onClick={() => openModal(MODALS.CONFIRM_FILE_REMOVE, { uri })}
|
onClick={() => openModal(MODALS.CONFIRM_FILE_REMOVE, { uri })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(!isLivestreamClaim || !claimIsMine) && (
|
{!claimIsMine && (
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
className="button--file-action"
|
className="button--file-action"
|
||||||
|
@ -159,7 +155,6 @@ function FileActions(props: Props) {
|
||||||
<Icon size={20} icon={ICONS.MORE} />
|
<Icon size={20} icon={ICONS.MORE} />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList className="menu__list">
|
<MenuList className="menu__list">
|
||||||
{!claimIsMine && (
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className="comment__menu-option"
|
className="comment__menu-option"
|
||||||
onSelect={() => push(`/$/${PAGES.REPORT_CONTENT}?claimId=${claimId}`)}
|
onSelect={() => push(`/$/${PAGES.REPORT_CONTENT}?claimId=${claimId}`)}
|
||||||
|
@ -169,7 +164,6 @@ function FileActions(props: Props) {
|
||||||
{__('Report content')}
|
{__('Report content')}
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -16,7 +16,6 @@ type Props = {
|
||||||
likeCount: number,
|
likeCount: number,
|
||||||
dislikeCount: number,
|
dislikeCount: number,
|
||||||
myReaction: ?string,
|
myReaction: ?string,
|
||||||
livestream?: boolean,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function FileReactions(props: Props) {
|
function FileReactions(props: Props) {
|
||||||
|
@ -29,7 +28,6 @@ function FileReactions(props: Props) {
|
||||||
myReaction,
|
myReaction,
|
||||||
likeCount,
|
likeCount,
|
||||||
dislikeCount,
|
dislikeCount,
|
||||||
livestream,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const claimId = claim && claim.claim_id;
|
const claimId = claim && claim.claim_id;
|
||||||
|
@ -42,21 +40,10 @@ function FileReactions(props: Props) {
|
||||||
doFetchReactions(claimId);
|
doFetchReactions(claimId);
|
||||||
}
|
}
|
||||||
|
|
||||||
let fetchInterval;
|
|
||||||
if (claimId) {
|
if (claimId) {
|
||||||
fetchReactions();
|
fetchReactions();
|
||||||
|
|
||||||
if (livestream) {
|
|
||||||
fetchInterval = setInterval(fetchReactions, 45000);
|
|
||||||
}
|
}
|
||||||
}
|
}, [claimId, doFetchReactions]);
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (fetchInterval) {
|
|
||||||
clearInterval(fetchInterval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [claimId, doFetchReactions, livestream]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -98,7 +85,9 @@ function FileReactions(props: Props) {
|
||||||
requiresAuth={IS_WEB}
|
requiresAuth={IS_WEB}
|
||||||
authSrc={'filereaction_dislike'}
|
authSrc={'filereaction_dislike'}
|
||||||
title={__('I dislike this')}
|
title={__('I dislike this')}
|
||||||
className={classnames('button--file-action', { 'button--file-action-active': myReaction === REACTION_TYPES.DISLIKE })}
|
className={classnames('button--file-action', {
|
||||||
|
'button--file-action-active': myReaction === REACTION_TYPES.DISLIKE,
|
||||||
|
})}
|
||||||
label={<>{formatNumberWithCommas(dislikeCount, 0)}</>}
|
label={<>{formatNumberWithCommas(dislikeCount, 0)}</>}
|
||||||
iconSize={18}
|
iconSize={18}
|
||||||
icon={dislikeIcon}
|
icon={dislikeIcon}
|
||||||
|
|
|
@ -1,28 +1,22 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import DateTime from 'component/dateTime';
|
|
||||||
import FileViewCount from 'component/fileViewCount';
|
import FileViewCount from 'component/fileViewCount';
|
||||||
import FileActions from 'component/fileActions';
|
import FileActions from 'component/fileActions';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
livestream?: boolean,
|
|
||||||
activeViewers?: number,
|
|
||||||
isLive?: boolean,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function FileSubtitle(props: Props) {
|
function FileSubtitle(props: Props) {
|
||||||
const { uri, livestream = false, activeViewers, isLive = false } = props;
|
const { uri } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="media__subtitle--between">
|
<div className="media__subtitle--between">
|
||||||
<div className="file__viewdate">
|
<div className="file__viewdate">
|
||||||
{livestream ? <span>{__('Right now')}</span> : <DateTime uri={uri} show={DateTime.SHOW_DATE} />}
|
<FileViewCount uri={uri} />
|
||||||
|
|
||||||
<FileViewCount uri={uri} livestream={livestream} activeViewers={activeViewers} isLive={isLive} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FileActions uri={uri} hideRepost={livestream} livestream={livestream} />
|
<FileActions uri={uri} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,15 @@ import { connect } from 'react-redux';
|
||||||
import { doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc';
|
import { doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc';
|
||||||
import { makeSelectTitleForUri, makeSelectClaimForUri } from 'redux/selectors/claims';
|
import { makeSelectTitleForUri, makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||||
import { makeSelectInsufficientCreditsForUri } from 'redux/selectors/content';
|
import { makeSelectInsufficientCreditsForUri } from 'redux/selectors/content';
|
||||||
import { makeSelectViewersForId } from 'redux/selectors/livestream';
|
|
||||||
import FileTitleSection from './view';
|
import FileTitleSection from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const claim = makeSelectClaimForUri(props.uri)(state);
|
const claim = makeSelectClaimForUri(props.uri)(state);
|
||||||
const viewers = claim && makeSelectViewersForId(claim.claim_id)(state);
|
|
||||||
const channelClaimId = claim && claim.signing_channel ? claim.signing_channel.claim_id : undefined;
|
const channelClaimId = claim && claim.signing_channel ? claim.signing_channel.claim_id : undefined;
|
||||||
const channelUri = claim && claim.signing_channel ? claim.signing_channel.canonical_url : undefined;
|
const channelUri = claim && claim.signing_channel ? claim.signing_channel.canonical_url : undefined;
|
||||||
const subCount = channelUri && makeSelectSubCountForUri(channelUri)(state);
|
const subCount = channelUri && makeSelectSubCountForUri(channelUri)(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
viewers,
|
|
||||||
isInsufficientCredits: makeSelectInsufficientCreditsForUri(props.uri)(state),
|
isInsufficientCredits: makeSelectInsufficientCreditsForUri(props.uri)(state),
|
||||||
title: makeSelectTitleForUri(props.uri)(state),
|
title: makeSelectTitleForUri(props.uri)(state),
|
||||||
channelClaimId,
|
channelClaimId,
|
||||||
|
|
|
@ -20,27 +20,13 @@ type Props = {
|
||||||
title: string,
|
title: string,
|
||||||
nsfw: boolean,
|
nsfw: boolean,
|
||||||
isNsfwBlocked: boolean,
|
isNsfwBlocked: boolean,
|
||||||
livestream?: boolean,
|
|
||||||
isLive?: boolean,
|
|
||||||
viewers?: number,
|
|
||||||
subCount: number,
|
subCount: number,
|
||||||
channelClaimId?: string,
|
channelClaimId?: string,
|
||||||
fetchSubCount: (string) => void,
|
fetchSubCount: (string) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function FileTitleSection(props: Props) {
|
function FileTitleSection(props: Props) {
|
||||||
const {
|
const { title, uri, nsfw, isNsfwBlocked, subCount, channelClaimId, fetchSubCount } = props;
|
||||||
title,
|
|
||||||
uri,
|
|
||||||
nsfw,
|
|
||||||
isNsfwBlocked,
|
|
||||||
livestream = false,
|
|
||||||
isLive = false,
|
|
||||||
viewers,
|
|
||||||
subCount,
|
|
||||||
channelClaimId,
|
|
||||||
fetchSubCount,
|
|
||||||
} = props;
|
|
||||||
const [hasAcknowledgedSec, setHasAcknowledgedSec] = usePersistedState('sec-nag', false);
|
const [hasAcknowledgedSec, setHasAcknowledgedSec] = usePersistedState('sec-nag', false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -78,7 +64,7 @@ function FileTitleSection(props: Props) {
|
||||||
body={
|
body={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ClaimInsufficientCredits uri={uri} />
|
<ClaimInsufficientCredits uri={uri} />
|
||||||
<FileSubtitle uri={uri} isLive={isLive} livestream={livestream} activeViewers={viewers} />
|
<FileSubtitle uri={uri} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectMediaTypeForUri } from 'redux/selectors/file_info';
|
import { makeSelectMediaTypeForUri } from 'redux/selectors/file_info';
|
||||||
import { makeSelectClaimIsStreamPlaceholder } from 'redux/selectors/claims';
|
|
||||||
import FileType from './view';
|
import FileType from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
mediaType: makeSelectMediaTypeForUri(props.uri)(state),
|
mediaType: makeSelectMediaTypeForUri(props.uri)(state),
|
||||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select)(FileType);
|
export default connect(select)(FileType);
|
||||||
|
|
|
@ -7,19 +7,18 @@ import * as COL from 'constants/collections';
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
mediaType: string,
|
mediaType: string,
|
||||||
isLivestream: boolean,
|
|
||||||
small: boolean,
|
small: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function FileType(props: Props) {
|
function FileType(props: Props) {
|
||||||
const { mediaType, isLivestream, small } = props;
|
const { mediaType, small } = props;
|
||||||
const size = small ? COL.ICON_SIZE : undefined;
|
const size = small ? COL.ICON_SIZE : undefined;
|
||||||
|
|
||||||
if (mediaType === 'image') {
|
if (mediaType === 'image') {
|
||||||
return <Icon size={size} icon={ICONS.IMAGE} />;
|
return <Icon size={size} icon={ICONS.IMAGE} />;
|
||||||
} else if (mediaType === 'audio') {
|
} else if (mediaType === 'audio') {
|
||||||
return <Icon size={size} icon={ICONS.AUDIO} />;
|
return <Icon size={size} icon={ICONS.AUDIO} />;
|
||||||
} else if (mediaType === 'video' || isLivestream) {
|
} else if (mediaType === 'video') {
|
||||||
return <Icon size={size} icon={ICONS.VIDEO} />;
|
return <Icon size={size} icon={ICONS.VIDEO} />;
|
||||||
} else if (mediaType === 'text') {
|
} else if (mediaType === 'text') {
|
||||||
return <Icon size={size} icon={ICONS.TEXT} />;
|
return <Icon size={size} icon={ICONS.TEXT} />;
|
||||||
|
|
|
@ -8,24 +8,12 @@ type Props = {
|
||||||
fetchingViewCount: boolean,
|
fetchingViewCount: boolean,
|
||||||
uri: string,
|
uri: string,
|
||||||
viewCount: string,
|
viewCount: string,
|
||||||
livestream?: boolean,
|
|
||||||
activeViewers?: number,
|
|
||||||
isLive?: boolean,
|
|
||||||
doAnalyticsView: (string) => void,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function FileViewCount(props: Props) {
|
function FileViewCount(props: Props) {
|
||||||
const { claim, uri, fetchViewCount, viewCount, livestream, activeViewers, doAnalyticsView } = props;
|
const { claim, uri, fetchViewCount, viewCount } = props;
|
||||||
const claimId = claim && claim.claim_id;
|
const claimId = claim && claim.claim_id;
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (livestream) {
|
|
||||||
// Regular claims will call the file/view event when a user actually watches the claim
|
|
||||||
// This can be removed when we get rid of the livestream iframe
|
|
||||||
doAnalyticsView(uri);
|
|
||||||
}
|
|
||||||
}, [livestream, doAnalyticsView, uri]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (claimId) {
|
if (claimId) {
|
||||||
fetchViewCount(claimId);
|
fetchViewCount(claimId);
|
||||||
|
@ -36,9 +24,7 @@ function FileViewCount(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="media__subtitle--centered">
|
<span className="media__subtitle--centered">
|
||||||
{!livestream &&
|
{viewCount !== 1 ? __('%view_count% views', { view_count: formattedViewCount }) : __('1 view')}
|
||||||
activeViewers === undefined &&
|
|
||||||
(viewCount !== 1 ? __('%view_count% views', { view_count: formattedViewCount }) : __('1 view'))}
|
|
||||||
{<HelpLink href="https://lbry.com/faq/views" />}
|
{<HelpLink href="https://lbry.com/faq/views" />}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'scss/component/_view_count.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
isLivestream?: boolean,
|
|
||||||
// --- select ---
|
// --- select ---
|
||||||
claim: ?StreamClaim,
|
claim: ?StreamClaim,
|
||||||
viewCount: string,
|
viewCount: string,
|
||||||
|
@ -12,7 +11,7 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FileViewCountInline(props: Props) {
|
export default function FileViewCountInline(props: Props) {
|
||||||
const { isLivestream, claim, viewCount, lang } = props;
|
const { claim, viewCount, lang } = props;
|
||||||
let formattedViewCount;
|
let formattedViewCount;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -30,8 +29,8 @@ export default function FileViewCountInline(props: Props) {
|
||||||
// clean up (only one place edit/remove).
|
// clean up (only one place edit/remove).
|
||||||
const isChannelPage = window.location.pathname.startsWith('/@');
|
const isChannelPage = window.location.pathname.startsWith('/@');
|
||||||
|
|
||||||
// dont show if no view count, if it's a repost, a livestream or isn't a channel page
|
// dont show if no view count, if it's a repost or isn't a channel page
|
||||||
if (!viewCount || (claim && claim.repost_url) || isLivestream || !isChannelPage) {
|
if (!viewCount || (claim && claim.repost_url) || !isChannelPage) {
|
||||||
// (1) Currently, makeSelectViewCountForUri doesn't differentiate between
|
// (1) Currently, makeSelectViewCountForUri doesn't differentiate between
|
||||||
// un-fetched view-count vs zero view-count. But since it's probably not
|
// un-fetched view-count vs zero view-count. But since it's probably not
|
||||||
// ideal to highlight that a view has 0 count, let's just not show anything.
|
// ideal to highlight that a view has 0 count, let's just not show anything.
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { doSignOut, doOpenModal } from 'redux/actions/app';
|
import { doSignOut, doOpenModal } from 'redux/actions/app';
|
||||||
import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
||||||
import { selectHasNavigated, selectActiveChannelClaim, selectActiveChannelStakedLevel } from 'redux/selectors/app';
|
import { selectHasNavigated, selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
import Header from './view';
|
import Header from './view';
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = (state) => ({
|
||||||
|
@ -27,7 +27,6 @@ const select = (state) => ({
|
||||||
hasNavigated: selectHasNavigated(state),
|
hasNavigated: selectHasNavigated(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
activeChannelClaim: selectActiveChannelClaim(state),
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
activeChannelStakedLevel: selectActiveChannelStakedLevel(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { ENABLE_NO_SOURCE_CLAIMS, CHANNEL_STAKED_LEVEL_LIVESTREAM, ENABLE_UI_NOTIFICATIONS } from 'config';
|
import { ENABLE_UI_NOTIFICATIONS } from 'config';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
|
@ -63,7 +63,6 @@ type Props = {
|
||||||
isAbsoluteSideNavHidden: boolean,
|
isAbsoluteSideNavHidden: boolean,
|
||||||
hideCancel: boolean,
|
hideCancel: boolean,
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
activeChannelStakedLevel: number,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header = (props: Props) => {
|
const Header = (props: Props) => {
|
||||||
|
@ -92,7 +91,6 @@ const Header = (props: Props) => {
|
||||||
hideCancel,
|
hideCancel,
|
||||||
user,
|
user,
|
||||||
activeChannelClaim,
|
activeChannelClaim,
|
||||||
activeChannelStakedLevel,
|
|
||||||
} = props;
|
} = props;
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
// on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
|
// on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
|
||||||
|
@ -103,12 +101,6 @@ const Header = (props: Props) => {
|
||||||
const hasBackout = Boolean(backout);
|
const hasBackout = Boolean(backout);
|
||||||
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
||||||
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
|
const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui);
|
||||||
const livestreamEnabled = Boolean(
|
|
||||||
ENABLE_NO_SOURCE_CLAIMS &&
|
|
||||||
user &&
|
|
||||||
!user.odysee_live_disabled &&
|
|
||||||
(activeChannelStakedLevel >= CHANNEL_STAKED_LEVEL_LIVESTREAM || user.odysee_live_enabled)
|
|
||||||
);
|
|
||||||
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
|
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
|
||||||
|
|
||||||
// Sign out if they click the "x" when they are on the password prompt
|
// Sign out if they click the "x" when they are on the password prompt
|
||||||
|
@ -288,7 +280,6 @@ const Header = (props: Props) => {
|
||||||
history={history}
|
history={history}
|
||||||
handleThemeToggle={handleThemeToggle}
|
handleThemeToggle={handleThemeToggle}
|
||||||
currentTheme={currentTheme}
|
currentTheme={currentTheme}
|
||||||
livestreamEnabled={livestreamEnabled}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -405,11 +396,10 @@ type HeaderMenuButtonProps = {
|
||||||
history: { push: (string) => void },
|
history: { push: (string) => void },
|
||||||
handleThemeToggle: (string) => void,
|
handleThemeToggle: (string) => void,
|
||||||
currentTheme: string,
|
currentTheme: string,
|
||||||
livestreamEnabled: boolean,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||||
const { authenticated, notificationsEnabled, history, handleThemeToggle, currentTheme, livestreamEnabled } = props;
|
const { authenticated, notificationsEnabled, history, handleThemeToggle, currentTheme } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="header__buttons">
|
<div className="header__buttons">
|
||||||
|
@ -437,18 +427,6 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||||
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||||
{__('New Channel')}
|
{__('New Channel')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/* @if TARGET='web' */}
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.YOUTUBE_SYNC}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.YOUTUBE} />
|
|
||||||
{__('Sync YouTube Channel')}
|
|
||||||
</MenuItem>
|
|
||||||
{/* @endif */}
|
|
||||||
{livestreamEnabled && (
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.LIVESTREAM}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.VIDEO} />
|
|
||||||
{__('Go Live')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { makeSelectStakedLevelForChannelUri, makeSelectClaimForUri } from 'redux/selectors/claims';
|
|
||||||
import LivestreamComment from './view';
|
|
||||||
|
|
||||||
const select = (state, props) => ({
|
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
|
||||||
stakedLevel: makeSelectStakedLevelForChannelUri(props.authorUri)(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select)(LivestreamComment);
|
|
|
@ -1,133 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
import React from 'react';
|
|
||||||
import { parseURI } from 'util/lbryURI';
|
|
||||||
import MarkdownPreview from 'component/common/markdown-preview';
|
|
||||||
import Tooltip from 'component/common/tooltip';
|
|
||||||
import ChannelThumbnail from 'component/channelThumbnail';
|
|
||||||
import { Menu, MenuButton } from '@reach/menu-button';
|
|
||||||
import Icon from 'component/common/icon';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import CommentMenuList from 'component/commentMenuList';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import CreditAmount from 'component/common/credit-amount';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
uri: string,
|
|
||||||
claim: StreamClaim,
|
|
||||||
authorUri: string,
|
|
||||||
commentId: string,
|
|
||||||
message: string,
|
|
||||||
commentIsMine: boolean,
|
|
||||||
stakedLevel: number,
|
|
||||||
supportAmount: number,
|
|
||||||
isModerator: boolean,
|
|
||||||
isGlobalMod: boolean,
|
|
||||||
isFiat: boolean,
|
|
||||||
isPinned: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
function LivestreamComment(props: Props) {
|
|
||||||
const {
|
|
||||||
claim,
|
|
||||||
uri,
|
|
||||||
authorUri,
|
|
||||||
message,
|
|
||||||
commentIsMine,
|
|
||||||
commentId,
|
|
||||||
stakedLevel,
|
|
||||||
supportAmount,
|
|
||||||
isModerator,
|
|
||||||
isGlobalMod,
|
|
||||||
isFiat,
|
|
||||||
isPinned,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const commentByOwnerOfContent = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri;
|
|
||||||
const { claimName } = parseURI(authorUri);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
className={classnames('livestream-comment', {
|
|
||||||
'livestream-comment--superchat': supportAmount > 0,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{supportAmount > 0 && (
|
|
||||||
<div className="super-chat livestream-superchat__banner">
|
|
||||||
<div className="livestream-superchat__banner-corner" />
|
|
||||||
<CreditAmount isFiat={isFiat} amount={supportAmount} superChat className="livestream-superchat__amount" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="livestream-comment__body">
|
|
||||||
{supportAmount > 0 && <ChannelThumbnail uri={authorUri} xsmall />}
|
|
||||||
<div className="livestream-comment__info">
|
|
||||||
{isGlobalMod && (
|
|
||||||
<Tooltip label={__('Admin')}>
|
|
||||||
<span className="comment__badge comment__badge--global-mod">
|
|
||||||
<Icon icon={ICONS.BADGE_MOD} size={16} />
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isModerator && (
|
|
||||||
<Tooltip label={__('Moderator')}>
|
|
||||||
<span className="comment__badge comment__badge--mod">
|
|
||||||
<Icon icon={ICONS.BADGE_MOD} size={16} />
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{commentByOwnerOfContent && (
|
|
||||||
<Tooltip label={__('Streamer')}>
|
|
||||||
<span className="comment__badge">
|
|
||||||
<Icon icon={ICONS.BADGE_STREAMER} size={16} />
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className={classnames('button--uri-indicator comment__author', {
|
|
||||||
'comment__author--creator': commentByOwnerOfContent,
|
|
||||||
})}
|
|
||||||
target="_blank"
|
|
||||||
navigate={authorUri}
|
|
||||||
>
|
|
||||||
{claimName}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{isPinned && (
|
|
||||||
<span className="comment__pin">
|
|
||||||
<Icon icon={ICONS.PIN} size={14} />
|
|
||||||
{__('Pinned')}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="livestream-comment__text">
|
|
||||||
<MarkdownPreview content={message} promptLinks stakedLevel={stakedLevel} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="livestream-comment__menu">
|
|
||||||
<Menu>
|
|
||||||
<MenuButton className="menu__button">
|
|
||||||
<Icon size={18} icon={ICONS.MORE_VERTICAL} />
|
|
||||||
</MenuButton>
|
|
||||||
<CommentMenuList
|
|
||||||
uri={uri}
|
|
||||||
commentId={commentId}
|
|
||||||
authorUri={authorUri}
|
|
||||||
commentIsMine={commentIsMine}
|
|
||||||
disableEdit
|
|
||||||
isTopLevel
|
|
||||||
isPinned={isPinned}
|
|
||||||
disableRemove={supportAmount > 0}
|
|
||||||
/>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LivestreamComment;
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { makeSelectClaimForUri, selectMyChannelClaims } from 'redux/selectors/claims';
|
|
||||||
import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket';
|
|
||||||
import { doCommentList, doSuperChatList } from 'redux/actions/comments';
|
|
||||||
import {
|
|
||||||
makeSelectTopLevelCommentsForUri,
|
|
||||||
selectIsFetchingComments,
|
|
||||||
makeSelectSuperChatsForUri,
|
|
||||||
makeSelectSuperChatTotalAmountForUri,
|
|
||||||
makeSelectPinnedCommentsForUri,
|
|
||||||
} from 'redux/selectors/comments';
|
|
||||||
import LivestreamComments from './view';
|
|
||||||
|
|
||||||
const select = (state, props) => ({
|
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
|
||||||
comments: makeSelectTopLevelCommentsForUri(props.uri)(state).slice(0, 75),
|
|
||||||
pinnedComments: makeSelectPinnedCommentsForUri(props.uri)(state),
|
|
||||||
fetchingComments: selectIsFetchingComments(state),
|
|
||||||
superChats: makeSelectSuperChatsForUri(props.uri)(state),
|
|
||||||
superChatsTotalAmount: makeSelectSuperChatTotalAmountForUri(props.uri)(state),
|
|
||||||
myChannels: selectMyChannelClaims(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, {
|
|
||||||
doCommentSocketConnect,
|
|
||||||
doCommentSocketDisconnect,
|
|
||||||
doCommentList,
|
|
||||||
doSuperChatList,
|
|
||||||
})(LivestreamComments);
|
|
|
@ -1,319 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React from 'react';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import Spinner from 'component/spinner';
|
|
||||||
import CommentCreate from 'component/commentCreate';
|
|
||||||
import LivestreamComment from 'component/livestreamComment';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import UriIndicator from 'component/uriIndicator';
|
|
||||||
import CreditAmount from 'component/common/credit-amount';
|
|
||||||
import ChannelThumbnail from 'component/channelThumbnail';
|
|
||||||
import Tooltip from 'component/common/tooltip';
|
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
uri: string,
|
|
||||||
claim: ?StreamClaim,
|
|
||||||
activeViewers: number,
|
|
||||||
embed?: boolean,
|
|
||||||
doCommentSocketConnect: (string, string) => void,
|
|
||||||
doCommentSocketDisconnect: (string) => void,
|
|
||||||
doCommentList: (string, string, number, number) => void,
|
|
||||||
comments: Array<Comment>,
|
|
||||||
pinnedComments: Array<Comment>,
|
|
||||||
fetchingComments: boolean,
|
|
||||||
doSuperChatList: (string) => void,
|
|
||||||
superChats: Array<Comment>,
|
|
||||||
myChannels: ?Array<ChannelClaim>,
|
|
||||||
};
|
|
||||||
|
|
||||||
const VIEW_MODE_CHAT = 'view_chat';
|
|
||||||
const VIEW_MODE_SUPER_CHAT = 'view_superchat';
|
|
||||||
const COMMENT_SCROLL_TIMEOUT = 25;
|
|
||||||
|
|
||||||
export default function LivestreamComments(props: Props) {
|
|
||||||
const {
|
|
||||||
claim,
|
|
||||||
uri,
|
|
||||||
embed,
|
|
||||||
doCommentSocketConnect,
|
|
||||||
doCommentSocketDisconnect,
|
|
||||||
comments: commentsByChronologicalOrder,
|
|
||||||
pinnedComments,
|
|
||||||
doCommentList,
|
|
||||||
fetchingComments,
|
|
||||||
doSuperChatList,
|
|
||||||
myChannels,
|
|
||||||
superChats: superChatsByTipAmount,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
let superChatsFiatAmount, superChatsTotalAmount;
|
|
||||||
|
|
||||||
const commentsRef = React.createRef();
|
|
||||||
const [viewMode, setViewMode] = React.useState(VIEW_MODE_CHAT);
|
|
||||||
const [scrollPos, setScrollPos] = React.useState(0);
|
|
||||||
const [showPinned, setShowPinned] = React.useState(true);
|
|
||||||
const claimId = claim && claim.claim_id;
|
|
||||||
const commentsLength = commentsByChronologicalOrder && commentsByChronologicalOrder.length;
|
|
||||||
|
|
||||||
// which kind of superchat to display, either
|
|
||||||
const commentsToDisplay = viewMode === VIEW_MODE_CHAT ? commentsByChronologicalOrder : superChatsByTipAmount;
|
|
||||||
|
|
||||||
const discussionElement = document.querySelector('.livestream__comments');
|
|
||||||
|
|
||||||
const pinnedComment = pinnedComments.length > 0 ? pinnedComments[0] : null;
|
|
||||||
|
|
||||||
function restoreScrollPos() {
|
|
||||||
if (discussionElement) {
|
|
||||||
discussionElement.scrollTop = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (claimId) {
|
|
||||||
doCommentList(uri, '', 1, 75);
|
|
||||||
doSuperChatList(uri);
|
|
||||||
doCommentSocketConnect(uri, claimId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (claimId) {
|
|
||||||
doCommentSocketDisconnect(claimId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [claimId, uri, doCommentList, doSuperChatList, doCommentSocketConnect, doCommentSocketDisconnect]);
|
|
||||||
|
|
||||||
// Register scroll handler (TODO: Should throttle/debounce)
|
|
||||||
React.useEffect(() => {
|
|
||||||
function handleScroll() {
|
|
||||||
if (discussionElement) {
|
|
||||||
const scrollTop = discussionElement.scrollTop;
|
|
||||||
if (scrollTop !== scrollPos) {
|
|
||||||
setScrollPos(scrollTop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discussionElement) {
|
|
||||||
discussionElement.addEventListener('scroll', handleScroll);
|
|
||||||
return () => discussionElement.removeEventListener('scroll', handleScroll);
|
|
||||||
}
|
|
||||||
}, [discussionElement, scrollPos]);
|
|
||||||
|
|
||||||
// Retain scrollPos=0 when receiving new messages.
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (discussionElement && commentsLength > 0) {
|
|
||||||
// Only update comment scroll if the user hasn't scrolled up to view old comments
|
|
||||||
if (scrollPos >= 0) {
|
|
||||||
// +ve scrollPos: not scrolled (Usually, there'll be a few pixels beyond 0).
|
|
||||||
// -ve scrollPos: user scrolled.
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
// Use a timer here to ensure we reset after the new comment has been rendered.
|
|
||||||
discussionElement.scrollTop = 0;
|
|
||||||
}, COMMENT_SCROLL_TIMEOUT);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [commentsLength]); // (Just respond to 'commentsLength' updates and nothing else)
|
|
||||||
|
|
||||||
// sum total amounts for fiat tips and lbc tips
|
|
||||||
if (superChatsByTipAmount) {
|
|
||||||
let fiatAmount = 0;
|
|
||||||
let LBCAmount = 0;
|
|
||||||
for (const superChat of superChatsByTipAmount) {
|
|
||||||
if (superChat.is_fiat) {
|
|
||||||
fiatAmount = fiatAmount + superChat.support_amount;
|
|
||||||
} else {
|
|
||||||
LBCAmount = LBCAmount + superChat.support_amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
superChatsFiatAmount = fiatAmount;
|
|
||||||
superChatsTotalAmount = LBCAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
let superChatsReversed;
|
|
||||||
// array of superchats organized by fiat or not first, then support amount
|
|
||||||
if (superChatsByTipAmount) {
|
|
||||||
const clonedSuperchats = JSON.parse(JSON.stringify(superChatsByTipAmount));
|
|
||||||
|
|
||||||
// for top to bottom display, oldest superchat on top most recent on bottom
|
|
||||||
superChatsReversed = clonedSuperchats
|
|
||||||
.sort((a, b) => {
|
|
||||||
return b.timestamp - a.timestamp;
|
|
||||||
}); }
|
|
||||||
|
|
||||||
// todo: implement comment_list --mine in SDK so redux can grab with selectCommentIsMine
|
|
||||||
function isMyComment(channelId: string) {
|
|
||||||
if (myChannels != null && channelId != null) {
|
|
||||||
for (let i = 0; i < myChannels.length; i++) {
|
|
||||||
if (myChannels[i].claim_id === channelId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!claim) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="card livestream__discussion">
|
|
||||||
<div className="card__header--between livestream-discussion__header">
|
|
||||||
<div className="livestream-discussion__title">{__('Live discussion')}</div>
|
|
||||||
{(superChatsTotalAmount || 0) > 0 && (
|
|
||||||
<div className="recommended-content__toggles">
|
|
||||||
{/* the superchats in chronological order button */}
|
|
||||||
<Button
|
|
||||||
className={classnames('button-toggle', {
|
|
||||||
'button-toggle--active': viewMode === VIEW_MODE_CHAT,
|
|
||||||
})}
|
|
||||||
label={__('Chat')}
|
|
||||||
onClick={() => {
|
|
||||||
setViewMode(VIEW_MODE_CHAT);
|
|
||||||
const livestreamCommentsDiv = document.getElementsByClassName('livestream__comments')[0];
|
|
||||||
livestreamCommentsDiv.scrollTop = livestreamCommentsDiv.scrollHeight;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* the button to show superchats listed by most to least support amount */}
|
|
||||||
<Button
|
|
||||||
className={classnames('button-toggle', {
|
|
||||||
'button-toggle--active': viewMode === VIEW_MODE_SUPER_CHAT,
|
|
||||||
})}
|
|
||||||
label={
|
|
||||||
<>
|
|
||||||
<CreditAmount amount={superChatsTotalAmount || 0} size={8} /> /
|
|
||||||
<CreditAmount amount={superChatsFiatAmount || 0} size={8} isFiat /> {__('Tipped')}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
setViewMode(VIEW_MODE_SUPER_CHAT);
|
|
||||||
const livestreamCommentsDiv = document.getElementsByClassName('livestream__comments')[0];
|
|
||||||
const divHeight = livestreamCommentsDiv.scrollHeight;
|
|
||||||
livestreamCommentsDiv.scrollTop = divHeight * -1;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<>
|
|
||||||
{fetchingComments && !commentsByChronologicalOrder && (
|
|
||||||
<div className="main--empty">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div ref={commentsRef} className="livestream__comments-wrapper">
|
|
||||||
{viewMode === VIEW_MODE_CHAT && superChatsByTipAmount && (superChatsTotalAmount || 0) > 0 && (
|
|
||||||
<div className="livestream-superchats__wrapper">
|
|
||||||
<div className="livestream-superchats__inner">
|
|
||||||
{superChatsByTipAmount.map((superChat: Comment) => (
|
|
||||||
<Tooltip key={superChat.comment_id} label={superChat.comment}>
|
|
||||||
<div className="livestream-superchat">
|
|
||||||
<div className="livestream-superchat__thumbnail">
|
|
||||||
<ChannelThumbnail uri={superChat.channel_url} xsmall />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="livestream-superchat__info">
|
|
||||||
<UriIndicator uri={superChat.channel_url} link />
|
|
||||||
<CreditAmount
|
|
||||||
size={10}
|
|
||||||
className="livestream-superchat__amount-large"
|
|
||||||
amount={superChat.support_amount}
|
|
||||||
isFiat={superChat.is_fiat}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{pinnedComment && showPinned && viewMode === VIEW_MODE_CHAT && (
|
|
||||||
<div className="livestream-pinned__wrapper">
|
|
||||||
<LivestreamComment
|
|
||||||
key={pinnedComment.comment_id}
|
|
||||||
uri={uri}
|
|
||||||
authorUri={pinnedComment.channel_url}
|
|
||||||
commentId={pinnedComment.comment_id}
|
|
||||||
message={pinnedComment.comment}
|
|
||||||
supportAmount={pinnedComment.support_amount}
|
|
||||||
isModerator={pinnedComment.is_moderator}
|
|
||||||
isGlobalMod={pinnedComment.is_global_mod}
|
|
||||||
isFiat={pinnedComment.is_fiat}
|
|
||||||
isPinned={pinnedComment.is_pinned}
|
|
||||||
commentIsMine={pinnedComment.channel_id && isMyComment(pinnedComment.channel_id)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
title={__('Dismiss pinned comment')}
|
|
||||||
button="inverse"
|
|
||||||
className="close-button"
|
|
||||||
onClick={() => setShowPinned(false)}
|
|
||||||
icon={ICONS.REMOVE}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* top to bottom comment display */}
|
|
||||||
{!fetchingComments && commentsByChronologicalOrder.length > 0 ? (
|
|
||||||
<div className="livestream__comments">
|
|
||||||
{viewMode === VIEW_MODE_CHAT &&
|
|
||||||
commentsToDisplay.map((comment) => (
|
|
||||||
<LivestreamComment
|
|
||||||
key={comment.comment_id}
|
|
||||||
uri={uri}
|
|
||||||
authorUri={comment.channel_url}
|
|
||||||
commentId={comment.comment_id}
|
|
||||||
message={comment.comment}
|
|
||||||
supportAmount={comment.support_amount}
|
|
||||||
isModerator={comment.is_moderator}
|
|
||||||
isGlobalMod={comment.is_global_mod}
|
|
||||||
isFiat={comment.is_fiat}
|
|
||||||
commentIsMine={comment.channel_id && isMyComment(comment.channel_id)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* listing comments on top of eachother */}
|
|
||||||
{viewMode === VIEW_MODE_SUPER_CHAT &&
|
|
||||||
superChatsReversed &&
|
|
||||||
superChatsReversed.map((comment) => (
|
|
||||||
<LivestreamComment
|
|
||||||
key={comment.comment_id}
|
|
||||||
uri={uri}
|
|
||||||
authorUri={comment.channel_url}
|
|
||||||
commentId={comment.comment_id}
|
|
||||||
message={comment.comment}
|
|
||||||
supportAmount={comment.support_amount}
|
|
||||||
isModerator={comment.is_moderator}
|
|
||||||
isGlobalMod={comment.is_global_mod}
|
|
||||||
isFiat={comment.is_fiat}
|
|
||||||
commentIsMine={comment.channel_id && isMyComment(comment.channel_id)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="main--empty" style={{ flex: 1 }} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{scrollPos < 0 && viewMode === VIEW_MODE_CHAT && (
|
|
||||||
<Button
|
|
||||||
button="secondary"
|
|
||||||
className="livestream__comments__scroll-to-recent"
|
|
||||||
label={__('Recent Comments')}
|
|
||||||
onClick={restoreScrollPos}
|
|
||||||
iconRight={ICONS.DOWN}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="livestream__comment-create">
|
|
||||||
<CommentCreate livestream bottom embed={embed} uri={uri} onDoneReplying={restoreScrollPos} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import {
|
|
||||||
makeSelectClaimForUri,
|
|
||||||
makeSelectTagInClaimOrChannelForUri,
|
|
||||||
makeSelectThumbnailForUri,
|
|
||||||
} from 'redux/selectors/claims';
|
|
||||||
import LivestreamLayout from './view';
|
|
||||||
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
|
|
||||||
|
|
||||||
const select = (state, props) => ({
|
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
|
||||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
|
||||||
chatDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select)(LivestreamLayout);
|
|
|
@ -1,63 +0,0 @@
|
||||||
// @flow
|
|
||||||
import { LIVESTREAM_EMBED_URL } from 'constants/livestream';
|
|
||||||
import React from 'react';
|
|
||||||
import FileTitleSection from 'component/fileTitleSection';
|
|
||||||
import LivestreamComments from 'component/livestreamComments';
|
|
||||||
import { useIsMobile } from 'effects/use-screensize';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
uri: string,
|
|
||||||
claim: ?StreamClaim,
|
|
||||||
isLive: boolean,
|
|
||||||
chatDisabled: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function LivestreamLayout(props: Props) {
|
|
||||||
const { claim, uri, isLive, chatDisabled } = props;
|
|
||||||
const isMobile = useIsMobile();
|
|
||||||
|
|
||||||
if (!claim || !claim.signing_channel) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channelName = claim.signing_channel.name;
|
|
||||||
const channelClaimId = claim.signing_channel.claim_id;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="section card-stack">
|
|
||||||
<div className="file-render file-render--video livestream">
|
|
||||||
<div className="file-viewer">
|
|
||||||
<iframe
|
|
||||||
src={`${LIVESTREAM_EMBED_URL}/${channelClaimId}?skin=odysee&autoplay=1`}
|
|
||||||
scrolling="no"
|
|
||||||
allowFullScreen
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{Boolean(chatDisabled) && (
|
|
||||||
<div className="help--notice">
|
|
||||||
{channelName
|
|
||||||
? __('%channel% has disabled chat for this stream. Enjoy the stream!', { channel: channelName })
|
|
||||||
: __('This channel has disabled chat for this stream. Enjoy the stream!')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isLive && (
|
|
||||||
<div className="help--notice">
|
|
||||||
{channelName
|
|
||||||
? __("%channelName% isn't live right now, but the chat is! Check back later to watch the stream.", {
|
|
||||||
channelName,
|
|
||||||
})
|
|
||||||
: __("This channel isn't live right now, but the chat is! Check back later to watch the stream.")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isMobile && <LivestreamComments uri={uri} />}
|
|
||||||
|
|
||||||
<FileTitleSection uri={uri} livestream isLive={isLive} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
|
||||||
import LivestreamLink from './view';
|
|
||||||
|
|
||||||
const select = (state, props) => ({
|
|
||||||
channelClaim: makeSelectClaimForUri(props.uri)(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select)(LivestreamLink);
|
|
|
@ -1,101 +0,0 @@
|
||||||
// @flow
|
|
||||||
import { LIVESTREAM_LIVE_API } from 'constants/livestream';
|
|
||||||
import * as CS from 'constants/claim_search';
|
|
||||||
import React from 'react';
|
|
||||||
import Card from 'component/common/card';
|
|
||||||
import ClaimPreview from 'component/claimPreview';
|
|
||||||
import Lbry from 'lbry';
|
|
||||||
import { useHistory } from 'react-router';
|
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
channelClaim: ChannelClaim,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function LivestreamLink(props: Props) {
|
|
||||||
const { channelClaim } = props;
|
|
||||||
const { push } = useHistory();
|
|
||||||
const [livestreamClaim, setLivestreamClaim] = React.useState(false);
|
|
||||||
const [isLivestreaming, setIsLivestreaming] = React.useState(false);
|
|
||||||
const livestreamChannelId = (channelClaim && channelClaim.claim_id) || ''; // TODO: fail in a safer way, probably
|
|
||||||
// TODO: pput this back when hubs claims_in_channel are fixed
|
|
||||||
const isChannelEmpty = !channelClaim || !channelClaim.meta;
|
|
||||||
// ||
|
|
||||||
// !channelClaim.meta.claims_in_channel;
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
// Don't search empty channels
|
|
||||||
if (livestreamChannelId && !isChannelEmpty) {
|
|
||||||
Lbry.claim_search({
|
|
||||||
channel_ids: [livestreamChannelId],
|
|
||||||
page: 1,
|
|
||||||
page_size: 1,
|
|
||||||
no_totals: true,
|
|
||||||
has_no_source: true,
|
|
||||||
claim_type: ['stream'],
|
|
||||||
order_by: CS.ORDER_BY_NEW_VALUE,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (res && res.items && res.items.length > 0) {
|
|
||||||
const claim = res.items[0];
|
|
||||||
// $FlowFixMe Too many Claim GenericClaim etc types.
|
|
||||||
setLivestreamClaim(claim);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
}, [livestreamChannelId, isChannelEmpty]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
function fetchIsStreaming() {
|
|
||||||
// $FlowFixMe livestream API can handle garbage
|
|
||||||
fetch(`${LIVESTREAM_LIVE_API}/${livestreamChannelId}`)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((res) => {
|
|
||||||
if (res && res.success && res.data && res.data.live) {
|
|
||||||
setIsLivestreaming(true);
|
|
||||||
} else {
|
|
||||||
setIsLivestreaming(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
let interval;
|
|
||||||
// Only call livestream api if channel has livestream claims
|
|
||||||
if (livestreamChannelId && livestreamClaim) {
|
|
||||||
if (!interval) fetchIsStreaming();
|
|
||||||
interval = setInterval(fetchIsStreaming, 10 * 1000);
|
|
||||||
}
|
|
||||||
// Prevent any more api calls on update
|
|
||||||
if (!livestreamChannelId || !livestreamClaim) {
|
|
||||||
if (interval) {
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
if (interval) {
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [livestreamChannelId, livestreamClaim]);
|
|
||||||
|
|
||||||
if (!livestreamClaim || !isLivestreaming) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// gonna pass the wrapper in so I don't have to rewrite the dmca/blocking logic in claimPreview.
|
|
||||||
const element = (props: { children: any }) => (
|
|
||||||
<Card
|
|
||||||
className="livestream__channel-link"
|
|
||||||
title={__('Live stream in progress')}
|
|
||||||
onClick={() => {
|
|
||||||
push(formatLbryUrlForWeb(livestreamClaim.canonical_url));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
|
|
||||||
return <ClaimPreview uri={livestreamClaim.canonical_url} wrapperElement={element} type="inline" />;
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import LivestreamList from './view';
|
|
||||||
|
|
||||||
const select = (state) => ({});
|
|
||||||
|
|
||||||
export default connect(select)(LivestreamList);
|
|
|
@ -1,78 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
import { LIVESTREAM_LIVE_API } from 'constants/livestream';
|
|
||||||
import React from 'react';
|
|
||||||
import Icon from 'component/common/icon';
|
|
||||||
import Spinner from 'component/spinner';
|
|
||||||
import ClaimTilesDiscover from 'component/claimTilesDiscover';
|
|
||||||
|
|
||||||
const LIVESTREAM_POLL_IN_MS = 10 * 1000;
|
|
||||||
|
|
||||||
export default function LivestreamList() {
|
|
||||||
const [loading, setLoading] = React.useState(true);
|
|
||||||
const [livestreamMap, setLivestreamMap] = React.useState();
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
function checkCurrentLivestreams() {
|
|
||||||
fetch(LIVESTREAM_LIVE_API)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((res) => {
|
|
||||||
setLoading(false);
|
|
||||||
if (!res.data) {
|
|
||||||
setLivestreamMap({});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const livestreamMap = res.data.reduce((acc, curr) => {
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[curr.claimId]: curr,
|
|
||||||
};
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
setLivestreamMap(livestreamMap);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
checkCurrentLivestreams();
|
|
||||||
let fetchInterval = setInterval(checkCurrentLivestreams, LIVESTREAM_POLL_IN_MS);
|
|
||||||
return () => {
|
|
||||||
if (fetchInterval) {
|
|
||||||
clearInterval(fetchInterval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{loading && (
|
|
||||||
<div className="main--empty">
|
|
||||||
<Spinner delayed />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{livestreamMap && Object.keys(livestreamMap).length > 0 && (
|
|
||||||
<ClaimTilesDiscover
|
|
||||||
showNoSourceClaims
|
|
||||||
hasNoSource
|
|
||||||
streamTypes={null}
|
|
||||||
channelIds={Object.keys(livestreamMap)}
|
|
||||||
limitClaimsPerChannel={1}
|
|
||||||
pageSize={50}
|
|
||||||
renderProperties={(claim) => {
|
|
||||||
const livestream = livestreamMap[claim.signing_channel.claim_id];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className="livestream__viewer-count">
|
|
||||||
{livestream.viewCount} <Icon icon={ICONS.EYE} />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -37,6 +37,10 @@ export default function Notification(props: Props) {
|
||||||
const [isReplying, setReplying] = React.useState(false);
|
const [isReplying, setReplying] = React.useState(false);
|
||||||
const [quickReply, setQuickReply] = React.useState();
|
const [quickReply, setQuickReply] = React.useState();
|
||||||
|
|
||||||
|
const isIgnoredNotification = notification_rule === RULE.NEW_LIVESTREAM;
|
||||||
|
if (isIgnoredNotification) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const isCommentNotification =
|
const isCommentNotification =
|
||||||
notification_rule === RULE.COMMENT ||
|
notification_rule === RULE.COMMENT ||
|
||||||
notification_rule === RULE.COMMENT_REPLY ||
|
notification_rule === RULE.COMMENT_REPLY ||
|
||||||
|
@ -83,10 +87,6 @@ export default function Notification(props: Props) {
|
||||||
channelUrl = notification_parameters.dynamic.channel_url;
|
channelUrl = notification_parameters.dynamic.channel_url;
|
||||||
icon = creatorIcon(channelUrl);
|
icon = creatorIcon(channelUrl);
|
||||||
break;
|
break;
|
||||||
case RULE.NEW_LIVESTREAM:
|
|
||||||
channelUrl = notification_parameters.dynamic.channel_url;
|
|
||||||
icon = creatorIcon(channelUrl);
|
|
||||||
break;
|
|
||||||
case RULE.DAILY_WATCH_AVAILABLE:
|
case RULE.DAILY_WATCH_AVAILABLE:
|
||||||
case RULE.DAILY_WATCH_REMIND:
|
case RULE.DAILY_WATCH_REMIND:
|
||||||
case RULE.MISSED_OUT:
|
case RULE.MISSED_OUT:
|
||||||
|
|
|
@ -28,7 +28,6 @@ type Props = {
|
||||||
fullWidthPage: boolean,
|
fullWidthPage: boolean,
|
||||||
videoTheaterMode: boolean,
|
videoTheaterMode: boolean,
|
||||||
isMarkdown?: boolean,
|
isMarkdown?: boolean,
|
||||||
livestream?: boolean,
|
|
||||||
chatDisabled: boolean,
|
chatDisabled: boolean,
|
||||||
rightSide?: Node,
|
rightSide?: Node,
|
||||||
backout: {
|
backout: {
|
||||||
|
@ -52,9 +51,7 @@ function Page(props: Props) {
|
||||||
backout,
|
backout,
|
||||||
videoTheaterMode,
|
videoTheaterMode,
|
||||||
isMarkdown = false,
|
isMarkdown = false,
|
||||||
livestream,
|
|
||||||
rightSide,
|
rightSide,
|
||||||
chatDisabled,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -127,8 +124,7 @@ function Page(props: Props) {
|
||||||
'main--file-page': filePage,
|
'main--file-page': filePage,
|
||||||
'main--settings-page': settingsPage,
|
'main--settings-page': settingsPage,
|
||||||
'main--markdown': isMarkdown,
|
'main--markdown': isMarkdown,
|
||||||
'main--theater-mode': isOnFilePage && videoTheaterMode && !livestream,
|
'main--theater-mode': isOnFilePage && videoTheaterMode,
|
||||||
'main--livestream': livestream && !chatDisabled,
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -2,12 +2,11 @@ import { connect } from 'react-redux';
|
||||||
import { selectBalance } from 'redux/selectors/wallet';
|
import { selectBalance } from 'redux/selectors/wallet';
|
||||||
import { selectIsStillEditing, makeSelectPublishFormValue } from 'redux/selectors/publish';
|
import { selectIsStillEditing, makeSelectPublishFormValue } from 'redux/selectors/publish';
|
||||||
import { doUpdatePublishForm, doClearPublish } from 'redux/actions/publish';
|
import { doUpdatePublishForm, doClearPublish } from 'redux/actions/publish';
|
||||||
import { makeSelectClaimIsStreamPlaceholder } from 'redux/selectors/claims';
|
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import { selectFfmpegStatus } from 'redux/selectors/settings';
|
import { selectFfmpegStatus } from 'redux/selectors/settings';
|
||||||
import PublishPage from './view';
|
import PublishPage from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state) => ({
|
||||||
name: makeSelectPublishFormValue('name')(state),
|
name: makeSelectPublishFormValue('name')(state),
|
||||||
title: makeSelectPublishFormValue('title')(state),
|
title: makeSelectPublishFormValue('title')(state),
|
||||||
filePath: makeSelectPublishFormValue('filePath')(state),
|
filePath: makeSelectPublishFormValue('filePath')(state),
|
||||||
|
@ -20,7 +19,6 @@ const select = (state, props) => ({
|
||||||
size: makeSelectPublishFormValue('fileSize')(state),
|
size: makeSelectPublishFormValue('fileSize')(state),
|
||||||
duration: makeSelectPublishFormValue('fileDur')(state),
|
duration: makeSelectPublishFormValue('fileDur')(state),
|
||||||
isVid: makeSelectPublishFormValue('fileVid')(state),
|
isVid: makeSelectPublishFormValue('fileVid')(state),
|
||||||
isLivestreamClaim: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { SITE_NAME, WEB_PUBLISH_SIZE_LIMIT_GB } from 'config';
|
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
@ -14,11 +13,6 @@ import I18nMessage from 'component/i18nMessage';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import * as PUBLISH_MODES from 'constants/publish_types';
|
import * as PUBLISH_MODES from 'constants/publish_types';
|
||||||
import PublishName from 'component/publishName';
|
import PublishName from 'component/publishName';
|
||||||
import CopyableText from 'component/copyableText';
|
|
||||||
import Empty from 'component/common/empty';
|
|
||||||
import moment from 'moment';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import ReactPaginate from 'react-paginate';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: ?string,
|
uri: ?string,
|
||||||
|
@ -44,12 +38,7 @@ type Props = {
|
||||||
setPublishMode: (string) => void,
|
setPublishMode: (string) => void,
|
||||||
setPrevFileText: (string) => void,
|
setPrevFileText: (string) => void,
|
||||||
header: Node,
|
header: Node,
|
||||||
livestreamData: LivestreamReplayData,
|
|
||||||
isLivestreamClaim: boolean,
|
|
||||||
checkLivestreams: (string, ?string, ?string) => void,
|
|
||||||
channelId: string,
|
channelId: string,
|
||||||
channelSignature: { signature?: string, signing_ts?: string },
|
|
||||||
isCheckingLivestreams: boolean,
|
|
||||||
setWaitForFile: (boolean) => void,
|
setWaitForFile: (boolean) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -76,23 +65,10 @@ function PublishFile(props: Props) {
|
||||||
setPublishMode,
|
setPublishMode,
|
||||||
setPrevFileText,
|
setPrevFileText,
|
||||||
header,
|
header,
|
||||||
livestreamData,
|
|
||||||
isLivestreamClaim,
|
|
||||||
subtitle,
|
subtitle,
|
||||||
checkLivestreams,
|
|
||||||
channelId,
|
|
||||||
channelSignature,
|
|
||||||
isCheckingLivestreams,
|
|
||||||
setWaitForFile,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const SOURCE_NONE = 'none';
|
|
||||||
const SOURCE_SELECT = 'select';
|
|
||||||
const SOURCE_UPLOAD = 'upload';
|
|
||||||
|
|
||||||
const RECOMMENDED_BITRATE = 6000000;
|
const RECOMMENDED_BITRATE = 6000000;
|
||||||
const TV_PUBLISH_SIZE_LIMIT_BYTES = WEB_PUBLISH_SIZE_LIMIT_GB * 1073741824;
|
|
||||||
const TV_PUBLISH_SIZE_LIMIT_GB_STR = String(WEB_PUBLISH_SIZE_LIMIT_GB);
|
|
||||||
|
|
||||||
const PROCESSING_MB_PER_SECOND = 0.5;
|
const PROCESSING_MB_PER_SECOND = 0.5;
|
||||||
const MINUTES_THRESHOLD = 30;
|
const MINUTES_THRESHOLD = 30;
|
||||||
|
@ -101,35 +77,10 @@ function PublishFile(props: Props) {
|
||||||
const sizeInMB = Number(size) / 1000000;
|
const sizeInMB = Number(size) / 1000000;
|
||||||
const secondsToProcess = sizeInMB / PROCESSING_MB_PER_SECOND;
|
const secondsToProcess = sizeInMB / PROCESSING_MB_PER_SECOND;
|
||||||
const ffmpegAvail = ffmpegStatus.available;
|
const ffmpegAvail = ffmpegStatus.available;
|
||||||
const [oversized, setOversized] = useState(false);
|
|
||||||
const [currentFile, setCurrentFile] = useState(null);
|
const [currentFile, setCurrentFile] = useState(null);
|
||||||
const [currentFileType, setCurrentFileType] = useState(null);
|
const [currentFileType, setCurrentFileType] = useState(null);
|
||||||
const [optimizeAvail, setOptimizeAvail] = useState(false);
|
const [optimizeAvail, setOptimizeAvail] = useState(false);
|
||||||
const [userOptimize, setUserOptimize] = usePersistedState('publish-file-user-optimize', false);
|
const [userOptimize, setUserOptimize] = usePersistedState('publish-file-user-optimize', false);
|
||||||
const UPLOAD_SIZE_MESSAGE = __(
|
|
||||||
'%SITE_NAME% uploads are limited to %limit% GB. Download the app for unrestricted publishing.',
|
|
||||||
{ SITE_NAME, limit: TV_PUBLISH_SIZE_LIMIT_GB_STR }
|
|
||||||
);
|
|
||||||
|
|
||||||
const fileSelectorModes = [
|
|
||||||
{ label: __('Upload'), actionName: SOURCE_UPLOAD, icon: ICONS.PUBLISH },
|
|
||||||
{ label: __('Choose Replay'), actionName: SOURCE_SELECT, icon: ICONS.MENU },
|
|
||||||
{ label: __('None'), actionName: SOURCE_NONE },
|
|
||||||
];
|
|
||||||
|
|
||||||
const livestreamDataStr = JSON.stringify(livestreamData);
|
|
||||||
const hasLivestreamData = livestreamData && Boolean(livestreamData.length);
|
|
||||||
const showSourceSelector = isLivestreamClaim || (hasLivestreamData && mode === PUBLISH_MODES.FILE);
|
|
||||||
|
|
||||||
const [fileSelectSource, setFileSelectSource] = useState(
|
|
||||||
IS_WEB && showSourceSelector && name ? SOURCE_SELECT : SOURCE_UPLOAD
|
|
||||||
);
|
|
||||||
// const [showFileUpdate, setShowFileUpdate] = useState(false);
|
|
||||||
const [selectedFileIndex, setSelectedFileIndex] = useState(null);
|
|
||||||
const PAGE_SIZE = 4;
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
const totalPages =
|
|
||||||
hasLivestreamData && livestreamData.length > PAGE_SIZE ? Math.ceil(livestreamData.length / PAGE_SIZE) : 1;
|
|
||||||
|
|
||||||
// Reset filePath if publish mode changed
|
// Reset filePath if publish mode changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -137,46 +88,12 @@ function PublishFile(props: Props) {
|
||||||
if (currentFileType !== 'text/markdown' && !isStillEditing) {
|
if (currentFileType !== 'text/markdown' && !isStillEditing) {
|
||||||
updatePublishForm({ filePath: '' });
|
updatePublishForm({ filePath: '' });
|
||||||
}
|
}
|
||||||
} else if (mode === PUBLISH_MODES.LIVESTREAM) {
|
|
||||||
updatePublishForm({ filePath: '' });
|
|
||||||
}
|
}
|
||||||
}, [currentFileType, mode, isStillEditing, updatePublishForm]);
|
}, [currentFileType, mode, isStillEditing, updatePublishForm]);
|
||||||
|
|
||||||
// set default file source to select if necessary
|
|
||||||
useEffect(() => {
|
|
||||||
if (hasLivestreamData && isLivestreamClaim) {
|
|
||||||
setWaitForFile(true);
|
|
||||||
setFileSelectSource(SOURCE_SELECT);
|
|
||||||
} else if (isLivestreamClaim) {
|
|
||||||
setFileSelectSource(SOURCE_NONE);
|
|
||||||
}
|
|
||||||
}, [hasLivestreamData, isLivestreamClaim, setFileSelectSource]);
|
|
||||||
|
|
||||||
const normalizeUrlForProtocol = (url) => {
|
|
||||||
if (url.startsWith('https://')) {
|
|
||||||
return url;
|
|
||||||
} else {
|
|
||||||
if (url.startsWith('http://')) {
|
|
||||||
return url;
|
|
||||||
} else {
|
|
||||||
return `https://${url}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// update remoteUrl when replay selected
|
|
||||||
useEffect(() => {
|
|
||||||
const livestreamData = JSON.parse(livestreamDataStr);
|
|
||||||
if (selectedFileIndex !== null && livestreamData && livestreamData.length) {
|
|
||||||
updatePublishForm({
|
|
||||||
remoteFileUrl: normalizeUrlForProtocol(livestreamData[selectedFileIndex].data.fileLocation),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [selectedFileIndex, updatePublishForm, livestreamDataStr]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!filePath || filePath === '') {
|
if (!filePath || filePath === '') {
|
||||||
setCurrentFile('');
|
setCurrentFile('');
|
||||||
setOversized(false);
|
|
||||||
updateFileInfo(0, 0, false);
|
updateFileInfo(0, 0, false);
|
||||||
} else if (typeof filePath !== 'string') {
|
} else if (typeof filePath !== 'string') {
|
||||||
// Update currentFile file
|
// Update currentFile file
|
||||||
|
@ -198,10 +115,6 @@ function PublishFile(props: Props) {
|
||||||
updatePublishForm({ fileDur: duration, fileSize: size, fileVid: isvid });
|
updatePublishForm({ fileDur: duration, fileSize: size, fileVid: isvid });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePaginateReplays(page) {
|
|
||||||
setCurrentPage(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBitrate(size, duration) {
|
function getBitrate(size, duration) {
|
||||||
const s = Number(size);
|
const s = Number(size);
|
||||||
const d = Number(duration);
|
const d = Number(duration);
|
||||||
|
@ -236,16 +149,6 @@ function PublishFile(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUploadMessage() {
|
function getUploadMessage() {
|
||||||
// @if TARGET='web'
|
|
||||||
if (oversized) {
|
|
||||||
return (
|
|
||||||
<p className="help--error">
|
|
||||||
{UPLOAD_SIZE_MESSAGE}{' '}
|
|
||||||
<Button button="link" label={__('Upload Guide')} href="https://lbry.com/faq/video-publishing-guide" />
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// @endif
|
|
||||||
if (isVid && duration && getBitrate(size, duration) > RECOMMENDED_BITRATE) {
|
if (isVid && duration && getBitrate(size, duration) > RECOMMENDED_BITRATE) {
|
||||||
return (
|
return (
|
||||||
<p className="help--warning">
|
<p className="help--warning">
|
||||||
|
@ -267,32 +170,13 @@ function PublishFile(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!!isStillEditing && name) {
|
if (!!isStillEditing && name) {
|
||||||
if (isLivestreamClaim) {
|
|
||||||
return (
|
|
||||||
<p className="help">{__('You can upload your own recording or select a replay when your stream is over')}</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<p className="help">
|
<p className="help">
|
||||||
{__("If you don't choose a file, the file from your existing claim %name% will be used", { name: name })}
|
{__("If you don't choose a file, the file from your existing claim %name% will be used", { name: name })}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// @if TARGET='web'
|
|
||||||
if (!isStillEditing) {
|
|
||||||
return (
|
|
||||||
<p className="help">
|
|
||||||
{__(
|
|
||||||
'For video content, use MP4s in H264/AAC format and a friendly bitrate (under 5 Mbps) and resolution (720p) for more reliable streaming. %SITE_NAME% uploads are restricted to %limit% GB.',
|
|
||||||
{ SITE_NAME, limit: TV_PUBLISH_SIZE_LIMIT_GB_STR }
|
|
||||||
)}{' '}
|
|
||||||
<Button button="link" label={__('Upload Guide')} href="https://lbry.com/faq/video-publishing-guide" />
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// @endif
|
|
||||||
|
|
||||||
// @if TARGET='app'
|
|
||||||
if (!isStillEditing) {
|
if (!isStillEditing) {
|
||||||
return (
|
return (
|
||||||
<p className="help">
|
<p className="help">
|
||||||
|
@ -303,7 +187,6 @@ function PublishFile(props: Props) {
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// @endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseName(newName) {
|
function parseName(newName) {
|
||||||
|
@ -311,27 +194,6 @@ function PublishFile(props: Props) {
|
||||||
return newName.replace(INVALID_URI_CHARS, '-');
|
return newName.replace(INVALID_URI_CHARS, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFileSource(source) {
|
|
||||||
if (source === SOURCE_NONE) {
|
|
||||||
// clear files and remotes...
|
|
||||||
// https://github.com/lbryio/lbry-desktop/issues/5855
|
|
||||||
// publish is trying to use one field to share html file blob and string and such
|
|
||||||
// $FlowFixMe
|
|
||||||
handleFileChange(false, false);
|
|
||||||
updatePublishForm({ remoteFileUrl: undefined });
|
|
||||||
} else if (source === SOURCE_UPLOAD) {
|
|
||||||
updatePublishForm({ remoteFileUrl: undefined });
|
|
||||||
} else if (source === SOURCE_SELECT) {
|
|
||||||
// $FlowFixMe
|
|
||||||
handleFileChange(false, false);
|
|
||||||
if (selectedFileIndex !== null) {
|
|
||||||
updatePublishForm({ remoteFileUrl: livestreamData[selectedFileIndex].data.fileLocation });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setFileSelectSource(source);
|
|
||||||
setWaitForFile(source !== SOURCE_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTitleChange(event) {
|
function handleTitleChange(event) {
|
||||||
const title = event.target.value;
|
const title = event.target.value;
|
||||||
// Update title
|
// Update title
|
||||||
|
@ -348,9 +210,7 @@ function PublishFile(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFileChange(file: WebFile, clearName = true) {
|
function handleFileChange(file: WebFile, clearName = true) {
|
||||||
const { showToast } = props;
|
|
||||||
window.URL = window.URL || window.webkitURL;
|
window.URL = window.URL || window.webkitURL;
|
||||||
setOversized(false);
|
|
||||||
|
|
||||||
// select file, start to select a new one, then cancel
|
// select file, start to select a new one, then cancel
|
||||||
if (!file) {
|
if (!file) {
|
||||||
|
@ -410,16 +270,6 @@ function PublishFile(props: Props) {
|
||||||
setPublishMode(PUBLISH_MODES.FILE);
|
setPublishMode(PUBLISH_MODES.FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @if TARGET='web'
|
|
||||||
// we only need to enforce file sizes on 'web'
|
|
||||||
if (file.size && Number(file.size) > TV_PUBLISH_SIZE_LIMIT_BYTES) {
|
|
||||||
setOversized(true);
|
|
||||||
showToast(__(UPLOAD_SIZE_MESSAGE));
|
|
||||||
updatePublishForm({ filePath: '', name: '' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// @endif
|
|
||||||
|
|
||||||
const publishFormParams: { filePath: string | WebFile, name?: string, optimize?: boolean } = {
|
const publishFormParams: { filePath: string | WebFile, name?: string, optimize?: boolean } = {
|
||||||
// if electron, we'll set filePath to the path string because SDK is handling publishing.
|
// if electron, we'll set filePath to the path string because SDK is handling publishing.
|
||||||
// File.path will be undefined from web due to browser security, so it will default to the File Object.
|
// File.path will be undefined from web due to browser security, so it will default to the File Object.
|
||||||
|
@ -472,149 +322,8 @@ function PublishFile(props: Props) {
|
||||||
value={title}
|
value={title}
|
||||||
onChange={handleTitleChange}
|
onChange={handleTitleChange}
|
||||||
/>
|
/>
|
||||||
{/* Decide whether to show file upload or replay selector */}
|
|
||||||
{/* @if TARGET='web' */}
|
|
||||||
<>
|
|
||||||
{showSourceSelector && (
|
|
||||||
<fieldset-section>
|
|
||||||
<div className="section__actions--between section__actions--align-bottom">
|
|
||||||
<div>
|
|
||||||
<label>{__('Replay video available')}</label>
|
|
||||||
<div className="button-group">
|
|
||||||
{fileSelectorModes.map((fmode) => (
|
|
||||||
<Button
|
|
||||||
key={fmode.label}
|
|
||||||
icon={fmode.icon || undefined}
|
|
||||||
iconSize={18}
|
|
||||||
label={fmode.label}
|
|
||||||
button="alt"
|
|
||||||
onClick={() => {
|
|
||||||
// $FlowFixMe
|
|
||||||
handleFileSource(fmode.actionName);
|
|
||||||
}}
|
|
||||||
className={classnames('button-toggle', {
|
|
||||||
'button-toggle--active': fileSelectSource === fmode.actionName,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{fileSelectSource === SOURCE_SELECT && (
|
|
||||||
<Button
|
|
||||||
button="secondary"
|
|
||||||
label={__('Check for Replays')}
|
|
||||||
disabled={isCheckingLivestreams}
|
|
||||||
icon={ICONS.REFRESH}
|
|
||||||
onClick={() =>
|
|
||||||
checkLivestreams(channelId, channelSignature.signature, channelSignature.signing_ts)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</fieldset-section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{fileSelectSource === SOURCE_UPLOAD && showFileUpload && (
|
|
||||||
<>
|
|
||||||
<FileSelector
|
|
||||||
label={__('File')}
|
|
||||||
disabled={disabled}
|
|
||||||
currentPath={currentFile}
|
|
||||||
onFileChosen={handleFileChange}
|
|
||||||
// https://stackoverflow.com/questions/19107685/safari-input-type-file-accept-video-ignores-mp4-files
|
|
||||||
placeholder={__('Select a file to upload')}
|
|
||||||
/>
|
|
||||||
{getUploadMessage()}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{fileSelectSource === SOURCE_SELECT && showFileUpload && hasLivestreamData && !isCheckingLivestreams && (
|
|
||||||
<>
|
|
||||||
<fieldset-section>
|
|
||||||
<label>{__('Select Replay')}</label>
|
|
||||||
<div className="table__wrapper">
|
|
||||||
<table className="table table--livestream-data">
|
|
||||||
<tbody>
|
|
||||||
{livestreamData.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE).map((item, i) => (
|
|
||||||
<tr
|
|
||||||
onClick={() => setSelectedFileIndex((currentPage - 1) * PAGE_SIZE + i)}
|
|
||||||
key={item.id}
|
|
||||||
className={classnames('livestream__data-row', {
|
|
||||||
'livestream__data-row--selected': selectedFileIndex === (currentPage - 1) * PAGE_SIZE + i,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
<FormField
|
|
||||||
type="radio"
|
|
||||||
checked={selectedFileIndex === (currentPage - 1) * PAGE_SIZE + i}
|
|
||||||
label={null}
|
|
||||||
onClick={() => setSelectedFileIndex((currentPage - 1) * PAGE_SIZE + i)}
|
|
||||||
className="livestream__data-row-radio"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div className="livestream_thumb_container">
|
|
||||||
{item.data.thumbnails.slice(0, 3).map((thumb) => (
|
|
||||||
<img key={thumb} className="livestream___thumb" src={thumb} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{`${Math.floor(item.data.fileDuration / 60)} ${
|
|
||||||
Math.floor(item.data.fileDuration / 60) > 1 ? __('minutes') : __('minute')
|
|
||||||
}`}
|
|
||||||
<div className="table__item-label">
|
|
||||||
{`${moment(item.data.uploadedAt).from(moment())}`}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<CopyableText
|
|
||||||
primaryButton
|
|
||||||
copyable={normalizeUrlForProtocol(item.data.fileLocation)}
|
|
||||||
snackMessage={__('Url copied.')}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</fieldset-section>
|
|
||||||
<fieldset-group class="fieldset-group--smushed fieldgroup--paginate">
|
|
||||||
<fieldset-section>
|
|
||||||
<ReactPaginate
|
|
||||||
pageCount={totalPages}
|
|
||||||
pageRangeDisplayed={2}
|
|
||||||
previousLabel="‹"
|
|
||||||
nextLabel="›"
|
|
||||||
activeClassName="pagination__item--selected"
|
|
||||||
pageClassName="pagination__item"
|
|
||||||
previousClassName="pagination__item pagination__item--previous"
|
|
||||||
nextClassName="pagination__item pagination__item--next"
|
|
||||||
breakClassName="pagination__item pagination__item--break"
|
|
||||||
marginPagesDisplayed={2}
|
|
||||||
onPageChange={(e) => handlePaginateReplays(e.selected + 1)}
|
|
||||||
forcePage={currentPage - 1}
|
|
||||||
initialPage={currentPage - 1}
|
|
||||||
containerClassName="pagination"
|
|
||||||
/>
|
|
||||||
</fieldset-section>
|
|
||||||
</fieldset-group>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{fileSelectSource === SOURCE_SELECT && showFileUpload && !hasLivestreamData && !isCheckingLivestreams && (
|
|
||||||
<div className="main--empty empty">
|
|
||||||
<Empty text={__('No replays found.')} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{fileSelectSource === SOURCE_SELECT && showFileUpload && isCheckingLivestreams && (
|
|
||||||
<div className="main--empty empty">
|
|
||||||
<Spinner small />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
{/* @endif */}
|
|
||||||
{/* @if TARGET='app' */}
|
|
||||||
{showFileUpload && (
|
{showFileUpload && (
|
||||||
|
<>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
label={__('File')}
|
label={__('File')}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -623,6 +332,8 @@ function PublishFile(props: Props) {
|
||||||
// https://stackoverflow.com/questions/19107685/safari-input-type-file-accept-video-ignores-mp4-files
|
// https://stackoverflow.com/questions/19107685/safari-input-type-file-accept-video-ignores-mp4-files
|
||||||
placeholder={__('Select file to upload')}
|
placeholder={__('Select file to upload')}
|
||||||
/>
|
/>
|
||||||
|
{getUploadMessage()}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{showFileUpload && (
|
{showFileUpload && (
|
||||||
<FormField
|
<FormField
|
||||||
|
@ -658,7 +369,6 @@ function PublishFile(props: Props) {
|
||||||
</I18nMessage>
|
</I18nMessage>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{/* @endif */}
|
|
||||||
{isPublishPost && (
|
{isPublishPost && (
|
||||||
<PostEditor
|
<PostEditor
|
||||||
label={__('Post --[noun, markdown post tab button]--')}
|
label={__('Post --[noun, markdown post tab button]--')}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
selectIsResolvingPublishUris,
|
selectIsResolvingPublishUris,
|
||||||
selectMyClaimForUri,
|
selectMyClaimForUri,
|
||||||
} from 'redux/selectors/publish';
|
} from 'redux/selectors/publish';
|
||||||
import { selectMyChannelClaims, makeSelectClaimIsStreamPlaceholder } from 'redux/selectors/claims';
|
import { selectMyChannelClaims } from 'redux/selectors/claims';
|
||||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { doClaimInitialRewards } from 'redux/actions/rewards';
|
import { doClaimInitialRewards } from 'redux/actions/rewards';
|
||||||
|
@ -45,7 +45,6 @@ const select = (state) => {
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
// The winning claim for a short lbry uri
|
// The winning claim for a short lbry uri
|
||||||
amountNeededForTakeover: selectTakeOverAmount(state),
|
amountNeededForTakeover: selectTakeOverAmount(state),
|
||||||
isLivestreamClaim: makeSelectClaimIsStreamPlaceholder(permanentUrl)(state),
|
|
||||||
isPostClaim,
|
isPostClaim,
|
||||||
permanentUrl,
|
permanentUrl,
|
||||||
// My previously published claims under this short lbry uri
|
// My previously published claims under this short lbry uri
|
||||||
|
|
|
@ -8,9 +8,8 @@
|
||||||
File upload is carried out in the background by that function.
|
File upload is carried out in the background by that function.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SITE_NAME, ENABLE_NO_SOURCE_CLAIMS, CHANNEL_STAKED_LEVEL_LIVESTREAM } from 'config';
|
import { SITE_NAME } from 'config';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import Lbry from 'lbry';
|
|
||||||
import { buildURI, isURIValid, isNameValid } from 'util/lbryURI';
|
import { buildURI, isURIValid, isNameValid } from 'util/lbryURI';
|
||||||
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
@ -29,13 +28,9 @@ import I18nMessage from 'component/i18nMessage';
|
||||||
import * as PUBLISH_MODES from 'constants/publish_types';
|
import * as PUBLISH_MODES from 'constants/publish_types';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import Spinner from 'component/spinner';
|
import Spinner from 'component/spinner';
|
||||||
import { toHex } from 'util/hex';
|
|
||||||
import { LIVESTREAM_REPLAY_API } from 'constants/livestream';
|
|
||||||
|
|
||||||
// @if TARGET='app'
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import tempy from 'tempy';
|
import tempy from 'tempy';
|
||||||
// @endif
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
disabled: boolean,
|
disabled: boolean,
|
||||||
|
@ -88,7 +83,6 @@ type Props = {
|
||||||
incognito: boolean,
|
incognito: boolean,
|
||||||
user: ?User,
|
user: ?User,
|
||||||
activeChannelStakedLevel: number,
|
activeChannelStakedLevel: number,
|
||||||
isLivestreamClaim: boolean,
|
|
||||||
isPostClaim: boolean,
|
isPostClaim: boolean,
|
||||||
permanentUrl: ?string,
|
permanentUrl: ?string,
|
||||||
remoteUrl: ?string,
|
remoteUrl: ?string,
|
||||||
|
@ -127,9 +121,6 @@ function PublishForm(props: Props) {
|
||||||
enablePublishPreview,
|
enablePublishPreview,
|
||||||
activeChannelClaim,
|
activeChannelClaim,
|
||||||
incognito,
|
incognito,
|
||||||
user,
|
|
||||||
activeChannelStakedLevel,
|
|
||||||
isLivestreamClaim,
|
|
||||||
isPostClaim,
|
isPostClaim,
|
||||||
permanentUrl,
|
permanentUrl,
|
||||||
remoteUrl,
|
remoteUrl,
|
||||||
|
@ -143,46 +134,31 @@ function PublishForm(props: Props) {
|
||||||
const TYPE_PARAM = 'type';
|
const TYPE_PARAM = 'type';
|
||||||
const uploadType = urlParams.get(TYPE_PARAM);
|
const uploadType = urlParams.get(TYPE_PARAM);
|
||||||
const _uploadType = uploadType && uploadType.toLowerCase();
|
const _uploadType = uploadType && uploadType.toLowerCase();
|
||||||
const enableLivestream =
|
|
||||||
ENABLE_NO_SOURCE_CLAIMS &&
|
|
||||||
user &&
|
|
||||||
!user.odysee_live_disabled &&
|
|
||||||
(activeChannelStakedLevel >= CHANNEL_STAKED_LEVEL_LIVESTREAM || user.odysee_live_enabled);
|
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
const AVAILABLE_MODES = Object.values(PUBLISH_MODES).filter((mode) => {
|
const AVAILABLE_MODES = Object.values(PUBLISH_MODES).filter((mode) => {
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
if (editingURI) {
|
if (editingURI) {
|
||||||
if (isPostClaim) {
|
if (isPostClaim) {
|
||||||
return mode === PUBLISH_MODES.POST;
|
return mode === PUBLISH_MODES.POST;
|
||||||
} else if (isLivestreamClaim) {
|
|
||||||
return mode === PUBLISH_MODES.LIVESTREAM && enableLivestream;
|
|
||||||
} else {
|
} else {
|
||||||
return mode === PUBLISH_MODES.FILE;
|
return mode === PUBLISH_MODES.FILE;
|
||||||
}
|
}
|
||||||
} else if (_uploadType) {
|
} else if (_uploadType) {
|
||||||
return mode === _uploadType && (mode !== PUBLISH_MODES.LIVESTREAM || enableLivestream);
|
return mode === _uploadType;
|
||||||
} else {
|
} else {
|
||||||
return mode !== PUBLISH_MODES.LIVESTREAM || enableLivestream;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const MODE_TO_I18N_STR = {
|
const MODE_TO_I18N_STR = {
|
||||||
[PUBLISH_MODES.FILE]: 'File',
|
[PUBLISH_MODES.FILE]: 'File',
|
||||||
[PUBLISH_MODES.POST]: 'Post --[noun, markdown post tab button]--',
|
[PUBLISH_MODES.POST]: 'Post --[noun, markdown post tab button]--',
|
||||||
[PUBLISH_MODES.LIVESTREAM]: 'Livestream --[noun, livestream tab button]--',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [mode, setMode] = React.useState(_uploadType || PUBLISH_MODES.FILE);
|
const [mode, setMode] = React.useState(_uploadType || PUBLISH_MODES.FILE);
|
||||||
const [isCheckingLivestreams, setCheckingLivestreams] = React.useState(false);
|
|
||||||
|
|
||||||
let customSubtitle;
|
let customSubtitle;
|
||||||
if (mode === PUBLISH_MODES.LIVESTREAM || isLivestreamClaim) {
|
if (mode === PUBLISH_MODES.POST || isPostClaim) {
|
||||||
if (isLivestreamClaim) {
|
|
||||||
customSubtitle = __('Update your livestream');
|
|
||||||
} else {
|
|
||||||
customSubtitle = __('Prepare an upcoming livestream');
|
|
||||||
}
|
|
||||||
} else if (mode === PUBLISH_MODES.POST || isPostClaim) {
|
|
||||||
if (isPostClaim) {
|
if (isPostClaim) {
|
||||||
customSubtitle = __('Edit your post');
|
customSubtitle = __('Edit your post');
|
||||||
} else {
|
} else {
|
||||||
|
@ -206,16 +182,12 @@ function PublishForm(props: Props) {
|
||||||
const [prevFileText, setPrevFileText] = React.useState('');
|
const [prevFileText, setPrevFileText] = React.useState('');
|
||||||
|
|
||||||
const [waitForFile, setWaitForFile] = useState(false);
|
const [waitForFile, setWaitForFile] = useState(false);
|
||||||
const [livestreamData, setLivestreamData] = React.useState([]);
|
|
||||||
const [signedMessage, setSignedMessage] = React.useState({ signature: undefined, signing_ts: undefined });
|
|
||||||
const signedMessageStr = JSON.stringify(signedMessage);
|
|
||||||
const TAGS_LIMIT = 5;
|
const TAGS_LIMIT = 5;
|
||||||
const fileFormDisabled = mode === PUBLISH_MODES.FILE && !filePath && !remoteUrl;
|
const fileFormDisabled = mode === PUBLISH_MODES.FILE && !filePath && !remoteUrl;
|
||||||
const emptyPostError = mode === PUBLISH_MODES.POST && (!fileText || fileText.trim() === '');
|
const emptyPostError = mode === PUBLISH_MODES.POST && (!fileText || fileText.trim() === '');
|
||||||
const formDisabled = (fileFormDisabled && !editingURI) || emptyPostError || publishing;
|
const formDisabled = (fileFormDisabled && !editingURI) || emptyPostError || publishing;
|
||||||
const isInProgress = filePath || editingURI || name || title;
|
const isInProgress = filePath || editingURI || name || title;
|
||||||
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
|
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
|
||||||
const activeChannelClaimStr = JSON.stringify(activeChannelClaim);
|
|
||||||
// Editing content info
|
// Editing content info
|
||||||
const fileMimeType =
|
const fileMimeType =
|
||||||
myClaimForUri && myClaimForUri.value && myClaimForUri.value.source
|
myClaimForUri && myClaimForUri.value && myClaimForUri.value.source
|
||||||
|
@ -251,27 +223,6 @@ function PublishForm(props: Props) {
|
||||||
|
|
||||||
const [previewing, setPreviewing] = React.useState(false);
|
const [previewing, setPreviewing] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (activeChannelClaimStr) {
|
|
||||||
const channelClaim = JSON.parse(activeChannelClaimStr);
|
|
||||||
const message = 'get-claim-id-replays';
|
|
||||||
setSignedMessage({ signature: null, signing_ts: null });
|
|
||||||
// ensure we have a channel
|
|
||||||
if (channelClaim.claim_id) {
|
|
||||||
Lbry.channel_sign({
|
|
||||||
channel_id: channelClaim.claim_id,
|
|
||||||
hexdata: toHex(message),
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
setSignedMessage(data);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setSignedMessage({ signature: null, signing_ts: null });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [activeChannelClaimStr, setSignedMessage]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hasClaimedInitialRewards) {
|
if (!hasClaimedInitialRewards) {
|
||||||
claimInitialRewards();
|
claimInitialRewards();
|
||||||
|
@ -286,32 +237,6 @@ function PublishForm(props: Props) {
|
||||||
}
|
}
|
||||||
}, [modal]);
|
}, [modal]);
|
||||||
|
|
||||||
// move this to lbryinc OR to a file under ui, and/or provide a standardized livestreaming config.
|
|
||||||
function fetchLivestreams(channelId, signature, timestamp) {
|
|
||||||
setCheckingLivestreams(true);
|
|
||||||
fetch(`${LIVESTREAM_REPLAY_API}/${channelId}?signature=${signature || ''}&signing_ts=${timestamp || ''}`) // claimChannelId
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((res) => {
|
|
||||||
if (!res || !res.data) {
|
|
||||||
setLivestreamData([]);
|
|
||||||
}
|
|
||||||
setLivestreamData(res.data);
|
|
||||||
setCheckingLivestreams(false);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
setLivestreamData([]);
|
|
||||||
setCheckingLivestreams(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const signedMessage = JSON.parse(signedMessageStr);
|
|
||||||
if (claimChannelId && signedMessage.signature) {
|
|
||||||
fetchLivestreams(claimChannelId, signedMessage.signature, signedMessage.signing_ts);
|
|
||||||
}
|
|
||||||
}, [claimChannelId, signedMessageStr]);
|
|
||||||
|
|
||||||
const isLivestreamMode = mode === PUBLISH_MODES.LIVESTREAM;
|
|
||||||
let submitLabel;
|
let submitLabel;
|
||||||
|
|
||||||
if (isClaimingInitialRewards) {
|
if (isClaimingInitialRewards) {
|
||||||
|
@ -319,8 +244,6 @@ function PublishForm(props: Props) {
|
||||||
} else if (publishing) {
|
} else if (publishing) {
|
||||||
if (isStillEditing) {
|
if (isStillEditing) {
|
||||||
submitLabel = __('Saving...');
|
submitLabel = __('Saving...');
|
||||||
} else if (isLivestreamMode) {
|
|
||||||
submitLabel = __('Creating...');
|
|
||||||
} else {
|
} else {
|
||||||
submitLabel = __('Uploading...');
|
submitLabel = __('Uploading...');
|
||||||
}
|
}
|
||||||
|
@ -329,8 +252,6 @@ function PublishForm(props: Props) {
|
||||||
} else {
|
} else {
|
||||||
if (isStillEditing) {
|
if (isStillEditing) {
|
||||||
submitLabel = __('Save');
|
submitLabel = __('Save');
|
||||||
} else if (isLivestreamMode) {
|
|
||||||
submitLabel = __('Create');
|
|
||||||
} else {
|
} else {
|
||||||
submitLabel = __('Upload');
|
submitLabel = __('Upload');
|
||||||
}
|
}
|
||||||
|
@ -398,26 +319,20 @@ function PublishForm(props: Props) {
|
||||||
}
|
}
|
||||||
}, [editingURI, resolveUri]);
|
}, [editingURI, resolveUri]);
|
||||||
|
|
||||||
// set isMarkdownPost in publish form if so, also update isLivestreamPublish
|
// set isMarkdownPost in publish form if so
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updatePublishForm({
|
updatePublishForm({
|
||||||
isMarkdownPost: mode === PUBLISH_MODES.POST,
|
isMarkdownPost: mode === PUBLISH_MODES.POST,
|
||||||
isLivestreamPublish: mode === PUBLISH_MODES.LIVESTREAM,
|
|
||||||
});
|
});
|
||||||
}, [mode, updatePublishForm]);
|
}, [mode, updatePublishForm]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (incognito) {
|
if (incognito) {
|
||||||
updatePublishForm({ channel: undefined });
|
updatePublishForm({ channel: undefined });
|
||||||
|
|
||||||
// Anonymous livestreams aren't supported
|
|
||||||
if (isLivestreamMode) {
|
|
||||||
setMode(PUBLISH_MODES.FILE);
|
|
||||||
}
|
|
||||||
} else if (activeChannelName) {
|
} else if (activeChannelName) {
|
||||||
updatePublishForm({ channel: activeChannelName });
|
updatePublishForm({ channel: activeChannelName });
|
||||||
}
|
}
|
||||||
}, [activeChannelName, incognito, updatePublishForm, isLivestreamMode]);
|
}, [activeChannelName, incognito, updatePublishForm]);
|
||||||
|
|
||||||
// set mode based on urlParams 'type'
|
// set mode based on urlParams 'type'
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -437,19 +352,10 @@ function PublishForm(props: Props) {
|
||||||
setMode(PUBLISH_MODES.POST);
|
setMode(PUBLISH_MODES.POST);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// LiveStream publish
|
|
||||||
if (_uploadType === PUBLISH_MODES.LIVESTREAM.toLowerCase()) {
|
|
||||||
if (enableLivestream) {
|
|
||||||
setMode(PUBLISH_MODES.LIVESTREAM);
|
|
||||||
} else {
|
|
||||||
setMode(PUBLISH_MODES.FILE);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to standard file publish
|
// Default to standard file publish
|
||||||
setMode(PUBLISH_MODES.FILE);
|
setMode(PUBLISH_MODES.FILE);
|
||||||
}, [_uploadType, enableLivestream]);
|
}, [_uploadType]);
|
||||||
|
|
||||||
// if we have a type urlparam, update it? necessary?
|
// if we have a type urlparam, update it? necessary?
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -509,7 +415,7 @@ function PublishForm(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Publish file
|
// Publish file
|
||||||
if (mode === PUBLISH_MODES.FILE || isLivestreamMode) {
|
if (mode === PUBLISH_MODES.FILE) {
|
||||||
runPublish = true;
|
runPublish = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,7 +461,7 @@ function PublishForm(props: Props) {
|
||||||
// Editing claim uri
|
// Editing claim uri
|
||||||
return (
|
return (
|
||||||
<div className="card-stack">
|
<div className="card-stack">
|
||||||
<ChannelSelect hideAnon={isLivestreamMode} disabled={disabled} />
|
<ChannelSelect disabled={disabled} />
|
||||||
|
|
||||||
<PublishFile
|
<PublishFile
|
||||||
uri={permanentUrl}
|
uri={permanentUrl}
|
||||||
|
@ -565,13 +471,9 @@ function PublishForm(props: Props) {
|
||||||
inProgress={isInProgress}
|
inProgress={isInProgress}
|
||||||
setPublishMode={setMode}
|
setPublishMode={setMode}
|
||||||
setPrevFileText={setPrevFileText}
|
setPrevFileText={setPrevFileText}
|
||||||
livestreamData={livestreamData}
|
|
||||||
subtitle={customSubtitle}
|
subtitle={customSubtitle}
|
||||||
setWaitForFile={setWaitForFile}
|
setWaitForFile={setWaitForFile}
|
||||||
isCheckingLivestreams={isCheckingLivestreams}
|
|
||||||
checkLivestreams={fetchLivestreams}
|
|
||||||
channelId={claimChannelId}
|
channelId={claimChannelId}
|
||||||
channelSignature={signedMessage}
|
|
||||||
header={
|
header={
|
||||||
<>
|
<>
|
||||||
{AVAILABLE_MODES.map((modeName) => (
|
{AVAILABLE_MODES.map((modeName) => (
|
||||||
|
@ -595,7 +497,7 @@ function PublishForm(props: Props) {
|
||||||
{!publishing && (
|
{!publishing && (
|
||||||
<div className={classnames({ 'card--disabled': formDisabled })}>
|
<div className={classnames({ 'card--disabled': formDisabled })}>
|
||||||
{mode !== PUBLISH_MODES.POST && <PublishDescription disabled={formDisabled} />}
|
{mode !== PUBLISH_MODES.POST && <PublishDescription disabled={formDisabled} />}
|
||||||
<Card actions={<SelectThumbnail livestreamdData={livestreamData} />} />
|
<Card actions={<SelectThumbnail />} />
|
||||||
<TagsSelect
|
<TagsSelect
|
||||||
suggestMature
|
suggestMature
|
||||||
disableAutoFocus
|
disableAutoFocus
|
||||||
|
@ -624,7 +526,7 @@ function PublishForm(props: Props) {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PublishBid disabled={isStillEditing || formDisabled} />
|
<PublishBid disabled={isStillEditing || formDisabled} />
|
||||||
{!isLivestreamMode && <PublishPrice disabled={formDisabled} />}
|
<PublishPrice disabled={formDisabled} />
|
||||||
<PublishAdditionalOptions disabled={formDisabled} />
|
<PublishAdditionalOptions disabled={formDisabled} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -46,8 +46,6 @@ import InvitedPage from 'page/invited';
|
||||||
import LibraryPage from 'page/library';
|
import LibraryPage from 'page/library';
|
||||||
import ListBlockedPage from 'page/listBlocked';
|
import ListBlockedPage from 'page/listBlocked';
|
||||||
import ListsPage from 'page/lists';
|
import ListsPage from 'page/lists';
|
||||||
import LiveStreamSetupPage from 'page/livestreamSetup';
|
|
||||||
import LivestreamCurrentPage from 'page/livestreamCurrent';
|
|
||||||
import OwnComments from 'page/ownComments';
|
import OwnComments from 'page/ownComments';
|
||||||
import PasswordResetPage from 'page/passwordReset';
|
import PasswordResetPage from 'page/passwordReset';
|
||||||
import PasswordSetPage from 'page/passwordSet';
|
import PasswordSetPage from 'page/passwordSet';
|
||||||
|
@ -306,8 +304,6 @@ function AppRouter(props: Props) {
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_CREATOR}`} component={SettingsCreatorPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_CREATOR}`} component={SettingsCreatorPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.LIVESTREAM}`} component={LiveStreamSetupPage} />
|
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.LIVESTREAM_CURRENT}`} component={LivestreamCurrentPage} />
|
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.BUY}`} component={BuyPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.BUY}`} component={BuyPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.RECEIVE}`} component={ReceivePage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.RECEIVE}`} component={ReceivePage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.SEND}`} component={SendPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.SEND}`} component={SendPage} />
|
||||||
|
|
|
@ -463,16 +463,6 @@ export const REPORT_CONTENT_STARTED = 'REPORT_CONTENT_STARTED';
|
||||||
export const REPORT_CONTENT_COMPLETED = 'REPORT_CONTENT_COMPLETED';
|
export const REPORT_CONTENT_COMPLETED = 'REPORT_CONTENT_COMPLETED';
|
||||||
export const REPORT_CONTENT_FAILED = 'REPORT_CONTENT_FAILED';
|
export const REPORT_CONTENT_FAILED = 'REPORT_CONTENT_FAILED';
|
||||||
|
|
||||||
// Livestream
|
|
||||||
export const FETCH_NO_SOURCE_CLAIMS_STARTED = 'FETCH_NO_SOURCE_CLAIMS_STARTED';
|
|
||||||
export const FETCH_NO_SOURCE_CLAIMS_COMPLETED = 'FETCH_NO_SOURCE_CLAIMS_COMPLETED';
|
|
||||||
export const FETCH_NO_SOURCE_CLAIMS_FAILED = 'FETCH_NO_SOURCE_CLAIMS_FAILED';
|
|
||||||
export const VIEWERS_RECEIVED = 'VIEWERS_RECEIVED';
|
|
||||||
export const FETCH_ACTIVE_LIVESTREAMS_STARTED = 'FETCH_ACTIVE_LIVESTREAMS_STARTED';
|
|
||||||
export const FETCH_ACTIVE_LIVESTREAMS_FAILED = 'FETCH_ACTIVE_LIVESTREAMS_FAILED';
|
|
||||||
export const FETCH_ACTIVE_LIVESTREAMS_SKIPPED = 'FETCH_ACTIVE_LIVESTREAMS_SKIPPED';
|
|
||||||
export const FETCH_ACTIVE_LIVESTREAMS_COMPLETED = 'FETCH_ACTIVE_LIVESTREAMS_COMPLETED';
|
|
||||||
|
|
||||||
// Blacklist
|
// Blacklist
|
||||||
export const FETCH_BLACK_LISTED_CONTENT_STARTED = 'FETCH_BLACK_LISTED_CONTENT_STARTED';
|
export const FETCH_BLACK_LISTED_CONTENT_STARTED = 'FETCH_BLACK_LISTED_CONTENT_STARTED';
|
||||||
export const FETCH_BLACK_LISTED_CONTENT_COMPLETED = 'FETCH_BLACK_LISTED_CONTENT_COMPLETED';
|
export const FETCH_BLACK_LISTED_CONTENT_COMPLETED = 'FETCH_BLACK_LISTED_CONTENT_COMPLETED';
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
export const FF_MAX_CHARS_DEFAULT = 2000;
|
export const FF_MAX_CHARS_DEFAULT = 2000;
|
||||||
export const FF_MAX_CHARS_IN_COMMENT = 2000;
|
export const FF_MAX_CHARS_IN_COMMENT = 2000;
|
||||||
export const FF_MAX_CHARS_IN_LIVESTREAM_COMMENT = 300;
|
|
||||||
export const FF_MAX_CHARS_IN_DESCRIPTION = 5000;
|
export const FF_MAX_CHARS_IN_DESCRIPTION = 5000;
|
||||||
export const FF_MAX_CHARS_REPORT_CONTENT_DETAILS = 500;
|
export const FF_MAX_CHARS_REPORT_CONTENT_DETAILS = 500;
|
||||||
export const FF_MAX_CHARS_REPORT_CONTENT_ADDRESS = 255;
|
export const FF_MAX_CHARS_REPORT_CONTENT_ADDRESS = 255;
|
||||||
|
|
|
@ -158,9 +158,6 @@ export const CHEESE = 'Cheese';
|
||||||
export const PEACE = 'Peace';
|
export const PEACE = 'Peace';
|
||||||
export const PORK_BUN = 'PorkBun';
|
export const PORK_BUN = 'PorkBun';
|
||||||
export const MIND_BLOWN = 'MindBlown';
|
export const MIND_BLOWN = 'MindBlown';
|
||||||
export const LIVESTREAM = 'Livestream';
|
|
||||||
export const LIVESTREAM_SOLID = 'LivestreamSolid';
|
|
||||||
export const LIVESTREAM_MONOCHROME = 'LivestreamMono';
|
|
||||||
export const STACK = 'stack';
|
export const STACK = 'stack';
|
||||||
export const TIME = 'time';
|
export const TIME = 'time';
|
||||||
export const GLOBE = 'globe';
|
export const GLOBE = 'globe';
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
export const LIVESTREAM_EMBED_URL = 'https://player.live.odysee.com/odysee';
|
|
||||||
export const LIVESTREAM_LIVE_API = 'https://api.live.odysee.com/v1/odysee/live';
|
|
||||||
export const LIVESTREAM_REPLAY_API = 'https://api.live.odysee.com/v1/replays/odysee';
|
|
||||||
export const LIVESTREAM_RTMP_URL = 'rtmp://stream.odysee.com/live';
|
|
|
@ -34,8 +34,6 @@ export const PAGE_TITLE = {
|
||||||
[PAGES.HELP]: 'Help',
|
[PAGES.HELP]: 'Help',
|
||||||
[PAGES.INVITE]: 'Invite',
|
[PAGES.INVITE]: 'Invite',
|
||||||
[PAGES.LISTS]: 'Lists',
|
[PAGES.LISTS]: 'Lists',
|
||||||
[PAGES.LIVESTREAM]: 'Go Live on Odysee',
|
|
||||||
[PAGES.LIVESTREAM_CURRENT]: 'Live (Experimental)',
|
|
||||||
[PAGES.NOTIFICATIONS]: 'Notifications',
|
[PAGES.NOTIFICATIONS]: 'Notifications',
|
||||||
[PAGES.RECEIVE]: 'Your address',
|
[PAGES.RECEIVE]: 'Your address',
|
||||||
[PAGES.REPORT]: 'Report an issue or request a feature',
|
[PAGES.REPORT]: 'Report an issue or request a feature',
|
||||||
|
|
|
@ -73,7 +73,5 @@ exports.SWAP = 'swap';
|
||||||
exports.CHANNEL_NEW = 'channel/new';
|
exports.CHANNEL_NEW = 'channel/new';
|
||||||
exports.NOTIFICATIONS = 'notifications';
|
exports.NOTIFICATIONS = 'notifications';
|
||||||
exports.YOUTUBE_SYNC = 'youtube';
|
exports.YOUTUBE_SYNC = 'youtube';
|
||||||
exports.LIVESTREAM = 'livestream';
|
|
||||||
exports.LIVESTREAM_CURRENT = 'live';
|
|
||||||
exports.GENERAL = 'general';
|
exports.GENERAL = 'general';
|
||||||
exports.LIST = 'list';
|
exports.LIST = 'list';
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export const FILE = 'file';
|
export const FILE = 'file';
|
||||||
export const POST = 'post';
|
export const POST = 'post';
|
||||||
export const LIVESTREAM = 'livestream';
|
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doHideModal } from 'redux/actions/app';
|
import { doHideModal } from 'redux/actions/app';
|
||||||
import ModalPublishSuccess from './view';
|
import ModalPublishSuccess from './view';
|
||||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
|
||||||
import { doClearPublish } from 'redux/actions/publish';
|
import { doClearPublish } from 'redux/actions/publish';
|
||||||
import { push } from 'connected-react-router';
|
import { push } from 'connected-react-router';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
closeModal: () => dispatch(doHideModal()),
|
closeModal: () => dispatch(doHideModal()),
|
||||||
clearPublish: () => dispatch(doClearPublish()),
|
clearPublish: () => dispatch(doClearPublish()),
|
||||||
navigate: (path) => dispatch(push(path)),
|
navigate: (path) => dispatch(push(path)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(ModalPublishSuccess);
|
export default connect(null, perform)(ModalPublishSuccess);
|
||||||
|
|
|
@ -15,7 +15,6 @@ type Props = {
|
||||||
isEdit: boolean,
|
isEdit: boolean,
|
||||||
filePath: ?string,
|
filePath: ?string,
|
||||||
lbryFirstError: ?string,
|
lbryFirstError: ?string,
|
||||||
claim: Claim,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModalPublishSuccess extends React.PureComponent<Props> {
|
class ModalPublishSuccess extends React.PureComponent<Props> {
|
||||||
|
@ -24,13 +23,10 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
||||||
clearPublish();
|
clearPublish();
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { closeModal, clearPublish, navigate, uri, isEdit, filePath, lbryFirstError, claim } = this.props;
|
const { closeModal, clearPublish, navigate, uri, isEdit, filePath, lbryFirstError } = this.props;
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
const livestream = claim && claim.value && claim.value_type === 'stream' && !claim.value.source;
|
|
||||||
let contentLabel;
|
let contentLabel;
|
||||||
if (livestream) {
|
if (isEdit) {
|
||||||
contentLabel = __('Livestream Created');
|
|
||||||
} else if (isEdit) {
|
|
||||||
contentLabel = __('Update published');
|
contentLabel = __('Update published');
|
||||||
} else {
|
} else {
|
||||||
contentLabel = __('File published');
|
contentLabel = __('File published');
|
||||||
|
@ -39,10 +35,6 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
||||||
let publishMessage;
|
let publishMessage;
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
publishMessage = __('Your update is now pending. It will take a few minutes to appear for other users.');
|
publishMessage = __('Your update is now pending. It will take a few minutes to appear for other users.');
|
||||||
} else if (livestream) {
|
|
||||||
publishMessage = __(
|
|
||||||
'Your livestream is now pending. You will be able to start shortly at the streaming dashboard.'
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
publishMessage = __('Your content will be live shortly.');
|
publishMessage = __('Your content will be live shortly.');
|
||||||
}
|
}
|
||||||
|
@ -54,7 +46,7 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
||||||
return (
|
return (
|
||||||
<Modal isOpen type="card" contentLabel={__(contentLabel)} onAborted={handleClose}>
|
<Modal isOpen type="card" contentLabel={__(contentLabel)} onAborted={handleClose}>
|
||||||
<Card
|
<Card
|
||||||
title={livestream ? __('Livestream Created') : __('Success')}
|
title={__('Success')}
|
||||||
subtitle={publishMessage}
|
subtitle={publishMessage}
|
||||||
body={
|
body={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -75,7 +67,6 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
<div className="section__actions">
|
<div className="section__actions">
|
||||||
{!livestream && (
|
|
||||||
<Button
|
<Button
|
||||||
button="primary"
|
button="primary"
|
||||||
label={__('View My Uploads')}
|
label={__('View My Uploads')}
|
||||||
|
@ -85,18 +76,6 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
||||||
closeModal();
|
closeModal();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{livestream && (
|
|
||||||
<Button
|
|
||||||
button="primary"
|
|
||||||
label={__('View My Dashboard')}
|
|
||||||
onClick={() => {
|
|
||||||
clearPublish();
|
|
||||||
navigate(`/$/${PAGES.LIVESTREAM}`);
|
|
||||||
closeModal();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button button="link" label={__('Close')} onClick={handleClose} />
|
<Button button="link" label={__('Close')} onClick={handleClose} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,21 @@ import { connect } from 'react-redux';
|
||||||
import { doHideModal } from 'redux/actions/app';
|
import { doHideModal } from 'redux/actions/app';
|
||||||
import ModalPublishPreview from './view';
|
import ModalPublishPreview from './view';
|
||||||
import { makeSelectPublishFormValue, selectPublishFormValues, selectIsStillEditing } from 'redux/selectors/publish';
|
import { makeSelectPublishFormValue, selectPublishFormValues, selectIsStillEditing } from 'redux/selectors/publish';
|
||||||
import { selectMyChannelClaims, makeSelectClaimIsStreamPlaceholder } from 'redux/selectors/claims';
|
import { selectMyChannelClaims } from 'redux/selectors/claims';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { selectFfmpegStatus, makeSelectClientSetting } from 'redux/selectors/settings';
|
import { selectFfmpegStatus, makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { doPublishDesktop } from 'redux/actions/publish';
|
import { doPublishDesktop } from 'redux/actions/publish';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state) => {
|
||||||
const editingUri = makeSelectPublishFormValue('editingURI')(state);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...selectPublishFormValues(state),
|
...selectPublishFormValues(state),
|
||||||
myChannels: selectMyChannelClaims(state),
|
myChannels: selectMyChannelClaims(state),
|
||||||
isVid: makeSelectPublishFormValue('fileVid')(state),
|
isVid: makeSelectPublishFormValue('fileVid')(state),
|
||||||
publishSuccess: makeSelectPublishFormValue('publishSuccess')(state),
|
|
||||||
publishing: makeSelectPublishFormValue('publishing')(state),
|
publishing: makeSelectPublishFormValue('publishing')(state),
|
||||||
remoteFile: makeSelectPublishFormValue('remoteFileUrl')(state),
|
|
||||||
isStillEditing: selectIsStillEditing(state),
|
isStillEditing: selectIsStillEditing(state),
|
||||||
ffmpegStatus: selectFfmpegStatus(state),
|
ffmpegStatus: selectFfmpegStatus(state),
|
||||||
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
|
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
|
||||||
isLivestreamClaim: makeSelectClaimIsStreamPlaceholder(editingUri)(state),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,6 @@ type Props = {
|
||||||
myChannels: ?Array<ChannelClaim>,
|
myChannels: ?Array<ChannelClaim>,
|
||||||
publishSuccess: boolean,
|
publishSuccess: boolean,
|
||||||
publishing: boolean,
|
publishing: boolean,
|
||||||
isLivestreamClaim: boolean,
|
|
||||||
remoteFile: string,
|
remoteFile: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,12 +74,9 @@ const ModalPublishPreview = (props: Props) => {
|
||||||
setEnablePublishPreview,
|
setEnablePublishPreview,
|
||||||
isStillEditing,
|
isStillEditing,
|
||||||
myChannels,
|
myChannels,
|
||||||
publishSuccess,
|
|
||||||
publishing,
|
publishing,
|
||||||
publish,
|
publish,
|
||||||
closeModal,
|
closeModal,
|
||||||
isLivestreamClaim,
|
|
||||||
remoteFile,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const maxCharsBeforeOverflow = 128;
|
const maxCharsBeforeOverflow = 128;
|
||||||
|
@ -99,20 +95,7 @@ const ModalPublishPreview = (props: Props) => {
|
||||||
return uri;
|
return uri;
|
||||||
}, [uri]);
|
}, [uri]);
|
||||||
|
|
||||||
const livestream =
|
|
||||||
(uri && isLivestreamClaim) ||
|
|
||||||
// $FlowFixMe
|
|
||||||
(previewResponse.outputs[0] && previewResponse.outputs[0].value && !previewResponse.outputs[0].value.source);
|
|
||||||
// leave the confirm modal up if we're not going straight to upload/reflecting
|
// leave the confirm modal up if we're not going straight to upload/reflecting
|
||||||
// @if TARGET='web'
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (publishing && !livestream) {
|
|
||||||
closeModal();
|
|
||||||
} else if (publishSuccess) {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
}, [publishSuccess, publishing, livestream]);
|
|
||||||
// @endif
|
|
||||||
function onConfirmed() {
|
function onConfirmed() {
|
||||||
// Publish for real:
|
// Publish for real:
|
||||||
publish(getFilePathName(filePath), false);
|
publish(getFilePathName(filePath), false);
|
||||||
|
@ -147,13 +130,7 @@ const ModalPublishPreview = (props: Props) => {
|
||||||
const isOptimizeAvail = filePath && filePath !== '' && isVid && ffmpegStatus.available;
|
const isOptimizeAvail = filePath && filePath !== '' && isVid && ffmpegStatus.available;
|
||||||
let modalTitle;
|
let modalTitle;
|
||||||
if (isStillEditing) {
|
if (isStillEditing) {
|
||||||
if (livestream) {
|
|
||||||
modalTitle = __('Confirm Update');
|
|
||||||
} else {
|
|
||||||
modalTitle = __('Confirm Edit');
|
modalTitle = __('Confirm Edit');
|
||||||
}
|
|
||||||
} else if (livestream) {
|
|
||||||
modalTitle = __('Create Livestream');
|
|
||||||
} else {
|
} else {
|
||||||
modalTitle = __('Confirm Upload');
|
modalTitle = __('Confirm Upload');
|
||||||
}
|
}
|
||||||
|
@ -162,16 +139,12 @@ const ModalPublishPreview = (props: Props) => {
|
||||||
if (!publishing) {
|
if (!publishing) {
|
||||||
if (isStillEditing) {
|
if (isStillEditing) {
|
||||||
confirmBtnText = __('Save');
|
confirmBtnText = __('Save');
|
||||||
} else if (livestream) {
|
|
||||||
confirmBtnText = __('Create');
|
|
||||||
} else {
|
} else {
|
||||||
confirmBtnText = __('Upload');
|
confirmBtnText = __('Upload');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isStillEditing) {
|
if (isStillEditing) {
|
||||||
confirmBtnText = __('Saving');
|
confirmBtnText = __('Saving');
|
||||||
} else if (livestream) {
|
|
||||||
confirmBtnText = __('Creating');
|
|
||||||
} else {
|
} else {
|
||||||
confirmBtnText = __('Uploading');
|
confirmBtnText = __('Uploading');
|
||||||
}
|
}
|
||||||
|
@ -240,8 +213,7 @@ const ModalPublishPreview = (props: Props) => {
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<table className="table table--condensed table--publish-preview">
|
<table className="table table--condensed table--publish-preview">
|
||||||
<tbody>
|
<tbody>
|
||||||
{!livestream && !isMarkdownPost && createRow(__('File'), getFilePathName(filePath))}
|
{!isMarkdownPost && createRow(__('File'), getFilePathName(filePath))}
|
||||||
{livestream && remoteFile && createRow(__('Replay'), __('Remote File Selected'))}
|
|
||||||
{isOptimizeAvail && createRow(__('Transcode'), optimize ? __('Yes') : __('No'))}
|
{isOptimizeAvail && createRow(__('Transcode'), optimize ? __('Yes') : __('No'))}
|
||||||
{createRow(__('Title'), formattedTitle)}
|
{createRow(__('Title'), formattedTitle)}
|
||||||
{createRow(__('Description'), descriptionValue)}
|
{createRow(__('Description'), descriptionValue)}
|
||||||
|
|
|
@ -3,8 +3,6 @@ import { connect } from 'react-redux';
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { doFetchActiveLivestreams } from 'redux/actions/livestream';
|
|
||||||
import { selectActiveLivestreams } from 'redux/selectors/livestream';
|
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import { selectFollowedTags } from 'redux/selectors/tags';
|
import { selectFollowedTags } from 'redux/selectors/tags';
|
||||||
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
||||||
|
@ -22,12 +20,10 @@ const select = (state, props) => {
|
||||||
repostedClaim: repostedUri ? makeSelectClaimForUri(repostedUri)(state) : null,
|
repostedClaim: repostedUri ? makeSelectClaimForUri(repostedUri)(state) : null,
|
||||||
isAuthenticated: selectUserVerifiedEmail(state),
|
isAuthenticated: selectUserVerifiedEmail(state),
|
||||||
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
|
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
|
||||||
activeLivestreams: selectActiveLivestreams(state),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(select, {
|
export default connect(select, {
|
||||||
doToggleTagFollowDesktop,
|
doToggleTagFollowDesktop,
|
||||||
doResolveUri,
|
doResolveUri,
|
||||||
doFetchActiveLivestreams,
|
|
||||||
})(Tags);
|
})(Tags);
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
makeSelectMetadataForUri,
|
makeSelectMetadataForUri,
|
||||||
makeSelectClaimIsNsfw,
|
makeSelectClaimIsNsfw,
|
||||||
makeSelectTagInClaimOrChannelForUri,
|
makeSelectTagInClaimOrChannelForUri,
|
||||||
makeSelectClaimIsStreamPlaceholder,
|
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
||||||
import { doFetchFileInfo } from 'redux/actions/file_info';
|
import { doFetchFileInfo } from 'redux/actions/file_info';
|
||||||
|
@ -34,7 +33,6 @@ const select = (state, props) => {
|
||||||
renderMode: makeSelectFileRenderModeForUri(props.uri)(state),
|
renderMode: makeSelectFileRenderModeForUri(props.uri)(state),
|
||||||
videoTheaterMode: makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state),
|
videoTheaterMode: makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state),
|
||||||
commentsDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state),
|
commentsDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state),
|
||||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
|
||||||
collection: makeSelectCollectionForId(collectionId)(state),
|
collection: makeSelectCollectionForId(collectionId)(state),
|
||||||
collectionId,
|
collectionId,
|
||||||
position: makeSelectContentPositionForUri(props.uri)(state),
|
position: makeSelectContentPositionForUri(props.uri)(state),
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
|
@ -11,8 +10,6 @@ import FileRenderDownload from 'component/fileRenderDownload';
|
||||||
import RecommendedContent from 'component/recommendedContent';
|
import RecommendedContent from 'component/recommendedContent';
|
||||||
import CollectionContent from 'component/collectionContentSidebar';
|
import CollectionContent from 'component/collectionContentSidebar';
|
||||||
import CommentsList from 'component/commentsList';
|
import CommentsList from 'component/commentsList';
|
||||||
import Button from 'component/button';
|
|
||||||
import I18nMessage from 'component/i18nMessage';
|
|
||||||
import Empty from 'component/common/empty';
|
import Empty from 'component/common/empty';
|
||||||
|
|
||||||
import PostViewer from 'component/postViewer';
|
import PostViewer from 'component/postViewer';
|
||||||
|
@ -36,7 +33,6 @@ type Props = {
|
||||||
videoTheaterMode: boolean,
|
videoTheaterMode: boolean,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
commentsDisabled: boolean,
|
commentsDisabled: boolean,
|
||||||
isLivestream: boolean,
|
|
||||||
clearPosition: (string) => void,
|
clearPosition: (string) => void,
|
||||||
position: number,
|
position: number,
|
||||||
};
|
};
|
||||||
|
@ -55,12 +51,9 @@ function FilePage(props: Props) {
|
||||||
linkedCommentId,
|
linkedCommentId,
|
||||||
setPrimaryUri,
|
setPrimaryUri,
|
||||||
videoTheaterMode,
|
videoTheaterMode,
|
||||||
|
|
||||||
claimIsMine,
|
|
||||||
commentsDisabled,
|
commentsDisabled,
|
||||||
collection,
|
collection,
|
||||||
collectionId,
|
collectionId,
|
||||||
isLivestream,
|
|
||||||
clearPosition,
|
clearPosition,
|
||||||
position,
|
position,
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -190,18 +183,6 @@ function FilePage(props: Props) {
|
||||||
{!isMarkdown && (
|
{!isMarkdown && (
|
||||||
<div className="file-page__secondary-content">
|
<div className="file-page__secondary-content">
|
||||||
<div>
|
<div>
|
||||||
{claimIsMine && isLivestream && (
|
|
||||||
<div className="livestream__creator-message">
|
|
||||||
<h4>{__('Only visible to you')}</h4>
|
|
||||||
<I18nMessage>
|
|
||||||
People who view this link will be redirected to your livestream. Make sure to use this for sharing
|
|
||||||
so your title and thumbnail are displayed properly.
|
|
||||||
</I18nMessage>
|
|
||||||
<div className="section__actions">
|
|
||||||
<Button button="primary" navigate={`/$/${PAGES.LIVESTREAM}`} label={__('View livestream')} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{RENDER_MODES.FLOATING_MODES.includes(renderMode) && <FileTitleSection uri={uri} />}
|
{RENDER_MODES.FLOATING_MODES.includes(renderMode) && <FileTitleSection uri={uri} />}
|
||||||
{commentsDisabled && <Empty text={__('The creator of this content has disabled comments.')} />}
|
{commentsDisabled && <Empty text={__('The creator of this content has disabled comments.')} />}
|
||||||
{!commentsDisabled && <CommentsList uri={uri} linkedCommentId={linkedCommentId} />}
|
{!commentsDisabled && <CommentsList uri={uri} linkedCommentId={linkedCommentId} />}
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { makeSelectTagInClaimOrChannelForUri, makeSelectClaimForUri } from 'redux/selectors/claims';
|
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
|
||||||
import { doSetPlayingUri } from 'redux/actions/content';
|
|
||||||
import { doUserSetReferrer } from 'redux/actions/user';
|
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
|
||||||
import { selectHasUnclaimedRefereeReward } from 'redux/selectors/rewards';
|
|
||||||
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
|
|
||||||
import LivestreamPage from './view';
|
|
||||||
|
|
||||||
const select = (state, props) => ({
|
|
||||||
hasUnclaimedRefereeReward: selectHasUnclaimedRefereeReward(state),
|
|
||||||
isAuthenticated: selectUserVerifiedEmail(state),
|
|
||||||
channelClaim: makeSelectClaimForUri(props.uri)(state),
|
|
||||||
chatDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, {
|
|
||||||
doSetPlayingUri,
|
|
||||||
doResolveUri,
|
|
||||||
doUserSetReferrer,
|
|
||||||
})(LivestreamPage);
|
|
|
@ -1,124 +0,0 @@
|
||||||
// @flow
|
|
||||||
import { LIVESTREAM_LIVE_API } from 'constants/livestream';
|
|
||||||
import React from 'react';
|
|
||||||
import Page from 'component/page';
|
|
||||||
import LivestreamLayout from 'component/livestreamLayout';
|
|
||||||
import LivestreamComments from 'component/livestreamComments';
|
|
||||||
import analytics from 'analytics';
|
|
||||||
import Lbry from 'lbry';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
uri: string,
|
|
||||||
claim: StreamClaim,
|
|
||||||
doSetPlayingUri: ({ uri: ?string }) => void,
|
|
||||||
isAuthenticated: boolean,
|
|
||||||
doUserSetReferrer: (string) => void,
|
|
||||||
channelClaim: ChannelClaim,
|
|
||||||
chatDisabled: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function LivestreamPage(props: Props) {
|
|
||||||
const { uri, claim, doSetPlayingUri, isAuthenticated, doUserSetReferrer, channelClaim, chatDisabled } = props;
|
|
||||||
const [isLive, setIsLive] = React.useState(false);
|
|
||||||
const livestreamChannelId = channelClaim && channelClaim.signing_channel && channelClaim.signing_channel.claim_id;
|
|
||||||
const [hasLivestreamClaim, setHasLivestreamClaim] = React.useState(false);
|
|
||||||
|
|
||||||
const STREAMING_POLL_INTERVAL_IN_MS = 10000;
|
|
||||||
const LIVESTREAM_CLAIM_POLL_IN_MS = 60000;
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
let checkClaimsInterval;
|
|
||||||
function checkHasLivestreamClaim() {
|
|
||||||
Lbry.claim_search({
|
|
||||||
channel_ids: [livestreamChannelId],
|
|
||||||
has_no_source: true,
|
|
||||||
claim_type: ['stream'],
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (res && res.items && res.items.length > 0) {
|
|
||||||
setHasLivestreamClaim(true);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
if (livestreamChannelId && !isLive) {
|
|
||||||
if (!checkClaimsInterval) checkHasLivestreamClaim();
|
|
||||||
checkClaimsInterval = setInterval(checkHasLivestreamClaim, LIVESTREAM_CLAIM_POLL_IN_MS);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (checkClaimsInterval) {
|
|
||||||
clearInterval(checkClaimsInterval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [livestreamChannelId, isLive]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
let interval;
|
|
||||||
function checkIsLive() {
|
|
||||||
// TODO: duplicate code below
|
|
||||||
// $FlowFixMe livestream API can handle garbage
|
|
||||||
fetch(`${LIVESTREAM_LIVE_API}/${livestreamChannelId}`)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((res) => {
|
|
||||||
if (!res || !res.data) {
|
|
||||||
setIsLive(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.data.hasOwnProperty('live')) {
|
|
||||||
setIsLive(res.data.live);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (livestreamChannelId && hasLivestreamClaim) {
|
|
||||||
if (!interval) checkIsLive();
|
|
||||||
interval = setInterval(checkIsLive, STREAMING_POLL_INTERVAL_IN_MS);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (interval) {
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [livestreamChannelId, hasLivestreamClaim]);
|
|
||||||
|
|
||||||
const stringifiedClaim = JSON.stringify(claim);
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (uri && stringifiedClaim) {
|
|
||||||
const jsonClaim = JSON.parse(stringifiedClaim);
|
|
||||||
|
|
||||||
if (jsonClaim) {
|
|
||||||
const { txid, nout, claim_id: claimId } = jsonClaim;
|
|
||||||
const outpoint = `${txid}:${nout}`;
|
|
||||||
|
|
||||||
analytics.apiLogView(uri, outpoint, claimId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
|
||||||
const uri = jsonClaim.signing_channel && jsonClaim.signing_channel.permanent_url;
|
|
||||||
if (uri) {
|
|
||||||
doUserSetReferrer(uri.replace('lbry://', ''));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [uri, stringifiedClaim, isAuthenticated]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
// Set playing uri to null so the popout player doesnt start playing the dummy claim if a user navigates back
|
|
||||||
// This can be removed when we start using the app video player, not a LIVESTREAM iframe
|
|
||||||
doSetPlayingUri({ uri: null });
|
|
||||||
}, [doSetPlayingUri]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Page
|
|
||||||
className="file-page"
|
|
||||||
noFooter
|
|
||||||
livestream
|
|
||||||
chatDisabled={chatDisabled}
|
|
||||||
rightSide={!chatDisabled && <LivestreamComments uri={uri} />}
|
|
||||||
>
|
|
||||||
<LivestreamLayout uri={uri} isLive={isLive} />
|
|
||||||
</Page>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { selectUser } from 'redux/selectors/user';
|
|
||||||
import LivestreamCurrent from './view';
|
|
||||||
|
|
||||||
const select = (state) => ({
|
|
||||||
user: selectUser(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select)(LivestreamCurrent);
|
|
|
@ -1,34 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React from 'react';
|
|
||||||
import LivestreamList from 'component/livestreamList';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import Page from 'component/page';
|
|
||||||
import Yrbl from 'component/yrbl';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
user: ?User,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function LivestreamCurrentPage(props: Props) {
|
|
||||||
const { user } = props;
|
|
||||||
const canView = process.env.ENABLE_WIP_FEATURES || (user && user.global_mod);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Page>
|
|
||||||
{canView ? (
|
|
||||||
<LivestreamList />
|
|
||||||
) : (
|
|
||||||
<Yrbl
|
|
||||||
type="sad"
|
|
||||||
title={__("This page isn't quite ready")}
|
|
||||||
subtitle={__('Check back later.')}
|
|
||||||
actions={
|
|
||||||
<div className="section__actions">
|
|
||||||
<Button button="primary" navigate="/" label={__('Go Home')} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Page>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { selectMyChannelClaims, selectFetchingMyChannels } from 'redux/selectors/claims';
|
|
||||||
import { doClearPublish } from 'redux/actions/publish';
|
|
||||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
|
||||||
import { doFetchNoSourceClaims } from 'redux/actions/livestream';
|
|
||||||
import {
|
|
||||||
makeSelectPendingLivestreamsForChannelId,
|
|
||||||
makeSelectLivestreamsForChannelId,
|
|
||||||
makeSelectIsFetchingLivestreams,
|
|
||||||
} from 'redux/selectors/livestream';
|
|
||||||
import LivestreamSetupPage from './view';
|
|
||||||
import { push } from 'connected-react-router';
|
|
||||||
|
|
||||||
const select = (state) => {
|
|
||||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
|
||||||
const { claim_id: channelId, name: channelName } = activeChannelClaim || {};
|
|
||||||
return {
|
|
||||||
channelName,
|
|
||||||
channelId,
|
|
||||||
channels: selectMyChannelClaims(state),
|
|
||||||
fetchingChannels: selectFetchingMyChannels(state),
|
|
||||||
activeChannelClaim,
|
|
||||||
myLivestreamClaims: makeSelectLivestreamsForChannelId(channelId)(state),
|
|
||||||
pendingClaims: makeSelectPendingLivestreamsForChannelId(channelId)(state),
|
|
||||||
fetchingLivestreams: makeSelectIsFetchingLivestreams(channelId)(state),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const perform = (dispatch) => ({
|
|
||||||
doNewLivestream: (path) => {
|
|
||||||
dispatch(doClearPublish());
|
|
||||||
dispatch(push(path));
|
|
||||||
},
|
|
||||||
fetchNoSourceClaims: (id) => dispatch(doFetchNoSourceClaims(id)),
|
|
||||||
});
|
|
||||||
export default connect(select, perform)(LivestreamSetupPage);
|
|
|
@ -1,324 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as PAGES from 'constants/pages';
|
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
import * as PUBLISH_MODES from 'constants/publish_types';
|
|
||||||
import I18nMessage from 'component/i18nMessage';
|
|
||||||
import React from 'react';
|
|
||||||
import Page from 'component/page';
|
|
||||||
import Spinner from 'component/spinner';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import ChannelSelector from 'component/channelSelector';
|
|
||||||
import Yrbl from 'component/yrbl';
|
|
||||||
import Lbry from 'lbry';
|
|
||||||
import { toHex } from 'util/hex';
|
|
||||||
import { FormField } from 'component/common/form';
|
|
||||||
import CopyableText from 'component/copyableText';
|
|
||||||
import Card from 'component/common/card';
|
|
||||||
import ClaimList from 'component/claimList';
|
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
|
||||||
import { LIVESTREAM_RTMP_URL } from 'constants/livestream';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
channels: Array<ChannelClaim>,
|
|
||||||
fetchingChannels: boolean,
|
|
||||||
activeChannelClaim: ?ChannelClaim,
|
|
||||||
pendingClaims: Array<Claim>,
|
|
||||||
doNewLivestream: (string) => void,
|
|
||||||
fetchNoSourceClaims: (string) => void,
|
|
||||||
myLivestreamClaims: Array<Claim>,
|
|
||||||
fetchingLivestreams: boolean,
|
|
||||||
channelId: ?string,
|
|
||||||
channelName: ?string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function LivestreamSetupPage(props: Props) {
|
|
||||||
const LIVESTREAM_CLAIM_POLL_IN_MS = 60000;
|
|
||||||
const {
|
|
||||||
channels,
|
|
||||||
fetchingChannels,
|
|
||||||
activeChannelClaim,
|
|
||||||
pendingClaims,
|
|
||||||
doNewLivestream,
|
|
||||||
fetchNoSourceClaims,
|
|
||||||
myLivestreamClaims,
|
|
||||||
fetchingLivestreams,
|
|
||||||
channelId,
|
|
||||||
channelName,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [sigData, setSigData] = React.useState({ signature: undefined, signing_ts: undefined });
|
|
||||||
const [showHelp, setShowHelp] = usePersistedState('livestream-help-seen', true);
|
|
||||||
|
|
||||||
const hasChannels = channels && channels.length > 0;
|
|
||||||
const hasLivestreamClaims = Boolean(myLivestreamClaims.length || pendingClaims.length);
|
|
||||||
|
|
||||||
function createStreamKey() {
|
|
||||||
if (!channelId || !channelName || !sigData.signature || !sigData.signing_ts) return null;
|
|
||||||
return `${channelId}?d=${toHex(channelName)}&s=${sigData.signature}&t=${sigData.signing_ts}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const streamKey = createStreamKey();
|
|
||||||
|
|
||||||
const pendingLength = pendingClaims.length;
|
|
||||||
const totalLivestreamClaims = pendingClaims.concat(myLivestreamClaims);
|
|
||||||
const helpText = (
|
|
||||||
<div className="section__subtitle">
|
|
||||||
<p>
|
|
||||||
{__(
|
|
||||||
`Create a Livestream by first submitting your livestream details and waiting for approval confirmation. This can be done well in advance and will take a few minutes.`
|
|
||||||
)}{' '}
|
|
||||||
{__(
|
|
||||||
`The livestream will not be visible on your channel page until you are live, but you can share the URL in advance.`
|
|
||||||
)}{' '}
|
|
||||||
{__(
|
|
||||||
`Once the your livestream is confirmed, configure your streaming software (OBS, Restream, etc) and input the server URL along with the stream key in it.`
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p>{__(`To ensure the best streaming experience with OBS, open settings -> output`)}</p>
|
|
||||||
<p>{__(`Select advanced mode from the dropdown at the top.`)}</p>
|
|
||||||
<p>{__(`Ensure the following settings are selected under the streaming tab:`)}</p>
|
|
||||||
<ul>
|
|
||||||
<li>{__(`Bitrate: 1000 to 2500 kbps`)}</li>
|
|
||||||
<li>{__(`Keyframes: 1`)}</li>
|
|
||||||
<li>{__(`Profile: High`)}</li>
|
|
||||||
<li>{__(`Tune: Zerolatency`)}</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
{__(`If using other streaming software, make sure the bitrate is below 4500 kbps or the stream will not work.`)}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{__(
|
|
||||||
`After your stream:\nClick the Update button on the content page. This will allow you to select a replay or upload your own edited MP4. Replays are limited to 4 hours and may take a few minutes to show (use the Check For Replays button).`
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p>{__(`Click Save, then confirm, and you are done!`)}</p>
|
|
||||||
<p>
|
|
||||||
{__(
|
|
||||||
`Note: If you don't plan on publishing your replay, you'll want to delete your livestream and then start with a fresh one next time.`
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
// ensure we have a channel
|
|
||||||
if (channelId && channelName) {
|
|
||||||
Lbry.channel_sign({
|
|
||||||
channel_id: channelId,
|
|
||||||
hexdata: toHex(channelName),
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
setSigData(data);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setSigData({ signature: null, signing_ts: null });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [channelName, channelId, setSigData]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
let checkClaimsInterval;
|
|
||||||
if (!channelId) return;
|
|
||||||
|
|
||||||
if (!checkClaimsInterval) {
|
|
||||||
fetchNoSourceClaims(channelId);
|
|
||||||
checkClaimsInterval = setInterval(() => fetchNoSourceClaims(channelId), LIVESTREAM_CLAIM_POLL_IN_MS);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
if (checkClaimsInterval) {
|
|
||||||
clearInterval(checkClaimsInterval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [channelId, pendingLength, fetchNoSourceClaims]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Page>
|
|
||||||
{fetchingChannels && (
|
|
||||||
<div className="main--empty">
|
|
||||||
<Spinner delayed />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!fetchingChannels && !hasChannels && (
|
|
||||||
<Yrbl
|
|
||||||
type="happy"
|
|
||||||
title={__("You haven't created a channel yet, let's fix that!")}
|
|
||||||
actions={
|
|
||||||
<div className="section__actions">
|
|
||||||
<Button button="primary" navigate={`/$/${PAGES.CHANNEL_NEW}`} label={__('Create A Channel')} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!fetchingChannels && (
|
|
||||||
<div className="section__actions--between">
|
|
||||||
<ChannelSelector hideAnon />
|
|
||||||
<Button button="link" onClick={() => setShowHelp(!showHelp)} label={__('How does this work?')} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{fetchingLivestreams && !fetchingChannels && !hasLivestreamClaims && (
|
|
||||||
<div className="main--empty">
|
|
||||||
<Spinner delayed />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="card-stack">
|
|
||||||
{!fetchingChannels && channelId && (
|
|
||||||
<>
|
|
||||||
{showHelp && (
|
|
||||||
<Card
|
|
||||||
titleActions={<Button button="close" icon={ICONS.REMOVE} onClick={() => setShowHelp(false)} />}
|
|
||||||
title={__('Go Live on Odysee')}
|
|
||||||
subtitle={__(`You're invited to try out our new livestreaming service while in beta!`)}
|
|
||||||
actions={helpText}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{streamKey && totalLivestreamClaims.length > 0 && (
|
|
||||||
<Card
|
|
||||||
className="section"
|
|
||||||
title={__('Your stream key')}
|
|
||||||
actions={
|
|
||||||
<>
|
|
||||||
<CopyableText
|
|
||||||
primaryButton
|
|
||||||
name="stream-server"
|
|
||||||
label={__('Stream server')}
|
|
||||||
copyable={LIVESTREAM_RTMP_URL}
|
|
||||||
snackMessage={__('Copied stream server URL.')}
|
|
||||||
/>
|
|
||||||
<CopyableText
|
|
||||||
primaryButton
|
|
||||||
enableInputMask
|
|
||||||
name="livestream-key"
|
|
||||||
label={__('Stream key')}
|
|
||||||
copyable={streamKey}
|
|
||||||
snackMessage={__('Copied stream key.')}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totalLivestreamClaims.length > 0 ? (
|
|
||||||
<>
|
|
||||||
{Boolean(pendingClaims.length) && (
|
|
||||||
<div className="section">
|
|
||||||
<ClaimList
|
|
||||||
header={__('Your pending livestream uploads')}
|
|
||||||
uris={pendingClaims.map((claim) => claim.permanent_url)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{Boolean(myLivestreamClaims.length) && (
|
|
||||||
<div className="section">
|
|
||||||
<ClaimList
|
|
||||||
header={__('Your livestream uploads')}
|
|
||||||
empty={
|
|
||||||
<I18nMessage
|
|
||||||
tokens={{
|
|
||||||
check_again: (
|
|
||||||
<Button
|
|
||||||
button="link"
|
|
||||||
onClick={() => fetchNoSourceClaims(channelId)}
|
|
||||||
label={__('Check again')}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Nothing here yet. %check_again%
|
|
||||||
</I18nMessage>
|
|
||||||
}
|
|
||||||
uris={myLivestreamClaims
|
|
||||||
.filter(
|
|
||||||
(claim) => !pendingClaims.some((pending) => pending.permanent_url === claim.permanent_url)
|
|
||||||
)
|
|
||||||
.map((claim) => claim.permanent_url)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Yrbl
|
|
||||||
className="livestream__publish-intro"
|
|
||||||
title={__('No livestream publishes found')}
|
|
||||||
subtitle={__(
|
|
||||||
'You need to upload your livestream details before you can go live. If you already created one in this channel, it should appear soon.'
|
|
||||||
)}
|
|
||||||
actions={
|
|
||||||
<div className="section__actions">
|
|
||||||
<Button
|
|
||||||
button="primary"
|
|
||||||
onClick={() =>
|
|
||||||
doNewLivestream(`/$/${PAGES.UPLOAD}?type=${PUBLISH_MODES.LIVESTREAM.toLowerCase()}`)
|
|
||||||
}
|
|
||||||
label={__('Create A Livestream')}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
button="alt"
|
|
||||||
onClick={() => {
|
|
||||||
fetchNoSourceClaims(channelId);
|
|
||||||
}}
|
|
||||||
label={__('Check again...')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Debug Stuff */}
|
|
||||||
{streamKey && false && activeChannelClaim && (
|
|
||||||
<div style={{ marginTop: 'var(--spacing-l)' }}>
|
|
||||||
<h3>Debug Info</h3>
|
|
||||||
|
|
||||||
{/* Channel ID */}
|
|
||||||
<FormField
|
|
||||||
name={'channelId'}
|
|
||||||
label={'Channel ID'}
|
|
||||||
type={'text'}
|
|
||||||
defaultValue={activeChannelClaim.claim_id}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Signature */}
|
|
||||||
<FormField
|
|
||||||
name={'signature'}
|
|
||||||
label={'Signature'}
|
|
||||||
type={'text'}
|
|
||||||
defaultValue={sigData.signature}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Signature TS */}
|
|
||||||
<FormField
|
|
||||||
name={'signaturets'}
|
|
||||||
label={'Signature Timestamp'}
|
|
||||||
type={'text'}
|
|
||||||
defaultValue={sigData.signing_ts}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Hex Data */}
|
|
||||||
<FormField
|
|
||||||
name={'datahex'}
|
|
||||||
label={'Hex Data'}
|
|
||||||
type={'text'}
|
|
||||||
defaultValue={toHex(activeChannelClaim.name)}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Channel Public Key */}
|
|
||||||
<FormField
|
|
||||||
name={'channelpublickey'}
|
|
||||||
label={'Public Key'}
|
|
||||||
type={'text'}
|
|
||||||
defaultValue={activeChannelClaim.value.public_key}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Page>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -57,7 +57,6 @@ export default function OwnComments(props: Props) {
|
||||||
return comments.map((comment) => {
|
return comments.map((comment) => {
|
||||||
const contentClaim = claimsById[comment.claim_id];
|
const contentClaim = claimsById[comment.claim_id];
|
||||||
const isChannel = contentClaim && contentClaim.value_type === 'channel';
|
const isChannel = contentClaim && contentClaim.value_type === 'channel';
|
||||||
const isLivestream = Boolean(contentClaim && contentClaim.value_type === 'stream' && !contentClaim.value.source);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={comment.comment_id} className="comments-own card__main-actions">
|
<div key={comment.comment_id} className="comments-own card__main-actions">
|
||||||
|
@ -68,7 +67,6 @@ export default function OwnComments(props: Props) {
|
||||||
uri={contentClaim.canonical_url}
|
uri={contentClaim.canonical_url}
|
||||||
searchParams={{
|
searchParams={{
|
||||||
...(isChannel ? { view: 'discussion' } : {}),
|
...(isChannel ? { view: 'discussion' } : {}),
|
||||||
...(isLivestream ? {} : { lc: comment.comment_id }),
|
|
||||||
}}
|
}}
|
||||||
hideActions
|
hideActions
|
||||||
hideMenu
|
hideMenu
|
||||||
|
|
|
@ -385,11 +385,11 @@ export default function SettingsCreatorPage(props: Props) {
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const HELP = {
|
const HELP = {
|
||||||
SLOW_MODE: 'Minimum time gap in seconds between comments (affects livestream chat as well).',
|
SLOW_MODE: 'Minimum time gap in seconds between comments.',
|
||||||
MIN_TIP: 'Enabling a minimum amount to comment will force all comments, including livestreams, to have tips associated with them. This can help prevent spam.',
|
MIN_TIP: 'Enabling a minimum amount to comment will force all comments to have tips associated with them. This can help prevent spam.',
|
||||||
MIN_SUPER: 'Enabling a minimum amount to hyperchat will force all TIPPED comments to have this value in order to be shown. This still allows regular comments to be posted.',
|
MIN_SUPER: 'Enabling a minimum amount to hyperchat will force all TIPPED comments to have this value in order to be shown. This still allows regular comments to be posted.',
|
||||||
MIN_SUPER_OFF: '(This settings is not applicable if all comments require a tip.)',
|
MIN_SUPER_OFF: '(This settings is not applicable if all comments require a tip.)',
|
||||||
BLOCKED_WORDS: 'Comments and livestream chat containing these words will be blocked.',
|
BLOCKED_WORDS: 'Comments containing these words will be blocked.',
|
||||||
MODERATORS: 'Moderators can block channels on your behalf. Blocked channels will appear in your "Blocked and Muted" list.',
|
MODERATORS: 'Moderators can block channels on your behalf. Blocked channels will appear in your "Blocked and Muted" list.',
|
||||||
MODERATOR_SEARCH: 'Enter a channel name or URL to add as a moderator.\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8',
|
MODERATOR_SEARCH: 'Enter a channel name or URL to add as a moderator.\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8',
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
makeSelectTitleForUri,
|
makeSelectTitleForUri,
|
||||||
makeSelectClaimIsMine,
|
makeSelectClaimIsMine,
|
||||||
makeSelectClaimIsPending,
|
makeSelectClaimIsPending,
|
||||||
makeSelectClaimIsStreamPlaceholder,
|
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
import {
|
import {
|
||||||
makeSelectCollectionForId,
|
makeSelectCollectionForId,
|
||||||
|
@ -79,7 +78,6 @@ const select = (state, props) => {
|
||||||
title: makeSelectTitleForUri(uri)(state),
|
title: makeSelectTitleForUri(uri)(state),
|
||||||
claimIsMine: makeSelectClaimIsMine(uri)(state),
|
claimIsMine: makeSelectClaimIsMine(uri)(state),
|
||||||
claimIsPending: makeSelectClaimIsPending(uri)(state),
|
claimIsPending: makeSelectClaimIsPending(uri)(state),
|
||||||
isLivestream: makeSelectClaimIsStreamPlaceholder(uri)(state),
|
|
||||||
collection: makeSelectCollectionForId(collectionId)(state),
|
collection: makeSelectCollectionForId(collectionId)(state),
|
||||||
collectionId: collectionId,
|
collectionId: collectionId,
|
||||||
collectionUrls: makeSelectUrlsForCollectionId(collectionId)(state),
|
collectionUrls: makeSelectUrlsForCollectionId(collectionId)(state),
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { ENABLE_NO_SOURCE_CLAIMS } from 'config';
|
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Redirect, useHistory } from 'react-router-dom';
|
import { Redirect, useHistory } from 'react-router-dom';
|
||||||
|
@ -14,7 +13,6 @@ import * as COLLECTIONS_CONSTS from 'constants/collections';
|
||||||
|
|
||||||
import AbandonedChannelPreview from 'component/abandonedChannelPreview';
|
import AbandonedChannelPreview from 'component/abandonedChannelPreview';
|
||||||
import FilePage from 'page/file';
|
import FilePage from 'page/file';
|
||||||
import LivestreamPage from 'page/livestream';
|
|
||||||
import Yrbl from 'component/yrbl';
|
import Yrbl from 'component/yrbl';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -31,7 +29,6 @@ type Props = {
|
||||||
title: string,
|
title: string,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
claimIsPending: boolean,
|
claimIsPending: boolean,
|
||||||
isLivestream: boolean,
|
|
||||||
beginPublish: (?string) => void,
|
beginPublish: (?string) => void,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
|
@ -51,7 +48,6 @@ function ShowPage(props: Props) {
|
||||||
claimIsMine,
|
claimIsMine,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
claimIsPending,
|
claimIsPending,
|
||||||
isLivestream,
|
|
||||||
beginPublish,
|
beginPublish,
|
||||||
fetchCollectionItems,
|
fetchCollectionItems,
|
||||||
collectionId,
|
collectionId,
|
||||||
|
@ -175,8 +171,6 @@ function ShowPage(props: Props) {
|
||||||
/>
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
} else if (isLivestream && ENABLE_NO_SOURCE_CLAIMS) {
|
|
||||||
innerContent = <LivestreamPage uri={uri} />;
|
|
||||||
} else {
|
} else {
|
||||||
innerContent = <FilePage uri={uri} location={location} />;
|
innerContent = <FilePage uri={uri} location={location} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import userReducer from 'redux/reducers/user';
|
||||||
import commentsReducer from 'redux/reducers/comments';
|
import commentsReducer from 'redux/reducers/comments';
|
||||||
import blockedReducer from 'redux/reducers/blocked';
|
import blockedReducer from 'redux/reducers/blocked';
|
||||||
import coinSwapReducer from 'redux/reducers/coinSwap';
|
import coinSwapReducer from 'redux/reducers/coinSwap';
|
||||||
import livestreamReducer from 'redux/reducers/livestream';
|
|
||||||
import searchReducer from 'redux/reducers/search';
|
import searchReducer from 'redux/reducers/search';
|
||||||
import reactionsReducer from 'redux/reducers/reactions';
|
import reactionsReducer from 'redux/reducers/reactions';
|
||||||
import syncReducer from 'redux/reducers/sync';
|
import syncReducer from 'redux/reducers/sync';
|
||||||
|
@ -34,7 +33,6 @@ export default (history) =>
|
||||||
content: contentReducer,
|
content: contentReducer,
|
||||||
costInfo: costInfoReducer,
|
costInfo: costInfoReducer,
|
||||||
fileInfo: fileInfoReducer,
|
fileInfo: fileInfoReducer,
|
||||||
livestream: livestreamReducer,
|
|
||||||
notifications: notificationsReducer,
|
notifications: notificationsReducer,
|
||||||
publish: publishReducer,
|
publish: publishReducer,
|
||||||
reactions: reactionsReducer,
|
reactions: reactionsReducer,
|
||||||
|
|
|
@ -552,7 +552,6 @@ export function doCommentReact(commentId: string, type: string) {
|
||||||
* @param claim_id - File claim id
|
* @param claim_id - File claim id
|
||||||
* @param parent_id - What is this?
|
* @param parent_id - What is this?
|
||||||
* @param uri
|
* @param uri
|
||||||
* @param livestream
|
|
||||||
* @param {string} [txid] Optional transaction id
|
* @param {string} [txid] Optional transaction id
|
||||||
* @param {string} [payment_intent_id] Optional transaction id
|
* @param {string} [payment_intent_id] Optional transaction id
|
||||||
* @param {string} [environment] Optional environment for Stripe (test|live)
|
* @param {string} [environment] Optional environment for Stripe (test|live)
|
||||||
|
@ -563,7 +562,6 @@ export function doCommentCreate(
|
||||||
claim_id: string = '',
|
claim_id: string = '',
|
||||||
parent_id?: string,
|
parent_id?: string,
|
||||||
uri: string,
|
uri: string,
|
||||||
livestream?: boolean = false,
|
|
||||||
txid?: string,
|
txid?: string,
|
||||||
payment_intent_id?: string,
|
payment_intent_id?: string,
|
||||||
environment?: string
|
environment?: string
|
||||||
|
@ -624,7 +622,6 @@ export function doCommentCreate(
|
||||||
type: ACTIONS.COMMENT_CREATE_COMPLETED,
|
type: ACTIONS.COMMENT_CREATE_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
uri,
|
uri,
|
||||||
livestream,
|
|
||||||
comment: result,
|
comment: result,
|
||||||
claimId: claim_id,
|
claimId: claim_id,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as ACTIONS from 'constants/action_types';
|
|
||||||
import { doClaimSearch } from 'redux/actions/claims';
|
|
||||||
import { LIVESTREAM_LIVE_API } from 'constants/livestream';
|
|
||||||
|
|
||||||
export const doFetchNoSourceClaims = (channelId: string) => async (dispatch: Dispatch, getState: GetState) => {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.FETCH_NO_SOURCE_CLAIMS_STARTED,
|
|
||||||
data: channelId,
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await dispatch(
|
|
||||||
doClaimSearch({
|
|
||||||
channel_ids: [channelId],
|
|
||||||
has_no_source: true,
|
|
||||||
claim_type: ['stream'],
|
|
||||||
no_totals: true,
|
|
||||||
page_size: 20,
|
|
||||||
page: 1,
|
|
||||||
include_is_my_output: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.FETCH_NO_SOURCE_CLAIMS_COMPLETED,
|
|
||||||
data: channelId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.FETCH_NO_SOURCE_CLAIMS_FAILED,
|
|
||||||
data: channelId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const FETCH_ACTIVE_LIVESTREAMS_MIN_INTERVAL_MS = 5 * 60 * 1000;
|
|
||||||
|
|
||||||
export const doFetchActiveLivestreams = (
|
|
||||||
orderBy: Array<string> = ['release_time'],
|
|
||||||
pageSize: number = 50,
|
|
||||||
forceFetch: boolean = false
|
|
||||||
) => {
|
|
||||||
return async (dispatch: Dispatch, getState: GetState) => {
|
|
||||||
const state = getState();
|
|
||||||
const now = Date.now();
|
|
||||||
const timeDelta = now - state.livestream.activeLivestreamsLastFetchedDate;
|
|
||||||
|
|
||||||
const prevOptions = state.livestream.activeLivestreamsLastFetchedOptions;
|
|
||||||
const nextOptions = { page_size: pageSize, order_by: orderBy };
|
|
||||||
const sameOptions = JSON.stringify(prevOptions) === JSON.stringify(nextOptions);
|
|
||||||
|
|
||||||
if (!forceFetch && sameOptions && timeDelta < FETCH_ACTIVE_LIVESTREAMS_MIN_INTERVAL_MS) {
|
|
||||||
dispatch({ type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_SKIPPED });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch({ type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_STARTED });
|
|
||||||
|
|
||||||
fetch(LIVESTREAM_LIVE_API)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((res) => {
|
|
||||||
if (!res.data) {
|
|
||||||
dispatch({ type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_FAILED });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeLivestreams: LivestreamInfo = res.data.reduce((acc, curr) => {
|
|
||||||
acc[curr.claimId] = {
|
|
||||||
live: curr.live,
|
|
||||||
viewCount: curr.viewCount,
|
|
||||||
creatorId: curr.claimId,
|
|
||||||
};
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
// ** Creators can have multiple livestream claims (each with unique
|
|
||||||
// chat), and all of them will play the same stream when creator goes
|
|
||||||
// live. The UI usually just wants to report the latest claim, so we
|
|
||||||
// query that store it in `latestClaimUri`.
|
|
||||||
doClaimSearch({
|
|
||||||
page: 1,
|
|
||||||
page_size: nextOptions.page_size,
|
|
||||||
has_no_source: true,
|
|
||||||
channel_ids: Object.keys(activeLivestreams),
|
|
||||||
claim_type: ['stream'],
|
|
||||||
order_by: nextOptions.order_by, // **
|
|
||||||
limit_claims_per_channel: 1, // **
|
|
||||||
no_totals: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then((resolveInfo) => {
|
|
||||||
Object.values(resolveInfo).forEach((x) => {
|
|
||||||
// $FlowFixMe
|
|
||||||
const channelId = x.stream.signing_channel.claim_id;
|
|
||||||
activeLivestreams[channelId] = {
|
|
||||||
...activeLivestreams[channelId],
|
|
||||||
// $FlowFixMe
|
|
||||||
latestClaimId: x.stream.claim_id,
|
|
||||||
// $FlowFixMe
|
|
||||||
latestClaimUri: x.stream.canonical_url,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_COMPLETED,
|
|
||||||
data: {
|
|
||||||
activeLivestreams,
|
|
||||||
activeLivestreamsLastFetchedDate: now,
|
|
||||||
activeLivestreamsLastFetchedOptions: nextOptions,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
dispatch({ type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_FAILED });
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
dispatch({ type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_FAILED });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -4,14 +4,8 @@ import * as ACTIONS from 'constants/action_types';
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import { batchActions } from 'util/batch-actions';
|
import { batchActions } from 'util/batch-actions';
|
||||||
import { doCheckPendingClaims } from 'redux/actions/claims';
|
import { doCheckPendingClaims } from 'redux/actions/claims';
|
||||||
import {
|
import { selectMyClaims, selectMyChannelClaims, selectReflectingById } from 'redux/selectors/claims';
|
||||||
makeSelectClaimForUri,
|
import { selectPublishFormValues, selectMyClaimForUri } from 'redux/selectors/publish';
|
||||||
selectMyClaims,
|
|
||||||
selectMyChannelClaims,
|
|
||||||
// selectMyClaimsWithoutChannels,
|
|
||||||
selectReflectingById,
|
|
||||||
} from 'redux/selectors/claims';
|
|
||||||
import { makeSelectPublishFormValue, selectPublishFormValues, selectMyClaimForUri } from 'redux/selectors/publish';
|
|
||||||
import { doError } from 'redux/actions/notifications';
|
import { doError } from 'redux/actions/notifications';
|
||||||
import { push } from 'connected-react-router';
|
import { push } from 'connected-react-router';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
|
@ -21,7 +15,6 @@ import { SPEECH_STATUS, SPEECH_PUBLISH } from 'constants/speech_urls';
|
||||||
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
||||||
import { creditsToString } from 'util/format-credits';
|
import { creditsToString } from 'util/format-credits';
|
||||||
import Lbry from 'lbry';
|
import Lbry from 'lbry';
|
||||||
// import LbryFirst from 'extras/lbry-first/lbry-first';
|
|
||||||
import { isClaimNsfw } from 'util/claim';
|
import { isClaimNsfw } from 'util/claim';
|
||||||
|
|
||||||
export const NO_FILE = '---';
|
export const NO_FILE = '---';
|
||||||
|
@ -34,14 +27,6 @@ export const doPublishDesktop = (filePath: string, preview?: boolean) => (dispat
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const noFileParam = !filePath || filePath === NO_FILE;
|
|
||||||
const state = getState();
|
|
||||||
const editingUri = makeSelectPublishFormValue('editingURI')(state) || '';
|
|
||||||
const remoteUrl = makeSelectPublishFormValue('remoteFileUrl')(state);
|
|
||||||
const claim = makeSelectClaimForUri(editingUri)(state) || {};
|
|
||||||
const hasSourceFile = claim.value && claim.value.source;
|
|
||||||
const redirectToLivestream = noFileParam && !hasSourceFile && !remoteUrl;
|
|
||||||
|
|
||||||
const publishSuccess = (successResponse, lbryFirstError) => {
|
const publishSuccess = (successResponse, lbryFirstError) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const myClaims = selectMyClaims(state);
|
const myClaims = selectMyClaims(state);
|
||||||
|
@ -87,14 +72,7 @@ export const doPublishDesktop = (filePath: string, preview?: boolean) => (dispat
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
dispatch(doCheckPendingClaims());
|
dispatch(doCheckPendingClaims());
|
||||||
// @if TARGET='app'
|
|
||||||
dispatch(doCheckReflectingFiles());
|
dispatch(doCheckReflectingFiles());
|
||||||
// @endif
|
|
||||||
// @if TARGET='web'
|
|
||||||
if (redirectToLivestream) {
|
|
||||||
dispatch(push(`/$/${PAGES.LIVESTREAM}`));
|
|
||||||
}
|
|
||||||
// @endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const publishFail = (error) => {
|
const publishFail = (error) => {
|
||||||
|
@ -111,15 +89,6 @@ export const doPublishDesktop = (filePath: string, preview?: boolean) => (dispat
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect on web immediately because we have a file upload progress componenet
|
|
||||||
// on the publishes page. This doesn't exist on desktop so wait until we get a response
|
|
||||||
// from the SDK
|
|
||||||
// @if TARGET='web'
|
|
||||||
if (!redirectToLivestream) {
|
|
||||||
dispatch(push(`/$/${PAGES.UPLOADS}`));
|
|
||||||
}
|
|
||||||
// @endif
|
|
||||||
|
|
||||||
dispatch(doPublish(publishSuccess, publishFail));
|
dispatch(doPublish(publishSuccess, publishFail));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -381,12 +350,9 @@ export const doPublish = (success: Function, fail: Function, preview: Function)
|
||||||
title,
|
title,
|
||||||
contentIsFree,
|
contentIsFree,
|
||||||
fee,
|
fee,
|
||||||
// uri,
|
|
||||||
tags,
|
tags,
|
||||||
// locations,
|
// locations,
|
||||||
optimize,
|
optimize,
|
||||||
isLivestreamPublish,
|
|
||||||
remoteFileUrl,
|
|
||||||
} = publishData;
|
} = publishData;
|
||||||
|
|
||||||
// Handle scenario where we have a claim that has the same name as a channel we are publishing with.
|
// Handle scenario where we have a claim that has the same name as a channel we are publishing with.
|
||||||
|
@ -439,10 +405,6 @@ export const doPublish = (success: Function, fail: Function, preview: Function)
|
||||||
};
|
};
|
||||||
// Temporary solution to keep the same publish flow with the new tags api
|
// Temporary solution to keep the same publish flow with the new tags api
|
||||||
// Eventually we will allow users to enter their own tags on publish
|
// Eventually we will allow users to enter their own tags on publish
|
||||||
// `nsfw` will probably be removed
|
|
||||||
if (remoteFileUrl) {
|
|
||||||
publishPayload.remote_url = remoteFileUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (publishingLicense) {
|
if (publishingLicense) {
|
||||||
publishPayload.license = publishingLicense;
|
publishPayload.license = publishingLicense;
|
||||||
|
@ -490,7 +452,7 @@ export const doPublish = (success: Function, fail: Function, preview: Function)
|
||||||
|
|
||||||
// Only pass file on new uploads, not metadata only edits.
|
// Only pass file on new uploads, not metadata only edits.
|
||||||
// The sdk will figure it out
|
// The sdk will figure it out
|
||||||
if (filePath && !isLivestreamPublish) publishPayload.file_path = filePath;
|
if (filePath) publishPayload.file_path = filePath;
|
||||||
|
|
||||||
if (preview) {
|
if (preview) {
|
||||||
publishPayload.preview = true;
|
publishPayload.preview = true;
|
||||||
|
@ -502,22 +464,7 @@ export const doPublish = (success: Function, fail: Function, preview: Function)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Lbry.publish(publishPayload).then((response: PublishResponse) => {
|
return Lbry.publish(publishPayload).then((response: PublishResponse) => {
|
||||||
// TODO: Restore LbryFirst
|
|
||||||
// if (!useLBRYUploader) {
|
|
||||||
return success(response);
|
return success(response);
|
||||||
// }
|
|
||||||
|
|
||||||
// $FlowFixMe
|
|
||||||
// publishPayload.permanent_url = response.outputs[0].permanent_url;
|
|
||||||
//
|
|
||||||
// return LbryFirst.upload(publishPayload)
|
|
||||||
// .then(() => {
|
|
||||||
// // Return original publish response so app treats it like a normal publish
|
|
||||||
// return success(response);
|
|
||||||
// })
|
|
||||||
// .catch((error) => {
|
|
||||||
// return success(response, error);
|
|
||||||
// });
|
|
||||||
}, fail);
|
}, fail);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -103,13 +103,6 @@ export const doCommentSocketConnect = (uri, claimId) => (dispatch) => {
|
||||||
data: { comment: newComment, claimId, uri },
|
data: { comment: newComment, claimId, uri },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (response.type === 'viewers') {
|
|
||||||
const connected = response.data.connected;
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.VIEWERS_RECEIVED,
|
|
||||||
data: { connected, claimId },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (response.type === 'pinned') {
|
if (response.type === 'pinned') {
|
||||||
const pinnedComment = response.data.comment;
|
const pinnedComment = response.data.comment;
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
|
@ -71,12 +71,7 @@ export default handleActions(
|
||||||
}),
|
}),
|
||||||
|
|
||||||
[ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
|
[ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
|
||||||
const {
|
const { comment, claimId, uri }: { comment: Comment, claimId: string, uri: string } = action.data;
|
||||||
comment,
|
|
||||||
claimId,
|
|
||||||
uri,
|
|
||||||
livestream,
|
|
||||||
}: { comment: Comment, claimId: string, uri: string, livestream: boolean } = action.data;
|
|
||||||
|
|
||||||
const commentById = Object.assign({}, state.commentById);
|
const commentById = Object.assign({}, state.commentById);
|
||||||
const byId = Object.assign({}, state.byId);
|
const byId = Object.assign({}, state.byId);
|
||||||
|
@ -87,8 +82,6 @@ export default handleActions(
|
||||||
const comments = byId[claimId] || [];
|
const comments = byId[claimId] || [];
|
||||||
const newCommentIds = comments.slice();
|
const newCommentIds = comments.slice();
|
||||||
|
|
||||||
// If it was created during a livestream, let the websocket handler perform the state update
|
|
||||||
if (!livestream) {
|
|
||||||
// add the comment by its ID
|
// add the comment by its ID
|
||||||
commentById[comment.comment_id] = comment;
|
commentById[comment.comment_id] = comment;
|
||||||
|
|
||||||
|
@ -119,7 +112,6 @@ export default handleActions(
|
||||||
topLevelCommentsById[claimId].unshift(comment.comment_id);
|
topLevelCommentsById[claimId].unshift(comment.comment_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as ACTIONS from 'constants/action_types';
|
|
||||||
import { handleActions } from 'util/redux-utils';
|
|
||||||
|
|
||||||
const defaultState: LivestreamState = {
|
|
||||||
fetchingById: {},
|
|
||||||
viewersById: {},
|
|
||||||
fetchingActiveLivestreams: false,
|
|
||||||
activeLivestreams: null,
|
|
||||||
activeLivestreamsLastFetchedDate: 0,
|
|
||||||
activeLivestreamsLastFetchedOptions: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default handleActions(
|
|
||||||
{
|
|
||||||
[ACTIONS.FETCH_NO_SOURCE_CLAIMS_STARTED]: (state: LivestreamState, action: any): LivestreamState => {
|
|
||||||
const claimId = action.data;
|
|
||||||
const newIdsFetching = Object.assign({}, state.fetchingById);
|
|
||||||
newIdsFetching[claimId] = true;
|
|
||||||
|
|
||||||
return { ...state, fetchingById: newIdsFetching };
|
|
||||||
},
|
|
||||||
[ACTIONS.FETCH_NO_SOURCE_CLAIMS_COMPLETED]: (state: LivestreamState, action: any): LivestreamState => {
|
|
||||||
const claimId = action.data;
|
|
||||||
const newIdsFetching = Object.assign({}, state.fetchingById);
|
|
||||||
newIdsFetching[claimId] = false;
|
|
||||||
|
|
||||||
return { ...state, fetchingById: newIdsFetching };
|
|
||||||
},
|
|
||||||
[ACTIONS.FETCH_NO_SOURCE_CLAIMS_FAILED]: (state: LivestreamState, action: any) => {
|
|
||||||
const claimId = action.data;
|
|
||||||
const newIdsFetching = Object.assign({}, state.fetchingById);
|
|
||||||
newIdsFetching[claimId] = false;
|
|
||||||
|
|
||||||
return { ...state, fetchingById: newIdsFetching };
|
|
||||||
},
|
|
||||||
[ACTIONS.VIEWERS_RECEIVED]: (state: LivestreamState, action: any) => {
|
|
||||||
const { connected, claimId } = action.data;
|
|
||||||
const newViewersById = Object.assign({}, state.viewersById);
|
|
||||||
newViewersById[claimId] = connected;
|
|
||||||
return { ...state, viewersById: newViewersById };
|
|
||||||
},
|
|
||||||
[ACTIONS.FETCH_ACTIVE_LIVESTREAMS_STARTED]: (state: LivestreamState) => {
|
|
||||||
return { ...state, fetchingActiveLivestreams: true };
|
|
||||||
},
|
|
||||||
[ACTIONS.FETCH_ACTIVE_LIVESTREAMS_FAILED]: (state: LivestreamState) => {
|
|
||||||
return { ...state, fetchingActiveLivestreams: false };
|
|
||||||
},
|
|
||||||
[ACTIONS.FETCH_ACTIVE_LIVESTREAMS_COMPLETED]: (state: LivestreamState, action: any) => {
|
|
||||||
const { activeLivestreams, activeLivestreamsLastFetchedDate, activeLivestreamsLastFetchedOptions } = action.data;
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
fetchingActiveLivestreams: false,
|
|
||||||
activeLivestreams,
|
|
||||||
activeLivestreamsLastFetchedDate,
|
|
||||||
activeLivestreamsLastFetchedOptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultState
|
|
||||||
);
|
|
|
@ -1,62 +0,0 @@
|
||||||
// @flow
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { selectMyClaims, selectPendingClaims } from 'redux/selectors/claims';
|
|
||||||
|
|
||||||
const selectState = (state) => state.livestream || {};
|
|
||||||
|
|
||||||
// select non-pending claims without sources for given channel
|
|
||||||
export const makeSelectLivestreamsForChannelId = (channelId: string) =>
|
|
||||||
createSelector(selectState, selectMyClaims, (livestreamState, myClaims = []) => {
|
|
||||||
return myClaims
|
|
||||||
.filter(
|
|
||||||
(claim) =>
|
|
||||||
claim.value_type === 'stream' &&
|
|
||||||
claim.value &&
|
|
||||||
!claim.value.source &&
|
|
||||||
claim.confirmations > 0 &&
|
|
||||||
claim.signing_channel &&
|
|
||||||
claim.signing_channel.claim_id === channelId
|
|
||||||
)
|
|
||||||
.sort((a, b) => b.timestamp - a.timestamp); // newest first
|
|
||||||
});
|
|
||||||
|
|
||||||
export const selectFetchingLivestreams = createSelector(selectState, (state) => state.fetchingById);
|
|
||||||
export const selectViewersById = createSelector(selectState, (state) => state.viewersById);
|
|
||||||
|
|
||||||
export const makeSelectIsFetchingLivestreams = (channelId: string) =>
|
|
||||||
createSelector(selectFetchingLivestreams, (fetchingLivestreams) => Boolean(fetchingLivestreams[channelId]));
|
|
||||||
|
|
||||||
export const makeSelectViewersForId = (channelId: string) =>
|
|
||||||
createSelector(selectViewersById, (viewers) => viewers[channelId]);
|
|
||||||
|
|
||||||
export const makeSelectPendingLivestreamsForChannelId = (channelId: string) =>
|
|
||||||
createSelector(selectPendingClaims, (pendingClaims) => {
|
|
||||||
return pendingClaims.filter(
|
|
||||||
(claim) =>
|
|
||||||
claim.value_type === 'stream' &&
|
|
||||||
claim.value &&
|
|
||||||
!claim.value.source &&
|
|
||||||
claim.signing_channel &&
|
|
||||||
claim.signing_channel.claim_id === channelId
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const selectActiveLivestreams = createSelector(selectState, (state) => state.activeLivestreams);
|
|
||||||
|
|
||||||
export const makeSelectIsActiveLivestream = (uri: string) =>
|
|
||||||
createSelector(selectState, (state) => {
|
|
||||||
const activeLivestreamValues = (state.activeLivestreams && Object.values(state.activeLivestreams)) || [];
|
|
||||||
// $FlowFixMe
|
|
||||||
return Boolean(activeLivestreamValues.find((v) => v.latestClaimUri === uri));
|
|
||||||
});
|
|
||||||
|
|
||||||
export const makeSelectActiveLivestreamUris = (uri: string) =>
|
|
||||||
createSelector(selectState, (state) => {
|
|
||||||
const activeLivestreamValues = (state.activeLivestreams && Object.values(state.activeLivestreams)) || [];
|
|
||||||
const uris = [];
|
|
||||||
activeLivestreamValues.forEach((v) => {
|
|
||||||
// $FlowFixMe
|
|
||||||
if (v.latestClaimUri) uris.push(v.latestClaimUri);
|
|
||||||
});
|
|
||||||
return uris;
|
|
||||||
});
|
|
|
@ -57,7 +57,6 @@
|
||||||
@import 'component/superchat';
|
@import 'component/superchat';
|
||||||
@import 'component/syntax-highlighter';
|
@import 'component/syntax-highlighter';
|
||||||
@import 'component/table';
|
@import 'component/table';
|
||||||
@import 'component/livestream';
|
|
||||||
@import 'component/tabs';
|
@import 'component/tabs';
|
||||||
@import 'component/tooltip';
|
@import 'component/tooltip';
|
||||||
@import 'component/txo-list';
|
@import 'component/txo-list';
|
||||||
|
|
|
@ -1,469 +0,0 @@
|
||||||
$discussion-header__height: 3rem;
|
|
||||||
$recent-msg-button__height: 2rem;
|
|
||||||
|
|
||||||
.livestream {
|
|
||||||
flex: 1;
|
|
||||||
width: 100%;
|
|
||||||
padding-top: var(--aspect-ratio-standard);
|
|
||||||
position: relative;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
|
|
||||||
.media__thumb,
|
|
||||||
iframe {
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
~ .notice-message {
|
|
||||||
margin-top: var(--spacing-m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__discussion {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
@media (min-width: $breakpoint-medium) {
|
|
||||||
margin: 0;
|
|
||||||
width: var(--livestream-comments-width);
|
|
||||||
height: calc(100vh - var(--header-height));
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
top: var(--header-height);
|
|
||||||
bottom: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
border-top: none;
|
|
||||||
border-bottom: none;
|
|
||||||
border-right: none;
|
|
||||||
|
|
||||||
.card__main-actions {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-discussion__header {
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
padding-bottom: var(--spacing-s);
|
|
||||||
margin-bottom: 0;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
@media (min-width: $breakpoint-small) {
|
|
||||||
height: $discussion-header__height;
|
|
||||||
padding: 0 var(--spacing-s);
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-discussion__title {
|
|
||||||
@extend .card__title-section;
|
|
||||||
@extend .card__title-section--small;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__comments-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: calc(100vh - var(--header-height) - #{$discussion-header__height});
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__comments {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
font-size: var(--font-small);
|
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: visible;
|
|
||||||
padding-top: var(--spacing-s);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-comment {
|
|
||||||
list-style-type: none;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.channel-name {
|
|
||||||
font-size: var(--font-xsmall);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-comment--superchat {
|
|
||||||
+ .livestream-comment--superchat {
|
|
||||||
margin-bottom: var(--spacing-xxs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-comment__info {
|
|
||||||
margin-top: calc(var(--spacing-xxs) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
max-height: 4rem;
|
|
||||||
border-top-right-radius: 2px;
|
|
||||||
border-bottom-right-radius: 2px;
|
|
||||||
width: 5px;
|
|
||||||
background-color: var(--color-superchat);
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-comment__body {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-comment__body {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-left: var(--spacing-s);
|
|
||||||
|
|
||||||
.channel-thumbnail {
|
|
||||||
@include handleChannelGif(2rem);
|
|
||||||
margin-top: var(--spacing-xxs);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-comment__menu {
|
|
||||||
position: absolute;
|
|
||||||
right: var(--spacing-xs);
|
|
||||||
top: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__comments__scroll-to-recent {
|
|
||||||
margin-top: -$recent-msg-button__height;
|
|
||||||
align-self: center;
|
|
||||||
margin-bottom: var(--spacing-xs);
|
|
||||||
font-size: var(--font-xsmall);
|
|
||||||
padding: var(--spacing-xxs) var(--spacing-s);
|
|
||||||
opacity: 0.9;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__comment-create {
|
|
||||||
padding: var(--spacing-s);
|
|
||||||
border-top: 1px solid var(--color-border);
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__channel-link {
|
|
||||||
margin-bottom: var(--spacing-xl);
|
|
||||||
box-shadow: 0 0 0 rgba(246, 72, 83, 0.4);
|
|
||||||
animation: livePulse 2s infinite;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes livePulse {
|
|
||||||
0% {
|
|
||||||
box-shadow: 0 0 0 0 rgba(246, 72, 83, 0.4);
|
|
||||||
}
|
|
||||||
70% {
|
|
||||||
box-shadow: 0 0 0 10px rgba(246, 72, 83, 0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0 0 0 rgba(246, 72, 83, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__publish-checkbox {
|
|
||||||
margin: var(--spacing-l) 0;
|
|
||||||
|
|
||||||
.checkbox,
|
|
||||||
.radio {
|
|
||||||
margin-top: var(--spacing-m);
|
|
||||||
|
|
||||||
label {
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__creator-message {
|
|
||||||
background-color: #fde68a;
|
|
||||||
padding: var(--spacing-m);
|
|
||||||
color: black;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: var(--font-small);
|
|
||||||
margin-bottom: var(--spacing-s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__publish-intro {
|
|
||||||
margin-top: var(--spacing-l);
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__viewer-count {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
margin-left: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-superchats__wrapper {
|
|
||||||
flex-shrink: 0;
|
|
||||||
position: relative;
|
|
||||||
overflow-x: scroll;
|
|
||||||
padding: var(--spacing-s) var(--spacing-xs);
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
font-size: var(--font-small);
|
|
||||||
background-color: var(--color-card-background);
|
|
||||||
|
|
||||||
@media (min-width: $breakpoint-small) {
|
|
||||||
padding: var(--spacing-xs);
|
|
||||||
width: var(--livestream-comments-width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-pinned__wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
position: relative;
|
|
||||||
padding: var(--spacing-s) var(--spacing-xs);
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
font-size: var(--font-small);
|
|
||||||
background-color: var(--color-card-background-highlighted);
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.livestream-comment {
|
|
||||||
width: 100%;
|
|
||||||
padding-top: var(--spacing-xs);
|
|
||||||
max-height: 6rem;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-button {
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
padding: 0 calc(var(--spacing-m) - var(--spacing-xs)) 0 var(--spacing-m);
|
|
||||||
color: var(--color-text-subtitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: $breakpoint-small) {
|
|
||||||
padding: var(--spacing-xs);
|
|
||||||
width: var(--livestream-comments-width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-superchat__amount-large {
|
|
||||||
.credit-amount {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-superchats__inner {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-superchat {
|
|
||||||
display: flex;
|
|
||||||
margin-right: var(--spacing-xs);
|
|
||||||
padding: var(--spacing-xxs);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
|
|
||||||
.channel-thumbnail {
|
|
||||||
margin-right: var(--spacing-xs);
|
|
||||||
@include handleChannelGif(2rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:first-of-type {
|
|
||||||
background-color: var(--color-superchat);
|
|
||||||
|
|
||||||
.channel-name {
|
|
||||||
max-width: 8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-of-type(2) {
|
|
||||||
background-color: var(--color-superchat-2);
|
|
||||||
}
|
|
||||||
&:nth-of-type(3) {
|
|
||||||
background-color: var(--color-superchat-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-of-type(-n + 3) {
|
|
||||||
.channel-name,
|
|
||||||
.credit-amount {
|
|
||||||
color: var(--color-black);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.channel-name {
|
|
||||||
max-width: 5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-superchat__info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: var(--font-xsmall);
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-superchat__banner {
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
padding: 0.25rem var(--spacing-s);
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is just a two small circles that overlap to make it look like
|
|
||||||
// the banner and the left border are connected
|
|
||||||
.livestream-superchat__banner-corner {
|
|
||||||
height: calc(var(--border-radius) * 2);
|
|
||||||
width: calc(var(--border-radius) * 2);
|
|
||||||
border-radius: 50%;
|
|
||||||
position: absolute;
|
|
||||||
background-color: var(--color-superchat);
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
transform: translateX(0) translateY(50%);
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
height: calc(var(--border-radius) * 2);
|
|
||||||
width: calc(var(--border-radius) * 2);
|
|
||||||
border-top-left-radius: var(--border-radius);
|
|
||||||
background-color: var(--color-card-background);
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
transform: translateX(25%) translateY(50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-comment__text {
|
|
||||||
padding-right: var(--spacing-xl);
|
|
||||||
padding-bottom: var(--spacing-xxs);
|
|
||||||
.markdown-preview {
|
|
||||||
p {
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.channel-name {
|
|
||||||
font-size: var(--font-small);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-superchat__tooltip-amount {
|
|
||||||
margin-top: var(--spacing-xs);
|
|
||||||
margin-left: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__superchat-comment {
|
|
||||||
margin-top: var(--spacing-s);
|
|
||||||
max-width: 5rem;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-superchat__amount-large {
|
|
||||||
min-width: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table--livestream-data {
|
|
||||||
td:nth-of-type(1) {
|
|
||||||
max-width: 4rem;
|
|
||||||
}
|
|
||||||
td:nth-of-type(2) {
|
|
||||||
min-width: 8.5rem;
|
|
||||||
}
|
|
||||||
td:nth-of-type(3) {
|
|
||||||
width: 4rem;
|
|
||||||
min-width: 9rem;
|
|
||||||
}
|
|
||||||
td:nth-of-type(4) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
@media (min-width: $breakpoint-small) {
|
|
||||||
td:nth-of-type(1) {
|
|
||||||
max-width: 4rem;
|
|
||||||
}
|
|
||||||
td:nth-of-type(2) {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
td:nth-of-type(3) {
|
|
||||||
width: 5rem;
|
|
||||||
}
|
|
||||||
td:nth-of-type(4) {
|
|
||||||
width: 100%;
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream_thumb_container {
|
|
||||||
height: 4rem;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream___thumb {
|
|
||||||
padding: 0 var(--spacing-xxs);
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream__data-row {
|
|
||||||
cursor: pointer;
|
|
||||||
.radio {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
&:nth-child(n) {
|
|
||||||
&.livestream__data-row--selected {
|
|
||||||
background-color: var(--color-button-toggle-bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
padding-right: var(--spacing-m);
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
|
||||||
padding: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
td {
|
|
||||||
.radio {
|
|
||||||
label::before {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: var(--color-input-toggle-bg-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: var(--color-input-bg-selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.livestream-list--view-more {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
margin-bottom: var(--spacing-m);
|
|
||||||
}
|
|
|
@ -206,47 +206,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main--livestream {
|
|
||||||
@extend .main--file-page;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
max-width: none;
|
|
||||||
|
|
||||||
.card-stack {
|
|
||||||
margin-top: var(--spacing-m);
|
|
||||||
margin-bottom: var(--spacing-m);
|
|
||||||
|
|
||||||
@media (min-width: $breakpoint-large + 300px) {
|
|
||||||
max-width: calc(var(--page-max-width--filepage) / 1.25);
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: $breakpoint-medium) and (max-width: $breakpoint-large + 300px) {
|
|
||||||
max-width: calc(100vw - var(--livestream-comments-width) - var(--spacing-m) * 3);
|
|
||||||
margin-left: var(--spacing-m);
|
|
||||||
margin-right: var(--spacing-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint-medium) {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.main__right-side {
|
|
||||||
width: var(--livestream-comments-width);
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint-medium) {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: var(--spacing-m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $breakpoint-medium) {
|
|
||||||
padding: 0 var(--spacing-m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.main--full-width {
|
.main--full-width {
|
||||||
@extend .main;
|
@extend .main;
|
||||||
|
|
||||||
|
|
|
@ -97,8 +97,6 @@ $breakpoint-large: 1600px;
|
||||||
--file-list-thumbnail-width: 10rem;
|
--file-list-thumbnail-width: 10rem;
|
||||||
|
|
||||||
--tag-height: 1.5rem;
|
--tag-height: 1.5rem;
|
||||||
|
|
||||||
--livestream-comments-width: 30rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to extract livestream claim uris from the output of
|
|
||||||
* `selectActiveLivestreams`.
|
|
||||||
*
|
|
||||||
* @param activeLivestreams Object obtained from `selectActiveLivestreams`.
|
|
||||||
* @param channelIds List of channel IDs to filter the results with.
|
|
||||||
* @returns {[]|Array<*>}
|
|
||||||
*/
|
|
||||||
export function getLivestreamUris(activeLivestreams: ?LivestreamInfo, channelIds: ?Array<string>) {
|
|
||||||
let values = (activeLivestreams && Object.values(activeLivestreams)) || [];
|
|
||||||
|
|
||||||
if (channelIds && channelIds.length > 0) {
|
|
||||||
// $FlowFixMe
|
|
||||||
values = values.filter((v) => channelIds.includes(v.creatorId) && Boolean(v.latestClaimUri));
|
|
||||||
} else {
|
|
||||||
// $FlowFixMe
|
|
||||||
values = values.filter((v) => Boolean(v.latestClaimUri));
|
|
||||||
}
|
|
||||||
|
|
||||||
// $FlowFixMe
|
|
||||||
return values.map((v) => v.latestClaimUri);
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue