Select thumbnail, spee.ch upload #1248

Merged
daovist merged 25 commits from select-thumbnail into master 2018-06-08 06:46:55 +02:00
13 changed files with 322 additions and 26 deletions

View file

@ -48,7 +48,7 @@
"formik": "^0.10.4",
"hast-util-sanitize": "^1.1.2",
"keytar": "^4.2.1",
"lbry-redux": "lbryio/lbry-redux#543af2fcee7e4c45ccaf73af7b47d4b1a5d8ad44",
"lbry-redux": "lbryio/lbry-redux#7759bc6e8c482bed173d1f10aee6f6f9a439a15a",
"localforage": "^1.7.1",
"mixpanel-browser": "^2.17.1",
"moment": "^2.22.0",

View file

@ -9,6 +9,8 @@ type Props = {
type: string,
currentPath: ?string,
onFileChosen: (string, string) => void,
fileLabel: ?string,
directoryLabel?: string,
};
class FileSelector extends React.PureComponent<Props> {
@ -47,15 +49,14 @@ class FileSelector extends React.PureComponent<Props> {
input: ?HTMLInputElement;
render() {
const { type, currentPath } = this.props;
const { type, currentPath, fileLabel, directoryLabel } = this.props;
const label =
type === 'file' ? fileLabel || __('Choose File') : directoryLabel || __('Choose Directory');
return (
<FormRow verticallyCentered padded>
<Button
button="primary"
onClick={() => this.handleButtonClick()}
label={type === 'file' ? __('Choose File') : __('Choose Directory')}
/>
<Button button="primary" onClick={() => this.handleButtonClick()} label={label} />
<input
webkitdirectory="true"
className="input-copyable"

View file

@ -7,6 +7,7 @@ import ChannelSection from 'component/selectChannel';
import classnames from 'classnames';
import type { PublishParams, UpdatePublishFormData } from 'redux/reducers/publish';
import FileSelector from 'component/common/file-selector';
import SelectThumbnail from 'component/selectThumbnail';
import { COPYRIGHT, OTHER } from 'constants/licenses';
import { CHANNEL_NEW, CHANNEL_ANONYMOUS, MINIMUM_PUBLISH_BID } from 'constants/claim';
import * as icons from 'constants/icons';
@ -21,6 +22,8 @@ type Props = {
editingURI: ?string,
title: ?string,
thumbnail: ?string,
uploadThumbnailStatus: ?string,
thumbnailPath: ?string,
description: ?string,
language: string,
nsfw: boolean,
@ -49,7 +52,8 @@ type Props = {
clearPublish: () => void,
resolveUri: string => void,
scrollToTop: () => void,
prepareEdit: ({}, string) => void,
prepareEdit: ({}) => void,
resetThumbnailStatus: () => void,
};
class PublishForm extends React.PureComponent<Props> {
@ -67,7 +71,10 @@ class PublishForm extends React.PureComponent<Props> {
(this: any).getNewUri = this.getNewUri.bind(this);
}
// Returns a new uri to be used in the form and begins to resolve that uri for bid help text
componentWillMount() {
this.props.resetThumbnailStatus();
}
skhameneh commented 2018-05-30 06:33:13 +02:00 (Migrated from github.com)
Review

This method is being deprecated, it's good to start using getDerivedStateFromProps()

https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

This method is being deprecated, it's good to start using `getDerivedStateFromProps()` https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
getNewUri(name: string, channel: string) {
const { resolveUri } = this.props;
// If they are midway through a channel creation, treat it as anonymous until it completes
@ -267,6 +274,7 @@ class PublishForm extends React.PureComponent<Props> {
editingURI,
title,
thumbnail,
uploadThumbnailStatus,
description,
language,
nsfw,
@ -289,6 +297,8 @@ class PublishForm extends React.PureComponent<Props> {
bidError,
publishing,
clearPublish,
thumbnailPath,
resetThumbnailStatus,
} = this.props;
const formDisabled = (!filePath && !editingURI) || publishing;
@ -349,18 +359,6 @@ class PublishForm extends React.PureComponent<Props> {
onChange={e => updatePublishForm({ title: e.target.value })}
/>
</FormRow>
<FormRow padded>
<FormField
stretch
type="text"
name="content_thumbnail"
label={__('Thumbnail')}
placeholder="http://spee.ch/mylogo"
value={thumbnail}
disabled={formDisabled}
onChange={e => updatePublishForm({ thumbnail: e.target.value })}
/>
</FormRow>
<FormRow padded>
<FormField
stretch
@ -375,6 +373,24 @@ class PublishForm extends React.PureComponent<Props> {
</FormRow>
</section>
<section className="card card--section">
<div className="card__title">{__('Thumbnail')}</div>
<div className="card__subtitle">
{__(
'Upload your thumbnail to spee.ch, or enter the url manually. Learn more about spee.ch '
)}
<Button button="link" label={__('here')} href="https://spee.ch/about" />.
</div>
<SelectThumbnail
thumbnailPath={thumbnailPath}
thumbnail={thumbnail}
uploadThumbnailStatus={uploadThumbnailStatus}
updatePublishForm={updatePublishForm}
formDisabled={formDisabled}
resetThumbnailStatus={resetThumbnailStatus}
/>
</section>
<section className="card card--section">
<div className="card__title">{__('Price')}</div>
<div className="card__subtitle">{__('How much will this content cost?')}</div>

View file

@ -0,0 +1,12 @@
import { connect } from 'react-redux';
import { doNotify } from 'lbry-redux';
import SelectThumbnail from './view';
const perform = dispatch => ({
openModal: (modal, props) => dispatch(doNotify(modal, props)),
});
export default connect(
null,
perform
)(SelectThumbnail);

View file

@ -0,0 +1,88 @@
// @flow
import { STATUSES, MODALS } from 'lbry-redux';
import React from 'react';
import { FormField, FormRow } from 'component/common/form';
import FileSelector from 'component/common/file-selector';
import Button from 'component/button';
type Props = {
thumbnail: ?string,
formDisabled: boolean,
uploadThumbnailStatus: string,
thumbnailPath: ?string,
openModal: ({ id: string }, {}) => void,
updatePublishForm: ({}) => void,
resetThumbnailStatus: () => void,
};
class SelectThumbnail extends React.PureComponent<Props> {
render() {
const {
thumbnail,
formDisabled,
uploadThumbnailStatus: status,
openModal,
updatePublishForm,
thumbnailPath,
resetThumbnailStatus,
} = this.props;
return (
<div>
{status === STATUSES.API_DOWN || status === STATUSES.MANUAL ? (
<FormRow padded>
<FormField
stretch
type="text"
name="content_thumbnail"
label={__('Url')}
placeholder="http://spee.ch/mylogo"
value={thumbnail}
disabled={formDisabled}
onChange={e => updatePublishForm({ thumbnail: e.target.value })}
/>
</FormRow>
) : (
<div className="form-row--padded">
{(status === STATUSES.READY || status === STATUSES.COMPLETE) && (
<FileSelector
currentPath={thumbnailPath}
fileLabel={__('Choose Thumbnail')}
onFileChosen={path => openModal({ id: MODALS.CONFIRM_THUMBNAIL_UPLOAD }, { path })}
/>
)}
{status === STATUSES.COMPLETE && (
<div>
<p>
Upload complete. View it{' '}
<Button button="link" href={thumbnail} label={__('here')} />.
</p>
<Button button="link" label={__('New thumbnail')} onClick={resetThumbnailStatus} />
</div>
)}
</div>
)}
<div className="card__actions">
{status === STATUSES.READY && (
<Button
button="link"
label={__('Or enter a URL manually')}
onClick={() => updatePublishForm({ uploadThumbnailStatus: STATUSES.MANUAL })}
/>
)}
{status === STATUSES.MANUAL && (
<Button
button="link"
label={__('Use thumbnail upload tool')}
onClick={() => updatePublishForm({ uploadThumbnailStatus: STATUSES.READY })}
/>
)}
</div>
{status === STATUSES.IN_PROGRESS && <p>{__('Uploading thumbnail')}...</p>}
</div>
);
}
}
export default SelectThumbnail;

View file

@ -0,0 +1,5 @@
export const API_DOWN = 'apiDown';
export const READY = 'ready';
export const IN_PROGRESS = 'inProgress';
export const COMPLETE = 'complete';
export const MANUAL = 'manual';

View file

@ -0,0 +1,21 @@
import { connect } from 'react-redux';
import { doHideNotification } from 'lbry-redux';
import { doUploadThumbnail, doUpdatePublishForm } from 'redux/actions/publish';
import { selectPublishFormValues } from 'redux/selectors/publish';
import ModalConfirmThumbnailUpload from './view';
const select = state => {
const publishState = selectPublishFormValues(state);
return { nsfw: publishState.nsfw };
};
const perform = dispatch => ({
closeModal: () => dispatch(doHideNotification()),
upload: (path, nsfw = false) => dispatch(doUploadThumbnail(path, nsfw)),
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
});
export default connect(
select,
perform
)(ModalConfirmThumbnailUpload);

View file

@ -0,0 +1,48 @@
// @flow
import React from 'react';
import { Modal } from 'modal/modal';
import { FormField } from 'component/common/form';
type Props = {
upload: (string, boolean) => void,
path: string,
nsfw: boolean,
closeModal: () => void,
updatePublishForm: ({}) => void,
};
class ModalConfirmThumbnailUpload extends React.PureComponent<Props> {
upload() {
const { upload, updatePublishForm, closeModal, path, nsfw } = this.props;
upload(path, nsfw);
updatePublishForm({ thumbnailPath: path });
closeModal();
}
render() {
const { closeModal, path, updatePublishForm, nsfw } = this.props;
return (
<Modal
isOpen
contentLabel={__('Confirm Thumbnail Upload')}
type="confirm"
confirmButtonLabel={__('Upload')}
onConfirmed={() => this.upload()}
onAborted={closeModal}
>
<p>{__('Are you sure you want to upload this thumbnail to spee.ch')}?</p>
<blockquote>{path}</blockquote>
<FormField
type="checkbox"
name="content_is_mature"
postfix={__('Mature audiences only')}
checked={nsfw}
onChange={event => updatePublishForm({ nsfw: event.target.checked })}
/>
</Modal>
);
}
}
export default ModalConfirmThumbnailUpload;

View file

@ -22,8 +22,13 @@ import ModalConfirmTransaction from 'modal/modalConfirmTransaction';
import ModalSendTip from '../modalSendTip';
import ModalPublish from '../modalPublish';
import ModalOpenExternalLink from '../modalOpenExternalLink';
import ModalConfirmThumbnailUpload from 'modal/modalConfirmThumbnailUpload';
class ModalRouter extends React.PureComponent {
type Props = {
modal: string,
};
class ModalRouter extends React.PureComponent<Props> {
constructor(props) {
super(props);
@ -56,7 +61,7 @@ class ModalRouter extends React.PureComponent {
if (
transitionModal &&
(transitionModal != this.state.lastTransitionModal || page != this.state.lastTransitionPage)
(transitionModal !== this.state.lastTransitionModal || page !== this.state.lastTransitionPage)
) {
openModal({ id: transitionModal });
this.setState({
@ -158,6 +163,8 @@ class ModalRouter extends React.PureComponent {
return <ModalOpenExternalLink {...notificationProps} />;
case MODALS.CONFIRM_TRANSACTION:
return <ModalConfirmTransaction {...notificationProps} />;
case MODALS.CONFIRM_THUMBNAIL_UPLOAD:
return <ModalConfirmThumbnailUpload {...notificationProps} />;
default:
return null;
}

View file

@ -10,6 +10,7 @@ import {
import { doNavigate } from 'redux/actions/navigation';
import { selectPublishFormValues } from 'redux/selectors/publish';
import {
doResetThumbnailStatus,
doClearPublish,
doUpdatePublishForm,
doPublish,
@ -55,7 +56,8 @@ const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)),
publish: params => dispatch(doPublish(params)),
navigate: path => dispatch(doNavigate(path)),
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
prepareEdit: claim => dispatch(doPrepareEdit(claim)),
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
});
export default connect(select, perform)(PublishPage);

View file

@ -6,6 +6,8 @@ import {
doNotify,
MODALS,
selectMyChannelClaims,
STATUSES,
batchActions,
} from 'lbry-redux';
import { selectPendingPublishes } from 'redux/selectors/publish';
import type {
@ -13,6 +15,8 @@ import type {
UpdatePublishFormAction,
PublishParams,
} from 'redux/reducers/publish';
import fs from 'fs';
import path from 'path';
type Action = UpdatePublishFormAction | { type: ACTIONS.CLEAR_PUBLISH };
type PromiseAction = Promise<Action>;
@ -30,6 +34,90 @@ export const doUpdatePublishForm = (publishFormValue: UpdatePublishFormData) =>
data: { ...publishFormValue },
});
neb-b commented 2018-06-04 21:00:11 +02:00 (Migrated from github.com)
Review

Is this the correct path?

Is this the correct path?
export const doResetThumbnailStatus = () => (dispatch: Dispatch): PromiseAction => {
dispatch({
type: ACTIONS.UPDATE_PUBLISH_FORM,
data: {
thumbnailPath: '',
},
});
return fetch('https://spee.ch/api/channel/availability/@testing')
.then(() =>
dispatch({
type: ACTIONS.UPDATE_PUBLISH_FORM,
data: {
uploadThumbnailStatus: STATUSES.READY,
thumbnail: '',
nsfw: false,
},
})
)
.catch(() =>
dispatch({
type: ACTIONS.UPDATE_PUBLISH_FORM,
data: {
uploadThumbnailStatus: STATUSES.API_DOWN,
thumbnail: '',
nsfw: false,
},
})
);
};
export const doUploadThumbnail = (filePath: string, nsfw: boolean) => (dispatch: Dispatch) => {
const thumbnail = fs.readFileSync(filePath);
const fileExt = path.extname(filePath);
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: STATUSES.API_DOWN },
},
dispatch(doNotify({ id: MODALS.ERROR, error }))
)
);
dispatch({
type: ACTIONS.UPDATE_PUBLISH_FORM,
data: { uploadThumbnailStatus: STATUSES.IN_PROGRESS },
});
skhameneh commented 2018-05-30 06:34:46 +02:00 (Migrated from github.com)
Review

Can you split this line out with brackets for legibility?

Can you split this line out with brackets for legibility?
const data = new FormData();
const name = makeid();
const blob = new Blob([thumbnail], { type: `image/${fileExt.slice(1)}` });
data.append('name', name);
data.append('file', blob);
data.append('nsfw', nsfw.toString());
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: STATUSES.COMPLETE,
thumbnail: `${json.data.url}${fileExt}`,
},
})
: uploadError('Upload failed')
skhameneh commented 2018-05-30 06:36:06 +02:00 (Migrated from github.com)
Review

Can you clean up some of the formatting from ln90 to 110?
Line breaks, indentation, etc

Can you clean up some of the formatting from ln90 to 110? Line breaks, indentation, etc
)
.catch(err => uploadError(err.message));
};
export const doPrepareEdit = (claim: any, uri: string) => (dispatch: Dispatch) => {
const {
name,
@ -39,6 +127,7 @@ export const doPrepareEdit = (claim: any, uri: string) => (dispatch: Dispatch) =
stream: { metadata },
},
} = claim;
const {
author,
description,

View file

@ -2,6 +2,7 @@
import { handleActions } from 'util/redux-utils';
import { buildURI } from 'lbry-redux';
import * as ACTIONS from 'constants/action_types';
import * as STATUSES from 'constants/thumbnail_upload_statuses';
import { CHANNEL_ANONYMOUS } from 'constants/claim';
type PublishState = {
@ -14,6 +15,8 @@ type PublishState = {
},
title: string,
thumbnail: string,
thumbnailPath: string,
uploadThumbnailStatus: string,
description: string,
language: string,
tosAccepted: boolean,
@ -38,6 +41,8 @@ export type UpdatePublishFormData = {
},
title?: string,
thumbnail?: string,
uploadThumbnailStatus?: string,
thumbnailPath?: string,
description?: string,
language?: string,
tosAccepted?: boolean,
@ -96,6 +101,8 @@ const defaultState: PublishState = {
},
title: '',
thumbnail: '',
thumbnailPath: '',
uploadThumbnailStatus: STATUSES.API_DOWN,
description: '',
language: 'en',
nsfw: false,

View file

@ -5647,9 +5647,9 @@ lazy-val@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.3.tgz#bb97b200ef00801d94c317e29dc6ed39e31c5edc"
lbry-redux@lbryio/lbry-redux#543af2fcee7e4c45ccaf73af7b47d4b1a5d8ad44:
lbry-redux@lbryio/lbry-redux#7759bc6e8c482bed173d1f10aee6f6f9a439a15a:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/543af2fcee7e4c45ccaf73af7b47d4b1a5d8ad44"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/7759bc6e8c482bed173d1f10aee6f6f9a439a15a"
dependencies:
proxy-polyfill "0.1.6"
reselect "^3.0.0"