strip out livestreams
This commit is contained in:
parent
fca18c26d3
commit
ab9f70930d
78 changed files with 126 additions and 3504 deletions
|
@ -7,7 +7,6 @@ import { useHistory } from 'react-router-dom';
|
|||
import Button from 'component/button';
|
||||
import ClaimListDiscover from 'component/claimListDiscover';
|
||||
import Icon from 'component/common/icon';
|
||||
import LivestreamLink from 'component/livestreamLink';
|
||||
import { Form, FormField } from 'component/common/form';
|
||||
import { DEBOUNCE_WAIT_DURATION_MS } from 'constants/search';
|
||||
import { lighthouse } from 'redux/actions/search';
|
||||
|
@ -115,9 +114,6 @@ function ChannelContent(props: Props) {
|
|||
{!fetching && Boolean(claimsInChannel) && !channelIsBlocked && !channelIsBlackListed && (
|
||||
<HiddenNsfwClaims uri={uri} />
|
||||
)}
|
||||
|
||||
<LivestreamLink uri={uri} />
|
||||
|
||||
{!fetching && channelIsBlackListed && (
|
||||
<section className="card card--section">
|
||||
<p>
|
||||
|
|
|
@ -19,11 +19,9 @@ type Props = {
|
|||
mentionTerm: string,
|
||||
noTopSuggestion?: boolean,
|
||||
showMature: boolean,
|
||||
isLivestream: boolean,
|
||||
creatorUri: string,
|
||||
commentorUris: Array<string>,
|
||||
subscriptionUris: Array<string>,
|
||||
unresolvedCommentors: Array<string>,
|
||||
unresolvedSubscriptions: Array<string>,
|
||||
canonicalCreator: string,
|
||||
canonicalCommentors: Array<string>,
|
||||
|
@ -34,10 +32,8 @@ type Props = {
|
|||
|
||||
export default function ChannelMentionSuggestions(props: Props) {
|
||||
const {
|
||||
unresolvedCommentors,
|
||||
unresolvedSubscriptions,
|
||||
canonicalCreator,
|
||||
isLivestream,
|
||||
creatorUri,
|
||||
inputRef,
|
||||
showMature,
|
||||
|
@ -184,11 +180,6 @@ export default function ChannelMentionSuggestions(props: Props) {
|
|||
}
|
||||
}, [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
|
||||
React.useEffect(() => {
|
||||
if (isTyping) return;
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
makeSelectClaimIsNsfw,
|
||||
makeSelectReflectingClaimForUri,
|
||||
makeSelectClaimWasPurchased,
|
||||
makeSelectClaimIsStreamPlaceholder,
|
||||
makeSelectTitleForUri,
|
||||
makeSelectDateForUri,
|
||||
} from 'redux/selectors/claims';
|
||||
|
@ -23,7 +22,6 @@ import { doCollectionEdit } from 'redux/actions/collections';
|
|||
import { doFileGet } from 'redux/actions/file';
|
||||
import { selectMutedChannels, makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||
import { makeSelectIsActiveLivestream } from 'redux/selectors/livestream';
|
||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
|
||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||
|
@ -58,8 +56,6 @@ const select = (state, props) => {
|
|||
isSubscribed: props.uri && makeSelectIsSubscribed(props.uri, true)(state),
|
||||
streamingUrl: props.uri && makeSelectStreamingUrlForUri(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),
|
||||
collectionUris: makeSelectUrlsForCollectionId(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 FileThumbnail from 'component/fileThumbnail';
|
||||
import UriIndicator from 'component/uriIndicator';
|
||||
import PreviewOverlayProperties from 'component/previewOverlayProperties';
|
||||
import ClaimTags from 'component/claimTags';
|
||||
import SubscribeButton from 'component/subscribeButton';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
|
@ -26,7 +25,6 @@ import ClaimMenuList from 'component/claimMenuList';
|
|||
import ClaimPreviewLoading from './claim-preview-loading';
|
||||
import ClaimPreviewHidden from './claim-preview-no-mature';
|
||||
import ClaimPreviewNoContent from './claim-preview-no-content';
|
||||
import { ENABLE_NO_SOURCE_CLAIMS } from 'config';
|
||||
import Button from 'component/button';
|
||||
import * as ICONS from 'constants/icons';
|
||||
|
||||
|
@ -78,8 +76,6 @@ type Props = {
|
|||
hideRepostLabel?: boolean,
|
||||
repostUrl?: string,
|
||||
hideMenu?: boolean,
|
||||
isLivestream?: boolean,
|
||||
isLivestreamActive: boolean,
|
||||
collectionId?: string,
|
||||
editCollection: (string, CollectionEditParams) => void,
|
||||
isCollectionMine: boolean,
|
||||
|
@ -144,8 +140,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
renderActions,
|
||||
hideMenu = false,
|
||||
// repostUrl,
|
||||
isLivestream, // need both? CHECK
|
||||
isLivestreamActive,
|
||||
collectionId,
|
||||
collectionIndex,
|
||||
editCollection,
|
||||
|
@ -296,7 +290,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
}
|
||||
}, [isValid, uri, isResolvingUri, shouldFetch, resolveUri]);
|
||||
|
||||
if ((shouldHide && !showNullPlaceholder) || (isLivestream && !ENABLE_NO_SOURCE_CLAIMS)) {
|
||||
if (shouldHide && !showNullPlaceholder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -329,11 +323,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
let liveProperty = null;
|
||||
if (isLivestreamActive === true) {
|
||||
liveProperty = (claim) => <>LIVE</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<WrapperElement
|
||||
ref={ref}
|
||||
|
@ -343,7 +332,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
'claim-preview__wrapper--channel': isChannelUri && type !== 'inline',
|
||||
'claim-preview__wrapper--inline': type === 'inline',
|
||||
'claim-preview__wrapper--small': type === 'small',
|
||||
'claim-preview__live': isLivestreamActive,
|
||||
'claim-preview__active': active,
|
||||
})}
|
||||
>
|
||||
|
@ -380,11 +368,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
)}
|
||||
</div>
|
||||
{/* @endif */}
|
||||
{(!isLivestream || isLivestreamActive) && (
|
||||
<div className="claim-preview__file-property-overlay">
|
||||
<PreviewOverlayProperties uri={uri} small={type === 'small'} properties={liveProperty} />
|
||||
</div>
|
||||
)}
|
||||
</FileThumbnail>
|
||||
</NavLink>
|
||||
) : (
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import * as PAGES from 'constants/pages';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
makeSelectClaimForUri,
|
||||
makeSelectClaimIsPending,
|
||||
makeSelectClaimIsStreamPlaceholder,
|
||||
} from 'redux/selectors/claims';
|
||||
import { makeSelectClaimForUri, makeSelectClaimIsPending } from 'redux/selectors/claims';
|
||||
import { doClearPublish, doPrepareEdit } from 'redux/actions/publish';
|
||||
import { push } from 'connected-react-router';
|
||||
import ClaimPreviewSubtitle from './view';
|
||||
|
@ -12,7 +8,6 @@ import ClaimPreviewSubtitle from './view';
|
|||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
pending: makeSelectClaimIsPending(props.uri)(state),
|
||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// @flow
|
||||
import { ENABLE_NO_SOURCE_CLAIMS } from 'config';
|
||||
import React from 'react';
|
||||
import UriIndicator from 'component/uriIndicator';
|
||||
import DateTime from 'component/dateTime';
|
||||
|
@ -13,12 +12,11 @@ type Props = {
|
|||
pending?: boolean,
|
||||
type: string,
|
||||
beginPublish: (?string) => void,
|
||||
isLivestream: boolean,
|
||||
};
|
||||
|
||||
// previews used in channel overview and homepage (and other places?)
|
||||
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;
|
||||
|
||||
let isChannel;
|
||||
|
@ -38,15 +36,12 @@ function ClaimPreviewSubtitle(props: Props) {
|
|||
type !== 'inline' &&
|
||||
`${claimsInChannel} ${claimsInChannel === 1 ? __('upload') : __('uploads')}`}
|
||||
|
||||
{!isChannel &&
|
||||
(isLivestream && ENABLE_NO_SOURCE_CLAIMS ? (
|
||||
__('Livestream')
|
||||
) : (
|
||||
{!isChannel && (
|
||||
<>
|
||||
<FileViewCountInline uri={uri} isLivestream={isLivestream} />
|
||||
<FileViewCountInline uri={uri} />
|
||||
<DateTime timeAgo uri={uri} />
|
||||
</>
|
||||
))}
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
|
|
@ -6,14 +6,12 @@ import {
|
|||
makeSelectTitleForUri,
|
||||
makeSelectChannelForClaimUri,
|
||||
makeSelectClaimIsNsfw,
|
||||
makeSelectClaimIsStreamPlaceholder,
|
||||
makeSelectDateForUri,
|
||||
} from 'redux/selectors/claims';
|
||||
import { doFileGet } from 'redux/actions/file';
|
||||
import { doResolveUri } from 'redux/actions/claims';
|
||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||
import { makeSelectViewCountForUri, selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||
import { makeSelectIsActiveLivestream } from 'redux/selectors/livestream';
|
||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||
import ClaimPreviewTile from './view';
|
||||
import formatMediaDuration from 'util/formatMediaDuration';
|
||||
|
@ -36,8 +34,6 @@ const select = (state, props) => {
|
|||
blockedChannelUris: selectMutedChannels(state),
|
||||
showMature: selectShowMatureContent(state),
|
||||
isMature: makeSelectClaimIsNsfw(props.uri)(state),
|
||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
||||
isLivestreamActive: makeSelectIsActiveLivestream(props.uri)(state),
|
||||
viewCount: makeSelectViewCountForUri(props.uri)(state),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -49,10 +49,7 @@ type Props = {
|
|||
showHiddenByUser?: boolean,
|
||||
properties?: (Claim) => void,
|
||||
collectionId?: string,
|
||||
showNoSourceClaims?: boolean,
|
||||
isLivestream: boolean,
|
||||
viewCount: string,
|
||||
isLivestreamActive: boolean,
|
||||
};
|
||||
|
||||
// preview image cards used in related video functionality, channel overview page and homepage
|
||||
|
@ -76,9 +73,6 @@ function ClaimPreviewTile(props: Props) {
|
|||
showMature,
|
||||
showHiddenByUser,
|
||||
properties,
|
||||
showNoSourceClaims,
|
||||
isLivestream,
|
||||
isLivestreamActive,
|
||||
collectionId,
|
||||
mediaDuration,
|
||||
viewCount,
|
||||
|
@ -175,13 +169,13 @@ function ClaimPreviewTile(props: Props) {
|
|||
shouldHide = blockedChannelUris.some((blockedUri) => isURIEqual(blockedUri, signingChannel.permanent_url));
|
||||
}
|
||||
|
||||
if (shouldHide || (isLivestream && !showNoSourceClaims)) {
|
||||
if (shouldHide) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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)) {
|
||||
return (
|
||||
|
@ -202,16 +196,12 @@ function ClaimPreviewTile(props: Props) {
|
|||
}
|
||||
|
||||
let liveProperty = null;
|
||||
if (isLivestreamActive === true) {
|
||||
liveProperty = (claim) => <>LIVE</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
onClick={handleClick}
|
||||
className={classnames('card claim-preview--tile', {
|
||||
'claim-preview__wrapper--channel': isChannel,
|
||||
'claim-preview__live': isLivestreamActive,
|
||||
})}
|
||||
>
|
||||
<NavLink {...navLinkProps} role="none" tabIndex={-1} aria-hidden>
|
||||
|
@ -273,7 +263,7 @@ function ClaimPreviewTile(props: Props) {
|
|||
<div className="claim-tile__about">
|
||||
<UriIndicator uri={uri} link />
|
||||
<div className="claim-tile__about--counts">
|
||||
<FileViewCountInline uri={uri} isLivestream={isLivestream} />
|
||||
<FileViewCountInline uri={uri} />
|
||||
<DateTime timeAgo uri={uri} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, makeSelectClaimIsStreamPlaceholder } from 'redux/selectors/claims';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import FileType from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(select)(FileType);
|
||||
|
|
|
@ -26,18 +26,7 @@ const select = (state, props) => ({
|
|||
|
||||
const perform = (dispatch, ownProps) => ({
|
||||
createComment: (comment, claimId, parentId, txid, payment_intent_id, environment) =>
|
||||
dispatch(
|
||||
doCommentCreate(
|
||||
comment,
|
||||
claimId,
|
||||
parentId,
|
||||
ownProps.uri,
|
||||
ownProps.livestream,
|
||||
txid,
|
||||
payment_intent_id,
|
||||
environment
|
||||
)
|
||||
),
|
||||
dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri, txid, payment_intent_id, environment)),
|
||||
sendTip: (params, callback, errorCallback) => dispatch(doSendTip(params, false, callback, errorCallback, false)),
|
||||
doToast: (options) => dispatch(doToast(options)),
|
||||
doFetchCreatorSettings: (channelClaimId) => dispatch(doFetchCreatorSettings(channelClaimId)),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @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 { getChannelIdFromClaim } from 'util/claim';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
|
@ -40,7 +40,6 @@ type Props = {
|
|||
activeChannel: string,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
bottom: boolean,
|
||||
livestream?: boolean,
|
||||
embed?: boolean,
|
||||
claimIsMine: boolean,
|
||||
supportDisabled: boolean,
|
||||
|
@ -68,8 +67,6 @@ export function CommentCreate(props: Props) {
|
|||
parentId,
|
||||
activeChannelClaim,
|
||||
bottom,
|
||||
livestream,
|
||||
embed,
|
||||
claimIsMine,
|
||||
settingsByChannelId,
|
||||
supportDisabled,
|
||||
|
@ -179,7 +176,6 @@ export function CommentCreate(props: Props) {
|
|||
let newMentionValue = mentionValue.replace('lbry://', '');
|
||||
if (newMentionValue.includes('#')) newMentionValue = newMentionValue.replace('#', ':');
|
||||
|
||||
if (livestream && key !== KEYCODES.TAB) setPauseQuickSend(true);
|
||||
setCommentValue(
|
||||
commentValue.substring(0, selectedMentionIndex) +
|
||||
`${newMentionValue}` +
|
||||
|
@ -190,7 +186,7 @@ export function CommentCreate(props: Props) {
|
|||
}
|
||||
|
||||
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();
|
||||
buttonRef.current.click();
|
||||
}
|
||||
|
@ -442,17 +438,8 @@ export function CommentCreate(props: Props) {
|
|||
<div
|
||||
role="button"
|
||||
onClick={() => {
|
||||
if (embed) {
|
||||
window.open(`https://odysee.com/$/${PAGES.AUTH}?redirect=/$/${PAGES.LIVESTREAM}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const pathPlusRedirect = `/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`;
|
||||
if (livestream) {
|
||||
window.open(pathPlusRedirect);
|
||||
} else {
|
||||
push(pathPlusRedirect);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormField type="textarea" name={'comment_signup_prompt'} placeholder={__('Say something about this...')} />
|
||||
|
@ -518,7 +505,6 @@ export function CommentCreate(props: Props) {
|
|||
{!advancedEditor && (
|
||||
<ChannelMentionSuggestions
|
||||
uri={uri}
|
||||
isLivestream={livestream}
|
||||
inputRef={formFieldInputRef}
|
||||
mentionTerm={channelMention}
|
||||
creatorUri={channelUri}
|
||||
|
@ -533,9 +519,7 @@ export function CommentCreate(props: Props) {
|
|||
className={isReply ? 'content_reply' : 'content_comment'}
|
||||
label={
|
||||
<span className="comment-new__label-wrapper">
|
||||
{!livestream && (
|
||||
<div className="comment-new__label">{isReply ? __('Replying as') + ' ' : __('Comment as') + ' '}</div>
|
||||
)}
|
||||
<SelectChannel tiny />
|
||||
</span>
|
||||
}
|
||||
|
@ -548,7 +532,7 @@ export function CommentCreate(props: Props) {
|
|||
charCount={charCount}
|
||||
onChange={handleCommentChange}
|
||||
autoFocus={isReply}
|
||||
textAreaMaxLength={livestream ? FF_MAX_CHARS_IN_LIVESTREAM_COMMENT : FF_MAX_CHARS_IN_COMMENT}
|
||||
textAreaMaxLength={FF_MAX_CHARS_IN_COMMENT}
|
||||
/>
|
||||
{isSupportComment && (
|
||||
<WalletTipAmountSelector
|
||||
|
|
|
@ -1767,532 +1767,6 @@ export const icons = {
|
|||
</g>
|
||||
</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) => (
|
||||
<svg
|
||||
{...props}
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
makeSelectClaimIsMine,
|
||||
makeSelectClaimForUri,
|
||||
selectMyChannelClaims,
|
||||
makeSelectClaimIsStreamPlaceholder,
|
||||
makeSelectTagInClaimOrChannelForUri,
|
||||
} from 'redux/selectors/claims';
|
||||
import { makeSelectStreamingUrlForUri, makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
||||
|
@ -24,7 +23,6 @@ const select = (state, props) => ({
|
|||
renderMode: makeSelectFileRenderModeForUri(props.uri)(state),
|
||||
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||
myChannels: selectMyChannelClaims(state),
|
||||
isLivestreamClaim: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
||||
reactionsDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state),
|
||||
streamingUrl: makeSelectStreamingUrlForUri(props.uri)(state),
|
||||
});
|
||||
|
|
|
@ -30,7 +30,6 @@ type Props = {
|
|||
doToast: ({ message: string }) => void,
|
||||
clearPlayingUri: () => void,
|
||||
hideRepost?: boolean,
|
||||
isLivestreamClaim: boolean,
|
||||
reactionsDisabled: boolean,
|
||||
download: (string) => void,
|
||||
streamingUrl: ?string,
|
||||
|
@ -50,7 +49,6 @@ function FileActions(props: Props) {
|
|||
clearPlayingUri,
|
||||
doToast,
|
||||
hideRepost,
|
||||
isLivestreamClaim,
|
||||
reactionsDisabled,
|
||||
} = props;
|
||||
const {
|
||||
|
@ -96,7 +94,7 @@ function FileActions(props: Props) {
|
|||
|
||||
const lhsSection = (
|
||||
<>
|
||||
{ENABLE_FILE_REACTIONS && !reactionsDisabled && <FileReactions uri={uri} livestream={isLivestreamClaim} />}
|
||||
{ENABLE_FILE_REACTIONS && !reactionsDisabled && <FileReactions uri={uri} />}
|
||||
<ClaimSupportButton uri={uri} fileAction />
|
||||
<ClaimCollectionAddButton uri={uri} fileAction />
|
||||
{!hideRepost && (
|
||||
|
@ -124,14 +122,12 @@ function FileActions(props: Props) {
|
|||
|
||||
const rhsSection = (
|
||||
<>
|
||||
{/* @if TARGET='app' */}
|
||||
<FileDownloadLink uri={uri} />
|
||||
{/* @endif */}
|
||||
{claimIsMine && (
|
||||
<Button
|
||||
className="button--file-action"
|
||||
icon={ICONS.EDIT}
|
||||
label={isLivestreamClaim ? __('Update') : __('Edit')}
|
||||
label={__('Edit')}
|
||||
navigate={`/$/${PAGES.UPLOAD}`}
|
||||
onClick={() => {
|
||||
prepareEdit(claim, editUri, fileInfo);
|
||||
|
@ -147,7 +143,7 @@ function FileActions(props: Props) {
|
|||
onClick={() => openModal(MODALS.CONFIRM_FILE_REMOVE, { uri })}
|
||||
/>
|
||||
)}
|
||||
{(!isLivestreamClaim || !claimIsMine) && (
|
||||
{!claimIsMine && (
|
||||
<Menu>
|
||||
<MenuButton
|
||||
className="button--file-action"
|
||||
|
@ -159,7 +155,6 @@ function FileActions(props: Props) {
|
|||
<Icon size={20} icon={ICONS.MORE} />
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list">
|
||||
{!claimIsMine && (
|
||||
<MenuItem
|
||||
className="comment__menu-option"
|
||||
onSelect={() => push(`/$/${PAGES.REPORT_CONTENT}?claimId=${claimId}`)}
|
||||
|
@ -169,7 +164,6 @@ function FileActions(props: Props) {
|
|||
{__('Report content')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
|
|
|
@ -16,7 +16,6 @@ type Props = {
|
|||
likeCount: number,
|
||||
dislikeCount: number,
|
||||
myReaction: ?string,
|
||||
livestream?: boolean,
|
||||
};
|
||||
|
||||
function FileReactions(props: Props) {
|
||||
|
@ -29,7 +28,6 @@ function FileReactions(props: Props) {
|
|||
myReaction,
|
||||
likeCount,
|
||||
dislikeCount,
|
||||
livestream,
|
||||
} = props;
|
||||
|
||||
const claimId = claim && claim.claim_id;
|
||||
|
@ -42,21 +40,10 @@ function FileReactions(props: Props) {
|
|||
doFetchReactions(claimId);
|
||||
}
|
||||
|
||||
let fetchInterval;
|
||||
if (claimId) {
|
||||
fetchReactions();
|
||||
|
||||
if (livestream) {
|
||||
fetchInterval = setInterval(fetchReactions, 45000);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (fetchInterval) {
|
||||
clearInterval(fetchInterval);
|
||||
}
|
||||
};
|
||||
}, [claimId, doFetchReactions, livestream]);
|
||||
}, [claimId, doFetchReactions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -98,7 +85,9 @@ function FileReactions(props: Props) {
|
|||
requiresAuth={IS_WEB}
|
||||
authSrc={'filereaction_dislike'}
|
||||
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)}</>}
|
||||
iconSize={18}
|
||||
icon={dislikeIcon}
|
||||
|
|
|
@ -1,28 +1,22 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import DateTime from 'component/dateTime';
|
||||
import FileViewCount from 'component/fileViewCount';
|
||||
import FileActions from 'component/fileActions';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
livestream?: boolean,
|
||||
activeViewers?: number,
|
||||
isLive?: boolean,
|
||||
};
|
||||
|
||||
function FileSubtitle(props: Props) {
|
||||
const { uri, livestream = false, activeViewers, isLive = false } = props;
|
||||
const { uri } = props;
|
||||
|
||||
return (
|
||||
<div className="media__subtitle--between">
|
||||
<div className="file__viewdate">
|
||||
{livestream ? <span>{__('Right now')}</span> : <DateTime uri={uri} show={DateTime.SHOW_DATE} />}
|
||||
|
||||
<FileViewCount uri={uri} livestream={livestream} activeViewers={activeViewers} isLive={isLive} />
|
||||
<FileViewCount uri={uri} />
|
||||
</div>
|
||||
|
||||
<FileActions uri={uri} hideRepost={livestream} livestream={livestream} />
|
||||
<FileActions uri={uri} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,18 +2,15 @@ import { connect } from 'react-redux';
|
|||
import { doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc';
|
||||
import { makeSelectTitleForUri, makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { makeSelectInsufficientCreditsForUri } from 'redux/selectors/content';
|
||||
import { makeSelectViewersForId } from 'redux/selectors/livestream';
|
||||
import FileTitleSection from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
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 channelUri = claim && claim.signing_channel ? claim.signing_channel.canonical_url : undefined;
|
||||
const subCount = channelUri && makeSelectSubCountForUri(channelUri)(state);
|
||||
|
||||
return {
|
||||
viewers,
|
||||
isInsufficientCredits: makeSelectInsufficientCreditsForUri(props.uri)(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
channelClaimId,
|
||||
|
|
|
@ -20,27 +20,13 @@ type Props = {
|
|||
title: string,
|
||||
nsfw: boolean,
|
||||
isNsfwBlocked: boolean,
|
||||
livestream?: boolean,
|
||||
isLive?: boolean,
|
||||
viewers?: number,
|
||||
subCount: number,
|
||||
channelClaimId?: string,
|
||||
fetchSubCount: (string) => void,
|
||||
};
|
||||
|
||||
function FileTitleSection(props: Props) {
|
||||
const {
|
||||
title,
|
||||
uri,
|
||||
nsfw,
|
||||
isNsfwBlocked,
|
||||
livestream = false,
|
||||
isLive = false,
|
||||
viewers,
|
||||
subCount,
|
||||
channelClaimId,
|
||||
fetchSubCount,
|
||||
} = props;
|
||||
const { title, uri, nsfw, isNsfwBlocked, subCount, channelClaimId, fetchSubCount } = props;
|
||||
const [hasAcknowledgedSec, setHasAcknowledgedSec] = usePersistedState('sec-nag', false);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
@ -78,7 +64,7 @@ function FileTitleSection(props: Props) {
|
|||
body={
|
||||
<React.Fragment>
|
||||
<ClaimInsufficientCredits uri={uri} />
|
||||
<FileSubtitle uri={uri} isLive={isLive} livestream={livestream} activeViewers={viewers} />
|
||||
<FileSubtitle uri={uri} />
|
||||
</React.Fragment>
|
||||
}
|
||||
actions={
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectMediaTypeForUri } from 'redux/selectors/file_info';
|
||||
import { makeSelectClaimIsStreamPlaceholder } from 'redux/selectors/claims';
|
||||
import FileType from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
mediaType: makeSelectMediaTypeForUri(props.uri)(state),
|
||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(select)(FileType);
|
||||
|
|
|
@ -7,19 +7,18 @@ import * as COL from 'constants/collections';
|
|||
type Props = {
|
||||
uri: string,
|
||||
mediaType: string,
|
||||
isLivestream: boolean,
|
||||
small: boolean,
|
||||
};
|
||||
|
||||
function FileType(props: Props) {
|
||||
const { mediaType, isLivestream, small } = props;
|
||||
const { mediaType, small } = props;
|
||||
const size = small ? COL.ICON_SIZE : undefined;
|
||||
|
||||
if (mediaType === 'image') {
|
||||
return <Icon size={size} icon={ICONS.IMAGE} />;
|
||||
} else if (mediaType === 'audio') {
|
||||
return <Icon size={size} icon={ICONS.AUDIO} />;
|
||||
} else if (mediaType === 'video' || isLivestream) {
|
||||
} else if (mediaType === 'video') {
|
||||
return <Icon size={size} icon={ICONS.VIDEO} />;
|
||||
} else if (mediaType === 'text') {
|
||||
return <Icon size={size} icon={ICONS.TEXT} />;
|
||||
|
|
|
@ -8,24 +8,12 @@ type Props = {
|
|||
fetchingViewCount: boolean,
|
||||
uri: string,
|
||||
viewCount: string,
|
||||
livestream?: boolean,
|
||||
activeViewers?: number,
|
||||
isLive?: boolean,
|
||||
doAnalyticsView: (string) => void,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
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(() => {
|
||||
if (claimId) {
|
||||
fetchViewCount(claimId);
|
||||
|
@ -36,9 +24,7 @@ function FileViewCount(props: Props) {
|
|||
|
||||
return (
|
||||
<span className="media__subtitle--centered">
|
||||
{!livestream &&
|
||||
activeViewers === undefined &&
|
||||
(viewCount !== 1 ? __('%view_count% views', { view_count: formattedViewCount }) : __('1 view'))}
|
||||
{viewCount !== 1 ? __('%view_count% views', { view_count: formattedViewCount }) : __('1 view')}
|
||||
{<HelpLink href="https://lbry.com/faq/views" />}
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'scss/component/_view_count.scss';
|
|||
|
||||
type Props = {
|
||||
uri: string,
|
||||
isLivestream?: boolean,
|
||||
// --- select ---
|
||||
claim: ?StreamClaim,
|
||||
viewCount: string,
|
||||
|
@ -12,7 +11,7 @@ type Props = {
|
|||
};
|
||||
|
||||
export default function FileViewCountInline(props: Props) {
|
||||
const { isLivestream, claim, viewCount, lang } = props;
|
||||
const { claim, viewCount, lang } = props;
|
||||
let formattedViewCount;
|
||||
|
||||
try {
|
||||
|
@ -30,8 +29,8 @@ export default function FileViewCountInline(props: Props) {
|
|||
// clean up (only one place edit/remove).
|
||||
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
|
||||
if (!viewCount || (claim && claim.repost_url) || isLivestream || !isChannelPage) {
|
||||
// dont show if no view count, if it's a repost or isn't a channel page
|
||||
if (!viewCount || (claim && claim.repost_url) || !isChannelPage) {
|
||||
// (1) Currently, makeSelectViewCountForUri doesn't differentiate between
|
||||
// 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.
|
||||
|
|
|
@ -9,7 +9,7 @@ import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
|
|||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { doSignOut, doOpenModal } from 'redux/actions/app';
|
||||
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';
|
||||
|
||||
const select = (state) => ({
|
||||
|
@ -27,7 +27,6 @@ const select = (state) => ({
|
|||
hasNavigated: selectHasNavigated(state),
|
||||
user: selectUser(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
activeChannelStakedLevel: selectActiveChannelStakedLevel(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @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 SETTINGS from 'constants/settings';
|
||||
import * as PAGES from 'constants/pages';
|
||||
|
@ -63,7 +63,6 @@ type Props = {
|
|||
isAbsoluteSideNavHidden: boolean,
|
||||
hideCancel: boolean,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
activeChannelStakedLevel: number,
|
||||
};
|
||||
|
||||
const Header = (props: Props) => {
|
||||
|
@ -92,7 +91,6 @@ const Header = (props: Props) => {
|
|||
hideCancel,
|
||||
user,
|
||||
activeChannelClaim,
|
||||
activeChannelStakedLevel,
|
||||
} = props;
|
||||
const isMobile = useIsMobile();
|
||||
// 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 { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
||||
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;
|
||||
|
||||
// Sign out if they click the "x" when they are on the password prompt
|
||||
|
@ -288,7 +280,6 @@ const Header = (props: Props) => {
|
|||
history={history}
|
||||
handleThemeToggle={handleThemeToggle}
|
||||
currentTheme={currentTheme}
|
||||
livestreamEnabled={livestreamEnabled}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -405,11 +396,10 @@ type HeaderMenuButtonProps = {
|
|||
history: { push: (string) => void },
|
||||
handleThemeToggle: (string) => void,
|
||||
currentTheme: string,
|
||||
livestreamEnabled: boolean,
|
||||
};
|
||||
|
||||
function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||
const { authenticated, notificationsEnabled, history, handleThemeToggle, currentTheme, livestreamEnabled } = props;
|
||||
const { authenticated, notificationsEnabled, history, handleThemeToggle, currentTheme } = props;
|
||||
|
||||
return (
|
||||
<div className="header__buttons">
|
||||
|
@ -437,18 +427,6 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
|||
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||
{__('New Channel')}
|
||||
</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>
|
||||
</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 [quickReply, setQuickReply] = React.useState();
|
||||
|
||||
const isIgnoredNotification = notification_rule === RULE.NEW_LIVESTREAM;
|
||||
if (isIgnoredNotification) {
|
||||
return null;
|
||||
}
|
||||
const isCommentNotification =
|
||||
notification_rule === RULE.COMMENT ||
|
||||
notification_rule === RULE.COMMENT_REPLY ||
|
||||
|
@ -83,10 +87,6 @@ export default function Notification(props: Props) {
|
|||
channelUrl = notification_parameters.dynamic.channel_url;
|
||||
icon = creatorIcon(channelUrl);
|
||||
break;
|
||||
case RULE.NEW_LIVESTREAM:
|
||||
channelUrl = notification_parameters.dynamic.channel_url;
|
||||
icon = creatorIcon(channelUrl);
|
||||
break;
|
||||
case RULE.DAILY_WATCH_AVAILABLE:
|
||||
case RULE.DAILY_WATCH_REMIND:
|
||||
case RULE.MISSED_OUT:
|
||||
|
|
|
@ -28,7 +28,6 @@ type Props = {
|
|||
fullWidthPage: boolean,
|
||||
videoTheaterMode: boolean,
|
||||
isMarkdown?: boolean,
|
||||
livestream?: boolean,
|
||||
chatDisabled: boolean,
|
||||
rightSide?: Node,
|
||||
backout: {
|
||||
|
@ -52,9 +51,7 @@ function Page(props: Props) {
|
|||
backout,
|
||||
videoTheaterMode,
|
||||
isMarkdown = false,
|
||||
livestream,
|
||||
rightSide,
|
||||
chatDisabled,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
|
@ -127,8 +124,7 @@ function Page(props: Props) {
|
|||
'main--file-page': filePage,
|
||||
'main--settings-page': settingsPage,
|
||||
'main--markdown': isMarkdown,
|
||||
'main--theater-mode': isOnFilePage && videoTheaterMode && !livestream,
|
||||
'main--livestream': livestream && !chatDisabled,
|
||||
'main--theater-mode': isOnFilePage && videoTheaterMode,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -2,12 +2,11 @@ import { connect } from 'react-redux';
|
|||
import { selectBalance } from 'redux/selectors/wallet';
|
||||
import { selectIsStillEditing, makeSelectPublishFormValue } from 'redux/selectors/publish';
|
||||
import { doUpdatePublishForm, doClearPublish } from 'redux/actions/publish';
|
||||
import { makeSelectClaimIsStreamPlaceholder } from 'redux/selectors/claims';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import { selectFfmpegStatus } from 'redux/selectors/settings';
|
||||
import PublishPage from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
const select = (state) => ({
|
||||
name: makeSelectPublishFormValue('name')(state),
|
||||
title: makeSelectPublishFormValue('title')(state),
|
||||
filePath: makeSelectPublishFormValue('filePath')(state),
|
||||
|
@ -20,7 +19,6 @@ const select = (state, props) => ({
|
|||
size: makeSelectPublishFormValue('fileSize')(state),
|
||||
duration: makeSelectPublishFormValue('fileDur')(state),
|
||||
isVid: makeSelectPublishFormValue('fileVid')(state),
|
||||
isLivestreamClaim: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// @flow
|
||||
import { SITE_NAME, WEB_PUBLISH_SIZE_LIMIT_GB } from 'config';
|
||||
import type { Node } from 'react';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
@ -14,11 +13,6 @@ import I18nMessage from 'component/i18nMessage';
|
|||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import * as PUBLISH_MODES from 'constants/publish_types';
|
||||
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 = {
|
||||
uri: ?string,
|
||||
|
@ -44,12 +38,7 @@ type Props = {
|
|||
setPublishMode: (string) => void,
|
||||
setPrevFileText: (string) => void,
|
||||
header: Node,
|
||||
livestreamData: LivestreamReplayData,
|
||||
isLivestreamClaim: boolean,
|
||||
checkLivestreams: (string, ?string, ?string) => void,
|
||||
channelId: string,
|
||||
channelSignature: { signature?: string, signing_ts?: string },
|
||||
isCheckingLivestreams: boolean,
|
||||
setWaitForFile: (boolean) => void,
|
||||
};
|
||||
|
||||
|
@ -76,23 +65,10 @@ function PublishFile(props: Props) {
|
|||
setPublishMode,
|
||||
setPrevFileText,
|
||||
header,
|
||||
livestreamData,
|
||||
isLivestreamClaim,
|
||||
subtitle,
|
||||
checkLivestreams,
|
||||
channelId,
|
||||
channelSignature,
|
||||
isCheckingLivestreams,
|
||||
setWaitForFile,
|
||||
} = props;
|
||||
|
||||
const SOURCE_NONE = 'none';
|
||||
const SOURCE_SELECT = 'select';
|
||||
const SOURCE_UPLOAD = 'upload';
|
||||
|
||||
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 MINUTES_THRESHOLD = 30;
|
||||
|
@ -101,35 +77,10 @@ function PublishFile(props: Props) {
|
|||
const sizeInMB = Number(size) / 1000000;
|
||||
const secondsToProcess = sizeInMB / PROCESSING_MB_PER_SECOND;
|
||||
const ffmpegAvail = ffmpegStatus.available;
|
||||
const [oversized, setOversized] = useState(false);
|
||||
const [currentFile, setCurrentFile] = useState(null);
|
||||
const [currentFileType, setCurrentFileType] = useState(null);
|
||||
const [optimizeAvail, setOptimizeAvail] = useState(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
|
||||
useEffect(() => {
|
||||
|
@ -137,46 +88,12 @@ function PublishFile(props: Props) {
|
|||
if (currentFileType !== 'text/markdown' && !isStillEditing) {
|
||||
updatePublishForm({ filePath: '' });
|
||||
}
|
||||
} else if (mode === PUBLISH_MODES.LIVESTREAM) {
|
||||
updatePublishForm({ filePath: '' });
|
||||
}
|
||||
}, [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(() => {
|
||||
if (!filePath || filePath === '') {
|
||||
setCurrentFile('');
|
||||
setOversized(false);
|
||||
updateFileInfo(0, 0, false);
|
||||
} else if (typeof filePath !== 'string') {
|
||||
// Update currentFile file
|
||||
|
@ -198,10 +115,6 @@ function PublishFile(props: Props) {
|
|||
updatePublishForm({ fileDur: duration, fileSize: size, fileVid: isvid });
|
||||
}
|
||||
|
||||
function handlePaginateReplays(page) {
|
||||
setCurrentPage(page);
|
||||
}
|
||||
|
||||
function getBitrate(size, duration) {
|
||||
const s = Number(size);
|
||||
const d = Number(duration);
|
||||
|
@ -236,16 +149,6 @@ function PublishFile(props: Props) {
|
|||
}
|
||||
|
||||
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) {
|
||||
return (
|
||||
<p className="help--warning">
|
||||
|
@ -267,32 +170,13 @@ function PublishFile(props: Props) {
|
|||
}
|
||||
|
||||
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 (
|
||||
<p className="help">
|
||||
{__("If you don't choose a file, the file from your existing claim %name% will be used", { name: name })}
|
||||
</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) {
|
||||
return (
|
||||
<p className="help">
|
||||
|
@ -303,7 +187,6 @@ function PublishFile(props: Props) {
|
|||
</p>
|
||||
);
|
||||
}
|
||||
// @endif
|
||||
}
|
||||
|
||||
function parseName(newName) {
|
||||
|
@ -311,27 +194,6 @@ function PublishFile(props: Props) {
|
|||
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) {
|
||||
const title = event.target.value;
|
||||
// Update title
|
||||
|
@ -348,9 +210,7 @@ function PublishFile(props: Props) {
|
|||
}
|
||||
|
||||
function handleFileChange(file: WebFile, clearName = true) {
|
||||
const { showToast } = props;
|
||||
window.URL = window.URL || window.webkitURL;
|
||||
setOversized(false);
|
||||
|
||||
// select file, start to select a new one, then cancel
|
||||
if (!file) {
|
||||
|
@ -410,16 +270,6 @@ function PublishFile(props: Props) {
|
|||
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 } = {
|
||||
// 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.
|
||||
|
@ -472,149 +322,8 @@ function PublishFile(props: Props) {
|
|||
value={title}
|
||||
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 && (
|
||||
<>
|
||||
<FileSelector
|
||||
label={__('File')}
|
||||
disabled={disabled}
|
||||
|
@ -623,6 +332,8 @@ function PublishFile(props: Props) {
|
|||
// https://stackoverflow.com/questions/19107685/safari-input-type-file-accept-video-ignores-mp4-files
|
||||
placeholder={__('Select file to upload')}
|
||||
/>
|
||||
{getUploadMessage()}
|
||||
</>
|
||||
)}
|
||||
{showFileUpload && (
|
||||
<FormField
|
||||
|
@ -658,7 +369,6 @@ function PublishFile(props: Props) {
|
|||
</I18nMessage>
|
||||
</p>
|
||||
)}
|
||||
{/* @endif */}
|
||||
{isPublishPost && (
|
||||
<PostEditor
|
||||
label={__('Post --[noun, markdown post tab button]--')}
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
selectIsResolvingPublishUris,
|
||||
selectMyClaimForUri,
|
||||
} 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 SETTINGS from 'constants/settings';
|
||||
import { doClaimInitialRewards } from 'redux/actions/rewards';
|
||||
|
@ -45,7 +45,6 @@ const select = (state) => {
|
|||
user: selectUser(state),
|
||||
// The winning claim for a short lbry uri
|
||||
amountNeededForTakeover: selectTakeOverAmount(state),
|
||||
isLivestreamClaim: makeSelectClaimIsStreamPlaceholder(permanentUrl)(state),
|
||||
isPostClaim,
|
||||
permanentUrl,
|
||||
// My previously published claims under this short lbry uri
|
||||
|
|
|
@ -8,9 +8,8 @@
|
|||
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 Lbry from 'lbry';
|
||||
import { buildURI, isURIValid, isNameValid } from 'util/lbryURI';
|
||||
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
||||
import Button from 'component/button';
|
||||
|
@ -29,13 +28,9 @@ import I18nMessage from 'component/i18nMessage';
|
|||
import * as PUBLISH_MODES from 'constants/publish_types';
|
||||
import { useHistory } from 'react-router';
|
||||
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 tempy from 'tempy';
|
||||
// @endif
|
||||
|
||||
type Props = {
|
||||
disabled: boolean,
|
||||
|
@ -88,7 +83,6 @@ type Props = {
|
|||
incognito: boolean,
|
||||
user: ?User,
|
||||
activeChannelStakedLevel: number,
|
||||
isLivestreamClaim: boolean,
|
||||
isPostClaim: boolean,
|
||||
permanentUrl: ?string,
|
||||
remoteUrl: ?string,
|
||||
|
@ -127,9 +121,6 @@ function PublishForm(props: Props) {
|
|||
enablePublishPreview,
|
||||
activeChannelClaim,
|
||||
incognito,
|
||||
user,
|
||||
activeChannelStakedLevel,
|
||||
isLivestreamClaim,
|
||||
isPostClaim,
|
||||
permanentUrl,
|
||||
remoteUrl,
|
||||
|
@ -143,46 +134,31 @@ function PublishForm(props: Props) {
|
|||
const TYPE_PARAM = 'type';
|
||||
const uploadType = urlParams.get(TYPE_PARAM);
|
||||
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
|
||||
const AVAILABLE_MODES = Object.values(PUBLISH_MODES).filter((mode) => {
|
||||
// $FlowFixMe
|
||||
if (editingURI) {
|
||||
if (isPostClaim) {
|
||||
return mode === PUBLISH_MODES.POST;
|
||||
} else if (isLivestreamClaim) {
|
||||
return mode === PUBLISH_MODES.LIVESTREAM && enableLivestream;
|
||||
} else {
|
||||
return mode === PUBLISH_MODES.FILE;
|
||||
}
|
||||
} else if (_uploadType) {
|
||||
return mode === _uploadType && (mode !== PUBLISH_MODES.LIVESTREAM || enableLivestream);
|
||||
return mode === _uploadType;
|
||||
} else {
|
||||
return mode !== PUBLISH_MODES.LIVESTREAM || enableLivestream;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const MODE_TO_I18N_STR = {
|
||||
[PUBLISH_MODES.FILE]: 'File',
|
||||
[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 [isCheckingLivestreams, setCheckingLivestreams] = React.useState(false);
|
||||
|
||||
let customSubtitle;
|
||||
if (mode === PUBLISH_MODES.LIVESTREAM || isLivestreamClaim) {
|
||||
if (isLivestreamClaim) {
|
||||
customSubtitle = __('Update your livestream');
|
||||
} else {
|
||||
customSubtitle = __('Prepare an upcoming livestream');
|
||||
}
|
||||
} else if (mode === PUBLISH_MODES.POST || isPostClaim) {
|
||||
if (mode === PUBLISH_MODES.POST || isPostClaim) {
|
||||
if (isPostClaim) {
|
||||
customSubtitle = __('Edit your post');
|
||||
} else {
|
||||
|
@ -206,16 +182,12 @@ function PublishForm(props: Props) {
|
|||
const [prevFileText, setPrevFileText] = React.useState('');
|
||||
|
||||
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 fileFormDisabled = mode === PUBLISH_MODES.FILE && !filePath && !remoteUrl;
|
||||
const emptyPostError = mode === PUBLISH_MODES.POST && (!fileText || fileText.trim() === '');
|
||||
const formDisabled = (fileFormDisabled && !editingURI) || emptyPostError || publishing;
|
||||
const isInProgress = filePath || editingURI || name || title;
|
||||
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
|
||||
const activeChannelClaimStr = JSON.stringify(activeChannelClaim);
|
||||
// Editing content info
|
||||
const fileMimeType =
|
||||
myClaimForUri && myClaimForUri.value && myClaimForUri.value.source
|
||||
|
@ -251,27 +223,6 @@ function PublishForm(props: Props) {
|
|||
|
||||
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(() => {
|
||||
if (!hasClaimedInitialRewards) {
|
||||
claimInitialRewards();
|
||||
|
@ -286,32 +237,6 @@ function PublishForm(props: Props) {
|
|||
}
|
||||
}, [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;
|
||||
|
||||
if (isClaimingInitialRewards) {
|
||||
|
@ -319,8 +244,6 @@ function PublishForm(props: Props) {
|
|||
} else if (publishing) {
|
||||
if (isStillEditing) {
|
||||
submitLabel = __('Saving...');
|
||||
} else if (isLivestreamMode) {
|
||||
submitLabel = __('Creating...');
|
||||
} else {
|
||||
submitLabel = __('Uploading...');
|
||||
}
|
||||
|
@ -329,8 +252,6 @@ function PublishForm(props: Props) {
|
|||
} else {
|
||||
if (isStillEditing) {
|
||||
submitLabel = __('Save');
|
||||
} else if (isLivestreamMode) {
|
||||
submitLabel = __('Create');
|
||||
} else {
|
||||
submitLabel = __('Upload');
|
||||
}
|
||||
|
@ -398,26 +319,20 @@ function PublishForm(props: Props) {
|
|||
}
|
||||
}, [editingURI, resolveUri]);
|
||||
|
||||
// set isMarkdownPost in publish form if so, also update isLivestreamPublish
|
||||
// set isMarkdownPost in publish form if so
|
||||
useEffect(() => {
|
||||
updatePublishForm({
|
||||
isMarkdownPost: mode === PUBLISH_MODES.POST,
|
||||
isLivestreamPublish: mode === PUBLISH_MODES.LIVESTREAM,
|
||||
});
|
||||
}, [mode, updatePublishForm]);
|
||||
|
||||
useEffect(() => {
|
||||
if (incognito) {
|
||||
updatePublishForm({ channel: undefined });
|
||||
|
||||
// Anonymous livestreams aren't supported
|
||||
if (isLivestreamMode) {
|
||||
setMode(PUBLISH_MODES.FILE);
|
||||
}
|
||||
} else if (activeChannelName) {
|
||||
updatePublishForm({ channel: activeChannelName });
|
||||
}
|
||||
}, [activeChannelName, incognito, updatePublishForm, isLivestreamMode]);
|
||||
}, [activeChannelName, incognito, updatePublishForm]);
|
||||
|
||||
// set mode based on urlParams 'type'
|
||||
useEffect(() => {
|
||||
|
@ -437,19 +352,10 @@ function PublishForm(props: Props) {
|
|||
setMode(PUBLISH_MODES.POST);
|
||||
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
|
||||
setMode(PUBLISH_MODES.FILE);
|
||||
}, [_uploadType, enableLivestream]);
|
||||
}, [_uploadType]);
|
||||
|
||||
// if we have a type urlparam, update it? necessary?
|
||||
useEffect(() => {
|
||||
|
@ -509,7 +415,7 @@ function PublishForm(props: Props) {
|
|||
}
|
||||
}
|
||||
// Publish file
|
||||
if (mode === PUBLISH_MODES.FILE || isLivestreamMode) {
|
||||
if (mode === PUBLISH_MODES.FILE) {
|
||||
runPublish = true;
|
||||
}
|
||||
|
||||
|
@ -555,7 +461,7 @@ function PublishForm(props: Props) {
|
|||
// Editing claim uri
|
||||
return (
|
||||
<div className="card-stack">
|
||||
<ChannelSelect hideAnon={isLivestreamMode} disabled={disabled} />
|
||||
<ChannelSelect disabled={disabled} />
|
||||
|
||||
<PublishFile
|
||||
uri={permanentUrl}
|
||||
|
@ -565,13 +471,9 @@ function PublishForm(props: Props) {
|
|||
inProgress={isInProgress}
|
||||
setPublishMode={setMode}
|
||||
setPrevFileText={setPrevFileText}
|
||||
livestreamData={livestreamData}
|
||||
subtitle={customSubtitle}
|
||||
setWaitForFile={setWaitForFile}
|
||||
isCheckingLivestreams={isCheckingLivestreams}
|
||||
checkLivestreams={fetchLivestreams}
|
||||
channelId={claimChannelId}
|
||||
channelSignature={signedMessage}
|
||||
header={
|
||||
<>
|
||||
{AVAILABLE_MODES.map((modeName) => (
|
||||
|
@ -595,7 +497,7 @@ function PublishForm(props: Props) {
|
|||
{!publishing && (
|
||||
<div className={classnames({ 'card--disabled': formDisabled })}>
|
||||
{mode !== PUBLISH_MODES.POST && <PublishDescription disabled={formDisabled} />}
|
||||
<Card actions={<SelectThumbnail livestreamdData={livestreamData} />} />
|
||||
<Card actions={<SelectThumbnail />} />
|
||||
<TagsSelect
|
||||
suggestMature
|
||||
disableAutoFocus
|
||||
|
@ -624,7 +526,7 @@ function PublishForm(props: Props) {
|
|||
/>
|
||||
|
||||
<PublishBid disabled={isStillEditing || formDisabled} />
|
||||
{!isLivestreamMode && <PublishPrice disabled={formDisabled} />}
|
||||
<PublishPrice disabled={formDisabled} />
|
||||
<PublishAdditionalOptions disabled={formDisabled} />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -46,8 +46,6 @@ import InvitedPage from 'page/invited';
|
|||
import LibraryPage from 'page/library';
|
||||
import ListBlockedPage from 'page/listBlocked';
|
||||
import ListsPage from 'page/lists';
|
||||
import LiveStreamSetupPage from 'page/livestreamSetup';
|
||||
import LivestreamCurrentPage from 'page/livestreamCurrent';
|
||||
import OwnComments from 'page/ownComments';
|
||||
import PasswordResetPage from 'page/passwordReset';
|
||||
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.WALLET}`} exact component={WalletPage} />
|
||||
<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.RECEIVE}`} component={ReceivePage} />
|
||||
<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_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
|
||||
export const FETCH_BLACK_LISTED_CONTENT_STARTED = 'FETCH_BLACK_LISTED_CONTENT_STARTED';
|
||||
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_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_REPORT_CONTENT_DETAILS = 500;
|
||||
export const FF_MAX_CHARS_REPORT_CONTENT_ADDRESS = 255;
|
||||
|
|
|
@ -158,9 +158,6 @@ export const CHEESE = 'Cheese';
|
|||
export const PEACE = 'Peace';
|
||||
export const PORK_BUN = 'PorkBun';
|
||||
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 TIME = 'time';
|
||||
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.INVITE]: 'Invite',
|
||||
[PAGES.LISTS]: 'Lists',
|
||||
[PAGES.LIVESTREAM]: 'Go Live on Odysee',
|
||||
[PAGES.LIVESTREAM_CURRENT]: 'Live (Experimental)',
|
||||
[PAGES.NOTIFICATIONS]: 'Notifications',
|
||||
[PAGES.RECEIVE]: 'Your address',
|
||||
[PAGES.REPORT]: 'Report an issue or request a feature',
|
||||
|
|
|
@ -73,7 +73,5 @@ exports.SWAP = 'swap';
|
|||
exports.CHANNEL_NEW = 'channel/new';
|
||||
exports.NOTIFICATIONS = 'notifications';
|
||||
exports.YOUTUBE_SYNC = 'youtube';
|
||||
exports.LIVESTREAM = 'livestream';
|
||||
exports.LIVESTREAM_CURRENT = 'live';
|
||||
exports.GENERAL = 'general';
|
||||
exports.LIST = 'list';
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export const FILE = 'file';
|
||||
export const POST = 'post';
|
||||
export const LIVESTREAM = 'livestream';
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import ModalPublishSuccess from './view';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { doClearPublish } from 'redux/actions/publish';
|
||||
import { push } from 'connected-react-router';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
closeModal: () => dispatch(doHideModal()),
|
||||
clearPublish: () => dispatch(doClearPublish()),
|
||||
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,
|
||||
filePath: ?string,
|
||||
lbryFirstError: ?string,
|
||||
claim: Claim,
|
||||
};
|
||||
|
||||
class ModalPublishSuccess extends React.PureComponent<Props> {
|
||||
|
@ -24,13 +23,10 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
|||
clearPublish();
|
||||
}
|
||||
render() {
|
||||
const { closeModal, clearPublish, navigate, uri, isEdit, filePath, lbryFirstError, claim } = this.props;
|
||||
const { closeModal, clearPublish, navigate, uri, isEdit, filePath, lbryFirstError } = this.props;
|
||||
// $FlowFixMe
|
||||
const livestream = claim && claim.value && claim.value_type === 'stream' && !claim.value.source;
|
||||
let contentLabel;
|
||||
if (livestream) {
|
||||
contentLabel = __('Livestream Created');
|
||||
} else if (isEdit) {
|
||||
if (isEdit) {
|
||||
contentLabel = __('Update published');
|
||||
} else {
|
||||
contentLabel = __('File published');
|
||||
|
@ -39,10 +35,6 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
|||
let publishMessage;
|
||||
if (isEdit) {
|
||||
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 {
|
||||
publishMessage = __('Your content will be live shortly.');
|
||||
}
|
||||
|
@ -54,7 +46,7 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
|||
return (
|
||||
<Modal isOpen type="card" contentLabel={__(contentLabel)} onAborted={handleClose}>
|
||||
<Card
|
||||
title={livestream ? __('Livestream Created') : __('Success')}
|
||||
title={__('Success')}
|
||||
subtitle={publishMessage}
|
||||
body={
|
||||
<React.Fragment>
|
||||
|
@ -75,7 +67,6 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
|||
}
|
||||
actions={
|
||||
<div className="section__actions">
|
||||
{!livestream && (
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('View My Uploads')}
|
||||
|
@ -85,18 +76,6 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
|
|||
closeModal();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{livestream && (
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('View My Dashboard')}
|
||||
onClick={() => {
|
||||
clearPublish();
|
||||
navigate(`/$/${PAGES.LIVESTREAM}`);
|
||||
closeModal();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button button="link" label={__('Close')} onClick={handleClose} />
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -2,26 +2,21 @@ import { connect } from 'react-redux';
|
|||
import { doHideModal } from 'redux/actions/app';
|
||||
import ModalPublishPreview from './view';
|
||||
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 { selectFfmpegStatus, makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doPublishDesktop } from 'redux/actions/publish';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
|
||||
const select = (state, props) => {
|
||||
const editingUri = makeSelectPublishFormValue('editingURI')(state);
|
||||
|
||||
const select = (state) => {
|
||||
return {
|
||||
...selectPublishFormValues(state),
|
||||
myChannels: selectMyChannelClaims(state),
|
||||
isVid: makeSelectPublishFormValue('fileVid')(state),
|
||||
publishSuccess: makeSelectPublishFormValue('publishSuccess')(state),
|
||||
publishing: makeSelectPublishFormValue('publishing')(state),
|
||||
remoteFile: makeSelectPublishFormValue('remoteFileUrl')(state),
|
||||
isStillEditing: selectIsStillEditing(state),
|
||||
ffmpegStatus: selectFfmpegStatus(state),
|
||||
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
|
||||
isLivestreamClaim: makeSelectClaimIsStreamPlaceholder(editingUri)(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ type Props = {
|
|||
myChannels: ?Array<ChannelClaim>,
|
||||
publishSuccess: boolean,
|
||||
publishing: boolean,
|
||||
isLivestreamClaim: boolean,
|
||||
remoteFile: string,
|
||||
};
|
||||
|
||||
|
@ -75,12 +74,9 @@ const ModalPublishPreview = (props: Props) => {
|
|||
setEnablePublishPreview,
|
||||
isStillEditing,
|
||||
myChannels,
|
||||
publishSuccess,
|
||||
publishing,
|
||||
publish,
|
||||
closeModal,
|
||||
isLivestreamClaim,
|
||||
remoteFile,
|
||||
} = props;
|
||||
|
||||
const maxCharsBeforeOverflow = 128;
|
||||
|
@ -99,20 +95,7 @@ const ModalPublishPreview = (props: Props) => {
|
|||
return 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
|
||||
// @if TARGET='web'
|
||||
React.useEffect(() => {
|
||||
if (publishing && !livestream) {
|
||||
closeModal();
|
||||
} else if (publishSuccess) {
|
||||
closeModal();
|
||||
}
|
||||
}, [publishSuccess, publishing, livestream]);
|
||||
// @endif
|
||||
function onConfirmed() {
|
||||
// Publish for real:
|
||||
publish(getFilePathName(filePath), false);
|
||||
|
@ -147,13 +130,7 @@ const ModalPublishPreview = (props: Props) => {
|
|||
const isOptimizeAvail = filePath && filePath !== '' && isVid && ffmpegStatus.available;
|
||||
let modalTitle;
|
||||
if (isStillEditing) {
|
||||
if (livestream) {
|
||||
modalTitle = __('Confirm Update');
|
||||
} else {
|
||||
modalTitle = __('Confirm Edit');
|
||||
}
|
||||
} else if (livestream) {
|
||||
modalTitle = __('Create Livestream');
|
||||
} else {
|
||||
modalTitle = __('Confirm Upload');
|
||||
}
|
||||
|
@ -162,16 +139,12 @@ const ModalPublishPreview = (props: Props) => {
|
|||
if (!publishing) {
|
||||
if (isStillEditing) {
|
||||
confirmBtnText = __('Save');
|
||||
} else if (livestream) {
|
||||
confirmBtnText = __('Create');
|
||||
} else {
|
||||
confirmBtnText = __('Upload');
|
||||
}
|
||||
} else {
|
||||
if (isStillEditing) {
|
||||
confirmBtnText = __('Saving');
|
||||
} else if (livestream) {
|
||||
confirmBtnText = __('Creating');
|
||||
} else {
|
||||
confirmBtnText = __('Uploading');
|
||||
}
|
||||
|
@ -240,8 +213,7 @@ const ModalPublishPreview = (props: Props) => {
|
|||
<div className="section">
|
||||
<table className="table table--condensed table--publish-preview">
|
||||
<tbody>
|
||||
{!livestream && !isMarkdownPost && createRow(__('File'), getFilePathName(filePath))}
|
||||
{livestream && remoteFile && createRow(__('Replay'), __('Remote File Selected'))}
|
||||
{!isMarkdownPost && createRow(__('File'), getFilePathName(filePath))}
|
||||
{isOptimizeAvail && createRow(__('Transcode'), optimize ? __('Yes') : __('No'))}
|
||||
{createRow(__('Title'), formattedTitle)}
|
||||
{createRow(__('Description'), descriptionValue)}
|
||||
|
|
|
@ -3,8 +3,6 @@ import { connect } from 'react-redux';
|
|||
import { doResolveUri } from 'redux/actions/claims';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
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 { selectFollowedTags } from 'redux/selectors/tags';
|
||||
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
||||
|
@ -22,12 +20,10 @@ const select = (state, props) => {
|
|||
repostedClaim: repostedUri ? makeSelectClaimForUri(repostedUri)(state) : null,
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
|
||||
activeLivestreams: selectActiveLivestreams(state),
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(select, {
|
||||
doToggleTagFollowDesktop,
|
||||
doResolveUri,
|
||||
doFetchActiveLivestreams,
|
||||
})(Tags);
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
makeSelectMetadataForUri,
|
||||
makeSelectClaimIsNsfw,
|
||||
makeSelectTagInClaimOrChannelForUri,
|
||||
makeSelectClaimIsStreamPlaceholder,
|
||||
} from 'redux/selectors/claims';
|
||||
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
||||
import { doFetchFileInfo } from 'redux/actions/file_info';
|
||||
|
@ -34,7 +33,6 @@ const select = (state, props) => {
|
|||
renderMode: makeSelectFileRenderModeForUri(props.uri)(state),
|
||||
videoTheaterMode: makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state),
|
||||
commentsDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state),
|
||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
||||
collection: makeSelectCollectionForId(collectionId)(state),
|
||||
collectionId,
|
||||
position: makeSelectContentPositionForUri(props.uri)(state),
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import * as React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Page from 'component/page';
|
||||
|
@ -11,8 +10,6 @@ import FileRenderDownload from 'component/fileRenderDownload';
|
|||
import RecommendedContent from 'component/recommendedContent';
|
||||
import CollectionContent from 'component/collectionContentSidebar';
|
||||
import CommentsList from 'component/commentsList';
|
||||
import Button from 'component/button';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import Empty from 'component/common/empty';
|
||||
|
||||
import PostViewer from 'component/postViewer';
|
||||
|
@ -36,7 +33,6 @@ type Props = {
|
|||
videoTheaterMode: boolean,
|
||||
claimIsMine: boolean,
|
||||
commentsDisabled: boolean,
|
||||
isLivestream: boolean,
|
||||
clearPosition: (string) => void,
|
||||
position: number,
|
||||
};
|
||||
|
@ -55,12 +51,9 @@ function FilePage(props: Props) {
|
|||
linkedCommentId,
|
||||
setPrimaryUri,
|
||||
videoTheaterMode,
|
||||
|
||||
claimIsMine,
|
||||
commentsDisabled,
|
||||
collection,
|
||||
collectionId,
|
||||
isLivestream,
|
||||
clearPosition,
|
||||
position,
|
||||
} = props;
|
||||
|
@ -190,18 +183,6 @@ function FilePage(props: Props) {
|
|||
{!isMarkdown && (
|
||||
<div className="file-page__secondary-content">
|
||||
<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} />}
|
||||
{commentsDisabled && <Empty text={__('The creator of this content has disabled comments.')} />}
|
||||
{!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) => {
|
||||
const contentClaim = claimsById[comment.claim_id];
|
||||
const isChannel = contentClaim && contentClaim.value_type === 'channel';
|
||||
const isLivestream = Boolean(contentClaim && contentClaim.value_type === 'stream' && !contentClaim.value.source);
|
||||
|
||||
return (
|
||||
<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}
|
||||
searchParams={{
|
||||
...(isChannel ? { view: 'discussion' } : {}),
|
||||
...(isLivestream ? {} : { lc: comment.comment_id }),
|
||||
}}
|
||||
hideActions
|
||||
hideMenu
|
||||
|
|
|
@ -385,11 +385,11 @@ export default function SettingsCreatorPage(props: Props) {
|
|||
|
||||
// prettier-ignore
|
||||
const HELP = {
|
||||
SLOW_MODE: 'Minimum time gap in seconds between comments (affects livestream chat as well).',
|
||||
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.',
|
||||
SLOW_MODE: 'Minimum time gap in seconds between comments.',
|
||||
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_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.',
|
||||
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,
|
||||
makeSelectClaimIsMine,
|
||||
makeSelectClaimIsPending,
|
||||
makeSelectClaimIsStreamPlaceholder,
|
||||
} from 'redux/selectors/claims';
|
||||
import {
|
||||
makeSelectCollectionForId,
|
||||
|
@ -79,7 +78,6 @@ const select = (state, props) => {
|
|||
title: makeSelectTitleForUri(uri)(state),
|
||||
claimIsMine: makeSelectClaimIsMine(uri)(state),
|
||||
claimIsPending: makeSelectClaimIsPending(uri)(state),
|
||||
isLivestream: makeSelectClaimIsStreamPlaceholder(uri)(state),
|
||||
collection: makeSelectCollectionForId(collectionId)(state),
|
||||
collectionId: collectionId,
|
||||
collectionUrls: makeSelectUrlsForCollectionId(collectionId)(state),
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// @flow
|
||||
import { ENABLE_NO_SOURCE_CLAIMS } from 'config';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Redirect, useHistory } from 'react-router-dom';
|
||||
|
@ -14,7 +13,6 @@ import * as COLLECTIONS_CONSTS from 'constants/collections';
|
|||
|
||||
import AbandonedChannelPreview from 'component/abandonedChannelPreview';
|
||||
import FilePage from 'page/file';
|
||||
import LivestreamPage from 'page/livestream';
|
||||
import Yrbl from 'component/yrbl';
|
||||
|
||||
type Props = {
|
||||
|
@ -31,7 +29,6 @@ type Props = {
|
|||
title: string,
|
||||
claimIsMine: boolean,
|
||||
claimIsPending: boolean,
|
||||
isLivestream: boolean,
|
||||
beginPublish: (?string) => void,
|
||||
collectionId: string,
|
||||
collection: Collection,
|
||||
|
@ -51,7 +48,6 @@ function ShowPage(props: Props) {
|
|||
claimIsMine,
|
||||
isSubscribed,
|
||||
claimIsPending,
|
||||
isLivestream,
|
||||
beginPublish,
|
||||
fetchCollectionItems,
|
||||
collectionId,
|
||||
|
@ -175,8 +171,6 @@ function ShowPage(props: Props) {
|
|||
/>
|
||||
</Page>
|
||||
);
|
||||
} else if (isLivestream && ENABLE_NO_SOURCE_CLAIMS) {
|
||||
innerContent = <LivestreamPage uri={uri} />;
|
||||
} else {
|
||||
innerContent = <FilePage uri={uri} location={location} />;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import userReducer from 'redux/reducers/user';
|
|||
import commentsReducer from 'redux/reducers/comments';
|
||||
import blockedReducer from 'redux/reducers/blocked';
|
||||
import coinSwapReducer from 'redux/reducers/coinSwap';
|
||||
import livestreamReducer from 'redux/reducers/livestream';
|
||||
import searchReducer from 'redux/reducers/search';
|
||||
import reactionsReducer from 'redux/reducers/reactions';
|
||||
import syncReducer from 'redux/reducers/sync';
|
||||
|
@ -34,7 +33,6 @@ export default (history) =>
|
|||
content: contentReducer,
|
||||
costInfo: costInfoReducer,
|
||||
fileInfo: fileInfoReducer,
|
||||
livestream: livestreamReducer,
|
||||
notifications: notificationsReducer,
|
||||
publish: publishReducer,
|
||||
reactions: reactionsReducer,
|
||||
|
|
|
@ -552,7 +552,6 @@ export function doCommentReact(commentId: string, type: string) {
|
|||
* @param claim_id - File claim id
|
||||
* @param parent_id - What is this?
|
||||
* @param uri
|
||||
* @param livestream
|
||||
* @param {string} [txid] Optional transaction id
|
||||
* @param {string} [payment_intent_id] Optional transaction id
|
||||
* @param {string} [environment] Optional environment for Stripe (test|live)
|
||||
|
@ -563,7 +562,6 @@ export function doCommentCreate(
|
|||
claim_id: string = '',
|
||||
parent_id?: string,
|
||||
uri: string,
|
||||
livestream?: boolean = false,
|
||||
txid?: string,
|
||||
payment_intent_id?: string,
|
||||
environment?: string
|
||||
|
@ -624,7 +622,6 @@ export function doCommentCreate(
|
|||
type: ACTIONS.COMMENT_CREATE_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
livestream,
|
||||
comment: result,
|
||||
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 { batchActions } from 'util/batch-actions';
|
||||
import { doCheckPendingClaims } from 'redux/actions/claims';
|
||||
import {
|
||||
makeSelectClaimForUri,
|
||||
selectMyClaims,
|
||||
selectMyChannelClaims,
|
||||
// selectMyClaimsWithoutChannels,
|
||||
selectReflectingById,
|
||||
} from 'redux/selectors/claims';
|
||||
import { makeSelectPublishFormValue, selectPublishFormValues, selectMyClaimForUri } from 'redux/selectors/publish';
|
||||
import { selectMyClaims, selectMyChannelClaims, selectReflectingById } from 'redux/selectors/claims';
|
||||
import { selectPublishFormValues, selectMyClaimForUri } from 'redux/selectors/publish';
|
||||
import { doError } from 'redux/actions/notifications';
|
||||
import { push } from 'connected-react-router';
|
||||
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 { creditsToString } from 'util/format-credits';
|
||||
import Lbry from 'lbry';
|
||||
// import LbryFirst from 'extras/lbry-first/lbry-first';
|
||||
import { isClaimNsfw } from 'util/claim';
|
||||
|
||||
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 state = getState();
|
||||
const myClaims = selectMyClaims(state);
|
||||
|
@ -87,14 +72,7 @@ export const doPublishDesktop = (filePath: string, preview?: boolean) => (dispat
|
|||
})
|
||||
);
|
||||
dispatch(doCheckPendingClaims());
|
||||
// @if TARGET='app'
|
||||
dispatch(doCheckReflectingFiles());
|
||||
// @endif
|
||||
// @if TARGET='web'
|
||||
if (redirectToLivestream) {
|
||||
dispatch(push(`/$/${PAGES.LIVESTREAM}`));
|
||||
}
|
||||
// @endif
|
||||
};
|
||||
|
||||
const publishFail = (error) => {
|
||||
|
@ -111,15 +89,6 @@ export const doPublishDesktop = (filePath: string, preview?: boolean) => (dispat
|
|||
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));
|
||||
};
|
||||
|
||||
|
@ -381,12 +350,9 @@ export const doPublish = (success: Function, fail: Function, preview: Function)
|
|||
title,
|
||||
contentIsFree,
|
||||
fee,
|
||||
// uri,
|
||||
tags,
|
||||
// locations,
|
||||
optimize,
|
||||
isLivestreamPublish,
|
||||
remoteFileUrl,
|
||||
} = publishData;
|
||||
|
||||
// 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
|
||||
// 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) {
|
||||
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.
|
||||
// The sdk will figure it out
|
||||
if (filePath && !isLivestreamPublish) publishPayload.file_path = filePath;
|
||||
if (filePath) publishPayload.file_path = filePath;
|
||||
|
||||
if (preview) {
|
||||
publishPayload.preview = true;
|
||||
|
@ -502,22 +464,7 @@ export const doPublish = (success: Function, fail: Function, preview: Function)
|
|||
}
|
||||
|
||||
return Lbry.publish(publishPayload).then((response: PublishResponse) => {
|
||||
// TODO: Restore LbryFirst
|
||||
// if (!useLBRYUploader) {
|
||||
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);
|
||||
};
|
||||
|
||||
|
|
|
@ -103,13 +103,6 @@ export const doCommentSocketConnect = (uri, claimId) => (dispatch) => {
|
|||
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') {
|
||||
const pinnedComment = response.data.comment;
|
||||
dispatch({
|
||||
|
|
|
@ -71,12 +71,7 @@ export default handleActions(
|
|||
}),
|
||||
|
||||
[ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
|
||||
const {
|
||||
comment,
|
||||
claimId,
|
||||
uri,
|
||||
livestream,
|
||||
}: { comment: Comment, claimId: string, uri: string, livestream: boolean } = action.data;
|
||||
const { comment, claimId, uri }: { comment: Comment, claimId: string, uri: string } = action.data;
|
||||
|
||||
const commentById = Object.assign({}, state.commentById);
|
||||
const byId = Object.assign({}, state.byId);
|
||||
|
@ -87,8 +82,6 @@ export default handleActions(
|
|||
const comments = byId[claimId] || [];
|
||||
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
|
||||
commentById[comment.comment_id] = comment;
|
||||
|
||||
|
@ -119,7 +112,6 @@ export default handleActions(
|
|||
topLevelCommentsById[claimId].unshift(comment.comment_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...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/syntax-highlighter';
|
||||
@import 'component/table';
|
||||
@import 'component/livestream';
|
||||
@import 'component/tabs';
|
||||
@import 'component/tooltip';
|
||||
@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 {
|
||||
@extend .main;
|
||||
|
||||
|
|
|
@ -97,8 +97,6 @@ $breakpoint-large: 1600px;
|
|||
--file-list-thumbnail-width: 10rem;
|
||||
|
||||
--tag-height: 1.5rem;
|
||||
|
||||
--livestream-comments-width: 30rem;
|
||||
}
|
||||
|
||||
@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…
Reference in a new issue