remove dependency on full claim list

paginate claim list
improve handling of pending publishes
add abandon to publishes list previews

use bodyCard

fix publish edit notification
This commit is contained in:
jessop 2020-04-24 09:51:00 -04:00
parent f83ab01c2d
commit 7730ee1e3f
20 changed files with 188 additions and 95 deletions

View file

@ -131,7 +131,7 @@
"imagesloaded": "^4.1.4",
"json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#f8c26fbe34f49a9898d48b8a41c956b9ad4ff582",
"lbry-redux": "lbryio/lbry-redux#58ff4d8086cf2d038e0f606f50e04d67efec596a",
"lbryinc": "lbryio/lbryinc#cc62a4eec10845cc0b31da7d0f27287cfa7c4866",
"lint-staged": "^7.0.2",
"localforage": "^1.7.1",

View file

@ -0,0 +1,13 @@
import { connect } from 'react-redux';
import { doOpenModal } from 'redux/actions/app';
import ClaimAbandonButton from './view';
import { makeSelectClaimForUri } from 'lbry-redux';
const select = (state, props) => ({
claim: props.uri && makeSelectClaimForUri(props.uri)(state),
});
export default connect(select, {
doOpenModal,
})(ClaimAbandonButton);

View file

@ -0,0 +1,21 @@
// @flow
import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons';
import React from 'react';
import Button from 'component/button';
type Props = {
doOpenModal: (string, {}) => void,
claim: StreamClaim,
abandonActionCallback: any => void,
};
export default function ClaimAbandonButton(props: Props) {
const { doOpenModal, claim, abandonActionCallback } = props;
function abandonClaim() {
doOpenModal(MODALS.CONFIRM_CLAIM_REVOKE, { claim: claim, cb: abandonActionCallback });
}
return <Button button="secondary" icon={ICONS.DELETE} onClick={abandonClaim} />;
}

View file

@ -30,6 +30,8 @@ type Props = {
showUnresolvedClaims?: boolean,
renderProperties: ?(Claim) => Node,
includeSupportAction?: boolean,
includeOwnerActions?: boolean,
abandonActionCallback?: any => void,
hideBlock: boolean,
injectedItem: ?Node,
timedOutMessage?: Node,
@ -55,6 +57,8 @@ export default function ClaimList(props: Props) {
showUnresolvedClaims,
renderProperties,
includeSupportAction,
includeOwnerActions,
abandonActionCallback,
hideBlock,
injectedItem,
timedOutMessage,
@ -148,6 +152,8 @@ export default function ClaimList(props: Props) {
uri={uri}
type={type}
includeSupportAction={includeSupportAction}
includeOwnerActions={includeOwnerActions}
abandonActionCallback={abandonActionCallback}
showUnresolvedClaim={showUnresolvedClaims}
properties={renderProperties || (type !== 'small' ? undefined : false)}
showUserBlocked={showHiddenByUser}

View file

@ -15,6 +15,7 @@ import SubscribeButton from 'component/subscribeButton';
import ChannelThumbnail from 'component/channelThumbnail';
import BlockButton from 'component/blockButton';
import ClaimSupportButton from 'component/claimSupportButton';
import ClaimAbandonButton from 'component/claimAbandonButton';
import useGetThumbnail from 'effects/use-get-thumbnail';
import ClaimPreviewTitle from 'component/claimPreviewTitle';
import ClaimPreviewSubtitle from 'component/claimPreviewSubtitle';
@ -58,6 +59,8 @@ type Props = {
customShouldHide?: Claim => boolean,
showUnresolvedClaim?: boolean,
includeSupportAction?: boolean,
includeOwnerActions?: boolean,
abandonActionCallback?: any => void,
};
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
@ -90,6 +93,8 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
customShouldHide,
showUnresolvedClaim,
includeSupportAction,
includeOwnerActions,
abandonActionCallback,
} = props;
const shouldFetch =
claim === undefined || (claim !== null && claim.value_type === 'channel' && isEmpty(claim.meta) && !pending);
@ -283,6 +288,9 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
<BlockButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)}
{includeSupportAction && <ClaimSupportButton uri={uri} />}
{includeOwnerActions && (
<ClaimAbandonButton uri={uri} abandonActionCallback={abandonActionCallback} />
)}
</div>
)}
</React.Fragment>

View file

@ -10,6 +10,7 @@ import {
doClearPublish,
doUpdatePublishForm,
doPrepareEdit,
doCheckPublishNameAvailability,
} from 'lbry-redux';
import { doPublishDesktop } from 'redux/actions/publish';
import { selectUnclaimedRewardValue } from 'lbryinc';
@ -35,9 +36,7 @@ const perform = dispatch => ({
publish: filePath => dispatch(doPublishDesktop(filePath)),
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
checkAvailability: name => dispatch(doCheckPublishNameAvailability(name)),
});
export default connect(
select,
perform
)(PublishPage);
export default connect(select, perform)(PublishPage);

View file

@ -64,6 +64,7 @@ type Props = {
amountNeededForTakeover: ?number,
// Add back type
updatePublishForm: any => void,
checkAvailability: string => void,
};
function PublishForm(props: Props) {
@ -86,6 +87,7 @@ function PublishForm(props: Props) {
tags,
publish,
disabled = false,
checkAvailability,
} = props;
const TAGS_LIMIT = 5;
const formDisabled = (!filePath && !editingURI) || publishing;
@ -134,11 +136,12 @@ function PublishForm(props: Props) {
}
const isValid = isURIValid(uri);
if (uri && isValid) {
if (uri && isValid && checkAvailability && name) {
resolveUri(uri);
checkAvailability(name);
updatePublishForm({ uri });
}
}, [name, channel, resolveUri, updatePublishForm]);
}, [name, channel, resolveUri, updatePublishForm, checkAvailability]);
return (
<div className="card-stack">

View file

@ -68,10 +68,10 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
const actualFilePath = filePath || downloadPath;
let isSupportedVideo = false;
if (typeof filePath === 'string') {
if (typeof actualFilePath === 'string') {
isSupportedVideo = Lbry.getMediaType(null, actualFilePath) === 'video';
} else if (filePath && filePath.type) {
isSupportedVideo = filePath.type.split('/')[0] === 'video';
} else if (actualFilePath && actualFilePath.type) {
isSupportedVideo = actualFilePath.type.split('/')[0] === 'video';
}
let thumbnailSrc;

View file

@ -8,7 +8,6 @@ import {
makeSelectMetadataForUri,
makeSelectClaimForUri,
doSupportAbandonForClaim,
doFetchClaimListMine,
selectAbandonClaimSupportError,
} from 'lbry-redux';
import SupportsLiquidate from './view';
@ -27,7 +26,6 @@ const select = (state, props) => ({
const perform = dispatch => ({
abandonSupportForClaim: (claimId, type, keep, preview) =>
dispatch(doSupportAbandonForClaim(claimId, type, keep, preview)),
fetchClaimListMine: () => dispatch(doFetchClaimListMine()),
});
export default connect(select, perform)(SupportsLiquidate);

View file

@ -7,7 +7,7 @@ import DateTime from 'component/dateTime';
import Button from 'component/button';
import Spinner from 'component/spinner';
import { toCapitalCase } from 'util/string';
import { buildURI, parseURI, TXO_LIST as TXO, TXO_ABANDON_STATES as TXO_STATES } from 'lbry-redux';
import { buildURI, parseURI, TXO_LIST as TXO, ABANDON_STATES } from 'lbry-redux';
type Props = {
txo: Txo,
@ -25,20 +25,20 @@ type State = {
class TxoListItem extends React.PureComponent<Props, State> {
constructor() {
super();
this.state = { abandonState: TXO_STATES.READY };
this.state = { abandonState: ABANDON_STATES.READY };
(this: any).abandonClaim = this.abandonClaim.bind(this);
(this: any).getLink = this.getLink.bind(this);
}
getLink(type: string) {
const { abandonState } = this.state;
if (this.state.abandonState === TXO_STATES.PENDING) {
if (this.state.abandonState === ABANDON_STATES.PENDING) {
return <Spinner type={'small'} />;
}
if (type === TXO.SUPPORT) {
return (
<Button
disabled={abandonState === TXO_STATES.DONE || abandonState === TXO_STATES.ERROR}
disabled={abandonState === ABANDON_STATES.DONE || abandonState === ABANDON_STATES.ERROR}
button="secondary"
icon={ICONS.UNLOCK}
onClick={this.abandonClaim}
@ -49,7 +49,7 @@ class TxoListItem extends React.PureComponent<Props, State> {
const abandonTitle = type === TXO.SUPPORT ? 'Abandon Support' : 'Abandon Claim';
return (
<Button
disabled={abandonState === TXO_STATES.DONE || abandonState === TXO_STATES.ERROR}
disabled={abandonState === ABANDON_STATES.DONE || abandonState === ABANDON_STATES.ERROR}
button="secondary"
icon={ICONS.DELETE}
onClick={this.abandonClaim}

View file

@ -3,6 +3,9 @@ export const MINIMUM_PUBLISH_BID = 0.00001;
export const CHANNEL_ANONYMOUS = 'anonymous';
export const CHANNEL_NEW = 'new';
export const PAGE_SIZE = 20;
export const PUBLISHES_PAGE_SIZE = 10;
export const PAGE_PARAM = 'page';
export const PAGE_SIZE_PARAM = 'page_size';
export const INVALID_NAME_ERROR =
__('LBRY names cannot contain spaces or reserved symbols') + ' ' + '($#@;/"<>%{}|^~[]`)';

View file

@ -11,7 +11,7 @@ import {
doClearRepostError,
doToast,
selectMyClaimsWithoutChannels,
doFetchClaimListMine,
doCheckPublishNameAvailability,
} from 'lbry-redux';
import ModalRepost from './view';
@ -25,13 +25,10 @@ const select = (state, props) => ({
myClaims: selectMyClaimsWithoutChannels(state),
});
export default connect(
select,
{
doHideModal,
doRepost,
doClearRepostError,
doToast,
doFetchClaimListMine,
}
)(ModalRepost);
export default connect(select, {
doHideModal,
doRepost,
doClearRepostError,
doToast,
doCheckPublishNameAvailability,
})(ModalRepost);

View file

@ -22,8 +22,7 @@ type Props = {
claim: ?StreamClaim,
balance: number,
channels: ?Array<ChannelClaim>,
myClaims: ?Array<StreamClaim>,
doFetchClaimListMine: () => void,
doCheckPublishNameAvailability: string => Promise<*>,
error: ?string,
reposting: boolean,
};
@ -40,8 +39,7 @@ function ModalRepost(props: Props) {
channels,
error,
reposting,
myClaims,
doFetchClaimListMine,
doCheckPublishNameAvailability,
} = props;
const defaultName = claim && claim.name;
const contentClaimId = claim && claim.claim_id;
@ -49,6 +47,7 @@ function ModalRepost(props: Props) {
const [repostBid, setRepostBid] = React.useState(0.01);
const [showAdvanced, setShowAdvanced] = React.useState();
const [repostName, setRepostName] = React.useState(defaultName);
const [available, setAvailable] = React.useState(true);
let repostBidError;
if (repostBid === 0) {
@ -66,12 +65,7 @@ function ModalRepost(props: Props) {
repostNameError = __('A name is required');
} else if (!isNameValid(repostName, false)) {
repostNameError = INVALID_NAME_ERROR;
} else if (
channels &&
channels.find(claim => claim.name === repostChannel) &&
myClaims &&
myClaims.find(claim => claim.name === repostName)
) {
} else if (!available) {
repostNameError = __('You already have a claim with this name.');
}
@ -91,12 +85,11 @@ function ModalRepost(props: Props) {
}
}, [channelStrings]);
const myClaimsString = myClaims && myClaims.map(channel => channel.permanent_url).join(',');
React.useEffect(() => {
if (myClaimsString === '') {
doFetchClaimListMine();
if (repostName && isNameValid(repostName, false)) {
doCheckPublishNameAvailability(repostName).then(r => setAvailable(r));
}
}, [myClaimsString, doFetchClaimListMine]);
}, [repostName, doCheckPublishNameAvailability]);
function handleSubmit() {
const channelToRepostTo = channels && channels.find(channel => channel.name === repostChannel);

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import { doAbandonTxo, selectTransactionItems } from 'lbry-redux';
import { doAbandonTxo, doAbandonClaim, selectTransactionItems } from 'lbry-redux';
import ModalRevokeClaim from './view';
const select = state => ({
@ -10,6 +10,7 @@ const select = state => ({
const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()),
abandonTxo: (txo, cb) => dispatch(doAbandonTxo(txo, cb)),
abandonClaim: (txid, nout, cb) => dispatch(doAbandonClaim(txid, nout, cb)),
});
export default connect(select, perform)(ModalRevokeClaim);

View file

@ -7,13 +7,15 @@ import * as txnTypes from 'constants/transaction_types';
type Props = {
closeModal: () => void,
abandonTxo: (Txo, () => void) => void,
abandonClaim: (string, number, ?() => void) => void,
tx: Txo,
claim: GenericClaim,
cb: () => void,
};
export default function ModalRevokeClaim(props: Props) {
const { tx, closeModal, abandonTxo, cb } = props;
const { value_type: valueType, type, normalized_name: name } = tx;
const { tx, claim, closeModal, abandonTxo, abandonClaim, cb } = props;
const { value_type: valueType, type, normalized_name: name } = tx || claim;
const [channelName, setChannelName] = useState('');
function getButtonLabel(type: string) {
@ -80,7 +82,7 @@ export default function ModalRevokeClaim(props: Props) {
}
function revokeClaim() {
abandonTxo(tx, cb);
tx ? abandonTxo(tx, cb) : abandonClaim(claim.txid, claim.nout, cb);
closeModal();
}

View file

@ -1,36 +1,39 @@
import { connect } from 'react-redux';
import {
selectIsFetchingClaimListMine,
makeSelectMyStreamUrlsForPage,
selectMyStreamUrlsCount,
selectMyClaimsPage,
selectMyClaimsPageItemCount,
selectFetchingMyClaimsPageError,
doClearPublish,
doFetchClaimListMine,
} from 'lbry-redux';
import { selectUploadCount } from 'lbryinc';
import { doCheckPendingPublishesApp } from 'redux/actions/publish';
import FileListPublished from './view';
import { withRouter } from 'react-router';
import { PUBLISHES_PAGE_SIZE, PAGE_PARAM, PAGE_SIZE_PARAM } from 'constants/claim';
const select = (state, props) => {
const { search } = props.location;
const urlParams = new URLSearchParams(search);
const page = Number(urlParams.get('page')) || 1;
const page = Number(urlParams.get(PAGE_PARAM)) || '1';
const pageSize = urlParams.get(PAGE_SIZE_PARAM) || String(PUBLISHES_PAGE_SIZE);
return {
page,
urls: makeSelectMyStreamUrlsForPage(page)(state),
urlTotal: selectMyStreamUrlsCount(state),
pageSize,
fetching: selectIsFetchingClaimListMine(state),
urls: selectMyClaimsPage(state),
urlTotal: selectMyClaimsPageItemCount(state),
error: selectFetchingMyClaimsPageError(state),
uploadCount: selectUploadCount(state),
};
};
const perform = dispatch => ({
checkPendingPublishes: () => dispatch(doCheckPendingPublishesApp()),
fetchClaimListMine: () => dispatch(doFetchClaimListMine()),
fetchClaimListMine: (page, pageSize) => dispatch(doFetchClaimListMine(page, pageSize)),
clearPublish: () => dispatch(doClearPublish()),
});
export default withRouter(
connect(
select,
perform
)(FileListPublished)
);
export default withRouter(connect(select, perform)(FileListPublished));

View file

@ -4,45 +4,86 @@ import Button from 'component/button';
import ClaimList from 'component/claimList';
import Page from 'component/page';
import Paginate from 'component/common/paginate';
import { PAGE_SIZE } from 'constants/claim';
import { PAGE_PARAM, PAGE_SIZE_PARAM } from 'constants/claim';
import WebUploadList from 'component/webUploadList';
import Spinner from 'component/spinner';
import Card from 'component/common/card';
type Props = {
uploadCount: number,
checkPendingPublishes: () => void,
clearPublish: () => void,
fetchClaimListMine: () => void,
fetchClaimListMine: (number, number) => void,
fetching: boolean,
urls: Array<string>,
urlTotal: ?number,
history: { replace: string => void },
urlTotal: number,
history: { replace: string => void, push: string => void },
page: number,
pageSize: number,
};
function FileListPublished(props: Props) {
const { checkPendingPublishes, clearPublish, fetchClaimListMine, fetching, urls, urlTotal } = props;
const {
uploadCount,
checkPendingPublishes,
clearPublish,
fetchClaimListMine,
fetching,
urls,
urlTotal,
page,
pageSize,
} = props;
const params = {};
params[PAGE_PARAM] = Number(page);
params[PAGE_SIZE_PARAM] = Number(pageSize);
const paramsString = JSON.stringify(params);
useEffect(() => {
checkPendingPublishes();
fetchClaimListMine();
}, [checkPendingPublishes, fetchClaimListMine]);
}, [checkPendingPublishes]);
useEffect(() => {
if (paramsString && fetchClaimListMine) {
const params = JSON.parse(paramsString);
fetchClaimListMine(params.page, params.page_size);
}
}, [uploadCount, paramsString, fetchClaimListMine]);
return (
<Page>
<WebUploadList />
{urls && Boolean(urls.length) && (
<React.Fragment>
<ClaimList
header={__('Your Publishes')}
loading={fetching}
persistedStorageKey="claim-list-published"
uris={urls}
headerAltControls={
<Button button="link" label={__('New Publish')} navigate="/$/publish" onClick={() => clearPublish()} />
}
/>
<Paginate totalPages={Math.ceil(Number(urlTotal) / Number(PAGE_SIZE))} loading={fetching} />
</React.Fragment>
)}
<Card
title={__('Publishes')}
titleActions={
<div className="card__actions--inline">
<Button button="alt" label={__('New Publish')} navigate="/$/publish" onClick={() => clearPublish()} />
<Button
button="secondary"
label={__('Refresh')}
onClick={() => fetchClaimListMine(params.page, params.page_size)}
/>
</div>
}
isBodyList
body={
<div>
<ClaimList
isCardBody
loading={fetching}
persistedStorageKey="claim-list-published"
uris={urls}
includeOwnerActions
abandonActionCallback={() => fetchClaimListMine(params.page, params.page_size)}
/>
<Paginate totalPages={urlTotal > 0 ? Math.ceil(urlTotal / Number(pageSize)) : 1} />
</div>
}
/>
{!(urls && urls.length) && (
<React.Fragment>
{!fetching ? (

View file

@ -9,7 +9,7 @@ import * as MODALS from 'constants/modal_types';
import {
Lbry,
doBalanceSubscribe,
doFetchFileInfosAndPublishedClaims,
doFetchFileInfos,
doError,
makeSelectClaimForUri,
makeSelectClaimIsMine,
@ -352,7 +352,7 @@ export function doDaemonReady() {
dispatch(doFindFFmpeg());
dispatch(doGetDaemonStatus());
dispatch(doFetchDaemonSettings());
dispatch(doFetchFileInfosAndPublishedClaims());
dispatch(doFetchFileInfos());
if (!selectIsUpgradeSkipped(state)) {
dispatch(doCheckUpgradeAvailable());
}

View file

@ -2,7 +2,14 @@
import * as MODALS from 'constants/modal_types';
import * as ACTIONS from 'constants/action_types';
import * as PAGES from 'constants/pages';
import { batchActions, doError, selectMyClaims, doPublish, doCheckPendingPublishes } from 'lbry-redux';
import {
batchActions,
doError,
selectMyClaims,
doPublish,
doCheckPendingPublishes,
ACTIONS as LBRY_REDUX_ACTIONS,
} from 'lbry-redux';
import { selectosNotificationsEnabled } from 'redux/selectors/settings';
import { push } from 'connected-react-router';
import analytics from 'analytics';
@ -31,23 +38,21 @@ export const doPublishDesktop = (filePath: string) => (dispatch: Dispatch, getSt
const isMatch = claim => claim.claim_id === pendingClaim.claim_id;
const isEdit = myClaims.some(isMatch);
const myNewClaims = isEdit
? myClaims.map(claim => (isMatch(claim) ? pendingClaim : claim))
: myClaims.concat(pendingClaim);
actions.push(
actions.push({
type: LBRY_REDUX_ACTIONS.UPDATE_PENDING_CLAIMS,
data: {
claims: [pendingClaim],
},
});
dispatch(batchActions(...actions));
dispatch(
doOpenModal(MODALS.PUBLISH, {
uri: url,
isEdit,
filePath,
})
);
actions.push({
type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
data: {
claims: myNewClaims,
},
});
dispatch(batchActions(...actions));
};
const publishFail = error => {

View file

@ -6178,9 +6178,9 @@ lazy-val@^1.0.4:
yargs "^13.2.2"
zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#f8c26fbe34f49a9898d48b8a41c956b9ad4ff582:
lbry-redux@lbryio/lbry-redux#58ff4d8086cf2d038e0f606f50e04d67efec596a:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/f8c26fbe34f49a9898d48b8a41c956b9ad4ff582"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/58ff4d8086cf2d038e0f606f50e04d67efec596a"
dependencies:
proxy-polyfill "0.1.6"
reselect "^3.0.0"