updates in component state

updates work

asset upload works

channelForm component
This commit is contained in:
jessop 2019-06-28 13:00:29 -04:00
parent c86b376279
commit 16bcaf7ceb
11 changed files with 467 additions and 40 deletions

View file

@ -0,0 +1,34 @@
import { connect } from 'react-redux';
import {
makeSelectTitleForUri,
makeSelectThumbnailForUri,
makeSelectCoverForUri,
selectCurrentChannelPage,
makeSelectMetadataItemForUri,
doUpdateChannel,
makeSelectAmountForUri,
} from 'lbry-redux';
import ChannelPage from './view';
const select = (state, props) => ({
title: makeSelectTitleForUri(props.uri)(state),
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
cover: makeSelectCoverForUri(props.uri)(state),
page: selectCurrentChannelPage(state),
description: makeSelectMetadataItemForUri(props.uri, 'description')(state),
website: makeSelectMetadataItemForUri(props.uri, 'website_url')(state),
email: makeSelectMetadataItemForUri(props.uri, 'email')(state),
tags: makeSelectMetadataItemForUri(props.uri, 'tags')(state),
locations: makeSelectMetadataItemForUri(props.uri, 'locations')(state),
languages: makeSelectMetadataItemForUri(props.uri, 'languages')(state),
amount: makeSelectAmountForUri(props.uri)(state),
});
const perform = dispatch => ({
updateChannel: params => dispatch(doUpdateChannel(params)),
});
export default connect(
select,
perform
)(ChannelPage);

View file

@ -0,0 +1,208 @@
// @flow
import React, { useState } from 'react';
import { parseURI } from 'lbry-redux';
import { Form, FormField } from 'component/common/form';
import Button from 'component/button';
import SelectAsset from '../../component/selectAsset/view';
type Props = {
uri: string,
title: ?string,
amount: string,
cover: ?string,
thumbnail: ?string,
location: { search: string },
description: string,
website: string,
email: string,
balance: number,
tags: Array<string>,
locations: Array<string>,
languages: Array<string>,
updateChannel: any => void,
updateThumb: string => void,
updateCover: string => void,
setEditing: boolean => void,
};
function ChannelForm(props: Props) {
const {
uri,
title,
cover,
description,
website,
email,
thumbnail,
tags,
locations,
languages,
amount,
updateChannel,
setEditing,
updateThumb,
updateCover,
} = props;
const { claimId } = parseURI(uri);
// fill this in with sdk data
const channelParams = {
website: website,
email: email,
languages: languages || [],
cover: cover,
description: description,
locations: locations || [],
title: title,
thumbnail: thumbnail,
tags: tags || [],
claim_id: claimId,
amount: amount,
};
const [params, setParams] = useState(channelParams);
const [bidError, setBidError] = useState('');
const MINIMUM_PUBLISH_BID = 0.00000001;
// If a user changes tabs, update the url so it stays on the same page if they refresh.
// We don't want to use links here because we can't animate the tab change and using links
// would alter the Tab label's role attribute, which should stay role="tab" to work with keyboards/screen readers.
const handleBidChange = (bid: number) => {
const { balance, amount } = props;
const totalAvailableBidAmount = parseFloat(amount) + parseFloat(balance);
setParams({ ...params, amount: bid });
setBidError('');
if (bid <= 0.0 || isNaN(bid)) {
setBidError(__('Deposit cannot be 0'));
} else if (totalAvailableBidAmount === bid) {
setBidError(__('Please decrease your deposit to account for transaction fees'));
} else if (totalAvailableBidAmount < bid) {
setBidError(__('Deposit cannot be higher than your balance'));
} else if (bid <= MINIMUM_PUBLISH_BID) {
setBidError(__('Your deposit must be higher'));
}
};
const handleThumbnailChange = (url: string) => {
setParams({ ...params, thumbnail: url });
updateThumb(url);
};
const handleCoverChange = (url: string) => {
setParams({ ...params, cover: url });
updateCover(url);
};
// TODO clear and bail after submit
return (
<div className={'card--section'}>
<section>
<div className={'card__title--flex-between'}>
<header className="card__header">
<h2 className="card__title">{__('Edit')}</h2>
</header>
<div className={'card__actions'}>
<Button button="primary" label={__('Submit')} onClick={() => updateChannel(params)} />
<Button
button="link"
label={__('Cancel')}
onClick={() => {
setParams({ ...channelParams });
setEditing(false);
}}
/>
</div>
</div>
<Form onSubmit={channelParams => updateChannel(channelParams)}>
<div className="card__content">
<SelectAsset
onUpdate={v => handleThumbnailChange(v)}
currentValue={params.thumbnail}
assetName={'Thumbnail'}
recommended={'(400x400)'}
/>
<SelectAsset
onUpdate={v => handleCoverChange(v)}
currentValue={params.cover}
assetName={'Cover'}
recommended={'(1000x300)'}
/>
<FormField
type="text"
name="channel_title2"
label={__('Title')}
placeholder={__('Titular Title')}
disabled={false}
value={params.title}
onChange={e => setParams({ ...params, title: e.target.value })}
/>
<FormField
className="form-field--price-amount"
type="number"
name="content_bid2"
step="any"
label={__('Deposit (LBC)')}
postfix="LBC"
value={params.amount}
error={bidError}
min="0.0"
disabled={false}
onChange={event => handleBidChange(parseFloat(event.target.value))}
placeholder={0.1}
// helper={
// <BidHelpText
// uri={shortUri}
// isResolvingUri={isResolvingUri}
// amountNeededForTakeover={amountNeededForTakeover}
// />
// }
/>
<FormField
type="text"
name="channel_website2"
label={__('Website')}
placeholder={__('aprettygoodsite.com')}
disabled={false}
value={params.website}
onChange={e => setParams({ ...params, website: e.target.value })}
/>
<FormField
type="text"
name="content_email2"
label={__('Email')}
placeholder={__('yourstruly@example.com')}
disabled={false}
value={params.email}
onChange={e => setParams({ ...params, email: e.target.value })}
/>
<FormField
type="markdown"
name="content_description2"
label={__('Description')}
placeholder={__('Description of your content')}
value={params.description}
disabled={false}
onChange={text => setParams({ ...params, description: text })}
/>
<div className={'card__actions'}>
<Button button="primary" label={__('Submit')} onClick={() => updateChannel(params)} />
<Button button="link" label={__('Cancel')} onClick={() => setParams({ ...channelParams })} />
</div>
</div>
</Form>
</section>
</div>
);
}
export default ChannelForm;

View file

@ -8,10 +8,11 @@ type Props = {
thumbnail: ?string, thumbnail: ?string,
uri: string, uri: string,
className?: string, className?: string,
thumbnailPreview: ?string,
}; };
function ChannelThumbnail(props: Props) { function ChannelThumbnail(props: Props) {
const { thumbnail, uri, className } = props; const { thumbnail, uri, className, thumbnailPreview } = props;
// Generate a random color class based on the first letter of the channel name // Generate a random color class based on the first letter of the channel name
const { channelName } = parseURI(uri); const { channelName } = parseURI(uri);
@ -24,8 +25,8 @@ function ChannelThumbnail(props: Props) {
[colorClassName]: !thumbnail, [colorClassName]: !thumbnail,
})} })}
> >
{!thumbnail && <img className="channel-thumbnail__default" src={Gerbil} />} {!thumbnail && <img className="channel-thumbnail__default" src={thumbnailPreview || Gerbil} />}
{thumbnail && <img className="channel-thumbnail__custom" src={thumbnail} />} {thumbnail && <img className="channel-thumbnail__custom" src={thumbnailPreview || thumbnail} />}
</div> </div>
); );
} }

View file

@ -12,7 +12,7 @@ type FileFilters = {
type Props = { type Props = {
type: string, type: string,
currentPath: ?string, currentPath?: ?string,
onFileChosen: (string, string) => void, onFileChosen: (string, string) => void,
label?: string, label?: string,
placeholder?: string, placeholder?: string,

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,142 @@
// @flow
import React, { useState } from 'react';
import { FormField } from 'component/common/form';
import FileSelector from 'component/common/file-selector';
import Button from 'component/button';
import fs from 'fs';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
const filters = [
{
name: __('Thumbnail Image'),
extensions: ['png', 'jpg', 'jpeg', 'gif'],
},
];
const SOURCE_URL = 'url';
const SOURCE_UPLOAD = 'upload';
const SPEECH_READY = 'READY';
const SPEECH_UPLOADING = 'UPLOADING';
type Props = {
assetName: string,
currentValue: ?string,
onUpdate: string => void,
recommended: string,
};
function SelectAsset(props: Props) {
const { onUpdate, assetName, currentValue, recommended } = props;
const [assetSource, setAssetSource] = useState(SOURCE_URL);
const [pathSelected, setPathSelected] = useState('');
const [uploadStatus, setUploadStatus] = useState(SPEECH_READY);
function doUploadAsset(filePath, thumbnailBuffer) {
let thumbnail, fileExt, fileName, fileType;
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 uploadError = (error = '') => {
console.log('error', error);
};
const setUrl = path => {
setUploadStatus(SPEECH_READY);
onUpdate(path);
setAssetSource(SOURCE_URL);
};
setUploadStatus(SPEECH_UPLOADING);
const data = new FormData();
const name = uuidv4();
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 ? setUrl(`${json.data.serveUrl}`) : uploadError(json.message)))
.catch(err => uploadError(err.message));
}
return (
<fieldset-section>
<fieldset-group className="fieldset-group--smushed">
<FormField
type="select"
name={assetName}
value={assetSource}
onChange={e => setAssetSource(e.target.value)}
label={__(assetName + ' source')}
>
<option key={'lmmnop'} value={'url'}>
URL
</option>
<option key={'lmmnopq'} value={'upload'}>
UPLOAD
</option>
</FormField>
{assetSource === SOURCE_UPLOAD && (
<>
{!pathSelected && (
<FileSelector
label={'File to upload'}
name={'assetSelector'}
onFileChosen={path => {
setPathSelected(path);
}}
filters={filters}
/>
)}
{pathSelected && (
<div>
{`...${pathSelected.slice(-18)}`} {uploadStatus}{' '}
<Button button={'primary'} onClick={() => doUploadAsset(pathSelected)}>
Upload
</Button>{' '}
<Button
button={'secondary'}
onClick={() => {
setPathSelected('');
}}
>
Clear
</Button>
</div>
)}
</>
)}
{assetSource === SOURCE_URL && (
<FormField
type={'text'}
name={'thumbnail'}
label={__(assetName + ' ' + recommended)}
placeholder={__('https://example.com/image.png')}
disabled={false}
value={currentValue}
onChange={e => {
onUpdate(e.target.value);
}}
/>
)}
</fieldset-group>
</fieldset-section>
);
}
export default SelectAsset;

View file

@ -1,15 +1,18 @@
// @flow // @flow
import React from 'react'; import React, { useState } from 'react';
import { parseURI } from 'lbry-redux'; import { parseURI } from 'lbry-redux';
import Page from 'component/page'; import Page from 'component/page';
import SubscribeButton from 'component/subscribeButton'; import SubscribeButton from 'component/subscribeButton';
import ShareButton from 'component/shareButton'; import ShareButton from 'component/shareButton';
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs'; import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import Button from 'component/button';
import { formatLbryUriForWeb } from 'util/uri'; import { formatLbryUriForWeb } from 'util/uri';
import ChannelContent from 'component/channelContent'; import ChannelContent from 'component/channelContent';
import ChannelAbout from 'component/channelAbout'; import ChannelAbout from 'component/channelAbout';
import ChannelThumbnail from 'component/channelThumbnail'; import ChannelThumbnail from 'component/channelThumbnail';
import ChannelForm from 'component/channelForm';
import * as ICONS from 'constants/icons';
const PAGE_VIEW_QUERY = `view`; const PAGE_VIEW_QUERY = `view`;
const ABOUT_PAGE = `about`; const ABOUT_PAGE = `about`;
@ -23,15 +26,20 @@ type Props = {
location: { search: string }, location: { search: string },
history: { push: string => void }, history: { push: string => void },
match: { params: { attribute: ?string } }, match: { params: { attribute: ?string } },
channelIsMine: boolean,
}; };
function ChannelPage(props: Props) { function ChannelPage(props: Props) {
const { uri, title, cover, history, location, page } = props; const { uri, title, cover, history, location, page, channelIsMine, thumbnail } = props;
const { channelName, claimName, claimId } = parseURI(uri); const { channelName, claimName, claimId } = parseURI(uri);
const { search } = location; const { search } = location;
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined; const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined;
const [editing, setEditing] = useState(false);
const [thumbPreview, setThumbPreview] = useState(thumbnail);
const [coverPreview, setCoverPreview] = useState(cover);
// If a user changes tabs, update the url so it stays on the same page if they refresh. // If a user changes tabs, update the url so it stays on the same page if they refresh.
// We don't want to use links here because we can't animate the tab change and using links // We don't want to use links here because we can't animate the tab change and using links
// would alter the Tab label's role attribute, which should stay role="tab" to work with keyboards/screen readers. // would alter the Tab label's role attribute, which should stay role="tab" to work with keyboards/screen readers.
@ -52,40 +60,59 @@ function ChannelPage(props: Props) {
<Page> <Page>
<div className="card"> <div className="card">
<header className="channel-cover"> <header className="channel-cover">
{cover && <img className="channel-cover__custom" src={cover} />} {!editing && cover && <img className="channel-cover__custom" src={cover} />}
{editing && <img className="channel-cover__custom" src={coverPreview} />}
<div className="channel__primary-info"> {/* component that offers select/upload */}
<ChannelThumbnail className="channel__thumbnail--channel-page" uri={uri} /> <div className="channel__primary-info ">
{!editing && <ChannelThumbnail className="channel__thumbnail--channel-page" uri={uri} />}
<div> {editing && (
<h1 className="channel__title">{title || channelName}</h1> <ChannelThumbnail
<h2 className="channel__url"> className="channel__thumbnail--channel-page"
{claimName} uri={uri}
{claimId && `#${claimId}`} thumbnailPreview={thumbPreview}
</h2> />
</div> )}
<h1 className="channel__title">
{title || channelName}
{channelIsMine && !editing && (
<Button onClick={() => setEditing(!editing)} icon={ICONS.EDIT} iconSize={49} />
)}
</h1>
<h2 className="channel__url">
{claimName}
{claimId && `#${claimId}`}
</h2>
</div> </div>
</header> </header>
{!editing && (
<Tabs onChange={onTabChange} index={tabIndex}>
<TabList className="tabs__list--channel-page">
<Tab>{__('Content')}</Tab>
<Tab>{__('About')}</Tab>
<div className="card__actions">
<ShareButton uri={uri} />
<SubscribeButton uri={uri} />
</div>
</TabList>
<Tabs onChange={onTabChange} index={tabIndex}> <TabPanels>
<TabList className="tabs__list--channel-page"> <TabPanel>
<Tab>{__('Content')}</Tab> <ChannelContent uri={uri} />
<Tab>{__('About')}</Tab> </TabPanel>
<div className="card__actions"> <TabPanel>
<ShareButton uri={uri} /> <ChannelAbout uri={uri} />
<SubscribeButton uri={uri} /> </TabPanel>
</div> </TabPanels>
</TabList> </Tabs>
)}
<TabPanels> {editing && (
<TabPanel> <ChannelForm
<ChannelContent uri={uri} /> uri={uri}
</TabPanel> setEditing={setEditing}
<TabPanel> updateThumb={v => setThumbPreview(v)}
<ChannelAbout uri={uri} /> updateCover={v => setCoverPreview(v)}
</TabPanel> />
</TabPanels> )}
</Tabs>
</div> </div>
</Page> </Page>
); );

View file

@ -345,7 +345,7 @@ export const doPublish = () => (dispatch: Dispatch, getState: () => {}) => {
dispatch({ type: ACTIONS.PUBLISH_FAIL }); dispatch({ type: ACTIONS.PUBLISH_FAIL });
dispatch(doError(error.message)); dispatch(doError(error.message));
}; };
console.log('PP', publishPayload);
return Lbry.publish(publishPayload).then(success, failure); return Lbry.publish(publishPayload).then(success, failure);
}; };

View file

@ -286,5 +286,8 @@
"Multi-language support is brand new and incomplete. Switching your language may have unintended consequences.": "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences.", "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences.": "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences.",
"Wallet": "Wallet", "Wallet": "Wallet",
"Home": "Home", "Home": "Home",
"Following": "Following" "Following": "Following",
"Update ready to install": "Update ready to install",
"Install now": "Install now",
"Edit": "Edit"
} }