paginate claim list #4059

Merged
jessopb merged 1 commit from feat-claimListPaginate into master 2020-05-01 20:26:21 +02:00
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={
jessopb commented 2020-04-28 22:27:46 +02:00 (Migrated from github.com)
Review

uploadCount is for lbry.tv.
I could use two different @if TARGETS for two different useEffect hooks, or just leave it - it seems to work.

uploadCount is for lbry.tv. I could use two different @if TARGETS for two different useEffect hooks, or just leave it - it seems to work.
neb-b commented 2020-04-29 16:17:11 +02:00 (Migrated from github.com)
Review

I think it's fine.

I think it's fine.
<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"