cleanup and add tags to publish page
This commit is contained in:
parent
f39ca4dd57
commit
f9269ae969
59 changed files with 1265 additions and 913 deletions
1
flow-typed/publish.js
vendored
1
flow-typed/publish.js
vendored
|
@ -48,4 +48,5 @@ declare type PublishParams = {
|
||||||
},
|
},
|
||||||
claim: StreamClaim,
|
claim: StreamClaim,
|
||||||
nsfw: boolean,
|
nsfw: boolean,
|
||||||
|
tags: Array<Tag>,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import type { Node } from 'react';
|
||||||
import React, { forwardRef } from 'react';
|
import React, { forwardRef } from 'react';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
@ -14,13 +15,12 @@ type Props = {
|
||||||
icon: ?string,
|
icon: ?string,
|
||||||
iconRight: ?string,
|
iconRight: ?string,
|
||||||
disabled: ?boolean,
|
disabled: ?boolean,
|
||||||
children: ?React.Node,
|
children: ?Node,
|
||||||
navigate: ?string,
|
navigate: ?string,
|
||||||
className: ?string,
|
className: ?string,
|
||||||
description: ?string,
|
description: ?string,
|
||||||
type: string,
|
type: string,
|
||||||
button: ?string, // primary, secondary, alt, link
|
button: ?string, // primary, secondary, alt, link
|
||||||
iconColor?: string,
|
|
||||||
iconSize?: number,
|
iconSize?: number,
|
||||||
constrict: ?boolean, // to shorten the button and ellipsis, only use for links
|
constrict: ?boolean, // to shorten the button and ellipsis, only use for links
|
||||||
activeClass?: string,
|
activeClass?: string,
|
||||||
|
@ -31,7 +31,9 @@ type Props = {
|
||||||
onMouseLeave: ?(any) => any,
|
onMouseLeave: ?(any) => any,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Button = forwardRef((props: Props, ref) => {
|
// use forwardRef to allow consumers to pass refs to the button content if they want to
|
||||||
|
// flow requires forwardRef have default type arguments passed to it
|
||||||
|
const Button = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
const {
|
const {
|
||||||
type = 'button',
|
type = 'button',
|
||||||
onClick,
|
onClick,
|
||||||
|
@ -48,7 +50,6 @@ const Button = forwardRef((props: Props, ref) => {
|
||||||
className,
|
className,
|
||||||
description,
|
description,
|
||||||
button,
|
button,
|
||||||
iconColor,
|
|
||||||
iconSize,
|
iconSize,
|
||||||
constrict,
|
constrict,
|
||||||
activeClass,
|
activeClass,
|
||||||
|
@ -74,10 +75,10 @@ const Button = forwardRef((props: Props, ref) => {
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<span className="button__content">
|
<span className="button__content">
|
||||||
{icon && <Icon icon={icon} iconColor={iconColor} size={iconSize} />}
|
{icon && <Icon icon={icon} size={iconSize} />}
|
||||||
{label && <span className="button__label">{label}</span>}
|
{label && <span className="button__label">{label}</span>}
|
||||||
{children && children}
|
{children && children}
|
||||||
{iconRight && <Icon icon={iconRight} iconColor={iconColor} size={iconSize} />}
|
{iconRight && <Icon icon={iconRight} size={iconSize} />}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import ClaimListItem from 'component/claimListItem';
|
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 'util/use-persisted-state';
|
||||||
|
@ -36,10 +36,10 @@ export default function ClaimList(props: Props) {
|
||||||
return (
|
return (
|
||||||
<section className={classnames('file-list')}>
|
<section className={classnames('file-list')}>
|
||||||
{header !== false && (
|
{header !== false && (
|
||||||
<div className={classnames('file-list__header', { 'file-list__header--small': type === 'small' })}>
|
<div className={classnames('claim-list__header', { 'claim-list__header--small': type === 'small' })}>
|
||||||
{header || (
|
{header || (
|
||||||
<FormField
|
<FormField
|
||||||
className="file-list__dropdown"
|
className="claim-list__dropdown"
|
||||||
type="select"
|
type="select"
|
||||||
name="file_sort"
|
name="file_sort"
|
||||||
value={currentSort}
|
value={currentSort}
|
||||||
|
@ -50,16 +50,16 @@ export default function ClaimList(props: Props) {
|
||||||
</FormField>
|
</FormField>
|
||||||
)}
|
)}
|
||||||
{loading && <Spinner light type="small" />}
|
{loading && <Spinner light type="small" />}
|
||||||
<div className="file-list__alt-controls">{headerAltControls}</div>
|
<div className="claim-list__alt-controls">{headerAltControls}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{meta && <div className="file-list__meta">{meta}</div>}
|
{meta && <div className="claim-list__meta">{meta}</div>}
|
||||||
{hasUris && (
|
{hasUris && (
|
||||||
<ul>
|
<ul>
|
||||||
{sortedUris.map((uri, index) => (
|
{sortedUris.map((uri, index) => (
|
||||||
<React.Fragment key={uri}>
|
<React.Fragment key={uri}>
|
||||||
<ClaimListItem uri={uri} type={type} />
|
<ClaimPreview uri={uri} type={type} />
|
||||||
{index === 4 && injectedItem && <li className="file-list__item--injected">{injectedItem}</li>}
|
{index === 4 && injectedItem && <li className="claim-list__item--injected">{injectedItem}</li>}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -70,7 +70,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
const header = (
|
const header = (
|
||||||
<h1 className="card__title--flex">
|
<h1 className="card__title--flex">
|
||||||
<FormField
|
<FormField
|
||||||
className="file-list__dropdown"
|
className="claim-list__dropdown"
|
||||||
type="select"
|
type="select"
|
||||||
name="trending_sort"
|
name="trending_sort"
|
||||||
value={typeSort}
|
value={typeSort}
|
||||||
|
@ -89,7 +89,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
<FormField
|
<FormField
|
||||||
type="select"
|
type="select"
|
||||||
name="trending_overview"
|
name="trending_overview"
|
||||||
className="file-list__dropdown"
|
className="claim-list__dropdown"
|
||||||
value={personalSort}
|
value={personalSort}
|
||||||
onChange={e => setPersonalSort(e.target.value)}
|
onChange={e => setPersonalSort(e.target.value)}
|
||||||
>
|
>
|
||||||
|
@ -107,7 +107,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{typeSort === 'top' && (
|
{typeSort === 'top' && (
|
||||||
<FormField
|
<FormField
|
||||||
className="file-list__dropdown"
|
className="claim-list__dropdown"
|
||||||
type="select"
|
type="select"
|
||||||
name="trending_time"
|
name="trending_time"
|
||||||
value={timeSort}
|
value={timeSort}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectBlackListedOutpoints } from 'lbryinc';
|
import { selectBlackListedOutpoints } from 'lbryinc';
|
||||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||||
import ClaimListItem from './view';
|
import ClaimPreview from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
pending: makeSelectClaimIsPending(props.uri)(state),
|
pending: makeSelectClaimIsPending(props.uri)(state),
|
||||||
|
@ -32,4 +32,4 @@ const perform = dispatch => ({
|
||||||
export default connect(
|
export default connect(
|
||||||
select,
|
select,
|
||||||
perform
|
perform
|
||||||
)(ClaimListItem);
|
)(ClaimPreview);
|
||||||
|
|
35
src/ui/component/claimPreview/index.js
Normal file
35
src/ui/component/claimPreview/index.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
doResolveUri,
|
||||||
|
makeSelectClaimForUri,
|
||||||
|
makeSelectIsUriResolving,
|
||||||
|
makeSelectClaimIsMine,
|
||||||
|
makeSelectClaimIsPending,
|
||||||
|
makeSelectThumbnailForUri,
|
||||||
|
makeSelectTitleForUri,
|
||||||
|
makeSelectClaimIsNsfw,
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import { selectBlackListedOutpoints } from 'lbryinc';
|
||||||
|
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||||
|
import ClaimPreview from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
pending: makeSelectClaimIsPending(props.uri)(state),
|
||||||
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
obscureNsfw: !selectShowNsfw(state),
|
||||||
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
|
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
|
||||||
|
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||||
|
title: makeSelectTitleForUri(props.uri)(state),
|
||||||
|
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
|
||||||
|
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(ClaimPreview);
|
|
@ -35,7 +35,7 @@ type Props = {
|
||||||
}>,
|
}>,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ClaimListItem(props: Props) {
|
function ClaimPreview(props: Props) {
|
||||||
const {
|
const {
|
||||||
obscureNsfw,
|
obscureNsfw,
|
||||||
claimIsMine,
|
claimIsMine,
|
||||||
|
@ -94,10 +94,10 @@ function ClaimListItem(props: Props) {
|
||||||
|
|
||||||
if (placeholder && !claim) {
|
if (placeholder && !claim) {
|
||||||
return (
|
return (
|
||||||
<li className="file-list__item" disabled>
|
<li className="claim-list__item" disabled>
|
||||||
<div className="placeholder media__thumb" />
|
<div className="placeholder media__thumb" />
|
||||||
<div className="placeholder__wrapper">
|
<div className="placeholder__wrapper">
|
||||||
<div className="placeholder file-list__item-title" />
|
<div className="placeholder claim-list__item-title" />
|
||||||
<div className="placeholder media__subtitle" />
|
<div className="placeholder media__subtitle" />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -107,16 +107,17 @@ function ClaimListItem(props: Props) {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
role="link"
|
role="link"
|
||||||
onClick={onClick}
|
onClick={pending ? undefined : onClick}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
className={classnames('file-list__item', {
|
className={classnames('claim-list__item', {
|
||||||
'file-list__item--large': type === 'large',
|
'claim-list__item--large': type === 'large',
|
||||||
|
'claim-list__pending': pending,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isChannel ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />}
|
{isChannel ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />}
|
||||||
<div className="file-list__item-metadata">
|
<div className="claim-list__item-metadata">
|
||||||
<div className="file-list__item-info">
|
<div className="claim-list__item-info">
|
||||||
<div className="file-list__item-title">
|
<div className="claim-list__item-title">
|
||||||
<TruncatedText text={title || (claim && claim.name)} lines={1} />
|
<TruncatedText text={title || (claim && claim.name)} lines={1} />
|
||||||
</div>
|
</div>
|
||||||
{type !== 'small' && (
|
{type !== 'small' && (
|
||||||
|
@ -127,7 +128,7 @@ function ClaimListItem(props: Props) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="file-list__item-properties">
|
<div className="claim-list__item-properties">
|
||||||
<div className="media__subtitle">
|
<div className="media__subtitle">
|
||||||
<UriIndicator uri={uri} link />
|
<UriIndicator uri={uri} link />
|
||||||
{pending && <div>Pending...</div>}
|
{pending && <div>Pending...</div>}
|
||||||
|
@ -141,4 +142,4 @@ function ClaimListItem(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(ClaimListItem);
|
export default withRouter(ClaimPreview);
|
|
@ -1,13 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
// @if TARGET='app'
|
|
||||||
// $FlowFixMe
|
|
||||||
import { remote } from 'electron';
|
import { remote } from 'electron';
|
||||||
// @endif
|
|
||||||
// @if TARGET='web'
|
|
||||||
// $FlowFixMe
|
|
||||||
import { remote } from 'web/stubs';
|
|
||||||
// @endif
|
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
@ -21,6 +14,8 @@ type Props = {
|
||||||
type: string,
|
type: string,
|
||||||
currentPath: ?string,
|
currentPath: ?string,
|
||||||
onFileChosen: (string, string) => void,
|
onFileChosen: (string, string) => void,
|
||||||
|
label?: string,
|
||||||
|
placeholder?: string,
|
||||||
fileLabel?: string,
|
fileLabel?: string,
|
||||||
directoryLabel?: string,
|
directoryLabel?: string,
|
||||||
filters?: FileFilters[],
|
filters?: FileFilters[],
|
||||||
|
@ -83,13 +78,14 @@ class FileSelector extends React.PureComponent<Props> {
|
||||||
input: ?HTMLInputElement;
|
input: ?HTMLInputElement;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { type, currentPath, fileLabel, directoryLabel } = this.props;
|
const { type, currentPath, label, fileLabel, directoryLabel, placeholder } = this.props;
|
||||||
|
|
||||||
const label = type === 'file' ? fileLabel || __('Choose File') : directoryLabel || __('Choose Directory');
|
const buttonLabel = type === 'file' ? fileLabel || __('Choose File') : directoryLabel || __('Choose Directory');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<FormField
|
<FormField
|
||||||
|
label={label}
|
||||||
webkitdirectory="true"
|
webkitdirectory="true"
|
||||||
className="form-field--copyable"
|
className="form-field--copyable"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -98,8 +94,8 @@ class FileSelector extends React.PureComponent<Props> {
|
||||||
if (this.fileInput) this.fileInput.current.select();
|
if (this.fileInput) this.fileInput.current.select();
|
||||||
}}
|
}}
|
||||||
readOnly="readonly"
|
readOnly="readonly"
|
||||||
value={currentPath || __('No File Chosen')}
|
value={currentPath || placeholder || __('Choose a file')}
|
||||||
inputButton={<Button button="primary" label={label} onClick={() => this.handleButtonClick()} />}
|
inputButton={<Button button="primary" label={buttonLabel} onClick={() => this.handleButtonClick()} />}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
// A housing for all of our icons. Mostly taken fromhttps://github.com/feathericons/react-feather
|
// A housing for all of our icons. Mostly taken from https://github.com/feathericons/react-feather
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React, { forwardRef } from 'react';
|
import React, { forwardRef } from 'react';
|
||||||
|
|
||||||
|
@ -9,7 +9,8 @@ type IconProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns a react component
|
// Returns a react component
|
||||||
const buildIcon = (iconStrokes: React$Node, options?: {} = {}) =>
|
// Icons with tooltips need to use this function so the ref can be properly forwarded
|
||||||
|
const buildIcon = (iconStrokes: React$Node) =>
|
||||||
forwardRef((props: IconProps, ref) => {
|
forwardRef((props: IconProps, ref) => {
|
||||||
const { size = 24, color = 'currentColor', ...otherProps } = props;
|
const { size = 24, color = 'currentColor', ...otherProps } = props;
|
||||||
return (
|
return (
|
||||||
|
@ -24,7 +25,6 @@ const buildIcon = (iconStrokes: React$Node, options?: {} = {}) =>
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
{...options}
|
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
{iconStrokes}
|
{iconStrokes}
|
||||||
|
@ -34,7 +34,7 @@ const buildIcon = (iconStrokes: React$Node, options?: {} = {}) =>
|
||||||
|
|
||||||
export const icons = {
|
export const icons = {
|
||||||
// The LBRY icon is different from the base icon set so don't use buildIcon()
|
// The LBRY icon is different from the base icon set so don't use buildIcon()
|
||||||
[ICONS.LBRY]: (
|
[ICONS.LBRY]: () => (
|
||||||
<svg stroke="currentColor" fill="currentColor" x="0px" y="0px" viewBox="0 0 322 254" className="icon lbry-icon">
|
<svg stroke="currentColor" fill="currentColor" x="0px" y="0px" viewBox="0 0 322 254" className="icon lbry-icon">
|
||||||
<path d="M296,85.9V100l-138.8,85.3L52.6,134l0.2-7.9l104,51.2L289,96.1v-5.8L164.2,30.1L25,116.2v38.5l131.8,65.2 l137.6-84.4l3.9,6l-141.1,86.4L18.1,159.1v-46.8l145.8-90.2C163.9,22.1,296,85.9,296,85.9z" />
|
<path d="M296,85.9V100l-138.8,85.3L52.6,134l0.2-7.9l104,51.2L289,96.1v-5.8L164.2,30.1L25,116.2v38.5l131.8,65.2 l137.6-84.4l3.9,6l-141.1,86.4L18.1,159.1v-46.8l145.8-90.2C163.9,22.1,296,85.9,296,85.9z" />
|
||||||
<path d="M294.3,150.9l2-12.6l-12.2-2.1l0.8-4.9l17.1,2.9l-2.8,17.5L294.3,150.9L294.3,150.9z" />
|
<path d="M294.3,150.9l2-12.6l-12.2-2.1l0.8-4.9l17.1,2.9l-2.8,17.5L294.3,150.9L294.3,150.9z" />
|
||||||
|
|
|
@ -13,7 +13,7 @@ const BLUE_COLOR = '#49b2e2';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
icon: string,
|
icon: string,
|
||||||
tooltip?: string, // tooltip direction
|
tooltip?: boolean,
|
||||||
iconColor?: string,
|
iconColor?: string,
|
||||||
size?: number,
|
size?: number,
|
||||||
className?: string,
|
className?: string,
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import type { Node } from 'react';
|
||||||
|
import React from 'react';
|
||||||
import ReachTooltip from '@reach/tooltip';
|
import ReachTooltip from '@reach/tooltip';
|
||||||
import '@reach/tooltip/styles.css';
|
import '@reach/tooltip/styles.css';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label: string,
|
label: string | Node,
|
||||||
children?: React.Node,
|
children?: Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
function Tooltip(props: Props) {
|
function Tooltip(props: Props) {
|
||||||
|
|
|
@ -3,10 +3,12 @@ import Button from 'component/button';
|
||||||
|
|
||||||
export default function UnsupportedOnWeb() {
|
export default function UnsupportedOnWeb() {
|
||||||
return (
|
return (
|
||||||
<div className="card__content help help--warning">
|
IS_WEB && (
|
||||||
This page is not currently supported on the web.{' '}
|
<div className="card__content help help--warning">
|
||||||
<Button button="link" label={__('Download the desktop app')} href="https://lbry.com/get" /> for full feature
|
This page is not currently supported on the web.{' '}
|
||||||
support.
|
<Button button="link" label={__('Download the desktop app')} href="https://lbry.com/get" /> for full feature
|
||||||
</div>
|
support.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ class ErrorBoundary extends React.Component<Props, State> {
|
||||||
render() {
|
render() {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
return (
|
return (
|
||||||
<div className="load-screen">
|
<div className="main main--empty">
|
||||||
<Yrbl
|
<Yrbl
|
||||||
type="sad"
|
type="sad"
|
||||||
title={__('Aw shucks!')}
|
title={__('Aw shucks!')}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import type { Node } from 'react';
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import * as React from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Tooltip from 'component/common/tooltip';
|
import Tooltip from 'component/common/tooltip';
|
||||||
import { requestFullscreen, fullscreenElement } from 'util/full-screen';
|
import { requestFullscreen, fullscreenElement } from 'util/full-screen';
|
||||||
|
@ -16,7 +17,7 @@ type Props = {
|
||||||
openModal: (id: string, { uri: string }) => void,
|
openModal: (id: string, { uri: string }) => void,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
fileInfo: FileInfo,
|
fileInfo: FileInfo,
|
||||||
viewerContainer: React.Ref,
|
viewerContainer: ?{ current: Node },
|
||||||
showFullscreen: boolean,
|
showFullscreen: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ class FileDownloadLink extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolTip label={__('Download')}>
|
<ToolTip label={__('Add to your library')}>
|
||||||
<Button
|
<Button
|
||||||
button="link"
|
button="link"
|
||||||
icon={ICONS.DOWNLOAD}
|
icon={ICONS.DOWNLOAD}
|
||||||
|
|
|
@ -23,7 +23,7 @@ export default function FileProperties(props: Props) {
|
||||||
<div className="file-properties">
|
<div className="file-properties">
|
||||||
{isSubscribed && <Icon tooltip icon={icons.SUBSCRIPTION} />}
|
{isSubscribed && <Icon tooltip icon={icons.SUBSCRIPTION} />}
|
||||||
{!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />}
|
{!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />}
|
||||||
{isRewardContent && <Icon tooltip iconColor="red" icon={icons.FEATURED} />}
|
{isRewardContent && <Icon tooltip icon={icons.FEATURED} />}
|
||||||
<FilePrice hideFree uri={uri} />
|
<FilePrice hideFree uri={uri} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,8 @@ import React from 'react';
|
||||||
import LoadingScreen from 'component/common/loading-screen';
|
import LoadingScreen from 'component/common/loading-screen';
|
||||||
import VideoViewer from 'component/viewers/videoViewer';
|
import VideoViewer from 'component/viewers/videoViewer';
|
||||||
|
|
||||||
|
// Audio player on hold until the current player is dropped
|
||||||
|
// This component is half working
|
||||||
// const AudioViewer = React.lazy<*>(() =>
|
// const AudioViewer = React.lazy<*>(() =>
|
||||||
// import(/* webpackChunkName: "audioViewer" */
|
// import(/* webpackChunkName: "audioViewer" */
|
||||||
// 'component/viewers/audioViewer')
|
// 'component/viewers/audioViewer')
|
||||||
|
|
46
src/ui/component/publishAdditionalOptions/index.js
Normal file
46
src/ui/component/publishAdditionalOptions/index.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doResolveUri, selectBalance } from 'lbry-redux';
|
||||||
|
import {
|
||||||
|
selectPublishFormValues,
|
||||||
|
selectIsStillEditing,
|
||||||
|
selectMyClaimForUri,
|
||||||
|
selectIsResolvingPublishUris,
|
||||||
|
selectTakeOverAmount,
|
||||||
|
} from 'redux/selectors/publish';
|
||||||
|
import {
|
||||||
|
doResetThumbnailStatus,
|
||||||
|
doClearPublish,
|
||||||
|
doUpdatePublishForm,
|
||||||
|
doPublish,
|
||||||
|
doPrepareEdit,
|
||||||
|
} from 'redux/actions/publish';
|
||||||
|
import { selectUnclaimedRewardValue } from 'lbryinc';
|
||||||
|
import PublishPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
...selectPublishFormValues(state),
|
||||||
|
// The winning claim for a short lbry uri
|
||||||
|
amountNeededForTakeover: selectTakeOverAmount(state),
|
||||||
|
// My previously published claims under this short lbry uri
|
||||||
|
myClaimForUri: selectMyClaimForUri(state),
|
||||||
|
// If I clicked the "edit" button, have I changed the uri?
|
||||||
|
// Need this to make it easier to find the source on previously published content
|
||||||
|
isStillEditing: selectIsStillEditing(state),
|
||||||
|
isResolvingUri: selectIsResolvingPublishUris(state),
|
||||||
|
totalRewardValue: selectUnclaimedRewardValue(state),
|
||||||
|
balance: selectBalance(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||||
|
clearPublish: () => dispatch(doClearPublish()),
|
||||||
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
publish: params => dispatch(doPublish(params)),
|
||||||
|
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
||||||
|
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(PublishPage);
|
|
@ -4,7 +4,7 @@ import { FormField } from 'component/common/form';
|
||||||
import { CC_LICENSES, COPYRIGHT, OTHER, PUBLIC_DOMAIN, NONE } from 'constants/licenses';
|
import { CC_LICENSES, COPYRIGHT, OTHER, PUBLIC_DOMAIN, NONE } from 'constants/licenses';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
licenseType: string,
|
licenseType: ?string,
|
||||||
licenseUrl: ?string,
|
licenseUrl: ?string,
|
||||||
otherLicenseDescription: ?string,
|
otherLicenseDescription: ?string,
|
||||||
handleLicenseChange: (string, string) => void,
|
handleLicenseChange: (string, string) => void,
|
80
src/ui/component/publishAdditionalOptions/view.jsx
Normal file
80
src/ui/component/publishAdditionalOptions/view.jsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import usePersistedState from 'util/use-persisted-state';
|
||||||
|
import { FormField } from 'component/common/form';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import LicenseType from './license-type';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
language: ?string,
|
||||||
|
name: ?string,
|
||||||
|
licenseType: ?string,
|
||||||
|
otherLicenseDescription: ?string,
|
||||||
|
licenseUrl: ?string,
|
||||||
|
disabled: boolean,
|
||||||
|
updatePublishForm: ({}) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function PublishAdvanced(props: Props) {
|
||||||
|
const { language, name, licenseType, otherLicenseDescription, licenseUrl, updatePublishForm } = props;
|
||||||
|
const [hideSection, setHideSection] = usePersistedState('publish-advanced-options', true);
|
||||||
|
|
||||||
|
function toggleHideSection() {
|
||||||
|
setHideSection(!hideSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="card card--section">
|
||||||
|
{!hideSection && (
|
||||||
|
<div className={classnames('card__content', { 'card--disabled': !name })}>
|
||||||
|
<FormField
|
||||||
|
label={__('Language')}
|
||||||
|
type="select"
|
||||||
|
name="content_language"
|
||||||
|
value={language}
|
||||||
|
onChange={event => updatePublishForm({ language: event.target.value })}
|
||||||
|
>
|
||||||
|
<option value="en">{__('English')}</option>
|
||||||
|
<option value="zh">{__('Chinese')}</option>
|
||||||
|
<option value="fr">{__('French')}</option>
|
||||||
|
<option value="de">{__('German')}</option>
|
||||||
|
<option value="jp">{__('Japanese')}</option>
|
||||||
|
<option value="ru">{__('Russian')}</option>
|
||||||
|
<option value="es">{__('Spanish')}</option>
|
||||||
|
<option value="id">{__('Indonesian')}</option>
|
||||||
|
<option value="it">{__('Italian')}</option>
|
||||||
|
<option value="nl">{__('Dutch')}</option>
|
||||||
|
<option value="tr">{__('Turkish')}</option>
|
||||||
|
<option value="pl">{__('Polish')}</option>
|
||||||
|
<option value="ms">{__('Malay')}</option>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<LicenseType
|
||||||
|
licenseType={licenseType}
|
||||||
|
otherLicenseDescription={otherLicenseDescription}
|
||||||
|
licenseUrl={licenseUrl}
|
||||||
|
handleLicenseChange={(newLicenseType, newLicenseUrl) =>
|
||||||
|
updatePublishForm({
|
||||||
|
licenseType: newLicenseType,
|
||||||
|
licenseUrl: newLicenseUrl,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
handleLicenseDescriptionChange={event =>
|
||||||
|
updatePublishForm({
|
||||||
|
otherLicenseDescription: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
handleLicenseUrlChange={event => updatePublishForm({ licenseUrl: event.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="card__actions">
|
||||||
|
<Button label={hideSection ? __('Additional Options') : __('Hide')} button="link" onClick={toggleHideSection} />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublishAdvanced;
|
21
src/ui/component/publishFile/index.js
Normal file
21
src/ui/component/publishFile/index.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectBalance } from 'lbry-redux';
|
||||||
|
import { selectIsStillEditing, makeSelectPublishFormValue } from 'redux/selectors/publish';
|
||||||
|
import { doUpdatePublishForm } from 'redux/actions/publish';
|
||||||
|
import PublishPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
name: makeSelectPublishFormValue('name')(state),
|
||||||
|
filePath: makeSelectPublishFormValue('filePath')(state),
|
||||||
|
isStillEditing: selectIsStillEditing(state),
|
||||||
|
balance: selectBalance(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(PublishPage);
|
54
src/ui/component/publishFile/view.jsx
Normal file
54
src/ui/component/publishFile/view.jsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { regexInvalidURI } from 'lbry-redux';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import FileSelector from 'component/common/file-selector';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
name: ?string,
|
||||||
|
filePath: ?string,
|
||||||
|
isStillEditing: boolean,
|
||||||
|
balance: number,
|
||||||
|
updatePublishForm: ({}) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function PublishFile(props: Props) {
|
||||||
|
const { name, balance, filePath, isStillEditing, updatePublishForm } = 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(' ', '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePublishForm(publishFormParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className={classnames('card card--section', {
|
||||||
|
'card--disabled': balance === 0,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<header className="card__header">
|
||||||
|
<h2 className="card__title card__title--flex-between">{isStillEditing ? __('Edit') : __('Publish')}</h2>
|
||||||
|
{isStillEditing && <p className="card__subtitle">{__('You are currently editing a claim.')}</p>}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="card__content">
|
||||||
|
<FileSelector currentPath={filePath} onFileChosen={handleFileChange} />
|
||||||
|
{!!isStillEditing && name && (
|
||||||
|
<p className="help">
|
||||||
|
{__("If you don't choose a file, the file from your existing claim")}
|
||||||
|
{` "${name}" `}
|
||||||
|
{__('will be used.')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublishFile;
|
|
@ -1,3 +1,45 @@
|
||||||
import PublishForm from './view';
|
import { connect } from 'react-redux';
|
||||||
|
import { doResolveUri } from 'lbry-redux';
|
||||||
|
import {
|
||||||
|
selectPublishFormValues,
|
||||||
|
selectIsStillEditing,
|
||||||
|
selectMyClaimForUri,
|
||||||
|
selectIsResolvingPublishUris,
|
||||||
|
selectTakeOverAmount,
|
||||||
|
} from 'redux/selectors/publish';
|
||||||
|
import {
|
||||||
|
doResetThumbnailStatus,
|
||||||
|
doClearPublish,
|
||||||
|
doUpdatePublishForm,
|
||||||
|
doPublish,
|
||||||
|
doPrepareEdit,
|
||||||
|
} from 'redux/actions/publish';
|
||||||
|
import { selectUnclaimedRewardValue } from 'lbryinc';
|
||||||
|
import PublishPage from './view';
|
||||||
|
|
||||||
export default PublishForm;
|
const select = state => ({
|
||||||
|
...selectPublishFormValues(state),
|
||||||
|
// The winning claim for a short lbry uri
|
||||||
|
amountNeededForTakeover: selectTakeOverAmount(state),
|
||||||
|
// My previously published claims under this short lbry uri
|
||||||
|
myClaimForUri: selectMyClaimForUri(state),
|
||||||
|
// If I clicked the "edit" button, have I changed the uri?
|
||||||
|
// Need this to make it easier to find the source on previously published content
|
||||||
|
isStillEditing: selectIsStillEditing(state),
|
||||||
|
isResolvingUri: selectIsResolvingPublishUris(state),
|
||||||
|
totalRewardValue: selectUnclaimedRewardValue(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||||
|
clearPublish: () => dispatch(doClearPublish()),
|
||||||
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
publish: params => dispatch(doPublish(params)),
|
||||||
|
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
||||||
|
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(PublishPage);
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
uri: ?string,
|
|
||||||
isResolvingUri: boolean,
|
|
||||||
amountNeededForTakeover: ?number,
|
|
||||||
};
|
|
||||||
|
|
||||||
class BidHelpText extends React.PureComponent<Props> {
|
|
||||||
render() {
|
|
||||||
const { uri, isResolvingUri, amountNeededForTakeover } = this.props;
|
|
||||||
let bidHelpText;
|
|
||||||
|
|
||||||
if (uri) {
|
|
||||||
if (isResolvingUri) {
|
|
||||||
bidHelpText = __('Checking the winning claim amount...');
|
|
||||||
} else if (!amountNeededForTakeover) {
|
|
||||||
bidHelpText = __('Any amount will give you the winning bid.');
|
|
||||||
} else {
|
|
||||||
bidHelpText = `${__('If you bid more than')} ${amountNeededForTakeover} LBC, ${__(
|
|
||||||
'when someone navigates to'
|
|
||||||
)} ${uri} ${__('it will load your published content')}. ${__(
|
|
||||||
'However, you can get a longer version of this URL for any bid'
|
|
||||||
)}.`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
{__('This LBC remains yours and the deposit can be undone at any time.')}
|
|
||||||
<div>{bidHelpText}</div>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BidHelpText;
|
|
|
@ -1,50 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as React from 'react';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import { buildURI } from 'lbry-redux';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
uri: ?string,
|
|
||||||
myClaimForUri: ?StreamClaim,
|
|
||||||
isStillEditing: boolean,
|
|
||||||
onEditMyClaim: (any, string) => void,
|
|
||||||
};
|
|
||||||
|
|
||||||
class NameHelpText extends React.PureComponent<Props> {
|
|
||||||
render() {
|
|
||||||
const { uri, myClaimForUri, onEditMyClaim, isStillEditing } = this.props;
|
|
||||||
|
|
||||||
let nameHelpText;
|
|
||||||
|
|
||||||
if (isStillEditing) {
|
|
||||||
nameHelpText = __(
|
|
||||||
'You are currently editing this claim. If you change the URL, you will need to reselect a file.'
|
|
||||||
);
|
|
||||||
} else if (uri && myClaimForUri) {
|
|
||||||
const editUri = buildURI({
|
|
||||||
contentName: myClaimForUri.name,
|
|
||||||
claimId: myClaimForUri.claim_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
nameHelpText = (
|
|
||||||
<React.Fragment>
|
|
||||||
{__('You already have a claim at')}
|
|
||||||
{` ${uri} `}
|
|
||||||
<Button button="link" label="Edit it" onClick={() => onEditMyClaim(myClaimForUri, editUri)} />
|
|
||||||
<br />
|
|
||||||
{__('Publishing will update your existing claim.')}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
{nameHelpText || (
|
|
||||||
<span>{__('Create a URL for this content. Simpler names are easier to find and remember.')}</span>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NameHelpText;
|
|
|
@ -1,21 +1,22 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { COPYRIGHT, OTHER } from 'constants/licenses';
|
import React, { useEffect, Fragment } from 'react';
|
||||||
import { CHANNEL_NEW, CHANNEL_ANONYMOUS, MINIMUM_PUBLISH_BID } from 'constants/claim';
|
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
||||||
import * as ICONS from 'constants/icons';
|
import { buildURI, THUMBNAIL_STATUSES } from 'lbry-redux';
|
||||||
import * as React from 'react';
|
|
||||||
import { isNameValid, buildURI, regexInvalidURI, THUMBNAIL_STATUSES } from 'lbry-redux';
|
|
||||||
import { Form, FormField, FormFieldPrice, Submit } 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 classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import FileSelector from 'component/common/file-selector';
|
|
||||||
import SelectThumbnail from 'component/selectThumbnail';
|
|
||||||
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
|
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
|
||||||
import BidHelpText from './internal/bid-help-text';
|
import TagSelect from 'component/tagsSelect';
|
||||||
import NameHelpText from './internal/name-help-text';
|
import PublishText from 'component/publishText';
|
||||||
import LicenseType from './internal/license-type';
|
import PublishPrice from 'component/publishPrice';
|
||||||
|
import PublishFile from 'component/publishFile';
|
||||||
|
import PublishName from 'component/publishName';
|
||||||
|
import PublishAdditionalOptions from 'component/publishAdditionalOptions';
|
||||||
|
import PublishFormErrors from 'component/publishFormErrors';
|
||||||
|
import SelectThumbnail from 'component/selectThumbnail';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
tags: Array<Tag>,
|
||||||
publish: PublishParams => void,
|
publish: PublishParams => void,
|
||||||
filePath: ?string,
|
filePath: ?string,
|
||||||
bid: ?number,
|
bid: ?number,
|
||||||
|
@ -34,7 +35,6 @@ type Props = {
|
||||||
},
|
},
|
||||||
channel: string,
|
channel: string,
|
||||||
name: ?string,
|
name: ?string,
|
||||||
updatePublishForm: UpdatePublishFormData => void,
|
|
||||||
nameError: ?string,
|
nameError: ?string,
|
||||||
isResolvingUri: boolean,
|
isResolvingUri: boolean,
|
||||||
winningBidForClaimUri: number,
|
winningBidForClaimUri: number,
|
||||||
|
@ -43,7 +43,6 @@ type Props = {
|
||||||
otherLicenseDescription: ?string,
|
otherLicenseDescription: ?string,
|
||||||
licenseUrl: ?string,
|
licenseUrl: ?string,
|
||||||
uri: ?string,
|
uri: ?string,
|
||||||
bidError: ?string,
|
|
||||||
publishing: boolean,
|
publishing: boolean,
|
||||||
balance: number,
|
balance: number,
|
||||||
isStillEditing: boolean,
|
isStillEditing: boolean,
|
||||||
|
@ -53,35 +52,49 @@ type Props = {
|
||||||
prepareEdit: (claim: any, uri: string) => void,
|
prepareEdit: (claim: any, uri: string) => void,
|
||||||
resetThumbnailStatus: () => void,
|
resetThumbnailStatus: () => void,
|
||||||
amountNeededForTakeover: ?number,
|
amountNeededForTakeover: ?number,
|
||||||
|
// Add back type
|
||||||
|
updatePublishForm: any => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
class PublishForm extends React.PureComponent<Props> {
|
function PublishForm(props: Props) {
|
||||||
constructor(props: Props) {
|
const {
|
||||||
super(props);
|
thumbnail,
|
||||||
|
name,
|
||||||
|
channel,
|
||||||
|
editingURI,
|
||||||
|
resolveUri,
|
||||||
|
title,
|
||||||
|
bid,
|
||||||
|
uploadThumbnailStatus,
|
||||||
|
resetThumbnailStatus,
|
||||||
|
updatePublishForm,
|
||||||
|
filePath,
|
||||||
|
publishing,
|
||||||
|
clearPublish,
|
||||||
|
isStillEditing,
|
||||||
|
tags,
|
||||||
|
publish,
|
||||||
|
} = props;
|
||||||
|
const formDisabled = (!filePath && !editingURI) || publishing;
|
||||||
|
// If they are editing, they don't need a new file chosen
|
||||||
|
const formValidLessFile = name && title && bid && !(uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS);
|
||||||
|
const formValid = editingURI && !filePath ? isStillEditing && formValidLessFile : formValidLessFile;
|
||||||
|
|
||||||
(this: any).handleFileChange = this.handleFileChange.bind(this);
|
let submitLabel;
|
||||||
(this: any).checkIsFormValid = this.checkIsFormValid.bind(this);
|
if (isStillEditing) {
|
||||||
(this: any).renderFormErrors = this.renderFormErrors.bind(this);
|
submitLabel = !publishing ? __('Edit') : __('Editing...');
|
||||||
(this: any).handlePublish = this.handlePublish.bind(this);
|
} else {
|
||||||
(this: any).handleCancelPublish = this.handleCancelPublish.bind(this);
|
submitLabel = !publishing ? __('Publish') : __('Publishing...');
|
||||||
(this: any).handleNameChange = this.handleNameChange.bind(this);
|
|
||||||
(this: any).handleChannelChange = this.handleChannelChange.bind(this);
|
|
||||||
(this: any).editExistingClaim = this.editExistingClaim.bind(this);
|
|
||||||
(this: any).getNewUri = this.getNewUri.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
useEffect(() => {
|
||||||
const { thumbnail, name, channel, editingURI } = this.props;
|
|
||||||
if (!thumbnail) {
|
if (!thumbnail) {
|
||||||
this.props.resetThumbnailStatus();
|
resetThumbnailStatus();
|
||||||
}
|
}
|
||||||
if (editingURI) {
|
}, [thumbnail, resetThumbnailStatus]);
|
||||||
this.getNewUri(name, channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getNewUri(name: string, channel: string) {
|
// Every time the channel or name changes, resolve the uris to find winning bid amounts
|
||||||
const { resolveUri } = this.props;
|
useEffect(() => {
|
||||||
// If they are midway through a channel creation, treat it as anonymous until it completes
|
// If they are midway through a channel creation, treat it as anonymous until it completes
|
||||||
const channelName = channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : channel;
|
const channelName = channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : channel;
|
||||||
|
|
||||||
|
@ -89,523 +102,78 @@ class PublishForm extends React.PureComponent<Props> {
|
||||||
let uri;
|
let uri;
|
||||||
try {
|
try {
|
||||||
uri = buildURI({ contentName: name, channelName });
|
uri = buildURI({ contentName: name, channelName });
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
// something wrong with channel or name
|
|
||||||
|
if (channelName) {
|
||||||
|
// resolve without the channel name so we know the winning bid for it
|
||||||
|
const uriLessChannel = buildURI({ contentName: name });
|
||||||
|
resolveUri(uriLessChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri) {
|
if (uri) {
|
||||||
if (channelName) {
|
|
||||||
// resolve without the channel name so we know the winning bid for it
|
|
||||||
const uriLessChannel = buildURI({ contentName: name });
|
|
||||||
resolveUri(uriLessChannel);
|
|
||||||
}
|
|
||||||
resolveUri(uri);
|
resolveUri(uri);
|
||||||
return uri;
|
updatePublishForm({ uri });
|
||||||
}
|
}
|
||||||
|
}, [name, channel, resolveUri, updatePublishForm]);
|
||||||
|
|
||||||
return '';
|
return (
|
||||||
}
|
<Fragment>
|
||||||
|
<UnsupportedOnWeb />
|
||||||
|
|
||||||
handleFileChange(filePath: string, fileName: string) {
|
<PublishFile />
|
||||||
const { updatePublishForm, channel, name } = this.props;
|
<div className={classnames({ 'card--disabled': formDisabled })}>
|
||||||
const newFileParams: UpdatePublishFormData = { filePath };
|
<PublishText disabled={formDisabled} />
|
||||||
|
<div className="card card--section">
|
||||||
if (!name) {
|
{/* This should probably be PublishThumbnail */}
|
||||||
const parsedFileName = fileName.replace(regexInvalidURI, '');
|
<SelectThumbnail />
|
||||||
const uri = this.getNewUri(parsedFileName, channel);
|
|
||||||
newFileParams.name = parsedFileName;
|
|
||||||
newFileParams.uri = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePublishForm(newFileParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleNameChange(name: ?string) {
|
|
||||||
const { channel, updatePublishForm } = this.props;
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
updatePublishForm({ name: '', nameError: __('A name is required.') });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNameValid(name, false)) {
|
|
||||||
updatePublishForm({
|
|
||||||
name,
|
|
||||||
nameError: __('LBRY names must contain only letters, numbers and dashes.'),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uri = this.getNewUri(name, channel);
|
|
||||||
updatePublishForm({
|
|
||||||
name,
|
|
||||||
uri,
|
|
||||||
nameError: undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChannelChange(channelName: string) {
|
|
||||||
const { name, updatePublishForm } = this.props;
|
|
||||||
const form: UpdatePublishFormData = { channel: channelName };
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
form.uri = this.getNewUri(name, channelName);
|
|
||||||
}
|
|
||||||
updatePublishForm(form);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleBidChange(bid: number) {
|
|
||||||
const { balance, updatePublishForm, myClaimForUri } = this.props;
|
|
||||||
|
|
||||||
let previousBidAmount = 0;
|
|
||||||
if (myClaimForUri) {
|
|
||||||
previousBidAmount = Number(myClaimForUri.amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalAvailableBidAmount = previousBidAmount + balance;
|
|
||||||
|
|
||||||
let bidError;
|
|
||||||
if (bid === 0) {
|
|
||||||
bidError = __('Deposit cannot be 0');
|
|
||||||
} else if (totalAvailableBidAmount === bid) {
|
|
||||||
bidError = __('Please decrease your deposit to account for transaction fees');
|
|
||||||
} else if (totalAvailableBidAmount < bid) {
|
|
||||||
bidError = __('Deposit cannot be higher than your balance');
|
|
||||||
} else if (bid <= MINIMUM_PUBLISH_BID) {
|
|
||||||
bidError = __('Your deposit must be higher');
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePublishForm({ bid, bidError });
|
|
||||||
}
|
|
||||||
|
|
||||||
editExistingClaim(myClaimForUri: ?{}, uri: string) {
|
|
||||||
const { prepareEdit, scrollToTop } = this.props;
|
|
||||||
if (myClaimForUri) {
|
|
||||||
prepareEdit(myClaimForUri, uri);
|
|
||||||
scrollToTop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCancelPublish() {
|
|
||||||
const { clearPublish, scrollToTop } = this.props;
|
|
||||||
scrollToTop();
|
|
||||||
clearPublish();
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePublish() {
|
|
||||||
const { filePath, licenseType, licenseUrl, otherLicenseDescription, publish } = this.props;
|
|
||||||
|
|
||||||
let publishingLicense;
|
|
||||||
switch (licenseType) {
|
|
||||||
case COPYRIGHT:
|
|
||||||
case OTHER:
|
|
||||||
publishingLicense = otherLicenseDescription;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
publishingLicense = licenseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
const publishingLicenseUrl = licenseType === COPYRIGHT ? '' : licenseUrl;
|
|
||||||
|
|
||||||
const publishParams: PublishParams = {
|
|
||||||
filePath: filePath || undefined,
|
|
||||||
bid: this.props.bid || undefined,
|
|
||||||
title: this.props.title || '',
|
|
||||||
thumbnail: this.props.thumbnail,
|
|
||||||
description: this.props.description,
|
|
||||||
language: this.props.language,
|
|
||||||
nsfw: this.props.nsfw,
|
|
||||||
license: publishingLicense,
|
|
||||||
licenseUrl: publishingLicenseUrl,
|
|
||||||
otherLicenseDescription,
|
|
||||||
name: this.props.name || undefined,
|
|
||||||
contentIsFree: this.props.contentIsFree,
|
|
||||||
fee: this.props.fee,
|
|
||||||
uri: this.props.uri || undefined,
|
|
||||||
channel: this.props.channel,
|
|
||||||
isStillEditing: this.props.isStillEditing,
|
|
||||||
claim: this.props.myClaimForUri,
|
|
||||||
};
|
|
||||||
|
|
||||||
publish(publishParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkIsFormValid() {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
nameError,
|
|
||||||
title,
|
|
||||||
bid,
|
|
||||||
bidError,
|
|
||||||
editingURI,
|
|
||||||
isStillEditing,
|
|
||||||
filePath,
|
|
||||||
uploadThumbnailStatus,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
// If they are editing, they don't need a new file chosen
|
|
||||||
const formValidLessFile =
|
|
||||||
name && !nameError && title && bid && !bidError && !(uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS);
|
|
||||||
return editingURI && !filePath ? isStillEditing && formValidLessFile : formValidLessFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderFormErrors() {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
nameError,
|
|
||||||
title,
|
|
||||||
bid,
|
|
||||||
bidError,
|
|
||||||
editingURI,
|
|
||||||
filePath,
|
|
||||||
isStillEditing,
|
|
||||||
uploadThumbnailStatus,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const isFormValid = this.checkIsFormValid();
|
|
||||||
|
|
||||||
// These are extra help
|
|
||||||
// If there is an error it will be presented as an inline error as well
|
|
||||||
return (
|
|
||||||
!isFormValid && (
|
|
||||||
<div className="card__content error-text">
|
|
||||||
{!title && <div>{__('A title is required')}</div>}
|
|
||||||
{!name && <div>{__('A URL is required')}</div>}
|
|
||||||
{name && nameError && <div>{__('The URL you created is not valid')}</div>}
|
|
||||||
{!bid && <div>{__('A deposit amount is required')}</div>}
|
|
||||||
{!!bid && bidError && <div>{bidError}</div>}
|
|
||||||
{uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS && (
|
|
||||||
<div>{__('Please wait for thumbnail to finish uploading')}</div>
|
|
||||||
)}
|
|
||||||
{!!editingURI && !isStillEditing && !filePath && (
|
|
||||||
<div>{__('You need to reselect a file after changing the LBRY URL')}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
<div className="card">
|
||||||
);
|
<TagSelect
|
||||||
}
|
title={false}
|
||||||
|
help={__('The better your tags are, the easier it will be for people to discover your content.')}
|
||||||
render() {
|
empty={__('No tags added')}
|
||||||
const {
|
onSelect={tag => updatePublishForm({ tags: [...tags, tag] })}
|
||||||
filePath,
|
onRemove={clickedTag => {
|
||||||
editingURI,
|
const newTags = tags.slice().filter(tag => tag.name !== clickedTag.name);
|
||||||
title,
|
updatePublishForm({ tags: newTags });
|
||||||
thumbnail,
|
}}
|
||||||
uploadThumbnailStatus,
|
tagsChosen={tags}
|
||||||
description,
|
/>
|
||||||
language,
|
</div>
|
||||||
nsfw,
|
<section className="card card--section">
|
||||||
contentIsFree,
|
<div className="card__content">
|
||||||
fee,
|
<ChannelSection channel={channel} onChannelChange={channel => updatePublishForm({ channel })} />
|
||||||
channel,
|
<p className="help">
|
||||||
name,
|
{__('This is a username or handle that your content can be found under.')}{' '}
|
||||||
updatePublishForm,
|
{__('Ex. @Marvel, @TheBeatles, @BooksByJoe')}
|
||||||
bid,
|
</p>
|
||||||
nameError,
|
|
||||||
isResolvingUri,
|
|
||||||
winningBidForClaimUri,
|
|
||||||
myClaimForUri,
|
|
||||||
licenseType,
|
|
||||||
otherLicenseDescription,
|
|
||||||
licenseUrl,
|
|
||||||
uri,
|
|
||||||
bidError,
|
|
||||||
publishing,
|
|
||||||
clearPublish,
|
|
||||||
thumbnailPath,
|
|
||||||
resetThumbnailStatus,
|
|
||||||
isStillEditing,
|
|
||||||
amountNeededForTakeover,
|
|
||||||
balance,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const formDisabled = (!filePath && !editingURI) || publishing;
|
|
||||||
const formValid = this.checkIsFormValid();
|
|
||||||
|
|
||||||
let submitLabel;
|
|
||||||
if (isStillEditing) {
|
|
||||||
submitLabel = !publishing ? __('Edit') : __('Editing...');
|
|
||||||
} else {
|
|
||||||
submitLabel = !publishing ? __('Publish') : __('Publishing...');
|
|
||||||
}
|
|
||||||
|
|
||||||
const shortUri = buildURI({ contentName: name });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
{IS_WEB && <UnsupportedOnWeb />}
|
|
||||||
<Form onSubmit={this.handlePublish}>
|
|
||||||
<section
|
|
||||||
className={classnames('card card--section', {
|
|
||||||
'card--disabled': IS_WEB || publishing || balance === 0,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<header className="card__header">
|
|
||||||
<h2 className="card__title card__title--flex-between">
|
|
||||||
{__('Content')}
|
|
||||||
{(filePath || !!editingURI) && (
|
|
||||||
<Button button="inverse" icon={ICONS.REMOVE} label={__('Clear')} onClick={clearPublish} />
|
|
||||||
)}
|
|
||||||
</h2>
|
|
||||||
<p className="card__subtitle">
|
|
||||||
{isStillEditing ? __('You are currently editing a claim.') : __('What are you publishing?')}{' '}
|
|
||||||
{__('Read our')} <Button button="link" label={__('FAQ')} href="https://lbry.com/faq/how-to-publish" />{' '}
|
|
||||||
{__('to learn more.')}
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="card__content">
|
|
||||||
<FileSelector currentPath={filePath} onFileChosen={this.handleFileChange} />
|
|
||||||
{!!isStillEditing && name && (
|
|
||||||
<p className="help">
|
|
||||||
{__("If you don't choose a file, the file from your existing claim")}
|
|
||||||
{` "${name}" `}
|
|
||||||
{__('will be used.')}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<div className={classnames({ 'card--disabled': formDisabled })}>
|
|
||||||
<section className="card card--section">
|
|
||||||
<div className="card__content">
|
|
||||||
<FormField
|
|
||||||
type="text"
|
|
||||||
name="content_title"
|
|
||||||
label={__('Title')}
|
|
||||||
placeholder={__('Titular Title')}
|
|
||||||
disabled={formDisabled}
|
|
||||||
value={title}
|
|
||||||
onChange={e => updatePublishForm({ title: e.target.value })}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
type="markdown"
|
|
||||||
name="content_description"
|
|
||||||
label={__('Description')}
|
|
||||||
placeholder={__('Description of your content')}
|
|
||||||
value={description}
|
|
||||||
disabled={formDisabled}
|
|
||||||
onChange={text => updatePublishForm({ description: text })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card card--section">
|
|
||||||
<header className="card__header">
|
|
||||||
<h2 className="card__title">{__('Thumbnail')}</h2>
|
|
||||||
<p className="card__subtitle">
|
|
||||||
{(uploadThumbnailStatus === undefined && __('You should reselect your file to choose a thumbnail')) ||
|
|
||||||
(uploadThumbnailStatus === THUMBNAIL_STATUSES.API_DOWN ? (
|
|
||||||
__('Enter a URL for your thumbnail.')
|
|
||||||
) : (
|
|
||||||
<React.Fragment>
|
|
||||||
{__('Upload your thumbnail (.png/.jpg/.jpeg/.gif) to')}{' '}
|
|
||||||
<Button button="link" label={__('spee.ch')} href="https://spee.ch/about" />.{' '}
|
|
||||||
{__('Recommended size: 800x450 (16:9)')}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<SelectThumbnail
|
|
||||||
filePath={filePath}
|
|
||||||
thumbnailPath={thumbnailPath}
|
|
||||||
thumbnail={thumbnail}
|
|
||||||
uploadThumbnailStatus={uploadThumbnailStatus}
|
|
||||||
updatePublishForm={updatePublishForm}
|
|
||||||
formDisabled={formDisabled}
|
|
||||||
resetThumbnailStatus={resetThumbnailStatus}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card card--section">
|
|
||||||
<header className="card__header">
|
|
||||||
<h2 className="card__title">{__('Price')}</h2>
|
|
||||||
<p className="card__subtitle">{__('How much will this content cost?')}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="card__content">
|
|
||||||
<FormField
|
|
||||||
type="radio"
|
|
||||||
name="content_free"
|
|
||||||
label={__('Free')}
|
|
||||||
checked={contentIsFree}
|
|
||||||
disabled={formDisabled}
|
|
||||||
onChange={() => updatePublishForm({ contentIsFree: true })}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
type="radio"
|
|
||||||
name="content_cost"
|
|
||||||
label={__('Choose price')}
|
|
||||||
checked={!contentIsFree}
|
|
||||||
disabled={formDisabled}
|
|
||||||
onChange={() => updatePublishForm({ contentIsFree: false })}
|
|
||||||
/>
|
|
||||||
{!contentIsFree && (
|
|
||||||
<FormFieldPrice
|
|
||||||
name="content_cost_amount"
|
|
||||||
min="0"
|
|
||||||
price={fee}
|
|
||||||
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>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card card--section">
|
|
||||||
<header className="card__header">
|
|
||||||
<h2 className="card__title">{__('Anonymous or under a channel?')}</h2>
|
|
||||||
<p className="card__subtitle">
|
|
||||||
{__('This is a username or handle that your content can be found under.')}{' '}
|
|
||||||
{__('Ex. @Marvel, @TheBeatles, @BooksByJoe')}
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="card__content">
|
|
||||||
<ChannelSection channel={channel} onChannelChange={this.handleChannelChange} />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card card--section">
|
|
||||||
<header className="card__header">
|
|
||||||
<h2 className="card__title">{__('Where can people find this content?')}</h2>
|
|
||||||
<p className="card__subtitle">
|
|
||||||
{__('The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).')}{' '}
|
|
||||||
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/naming" />
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="card__content">
|
|
||||||
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
|
||||||
<fieldset-section>
|
|
||||||
<label>{__('Name')}</label>
|
|
||||||
<span className="form-field__prefix">{`lbry://${
|
|
||||||
!channel || channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : `${channel}/`
|
|
||||||
}`}</span>
|
|
||||||
</fieldset-section>
|
|
||||||
<FormField
|
|
||||||
type="text"
|
|
||||||
name="content_name"
|
|
||||||
value={name}
|
|
||||||
onChange={event => this.handleNameChange(event.target.value)}
|
|
||||||
error={nameError}
|
|
||||||
/>
|
|
||||||
</fieldset-group>
|
|
||||||
<div className="form-field__help">
|
|
||||||
<NameHelpText
|
|
||||||
isStillEditing={isStillEditing}
|
|
||||||
uri={uri}
|
|
||||||
myClaimForUri={myClaimForUri}
|
|
||||||
onEditMyClaim={this.editExistingClaim}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classnames('card__content', { 'card--disabled': !name })}>
|
|
||||||
<FormField
|
|
||||||
className="form-field--price-amount"
|
|
||||||
type="number"
|
|
||||||
name="content_bid"
|
|
||||||
step="any"
|
|
||||||
label={__('Deposit (LBC)')}
|
|
||||||
postfix="LBC"
|
|
||||||
value={bid}
|
|
||||||
error={bidError}
|
|
||||||
min="0"
|
|
||||||
disabled={!name}
|
|
||||||
onChange={event => this.handleBidChange(parseFloat(event.target.value))}
|
|
||||||
placeholder={winningBidForClaimUri ? winningBidForClaimUri + 0.1 : 0.1}
|
|
||||||
helper={
|
|
||||||
<BidHelpText
|
|
||||||
uri={shortUri}
|
|
||||||
isResolvingUri={isResolvingUri}
|
|
||||||
amountNeededForTakeover={amountNeededForTakeover}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card card--section">
|
|
||||||
<div className="card__content">
|
|
||||||
<FormField
|
|
||||||
type="checkbox"
|
|
||||||
name="content_is_mature"
|
|
||||||
label={__('Mature audiences only')}
|
|
||||||
checked={nsfw}
|
|
||||||
onChange={() => updatePublishForm({ nsfw: !nsfw })}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
label={__('Language')}
|
|
||||||
type="select"
|
|
||||||
name="content_language"
|
|
||||||
value={language}
|
|
||||||
onChange={event => updatePublishForm({ language: event.target.value })}
|
|
||||||
>
|
|
||||||
<option value="en">{__('English')}</option>
|
|
||||||
<option value="zh">{__('Chinese')}</option>
|
|
||||||
<option value="fr">{__('French')}</option>
|
|
||||||
<option value="de">{__('German')}</option>
|
|
||||||
<option value="jp">{__('Japanese')}</option>
|
|
||||||
<option value="ru">{__('Russian')}</option>
|
|
||||||
<option value="es">{__('Spanish')}</option>
|
|
||||||
<option value="id">{__('Indonesian')}</option>
|
|
||||||
<option value="it">{__('Italian')}</option>
|
|
||||||
<option value="nl">{__('Dutch')}</option>
|
|
||||||
<option value="tr">{__('Turkish')}</option>
|
|
||||||
<option value="pl">{__('Polish')}</option>
|
|
||||||
<option value="ms">{__('Malay')}</option>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<LicenseType
|
|
||||||
licenseType={licenseType}
|
|
||||||
otherLicenseDescription={otherLicenseDescription}
|
|
||||||
licenseUrl={licenseUrl}
|
|
||||||
handleLicenseChange={(newLicenseType, newLicenseUrl) =>
|
|
||||||
updatePublishForm({
|
|
||||||
licenseType: newLicenseType,
|
|
||||||
licenseUrl: newLicenseUrl,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
handleLicenseDescriptionChange={event =>
|
|
||||||
updatePublishForm({
|
|
||||||
otherLicenseDescription: event.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
handleLicenseUrlChange={event => updatePublishForm({ licenseUrl: event.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card card--section">
|
|
||||||
<div className="card__actions">
|
|
||||||
<Submit
|
|
||||||
label={submitLabel}
|
|
||||||
disabled={formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS}
|
|
||||||
/>
|
|
||||||
<Button button="link" onClick={this.handleCancelPublish} label={__('Cancel')} />
|
|
||||||
</div>
|
|
||||||
<p className="help">
|
|
||||||
{__('By continuing, you accept the')}{' '}
|
|
||||||
<Button button="link" href="https://www.lbry.com/termsofservice" label={__('LBRY Terms of Service')} />.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{!formDisabled && !formValid && this.renderFormErrors()}
|
<PublishName disabled={formDisabled} />
|
||||||
</Form>
|
<PublishPrice disabled={formDisabled} />
|
||||||
</React.Fragment>
|
<PublishAdditionalOptions disabled={formDisabled} />
|
||||||
);
|
|
||||||
}
|
<section className="card card--section">
|
||||||
|
{!formDisabled && !formValid && <PublishFormErrors />}
|
||||||
|
|
||||||
|
<div className="card__actions">
|
||||||
|
<Button
|
||||||
|
button="primary"
|
||||||
|
onClick={publish}
|
||||||
|
label={submitLabel}
|
||||||
|
disabled={formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS}
|
||||||
|
/>
|
||||||
|
<Button button="link" onClick={clearPublish} label={__('Cancel')} />
|
||||||
|
</div>
|
||||||
|
<p className="help">
|
||||||
|
{__('By continuing, you accept the')}{' '}
|
||||||
|
<Button button="link" href="https://www.lbry.com/termsofservice" label={__('LBRY Terms of Service')} />.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PublishForm;
|
export default PublishForm;
|
||||||
|
|
17
src/ui/component/publishFormErrors/index.js
Normal file
17
src/ui/component/publishFormErrors/index.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectPublishFormValue, selectIsStillEditing } from 'redux/selectors/publish';
|
||||||
|
import PublishPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
name: makeSelectPublishFormValue('name')(state),
|
||||||
|
title: makeSelectPublishFormValue('title')(state),
|
||||||
|
bid: makeSelectPublishFormValue('bid')(state),
|
||||||
|
editingUri: makeSelectPublishFormValue('editingUri')(state),
|
||||||
|
uploadThumbnailStatus: makeSelectPublishFormValue('uploadThumbnailStatus')(state),
|
||||||
|
isStillEditing: selectIsStillEditing(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
null
|
||||||
|
)(PublishPage);
|
35
src/ui/component/publishFormErrors/view.jsx
Normal file
35
src/ui/component/publishFormErrors/view.jsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { THUMBNAIL_STATUSES } from 'lbry-redux';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: ?string,
|
||||||
|
name: ?string,
|
||||||
|
bid: ?string,
|
||||||
|
editingURI: ?string,
|
||||||
|
filePath: ?string,
|
||||||
|
isStillEditing: boolean,
|
||||||
|
uploadThumbnailStatus: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function PublishFormErrors(props: Props) {
|
||||||
|
const { name, title, bid, editingURI, filePath, isStillEditing, uploadThumbnailStatus } = props;
|
||||||
|
|
||||||
|
// These are extra help
|
||||||
|
// If there is an error it will be presented as an inline error as well
|
||||||
|
return (
|
||||||
|
<div className="card__content error-text">
|
||||||
|
{!title && <div>{__('A title is required')}</div>}
|
||||||
|
{!name && <div>{__('A URL is required')}</div>}
|
||||||
|
{!bid && <div>{__('A deposit amount is required')}</div>}
|
||||||
|
{uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS && (
|
||||||
|
<div>{__('Please wait for thumbnail to finish uploading')}</div>
|
||||||
|
)}
|
||||||
|
{!!editingURI && !isStillEditing && !filePath && (
|
||||||
|
<div>{__('You need to reselect a file after changing the LBRY URL')}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublishFormErrors;
|
32
src/ui/component/publishName/bid-help-text.jsx
Normal file
32
src/ui/component/publishName/bid-help-text.jsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// @flow
|
||||||
|
type Props = {
|
||||||
|
uri: ?string,
|
||||||
|
isResolvingUri: boolean,
|
||||||
|
amountNeededForTakeover: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
function BidHelpText(props: Props) {
|
||||||
|
const { uri, isResolvingUri, amountNeededForTakeover } = props;
|
||||||
|
|
||||||
|
let bidHelpText;
|
||||||
|
|
||||||
|
if (uri) {
|
||||||
|
if (isResolvingUri) {
|
||||||
|
bidHelpText = __('Checking the winning claim amount...');
|
||||||
|
} else if (!amountNeededForTakeover) {
|
||||||
|
bidHelpText = __('Any amount will give you the winning bid.');
|
||||||
|
} else {
|
||||||
|
bidHelpText = `${__('If you bid more than')} ${amountNeededForTakeover} LBC, ${__(
|
||||||
|
'when someone navigates to'
|
||||||
|
)} ${uri} ${__('it will load your published content')}. ${__(
|
||||||
|
'However, you can get a longer version of this URL for any bid'
|
||||||
|
)}.`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bidHelpText = __('This LBC remains yours and the deposit can be undone at any time.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return bidHelpText;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BidHelpText;
|
32
src/ui/component/publishName/index.js
Normal file
32
src/ui/component/publishName/index.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
makeSelectPublishFormValue,
|
||||||
|
selectIsStillEditing,
|
||||||
|
selectMyClaimForUri,
|
||||||
|
selectIsResolvingPublishUris,
|
||||||
|
selectTakeOverAmount,
|
||||||
|
} from 'redux/selectors/publish';
|
||||||
|
import { doUpdatePublishForm, doPrepareEdit } from 'redux/actions/publish';
|
||||||
|
import { selectBalance } from 'lbry-redux';
|
||||||
|
import PublishPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
name: makeSelectPublishFormValue('name')(state),
|
||||||
|
channel: makeSelectPublishFormValue('channel')(state),
|
||||||
|
uri: makeSelectPublishFormValue('uri')(state),
|
||||||
|
isStillEditing: selectIsStillEditing(state),
|
||||||
|
isResolvingUri: selectIsResolvingPublishUris(state),
|
||||||
|
amountNeededForTakeover: selectTakeOverAmount(state),
|
||||||
|
balance: selectBalance(state),
|
||||||
|
myClaimForUri: selectMyClaimForUri(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||||
|
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(PublishPage);
|
46
src/ui/component/publishName/name-help-text.jsx
Normal file
46
src/ui/component/publishName/name-help-text.jsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import { buildURI } from 'lbry-redux';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
uri: ?string,
|
||||||
|
myClaimForUri: ?StreamClaim,
|
||||||
|
isStillEditing: boolean,
|
||||||
|
onEditMyClaim: (any, string) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function NameHelpText(props: Props) {
|
||||||
|
const { uri, myClaimForUri, onEditMyClaim, isStillEditing } = props;
|
||||||
|
|
||||||
|
let nameHelpText;
|
||||||
|
|
||||||
|
if (isStillEditing) {
|
||||||
|
nameHelpText = __('You are currently editing this claim. If you change the URL, you will need to reselect a file.');
|
||||||
|
} else if (uri && myClaimForUri) {
|
||||||
|
const editUri = buildURI({
|
||||||
|
contentName: myClaimForUri.name,
|
||||||
|
claimId: myClaimForUri.claim_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
nameHelpText = (
|
||||||
|
<React.Fragment>
|
||||||
|
{__('You already have a claim at')}
|
||||||
|
{` ${uri} `}
|
||||||
|
<Button button="link" label="Edit it" onClick={() => onEditMyClaim(myClaimForUri, editUri)} />
|
||||||
|
<br />
|
||||||
|
{__('Publishing will update your existing claim.')}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{nameHelpText || (
|
||||||
|
<span>{__('Create a URL for this content. Simpler names are easier to find and remember.')}</span>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NameHelpText;
|
124
src/ui/component/publishName/view.jsx
Normal file
124
src/ui/component/publishName/view.jsx
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
// @flow
|
||||||
|
import { CHANNEL_NEW, CHANNEL_ANONYMOUS, MINIMUM_PUBLISH_BID } from 'constants/claim';
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { isNameValid } from 'lbry-redux';
|
||||||
|
import { FormField } from 'component/common/form';
|
||||||
|
import NameHelpText from './name-help-text';
|
||||||
|
import BidHelpText from './bid-help-text';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
name: string,
|
||||||
|
channel: string,
|
||||||
|
uri: string,
|
||||||
|
bid: string,
|
||||||
|
balance: number,
|
||||||
|
isStillEditing: boolean,
|
||||||
|
myClaimForUri: ?StreamClaim,
|
||||||
|
isResolvingUri: boolean,
|
||||||
|
amountNeededForTakeover: number,
|
||||||
|
prepareEdit: ({}, string) => void,
|
||||||
|
updatePublishForm: ({}) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function PublishText(props: Props) {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
channel,
|
||||||
|
uri,
|
||||||
|
isStillEditing,
|
||||||
|
myClaimForUri,
|
||||||
|
bid: bidString,
|
||||||
|
isResolvingUri,
|
||||||
|
amountNeededForTakeover,
|
||||||
|
prepareEdit,
|
||||||
|
updatePublishForm,
|
||||||
|
balance,
|
||||||
|
} = props;
|
||||||
|
const [nameError, setNameError] = useState(undefined);
|
||||||
|
const [bidError, setBidError] = useState(undefined);
|
||||||
|
const previousBidAmount = myClaimForUri && Number(myClaimForUri.amount);
|
||||||
|
const bid = Number(bidString);
|
||||||
|
|
||||||
|
function editExistingClaim() {
|
||||||
|
if (myClaimForUri) {
|
||||||
|
prepareEdit(myClaimForUri, uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let nameError;
|
||||||
|
if (!name) {
|
||||||
|
nameError = __('A name is required');
|
||||||
|
} else if (!isNameValid(name, false)) {
|
||||||
|
nameError = __('LBRY names cannot contain that symbol ($, #, @)');
|
||||||
|
}
|
||||||
|
|
||||||
|
setNameError(nameError);
|
||||||
|
}, [name]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const totalAvailableBidAmount = previousBidAmount + balance;
|
||||||
|
|
||||||
|
let bidError;
|
||||||
|
if (bid === 0) {
|
||||||
|
bidError = __('Deposit cannot be 0');
|
||||||
|
} else if (totalAvailableBidAmount === bid) {
|
||||||
|
bidError = __('Please decrease your deposit to account for transaction fees');
|
||||||
|
} else if (totalAvailableBidAmount < bid) {
|
||||||
|
bidError = __('Deposit cannot be higher than your balance');
|
||||||
|
} else if (bid <= MINIMUM_PUBLISH_BID) {
|
||||||
|
bidError = __('Your deposit must be higher');
|
||||||
|
}
|
||||||
|
|
||||||
|
setBidError(bidError);
|
||||||
|
}, [bid, previousBidAmount, balance]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="card card--section">
|
||||||
|
<div className="card__content">
|
||||||
|
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
||||||
|
<fieldset-section>
|
||||||
|
<label>{__('Name')}</label>
|
||||||
|
<span className="form-field__prefix">{`lbry://${
|
||||||
|
!channel || channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : `${channel}/`
|
||||||
|
}`}</span>
|
||||||
|
</fieldset-section>
|
||||||
|
<FormField
|
||||||
|
type="text"
|
||||||
|
name="content_name"
|
||||||
|
value={name}
|
||||||
|
error={nameError}
|
||||||
|
onChange={event => updatePublishForm({ name: event.target.value })}
|
||||||
|
/>
|
||||||
|
</fieldset-group>
|
||||||
|
<div className="form-field__help">
|
||||||
|
<NameHelpText
|
||||||
|
uri={uri}
|
||||||
|
isStillEditing={isStillEditing}
|
||||||
|
myClaimForUri={myClaimForUri}
|
||||||
|
onEditMyClaim={editExistingClaim}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FormField
|
||||||
|
type="number"
|
||||||
|
name="content_bid"
|
||||||
|
min="0"
|
||||||
|
step="any"
|
||||||
|
placeholder="0.123"
|
||||||
|
className="form-field--price-amount"
|
||||||
|
label={__('Deposit (LBC)')}
|
||||||
|
postfix="LBC"
|
||||||
|
value={bid}
|
||||||
|
error={bidError}
|
||||||
|
disabled={!name}
|
||||||
|
onChange={event => updatePublishForm({ bid: parseFloat(event.target.value) })}
|
||||||
|
helper={
|
||||||
|
<BidHelpText uri={uri} amountNeededForTakeover={amountNeededForTakeover} isResolvingUri={isResolvingUri} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublishText;
|
46
src/ui/component/publishPrice/index.js
Normal file
46
src/ui/component/publishPrice/index.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doResolveUri, selectBalance } from 'lbry-redux';
|
||||||
|
import {
|
||||||
|
selectPublishFormValues,
|
||||||
|
selectIsStillEditing,
|
||||||
|
selectMyClaimForUri,
|
||||||
|
selectIsResolvingPublishUris,
|
||||||
|
selectTakeOverAmount,
|
||||||
|
} from 'redux/selectors/publish';
|
||||||
|
import {
|
||||||
|
doResetThumbnailStatus,
|
||||||
|
doClearPublish,
|
||||||
|
doUpdatePublishForm,
|
||||||
|
doPublish,
|
||||||
|
doPrepareEdit,
|
||||||
|
} from 'redux/actions/publish';
|
||||||
|
import { selectUnclaimedRewardValue } from 'lbryinc';
|
||||||
|
import PublishPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
...selectPublishFormValues(state),
|
||||||
|
// The winning claim for a short lbry uri
|
||||||
|
amountNeededForTakeover: selectTakeOverAmount(state),
|
||||||
|
// My previously published claims under this short lbry uri
|
||||||
|
myClaimForUri: selectMyClaimForUri(state),
|
||||||
|
// If I clicked the "edit" button, have I changed the uri?
|
||||||
|
// Need this to make it easier to find the source on previously published content
|
||||||
|
isStillEditing: selectIsStillEditing(state),
|
||||||
|
balance: selectBalance(state),
|
||||||
|
isResolvingUri: selectIsResolvingPublishUris(state),
|
||||||
|
totalRewardValue: selectUnclaimedRewardValue(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||||
|
clearPublish: () => dispatch(doClearPublish()),
|
||||||
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
publish: params => dispatch(doPublish(params)),
|
||||||
|
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
||||||
|
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(PublishPage);
|
55
src/ui/component/publishPrice/view.jsx
Normal file
55
src/ui/component/publishPrice/view.jsx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { FormField, FormFieldPrice } from 'component/common/form';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
contentIsFree: boolean,
|
||||||
|
fee: Fee,
|
||||||
|
disabled: boolean,
|
||||||
|
updatePublishForm: ({}) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function PublishText(props: Props) {
|
||||||
|
const { contentIsFree, fee, updatePublishForm, disabled } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="card card--section">
|
||||||
|
<div className="card__content">
|
||||||
|
<FormField
|
||||||
|
type="radio"
|
||||||
|
name="content_free"
|
||||||
|
label={__('Free')}
|
||||||
|
checked={contentIsFree}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={() => updatePublishForm({ contentIsFree: true })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
type="radio"
|
||||||
|
name="content_cost"
|
||||||
|
label={__('Add a price to this file')}
|
||||||
|
checked={!contentIsFree}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={() => updatePublishForm({ contentIsFree: false })}
|
||||||
|
/>
|
||||||
|
{!contentIsFree && (
|
||||||
|
<FormFieldPrice
|
||||||
|
name="content_cost_amount"
|
||||||
|
min="0"
|
||||||
|
price={fee}
|
||||||
|
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>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublishText;
|
18
src/ui/component/publishText/index.js
Normal file
18
src/ui/component/publishText/index.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectPublishFormValue } from 'redux/selectors/publish';
|
||||||
|
import { doUpdatePublishForm } from 'redux/actions/publish';
|
||||||
|
import PublishPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
title: makeSelectPublishFormValue('title')(state),
|
||||||
|
description: makeSelectPublishFormValue('description')(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(PublishPage);
|
51
src/ui/component/publishText/view.jsx
Normal file
51
src/ui/component/publishText/view.jsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { FormField } from 'component/common/form';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import usePersistedState from 'util/use-persisted-state';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: ?string,
|
||||||
|
description: ?string,
|
||||||
|
disabled: boolean,
|
||||||
|
updatePublishForm: ({}) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function PublishText(props: Props) {
|
||||||
|
const { title, description, updatePublishForm, disabled } = props;
|
||||||
|
const [advancedEditor, setAdvancedEditor] = usePersistedState('publish-form-description-mode', false);
|
||||||
|
function toggleMarkdown() {
|
||||||
|
setAdvancedEditor(!advancedEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="card card--section">
|
||||||
|
<div className="card__content">
|
||||||
|
<FormField
|
||||||
|
type="text"
|
||||||
|
name="content_title"
|
||||||
|
label={__('Title')}
|
||||||
|
placeholder={__('Titular Title')}
|
||||||
|
disabled={disabled}
|
||||||
|
value={title}
|
||||||
|
onChange={e => updatePublishForm({ title: e.target.value })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
type={advancedEditor ? 'markdown' : 'textarea'}
|
||||||
|
name="content_description"
|
||||||
|
label={__('Description')}
|
||||||
|
placeholder={__('My description for this and that')}
|
||||||
|
value={description}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={value => updatePublishForm({ description: advancedEditor ? value : value.target.text })}
|
||||||
|
/>
|
||||||
|
<div className="card__actions">
|
||||||
|
<Button button="link" onClick={toggleMarkdown} label={advancedEditor ? 'Simple Editor' : 'Advanced Editor'} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublishText;
|
|
@ -20,7 +20,7 @@ import UserHistoryPage from 'page/userHistory';
|
||||||
import WalletPage from 'page/wallet';
|
import WalletPage from 'page/wallet';
|
||||||
import NavigationHistory from 'page/navigationHistory';
|
import NavigationHistory from 'page/navigationHistory';
|
||||||
import TagsPage from 'page/tags';
|
import TagsPage from 'page/tags';
|
||||||
import TagsEditPage from 'page/tagsEdit';
|
import FollowingPage from 'page/following';
|
||||||
|
|
||||||
const Scroll = withRouter(function ScrollWrapper(props) {
|
const Scroll = withRouter(function ScrollWrapper(props) {
|
||||||
const { pathname } = props.location;
|
const { pathname } = props.location;
|
||||||
|
@ -50,17 +50,17 @@ export default function AppRouter() {
|
||||||
<Route path={`/$/${PAGES.REWARDS}`} exact component={RewardsPage} />
|
<Route path={`/$/${PAGES.REWARDS}`} exact component={RewardsPage} />
|
||||||
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
||||||
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
|
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
|
||||||
<Route path={`/$/${PAGES.SUBSCRIPTIONS}`} exact component={SubscriptionsPage} />
|
|
||||||
<Route path={`/$/${PAGES.TRANSACTIONS}`} exact component={TransactionHistoryPage} />
|
<Route path={`/$/${PAGES.TRANSACTIONS}`} exact component={TransactionHistoryPage} />
|
||||||
<Route path={`/$/${PAGES.LIBRARY}`} exact component={UserHistoryPage} />
|
<Route path={`/$/${PAGES.LIBRARY}`} exact component={UserHistoryPage} />
|
||||||
<Route path={`/$/${PAGES.ACCOUNT}`} exact component={AccountPage} />
|
<Route path={`/$/${PAGES.ACCOUNT}`} exact component={AccountPage} />
|
||||||
<Route path={`/$/${PAGES.LIBRARY}/all`} exact component={NavigationHistory} />
|
<Route path={`/$/${PAGES.LIBRARY}/all`} exact component={NavigationHistory} />
|
||||||
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
|
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
|
||||||
<Route path={`/$/${PAGES.TAGS}/edit`} exact component={TagsEditPage} />
|
<Route path={`/$/${PAGES.FOLLOWING}`} exact component={SubscriptionsPage} />
|
||||||
|
<Route path={`/$/${PAGES.FOLLOWING}/edit`} exact component={FollowingPage} />
|
||||||
<Route path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
<Route path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
||||||
{/* Below need to go at the end to make sure we don't match any of our pages first */}
|
{/* Below need to go at the end to make sure we don't match any of our pages first */}
|
||||||
<Route path="/:claimName" exact component={ShowPage} />
|
<Route path="/:claimName" exact component={ShowPage} />
|
||||||
<Route path="/:claimName/:claimId" exact component={ShowPage} />
|
<Route path="/:claimName/:contentName" exact component={ShowPage} />
|
||||||
|
|
||||||
{/* Route not found. Mostly for people typing crazy urls into the url */}
|
{/* Route not found. Mostly for people typing crazy urls into the url */}
|
||||||
<Route render={() => <Redirect to="/" />} />
|
<Route render={() => <Redirect to="/" />} />
|
||||||
|
|
|
@ -1,12 +1,47 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { doResolveUri } from 'lbry-redux';
|
||||||
|
import {
|
||||||
|
selectPublishFormValues,
|
||||||
|
selectIsStillEditing,
|
||||||
|
selectMyClaimForUri,
|
||||||
|
selectIsResolvingPublishUris,
|
||||||
|
selectTakeOverAmount,
|
||||||
|
} from 'redux/selectors/publish';
|
||||||
|
import {
|
||||||
|
doResetThumbnailStatus,
|
||||||
|
doClearPublish,
|
||||||
|
doUpdatePublishForm,
|
||||||
|
doPublish,
|
||||||
|
doPrepareEdit,
|
||||||
|
} from 'redux/actions/publish';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import SelectThumbnail from './view';
|
import { selectUnclaimedRewardValue } from 'lbryinc';
|
||||||
|
import PublishPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
...selectPublishFormValues(state),
|
||||||
|
// The winning claim for a short lbry uri
|
||||||
|
amountNeededForTakeover: selectTakeOverAmount(state),
|
||||||
|
// My previously published claims under this short lbry uri
|
||||||
|
myClaimForUri: selectMyClaimForUri(state),
|
||||||
|
// If I clicked the "edit" button, have I changed the uri?
|
||||||
|
// Need this to make it easier to find the source on previously published content
|
||||||
|
isStillEditing: selectIsStillEditing(state),
|
||||||
|
isResolvingUri: selectIsResolvingPublishUris(state),
|
||||||
|
totalRewardValue: selectUnclaimedRewardValue(state),
|
||||||
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||||
|
clearPublish: () => dispatch(doClearPublish()),
|
||||||
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
publish: params => dispatch(doPublish(params)),
|
||||||
|
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
||||||
|
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
|
||||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
null,
|
select,
|
||||||
perform
|
perform
|
||||||
)(SelectThumbnail);
|
)(PublishPage);
|
||||||
|
|
|
@ -122,7 +122,8 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
|
||||||
{status === THUMBNAIL_STATUSES.READY && (
|
{status === THUMBNAIL_STATUSES.READY && (
|
||||||
<FileSelector
|
<FileSelector
|
||||||
currentPath={thumbnailPath}
|
currentPath={thumbnailPath}
|
||||||
fileLabel={__('Choose Thumbnail')}
|
label={__('Thumbnail')}
|
||||||
|
placeholder={__('Choose a thumbnail')}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
onFileChosen={path => openModal(MODALS.CONFIRM_THUMBNAIL_UPLOAD, { path })}
|
onFileChosen={path => openModal(MODALS.CONFIRM_THUMBNAIL_UPLOAD, { path })}
|
||||||
/>
|
/>
|
||||||
|
@ -160,6 +161,18 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{status === THUMBNAIL_STATUSES.IN_PROGRESS && <p>{__('Uploading thumbnail')}...</p>}
|
{status === THUMBNAIL_STATUSES.IN_PROGRESS && <p>{__('Uploading thumbnail')}...</p>}
|
||||||
|
<p className="help">
|
||||||
|
{(status === undefined && __('You should reselect your file to choose a thumbnail')) ||
|
||||||
|
(status === THUMBNAIL_STATUSES.API_DOWN ? (
|
||||||
|
__('Enter a URL for your thumbnail.')
|
||||||
|
) : (
|
||||||
|
<React.Fragment>
|
||||||
|
{__('Upload your thumbnail (.png/.jpg/.jpeg/.gif) to')}{' '}
|
||||||
|
<Button button="link" label={__('spee.ch')} href="https://spee.ch/about" />.{' '}
|
||||||
|
{__('Recommended size: 800x450 (16:9)')}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,23 +34,23 @@ function SideBar(props: Props) {
|
||||||
...buildLink(null, __('Home'), ICONS.HOME),
|
...buildLink(null, __('Home'), ICONS.HOME),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...buildLink(PAGES.SUBSCRIPTIONS, __('Subscriptions'), ICONS.SUBSCRIPTION),
|
...buildLink(PAGES.FOLLOWING, __('Following'), ICONS.SUBSCRIPTION),
|
||||||
},
|
|
||||||
{
|
|
||||||
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISHED),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
|
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISHED),
|
||||||
|
},
|
||||||
].map(renderLink)}
|
].map(renderLink)}
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<Button
|
<Button
|
||||||
navigate="/$/tags/edit"
|
navigate="/$/following/edit"
|
||||||
icon={ICONS.EDIT}
|
icon={ICONS.EDIT}
|
||||||
className="navigation__link"
|
className="navigation__link"
|
||||||
activeClass="navigation__link--active"
|
activeClass="navigation__link--active"
|
||||||
label={__('Following')}
|
label={__('Edit')}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -15,33 +15,48 @@ type Props = {
|
||||||
followedTags: Array<Tag>,
|
followedTags: Array<Tag>,
|
||||||
doToggleTagFollow: string => void,
|
doToggleTagFollow: string => void,
|
||||||
doAddTag: string => void,
|
doAddTag: string => void,
|
||||||
|
onSelect?: Tag => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TagSelect(props: Props) {
|
export default function TagSelect(props: Props) {
|
||||||
const { unfollowedTags, followedTags, doToggleTagFollow, doAddTag } = props;
|
const { unfollowedTags, followedTags, doToggleTagFollow, doAddTag, onSelect } = props;
|
||||||
const [newTag, setNewTag] = useState('');
|
const [newTag, setNewTag] = useState('');
|
||||||
|
|
||||||
let tags = unfollowedTags.slice();
|
let tags = unfollowedTags.slice();
|
||||||
if (newTag) {
|
if (newTag) {
|
||||||
tags = [{ name: newTag }, ...tags];
|
tags.unshift({ name: newTag });
|
||||||
}
|
}
|
||||||
const suggestedTags = tags
|
|
||||||
.filter(({ name }) => (newTag ? name.toLowerCase().includes(newTag.toLowerCase()) : true))
|
const doesTagMatch = ({ name }) => (newTag ? name.toLowerCase().includes(newTag.toLowerCase()) : true);
|
||||||
.slice(0, 5);
|
const suggestedTags = tags.filter(doesTagMatch).slice(0, 5);
|
||||||
const suggestedTransitions = useTransition(suggestedTags, tag => tag.name, unfollowedTagsAnimation);
|
const suggestedTransitions = useTransition(suggestedTags, tag => tag.name, unfollowedTagsAnimation);
|
||||||
|
|
||||||
function onChange(e) {
|
function onChange(e) {
|
||||||
setNewTag(e.target.value);
|
setNewTag(e.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
setNewTag('');
|
setNewTag('');
|
||||||
|
|
||||||
if (!unfollowedTags.includes(newTag)) {
|
if (onSelect) {
|
||||||
doAddTag(newTag);
|
onSelect({ name: newTag });
|
||||||
}
|
} else {
|
||||||
|
if (!unfollowedTags.includes(newTag)) {
|
||||||
|
doAddTag(newTag);
|
||||||
|
}
|
||||||
|
|
||||||
if (!followedTags.includes(newTag)) {
|
if (!followedTags.includes(newTag)) {
|
||||||
doToggleTagFollow(newTag);
|
doToggleTagFollow(newTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTagClick(tag) {
|
||||||
|
if (onSelect) {
|
||||||
|
onSelect(tag);
|
||||||
|
} else {
|
||||||
|
doToggleTagFollow(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +74,7 @@ export default function TagSelect(props: Props) {
|
||||||
<ul className="tags">
|
<ul className="tags">
|
||||||
{suggestedTransitions.map(({ item, key, props }) => (
|
{suggestedTransitions.map(({ item, key, props }) => (
|
||||||
<animated.li key={key} style={props}>
|
<animated.li key={key} style={props}>
|
||||||
<Tag name={item.name} type="add" onClick={() => doToggleTagFollow(item.name)} />
|
<Tag name={item.name} type="add" onClick={() => handleTagClick(item)} />
|
||||||
</animated.li>
|
</animated.li>
|
||||||
))}
|
))}
|
||||||
{!suggestedTransitions.length && <p className="empty tags__empty-message">No suggested tags</p>}
|
{!suggestedTransitions.length && <p className="empty tags__empty-message">No suggested tags</p>}
|
||||||
|
|
|
@ -8,10 +8,18 @@ import usePersistedState from 'util/use-persisted-state';
|
||||||
import { useTransition, animated } from 'react-spring';
|
import { useTransition, animated } from 'react-spring';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
followedTags: Array<Tag>,
|
|
||||||
showClose: boolean,
|
showClose: boolean,
|
||||||
title: string,
|
followedTags: Array<Tag>,
|
||||||
doDeleteTag: string => void,
|
doToggleTagFollow: string => void,
|
||||||
|
|
||||||
|
// Ovverides
|
||||||
|
// The default component is for following tags
|
||||||
|
title?: string,
|
||||||
|
help?: string,
|
||||||
|
empty?: string,
|
||||||
|
tagsChosen?: Array<Tag>,
|
||||||
|
onSelect?: Tag => void,
|
||||||
|
onRemove?: Tag => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
const tagsAnimation = {
|
const tagsAnimation = {
|
||||||
|
@ -21,23 +29,32 @@ const tagsAnimation = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TagSelect(props: Props) {
|
export default function TagSelect(props: Props) {
|
||||||
const { title, followedTags, showClose, doDeleteTag } = props;
|
const { showClose, followedTags, doToggleTagFollow, title, help, empty, tagsChosen, onSelect, onRemove } = props;
|
||||||
const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false);
|
const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false);
|
||||||
|
const tagsToDisplay = tagsChosen || followedTags;
|
||||||
|
const transitions = useTransition(tagsToDisplay, tag => tag.name, tagsAnimation);
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
setHasClosed(true);
|
setHasClosed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const transitions = useTransition(followedTags.map(tag => tag), tag => tag.name, tagsAnimation);
|
function handleTagClick(tag) {
|
||||||
|
if (onRemove) {
|
||||||
|
onRemove(tag);
|
||||||
|
} else {
|
||||||
|
doToggleTagFollow(tag.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
((showClose && !hasClosed) || !showClose) && (
|
((showClose && !hasClosed) || !showClose) && (
|
||||||
<div className="card--section">
|
<div className="card--section">
|
||||||
<h2 className="card__title card__title--flex-between">
|
{title !== false && (
|
||||||
{title}
|
<h2 className="card__title card__title--flex-between">
|
||||||
{showClose && !hasClosed && <Button button="close" icon={ICONS.REMOVE} onClick={handleClose} />}
|
{title}
|
||||||
</h2>
|
{showClose && !hasClosed && <Button button="close" icon={ICONS.REMOVE} onClick={handleClose} />}
|
||||||
<p className="help">{__("The tags you follow will change what's trending for you.")}</p>
|
</h2>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<ul className="tags--remove">
|
<ul className="tags--remove">
|
||||||
|
@ -47,16 +64,19 @@ export default function TagSelect(props: Props) {
|
||||||
name={item.name}
|
name={item.name}
|
||||||
type="remove"
|
type="remove"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
doDeleteTag(item.name);
|
handleTagClick(item);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</animated.li>
|
</animated.li>
|
||||||
))}
|
))}
|
||||||
{!transitions.length && (
|
{!transitions.length && (
|
||||||
<div className="card__subtitle">{__("You aren't following any tags, try searching for one.")}</div>
|
<div className="empty">{empty || __("You aren't following any tags, try searching for one.")}</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
<TagsSearch />
|
<TagsSearch onSelect={onSelect} />
|
||||||
|
{help !== false && (
|
||||||
|
<p className="help">{help || __("The tags you follow will change what's trending for you.")}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { buildURI } from 'lbry-redux';
|
import { buildURI } from 'lbry-redux';
|
||||||
|
import Tooltip from 'component/common/tooltip';
|
||||||
|
import ClaimPreview from 'component/claimPreview';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isResolvingUri: boolean,
|
isResolvingUri: boolean,
|
||||||
|
@ -61,7 +63,7 @@ class UriIndicator extends React.PureComponent<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button className="button--uri-indicator" navigate={channelLink}>
|
<Button className="button--uri-indicator" navigate={channelLink}>
|
||||||
{inner}
|
<Tooltip label={<ClaimPreview uri={channelLink} type="small" />}>{inner}</Tooltip>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -20,3 +20,4 @@ export const SEARCH = 'search';
|
||||||
export const TRANSACTIONS = 'transactions';
|
export const TRANSACTIONS = 'transactions';
|
||||||
export const TAGS = 'tags';
|
export const TAGS = 'tags';
|
||||||
export const WALLET = 'wallet';
|
export const WALLET = 'wallet';
|
||||||
|
export const FOLLOWING = 'following';
|
||||||
|
|
|
@ -232,9 +232,9 @@ class FilePage extends React.Component<Props> {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{claimIsMine && (
|
{claimIsMine && (
|
||||||
<div className="media__subtext--large">
|
<p>
|
||||||
{viewCount} {viewCount !== 1 ? __('Views') : __('View')}
|
{viewCount} {viewCount !== 1 ? __('Views') : __('View')}
|
||||||
</div>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectFollowedTags } from 'lbry-redux';
|
import { selectFollowedTags } from 'lbry-redux';
|
||||||
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
import TagsEdit from './view';
|
import TagsEdit from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
followedTags: selectFollowedTags(state),
|
followedTags: selectFollowedTags(state),
|
||||||
|
subscribedChannels: selectSubscriptions(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = {};
|
const perform = {};
|
30
src/ui/page/following/view.jsx
Normal file
30
src/ui/page/following/view.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Page from 'component/page';
|
||||||
|
import TagsSelect from 'component/tagsSelect';
|
||||||
|
import ClaimList from 'component/claimList';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
subscribedChannels: Array<{ uri: string }>,
|
||||||
|
};
|
||||||
|
|
||||||
|
function DiscoverPage(props: Props) {
|
||||||
|
const { subscribedChannels } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<div className="card">
|
||||||
|
<TagsSelect showClose={false} title={__('Find New Tags To Follow')} />
|
||||||
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<ClaimList
|
||||||
|
header={<h1>{__('Channels You Are Following')}</h1>}
|
||||||
|
empty={__("You aren't following any channels.")}
|
||||||
|
uris={subscribedChannels.map(({ uri }) => uri)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DiscoverPage;
|
|
@ -1,46 +1,14 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doResolveUri, selectBalance } from 'lbry-redux';
|
import { selectBalance } from 'lbry-redux';
|
||||||
import {
|
|
||||||
selectPublishFormValues,
|
|
||||||
selectIsStillEditing,
|
|
||||||
selectMyClaimForUri,
|
|
||||||
selectIsResolvingPublishUris,
|
|
||||||
selectTakeOverAmount,
|
|
||||||
} from 'redux/selectors/publish';
|
|
||||||
import {
|
|
||||||
doResetThumbnailStatus,
|
|
||||||
doClearPublish,
|
|
||||||
doUpdatePublishForm,
|
|
||||||
doPublish,
|
|
||||||
doPrepareEdit,
|
|
||||||
} from 'redux/actions/publish';
|
|
||||||
import { selectUnclaimedRewardValue } from 'lbryinc';
|
import { selectUnclaimedRewardValue } from 'lbryinc';
|
||||||
import PublishPage from './view';
|
import PublishPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
...selectPublishFormValues(state),
|
|
||||||
// The winning claim for a short lbry uri
|
|
||||||
amountNeededForTakeover: selectTakeOverAmount(state),
|
|
||||||
// My previously published claims under this short lbry uri
|
|
||||||
myClaimForUri: selectMyClaimForUri(state),
|
|
||||||
// If I clicked the "edit" button, have I changed the uri?
|
|
||||||
// Need this to make it easier to find the source on previously published content
|
|
||||||
isStillEditing: selectIsStillEditing(state),
|
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
isResolvingUri: selectIsResolvingPublishUris(state),
|
|
||||||
totalRewardValue: selectUnclaimedRewardValue(state),
|
totalRewardValue: selectUnclaimedRewardValue(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
|
||||||
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
|
||||||
clearPublish: () => dispatch(doClearPublish()),
|
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
|
||||||
publish: params => dispatch(doPublish(params)),
|
|
||||||
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
|
||||||
resetThumbnailStatus: () => dispatch(doResetThumbnailStatus()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
select,
|
select,
|
||||||
perform
|
null
|
||||||
)(PublishPage);
|
)(PublishPage);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PublishForm from 'component/publishForm';
|
import PublishForm from 'component/publishForm';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
|
@ -6,64 +7,67 @@ import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
import CreditAmount from 'component/common/credit-amount';
|
import CreditAmount from 'component/common/credit-amount';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
|
||||||
class PublishPage extends React.PureComponent {
|
type Props = {
|
||||||
scrollToTop = () => {
|
balance: number,
|
||||||
|
totalRewardValue: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
function PublishPage(props: Props) {
|
||||||
|
const { balance, totalRewardValue } = props;
|
||||||
|
const totalRewardRounded = Math.floor(totalRewardValue / 10) * 10;
|
||||||
|
|
||||||
|
function scrollToTop() {
|
||||||
const mainContent = document.querySelector('main');
|
const mainContent = document.querySelector('main');
|
||||||
if (mainContent) {
|
if (mainContent) {
|
||||||
mainContent.scrollTop = 0; // It would be nice to animate this
|
mainContent.scrollTop = 0; // It would be nice to animate this
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { balance, totalRewardValue } = this.props;
|
|
||||||
const totalRewardRounded = Math.floor(totalRewardValue / 10) * 10;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Page>
|
|
||||||
{balance === 0 && (
|
|
||||||
<Fragment>
|
|
||||||
<Yrbl
|
|
||||||
title={__("You can't publish things quite yet")}
|
|
||||||
subtitle={
|
|
||||||
<Fragment>
|
|
||||||
<p>
|
|
||||||
{__(
|
|
||||||
'LBRY uses a blockchain, which is a fancy way of saying that users (you) are in control of your data.'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{__('Because of the blockchain, some actions require LBRY credits')} (
|
|
||||||
<LbcSymbol />
|
|
||||||
).
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<LbcSymbol />{' '}
|
|
||||||
{__(
|
|
||||||
'allows you to do some neat things, like paying your favorite creators for their content. And no company can stop you.'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</Fragment>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<section className="card card--section">
|
|
||||||
<header className="card__header">
|
|
||||||
<h1 className="card__title">{__('LBRY Credits Required')}</h1>
|
|
||||||
</header>
|
|
||||||
<p className="card__subtitle">
|
|
||||||
{__(' There are a variety of ways to get credits, including more than')}{' '}
|
|
||||||
<CreditAmount inheritStyle amount={totalRewardRounded} />{' '}
|
|
||||||
{__('in free rewards for participating in the LBRY beta.')}
|
|
||||||
</p>
|
|
||||||
<div className="card__actions">
|
|
||||||
<Button button="link" navigate="/$/rewards" label={__('Checkout the rewards')} />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
<PublishForm {...this.props} scrollToTop={this.scrollToTop} />
|
|
||||||
</Page>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
{balance === 0 && (
|
||||||
|
<Fragment>
|
||||||
|
<Yrbl
|
||||||
|
title={__("You can't publish things quite yet")}
|
||||||
|
subtitle={
|
||||||
|
<Fragment>
|
||||||
|
<p>
|
||||||
|
{__(
|
||||||
|
'LBRY uses a blockchain, which is a fancy way of saying that users (you) are in control of your data.'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{__('Because of the blockchain, some actions require LBRY credits')} (
|
||||||
|
<LbcSymbol />
|
||||||
|
).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<LbcSymbol />{' '}
|
||||||
|
{__(
|
||||||
|
'allows you to do some neat things, like paying your favorite creators for their content. And no company can stop you.'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<section className="card card--section">
|
||||||
|
<header className="card__header">
|
||||||
|
<h1 className="card__title">{__('LBRY Credits Required')}</h1>
|
||||||
|
</header>
|
||||||
|
<p className="card__subtitle">
|
||||||
|
{__(' There are a variety of ways to get credits, including more than')}{' '}
|
||||||
|
<CreditAmount inheritStyle amount={totalRewardRounded} />{' '}
|
||||||
|
{__('in free rewards for participating in the LBRY beta.')}
|
||||||
|
</p>
|
||||||
|
<div className="card__actions">
|
||||||
|
<Button button="link" navigate="/$/rewards" label={__('Checkout the rewards')} />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
<PublishForm scrollToTop={scrollToTop} />
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PublishPage;
|
export default PublishPage;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React, { useEffect, Fragment } from 'react';
|
import React, { useEffect, Fragment } from 'react';
|
||||||
import { isURIValid, normalizeURI } from 'lbry-redux';
|
import { isURIValid, normalizeURI } from 'lbry-redux';
|
||||||
import ClaimListItem from 'component/claimListItem';
|
import ClaimPreview from 'component/claimPreview';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import SearchOptions from 'component/searchOptions';
|
import SearchOptions from 'component/searchOptions';
|
||||||
|
@ -29,7 +29,6 @@ export default function SearchPage(props: Props) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (urlQuery) {
|
if (urlQuery) {
|
||||||
console.log('search', urlQuery);
|
|
||||||
search(urlQuery);
|
search(urlQuery);
|
||||||
}
|
}
|
||||||
}, [search, urlQuery]);
|
}, [search, urlQuery]);
|
||||||
|
@ -44,7 +43,7 @@ export default function SearchPage(props: Props) {
|
||||||
<Button button="alt" navigate={uri} className="media__uri">
|
<Button button="alt" navigate={uri} className="media__uri">
|
||||||
{uri}
|
{uri}
|
||||||
</Button>
|
</Button>
|
||||||
<ClaimListItem uri={uri} type="large" />
|
<ClaimPreview uri={uri} type="large" />
|
||||||
</header>
|
</header>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default function SubscriptionsPage(props: Props) {
|
||||||
const viewingSuggestedSubs = urlParams.get('view');
|
const viewingSuggestedSubs = urlParams.get('view');
|
||||||
|
|
||||||
function onClick() {
|
function onClick() {
|
||||||
let url = `/$/${PAGES.SUBSCRIPTIONS}`;
|
let url = `/$/${PAGES.FOLLOWING}`;
|
||||||
if (!viewingSuggestedSubs) {
|
if (!viewingSuggestedSubs) {
|
||||||
url += '?view=discover';
|
url += '?view=discover';
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ export default function SubscriptionsPage(props: Props) {
|
||||||
const ids = idString.split(',');
|
const ids = idString.split(',');
|
||||||
const options = {
|
const options = {
|
||||||
channel_ids: ids,
|
channel_ids: ids,
|
||||||
|
order_by: ['release_time'],
|
||||||
};
|
};
|
||||||
|
|
||||||
doClaimSearch(20, options);
|
doClaimSearch(20, options);
|
||||||
|
@ -72,7 +73,8 @@ export default function SubscriptionsPage(props: Props) {
|
||||||
onClick={() => onClick()}
|
onClick={() => onClick()}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris}
|
// Fix the need to reverse this
|
||||||
|
uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris.reverse()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React from 'react';
|
|
||||||
import Page from 'component/page';
|
|
||||||
import TagsSelect from 'component/tagsSelect';
|
|
||||||
|
|
||||||
type Props = {};
|
|
||||||
|
|
||||||
function DiscoverPage(props: Props) {
|
|
||||||
return (
|
|
||||||
<Page>
|
|
||||||
<div className="card">
|
|
||||||
<TagsSelect showClose={false} title={__('Find New Tags To Follow')} />
|
|
||||||
</div>
|
|
||||||
</Page>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DiscoverPage;
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import { selectosNotificationsEnabled } from 'redux/selectors/settings';
|
import { selectosNotificationsEnabled } from 'redux/selectors/settings';
|
||||||
|
import { selectMyClaimForUri, selectPublishFormValues } from 'redux/selectors/publish';
|
||||||
import { push } from 'connected-react-router';
|
import { push } from 'connected-react-router';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import { formatLbryUriForWeb } from 'util/uri';
|
import { formatLbryUriForWeb } from 'util/uri';
|
||||||
|
@ -146,7 +147,7 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileLis
|
||||||
// use same values as default state
|
// use same values as default state
|
||||||
// fee will be undefined for free content
|
// fee will be undefined for free content
|
||||||
fee = {
|
fee = {
|
||||||
amount: 0,
|
amount: '0',
|
||||||
currency: 'LBC',
|
currency: 'LBC',
|
||||||
},
|
},
|
||||||
languages,
|
languages,
|
||||||
|
@ -159,11 +160,11 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileLis
|
||||||
const publishData: UpdatePublishFormData = {
|
const publishData: UpdatePublishFormData = {
|
||||||
name,
|
name,
|
||||||
channel: channelName,
|
channel: channelName,
|
||||||
bid: amount,
|
bid: Number(amount),
|
||||||
contentIsFree: !fee.amount,
|
contentIsFree: !fee.amount,
|
||||||
author,
|
author,
|
||||||
description,
|
description,
|
||||||
fee: { amount: fee.amount, currency: fee.currency },
|
fee,
|
||||||
languages,
|
languages,
|
||||||
thumbnail: thumbnail ? thumbnail.url : null,
|
thumbnail: thumbnail ? thumbnail.url : null,
|
||||||
title,
|
title,
|
||||||
|
@ -201,10 +202,13 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileLis
|
||||||
dispatch({ type: ACTIONS.DO_PREPARE_EDIT, data: publishData });
|
dispatch({ type: ACTIONS.DO_PREPARE_EDIT, data: publishData });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getState: () => {}) => {
|
export const doPublish = () => (dispatch: Dispatch, getState: () => {}) => {
|
||||||
dispatch({ type: ACTIONS.PUBLISH_START });
|
dispatch({ type: ACTIONS.PUBLISH_START });
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
const publishData = selectPublishFormValues(state);
|
||||||
|
const myClaimForUri = selectMyClaimForUri(state);
|
||||||
|
|
||||||
const myChannels = selectMyChannelClaims(state);
|
const myChannels = selectMyChannelClaims(state);
|
||||||
const myClaims = selectMyClaimsWithoutChannels(state);
|
const myClaims = selectMyClaimsWithoutChannels(state);
|
||||||
|
|
||||||
|
@ -214,8 +218,9 @@ export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getStat
|
||||||
filePath,
|
filePath,
|
||||||
description,
|
description,
|
||||||
language,
|
language,
|
||||||
license,
|
|
||||||
licenseUrl,
|
licenseUrl,
|
||||||
|
licenseType,
|
||||||
|
otherLicenseDescription,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
channel,
|
channel,
|
||||||
title,
|
title,
|
||||||
|
@ -223,8 +228,19 @@ export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getStat
|
||||||
fee,
|
fee,
|
||||||
uri,
|
uri,
|
||||||
nsfw,
|
nsfw,
|
||||||
claim,
|
tags,
|
||||||
} = params;
|
locations,
|
||||||
|
} = publishData;
|
||||||
|
|
||||||
|
let publishingLicense;
|
||||||
|
switch (licenseType) {
|
||||||
|
case COPYRIGHT:
|
||||||
|
case OTHER:
|
||||||
|
publishingLicense = otherLicenseDescription;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
publishingLicense = licenseType;
|
||||||
|
}
|
||||||
|
|
||||||
// get the claim id from the channel name, we will use that instead
|
// get the claim id from the channel name, we will use that instead
|
||||||
const namedChannelClaim = myChannels.find(myChannel => myChannel.name === channel);
|
const namedChannelClaim = myChannels.find(myChannel => myChannel.name === channel);
|
||||||
|
@ -244,29 +260,19 @@ export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getStat
|
||||||
fee_amount?: string,
|
fee_amount?: string,
|
||||||
} = {
|
} = {
|
||||||
name,
|
name,
|
||||||
bid: creditsToString(bid),
|
|
||||||
title,
|
title,
|
||||||
license,
|
|
||||||
languages: [language],
|
|
||||||
description,
|
description,
|
||||||
tags: (claim && claim.value.tags) || [],
|
locations,
|
||||||
locations: claim && claim.value.locations,
|
bid: creditsToString(bid),
|
||||||
|
license: publishingLicense,
|
||||||
|
languages: [language],
|
||||||
|
tags: tags && tags.map(tag => tag.name),
|
||||||
|
license_url: licenseType === COPYRIGHT ? '' : licenseUrl,
|
||||||
|
thumbnail_url: thumbnail,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Temporary solution to keep the same publish flow with the new tags api
|
if (myClaimForUri && myClaimForUri.value.release_time) {
|
||||||
// Eventually we will allow users to enter their own tags on publish
|
publishPayload.release_time = Number(myClaimForUri.value.release_time);
|
||||||
// `nsfw` will probably be removed
|
|
||||||
|
|
||||||
if (licenseUrl) {
|
|
||||||
publishPayload.license_url = licenseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thumbnail) {
|
|
||||||
publishPayload.thumbnail_url = thumbnail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (claim && claim.value.release_time) {
|
|
||||||
publishPayload.release_time = Number(claim.value.release_time);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nsfw) {
|
if (nsfw) {
|
||||||
|
@ -346,23 +352,25 @@ export const doCheckPendingPublishes = () => (dispatch: Dispatch, getState: GetS
|
||||||
|
|
||||||
const checkFileList = () => {
|
const checkFileList = () => {
|
||||||
Lbry.claim_list().then(claims => {
|
Lbry.claim_list().then(claims => {
|
||||||
claims.forEach(claim => {
|
if (claims) {
|
||||||
// If it's confirmed, check if it was pending previously
|
claims.forEach(claim => {
|
||||||
if (claim.confirmations > 0 && pendingById[claim.claim_id]) {
|
// If it's confirmed, check if it was pending previously
|
||||||
delete pendingById[claim.claim_id];
|
if (claim.confirmations > 0 && pendingById[claim.claim_id]) {
|
||||||
|
delete pendingById[claim.claim_id];
|
||||||
|
|
||||||
// If it's confirmed, check if we should notify the user
|
// If it's confirmed, check if we should notify the user
|
||||||
if (selectosNotificationsEnabled(getState())) {
|
if (selectosNotificationsEnabled(getState())) {
|
||||||
const notif = new window.Notification('LBRY Publish Complete', {
|
const notif = new window.Notification('LBRY Publish Complete', {
|
||||||
body: `${claim.value.title} has been published to lbry://${claim.name}. Click here to view it`,
|
body: `${claim.value.title} has been published to lbry://${claim.name}. Click here to view it`,
|
||||||
silent: false,
|
silent: false,
|
||||||
});
|
});
|
||||||
notif.onclick = () => {
|
notif.onclick = () => {
|
||||||
dispatch(push(formatLbryUriForWeb(claim.permanent_url)));
|
dispatch(push(formatLbryUriForWeb(claim.permanent_url)));
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
|
type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
|
||||||
|
|
|
@ -27,6 +27,7 @@ type PublishState = {
|
||||||
bidError: ?string,
|
bidError: ?string,
|
||||||
otherLicenseDescription: string,
|
otherLicenseDescription: string,
|
||||||
licenseUrl: string,
|
licenseUrl: string,
|
||||||
|
tags: Array<string>,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultState: PublishState = {
|
const defaultState: PublishState = {
|
||||||
|
@ -53,6 +54,7 @@ const defaultState: PublishState = {
|
||||||
licenseType: 'None',
|
licenseType: 'None',
|
||||||
otherLicenseDescription: 'All rights reserved',
|
otherLicenseDescription: 'All rights reserved',
|
||||||
licenseUrl: '',
|
licenseUrl: '',
|
||||||
|
tags: [],
|
||||||
publishing: false,
|
publishing: false,
|
||||||
publishSuccess: false,
|
publishSuccess: false,
|
||||||
publishError: undefined,
|
publishError: undefined,
|
||||||
|
|
|
@ -18,6 +18,12 @@ export const selectPublishFormValues = createSelector(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const makeSelectPublishFormValue = item =>
|
||||||
|
createSelector(
|
||||||
|
selectState,
|
||||||
|
state => state[item]
|
||||||
|
);
|
||||||
|
|
||||||
// Is the current uri the same as the uri they clicked "edit" on
|
// Is the current uri the same as the uri they clicked "edit" on
|
||||||
export const selectIsStillEditing = createSelector(
|
export const selectIsStillEditing = createSelector(
|
||||||
selectPublishFormValues,
|
selectPublishFormValues,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.file-list__header {
|
.claim-list__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 4.5rem;
|
min-height: 4.5rem;
|
||||||
|
@ -31,12 +31,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__header--small {
|
.claim-list__header--small {
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
|
min-height: 3rem;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__dropdown {
|
.claim-list__dropdown {
|
||||||
background-position: 95% center;
|
background-position: 95% center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 1.2rem;
|
background-size: 1.2rem;
|
||||||
|
@ -50,8 +51,8 @@
|
||||||
background-color: lighten($lbry-black, 10%);
|
background-color: lighten($lbry-black, 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__header,
|
.claim-list__header,
|
||||||
.file-list__dropdown {
|
.claim-list__dropdown {
|
||||||
background-color: lighten($lbry-black, 10%);
|
background-color: lighten($lbry-black, 10%);
|
||||||
|
|
||||||
[data-mode='dark'] & {
|
[data-mode='dark'] & {
|
||||||
|
@ -59,17 +60,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__header-text {
|
.claim-list__header-text {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__header-text,
|
.claim-list__header-text,
|
||||||
.file-list__dropdown {
|
.claim-list__dropdown {
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__alt-controls {
|
.claim-list__alt-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
@ -80,7 +81,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__item {
|
.claim-list__item {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
|
@ -103,12 +104,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__item--injected,
|
.claim-list__item--injected,
|
||||||
.file-list__item {
|
.claim-list__item + .claim-list__item {
|
||||||
border-bottom: 1px solid rgba($lbry-teal-5, 0.1);
|
border-top: 1px solid rgba($lbry-teal-5, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__item--large {
|
.claim-list__item--large {
|
||||||
@include mediaThumbHoverZoom;
|
@include mediaThumbHoverZoom;
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
|
@ -124,36 +125,49 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__item-metadata {
|
.claim-list__pending {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.6;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $lbry-white;
|
||||||
|
|
||||||
|
[data-mode='dark'] & {
|
||||||
|
background-color: lighten($lbry-black, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.claim-list__item-metadata {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__item-info {
|
.claim-list__item-info {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__item-info,
|
.claim-list__item-info,
|
||||||
.file-list__item-properties {
|
.claim-list__item-properties {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__item-properties {
|
.claim-list__item-properties {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__item-title {
|
.claim-list__item-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__item-tags {
|
.claim-list__item-tags {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list__meta {
|
.claim-list__meta {
|
||||||
padding: var(--spacing-medium);
|
padding: var(--spacing-medium);
|
||||||
background-color: lighten($lbry-teal-5, 55%);
|
background-color: lighten($lbry-teal-5, 55%);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,36 @@
|
||||||
@import '~@lbry/components/sass/form/_index.scss';
|
@import '~@lbry/components/sass/form/_index.scss';
|
||||||
|
|
||||||
|
// replace this
|
||||||
|
form {
|
||||||
|
// setting the font size here sizes everything within
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: var(--spacing-s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
height: var(--spacing-l);
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkbox-element,
|
||||||
|
radio-element,
|
||||||
|
select {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
&::placeholder {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// lbry/components overrides and minor styles
|
// lbry/components overrides and minor styles
|
||||||
// Some items have very specific styling
|
// Some items have very specific styling
|
||||||
// This is because many styles inside `lbry/components/sass/form/` are very specific
|
// This is because many styles inside `lbry/components/sass/form/` are very specific
|
||||||
// As styles become hardened here, they _should_ slowly move over to that repo
|
// As styles become hardened here, they _should_ slowly move over to that repo
|
||||||
|
|
||||||
input,
|
|
||||||
textarea,
|
|
||||||
select {
|
|
||||||
border-radius: var(--input-border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
input-submit {
|
input-submit {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +41,8 @@ input[type='number'] {
|
||||||
|
|
||||||
input[type='text'],
|
input[type='text'],
|
||||||
input[type='number'],
|
input[type='number'],
|
||||||
select {
|
select,
|
||||||
|
textarea {
|
||||||
padding-bottom: 0.1em;
|
padding-bottom: 0.1em;
|
||||||
|
|
||||||
[data-mode='dark'] & {
|
[data-mode='dark'] & {
|
||||||
|
@ -31,6 +52,14 @@ select {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
border-color: lighten($lbry-black, 20%);
|
||||||
|
border-radius: var(--input-border-radius);
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
fieldset-section {
|
fieldset-section {
|
||||||
label {
|
label {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
padding-top: var(--header-height);
|
padding-top: var(--header-height);
|
||||||
padding-left: var(--spacing-large);
|
padding-left: var(--spacing-large);
|
||||||
padding-right: var(--spacing-large);
|
padding-right: var(--spacing-large);
|
||||||
|
padding-bottom: var(--spacing-large);
|
||||||
background-color: mix($lbry-white, $lbry-gray-1, 70%);
|
background-color: mix($lbry-white, $lbry-gray-1, 70%);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
.placeholder {
|
.placeholder {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
&.file-list__item-title {
|
&.claim-list__item-title {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue