diff --git a/flow-typed/livestream.js b/flow-typed/livestream.js index 9b1b2d30a..75218e035 100644 --- a/flow-typed/livestream.js +++ b/flow-typed/livestream.js @@ -20,3 +20,7 @@ declare type LivestreamReplayItem = { id: string, } declare type LivestreamReplayData = Array; + +declare type LivestreamState = { + idsFetching: {}, +} diff --git a/ui/constants/action_types.js b/ui/constants/action_types.js index cdf06dd79..ad79ca69b 100644 --- a/ui/constants/action_types.js +++ b/ui/constants/action_types.js @@ -324,3 +324,8 @@ export const REACTIONS_DISLIKE_COMPLETED = 'REACTIONS_DISLIKE_COMPLETED'; 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'; diff --git a/ui/page/livestreamSetup/index.js b/ui/page/livestreamSetup/index.js index 8cdf6a579..cbb092976 100644 --- a/ui/page/livestreamSetup/index.js +++ b/ui/page/livestreamSetup/index.js @@ -1,19 +1,34 @@ import { connect } from 'react-redux'; -import { selectMyChannelClaims, selectFetchingMyChannels, selectPendingClaims, doClearPublish } from 'lbry-redux'; +import { selectMyChannelClaims, selectFetchingMyChannels, doClearPublish } from 'lbry-redux'; 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) => ({ - channels: selectMyChannelClaims(state), - fetchingChannels: selectFetchingMyChannels(state), - activeChannelClaim: selectActiveChannelClaim(state), - pendingClaims: selectPendingClaims(state), -}); +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); diff --git a/ui/page/livestreamSetup/view.jsx b/ui/page/livestreamSetup/view.jsx index 81c0a8552..e3f5d5718 100644 --- a/ui/page/livestreamSetup/view.jsx +++ b/ui/page/livestreamSetup/view.jsx @@ -16,7 +16,6 @@ import CopyableText from 'component/copyableText'; import Card from 'component/common/card'; import ClaimList from 'component/claimList'; import usePersistedState from 'effects/use-persisted-state'; -import usePrevious from 'effects/use-previous'; type Props = { channels: Array, @@ -24,47 +23,43 @@ type Props = { activeChannelClaim: ?ChannelClaim, pendingClaims: Array, doNewLivestream: (string) => void, + fetchNoSourceClaims: (string) => void, + myLivestreamClaims: Array, + 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 } = props; + const { + channels, + fetchingChannels, + activeChannelClaim, + pendingClaims, + doNewLivestream, + fetchNoSourceClaims, + myLivestreamClaims, + fetchingLivestreams, + channelId, + channelName, + } = props; const [sigData, setSigData] = React.useState({ signature: undefined, signing_ts: undefined }); - const [showHelpTest, setShowHelpTest] = usePersistedState('livestream-help-seen', true); - const [spin, setSpin] = React.useState(true); - const [livestreamClaims, setLivestreamClaims] = React.useState([]); + const [showHelp, setShowHelp] = usePersistedState('livestream-help-seen', true); const hasChannels = channels && channels.length > 0; - const activeChannelClaimStr = JSON.stringify(activeChannelClaim); + const hasLivestreamClaims = Boolean(myLivestreamClaims.length || pendingClaims.length); function createStreamKey() { - if (!activeChannelClaim || !sigData.signature || !sigData.signing_ts) return null; - return `${activeChannelClaim.claim_id}?d=${toHex(activeChannelClaim.name)}&s=${sigData.signature}&t=${ - sigData.signing_ts - }`; + 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 pendingLiveStreamClaims = pendingClaims - ? pendingClaims.filter( - (claim) => - // $FlowFixMe - claim.value_type === 'stream' && !(claim.value && claim.value.source) - ) - : []; - const [localPending, setLocalPending] = React.useState([]); // - const localPendingStr = JSON.stringify(localPending); - const pendingLivestreamClaimsStr = JSON.stringify(pendingLiveStreamClaims); - const prevPendingLiveStreamClaimStr = usePrevious(pendingLivestreamClaimsStr); - const liveStreamClaimsStr = JSON.stringify(livestreamClaims); - const prevLiveStreamClaimsStr = JSON.stringify(liveStreamClaimsStr); - const pendingLength = pendingLiveStreamClaims.length; - const totalLivestreamClaims = pendingLiveStreamClaims.concat(livestreamClaims); - const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id; - const localPendingForChannel = localPending.filter( - (claim) => claim.signing_channel && claim.signing_channel.claim_id === activeChannelId - ); + + const pendingLength = pendingClaims.length; + const totalLivestreamClaims = pendingClaims.concat(myLivestreamClaims); const helpText = (

@@ -104,112 +99,41 @@ export default function LivestreamSetupPage(props: Props) { ); React.useEffect(() => { - if (activeChannelClaimStr) { - const channelClaim = JSON.parse(activeChannelClaimStr); - - // ensure we have a channel - if (channelClaim.claim_id) { - Lbry.channel_sign({ - channel_id: channelClaim.claim_id, - hexdata: toHex(channelClaim.name), - }) - .then((data) => { - setSigData(data); - }) - .catch((error) => { - setSigData({ signature: null, signing_ts: null }); - }); - } - } - }, [activeChannelClaimStr, setSigData]); - - // The following 2 effects handle the time between pending disappearing and claim_search being able to find it. - // We'll maintain our own pending list: - // add to it when there are new things in pending - // remove items only when our claim_search finds it - React.useEffect(() => { - // add to localPending when pending changes - const localPending = JSON.parse(localPendingStr); - const pendingLivestreamClaims = JSON.parse(pendingLivestreamClaimsStr); - if ( - pendingLiveStreamClaims !== prevPendingLiveStreamClaimStr || - (pendingLivestreamClaims.length && !localPending.length) - ) { - const prevPendingLivestreamClaims = prevPendingLiveStreamClaimStr - ? JSON.parse(prevPendingLiveStreamClaimStr) - : []; - const pendingClaimIds = pendingLivestreamClaims.map((claim) => claim.claim_id); - const prevPendingClaimIds = prevPendingLivestreamClaims.map((claim) => claim.claim_id); - const newLocalPending = []; - if (pendingClaimIds.length > prevPendingClaimIds.length) { - pendingLivestreamClaims.forEach((pendingClaim) => { - if (!localPending.some((lClaim) => lClaim.claim_id === pendingClaim.claim_id)) { - newLocalPending.push(pendingClaim); - } - }); - setLocalPending(localPending.concat(newLocalPending)); - } - } - }, [pendingLivestreamClaimsStr, prevPendingLiveStreamClaimStr, localPendingStr, setLocalPending]); - - React.useEffect(() => { - // remove from localPending when livestreamClaims found - const localPending = JSON.parse(localPendingStr); - if (liveStreamClaimsStr !== prevLiveStreamClaimsStr && localPending.length) { - const livestreamClaims = JSON.parse(liveStreamClaimsStr); - setLocalPending( - localPending.filter((pending) => !livestreamClaims.some((claim) => claim.claim_id === pending.claim_id)) - ); - } - }, [liveStreamClaimsStr, prevLiveStreamClaimsStr, localPendingStr, setLocalPending]); - - const checkLivestreams = React.useCallback( - function checkLivestreamClaims(channelClaimId, setLivestreamClaims, setSpin) { - Lbry.claim_search({ - channel_ids: [channelClaimId], - has_no_source: true, - claim_type: ['stream'], - include_purchase_receipt: true, + // ensure we have a channel + if (channelId && channelName) { + Lbry.channel_sign({ + channel_id: channelId, + hexdata: toHex(channelName), }) - .then((res) => { - if (res && res.items && res.items.length > 0) { - setLivestreamClaims(res.items.reverse()); - } else { - setLivestreamClaims([]); - } - setSpin(false); + .then((data) => { + setSigData(data); }) - .catch(() => { - setLivestreamClaims([]); - setSpin(false); + .catch((error) => { + setSigData({ signature: null, signing_ts: null }); }); - }, - [activeChannelId] - ); + } + }, [channelName, channelId, setSigData]); + React.useEffect(() => { let checkClaimsInterval; - if (!activeChannelClaimStr) return; - const channelClaim = JSON.parse(activeChannelClaimStr); + if (!channelId) return; if (!checkClaimsInterval) { - checkLivestreams(channelClaim.claim_id, setLivestreamClaims, setSpin); - checkClaimsInterval = setInterval( - () => checkLivestreams(channelClaim.claim_id, setLivestreamClaims, setSpin), - LIVESTREAM_CLAIM_POLL_IN_MS - ); + fetchNoSourceClaims(channelId); + checkClaimsInterval = setInterval(() => fetchNoSourceClaims(channelId), LIVESTREAM_CLAIM_POLL_IN_MS); } return () => { if (checkClaimsInterval) { clearInterval(checkClaimsInterval); } }; - }, [activeChannelClaimStr, pendingLength, setSpin, checkLivestreams]); + }, [channelId, pendingLength, fetchNoSourceClaims]); return ( - {(fetchingChannels || spin) && ( + {fetchingChannels && (

- +
)} @@ -227,21 +151,21 @@ export default function LivestreamSetupPage(props: Props) { {!fetchingChannels && (
-
)} - {spin && !fetchingChannels && ( + {fetchingLivestreams && !fetchingChannels && !hasLivestreamClaims && (
- +
)}
- {!spin && !fetchingChannels && activeChannelClaim && ( + {hasLivestreamClaims && !fetchingChannels && channelId && ( <> - {showHelpTest && ( + {showHelp && ( setShowHelpTest(false)} />} + titleActions={