diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index ca6f1850d..291dc4d81 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -396,14 +396,16 @@ export function doPublish(params) { } else { uri = lbryuri.build({ name: name }, false); } + const fakeId = "pending"; const pendingPublish = { name, channel_name, - claim_id: "pending_claim_" + uri, + claim_id: fakeId, txid: "pending_" + uri, nout: 0, - outpoint: "pending_" + uri + ":0", + outpoint: fakeId + ":0", time: Date.now(), + pending: true, }; dispatch({ diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index 70db7244d..eb4a6753f 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -102,14 +102,20 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) { }, }); - const success = () => { - dispatch({ - type: types.ABANDON_CLAIM_COMPLETED, - data: { - claimId: fileInfo.claim_id, - }, - }); - }; + // We need to run this after a few seconds or the claim gets added back + // to the store again by an already running fetch claims query. + const success = setTimeout( + () => { + dispatch({ + type: types.ABANDON_CLAIM_COMPLETED, + data: { + claimId: fileInfo.claim_id, + }, + }); + }, + 10000, + { once: true } + ); lbry.claim_abandon({ claim_id: fileInfo.claim_id }).then(success); } } diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index 8785972d9..edd64b993 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -96,7 +96,6 @@ class FileList extends React.PureComponent { - {content} diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index c0c25a202..eb865d1ac 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -64,9 +64,16 @@ class FileTile extends React.PureComponent { const isClaimable = lbryuri.isClaimable(uri); const title = isClaimed && metadata && metadata.title ? metadata.title - : uri; + : lbryuri.parse(uri).contentName; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - let onClick = () => navigate("/show", { uri }); + let onClick; + if (isClaimed) { + onClick = () => navigate("/show", { uri }); + } else { + onClick = () => { + return false; + }; + } let description = ""; if (isClaimed) { diff --git a/ui/js/component/publishForm/view.jsx b/ui/js/component/publishForm/view.jsx index 91fd5eb09..446081f6d 100644 --- a/ui/js/component/publishForm/view.jsx +++ b/ui/js/component/publishForm/view.jsx @@ -873,7 +873,11 @@ class PublishForm extends React.PureComponent { onClick={event => { this.handleSubmit(event); }} - disabled={this.state.submitting} + disabled={ + this.state.submitting || + (this.state.uri && + this.props.resolvingUris.indexOf(this.state.uri) !== -1) + } /> ({ - claims: selectMyClaims(state), + claims: selectMyClaimsWithoutChannels(state), isFetching: selectIsFetchingClaimListMine(state), }); diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 3ee992f94..8f60bf9a1 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -40,19 +40,39 @@ reducers[types.FETCH_CLAIM_LIST_MINE_STARTED] = function(state, action) { reducers[types.FETCH_CLAIM_LIST_MINE_COMPLETED] = function(state, action) { const { claims } = action.data; - const myClaims = new Set(state.myClaims); const byUri = Object.assign({}, state.claimsByUri); const byId = Object.assign({}, state.byId); + const pendingById = Object.assign({}, state.pendingById); + + const myClaims = new Set(claims.map(claim => claim.claim_id)); claims.forEach(claim => { - myClaims.add(claim.claim_id); byId[claim.claim_id] = claim; + + const pending = Object.values(pendingById).find(pendingClaim => { + return ( + pendingClaim.name == claim.name && + pendingClaim.channel_name == claim.channel_name + ); + }); + + if (pending) { + delete pendingById[pending.claim_id]; + } }); + // Remove old timed out pending publishes + const old = Object.values(pendingById) + .filter(pendingClaim => Date.now() - pendingClaim.time >= 20 * 60 * 1000) + .forEach(pendingClaim => { + delete pendingById[pendingClaim.claim_id]; + }); + return Object.assign({}, state, { isFetchingClaimListMine: false, myClaims: myClaims, byId, + pendingById, }); }; @@ -91,6 +111,17 @@ reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) { }); }; +reducers[types.ABANDON_CLAIM_STARTED] = function(state, action) { + const { claimId } = action.data; + const abandoningById = Object.assign({}, state.abandoningById); + + abandoningById[claimId] = true; + + return Object.assign({}, state, { + abandoningById, + }); +}; + reducers[types.ABANDON_CLAIM_COMPLETED] = function(state, action) { const { claimId } = action.data; const myClaims = new Set(state.myClaims); @@ -128,17 +159,42 @@ reducers[types.CREATE_CHANNEL_COMPLETED] = function(state, action) { }); }; +reducers[types.PUBLISH_STARTED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingById = Object.assign({}, state.pendingById); + + pendingById[pendingPublish.claim_id] = pendingPublish; + + return Object.assign({}, state, { + pendingById, + }); +}; + reducers[types.PUBLISH_COMPLETED] = function(state, action) { - const { claim } = action.data; + const { claim, pendingPublish } = action.data; const byId = Object.assign({}, state.byId); const myClaims = new Set(state.myClaims); + const pendingById = Object.assign({}, state.pendingById); byId[claim.claim_id] = claim; myClaims.add(claim.claim_id); + delete pendingById[pendingPublish.claim_id]; return Object.assign({}, state, { byId, myClaims, + pendingById, + }); +}; + +reducers[types.PUBLISH_FAILED] = function(state, action) { + const { pendingPublish } = action.data; + const pendingById = Object.assign({}, state.pendingById); + + delete pendingById[pendingPublish.claim_id]; + + return Object.assign({}, state, { + pendingById, }); }; diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index e462f3cf6..c9e2817b3 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -138,39 +138,6 @@ reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { }); }; -reducers[types.PUBLISH_STARTED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); - - pendingByOutpoint[pendingPublish.outpoint] = pendingPublish; - - return Object.assign({}, state, { - pendingByOutpoint, - }); -}; - -reducers[types.PUBLISH_COMPLETED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); - - delete pendingByOutpoint[pendingPublish.outpoint]; - - return Object.assign({}, state, { - pendingByOutpoint, - }); -}; - -reducers[types.PUBLISH_FAILED] = function(state, action) { - const { pendingPublish } = action.data; - const pendingByOutpoint = Object.assign({}, state.pendingByOutpoint); - - delete pendingByOutpoint[pendingPublish.outpoint]; - - return Object.assign({}, state, { - pendingByOutpoint, - }); -}; - export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index f0708cf86..3c10ec931 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -110,22 +110,37 @@ export const selectMyClaimsRaw = createSelector( state => new Set(state.myClaims) ); +export const selectAbandoningIds = createSelector(_selectState, state => + Object.keys(state.abandoningById || {}) +); + +export const selectPendingClaims = createSelector(_selectState, state => + Object.values(state.pendingById || {}) +); + export const selectMyClaims = createSelector( selectMyClaimsRaw, selectClaimsById, - (myClaimIds, byId) => { + selectAbandoningIds, + selectPendingClaims, + (myClaimIds, byId, abandoningIds, pendingClaims) => { const claims = []; myClaimIds.forEach(id => { const claim = byId[id]; - if (claim) claims.push(claim); + if (claim && abandoningIds.indexOf(id) == -1) claims.push(claim); }); - return claims; + return [...claims, ...pendingClaims]; } ); +export const selectMyClaimsWithoutChannels = createSelector( + selectMyClaims, + myClaims => myClaims.filter(claim => !claim.name.match(/^@/)) +); + export const selectMyClaimsOutpoints = createSelector( selectMyClaims, myClaims => { diff --git a/ui/js/store.js b/ui/js/store.js index 35f7ab2ec..124174bc1 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -91,17 +91,14 @@ const saveClaimsFilter = createFilter("claims", [ "claimsByUri", "myClaims", "myChannelClaims", -]); -const saveFileInfosFilter = createFilter("fileInfo", [ - "fileInfos", - "pendingByOutpoint", + "pendingById", ]); const persistOptions = { - whitelist: ["claims", "fileInfo"], + whitelist: ["claims"], // Order is important. Needs to be compressed last or other transforms can't // read the data - transforms: [saveClaimsFilter, saveFileInfosFilter, compressor], + transforms: [saveClaimsFilter, compressor], debounce: 10000, storage: localForage, };