This commit is contained in:
Sean Yesmunt 2019-09-27 14:56:15 -04:00
parent 4c014e3147
commit ecf5e52dd4
47 changed files with 462 additions and 453 deletions

View file

@ -24,6 +24,7 @@ module.name_mapper='^app\(.*\)$' -> '<PROJECT_ROOT>/src/ui/app\1'
module.name_mapper='^native\(.*\)$' -> '<PROJECT_ROOT>/src/ui/native\1' module.name_mapper='^native\(.*\)$' -> '<PROJECT_ROOT>/src/ui/native\1'
module.name_mapper='^analytics\(.*\)$' -> '<PROJECT_ROOT>/src/ui/analytics\1' module.name_mapper='^analytics\(.*\)$' -> '<PROJECT_ROOT>/src/ui/analytics\1'
module.name_mapper='^i18n\(.*\)$' -> '<PROJECT_ROOT>/src/ui/i18n\1' module.name_mapper='^i18n\(.*\)$' -> '<PROJECT_ROOT>/src/ui/i18n\1'
module.name_mapper='^effects\(.*\)$' -> '<PROJECT_ROOT>/src/ui/effects\1'
module.name_mapper='^config\(.*\)$' -> '<PROJECT_ROOT>/config\1' module.name_mapper='^config\(.*\)$' -> '<PROJECT_ROOT>/config\1'

View file

@ -128,7 +128,7 @@
"husky": "^0.14.3", "husky": "^0.14.3",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git", "lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#d44cd9ca56dee784dba42c0cc13061ae75cbd46c", "lbry-redux": "lbryio/lbry-redux#42bf926138872d14523be7191694309be4f37605",
"lbryinc": "lbryio/lbryinc#368040d64658cf2a4b8a7a6725ec1787329ce65d", "lbryinc": "lbryio/lbryinc#368040d64658cf2a4b8a7a6725ec1787329ce65d",
"lint-staged": "^7.0.2", "lint-staged": "^7.0.2",
"localforage": "^1.7.1", "localforage": "^1.7.1",

View file

@ -11,8 +11,7 @@ import useKonamiListener from 'util/enhanced-layout';
import Yrbl from 'component/yrbl'; import Yrbl from 'component/yrbl';
import FileViewer from 'component/fileViewer'; import FileViewer from 'component/fileViewer';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import usePrevious from 'util/use-previous'; import usePrevious from 'effects/use-previous';
import SyncBackgroundManager from 'component/syncBackgroundManager';
import Button from 'component/button'; import Button from 'component/button';
export const MAIN_WRAPPER_CLASS = 'main-wrapper'; export const MAIN_WRAPPER_CLASS = 'main-wrapper';
@ -121,7 +120,6 @@ function App(props: Props) {
<Router /> <Router />
<ModalRouter /> <ModalRouter />
<FileViewer pageUri={uri} /> <FileViewer pageUri={uri} />
<SyncBackgroundManager />
{/* @if TARGET='app' */} {/* @if TARGET='app' */}
{showUpgradeButton && ( {showUpgradeButton && (

View file

@ -3,7 +3,7 @@ import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import Button from 'component/button'; import Button from 'component/button';
import useHover from 'util/use-hover'; import useHover from 'effects/use-hover';
type Props = { type Props = {
permanentUrl: ?string, permanentUrl: ?string,

View file

@ -64,7 +64,7 @@ function ChannelContent(props: Props) {
</div> </div>
)} )}
{!channelIsMine && <HiddenNsfwClaims className="card__subtitle" uri={uri} />} {!channelIsMine && <HiddenNsfwClaims uri={uri} />}
{hasContent && !channelIsBlocked && !channelIsBlackListed && ( {hasContent && !channelIsBlocked && !channelIsBlackListed && (
<ClaimList header={false} uris={claimsInChannel.map(claim => claim && claim.canonical_url)} /> <ClaimList header={false} uris={claimsInChannel.map(claim => claim && claim.canonical_url)} />

View file

@ -182,7 +182,7 @@ function ChannelForm(props: Props) {
onChange={text => setParams({ ...params, description: text })} onChange={text => setParams({ ...params, description: text })}
/> />
<TagSelect <TagSelect
title={false} title={__('Add Tags')}
suggestMature suggestMature
help={__('The better your tags are, the easier it will be for people to discover your channel.')} help={__('The better your tags are, the easier it will be for people to discover your channel.')}
empty={__('No tags added')} empty={__('No tags added')}

View file

@ -6,7 +6,7 @@ import classnames from 'classnames';
import ClaimPreview from 'component/claimPreview'; import ClaimPreview from 'component/claimPreview';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import usePersistedState from 'util/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
const SORT_NEW = 'new'; const SORT_NEW = 'new';
const SORT_OLD = 'old'; const SORT_OLD = 'old';

View file

@ -187,19 +187,19 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
<div className="claim-preview-title"> <div className="claim-preview-title">
{claim ? <TruncatedText text={title || claim.name} lines={1} /> : <span>{__('Nothing here')}</span>} {claim ? <TruncatedText text={title || claim.name} lines={1} /> : <span>{__('Nothing here')}</span>}
</div> </div>
{actions !== undefined {!hideActions && actions !== undefined ? (
? actions actions
: !hideActions && ( ) : (
<div className="card__actions--inline"> <div className="card__actions--inline">
{isChannel && !channelIsBlocked && !claimIsMine && ( {isChannel && !channelIsBlocked && !claimIsMine && (
<SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} /> <SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)}
{isChannel && !isSubscribed && !claimIsMine && (
<BlockButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)}
{!isChannel && claim && <FileProperties uri={uri} />}
</div>
)} )}
{isChannel && !isSubscribed && !claimIsMine && (
<BlockButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)}
{!isChannel && claim && <FileProperties uri={uri} />}
</div>
)}
</div> </div>
<div className="claim-preview-properties"> <div className="claim-preview-properties">

View file

@ -5,7 +5,7 @@ import { FormField, Form } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import ChannelSection from 'component/selectChannel'; import ChannelSection from 'component/selectChannel';
import UnsupportedOnWeb from 'component/common/unsupported-on-web'; import UnsupportedOnWeb from 'component/common/unsupported-on-web';
import usePersistedState from 'util/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
type Props = { type Props = {
uri: string, uri: string,

View file

@ -5,7 +5,7 @@ import classnames from 'classnames';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
type Props = { type Props = {
title: string | Node, title?: string | Node,
subtitle?: string | Node, subtitle?: string | Node,
body?: string | Node, body?: string | Node,
actions?: string | Node, actions?: string | Node,
@ -16,15 +16,17 @@ export default function Card(props: Props) {
const { title, subtitle, body, actions, icon } = props; const { title, subtitle, body, actions, icon } = props;
return ( return (
<section className={classnames('card')}> <section className={classnames('card')}>
<div className="card__header"> {title && (
<div className="section__flex"> <div className="card__header">
{icon && <Icon sectionIcon icon={icon} />} <div className="section__flex">
<div> {icon && <Icon sectionIcon icon={icon} />}
<h2 className="section__title">{title}</h2> <div>
<p className="section__subtitle">{subtitle}</p> <h2 className="section__title">{title}</h2>
<p className="section__subtitle">{subtitle}</p>
</div>
</div> </div>
</div> </div>
</div> )}
{body && <div className={classnames('card__body', { 'card__body--with-icon': icon })}>{body}</div>} {body && <div className={classnames('card__body', { 'card__body--with-icon': icon })}>{body}</div>}
{actions && ( {actions && (

View file

@ -6,8 +6,8 @@ import classnames from 'classnames';
import LoadingScreen from 'component/common/loading-screen'; import LoadingScreen from 'component/common/loading-screen';
import FileRender from 'component/fileRender'; import FileRender from 'component/fileRender';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
import usePersistedState from 'util/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import usePrevious from 'util/use-previous'; import usePrevious from 'effects/use-previous';
import { FILE_WRAPPER_CLASS } from 'page/file/view'; import { FILE_WRAPPER_CLASS } from 'page/file/view';
import Draggable from 'react-draggable'; import Draggable from 'react-draggable';
import Tooltip from 'component/common/tooltip'; import Tooltip from 'component/common/tooltip';
@ -86,7 +86,7 @@ export default function FileViewer(props: Props) {
function handleResize() { function handleResize() {
const element = document.querySelector(`.${FILE_WRAPPER_CLASS}`); const element = document.querySelector(`.${FILE_WRAPPER_CLASS}`);
if (!element) { if (!element) {
console.error("Can't find file viewer wrapper to attach to the inline viewer to"); console.error("Can't find file viewer wrapper to attach to the inline viewer to"); // eslint-disable-line
return; return;
} }

View file

@ -1,7 +1,6 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import classnames from 'classnames';
type Props = { type Props = {
numberOfNsfwClaims: number, numberOfNsfwClaims: number,
@ -10,12 +9,12 @@ type Props = {
}; };
export default (props: Props) => { export default (props: Props) => {
const { numberOfNsfwClaims, obscureNsfw, className } = props; const { numberOfNsfwClaims, obscureNsfw } = props;
return ( return (
obscureNsfw && obscureNsfw &&
Boolean(numberOfNsfwClaims) && ( Boolean(numberOfNsfwClaims) && (
<div className={classnames('card--section', className || 'help')}> <div className="section--padded section__subtitle">
{numberOfNsfwClaims} {numberOfNsfwClaims > 1 ? __('files') : __('file')} {__('hidden due to your')}{' '} {numberOfNsfwClaims} {numberOfNsfwClaims > 1 ? __('files') : __('file')} {__('hidden due to your')}{' '}
<Button button="link" navigate="/$/settings" label={__('content viewing preferences')} />. <Button button="link" navigate="/$/settings" label={__('content viewing preferences')} />.
</div> </div>

View file

@ -1,10 +1,11 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import usePersistedState from 'util/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import LicenseType from './license-type'; import LicenseType from './license-type';
import Card from 'component/common/card';
type Props = { type Props = {
language: ?string, language: ?string,
@ -25,67 +26,75 @@ function PublishAdvanced(props: Props) {
} }
return ( return (
<section className="card card--section"> <Card
{!hideSection && ( actions={
<div className={classnames({ 'card--disabled': !name })}> <React.Fragment>
<FormField {!hideSection && (
label={__('Language')} <div className={classnames({ 'card--disabled': !name })}>
type="select" <FormField
name="content_language" label={__('Language')}
value={language} type="select"
onChange={event => updatePublishForm({ language: event.target.value })} name="content_language"
> value={language}
<option value="en">{__('English')}</option> onChange={event => updatePublishForm({ language: event.target.value })}
<option value="zh">{__('Chinese')}</option> >
<option value="fr">{__('French')}</option> <option value="en">{__('English')}</option>
<option value="de">{__('German')}</option> <option value="zh">{__('Chinese')}</option>
<option value="jp">{__('Japanese')}</option> <option value="fr">{__('French')}</option>
<option value="ru">{__('Russian')}</option> <option value="de">{__('German')}</option>
<option value="es">{__('Spanish')}</option> <option value="jp">{__('Japanese')}</option>
<option value="id">{__('Indonesian')}</option> <option value="ru">{__('Russian')}</option>
<option value="it">{__('Italian')}</option> <option value="es">{__('Spanish')}</option>
<option value="nl">{__('Dutch')}</option> <option value="id">{__('Indonesian')}</option>
<option value="tr">{__('Turkish')}</option> <option value="it">{__('Italian')}</option>
<option value="pl">{__('Polish')}</option> <option value="nl">{__('Dutch')}</option>
<option value="ms">{__('Malay')}</option> <option value="tr">{__('Turkish')}</option>
<option value="pt">{__('Portuguese')}</option> <option value="pl">{__('Polish')}</option>
<option value="vi">{__('Vietnamese')}</option> <option value="ms">{__('Malay')}</option>
<option value="th">{__('Thai')}</option> <option value="pt">{__('Portuguese')}</option>
<option value="ar">{__('Arabic')}</option> <option value="vi">{__('Vietnamese')}</option>
<option value="cs">{__('Czech')}</option> <option value="th">{__('Thai')}</option>
<option value="hr">{__('Croatian')}</option> <option value="ar">{__('Arabic')}</option>
<option value="km">{__('Cambodian')}</option> <option value="cs">{__('Czech')}</option>
<option value="ko">{__('Korean')}</option> <option value="hr">{__('Croatian')}</option>
<option value="no">{__('Norwegian')}</option> <option value="km">{__('Cambodian')}</option>
<option value="ro">{__('Romanian')}</option> <option value="ko">{__('Korean')}</option>
<option value="hi">{__('Hindi')}</option> <option value="no">{__('Norwegian')}</option>
<option value="el">{__('Greek')}</option> <option value="ro">{__('Romanian')}</option>
</FormField> <option value="hi">{__('Hindi')}</option>
<option value="el">{__('Greek')}</option>
</FormField>
<LicenseType <LicenseType
licenseType={licenseType} licenseType={licenseType}
otherLicenseDescription={otherLicenseDescription} otherLicenseDescription={otherLicenseDescription}
licenseUrl={licenseUrl} licenseUrl={licenseUrl}
handleLicenseChange={(newLicenseType, newLicenseUrl) => handleLicenseChange={(newLicenseType, newLicenseUrl) =>
updatePublishForm({ updatePublishForm({
licenseType: newLicenseType, licenseType: newLicenseType,
licenseUrl: newLicenseUrl, licenseUrl: newLicenseUrl,
}) })
} }
handleLicenseDescriptionChange={event => handleLicenseDescriptionChange={event =>
updatePublishForm({ updatePublishForm({
otherLicenseDescription: event.target.value, otherLicenseDescription: event.target.value,
}) })
} }
handleLicenseUrlChange={event => updatePublishForm({ licenseUrl: event.target.value })} handleLicenseUrlChange={event => updatePublishForm({ licenseUrl: event.target.value })}
/> />
</div> </div>
)} )}
<div className="card__actions"> <div className="card__actions">
<Button label={hideSection ? __('Additional Options') : __('Hide')} button="link" onClick={toggleHideSection} /> <Button
</div> label={hideSection ? __('Additional Options') : __('Hide')}
</section> button="link"
onClick={toggleHideSection}
/>
</div>
</React.Fragment>
}
/>
); );
} }

View file

@ -1,9 +1,10 @@
// @flow // @flow
import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
import { regexInvalidURI } from 'lbry-redux'; import { regexInvalidURI } from 'lbry-redux';
import classnames from 'classnames';
import FileSelector from 'component/common/file-selector'; import FileSelector from 'component/common/file-selector';
import Button from 'component/button'; import Button from 'component/button';
import Card from 'component/common/card';
type Props = { type Props = {
name: ?string, name: ?string,
@ -11,10 +12,11 @@ type Props = {
isStillEditing: boolean, isStillEditing: boolean,
balance: number, balance: number,
updatePublishForm: ({}) => void, updatePublishForm: ({}) => void,
disabled: boolean,
}; };
function PublishFile(props: Props) { function PublishFile(props: Props) {
const { name, balance, filePath, isStillEditing, updatePublishForm } = props; const { name, balance, filePath, isStillEditing, updatePublishForm, disabled } = props;
function handleFileChange(filePath: string, fileName: string) { function handleFileChange(filePath: string, fileName: string) {
const publishFormParams: { filePath: string, name?: string } = { filePath }; const publishFormParams: { filePath: string, name?: string } = { filePath };
@ -28,29 +30,29 @@ function PublishFile(props: Props) {
} }
return ( return (
<section <Card
className={classnames('card card--section', { className={disabled ? 'card--disabled' : undefined}
'card--disabled': balance === 0, icon={ICONS.PUBLISH}
})} disabled={balance === 0}
> title={isStillEditing ? __('Edit') : __('Publish')}
<h2 className="card__title">{isStillEditing ? __('Edit') : __('Publish')}</h2> subtitle={__('You are currently editing a claim.')}
{isStillEditing && <p className="card__subtitle">{__('You are currently editing a claim.')}</p>} actions={
<React.Fragment>
<div className="card__content"> <FileSelector currentPath={filePath} onFileChosen={handleFileChange} />
<FileSelector currentPath={filePath} onFileChosen={handleFileChange} /> {!isStillEditing && (
{!isStillEditing && ( <p className="help">
<p className="help"> {__('For video content, use MP4s in H264/AAC format for best compatibility.')}{' '}
{__('For video content, use MP4s in H264/AAC format for best compatibility.')}{' '} <Button button="link" label={__('Learn more')} href="https://lbry.com/faq/how-to-publish" />.
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/how-to-publish" />. </p>
</p> )}
)} {!!isStillEditing && name && (
{!!isStillEditing && name && ( <p className="help">
<p className="help"> {__("If you don't choose a file, the file from your existing claim %name% will be used", { name: name })}
{__("If you don't choose a file, the file from your existing claim %name% will be used", { name: name })} </p>
</p> )}
)} </React.Fragment>
</div> }
</section> />
); );
} }

View file

@ -14,6 +14,7 @@ import PublishName from 'component/publishName';
import PublishAdditionalOptions from 'component/publishAdditionalOptions'; import PublishAdditionalOptions from 'component/publishAdditionalOptions';
import PublishFormErrors from 'component/publishFormErrors'; import PublishFormErrors from 'component/publishFormErrors';
import SelectThumbnail from 'component/selectThumbnail'; import SelectThumbnail from 'component/selectThumbnail';
import Card from 'component/common/card';
type Props = { type Props = {
tags: Array<Tag>, tags: Array<Tag>,
@ -127,35 +128,36 @@ function PublishForm(props: Props) {
<PublishFile /> <PublishFile />
<div className={classnames({ 'card--disabled': formDisabled })}> <div className={classnames({ 'card--disabled': formDisabled })}>
<PublishText disabled={formDisabled} /> <PublishText disabled={formDisabled} />
<div className="card card--section"> <Card actions={<SelectThumbnail />} />
{/* This should probably be PublishThumbnail */}
<SelectThumbnail /> <TagSelect
</div> title={__('Add Tags')}
<div className="card card--section"> suggestMature
<TagSelect help={__('The better your tags are, the easier it will be for people to discover your content.')}
title={false} empty={__('No tags added')}
suggestMature onSelect={newTag => {
help={__('The better your tags are, the easier it will be for people to discover your content.')} if (!tags.map(savedTag => savedTag.name).includes(newTag.name)) {
empty={__('No tags added')} updatePublishForm({ tags: [...tags, newTag] });
onSelect={newTag => { }
if (!tags.map(savedTag => savedTag.name).includes(newTag.name)) { }}
updatePublishForm({ tags: [...tags, newTag] }); onRemove={clickedTag => {
} const newTags = tags.slice().filter(tag => tag.name !== clickedTag.name);
}} updatePublishForm({ tags: newTags });
onRemove={clickedTag => { }}
const newTags = tags.slice().filter(tag => tag.name !== clickedTag.name); tagsChosen={tags}
updatePublishForm({ tags: newTags }); />
}}
tagsChosen={tags} <Card
/> actions={
</div> <React.Fragment>
<section className="card card--section"> <ChannelSection channel={channel} onChannelChange={channel => updatePublishForm({ channel })} />
<ChannelSection channel={channel} onChannelChange={channel => updatePublishForm({ channel })} /> <p className="help">
<p className="help"> {__('This is a username or handle that your content can be found under.')}{' '}
{__('This is a username or handle that your content can be found under.')}{' '} {__('Ex. @Marvel, @TheBeatles, @BooksByJoe')}
{__('Ex. @Marvel, @TheBeatles, @BooksByJoe')} </p>
</p> </React.Fragment>
</section> }
/>
<PublishName disabled={formDisabled} /> <PublishName disabled={formDisabled} />
<PublishPrice disabled={formDisabled} /> <PublishPrice disabled={formDisabled} />

View file

@ -5,6 +5,7 @@ import { isNameValid } from 'lbry-redux';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import NameHelpText from './name-help-text'; import NameHelpText from './name-help-text';
import BidHelpText from './bid-help-text'; import BidHelpText from './bid-help-text';
import Card from 'component/common/card';
type Props = { type Props = {
name: string, name: string,
@ -73,48 +74,56 @@ function PublishName(props: Props) {
}, [bid, previousBidAmount, balance]); }, [bid, previousBidAmount, balance]);
return ( return (
<section className="card card--section"> <Card
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix"> actions={
<fieldset-section> <React.Fragment>
<label>{__('Name')}</label> <fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
<div className="form-field__prefix">{`lbry://${ <fieldset-section>
!channel || channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : `${channel}/` <label>{__('Name')}</label>
}`}</div> <div className="form-field__prefix">{`lbry://${
</fieldset-section> !channel || channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : `${channel}/`
<FormField }`}</div>
type="text" </fieldset-section>
name="content_name" <FormField
value={name} type="text"
error={nameError} name="content_name"
onChange={event => updatePublishForm({ name: event.target.value })} value={name}
/> error={nameError}
</fieldset-group> onChange={event => updatePublishForm({ name: event.target.value })}
<div className="form-field__help"> />
<NameHelpText </fieldset-group>
uri={uri} <div className="form-field__help">
isStillEditing={isStillEditing} <NameHelpText
myClaimForUri={myClaimForUri} uri={uri}
onEditMyClaim={editExistingClaim} isStillEditing={isStillEditing}
/> myClaimForUri={myClaimForUri}
</div> onEditMyClaim={editExistingClaim}
<FormField />
type="number" </div>
name="content_bid" <FormField
min="0" type="number"
step="any" name="content_bid"
placeholder="0.123" min="0"
className="form-field--price-amount" step="any"
label={__('Deposit (LBC)')} placeholder="0.123"
postfix="LBC" className="form-field--price-amount"
value={bid} label={__('Deposit (LBC)')}
error={bidError} postfix="LBC"
disabled={!name} value={bid}
onChange={event => updatePublishForm({ bid: parseFloat(event.target.value) })} error={bidError}
helper={ disabled={!name}
<BidHelpText uri={uri} amountNeededForTakeover={amountNeededForTakeover} isResolvingUri={isResolvingUri} /> onChange={event => updatePublishForm({ bid: parseFloat(event.target.value) })}
} helper={
/> <BidHelpText
</section> uri={uri}
amountNeededForTakeover={amountNeededForTakeover}
isResolvingUri={isResolvingUri}
/>
}
/>
</React.Fragment>
}
/>
); );
} }

View file

@ -1,6 +1,7 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { FormField, FormFieldPrice } from 'component/common/form'; import { FormField, FormFieldPrice } from 'component/common/form';
import Card from 'component/common/card';
type Props = { type Props = {
contentIsFree: boolean, contentIsFree: boolean,
@ -13,40 +14,44 @@ function PublishPrice(props: Props) {
const { contentIsFree, fee, updatePublishForm, disabled } = props; const { contentIsFree, fee, updatePublishForm, disabled } = props;
return ( return (
<section className="card card--section"> <Card
<FormField actions={
type="radio" <React.Fragment>
name="content_free" <FormField
label={__('Free')} type="radio"
checked={contentIsFree} name="content_free"
disabled={disabled} label={__('Free')}
onChange={() => updatePublishForm({ contentIsFree: true })} checked={contentIsFree}
/> disabled={disabled}
onChange={() => updatePublishForm({ contentIsFree: true })}
/>
<FormField <FormField
type="radio" type="radio"
name="content_cost" name="content_cost"
label={__('Add a price to this file')} label={__('Add a price to this file')}
checked={!contentIsFree} checked={!contentIsFree}
disabled={disabled} disabled={disabled}
onChange={() => updatePublishForm({ contentIsFree: false })} onChange={() => updatePublishForm({ contentIsFree: false })}
/> />
{!contentIsFree && ( {!contentIsFree && (
<FormFieldPrice <FormFieldPrice
name="content_cost_amount" name="content_cost_amount"
min="0" min="0"
price={fee} price={fee}
onChange={newFee => updatePublishForm({ fee: newFee })} onChange={newFee => updatePublishForm({ fee: newFee })}
/> />
)}
{fee && fee.currency !== 'LBC' && (
<p className="form-field__help">
{__(
'All content fees are charged in LBC. For non-LBC payment methods, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase.'
)} )}
</p> {fee && fee.currency !== 'LBC' && (
)} <p className="form-field__help">
</section> {__(
'All content fees are charged in LBC. For non-LBC payment methods, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase.'
)}
</p>
)}
</React.Fragment>
}
/>
); );
} }

View file

@ -2,7 +2,8 @@
import React from 'react'; import React from 'react';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import usePersistedState from 'util/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import Card from 'component/common/card';
type Props = { type Props = {
title: ?string, title: ?string,
@ -19,30 +20,38 @@ function PublishText(props: Props) {
} }
return ( return (
<section className="card card--section"> <Card
<FormField actions={
type="text" <React.Fragment>
name="content_title" <FormField
label={__('Title')} type="text"
placeholder={__('Titular Title')} name="content_title"
disabled={disabled} label={__('Title')}
value={title} placeholder={__('Titular Title')}
onChange={e => updatePublishForm({ title: e.target.value })} disabled={disabled}
/> value={title}
onChange={e => updatePublishForm({ title: e.target.value })}
/>
<FormField <FormField
type={advancedEditor ? 'markdown' : 'textarea'} type={advancedEditor ? 'markdown' : 'textarea'}
name="content_description" name="content_description"
label={__('Description')} label={__('Description')}
placeholder={__('My description for this and that')} placeholder={__('My description for this and that')}
value={description} value={description}
disabled={disabled} disabled={disabled}
onChange={value => updatePublishForm({ description: advancedEditor ? value : value.target.value })} onChange={value => updatePublishForm({ description: advancedEditor ? value : value.target.value })}
/> />
<div className="card__actions"> <div className="card__actions">
<Button button="link" onClick={toggleMarkdown} label={advancedEditor ? 'Simple Editor' : 'Advanced Editor'} /> <Button
</div> button="link"
</section> onClick={toggleMarkdown}
label={advancedEditor ? 'Simple Editor' : 'Advanced Editor'}
/>
</div>
</React.Fragment>
}
/>
); );
} }

View file

@ -1,7 +1,7 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import TotalBackground from './total-background.png'; import TotalBackground from './total-background.png';
import useTween from 'util/use-tween'; import useTween from 'effects/use-tween';
type Props = { type Props = {
rewards: Array<Reward>, rewards: Array<Reward>,

View file

@ -69,25 +69,27 @@ function SideBar(props: Props) {
className="navigation-link" className="navigation-link"
activeClass="navigation-link--active" activeClass="navigation-link--active"
/> />
<ul className="navigation-links tags--vertical"> <section className="navigation-links__inline">
{followedTags.map(({ name }, key) => ( <ul className="navigation-links--small tags--vertical">
<li className="navigation-link__wrapper" key={name}> {followedTags.map(({ name }, key) => (
<Tag navigate={`/$/tags?t${name}`} name={name} /> <li className="navigation-link__wrapper" key={name}>
</li> <Tag navigate={`/$/tags?t${name}`} name={name} />
))} </li>
</ul> ))}
<ul className="navigation-links--small"> </ul>
{subscriptions.map(({ uri, channelName }, index) => ( <ul className="navigation-links--small">
<li key={uri} className="navigation-link__wrapper"> {subscriptions.map(({ uri, channelName }, index) => (
<Button <li key={uri} className="navigation-link__wrapper">
navigate={uri} <Button
label={channelName} navigate={uri}
className="navigation-link" label={channelName}
activeClass="navigation-link--active" className="navigation-link"
/> activeClass="navigation-link--active"
</li> />
))} </li>
</ul> ))}
</ul>
</section>
</nav> </nav>
</StickyBox> </StickyBox>
); );

View file

@ -124,11 +124,6 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
clearTimeout(this.timeout); clearTimeout(this.timeout);
} }
//
//
// Try to unlock by default here
//
//
// Make sure there isn't another active modal (like INCOMPATIBLE_DAEMON) // Make sure there isn't another active modal (like INCOMPATIBLE_DAEMON)
if (launchedModal === false && !modal) { if (launchedModal === false && !modal) {
this.setState({ launchedModal: true }, () => notifyUnlockWallet()); this.setState({ launchedModal: true }, () => notifyUnlockWallet());
@ -153,10 +148,10 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
}); });
} }
} else if (wallet && wallet.blocks_behind > 0) { } else if (wallet && wallet.blocks_behind > 0) {
const format = wallet.blocks_behind === 1 ? '%s block behind' : '%s blocks behind'; const amountBehind = wallet.blocks_behind === 1 ? '%amountBehind% block behind' : '%amountBehind% blocks behind';
this.setState({ this.setState({
message: __('Blockchain Sync'), message: __('Blockchain Sync'),
details: `${__('Catching up...')} (${__(format, wallet.blocks_behind)})`, details: `${__('Catching up...')} (${__(amountBehind, { amountBehind: wallet.blocks_behind })})`,
}); });
} else if ( } else if (
wallet && wallet &&

View file

@ -4,7 +4,7 @@ import * as ICONS from 'constants/icons';
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { parseURI } from 'lbry-redux'; import { parseURI } from 'lbry-redux';
import Button from 'component/button'; import Button from 'component/button';
import useHover from 'util/use-hover'; import useHover from 'effects/use-hover';
type SubscriptionArgs = { type SubscriptionArgs = {
channelName: string, channelName: string,

View file

@ -1,24 +0,0 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import { selectBalance } from 'lbry-redux';
import { doGetSync, selectSyncHash, selectUserVerifiedEmail } from 'lbryinc';
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import WalletSecurityAndSync from './view';
const select = state => ({
balance: selectBalance(state),
verifiedEmail: selectUserVerifiedEmail(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
hasSyncHash: selectSyncHash(state),
});
const perform = dispatch => ({
getSync: password => dispatch(doGetSync(password)),
setSync: value => dispatch(doSetClientSetting(SETTINGS.ENABLE_SYNC, value)),
});
export default connect(
select,
perform
)(WalletSecurityAndSync);

View file

@ -1,22 +0,0 @@
// @flow
import React from 'react';
type Props = {
syncEnabled: boolean,
verifiedEmail?: string,
getSync: () => void,
};
function SyncBackgroundManager(props: Props) {
const { syncEnabled, getSync, verifiedEmail } = props;
React.useEffect(() => {
if (syncEnabled && verifiedEmail) {
getSync();
}
}, [syncEnabled, verifiedEmail, getSync]);
return null;
}
export default SyncBackgroundManager;

View file

@ -4,7 +4,7 @@ import * as React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import Tag from 'component/tag'; import Tag from 'component/tag';
import TagsSearch from 'component/tagsSearch'; import TagsSearch from 'component/tagsSearch';
import usePersistedState from 'util/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import analytics from 'analytics'; import analytics from 'analytics';
import Card from 'component/common/card'; import Card from 'component/common/card';
@ -73,7 +73,15 @@ export default function TagSelect(props: Props) {
)} )}
</React.Fragment> </React.Fragment>
} }
body={ subtitle={
help !== false && (
<span>
{help || __("The tags you follow will change what's trending for you.")}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/trending" />.
</span>
)
}
actions={
<React.Fragment> <React.Fragment>
<TagsSearch <TagsSearch
onRemove={handleTagClick} onRemove={handleTagClick}
@ -81,12 +89,6 @@ export default function TagSelect(props: Props) {
suggestMature={suggestMature && !hasMatureTag} suggestMature={suggestMature && !hasMatureTag}
tagsPasssedIn={tagsToDisplay} tagsPasssedIn={tagsToDisplay}
/> />
{help !== false && (
<p className="help">
{help || __("The tags you follow will change what's trending for you.")}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/trending" />.
</p>
)}
</React.Fragment> </React.Fragment>
} }
/> />

View file

@ -40,9 +40,7 @@ function UserEmail(props: Props) {
'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to save account information and earn rewards.' 'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to save account information and earn rewards.'
)} )}
actions={ actions={
!isVerified ? ( isVerified ? (
<Button button="primary" label={__('Add Email')} navigate={`/$/${PAGES.AUTH}`} />
) : (
<FormField <FormField
type="text" type="text"
className="form-field--copyable" className="form-field--copyable"
@ -60,6 +58,8 @@ function UserEmail(props: Props) {
inputButton={<UserSignOutButton button="inverse" />} inputButton={<UserSignOutButton button="inverse" />}
value={email || ''} value={email || ''}
/> />
) : (
<Button button="primary" label={__('Add Email')} navigate={`/$/${PAGES.AUTH}`} />
) )
} }
/> />

View file

@ -30,6 +30,7 @@ function UserEmailNew(props: Props) {
} }
React.useEffect(() => { React.useEffect(() => {
// Sync currently doesn't work for wallets with balances
if (syncEnabled && balance) { if (syncEnabled && balance) {
setSync(false); setSync(false);
} }
@ -56,7 +57,9 @@ function UserEmailNew(props: Props) {
name="sync_checkbox" name="sync_checkbox"
label={__('Sync your balance between devices')} label={__('Sync your balance between devices')}
helper={ helper={
balance > 0 ? __('This is only available for empty wallets') : __('Maybe some more text about something') balance > 0
? __("This is only available if you don't have a balance")
: __('Maybe some more text about something')
} }
checked={syncEnabled} checked={syncEnabled}
onChange={() => setSync(!syncEnabled)} onChange={() => setSync(!syncEnabled)}

View file

@ -33,9 +33,9 @@ const select = state => ({
youtubeChannels: selectYoutubeChannels(state), youtubeChannels: selectYoutubeChannels(state),
userFetchPending: selectUserIsPending(state), userFetchPending: selectUserIsPending(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state), syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
syncIsPending: selectGetSyncIsPending(state), syncingWallet: selectGetSyncIsPending(state),
getSyncError: selectGetSyncErrorMessage(state), getSyncError: selectGetSyncErrorMessage(state),
syncHash: selectSyncHash(state), hasSynced: Boolean(selectSyncHash(state)),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -6,19 +6,11 @@ import UserEmailVerify from 'component/userEmailVerify';
import UserFirstChannel from 'component/userFirstChannel'; import UserFirstChannel from 'component/userFirstChannel';
import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view'; import { DEFAULT_BID_FOR_FIRST_CHANNEL } from 'component/userFirstChannel/view';
import { rewards as REWARDS } from 'lbryinc'; import { rewards as REWARDS } from 'lbryinc';
import usePrevious from 'util/use-previous';
import UserVerify from 'component/userVerify'; import UserVerify from 'component/userVerify';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
import YoutubeTransferWelcome from 'component/youtubeTransferWelcome'; import YoutubeTransferWelcome from 'component/youtubeTransferWelcome';
import SyncPassword from 'component/syncPassword'; import SyncPassword from 'component/syncPassword';
import useFetched from 'effects/use-fetched';
/*
- Brand new user
- Brand new user, not auto approved
- Second device (first time user), first device has a password + rewards not approved
- Second device (first time user), first device has a password + rewards approved
*/
type Props = { type Props = {
user: ?User, user: ?User,
@ -33,24 +25,12 @@ type Props = {
history: { replace: string => void }, history: { replace: string => void },
location: { search: string }, location: { search: string },
youtubeChannels: Array<any>, youtubeChannels: Array<any>,
syncIsPending: boolean, syncEnabled: boolean,
hasSynced: boolean,
syncingWallet: boolean,
getSyncError: ?string, getSyncError: ?string,
hasSyncedSuccessfully: boolean,
}; };
function useFetched(fetching) {
const wasFetching = usePrevious(fetching);
const [fetched, setFetched] = React.useState(false);
React.useEffect(() => {
if (wasFetching && !fetching) {
setFetched(true);
}
}, [wasFetching, fetching, setFetched]);
return fetched;
}
function UserSignIn(props: Props) { function UserSignIn(props: Props) {
const { const {
emailToVerify, emailToVerify,
@ -65,9 +45,9 @@ function UserSignIn(props: Props) {
fetchUser, fetchUser,
youtubeChannels, youtubeChannels,
syncEnabled, syncEnabled,
syncIsPending, syncingWallet,
getSyncError, getSyncError,
syncHash, hasSynced,
fetchingChannels, fetchingChannels,
} = props; } = props;
const { search } = location; const { search } = location;
@ -76,56 +56,64 @@ function UserSignIn(props: Props) {
const hasVerifiedEmail = user && user.has_verified_email; const hasVerifiedEmail = user && user.has_verified_email;
const rewardsApproved = user && user.is_reward_approved; const rewardsApproved = user && user.is_reward_approved;
const hasFetchedReward = useFetched(claimingReward); const hasFetchedReward = useFetched(claimingReward);
// const hasFetchedSync = useFetched(syncIsPending);
// const hasTriedSyncForReal = syncEnabled && hasFetchedSync;
const channelCount = channels ? channels.length : 0; const channelCount = channels ? channels.length : 0;
const hasClaimedEmailAward = claimedRewards.some(reward => reward.reward_type === REWARDS.TYPE_CONFIRM_EMAIL); const hasClaimedEmailAward = claimedRewards.some(reward => reward.reward_type === REWARDS.TYPE_CONFIRM_EMAIL);
const hasYoutubeChannels = youtubeChannels && youtubeChannels.length; const hasYoutubeChannels = youtubeChannels && Boolean(youtubeChannels.length);
const hasTransferrableYoutubeChannels = hasYoutubeChannels && youtubeChannels.some(channel => channel.transferable); const hasTransferrableYoutubeChannels = hasYoutubeChannels && youtubeChannels.some(channel => channel.transferable);
const hasPendingYoutubeTransfer = const hasPendingYoutubeTransfer =
hasYoutubeChannels && youtubeChannels.some(channel => channel.transfer_state === 'pending_transfer'); hasYoutubeChannels && youtubeChannels.some(channel => channel.transfer_state === 'pending_transfer');
React.useEffect(() => { // Complexity warning
if ( // We can't just check if we are currently fetching something
hasVerifiedEmail && // We may want to keep a component rendered while something is being fetched, instead of replacing it with the large spinner
balance !== undefined && // The verbose variable names are an attempt to alleviate _some_ of the confusion from handling all edge cases that come from
!hasClaimedEmailAward && // reward claiming (plus the balance updating after), channel creation, account syncing, and youtube transfer
!hasFetchedReward && const canHijackSignInFlowWithSpinner = hasVerifiedEmail && balance === 0 && !getSyncError;
(!syncEnabled || (syncEnabled && syncHash)) const isCurrentlyFetchingSomething = fetchingChannels || claimingReward || syncingWallet;
) { const isWaitingForSomethingToFinish =
claimReward(); // If the user has claimed the email award, we need to wait until the balance updates sometime in the future
} !hasFetchedReward || (hasFetchedReward && hasClaimedEmailAward) || (syncEnabled && !hasSynced);
}, [hasVerifiedEmail, claimReward, balance, hasClaimedEmailAward, hasFetchedReward, syncEnabled, syncHash]);
// The possible screens for the sign in flow
const showEmail = !emailToVerify && !hasVerifiedEmail;
const showEmailVerification = emailToVerify && !hasVerifiedEmail;
const showUserVerification = hasVerifiedEmail && !rewardsApproved;
const showSyncPassword = syncEnabled && getSyncError && !hasSynced;
const showChannelCreation =
hasVerifiedEmail && balance && balance > DEFAULT_BID_FOR_FIRST_CHANNEL && channelCount === 0 && !hasYoutubeChannels;
const showYoutubeTransfer =
hasVerifiedEmail && hasYoutubeChannels && (hasTransferrableYoutubeChannels || hasPendingYoutubeTransfer);
const showLoadingSpinner =
canHijackSignInFlowWithSpinner && (isCurrentlyFetchingSomething || isWaitingForSomethingToFinish);
React.useEffect(() => { React.useEffect(() => {
fetchUser(); fetchUser();
}, [fetchUser]); }, [fetchUser]);
React.useEffect(() => {
// Don't claim the reward if sync is enabled until after a sync has been completed successfully
// If we do it before, we could end up trying to sync a wallet with a non-zero balance which will fail to sync
const delayForSync = syncEnabled && !hasSynced;
if (hasVerifiedEmail && !hasClaimedEmailAward && !hasFetchedReward && !delayForSync) {
claimReward();
}
}, [hasVerifiedEmail, claimReward, hasClaimedEmailAward, hasFetchedReward, syncEnabled, hasSynced]);
// Loop through this list from the end, until it finds a matching component
// If it never finds one, assume the user has completed every step and redirect them
const SIGN_IN_FLOW = [ const SIGN_IN_FLOW = [
!emailToVerify && !hasVerifiedEmail && <UserEmailNew />, showEmail && <UserEmailNew />,
emailToVerify && !hasVerifiedEmail && <UserEmailVerify />, showEmailVerification && <UserEmailVerify />,
hasVerifiedEmail && !rewardsApproved && <UserVerify />, showUserVerification && <UserVerify />,
getSyncError && !syncHash && <SyncPassword />, showSyncPassword && <SyncPassword />,
hasVerifiedEmail && balance > DEFAULT_BID_FOR_FIRST_CHANNEL && channelCount === 0 && !hasYoutubeChannels && ( showChannelCreation && <UserFirstChannel />,
<UserFirstChannel /> showYoutubeTransfer && <YoutubeTransferWelcome />,
showLoadingSpinner && (
<div className="main--empty">
<Spinner />
</div>
), ),
hasVerifiedEmail && hasYoutubeChannels && (hasTransferrableYoutubeChannels || hasPendingYoutubeTransfer) && (
<YoutubeTransferWelcome />
),
hasVerifiedEmail &&
balance === 0 &&
!getSyncError &&
(fetchingChannels ||
!hasFetchedReward ||
claimingReward ||
syncIsPending ||
(syncEnabled && !syncHash) ||
// Just claimed the email award, wait until the balance updates to move forward
(balance === 0 && hasFetchedReward && hasClaimedEmailAward)) && (
<div className="main--empty">
<Spinner />
</div>
),
]; ];
let componentToRender; let componentToRender;

View file

@ -13,25 +13,33 @@ type Props = {
videosImported: ?Array<number>, // [currentAmountImported, totalAmountToImport] videosImported: ?Array<number>, // [currentAmountImported, totalAmountToImport]
}; };
const LBRY_YT_URL = 'https://lbry.com/youtube/status/'; const NOT_TRANSFERRED = 'not_transferred';
const NOT_TRANSFERED = 'not_transferred';
const PENDING_TRANSFER = 'pending_transfer'; const PENDING_TRANSFER = 'pending_transfer';
const COMPLETED_TRANSFER = 'completed_transfer'; const COMPLETED_TRANSFER = 'completed_transfer';
export default function YoutubeTransferStatus(props: Props) { export default function YoutubeTransferStatus(props: Props) {
const { youtubeChannels, ytImportPending, claimChannels, videosImported, checkYoutubeTransfer, updateUser } = props; const { youtubeChannels, ytImportPending, claimChannels, videosImported, checkYoutubeTransfer, updateUser } = props;
const hasChannels = youtubeChannels && youtubeChannels.length; const hasChannels = youtubeChannels && youtubeChannels.length;
const transferEnabled = youtubeChannels && youtubeChannels.some(el => el.transferable === true);
const transferComplete =
youtubeChannels &&
youtubeChannels.some(({ transfer_state: transferState }) => transferState === COMPLETED_TRANSFER);
let youtubeUrls = let transferEnabled = false;
youtubeChannels && let transferStarted = false;
youtubeChannels.map( let transferComplete = false;
({ lbry_channel_name: channelName, channel_claim_id: claimId }) => `lbry://${channelName}#${claimId}` if (hasChannels) {
); for (var i = 0; i < youtubeChannels.length; i++) {
const { transfer_state: transferState, transferable } = youtubeChannels[i];
if (transferable) {
transferEnabled = true;
}
if (transferState === COMPLETED_TRANSFER) {
transferComplete = true;
}
if (transferState === PENDING_TRANSFER) {
transferStarted = true;
}
}
}
let total; let total;
let complete; let complete;
@ -44,7 +52,7 @@ export default function YoutubeTransferStatus(props: Props) {
const { transferable, transfer_state: transferState, sync_status: syncStatus } = channel; const { transferable, transfer_state: transferState, sync_status: syncStatus } = channel;
if (!transferable) { if (!transferable) {
switch (transferState) { switch (transferState) {
case NOT_TRANSFERED: case NOT_TRANSFERRED:
return syncStatus[0].toUpperCase() + syncStatus.slice(1); return syncStatus[0].toUpperCase() + syncStatus.slice(1);
case PENDING_TRANSFER: case PENDING_TRANSFER:
return __('Transfer in progress'); return __('Transfer in progress');
@ -58,7 +66,7 @@ export default function YoutubeTransferStatus(props: Props) {
React.useEffect(() => { React.useEffect(() => {
// If a channel is transferrable, theres nothing to check // If a channel is transferrable, theres nothing to check
if (!transferComplete) { if (transferStarted && !transferComplete) {
checkYoutubeTransfer(); checkYoutubeTransfer();
let interval = setInterval(() => { let interval = setInterval(() => {
@ -70,30 +78,29 @@ export default function YoutubeTransferStatus(props: Props) {
clearInterval(interval); clearInterval(interval);
}; };
} }
}, [transferComplete, checkYoutubeTransfer, updateUser]); }, [transferComplete, transferStarted, checkYoutubeTransfer, updateUser]);
return ( return (
hasChannels && hasChannels &&
!transferComplete && ( !transferComplete && (
<div> <div>
<Card <Card
title={youtubeUrls.length > 1 ? __('Your YouTube Channels') : __('Your YouTube Channel')} title={youtubeChannels.length > 1 ? __('Your YouTube Channels') : __('Your YouTube Channel')}
subtitle={ subtitle={
<span> <span>
{__('Your videos are currently being transferred. There is nothing else for you to do.')}{' '} {transferStarted
<Button button="link" href={LBRY_YT_URL} label={__('Learn more')} />. ? __('Your videos are currently being transferred. There is nothing else for you to do.')
: __('Your videos are ready to be transferred.')}
</span> </span>
} }
body={ body={
<section> <section>
{youtubeUrls.map((url, index) => { {youtubeChannels.map((channel, index) => {
const channel = youtubeChannels[index]; const { lbry_channel_name: channelName, channel_claim_id: claimId } = channel;
const url = `lbry://${channelName}#${claimId}`;
const transferState = getMessage(channel); const transferState = getMessage(channel);
return ( return (
<div <div key={url} className="card--inline">
key={url}
style={{ border: '1px solid #ccc', borderRadius: 'var(--card-radius)', marginBottom: '1rem' }}
>
<ClaimPreview uri={url} actions={<span className="help">{transferState}</span>} properties={''} /> <ClaimPreview uri={url} actions={<span className="help">{transferState}</span>} properties={''} />
</div> </div>
); );

View file

@ -0,0 +1,17 @@
// @flow
import React from 'react';
import usePrevious from 'effects/use-previous';
// Returns true once a loading value has changed from false => true => false
export default function useFetched(fetching: boolean) {
const wasFetching = usePrevious(fetching);
const [fetched, setFetched] = React.useState(false);
React.useEffect(() => {
if (wasFetching && !fetching) {
setFetched(true);
}
}, [wasFetching, fetching, setFetched]);
return fetched;
}

View file

@ -3,7 +3,7 @@ import React from 'react';
import { Modal } from 'modal/modal'; import { Modal } from 'modal/modal';
import { Form, FormField } from 'component/common/form'; import { Form, FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import usePersistedState from 'util/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
type Props = { type Props = {
uri: string, uri: string,

View file

@ -16,7 +16,7 @@ function DiscoverPage(props: Props) {
return ( return (
<Page> <Page>
{email && <TagsSelect showClose title={__('Customize Your Homepage')} />} {(email || !IS_WEB) && <TagsSelect showClose title={__('Customize Your Homepage')} />}
<ClaimListDiscover <ClaimListDiscover
hideCustomization={IS_WEB && !email} hideCustomization={IS_WEB && !email}
personalView personalView

View file

@ -38,9 +38,7 @@ function FollowingPage(props: Props) {
return ( return (
<Page> <Page>
<div className="card card--section"> <TagsSelect showClose={false} title={__('Follow New Tags')} />
<TagsSelect showClose={false} title={__('Customize Your Tags')} />
</div>
<div className="card"> <div className="card">
<ClaimList <ClaimList
header={viewingSuggestedSubs ? __('Discover New Channels') : __('Channels You Follow')} header={viewingSuggestedSubs ? __('Discover New Channels') : __('Channels You Follow')}

View file

@ -3,7 +3,7 @@ import React, { useRef } from 'react';
import Page from 'component/page'; import Page from 'component/page';
import ClaimListDiscover from 'component/claimListDiscover'; import ClaimListDiscover from 'component/claimListDiscover';
import Button from 'component/button'; import Button from 'component/button';
import useHover from 'util/use-hover'; import useHover from 'effects/use-hover';
import analytics from 'analytics'; import analytics from 'analytics';
type Props = { type Props = {

View file

@ -16,6 +16,7 @@ import {
makeSelectClaimIsMine, makeSelectClaimIsMine,
doPopulateSharedUserState, doPopulateSharedUserState,
doFetchChannelListMine, doFetchChannelListMine,
getSync,
} from 'lbry-redux'; } from 'lbry-redux';
import Native from 'native'; import Native from 'native';
import { doFetchDaemonSettings } from 'redux/actions/settings'; import { doFetchDaemonSettings } from 'redux/actions/settings';
@ -454,6 +455,8 @@ export function doSignIn() {
dispatch(doBalanceSubscribe()); dispatch(doBalanceSubscribe());
dispatch(doCheckSubscriptionsInit()); dispatch(doCheckSubscriptionsInit());
dispatch(doFetchChannelListMine()); dispatch(doFetchChannelListMine());
dispatch(getSync());
// @endif // @endif
Lbryio.call('user_settings', 'get').then(settings => { Lbryio.call('user_settings', 'get').then(settings => {

View file

@ -55,15 +55,20 @@
margin-right: auto; margin-right: auto;
} }
// "cards" inside cards
.card--inline { .card--inline {
box-shadow: none; border: 1px solid $lbry-gray-1;
border-radius: none; border-radius: var(--card-radius);
margin-bottom: 0;
[data-mode='dark'] & {
border-color: var(--dm-color-03);
}
} }
.card--claim-preview-wrap { .card--claim-preview-wrap {
@extend .card; @extend .card;
margin: var(--spacing-xlarge) 0; margin: var(--spacing-xlarge) 0;
min-width: 35rem;
} }
.card--claim-preview-selected { .card--claim-preview-selected {
@ -241,7 +246,7 @@
.card__main-actions { .card__main-actions {
padding: var(--spacing-large); padding: var(--spacing-large);
background-color: rgba($lbry-blue-1, 0.1); background-color: var(--color-card-actions);
color: darken($lbry-gray-5, 15%); color: darken($lbry-gray-5, 15%);
font-size: var(--font-body); font-size: var(--font-body);

View file

@ -1,6 +1,12 @@
$border-color: rgba($lbry-teal-5, 0.1); $border-color: rgba($lbry-teal-5, 0.1);
$border-color--dark: var(--dm-color-04); $border-color--dark: var(--dm-color-04);
.claim-list {
.claim-preview {
border-top: 1px solid $border-color;
}
}
.claim-list__header { .claim-list__header {
display: flex; display: flex;
align-items: center; align-items: center;
@ -85,6 +91,7 @@ $border-color--dark: var(--dm-color-04);
} }
.claim-preview { .claim-preview {
flex: 1;
display: flex; display: flex;
position: relative; position: relative;
overflow: visible; overflow: visible;
@ -99,14 +106,6 @@ $border-color--dark: var(--dm-color-04);
flex-shrink: 0; flex-shrink: 0;
margin-right: var(--spacing-medium); margin-right: var(--spacing-medium);
} }
}
.claim-preview {
border-top: 1px solid $border-color;
&:only-of-type {
border: none;
}
[data-mode='dark'] & { [data-mode='dark'] & {
color: $lbry-white; color: $lbry-white;
@ -114,14 +113,6 @@ $border-color--dark: var(--dm-color-04);
} }
} }
.claim-preview--injected + .claim-preview {
border-top: 1px solid $border-color;
[data-mode='dark'] & {
border-color: $border-color--dark;
}
}
.claim-preview--large { .claim-preview--large {
border: none; border: none;
padding: 0; padding: 0;

View file

@ -1,5 +1,5 @@
.icon__wrapper { .icon__wrapper {
@extend .card__subtitle; background-color: var(--color-card-actions);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View file

@ -66,10 +66,13 @@
} }
.main--contained { .main--contained {
max-width: 40rem;
min-width: 25rem;
margin: auto; margin: auto;
margin-top: 5rem; margin-top: 5rem;
display: flex;
flex-direction: column;
align-items: flex-start;
max-width: 40rem;
text-align: left;
} }
.main--full-width { .main--full-width {

View file

@ -39,6 +39,10 @@
font-size: var(--font-multiplier-small); font-size: var(--font-multiplier-small);
} }
.navigation-links__inline {
margin-left: 1.7rem;
}
.navigation-link__wrapper { .navigation-link__wrapper {
margin: var(--spacing-miniscule) 0; margin: var(--spacing-miniscule) 0;
} }

View file

@ -40,6 +40,7 @@ $large-breakpoint: 1921px;
// Color // Color
--color-background: #f7f7f7; --color-background: #f7f7f7;
--color-background--splash: #270f34; --color-background--splash: #270f34;
--color-card-actions: #f7fbfe;
// Dark Mode // Dark Mode
--dm-color-01: #ddd; --dm-color-01: #ddd;

View file

@ -6850,9 +6850,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
yargs "^13.2.2" yargs "^13.2.2"
zstd-codec "^0.1.1" zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#d44cd9ca56dee784dba42c0cc13061ae75cbd46c: lbry-redux@lbryio/lbry-redux#42bf926138872d14523be7191694309be4f37605:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/d44cd9ca56dee784dba42c0cc13061ae75cbd46c" resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/42bf926138872d14523be7191694309be4f37605"
dependencies: dependencies:
proxy-polyfill "0.1.6" proxy-polyfill "0.1.6"
reselect "^3.0.0" reselect "^3.0.0"