Publishing (#158)
* add actions, selectors and reducer for publishing * add publish types * support thumbnail uploads for mobile * update publishSuccess value in reducer
This commit is contained in:
parent
1415935006
commit
99e08fc733
10 changed files with 1949 additions and 592 deletions
1731
dist/bundle.es.js
vendored
1731
dist/bundle.es.js
vendored
File diff suppressed because it is too large
Load diff
51
dist/flow-typed/Publish.js
vendored
Normal file
51
dist/flow-typed/Publish.js
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
// @flow
|
||||
|
||||
declare type UpdatePublishFormData = {
|
||||
filePath?: string,
|
||||
contentIsFree?: boolean,
|
||||
fee?: {
|
||||
amount: string,
|
||||
currency: string,
|
||||
},
|
||||
title?: string,
|
||||
thumbnail_url?: string,
|
||||
uploadThumbnailStatus?: string,
|
||||
thumbnailPath?: string,
|
||||
description?: string,
|
||||
language?: string,
|
||||
channel?: string,
|
||||
channelId?: string,
|
||||
name?: string,
|
||||
nameError?: string,
|
||||
bid?: number,
|
||||
bidError?: string,
|
||||
otherLicenseDescription?: string,
|
||||
licenseUrl?: string,
|
||||
licenseType?: string,
|
||||
uri?: string,
|
||||
nsfw: boolean,
|
||||
};
|
||||
|
||||
declare type PublishParams = {
|
||||
name: ?string,
|
||||
bid: ?number,
|
||||
filePath?: string,
|
||||
description: ?string,
|
||||
language: string,
|
||||
publishingLicense?: string,
|
||||
publishingLicenseUrl?: string,
|
||||
thumbnail: ?string,
|
||||
channel: string,
|
||||
channelId?: string,
|
||||
title: string,
|
||||
contentIsFree: boolean,
|
||||
uri?: string,
|
||||
license: ?string,
|
||||
licenseUrl: ?string,
|
||||
fee?: {
|
||||
amount: string,
|
||||
currency: string,
|
||||
},
|
||||
claim: StreamClaim,
|
||||
nsfw: boolean,
|
||||
};
|
51
flow-typed/Publish.js
vendored
Normal file
51
flow-typed/Publish.js
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
// @flow
|
||||
|
||||
declare type UpdatePublishFormData = {
|
||||
filePath?: string,
|
||||
contentIsFree?: boolean,
|
||||
fee?: {
|
||||
amount: string,
|
||||
currency: string,
|
||||
},
|
||||
title?: string,
|
||||
thumbnail_url?: string,
|
||||
uploadThumbnailStatus?: string,
|
||||
thumbnailPath?: string,
|
||||
description?: string,
|
||||
language?: string,
|
||||
channel?: string,
|
||||
channelId?: string,
|
||||
name?: string,
|
||||
nameError?: string,
|
||||
bid?: number,
|
||||
bidError?: string,
|
||||
otherLicenseDescription?: string,
|
||||
licenseUrl?: string,
|
||||
licenseType?: string,
|
||||
uri?: string,
|
||||
nsfw: boolean,
|
||||
};
|
||||
|
||||
declare type PublishParams = {
|
||||
name: ?string,
|
||||
bid: ?number,
|
||||
filePath?: string,
|
||||
description: ?string,
|
||||
language: string,
|
||||
publishingLicense?: string,
|
||||
publishingLicenseUrl?: string,
|
||||
thumbnail: ?string,
|
||||
channel: string,
|
||||
channelId?: string,
|
||||
title: string,
|
||||
contentIsFree: boolean,
|
||||
uri?: string,
|
||||
license: ?string,
|
||||
licenseUrl: ?string,
|
||||
fee?: {
|
||||
amount: string,
|
||||
currency: string,
|
||||
},
|
||||
claim: StreamClaim,
|
||||
nsfw: boolean,
|
||||
};
|
5
src/constants/claim.js
Normal file
5
src/constants/claim.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export const MINIMUM_PUBLISH_BID = 0.00000001;
|
||||
|
||||
export const CHANNEL_ANONYMOUS = 'anonymous';
|
||||
export const CHANNEL_NEW = 'new';
|
||||
export const PAGE_SIZE = 20;
|
31
src/constants/licenses.js
Normal file
31
src/constants/licenses.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
export const CC_LICENSES = [
|
||||
{
|
||||
value: 'Creative Commons Attribution 4.0 International',
|
||||
url: 'https://creativecommons.org/licenses/by/4.0/legalcode',
|
||||
},
|
||||
{
|
||||
value: 'Creative Commons Attribution-ShareAlike 4.0 International',
|
||||
url: 'https://creativecommons.org/licenses/by-sa/4.0/legalcode',
|
||||
},
|
||||
{
|
||||
value: 'Creative Commons Attribution-NoDerivatives 4.0 International',
|
||||
url: 'https://creativecommons.org/licenses/by-nd/4.0/legalcode',
|
||||
},
|
||||
{
|
||||
value: 'Creative Commons Attribution-NonCommercial 4.0 International',
|
||||
url: 'https://creativecommons.org/licenses/by-nc/4.0/legalcode',
|
||||
},
|
||||
{
|
||||
value: 'Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International',
|
||||
url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode',
|
||||
},
|
||||
{
|
||||
value: 'Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International',
|
||||
url: 'https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode',
|
||||
},
|
||||
];
|
||||
|
||||
export const NONE = 'None';
|
||||
export const PUBLIC_DOMAIN = 'Public Domain';
|
||||
export const OTHER = 'other';
|
||||
export const COPYRIGHT = 'copyright';
|
43
src/index.js
43
src/index.js
|
@ -1,9 +1,11 @@
|
|||
import * as CLAIM_VALUES from 'constants/claim';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import * as TRANSACTIONS from 'constants/transaction_types';
|
||||
import * as SORT_OPTIONS from 'constants/sort_options';
|
||||
import * as LICENSES from 'constants/licenses';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import * as SORT_OPTIONS from 'constants/sort_options';
|
||||
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
||||
import * as TRANSACTIONS from 'constants/transaction_types';
|
||||
import { SEARCH_TYPES, SEARCH_OPTIONS } from 'constants/search';
|
||||
import Lbry from 'lbry';
|
||||
import { selectState as selectSearchState } from 'redux/selectors/search';
|
||||
|
@ -11,6 +13,8 @@ import { selectState as selectSearchState } from 'redux/selectors/search';
|
|||
// constants
|
||||
export {
|
||||
ACTIONS,
|
||||
CLAIM_VALUES,
|
||||
LICENSES,
|
||||
THUMBNAIL_STATUSES,
|
||||
SEARCH_TYPES,
|
||||
SEARCH_OPTIONS,
|
||||
|
@ -57,6 +61,16 @@ export {
|
|||
doSetFileListSort,
|
||||
} from 'redux/actions/file_info';
|
||||
|
||||
export {
|
||||
doResetThumbnailStatus,
|
||||
doClearPublish,
|
||||
doUpdatePublishForm,
|
||||
doUploadThumbnail,
|
||||
doPrepareEdit,
|
||||
doPublish,
|
||||
doCheckPendingPublishes
|
||||
} from 'redux/actions/publish';
|
||||
|
||||
export {
|
||||
doSearch,
|
||||
doUpdateSearchQuery,
|
||||
|
@ -100,14 +114,15 @@ export { isClaimNsfw } from 'util/claim';
|
|||
|
||||
// reducers
|
||||
export { claimsReducer } from 'redux/reducers/claims';
|
||||
export { fileReducer } from 'redux/reducers/file';
|
||||
export { fileInfoReducer } from 'redux/reducers/file_info';
|
||||
export { notificationsReducer } from 'redux/reducers/notifications';
|
||||
export { searchReducer } from 'redux/reducers/search';
|
||||
export { walletReducer } from 'redux/reducers/wallet';
|
||||
export { contentReducer } from 'redux/reducers/content';
|
||||
export { commentReducer } from 'redux/reducers/comments';
|
||||
export { contentReducer } from 'redux/reducers/content';
|
||||
export { fileInfoReducer } from 'redux/reducers/file_info';
|
||||
export { fileReducer } from 'redux/reducers/file';
|
||||
export { notificationsReducer } from 'redux/reducers/notifications';
|
||||
export { publishReducer } from 'redux/reducers/publish';
|
||||
export { searchReducer } from 'redux/reducers/search';
|
||||
export { tagsReducerBuilder } from 'redux/reducers/tags';
|
||||
export { walletReducer } from 'redux/reducers/wallet';
|
||||
|
||||
// selectors
|
||||
export { makeSelectContentPositionForUri } from 'redux/selectors/content';
|
||||
|
@ -193,6 +208,14 @@ export {
|
|||
selectDownloadedUris,
|
||||
} from 'redux/selectors/file_info';
|
||||
|
||||
export {
|
||||
selectPublishFormValues,
|
||||
selectIsStillEditing,
|
||||
selectMyClaimForUri,
|
||||
selectIsResolvingPublishUris,
|
||||
selectTakeOverAmount,
|
||||
} from 'redux/selectors/publish';
|
||||
|
||||
export { selectSearchState };
|
||||
export {
|
||||
makeSelectSearchUris,
|
||||
|
|
404
src/redux/actions/publish.js
Normal file
404
src/redux/actions/publish.js
Normal file
|
@ -0,0 +1,404 @@
|
|||
// @flow
|
||||
import { CC_LICENSES, COPYRIGHT, OTHER, NONE, PUBLIC_DOMAIN } from 'constants/licenses';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
//import * as MODALS from 'constants/modal_types';
|
||||
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
||||
import Lbry from 'lbry';
|
||||
import { batchActions } from 'util/batchActions';
|
||||
import { creditsToString } from 'util/formatCredits';
|
||||
import { doError } from 'redux/actions/notifications';
|
||||
import { isClaimNsfw } from 'util/claim';
|
||||
import {
|
||||
selectMyChannelClaims,
|
||||
selectPendingById,
|
||||
selectMyClaimsWithoutChannels,
|
||||
} from 'redux/selectors/claims';
|
||||
import { formatLbryUriForWeb } from 'util/uri';
|
||||
|
||||
export const doResetThumbnailStatus = () => (dispatch: Dispatch) => {
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_PUBLISH_FORM,
|
||||
data: {
|
||||
thumbnailPath: '',
|
||||
},
|
||||
});
|
||||
|
||||
return fetch('https://spee.ch/api/config/site/publishing')
|
||||
.then(res => res.json())
|
||||
.then(status => {
|
||||
if (status.disabled) {
|
||||
throw Error();
|
||||
}
|
||||
|
||||
return dispatch({
|
||||
type: ACTIONS.UPDATE_PUBLISH_FORM,
|
||||
data: {
|
||||
uploadThumbnailStatus: THUMBNAIL_STATUSES.READY,
|
||||
thumbnail: '',
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() =>
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_PUBLISH_FORM,
|
||||
data: {
|
||||
uploadThumbnailStatus: THUMBNAIL_STATUSES.API_DOWN,
|
||||
thumbnail: '',
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const doClearPublish = () => (dispatch: Dispatch) => {
|
||||
dispatch({ type: ACTIONS.CLEAR_PUBLISH });
|
||||
return dispatch(doResetThumbnailStatus());
|
||||
};
|
||||
|
||||
export const doUpdatePublishForm = (publishFormValue: UpdatePublishFormData) => (dispatch: Dispatch) =>
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_PUBLISH_FORM,
|
||||
data: { ...publishFormValue },
|
||||
});
|
||||
|
||||
export const doUploadThumbnail = (filePath: string, thumbnailBuffer: Uint8Array, fsAdapter: any) => (dispatch: Dispatch) => {
|
||||
let thumbnail, fileExt, fileName, fileType;
|
||||
|
||||
const makeid = () => {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < 24; i += 1) text += possible.charAt(Math.floor(Math.random() * 62));
|
||||
return text;
|
||||
};
|
||||
|
||||
const uploadError = (error = '') => {
|
||||
dispatch(
|
||||
batchActions(
|
||||
{
|
||||
type: ACTIONS.UPDATE_PUBLISH_FORM,
|
||||
data: {
|
||||
uploadThumbnailStatus: THUMBNAIL_STATUSES.READY,
|
||||
thumbnail: '',
|
||||
nsfw: false,
|
||||
},
|
||||
},
|
||||
doError(error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_PUBLISH_FORM,
|
||||
data: { uploadThumbnailStatus: THUMBNAIL_STATUSES.IN_PROGRESS },
|
||||
});
|
||||
|
||||
if (fsAdapter && fsAdapter.readFile && filePath) {
|
||||
fsAdapter.readFile(filePath, 'base64').then(base64Image => {
|
||||
fileExt = 'png';
|
||||
fileName = 'thumbnail.png';
|
||||
fileType = 'image/png';
|
||||
|
||||
const data = new FormData();
|
||||
const name = makeid();
|
||||
data.append('name', name);
|
||||
data.append('file', { uri: 'file://' + filePath, type: fileType, name: fileName });
|
||||
|
||||
return fetch('https://spee.ch/api/claim/publish', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
}).then(response => response.json())
|
||||
.then(json => json.success
|
||||
? dispatch({
|
||||
type: ACTIONS.UPDATE_PUBLISH_FORM,
|
||||
data: {
|
||||
uploadThumbnailStatus: THUMBNAIL_STATUSES.COMPLETE,
|
||||
thumbnail: `${json.data.url}${fileExt}`,
|
||||
},
|
||||
})
|
||||
: uploadError(json.message)
|
||||
)
|
||||
.catch(err => uploadError(err.message));
|
||||
});
|
||||
} else {
|
||||
if (filePath) {
|
||||
thumbnail = fs.readFileSync(filePath);
|
||||
fileExt = path.extname(filePath);
|
||||
fileName = path.basename(filePath);
|
||||
fileType = `image/${fileExt.slice(1)}`;
|
||||
} else if (thumbnailBuffer) {
|
||||
thumbnail = thumbnailBuffer;
|
||||
fileExt = '.png';
|
||||
fileName = 'thumbnail.png';
|
||||
fileType = 'image/png';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = new FormData();
|
||||
const name = makeid();
|
||||
const file = new File([thumbnail], fileName, { type: fileType });
|
||||
data.append('name', name);
|
||||
data.append('file', file);
|
||||
|
||||
return fetch('https://spee.ch/api/claim/publish', {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(json =>
|
||||
json.success
|
||||
? dispatch({
|
||||
type: ACTIONS.UPDATE_PUBLISH_FORM,
|
||||
data: {
|
||||
uploadThumbnailStatus: THUMBNAIL_STATUSES.COMPLETE,
|
||||
thumbnail: `${json.data.url}${fileExt}`,
|
||||
},
|
||||
})
|
||||
: uploadError(json.message)
|
||||
)
|
||||
.catch(err => uploadError(err.message));
|
||||
}
|
||||
};
|
||||
|
||||
export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileListItem) => (dispatch: Dispatch) => {
|
||||
const { name, amount, channel_name: channelName, value } = claim;
|
||||
|
||||
const {
|
||||
author,
|
||||
description,
|
||||
// use same values as default state
|
||||
// fee will be undefined for free content
|
||||
fee = {
|
||||
amount: 0,
|
||||
currency: 'LBC',
|
||||
},
|
||||
languages,
|
||||
license,
|
||||
license_url: licenseUrl,
|
||||
thumbnail,
|
||||
title,
|
||||
} = value;
|
||||
|
||||
const publishData: UpdatePublishFormData = {
|
||||
name,
|
||||
channel: channelName,
|
||||
bid: amount,
|
||||
contentIsFree: !fee.amount,
|
||||
author,
|
||||
description,
|
||||
fee: { amount: fee.amount, currency: fee.currency },
|
||||
languages,
|
||||
thumbnail: thumbnail ? thumbnail.url : null,
|
||||
title,
|
||||
uri,
|
||||
uploadThumbnailStatus: thumbnail ? THUMBNAIL_STATUSES.MANUAL : undefined,
|
||||
licenseUrl,
|
||||
nsfw: isClaimNsfw(claim),
|
||||
};
|
||||
|
||||
// Make sure custom liscence's are mapped properly
|
||||
// If the license isn't one of the standard licenses, map the custom license and description/url
|
||||
if (!CC_LICENSES.some(({ value }) => value === license)) {
|
||||
if (!license || license === NONE || license === PUBLIC_DOMAIN) {
|
||||
publishData.licenseType = license;
|
||||
} else if (license && !licenseUrl && license !== NONE) {
|
||||
publishData.licenseType = COPYRIGHT;
|
||||
} else {
|
||||
publishData.licenseType = OTHER;
|
||||
}
|
||||
|
||||
publishData.otherLicenseDescription = license;
|
||||
} else {
|
||||
publishData.licenseType = license;
|
||||
}
|
||||
|
||||
if (fileInfo && fileInfo.download_path) {
|
||||
try {
|
||||
fs.accessSync(fileInfo.download_path, fs.constants.R_OK);
|
||||
publishData.filePath = fileInfo.download_path;
|
||||
} catch (e) {
|
||||
console.error(e.name, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
dispatch({ type: ACTIONS.DO_PREPARE_EDIT, data: publishData });
|
||||
};
|
||||
|
||||
export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getState: () => {}) => {
|
||||
dispatch({ type: ACTIONS.PUBLISH_START });
|
||||
|
||||
const state = getState();
|
||||
const myChannels = selectMyChannelClaims(state);
|
||||
const myClaims = selectMyClaimsWithoutChannels(state);
|
||||
|
||||
const {
|
||||
name,
|
||||
bid,
|
||||
filePath,
|
||||
description,
|
||||
language,
|
||||
license,
|
||||
licenseUrl,
|
||||
thumbnail,
|
||||
channel,
|
||||
title,
|
||||
contentIsFree,
|
||||
fee,
|
||||
uri,
|
||||
nsfw,
|
||||
claim,
|
||||
} = params;
|
||||
|
||||
// get the claim id from the channel name, we will use that instead
|
||||
const namedChannelClaim = myChannels.find(myChannel => myChannel.name === channel);
|
||||
const channelId = namedChannelClaim ? namedChannelClaim.claim_id : '';
|
||||
|
||||
const publishPayload: {
|
||||
name: ?string,
|
||||
channel_id?: string,
|
||||
bid: number,
|
||||
file_path?: string,
|
||||
tags: Array<string>,
|
||||
locations?: Array<Location>,
|
||||
license_url?: string,
|
||||
thumbnail_url?: string,
|
||||
release_time?: number,
|
||||
fee_currency?: string,
|
||||
fee_amount?: string,
|
||||
} = {
|
||||
name,
|
||||
bid: creditsToString(bid),
|
||||
title,
|
||||
license,
|
||||
languages: [language],
|
||||
description,
|
||||
tags: (claim && claim.value.tags) || [],
|
||||
locations: claim && claim.value.locations,
|
||||
};
|
||||
|
||||
// Temporary solution to keep the same publish flow with the new tags api
|
||||
// Eventually we will allow users to enter their own tags on publish
|
||||
// `nsfw` will probably be removed
|
||||
|
||||
if (licenseUrl) {
|
||||
publishPayload.license_url = licenseUrl;
|
||||
}
|
||||
|
||||
if (thumbnail) {
|
||||
publishPayload.thumbnail_url = thumbnail;
|
||||
}
|
||||
|
||||
if (claim && claim.value.release_time) {
|
||||
publishPayload.release_time = Number(claim.value.release_time);
|
||||
}
|
||||
|
||||
if (nsfw) {
|
||||
if (!publishPayload.tags.includes('mature')) {
|
||||
publishPayload.tags.push('mature');
|
||||
}
|
||||
} else {
|
||||
const indexToRemove = publishPayload.tags.indexOf('mature');
|
||||
if (indexToRemove > -1) {
|
||||
publishPayload.tags.splice(indexToRemove, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (channelId) {
|
||||
publishPayload.channel_id = channelId;
|
||||
}
|
||||
|
||||
if (!contentIsFree && fee && (fee.currency && Number(fee.amount) > 0)) {
|
||||
publishPayload.fee_currency = fee.currency;
|
||||
publishPayload.fee_amount = creditsToString(fee.amount);
|
||||
}
|
||||
|
||||
// Only pass file on new uploads, not metadata only edits.
|
||||
// The sdk will figure it out
|
||||
if (filePath) publishPayload.file_path = filePath;
|
||||
|
||||
const success = successResponse => {
|
||||
//analytics.apiLogPublish();
|
||||
|
||||
const pendingClaim = successResponse.outputs[0];
|
||||
const actions = [];
|
||||
|
||||
actions.push({
|
||||
type: ACTIONS.PUBLISH_SUCCESS,
|
||||
});
|
||||
|
||||
//actions.push(doOpenModal(MODALS.PUBLISH, { uri }));
|
||||
|
||||
// We have to fake a temp claim until the new pending one is returned by claim_list_mine
|
||||
// We can't rely on claim_list_mine because there might be some delay before the new claims are returned
|
||||
// Doing this allows us to show the pending claim immediately, it will get overwritten by the real one
|
||||
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({
|
||||
type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
|
||||
data: {
|
||||
claims: myNewClaims,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch(batchActions(...actions));
|
||||
};
|
||||
|
||||
const failure = error => {
|
||||
dispatch({ type: ACTIONS.PUBLISH_FAIL });
|
||||
dispatch(doError(error.message));
|
||||
};
|
||||
|
||||
return Lbry.publish(publishPayload).then(success, failure);
|
||||
};
|
||||
|
||||
// Calls claim_list_mine until any pending publishes are confirmed
|
||||
export const doCheckPendingPublishes = () => (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const pendingById = selectPendingById(state);
|
||||
|
||||
if (!Object.keys(pendingById).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let publishCheckInterval;
|
||||
|
||||
const checkFileList = () => {
|
||||
Lbry.claim_list().then(claims => {
|
||||
claims.forEach(claim => {
|
||||
// If it's confirmed, check if it was pending previously
|
||||
if (claim.confirmations > 0 && pendingById[claim.claim_id]) {
|
||||
delete pendingById[claim.claim_id];
|
||||
|
||||
// If it's confirmed, check if we should notify the user
|
||||
if (selectosNotificationsEnabled(getState())) {
|
||||
const notif = new window.Notification('LBRY Publish Complete', {
|
||||
body: `${claim.value.title} has been published to lbry://${claim.name}. Click here to view it`,
|
||||
silent: false,
|
||||
});
|
||||
notif.onclick = () => {
|
||||
dispatch(push(formatLbryUriForWeb(claim.permanent_url)));
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
|
||||
data: {
|
||||
claims,
|
||||
},
|
||||
});
|
||||
|
||||
if (!Object.keys(pendingById).length) {
|
||||
clearInterval(publishCheckInterval);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
publishCheckInterval = setInterval(() => {
|
||||
checkFileList();
|
||||
}, 30000);
|
||||
};
|
107
src/redux/reducers/publish.js
Normal file
107
src/redux/reducers/publish.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
// @flow
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
import { buildURI } from 'lbryURI';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
|
||||
import { CHANNEL_ANONYMOUS } from 'constants/claim';
|
||||
|
||||
type PublishState = {
|
||||
editingURI: ?string,
|
||||
filePath: ?string,
|
||||
contentIsFree: boolean,
|
||||
fee: {
|
||||
amount: number,
|
||||
currency: string,
|
||||
},
|
||||
title: string,
|
||||
thumbnail_url: string,
|
||||
thumbnailPath: string,
|
||||
uploadThumbnailStatus: string,
|
||||
description: string,
|
||||
language: string,
|
||||
channel: string,
|
||||
channelId: ?string,
|
||||
name: string,
|
||||
nameError: ?string,
|
||||
bid: number,
|
||||
bidError: ?string,
|
||||
otherLicenseDescription: string,
|
||||
licenseUrl: string,
|
||||
};
|
||||
|
||||
const defaultState: PublishState = {
|
||||
editingURI: undefined,
|
||||
filePath: undefined,
|
||||
contentIsFree: true,
|
||||
fee: {
|
||||
amount: 1,
|
||||
currency: 'LBC',
|
||||
},
|
||||
title: '',
|
||||
thumbnail_url: '',
|
||||
thumbnailPath: '',
|
||||
uploadThumbnailStatus: THUMBNAIL_STATUSES.API_DOWN,
|
||||
description: '',
|
||||
language: 'en',
|
||||
nsfw: false,
|
||||
channel: CHANNEL_ANONYMOUS,
|
||||
channelId: '',
|
||||
name: '',
|
||||
nameError: undefined,
|
||||
bid: 0.1,
|
||||
bidError: undefined,
|
||||
licenseType: 'None',
|
||||
otherLicenseDescription: 'All rights reserved',
|
||||
licenseUrl: '',
|
||||
publishing: false,
|
||||
publishSuccess: false,
|
||||
publishError: undefined,
|
||||
};
|
||||
|
||||
export const publishReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.UPDATE_PUBLISH_FORM]: (state, action): PublishState => {
|
||||
const { data } = action;
|
||||
return {
|
||||
...state,
|
||||
...data,
|
||||
};
|
||||
},
|
||||
[ACTIONS.CLEAR_PUBLISH]: (): PublishState => ({
|
||||
...defaultState,
|
||||
}),
|
||||
[ACTIONS.PUBLISH_START]: (state: PublishState): PublishState => ({
|
||||
...state,
|
||||
publishing: true,
|
||||
publishSuccess: false,
|
||||
}),
|
||||
[ACTIONS.PUBLISH_FAIL]: (state: PublishState): PublishState => ({
|
||||
...state,
|
||||
publishing: false,
|
||||
}),
|
||||
[ACTIONS.PUBLISH_SUCCESS]: (state: PublishState): PublishState => ({
|
||||
...state,
|
||||
publishing: false,
|
||||
publishSuccess: true,
|
||||
}),
|
||||
[ACTIONS.DO_PREPARE_EDIT]: (state: PublishState, action) => {
|
||||
const { ...publishData } = action.data;
|
||||
const { channel, name, uri } = publishData;
|
||||
|
||||
// The short uri is what is presented to the user
|
||||
// The editingUri is the full uri with claim id
|
||||
const shortUri = buildURI({
|
||||
channelName: channel,
|
||||
contentName: name,
|
||||
});
|
||||
|
||||
return {
|
||||
...defaultState,
|
||||
...publishData,
|
||||
editingURI: uri,
|
||||
uri: shortUri,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
105
src/redux/selectors/publish.js
Normal file
105
src/redux/selectors/publish.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import { buildURI, parseURI } from 'lbryURI';
|
||||
import {
|
||||
selectClaimsById,
|
||||
selectMyClaimsWithoutChannels,
|
||||
selectResolvingUris,
|
||||
selectClaimsByUri,
|
||||
} from 'redux/selectors/claims';
|
||||
|
||||
const selectState = state => state.publish || {};
|
||||
|
||||
export const selectPublishFormValues = createSelector(
|
||||
selectState,
|
||||
state => {
|
||||
const { pendingPublish, ...formValues } = state;
|
||||
return formValues;
|
||||
}
|
||||
);
|
||||
|
||||
// Is the current uri the same as the uri they clicked "edit" on
|
||||
export const selectIsStillEditing = createSelector(
|
||||
selectPublishFormValues,
|
||||
publishState => {
|
||||
const { editingURI, uri } = publishState;
|
||||
|
||||
if (!editingURI || !uri) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { isChannel: currentIsChannel, claimName: currentClaimName, contentName: currentContentName } = parseURI(uri);
|
||||
const { isChannel: editIsChannel, claimName: editClaimName, contentName: editContentName } = parseURI(editingURI);
|
||||
|
||||
// Depending on the previous/current use of a channel, we need to compare different things
|
||||
// ex: going from a channel to anonymous, the new uri won't return contentName, so we need to use claimName
|
||||
const currentName = currentIsChannel ? currentContentName : currentClaimName;
|
||||
const editName = editIsChannel ? editContentName : editClaimName;
|
||||
return currentName === editName;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectMyClaimForUri = createSelector(
|
||||
selectPublishFormValues,
|
||||
selectIsStillEditing,
|
||||
selectClaimsById,
|
||||
selectMyClaimsWithoutChannels,
|
||||
({ editingURI, uri }, isStillEditing, claimsById, myClaims) => {
|
||||
const { contentName, claimName } = parseURI(uri);
|
||||
const { claimId: editClaimId } = parseURI(editingURI);
|
||||
|
||||
// If isStillEditing
|
||||
// They clicked "edit" from the file page
|
||||
// They haven't changed the channel/name after clicking edit
|
||||
// Get the claim so they can edit without re-uploading a new file
|
||||
return isStillEditing
|
||||
? claimsById[editClaimId]
|
||||
: myClaims.find(claim =>
|
||||
!contentName ? claim.name === claimName : claim.name === contentName || claim.name === claimName
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const selectIsResolvingPublishUris = createSelector(
|
||||
selectState,
|
||||
selectResolvingUris,
|
||||
({ uri, name }, resolvingUris) => {
|
||||
if (uri) {
|
||||
const isResolvingUri = resolvingUris.includes(uri);
|
||||
const { isChannel } = parseURI(uri);
|
||||
|
||||
let isResolvingShortUri;
|
||||
if (isChannel) {
|
||||
const shortUri = buildURI({ contentName: name });
|
||||
isResolvingShortUri = resolvingUris.includes(shortUri);
|
||||
}
|
||||
|
||||
return isResolvingUri || isResolvingShortUri;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectTakeOverAmount = createSelector(
|
||||
selectState,
|
||||
selectMyClaimForUri,
|
||||
selectClaimsByUri,
|
||||
({ name }, myClaimForUri, claimsByUri) => {
|
||||
// We only care about the winning claim for the short uri
|
||||
const shortUri = buildURI({ contentName: name });
|
||||
const claimForShortUri = claimsByUri[shortUri];
|
||||
|
||||
if (!myClaimForUri && claimForShortUri) {
|
||||
return claimForShortUri.effective_amount;
|
||||
} else if (myClaimForUri && claimForShortUri) {
|
||||
// https://github.com/lbryio/lbry/issues/1476
|
||||
// We should check the current effective_amount on my claim to see how much additional lbc
|
||||
// is needed to win the claim. Currently this is not possible during a takeover.
|
||||
// With this, we could say something like, "You have x lbc in support, if you bid y additional LBC you will control the claim"
|
||||
// For now just ignore supports. We will just show the winning claim's bid amount
|
||||
return claimForShortUri.effective_amount || claimForShortUri.amount;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
13
src/util/uri.js
Normal file
13
src/util/uri.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
// @flow
|
||||
import { parseURI } from 'lbryURI';
|
||||
|
||||
export const formatLbryUriForWeb = (uri: string) => {
|
||||
const { claimName, claimId } = parseURI(uri);
|
||||
|
||||
let webUrl = `/${claimName}`;
|
||||
if (claimId) {
|
||||
webUrl += `/${claimId}`;
|
||||
}
|
||||
|
||||
return webUrl;
|
||||
};
|
Loading…
Reference in a new issue