use redux for livestream claim setup
This commit is contained in:
parent
b0193202d1
commit
f3463ebdeb
8 changed files with 196 additions and 140 deletions
4
flow-typed/livestream.js
vendored
4
flow-typed/livestream.js
vendored
|
@ -20,3 +20,7 @@ declare type LivestreamReplayItem = {
|
||||||
id: string,
|
id: string,
|
||||||
}
|
}
|
||||||
declare type LivestreamReplayData = Array<LivestreamReplayItem>;
|
declare type LivestreamReplayData = Array<LivestreamReplayItem>;
|
||||||
|
|
||||||
|
declare type LivestreamState = {
|
||||||
|
idsFetching: {},
|
||||||
|
}
|
||||||
|
|
|
@ -324,3 +324,8 @@ export const REACTIONS_DISLIKE_COMPLETED = 'REACTIONS_DISLIKE_COMPLETED';
|
||||||
export const REPORT_CONTENT_STARTED = 'REPORT_CONTENT_STARTED';
|
export const REPORT_CONTENT_STARTED = 'REPORT_CONTENT_STARTED';
|
||||||
export const REPORT_CONTENT_COMPLETED = 'REPORT_CONTENT_COMPLETED';
|
export const REPORT_CONTENT_COMPLETED = 'REPORT_CONTENT_COMPLETED';
|
||||||
export const REPORT_CONTENT_FAILED = 'REPORT_CONTENT_FAILED';
|
export const REPORT_CONTENT_FAILED = 'REPORT_CONTENT_FAILED';
|
||||||
|
|
||||||
|
// Livestream
|
||||||
|
export const FETCH_NO_SOURCE_CLAIMS_STARTED = 'FETCH_NO_SOURCE_CLAIMS_STARTED';
|
||||||
|
export const FETCH_NO_SOURCE_CLAIMS_COMPLETED = 'FETCH_NO_SOURCE_CLAIMS_COMPLETED';
|
||||||
|
export const FETCH_NO_SOURCE_CLAIMS_FAILED = 'FETCH_NO_SOURCE_CLAIMS_FAILED';
|
||||||
|
|
|
@ -1,19 +1,34 @@
|
||||||
import { connect } from 'react-redux';
|
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 { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
|
import { doFetchNoSourceClaims } from 'redux/actions/livestream';
|
||||||
|
import {
|
||||||
|
makeSelectPendingLivestreamsForChannelId,
|
||||||
|
makeSelectLivestreamsForChannelId,
|
||||||
|
makeSelectIsFetchingLivestreams,
|
||||||
|
} from 'redux/selectors/livestream';
|
||||||
import LivestreamSetupPage from './view';
|
import LivestreamSetupPage from './view';
|
||||||
import { push } from 'connected-react-router';
|
import { push } from 'connected-react-router';
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = (state) => {
|
||||||
channels: selectMyChannelClaims(state),
|
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||||
fetchingChannels: selectFetchingMyChannels(state),
|
const { claim_id: channelId, name: channelName } = activeChannelClaim || {};
|
||||||
activeChannelClaim: selectActiveChannelClaim(state),
|
return {
|
||||||
pendingClaims: selectPendingClaims(state),
|
channelName,
|
||||||
});
|
channelId,
|
||||||
|
channels: selectMyChannelClaims(state),
|
||||||
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
|
activeChannelClaim,
|
||||||
|
myLivestreamClaims: makeSelectLivestreamsForChannelId(channelId)(state),
|
||||||
|
pendingClaims: makeSelectPendingLivestreamsForChannelId(channelId)(state),
|
||||||
|
fetchingLivestreams: makeSelectIsFetchingLivestreams(channelId)(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
doNewLivestream: (path) => {
|
doNewLivestream: (path) => {
|
||||||
dispatch(doClearPublish());
|
dispatch(doClearPublish());
|
||||||
dispatch(push(path));
|
dispatch(push(path));
|
||||||
},
|
},
|
||||||
|
fetchNoSourceClaims: (id) => dispatch(doFetchNoSourceClaims(id)),
|
||||||
});
|
});
|
||||||
export default connect(select, perform)(LivestreamSetupPage);
|
export default connect(select, perform)(LivestreamSetupPage);
|
||||||
|
|
|
@ -16,7 +16,6 @@ import CopyableText from 'component/copyableText';
|
||||||
import Card from 'component/common/card';
|
import Card from 'component/common/card';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import usePrevious from 'effects/use-previous';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
channels: Array<ChannelClaim>,
|
channels: Array<ChannelClaim>,
|
||||||
|
@ -24,47 +23,43 @@ type Props = {
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
pendingClaims: Array<Claim>,
|
pendingClaims: Array<Claim>,
|
||||||
doNewLivestream: (string) => void,
|
doNewLivestream: (string) => void,
|
||||||
|
fetchNoSourceClaims: (string) => void,
|
||||||
|
myLivestreamClaims: Array<Claim>,
|
||||||
|
fetchingLivestreams: boolean,
|
||||||
|
channelId: ?string,
|
||||||
|
channelName: ?string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LivestreamSetupPage(props: Props) {
|
export default function LivestreamSetupPage(props: Props) {
|
||||||
const LIVESTREAM_CLAIM_POLL_IN_MS = 60000;
|
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 [sigData, setSigData] = React.useState({ signature: undefined, signing_ts: undefined });
|
||||||
const [showHelpTest, setShowHelpTest] = usePersistedState('livestream-help-seen', true);
|
const [showHelp, setShowHelp] = usePersistedState('livestream-help-seen', true);
|
||||||
const [spin, setSpin] = React.useState(true);
|
|
||||||
const [livestreamClaims, setLivestreamClaims] = React.useState([]);
|
|
||||||
|
|
||||||
const hasChannels = channels && channels.length > 0;
|
const hasChannels = channels && channels.length > 0;
|
||||||
const activeChannelClaimStr = JSON.stringify(activeChannelClaim);
|
const hasLivestreamClaims = Boolean(myLivestreamClaims.length || pendingClaims.length);
|
||||||
|
|
||||||
function createStreamKey() {
|
function createStreamKey() {
|
||||||
if (!activeChannelClaim || !sigData.signature || !sigData.signing_ts) return null;
|
if (!channelId || !channelName || !sigData.signature || !sigData.signing_ts) return null;
|
||||||
return `${activeChannelClaim.claim_id}?d=${toHex(activeChannelClaim.name)}&s=${sigData.signature}&t=${
|
return `${channelId}?d=${toHex(channelName)}&s=${sigData.signature}&t=${sigData.signing_ts}`;
|
||||||
sigData.signing_ts
|
|
||||||
}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const streamKey = createStreamKey();
|
const streamKey = createStreamKey();
|
||||||
const pendingLiveStreamClaims = pendingClaims
|
|
||||||
? pendingClaims.filter(
|
const pendingLength = pendingClaims.length;
|
||||||
(claim) =>
|
const totalLivestreamClaims = pendingClaims.concat(myLivestreamClaims);
|
||||||
// $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 helpText = (
|
const helpText = (
|
||||||
<div className="section__subtitle">
|
<div className="section__subtitle">
|
||||||
<p>
|
<p>
|
||||||
|
@ -104,112 +99,41 @@ export default function LivestreamSetupPage(props: Props) {
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (activeChannelClaimStr) {
|
// ensure we have a channel
|
||||||
const channelClaim = JSON.parse(activeChannelClaimStr);
|
if (channelId && channelName) {
|
||||||
|
Lbry.channel_sign({
|
||||||
// ensure we have a channel
|
channel_id: channelId,
|
||||||
if (channelClaim.claim_id) {
|
hexdata: toHex(channelName),
|
||||||
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,
|
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((data) => {
|
||||||
if (res && res.items && res.items.length > 0) {
|
setSigData(data);
|
||||||
setLivestreamClaims(res.items.reverse());
|
|
||||||
} else {
|
|
||||||
setLivestreamClaims([]);
|
|
||||||
}
|
|
||||||
setSpin(false);
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
setLivestreamClaims([]);
|
setSigData({ signature: null, signing_ts: null });
|
||||||
setSpin(false);
|
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
[activeChannelId]
|
}, [channelName, channelId, setSigData]);
|
||||||
);
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let checkClaimsInterval;
|
let checkClaimsInterval;
|
||||||
if (!activeChannelClaimStr) return;
|
if (!channelId) return;
|
||||||
const channelClaim = JSON.parse(activeChannelClaimStr);
|
|
||||||
|
|
||||||
if (!checkClaimsInterval) {
|
if (!checkClaimsInterval) {
|
||||||
checkLivestreams(channelClaim.claim_id, setLivestreamClaims, setSpin);
|
fetchNoSourceClaims(channelId);
|
||||||
checkClaimsInterval = setInterval(
|
checkClaimsInterval = setInterval(() => fetchNoSourceClaims(channelId), LIVESTREAM_CLAIM_POLL_IN_MS);
|
||||||
() => checkLivestreams(channelClaim.claim_id, setLivestreamClaims, setSpin),
|
|
||||||
LIVESTREAM_CLAIM_POLL_IN_MS
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
if (checkClaimsInterval) {
|
if (checkClaimsInterval) {
|
||||||
clearInterval(checkClaimsInterval);
|
clearInterval(checkClaimsInterval);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [activeChannelClaimStr, pendingLength, setSpin, checkLivestreams]);
|
}, [channelId, pendingLength, fetchNoSourceClaims]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
{(fetchingChannels || spin) && (
|
{fetchingChannels && (
|
||||||
<div className="main--empty">
|
<div className="main--empty">
|
||||||
<Spinner delayed />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -227,21 +151,21 @@ export default function LivestreamSetupPage(props: Props) {
|
||||||
{!fetchingChannels && (
|
{!fetchingChannels && (
|
||||||
<div className="section__actions--between">
|
<div className="section__actions--between">
|
||||||
<ChannelSelector hideAnon />
|
<ChannelSelector hideAnon />
|
||||||
<Button button="link" onClick={() => setShowHelpTest(!showHelpTest)} label={__('How does this work?')} />
|
<Button button="link" onClick={() => setShowHelp(!showHelp)} label={__('How does this work?')} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{spin && !fetchingChannels && (
|
{fetchingLivestreams && !fetchingChannels && !hasLivestreamClaims && (
|
||||||
<div className="main--empty">
|
<div className="main--empty">
|
||||||
<Spinner delayed />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="card-stack">
|
<div className="card-stack">
|
||||||
{!spin && !fetchingChannels && activeChannelClaim && (
|
{hasLivestreamClaims && !fetchingChannels && channelId && (
|
||||||
<>
|
<>
|
||||||
{showHelpTest && (
|
{showHelp && (
|
||||||
<Card
|
<Card
|
||||||
titleActions={<Button button="close" icon={ICONS.REMOVE} onClick={() => setShowHelpTest(false)} />}
|
titleActions={<Button button="close" icon={ICONS.REMOVE} onClick={() => setShowHelp(false)} />}
|
||||||
title={__('Go Live on Odysee')}
|
title={__('Go Live on Odysee')}
|
||||||
subtitle={__(`You're invited to try out our new livestreaming service while in beta!`)}
|
subtitle={__(`You're invited to try out our new livestreaming service while in beta!`)}
|
||||||
actions={helpText}
|
actions={helpText}
|
||||||
|
@ -274,15 +198,15 @@ export default function LivestreamSetupPage(props: Props) {
|
||||||
|
|
||||||
{totalLivestreamClaims.length > 0 ? (
|
{totalLivestreamClaims.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{Boolean(localPendingForChannel.length) && (
|
{Boolean(pendingClaims.length) && (
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<ClaimList
|
<ClaimList
|
||||||
header={__('Your pending livestream uploads')}
|
header={__('Your pending livestream uploads')}
|
||||||
uris={localPendingForChannel.map((claim) => claim.permanent_url)}
|
uris={pendingClaims.map((claim) => claim.permanent_url)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{Boolean(livestreamClaims.length) && (
|
{Boolean(myLivestreamClaims.length) && (
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<ClaimList
|
<ClaimList
|
||||||
header={__('Your livestream uploads')}
|
header={__('Your livestream uploads')}
|
||||||
|
@ -292,7 +216,7 @@ export default function LivestreamSetupPage(props: Props) {
|
||||||
check_again: (
|
check_again: (
|
||||||
<Button
|
<Button
|
||||||
button="link"
|
button="link"
|
||||||
onClick={() => checkLivestreams(activeChannelId, setLivestreamClaims, setSpin)}
|
onClick={() => fetchNoSourceClaims(channelId)}
|
||||||
label={__('Check again')}
|
label={__('Check again')}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
@ -301,8 +225,10 @@ export default function LivestreamSetupPage(props: Props) {
|
||||||
Nothing here yet. %check_again%
|
Nothing here yet. %check_again%
|
||||||
</I18nMessage>
|
</I18nMessage>
|
||||||
}
|
}
|
||||||
uris={livestreamClaims
|
uris={myLivestreamClaims
|
||||||
.filter((c) => !pendingLiveStreamClaims.some((p) => p.permanent_url === c.permanent_url))
|
.filter(
|
||||||
|
(claim) => !pendingClaims.some((pending) => pending.permanent_url === claim.permanent_url)
|
||||||
|
)
|
||||||
.map((claim) => claim.permanent_url)}
|
.map((claim) => claim.permanent_url)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -327,8 +253,7 @@ export default function LivestreamSetupPage(props: Props) {
|
||||||
<Button
|
<Button
|
||||||
button="alt"
|
button="alt"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSpin(true);
|
fetchNoSourceClaims(channelId);
|
||||||
checkLivestreams(activeChannelId, setLivestreamClaims, setSpin);
|
|
||||||
}}
|
}}
|
||||||
label={__('Check again...')}
|
label={__('Check again...')}
|
||||||
/>
|
/>
|
||||||
|
@ -338,7 +263,7 @@ export default function LivestreamSetupPage(props: Props) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Debug Stuff */}
|
{/* Debug Stuff */}
|
||||||
{streamKey && false && (
|
{streamKey && false && activeChannelClaim && (
|
||||||
<div style={{ marginTop: 'var(--spacing-l)' }}>
|
<div style={{ marginTop: 'var(--spacing-l)' }}>
|
||||||
<h3>Debug Info</h3>
|
<h3>Debug Info</h3>
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import userReducer from 'redux/reducers/user';
|
||||||
import commentsReducer from 'redux/reducers/comments';
|
import commentsReducer from 'redux/reducers/comments';
|
||||||
import blockedReducer from 'redux/reducers/blocked';
|
import blockedReducer from 'redux/reducers/blocked';
|
||||||
import coinSwapReducer from 'redux/reducers/coinSwap';
|
import coinSwapReducer from 'redux/reducers/coinSwap';
|
||||||
|
import livestreamReducer from 'redux/reducers/livestream';
|
||||||
import searchReducer from 'redux/reducers/search';
|
import searchReducer from 'redux/reducers/search';
|
||||||
import reactionsReducer from 'redux/reducers/reactions';
|
import reactionsReducer from 'redux/reducers/reactions';
|
||||||
import syncReducer from 'redux/reducers/sync';
|
import syncReducer from 'redux/reducers/sync';
|
||||||
|
@ -30,6 +31,7 @@ export default (history) =>
|
||||||
costInfo: costInfoReducer,
|
costInfo: costInfoReducer,
|
||||||
fileInfo: fileInfoReducer,
|
fileInfo: fileInfoReducer,
|
||||||
homepage: homepageReducer,
|
homepage: homepageReducer,
|
||||||
|
livestream: livestreamReducer,
|
||||||
notifications: notificationsReducer,
|
notifications: notificationsReducer,
|
||||||
publish: publishReducer,
|
publish: publishReducer,
|
||||||
reactions: reactionsReducer,
|
reactions: reactionsReducer,
|
||||||
|
|
33
ui/redux/actions/livestream.js
Normal file
33
ui/redux/actions/livestream.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// @flow
|
||||||
|
import * as ACTIONS from 'constants/action_types';
|
||||||
|
import { doClaimSearch } from 'lbry-redux';
|
||||||
|
|
||||||
|
export const doFetchNoSourceClaims = (channelId: string) => async (dispatch: Dispatch, getState: GetState) => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.FETCH_NO_SOURCE_CLAIMS_STARTED,
|
||||||
|
data: channelId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = 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,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (items) {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.FETCH_NO_SOURCE_CLAIMS_COMPLETED,
|
||||||
|
data: channelId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.FETCH_NO_SOURCE_CLAIMS_FAILED,
|
||||||
|
data: channelId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
34
ui/redux/reducers/livestream.js
Normal file
34
ui/redux/reducers/livestream.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// @flow
|
||||||
|
import * as ACTIONS from 'constants/action_types';
|
||||||
|
import { handleActions } from 'util/redux-utils';
|
||||||
|
|
||||||
|
const defaultState: LivestreamState = {
|
||||||
|
idsFetching: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handleActions(
|
||||||
|
{
|
||||||
|
[ACTIONS.FETCH_NO_SOURCE_CLAIMS_STARTED]: (state: LivestreamState, action: any): LivestreamState => {
|
||||||
|
const claimId = action.data;
|
||||||
|
const newIdsFetching = Object.assign({}, state.idsFetching);
|
||||||
|
newIdsFetching[claimId] = true;
|
||||||
|
|
||||||
|
return { ...state, idsFetching: newIdsFetching };
|
||||||
|
},
|
||||||
|
[ACTIONS.FETCH_NO_SOURCE_CLAIMS_COMPLETED]: (state: LivestreamState, action: any): LivestreamState => {
|
||||||
|
const claimId = action.data;
|
||||||
|
const newIdsFetching = Object.assign({}, state.idsFetching);
|
||||||
|
newIdsFetching[claimId] = false;
|
||||||
|
|
||||||
|
return { ...state, idsFetching: newIdsFetching };
|
||||||
|
},
|
||||||
|
[ACTIONS.FETCH_NO_SOURCE_CLAIMS_FAILED]: (state: LivestreamState, action: any) => {
|
||||||
|
const claimId = action.data;
|
||||||
|
const newIdsFetching = Object.assign({}, state.idsFetching);
|
||||||
|
newIdsFetching[claimId] = false;
|
||||||
|
|
||||||
|
return { ...state, idsFetching: newIdsFetching };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultState
|
||||||
|
);
|
38
ui/redux/selectors/livestream.js
Normal file
38
ui/redux/selectors/livestream.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// @flow
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { selectMyClaims, selectPendingClaims } from 'lbry-redux';
|
||||||
|
|
||||||
|
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.idsFetching);
|
||||||
|
|
||||||
|
export const makeSelectIsFetchingLivestreams = (channelId: string) =>
|
||||||
|
createSelector(selectFetchingLivestreams, (fetchingLivestreams) => Boolean(fetchingLivestreams[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
|
||||||
|
);
|
||||||
|
});
|
Loading…
Reference in a new issue