file selector operational for thumbs, publishes, thumb generation, channel edits

This commit is contained in:
jessop 2019-10-07 16:02:32 -04:00 committed by Sean Yesmunt
parent bd1d32e5d5
commit f8ea6164f7
11 changed files with 92 additions and 135 deletions

4
flow-typed/web-file.js vendored Normal file
View file

@ -0,0 +1,4 @@
declare type WebFile = {
name: string,
path?: string,
}

View file

@ -1,27 +1,18 @@
// @flow
import * as React from 'react';
// @if TARGET='app'
// $FlowFixMe
import { remote } from 'electron';
// @endif
import Button from 'component/button';
import { FormField } from 'component/common/form';
import path from 'path';
type FileFilters = {
name: string,
extensions: string[],
};
type Props = {
type: string,
currentPath?: ?string,
onFileChosen: (string, string) => void,
onFileChosen: WebFile => void,
label?: string,
placeholder?: string,
fileLabel?: string,
directoryLabel?: string,
filters?: FileFilters[],
accept?: string,
};
class FileSelector extends React.PureComponent<Props> {
@ -33,78 +24,53 @@ class FileSelector extends React.PureComponent<Props> {
constructor() {
super();
// @if TARGET='web'
this.fileInput = React.createRef();
// @endif
this.handleFileInputSelection = this.handleFileInputSelection.bind(this);
this.fileInputButton = this.fileInputButton.bind(this);
}
handleButtonClick() {
remote.dialog.showOpenDialog(
remote.getCurrentWindow(),
{
properties: this.props.type === 'file' ? ['openFile'] : ['openDirectory', 'createDirectory'],
filters: this.props.filters,
},
paths => {
if (!paths) {
// User hit cancel, so do nothing
return;
}
const filePath = paths[0];
const extension = path.extname(filePath);
const fileName = path.basename(filePath, extension);
if (this.props.onFileChosen) {
this.props.onFileChosen(filePath, fileName);
}
}
);
}
// TODO: Add this back for web publishing
handleFileInputSelection() {
handleFileInputSelection = () => {
const { files } = this.fileInput.current;
if (!files) {
return;
}
const filePath = files[0];
const fileName = filePath.name;
const file = files[0];
if (this.props.onFileChosen) {
this.props.onFileChosen(filePath, fileName);
this.props.onFileChosen(file);
}
}
};
fileInputButton = () => {
this.fileInput.current.click();
};
input: ?HTMLInputElement;
render() {
const { type, currentPath, label, fileLabel, directoryLabel, placeholder } = this.props;
const { type, currentPath, label, fileLabel, directoryLabel, placeholder, accept } = this.props;
const buttonLabel = type === 'file' ? fileLabel || __('Choose File') : directoryLabel || __('Choose Directory');
const placeHolder = currentPath || placeholder;
return (
<React.Fragment>
{/* @if TARGET='app' */}
<FormField
label={label}
webkitdirectory="true"
className="form-field--copyable"
type="text"
ref={this.fileInput}
onFocus={() => {
if (this.fileInput) this.fileInput.select();
}}
readOnly="readonly"
value={currentPath || placeholder || __('Choose a file')}
inputButton={<Button button="primary" onClick={() => this.handleButtonClick()} label={buttonLabel} />}
value={placeHolder || __('Choose a file')}
inputButton={<Button button="primary" onClick={this.fileInputButton} label={buttonLabel} />}
/>
<input
type={'file'}
style={{ display: 'none' }}
accept={accept}
ref={this.fileInput}
onChange={() => this.handleFileInputSelection()}
webkitdirectory={type === 'openDirectory' ? 'True' : null}
/>
{/* @endif */}
{/* @if TARGET='web' */}
<input type="file" ref={this.fileInput} onChange={() => this.handleFileInputSelection()} />
{/* @endif */}
</React.Fragment>
);
}

View file

@ -8,7 +8,7 @@ import Card from 'component/common/card';
type Props = {
name: ?string,
filePath: ?string,
filePath: string | WebFile,
isStillEditing: boolean,
balance: number,
updatePublishForm: ({}) => void,
@ -18,13 +18,26 @@ type Props = {
function PublishFile(props: Props) {
const { name, balance, filePath, isStillEditing, updatePublishForm, disabled } = props;
function handleFileChange(filePath: string, fileName: string) {
const publishFormParams: { filePath: string, name?: string } = { filePath };
if (!name) {
const parsedFileName = fileName.replace(regexInvalidURI, '');
publishFormParams.name = parsedFileName.replace(' ', '-');
let currentFile = '';
if (filePath) {
if (typeof filePath === 'string') {
currentFile = filePath;
} else {
currentFile = filePath.name;
}
}
function handleFileChange(file: WebFile) {
// if electron, we'll set filePath to the path string because SDK is handling publishing.
// if web, we set the filePath (dumb name) to the File() object
// file.path will be undefined from web due to browser security, so it will default to the File Object.
const publishFormParams: { filePath: string | WebFile, name?: string } = {
filePath: file.path || file,
name: file.name,
};
const parsedFileName = file.name.replace(regexInvalidURI, '');
publishFormParams.name = parsedFileName.replace(' ', '-');
updatePublishForm(publishFormParams);
}
@ -39,7 +52,7 @@ function PublishFile(props: Props) {
}
actions={
<React.Fragment>
<FileSelector currentPath={filePath} onFileChosen={handleFileChange} />
<FileSelector currentPath={currentFile} onFileChosen={handleFileChange} />
{!isStillEditing && (
<p className="help">
{__('For video content, use MP4s in H264/AAC format for best compatibility.')}{' '}

View file

@ -4,18 +4,10 @@ import React, { useState } from 'react';
import { FormField } from 'component/common/form';
import FileSelector from 'component/common/file-selector';
import Button from 'component/button';
// @if TARGET='app'
import fs from 'fs';
// @endif
import path from 'path';
import { SPEECH_URLS } from 'lbry-redux';
import uuid from 'uuid/v4';
const filters = [
{
name: __('Thumbnail Image'),
extensions: ['png', 'jpg', 'jpeg', 'gif'],
},
];
const accept = '.png, .jpg, .jpeg, .gif';
const SOURCE_URL = 'url';
const SOURCE_UPLOAD = 'upload';
@ -32,29 +24,10 @@ function SelectAsset(props: Props) {
const { onUpdate, assetName, currentValue, recommended } = props;
const [assetSource, setAssetSource] = useState(SOURCE_URL);
const [pathSelected, setPathSelected] = useState('');
const [fileSelected, setFileSelected] = useState(null);
const [uploadStatus, setUploadStatus] = useState(SPEECH_READY);
function doUploadAsset(filePath, thumbnailBuffer) {
let thumbnail, fileExt, fileName, fileType;
if (IS_WEB) {
console.error('no upload support for web');
return;
}
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;
}
function doUploadAsset(file) {
const uploadError = (error = '') => {
console.log('error', error);
};
@ -69,11 +42,10 @@ function SelectAsset(props: Props) {
const data = new FormData();
const name = uuid();
const file = new File([thumbnail], fileName, { type: fileType });
data.append('name', name);
data.append('file', file);
return fetch('https://spee.ch/api/claim/publish', {
return fetch(SPEECH_URLS.SPEECH_PUBLISH, {
method: 'POST',
body: data,
})
@ -104,22 +76,26 @@ function SelectAsset(props: Props) {
<FileSelector
label={'File to upload'}
name={'assetSelector'}
onFileChosen={path => {
setPathSelected(path);
onFileChosen={file => {
if (file.name) {
setPathSelected(file.path);
setFileSelected(file);
}
}}
filters={filters}
accept={accept}
/>
)}
{pathSelected && (
<div>
{`...${pathSelected.slice(-18)}`} {uploadStatus}{' '}
<Button button={'primary'} onClick={() => doUploadAsset(pathSelected)}>
<Button button={'primary'} onClick={() => doUploadAsset(fileSelected)}>
Upload
</Button>{' '}
<Button
button={'secondary'}
onClick={() => {
setPathSelected('');
setFileSelected(null);
}}
>
Clear

View file

@ -25,13 +25,6 @@ type State = {
thumbnailError: boolean,
};
const filters = [
{
name: __('Thumbnail Image'),
extensions: ['png', 'jpg', 'jpeg', 'gif'],
},
];
class SelectThumbnail extends React.PureComponent<Props, State> {
constructor() {
super();
@ -66,13 +59,19 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
} = this.props;
const { thumbnailError } = this.state;
const accept = '.png, .jpg, .jpeg, .gif';
const outpoint = myClaimForUri ? `${myClaimForUri.txid}:${myClaimForUri.nout}` : undefined;
const fileInfo = outpoint ? fileInfos[outpoint] : undefined;
const downloadPath = fileInfo ? fileInfo.download_path : undefined;
const actualFilePath = filePath || downloadPath;
const isSupportedVideo = Lbry.getMediaType(null, actualFilePath) === 'video';
let isSupportedVideo = false;
if (typeof filePath === 'string') {
isSupportedVideo = Lbry.getMediaType(null, actualFilePath) === 'video';
} else if (filePath && filePath.type) {
isSupportedVideo = filePath.type.split('/')[0] === 'video';
}
let thumbnailSrc;
if (!thumbnail) {
@ -132,8 +131,8 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
currentPath={thumbnailPath}
label={__('Thumbnail')}
placeholder={__('Choose a thumbnail')}
filters={filters}
onFileChosen={path => openModal(MODALS.CONFIRM_THUMBNAIL_UPLOAD, { path })}
accept={accept}
onFileChosen={file => openModal(MODALS.CONFIRM_THUMBNAIL_UPLOAD, { file })}
/>
)}
{status === THUMBNAIL_STATUSES.COMPLETE && thumbnail && (

View file

@ -1,13 +1,11 @@
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import { doToast, doUploadThumbnail } from 'lbry-redux';
import fs from 'fs';
import path from 'path';
import ModalAutoGenerateThumbnail from './view';
const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()),
upload: buffer => dispatch(doUploadThumbnail(null, buffer, null, fs, path)),
upload: file => dispatch(doUploadThumbnail(null, file, null, null, 'Generated')),
showToast: options => dispatch(doToast(options)),
});

View file

@ -4,7 +4,7 @@ import { Modal } from 'modal/modal';
import { formatPathForWeb } from 'util/uri';
type Props = {
upload: Buffer => void,
upload: WebFile => void,
filePath: string,
closeModal: () => void,
showToast: ({}) => void,
@ -13,12 +13,18 @@ type Props = {
function ModalAutoGenerateThumbnail(props: Props) {
const { closeModal, filePath, upload, showToast } = props;
const playerRef = useRef();
const videoSrc = formatPathForWeb(filePath);
let videoSrc;
if (typeof filePath === 'string') {
videoSrc = formatPathForWeb(filePath);
} else {
videoSrc = URL.createObjectURL(filePath);
}
function uploadImage() {
const imageBuffer = captureSnapshot();
const file = new File([imageBuffer], 'thumbnail.png', { type: 'image/png' });
if (imageBuffer) {
upload(imageBuffer);
upload(file);
closeModal();
} else {
onError();

View file

@ -1,13 +1,11 @@
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import { doUploadThumbnail, doUpdatePublishForm } from 'lbry-redux';
import fs from 'fs';
import path from 'path';
import ModalConfirmThumbnailUpload from './view';
const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()),
upload: filePath => dispatch(doUploadThumbnail(filePath, null, null, fs, path)),
upload: file => dispatch(doUploadThumbnail(null, file, null, null, file.path)),
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
});

View file

@ -3,22 +3,22 @@ import React from 'react';
import { Modal } from 'modal/modal';
type Props = {
upload: string => void,
path: string,
upload: WebFile => void,
file: WebFile,
closeModal: () => void,
updatePublishForm: ({}) => void,
};
class ModalConfirmThumbnailUpload extends React.PureComponent<Props> {
upload() {
const { upload, updatePublishForm, closeModal, path } = this.props;
upload(path);
updatePublishForm({ thumbnailPath: path });
const { upload, updatePublishForm, closeModal, file } = this.props;
upload(file);
updatePublishForm({ thumbnailPath: file.path });
closeModal();
}
render() {
const { closeModal, path } = this.props;
const { closeModal, file } = this.props;
return (
<Modal
@ -32,7 +32,7 @@ class ModalConfirmThumbnailUpload extends React.PureComponent<Props> {
>
<p>{__('Are you sure you want to upload this thumbnail to spee.ch')}?</p>
<blockquote>{path}</blockquote>
<blockquote>{file.path || file.name}</blockquote>
</Modal>
);
}

View file

@ -233,8 +233,8 @@ class SettingsPage extends React.PureComponent<Props, State> {
<FileSelector
type="openDirectory"
currentPath={daemonSettings.download_dir}
onFileChosen={(newDirectory: string) => {
setDaemonSetting('download_dir', newDirectory);
onFileChosen={(newDirectory: WebFile) => {
setDaemonSetting('download_dir', newDirectory.path);
}}
/>
<p className="help">{__('LBRY downloads will be saved here.')}</p>

View file

@ -710,9 +710,6 @@
"You have %credit_amount% in unclaimed rewards.": "You have %credit_amount% in unclaimed rewards.",
"URI does not include name.": "URI does not include name.",
"to fix it. If that doesn't work, press CMD/CTRL-R to reset to the homepage.": "to fix it. If that doesn't work, press CMD/CTRL-R to reset to the homepage.",
"Add Email": "Add Email",
"Sign Out": "Sign Out",
"Follow more tags": "Follow more tags",
"In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this channel from our applications.": "In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this channel from our applications.",
"Read More": "Read More",
"Subscribers": "Subscribers",