adds tip unlock modal to file page
i18n messages, handle error case max copy copy update @lbry/components and tweak range styles sigfigs error catching and cleanup apply review changes style table and unlock button handle tip errors separate fileDescription from fileDetails make expandable cards ui tweaks tweak copy, style, behavior remove unused strings forgot an important line
This commit is contained in:
parent
872259b73a
commit
9faca8da2b
29 changed files with 645 additions and 115 deletions
|
@ -130,7 +130,7 @@
|
|||
"imagesloaded": "^4.1.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#90ba18d0602956016ded63e816734713cfd2023a",
|
||||
"lbry-redux": "lbryio/lbry-redux#1097a63d44a20b87e443fbaa48f95fe3ea5e3f70",
|
||||
"lbryinc": "lbryio/lbryinc#19260fac560daaa4be2d4af372f28109ea96ebf9",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
|
|
|
@ -335,6 +335,7 @@
|
|||
"credits": "credits",
|
||||
"No channel name after @.": "No channel name after @.",
|
||||
"View channel": "View channel",
|
||||
"Add to your library": "Add to your library",
|
||||
"Web link": "Web link",
|
||||
"Facebook": "Facebook",
|
||||
"Twitter": "Twitter",
|
||||
|
@ -841,6 +842,7 @@
|
|||
"Any amount will give you the highest bid, but larger amounts help your content be trusted and discovered.": "Any amount will give you the highest bid, but larger amounts help your content be trusted and discovered.",
|
||||
"Loading 3D model.": "Loading 3D model.",
|
||||
"Click here": "Click here",
|
||||
"PDF opened externally. %click_here% to open it again.": "PDF opened externally. %click_here% to open it again.",
|
||||
"Wallet Server": "Wallet Server",
|
||||
"lbry.tv wallet servers": "lbry.tv wallet servers",
|
||||
"Custom wallet servers": "Custom wallet servers",
|
||||
|
@ -997,7 +999,7 @@
|
|||
"Short": "Short",
|
||||
"How Fresh": "How Fresh",
|
||||
"This Default": "This Default",
|
||||
"Sorry, your request timed out. Modify your options or %again%": "Sorry, your request timed out. Modify your options or %again%",
|
||||
"Sorry, your request returned no results or timed out. Modify your options or %again%": "Sorry, your request returned no results or timed out. Modify your options or %again%",
|
||||
"Image": "Image",
|
||||
"Model": "Model",
|
||||
"Binary": "Binary",
|
||||
|
@ -1008,7 +1010,6 @@
|
|||
"Only apply a few tags that are relevant to your content, and use the Mature tag as appropriate. Tag abuse will not be tolerated.": "Only apply a few tags that are relevant to your content, and use the Mature tag as appropriate. Tag abuse will not be tolerated.",
|
||||
"Add relevant tags...": "Add relevant tags...",
|
||||
"Enter up to five (5) tags that are relevant to your content, and use the Mature tag as appropriate. Tag abuse will not be tolerated.": "Enter up to five (5) tags that are relevant to your content, and use the Mature tag as appropriate. Tag abuse will not be tolerated.",
|
||||
"Sorry, your request timed out. Modify your options or %again%": "Sorry, your request timed out. Modify your options or %again%",
|
||||
"gaming, crypto": "gaming, crypto",
|
||||
"Autocomplete": "Autocomplete",
|
||||
"Followed Tags": "Followed Tags",
|
||||
|
@ -1084,5 +1085,37 @@
|
|||
"Kannada": "Kannada",
|
||||
"Transcoding this %size%MB file should take under %processTime% %units%.": "Transcoding this %size%MB file should take under %processTime% %units%.",
|
||||
"FFmpeg not configured. More in %settings_link%.": "FFmpeg not configured. More in %settings_link%.",
|
||||
"smallresult": "smallresult"
|
||||
"File Details": "File Details",
|
||||
"LBC Details": "LBC Details",
|
||||
"Publish Amount": "Publish Amount",
|
||||
"Supports and Tips": "Supports and Tips",
|
||||
"Top for name": "Top for name",
|
||||
"%name%": "%name%",
|
||||
"Amount must be a number": "Amount must be a number",
|
||||
"Amount cannot be blank": "Amount cannot be blank",
|
||||
"Amount cannot be more than available": "Amount cannot be more than available",
|
||||
"Amount to unlock": "Amount to unlock",
|
||||
"Unlock Tips": "Unlock Tips",
|
||||
"available to unlock.": "available to unlock.",
|
||||
"%message%": "%message%",
|
||||
"Support This Claim": "Support This Claim",
|
||||
"view other claims at lbry://%name%": "view other claims at lbry://%name%",
|
||||
"Not top for name": "Not top for name",
|
||||
"loading": "loading",
|
||||
"Original Publish Amount": "Original Publish Amount",
|
||||
"Total Staked Amount": "Total Staked Amount",
|
||||
"Set Inviter": "Set Inviter",
|
||||
"Sign In to lbry.tv to Earn Rewards From Inviting Your Friends": "Sign In to lbry.tv to Earn Rewards From Inviting Your Friends",
|
||||
"You haven't published anything with this channel yet!": "You haven't published anything with this channel yet!",
|
||||
"Publish Something": "Publish Something",
|
||||
"Amount cannot be zero": "Amount cannot be zero",
|
||||
"Your content will do better with more staked on it": "Your content will do better with more staked on it",
|
||||
"She's about to close up the library!": "She's about to close up the library!",
|
||||
"Community Choice?": "Community Choice?",
|
||||
"Download to your Library": "Download to your Library",
|
||||
"Leave a Comment": "Leave a Comment",
|
||||
"Repost %count%": "Repost %count%",
|
||||
"File Description": "File Description",
|
||||
"View %count% reposts": "View %count% reposts",
|
||||
"Preparing your content": "Preparing your content"
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@ function ChannelForm(props: Props) {
|
|||
return (
|
||||
<Fragment>
|
||||
<Card
|
||||
actionIconPadding={false}
|
||||
icon={ICONS.CHANNEL}
|
||||
title="Create a New Channel"
|
||||
subtitle="This is a username or handle that your content can be found under."
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
// @flow
|
||||
import type { Node } from 'react';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Icon from 'component/common/icon';
|
||||
import Button from 'component/button';
|
||||
import { toCapitalCase } from 'util/string';
|
||||
import * as ICONS from 'constants/icons';
|
||||
|
||||
type Props = {
|
||||
title?: string | Node,
|
||||
|
@ -13,7 +16,7 @@ type Props = {
|
|||
className?: string,
|
||||
isPageTitle?: boolean,
|
||||
isBodyTable?: boolean,
|
||||
actionIconPadding?: boolean,
|
||||
defaultExpand?: boolean,
|
||||
};
|
||||
|
||||
export default function Card(props: Props) {
|
||||
|
@ -26,38 +29,49 @@ export default function Card(props: Props) {
|
|||
className,
|
||||
isPageTitle = false,
|
||||
isBodyTable = false,
|
||||
actionIconPadding = true,
|
||||
defaultExpand,
|
||||
} = props;
|
||||
const [expanded, setExpanded] = useState(defaultExpand);
|
||||
const expandable = defaultExpand !== undefined;
|
||||
|
||||
return (
|
||||
<section className={classnames(className, 'card')}>
|
||||
{(title || subtitle) && (
|
||||
<div className="card__header">
|
||||
{icon && <Icon sectionIcon icon={icon} />}
|
||||
<div>
|
||||
{isPageTitle && <h1 className="card__title">{title}</h1>}
|
||||
{!isPageTitle && <h2 className="card__title">{title}</h2>}
|
||||
{subtitle && <div className="card__subtitle">{subtitle}</div>}
|
||||
<div className="card__header--between">
|
||||
<div className="card__section--flex">
|
||||
{icon && <Icon sectionIcon icon={icon} />}
|
||||
<div>
|
||||
{isPageTitle && <h1 className="card__title">{title}</h1>}
|
||||
{!isPageTitle && <h2 className="card__title">{title}</h2>}
|
||||
{subtitle && <div className="card__subtitle">{subtitle}</div>}
|
||||
</div>
|
||||
</div>
|
||||
{expandable && (
|
||||
<div className="section--padded">
|
||||
<Button
|
||||
button={'alt'}
|
||||
aria-label={__('More')}
|
||||
icon={expanded ? toCapitalCase(ICONS.SUBTRACT) : toCapitalCase(ICONS.ADD)}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{body && (
|
||||
<div
|
||||
className={classnames('card__body', {
|
||||
'card__body--with-icon': icon,
|
||||
'card__body--no-title': !title && !subtitle,
|
||||
'card__body--table': isBodyTable,
|
||||
})}
|
||||
>
|
||||
{body}
|
||||
</div>
|
||||
)}
|
||||
{actions && (
|
||||
<div
|
||||
className={classnames('card__main-actions', { 'card__main-actions--with-icon': icon && actionIconPadding })}
|
||||
>
|
||||
{actions}
|
||||
</div>
|
||||
{(!expandable || (expandable && expanded)) && (
|
||||
<>
|
||||
{body && (
|
||||
<div
|
||||
className={classnames('card__body', {
|
||||
'card__body--no-title': !title && !subtitle,
|
||||
'card__body--table': isBodyTable,
|
||||
})}
|
||||
>
|
||||
{body}
|
||||
</div>
|
||||
)}
|
||||
{actions && <div className="card__main-actions">{actions}</div>}
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
|
|
|
@ -32,6 +32,9 @@ type Props = {
|
|||
blockWrap: boolean,
|
||||
charCount?: number,
|
||||
textAreaMaxLength?: number,
|
||||
range?: number,
|
||||
min?: number,
|
||||
max?: number,
|
||||
};
|
||||
|
||||
export class FormField extends React.PureComponent<Props> {
|
||||
|
@ -98,6 +101,13 @@ export class FormField extends React.PureComponent<Props> {
|
|||
<label htmlFor={name}>{label}</label>
|
||||
</div>
|
||||
);
|
||||
} else if (type === 'range') {
|
||||
input = (
|
||||
<div>
|
||||
<input id={name} type="range" {...inputProps} />
|
||||
<label htmlFor={name}>{label}</label>
|
||||
</div>
|
||||
);
|
||||
} else if (type === 'select') {
|
||||
input = (
|
||||
<fieldset-section>
|
||||
|
|
|
@ -209,6 +209,11 @@ export const icons = {
|
|||
<line x1="5" y1="12" x2="19" y2="12" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.SUBTRACT]: buildIcon(
|
||||
<g>
|
||||
<line x1="5" y1="12" x2="19" y2="12" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.CHAT]: buildIcon(
|
||||
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
|
||||
),
|
||||
|
@ -218,7 +223,7 @@ export const icons = {
|
|||
[ICONS.NO]: buildIcon(
|
||||
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" />
|
||||
),
|
||||
[ICONS.UP]: buildIcon(<polyline transform="translate(-5.000) scale(1.1, 1.1)" points="18 15 12 9 6 15" />),
|
||||
[ICONS.UP]: buildIcon(<polyline transform="matrix(1,0,0,-1,0,24.707107)" points="6 9 12 15 18 9" />),
|
||||
[ICONS.DOWN]: buildIcon(<polyline points="6 9 12 15 18 9" />),
|
||||
[ICONS.FULLSCREEN]: buildIcon(
|
||||
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" />
|
||||
|
|
13
ui/component/fileDescription/index.js
Normal file
13
ui/component/fileDescription/index.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, makeSelectMetadataForUri, makeSelectTagsForUri } from 'lbry-redux';
|
||||
import { selectUser } from 'lbryinc';
|
||||
import FileDescription from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
metadata: makeSelectMetadataForUri(props.uri)(state),
|
||||
user: selectUser(state),
|
||||
tags: makeSelectTagsForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(select, null)(FileDescription);
|
47
ui/component/fileDescription/view.jsx
Normal file
47
ui/component/fileDescription/view.jsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
// @flow
|
||||
import React, { Fragment, PureComponent } from 'react';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
import ClaimTags from 'component/claimTags';
|
||||
import Card from 'component/common/card';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
claim: StreamClaim,
|
||||
metadata: StreamMetadata,
|
||||
user: ?any,
|
||||
tags: any,
|
||||
};
|
||||
|
||||
class FileDescription extends PureComponent<Props> {
|
||||
render() {
|
||||
const { uri, claim, metadata, tags } = this.props;
|
||||
|
||||
if (!claim || !metadata) {
|
||||
return <span className="empty">{__('Empty claim or metadata info.')}</span>;
|
||||
}
|
||||
|
||||
const { description } = metadata;
|
||||
|
||||
if (!description && !(tags && tags.length)) return null;
|
||||
return (
|
||||
<Fragment>
|
||||
<Card
|
||||
title={__('Description')}
|
||||
defaultExpand
|
||||
actions={
|
||||
<>
|
||||
{description && (
|
||||
<div className="media__info-text">
|
||||
<MarkdownPreview content={description} />
|
||||
</div>
|
||||
)}
|
||||
<ClaimTags uri={uri} type="large" />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileDescription;
|
|
@ -1,10 +1,7 @@
|
|||
// @flow
|
||||
import React, { Fragment, PureComponent } from 'react';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
import Button from 'component/button';
|
||||
import Expandable from 'component/expandable';
|
||||
import path from 'path';
|
||||
import ClaimTags from 'component/claimTags';
|
||||
import Card from 'component/common/card';
|
||||
|
||||
type Props = {
|
||||
|
@ -19,13 +16,13 @@ type Props = {
|
|||
|
||||
class FileDetails extends PureComponent<Props> {
|
||||
render() {
|
||||
const { uri, claim, contentType, fileInfo, metadata, openFolder } = this.props;
|
||||
const { claim, contentType, fileInfo, metadata, openFolder } = this.props;
|
||||
|
||||
if (!claim || !metadata) {
|
||||
return <span className="empty">{__('Empty claim or metadata info.')}</span>;
|
||||
}
|
||||
|
||||
const { description, languages, license } = metadata;
|
||||
const { languages, license } = metadata;
|
||||
|
||||
const mediaType = contentType || 'unknown';
|
||||
const fileSize =
|
||||
|
@ -44,75 +41,56 @@ class FileDetails extends PureComponent<Props> {
|
|||
return (
|
||||
<Fragment>
|
||||
<Card
|
||||
title={__('Details')}
|
||||
body={
|
||||
<Expandable>
|
||||
{description && (
|
||||
<div className="media__info-text">
|
||||
<MarkdownPreview content={description} />
|
||||
</div>
|
||||
)}
|
||||
<ClaimTags uri={uri} type="large" />
|
||||
<table className="table table--condensed table--fixed table--file-details">
|
||||
<tbody>
|
||||
title={__('File Details')}
|
||||
defaultExpand={false}
|
||||
actions={
|
||||
<table className="table table--condensed table--fixed table--file-details">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> {__('Content Type')}</td>
|
||||
<td>{mediaType}</td>
|
||||
</tr>
|
||||
{fileSize && (
|
||||
<tr>
|
||||
<td> {__('Content Type')}</td>
|
||||
<td>{mediaType}</td>
|
||||
</tr>
|
||||
{fileSize && (
|
||||
<tr>
|
||||
<td> {__('File Size')}</td>
|
||||
<td>{fileSize}</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td> {__('Bid Amount')}</td>
|
||||
<td>{claim.amount} LBC</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> {__('Effective Amount')}</td>
|
||||
<td>{claim.meta.effective_amount} LBC</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> {__('Is Controlling')}</td>
|
||||
<td>{claim.meta.is_controlling ? __('Yes') : __('No')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> {__('Claim ID')}</td>
|
||||
<td>{claim.claim_id}</td>
|
||||
<td> {__('File Size')}</td>
|
||||
<td>{fileSize}</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td> {__('Claim ID')}</td>
|
||||
<td>{claim.claim_id}</td>
|
||||
</tr>
|
||||
|
||||
{languages && (
|
||||
<tr>
|
||||
<td>{__('Languages')}</td>
|
||||
<td>{languages.join(' ')}</td>
|
||||
</tr>
|
||||
)}
|
||||
{languages && (
|
||||
<tr>
|
||||
<td>{__('License')}</td>
|
||||
<td>{license}</td>
|
||||
<td>{__('Languages')}</td>
|
||||
<td>{languages.join(' ')}</td>
|
||||
</tr>
|
||||
{downloadPath && (
|
||||
<tr>
|
||||
<td>{__('Downloaded to')}</td>
|
||||
<td>
|
||||
{/* {downloadPath.replace(/(.{10})/g, '$1\u200b')} */}
|
||||
<Button
|
||||
button="link"
|
||||
className="button--download-link"
|
||||
onClick={() => {
|
||||
if (downloadPath) {
|
||||
openFolder(downloadPath);
|
||||
}
|
||||
}}
|
||||
label={downloadNote || downloadPath.replace(/(.{10})/g, '$1\u200b')}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</Expandable>
|
||||
)}
|
||||
<tr>
|
||||
<td>{__('License')}</td>
|
||||
<td>{license}</td>
|
||||
</tr>
|
||||
{downloadPath && (
|
||||
<tr>
|
||||
<td>{__('Downloaded to')}</td>
|
||||
<td>
|
||||
{/* {downloadPath.replace(/(.{10})/g, '$1\u200b')} */}
|
||||
<Button
|
||||
button="link"
|
||||
className="button--download-link"
|
||||
onClick={() => {
|
||||
if (downloadPath) {
|
||||
openFolder(downloadPath);
|
||||
}
|
||||
}}
|
||||
label={downloadNote || downloadPath.replace(/(.{10})/g, '$1\u200b')}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri, makeSelectPendingAmountByUri } from 'lbry-redux';
|
||||
import FileSubtitle from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
pendingAmount: makeSelectPendingAmountByUri(props.uri)(state),
|
||||
});
|
||||
export default connect(select)(FileSubtitle);
|
||||
|
|
|
@ -7,10 +7,11 @@ import CreditAmount from 'component/common/credit-amount';
|
|||
type Props = {
|
||||
uri: string,
|
||||
claim: StreamClaim,
|
||||
pendingAmount: string,
|
||||
};
|
||||
|
||||
function FileSubtitle(props: Props) {
|
||||
const { uri, claim } = props;
|
||||
const { uri, claim, pendingAmount } = props;
|
||||
|
||||
return (
|
||||
<div className="media__subtitle--between">
|
||||
|
@ -18,7 +19,7 @@ function FileSubtitle(props: Props) {
|
|||
<span>
|
||||
<CreditAmount
|
||||
badge={false}
|
||||
amount={parseFloat(claim.amount) + parseFloat(claim.meta.support_amount)}
|
||||
amount={parseFloat(claim.amount) + parseFloat(pendingAmount || claim.meta.support_amount)}
|
||||
precision={2}
|
||||
/>
|
||||
{' • ' /* this is bad, but it's quick! */}
|
||||
|
|
29
ui/component/fileValues/index.js
Normal file
29
ui/component/fileValues/index.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
makeSelectClaimForUri,
|
||||
makeSelectContentTypeForUri,
|
||||
makeSelectMetadataForUri,
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectPendingAmountByUri,
|
||||
makeSelectClaimIsMine,
|
||||
} from 'lbry-redux';
|
||||
import { selectUser } from 'lbryinc';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
|
||||
import FileValues from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
contentType: makeSelectContentTypeForUri(props.uri)(state),
|
||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||
metadata: makeSelectMetadataForUri(props.uri)(state),
|
||||
user: selectUser(state),
|
||||
pendingAmount: makeSelectPendingAmountByUri(props.uri)(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FileValues);
|
115
ui/component/fileValues/view.jsx
Normal file
115
ui/component/fileValues/view.jsx
Normal file
|
@ -0,0 +1,115 @@
|
|||
// @flow
|
||||
import React, { Fragment, PureComponent } from 'react';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import Button from 'component/button';
|
||||
import Spinner from 'component/spinner';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import HelpLink from 'component/common/help-link';
|
||||
import CreditAmount from 'component/common/credit-amount';
|
||||
import Card from 'component/common/card';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
claim: StreamClaim,
|
||||
fileInfo: FileListItem,
|
||||
metadata: StreamMetadata,
|
||||
openFolder: string => void,
|
||||
contentType: string,
|
||||
user: ?any,
|
||||
pendingAmount: string,
|
||||
openModal: (id: string, { uri: string }) => void,
|
||||
claimIsMine: boolean,
|
||||
};
|
||||
|
||||
class FileValues extends PureComponent<Props> {
|
||||
render() {
|
||||
const { uri, claim, metadata, openModal, pendingAmount, claimIsMine } = this.props;
|
||||
if (!claim || !metadata) {
|
||||
return <span className="empty">{__('Empty claim or metadata info.')}</span>;
|
||||
}
|
||||
const supportsAmount =
|
||||
claim &&
|
||||
claim.meta &&
|
||||
claim.amount &&
|
||||
claim.meta.effective_amount &&
|
||||
Number(claim.meta.effective_amount) - Number(claim.amount);
|
||||
return (
|
||||
<Fragment>
|
||||
<Card
|
||||
title={__('LBC Details')}
|
||||
defaultExpand={false}
|
||||
actions={
|
||||
<table className="table table--condensed table--fixed table--lbc-details">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> {__('Original Publish Amount')}</td>
|
||||
<td>
|
||||
{claim && claim.amount ? (
|
||||
<CreditAmount badge={false} amount={Number(claim.amount)} precision={2} />
|
||||
) : (
|
||||
<p>...</p>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{' '}
|
||||
{__('Supports and Tips')}
|
||||
<HelpLink href="https://lbry.com/faq/tipping" />
|
||||
</td>
|
||||
<td>
|
||||
{claimIsMine && !pendingAmount && Boolean(supportsAmount) && (
|
||||
<>
|
||||
<Button
|
||||
button="link"
|
||||
className="expandable__button"
|
||||
icon={ICONS.UNLOCK}
|
||||
label={<CreditAmount badge={false} amount={Number(supportsAmount)} precision={2} />}
|
||||
onClick={() => {
|
||||
openModal(MODALS.LIQUIDATE_SUPPORTS, { uri });
|
||||
}}
|
||||
/>{' '}
|
||||
</>
|
||||
)}
|
||||
{(!claimIsMine || (claimIsMine && !pendingAmount && supportsAmount === 0)) && (
|
||||
<CreditAmount badge={false} amount={Number(supportsAmount)} precision={2} />
|
||||
)}
|
||||
|
||||
{claimIsMine && pendingAmount && <Spinner type={'small'} />}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
{__('Total Staked Amount')}
|
||||
<HelpLink href="https://lbry.com/faq/tipping" />
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<CreditAmount badge={false} amount={Number(claim.meta.effective_amount)} precision={2} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{__('Community Choice?')}
|
||||
<HelpLink href="https://lbry.com/faq/naming" />
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
button="link"
|
||||
label={claim.meta.is_controlling ? __('Yes') : __('No')}
|
||||
navigate={`/$/${PAGES.TOP}?name=${claim.name}`}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileValues;
|
|
@ -249,7 +249,6 @@ function PublishFile(props: Props) {
|
|||
|
||||
return (
|
||||
<Card
|
||||
actionIconPadding={false}
|
||||
icon={ICONS.PUBLISH}
|
||||
disabled={disabled || balance === 0}
|
||||
title={
|
||||
|
|
33
ui/component/supportsLiquidate/index.js
Normal file
33
ui/component/supportsLiquidate/index.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectBalance,
|
||||
selectTotalBalance,
|
||||
selectClaimsBalance,
|
||||
selectSupportsBalance,
|
||||
selectTipsBalance,
|
||||
makeSelectMetadataForUri,
|
||||
makeSelectClaimForUri,
|
||||
doSupportAbandonForClaim,
|
||||
doFetchClaimListMine,
|
||||
selectAbandonClaimSupportError,
|
||||
} from 'lbry-redux';
|
||||
import SupportsLiquidate from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
balance: selectBalance(state),
|
||||
totalBalance: selectTotalBalance(state),
|
||||
claimsBalance: selectClaimsBalance(state) || undefined,
|
||||
supportsBalance: selectSupportsBalance(state) || undefined,
|
||||
tipsBalance: selectTipsBalance(state) || undefined,
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
metadata: makeSelectMetadataForUri(props.uri)(state),
|
||||
abandonClaimError: selectAbandonClaimSupportError(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
abandonSupportForClaim: (claimId, type, keep, preview) =>
|
||||
dispatch(doSupportAbandonForClaim(claimId, type, keep, preview)),
|
||||
fetchClaimListMine: () => dispatch(doFetchClaimListMine()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(SupportsLiquidate);
|
167
ui/component/supportsLiquidate/view.jsx
Normal file
167
ui/component/supportsLiquidate/view.jsx
Normal file
|
@ -0,0 +1,167 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import CreditAmount from 'component/common/credit-amount';
|
||||
import Button from 'component/button';
|
||||
import { Form, FormField } from 'component/common/form';
|
||||
import Card from 'component/common/card';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
|
||||
type Props = {
|
||||
balance: number,
|
||||
totalBalance: number,
|
||||
claimsBalance: number,
|
||||
supportsBalance: number,
|
||||
tipsBalance: number,
|
||||
claim: any,
|
||||
metaData: any,
|
||||
handleClose: () => void,
|
||||
abandonSupportForClaim: (string, string, boolean | string, boolean) => any,
|
||||
abandonClaimError: ?string,
|
||||
};
|
||||
|
||||
const SupportsLiquidate = (props: Props) => {
|
||||
const { claim, abandonSupportForClaim, handleClose, abandonClaimError } = props;
|
||||
const [previewBalance, setPreviewBalance] = useState(undefined);
|
||||
const [amount, setAmount] = useState(0);
|
||||
const [error, setError] = useState(false);
|
||||
const initialMessage = '';
|
||||
const [message, setMessage] = useState(initialMessage);
|
||||
const keep =
|
||||
previewBalance && amount && Number(amount) < previewBalance
|
||||
? Number.parseFloat(String(previewBalance - Number(amount))).toFixed(8)
|
||||
: false;
|
||||
const claimId = claim && claim.claim_id;
|
||||
const type = claim.value_type;
|
||||
|
||||
useEffect(() => {
|
||||
if (claimId && abandonSupportForClaim) {
|
||||
abandonSupportForClaim(claimId, type, false, true).then(r => {
|
||||
setPreviewBalance(r.total_input);
|
||||
});
|
||||
}
|
||||
}, [abandonSupportForClaim, claimId, type, setPreviewBalance]);
|
||||
|
||||
function handleSubmit() {
|
||||
abandonSupportForClaim(claimId, type, keep, false).then(r => {
|
||||
if (r) {
|
||||
handleClose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleChange(a) {
|
||||
if (a === undefined || isNaN(Number(a))) {
|
||||
setMessage(__('Amount must be a number'));
|
||||
setError(true);
|
||||
setAmount('');
|
||||
} else if (a === '') {
|
||||
setAmount('');
|
||||
setError(true);
|
||||
setMessage(__('Amount cannot be blank'));
|
||||
} else if (Number(a) > Number(previewBalance)) {
|
||||
setMessage(__('Amount cannot be more than available'));
|
||||
setError(false);
|
||||
} else if (Number(a) === Number(previewBalance)) {
|
||||
setMessage(__(`She's about to close up the library!`));
|
||||
setAmount(a);
|
||||
setError(false);
|
||||
} else if (Number(a) > Number(previewBalance) / 2) {
|
||||
setMessage(__('Your content will do better with more staked on it'));
|
||||
setAmount(a);
|
||||
setError(false);
|
||||
} else if (a === '0') {
|
||||
setMessage(__('Amount cannot be zero'));
|
||||
setAmount(a);
|
||||
setError(true);
|
||||
} else {
|
||||
setMessage(initialMessage);
|
||||
setAmount(a);
|
||||
setError(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
icon={ICONS.UNLOCK}
|
||||
title={__('Unlock Tips')}
|
||||
subtitle={
|
||||
<>
|
||||
<p>
|
||||
{__('You can unlock all or some of this LBC at any time.')}{' '}
|
||||
{__('Keeping it locked improves the trust and discoverability of your content.')}
|
||||
</p>
|
||||
<p>
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
learn_more: <Button button="link" label={__('Learn More')} href="https://lbry.com/faq/tipping" />,
|
||||
}}
|
||||
>
|
||||
It's usually only worth unlocking what you intend to use immediately. %learn_more%
|
||||
</I18nMessage>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
body={
|
||||
<>
|
||||
<div className="section">
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
amount: (
|
||||
<strong>
|
||||
<CreditAmount badge={false} amount={Number(previewBalance)} precision={8} />
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
>
|
||||
%amount% available to unlock
|
||||
</I18nMessage>
|
||||
</div>
|
||||
<div className="section">
|
||||
{previewBalance === 0 && <p>{__('No unlockable tips available')}</p>}
|
||||
{previewBalance === undefined && <p>{__('Loading...')}</p>}
|
||||
{previewBalance && (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<label htmlFor="supports_liquidate_range">{__('Amount to unlock')}</label>
|
||||
<FormField
|
||||
name="supports_liquidate_range"
|
||||
type={'range'}
|
||||
min={0}
|
||||
step={0.01}
|
||||
max={previewBalance} // times 100 to so we're more granular than whole numbers.
|
||||
value={Number(amount) || previewBalance / 4} // by default, set it to 25% of available
|
||||
onChange={e => handleChange(e.target.value)}
|
||||
/>
|
||||
<label className="range__label">
|
||||
<span>0</span>
|
||||
<span>{previewBalance / 2}</span>
|
||||
<span>{previewBalance}</span>
|
||||
</label>
|
||||
<FormField
|
||||
type="text"
|
||||
value={amount || (previewBalance && previewBalance / 4)}
|
||||
helper={message}
|
||||
onChange={e => handleChange(e.target.value)}
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<React.Fragment>
|
||||
{abandonClaimError ? (
|
||||
<>
|
||||
<div className="error-text">{__('%message%', { message: abandonClaimError })}</div>
|
||||
<Button disabled={error} button="primary" onClick={handleClose} label={__('Done')} />
|
||||
</>
|
||||
) : (
|
||||
<Button disabled={error} button="primary" onClick={handleSubmit} label={__('Unlock')} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SupportsLiquidate;
|
|
@ -79,7 +79,6 @@ export default function TagsSelect(props: Props) {
|
|||
return (
|
||||
((showClose && !hasClosed) || !showClose) && (
|
||||
<Card
|
||||
actionIconPadding={false}
|
||||
icon={ICONS.TAG}
|
||||
title={
|
||||
hideHeader ? null : (
|
||||
|
|
|
@ -13,6 +13,7 @@ export const DOWNLOAD = 'Download';
|
|||
export const PUBLISH = 'UploadCloud';
|
||||
export const REMOVE = 'X';
|
||||
export const ADD = 'Plus';
|
||||
export const SUBTRACT = 'Subtract';
|
||||
export const EDIT = 'Edit';
|
||||
export const DELETE = 'Trash';
|
||||
export const REPORT = 'Flag';
|
||||
|
|
|
@ -37,3 +37,4 @@ export const MOBILE_NAVIGATION = 'mobile_navigation';
|
|||
export const SET_REFERRER = 'set_referrer';
|
||||
export const REPOST = 'repost';
|
||||
export const SIGN_OUT = 'sign_out';
|
||||
export const LIQUIDATE_SUPPORTS = 'liquidate_supports';
|
||||
|
|
|
@ -122,7 +122,6 @@ function ModalRepost(props: Props) {
|
|||
return (
|
||||
<Modal isOpen type="card" onAborted={handleCloseModal} onConfirmed={handleCloseModal}>
|
||||
<Card
|
||||
actionIconPadding={false}
|
||||
icon={ICONS.REPOST}
|
||||
title={
|
||||
<span>
|
||||
|
|
|
@ -36,6 +36,7 @@ import ModalMobileNavigation from 'modal/modalMobileNavigation';
|
|||
import ModalSetReferrer from 'modal/modalSetReferrer';
|
||||
import ModalRepost from 'modal/modalRepost';
|
||||
import ModalSignOut from 'modal/modalSignOut';
|
||||
import ModalLiquidateSupports from '../modalSupportsLiquidate';
|
||||
|
||||
type Props = {
|
||||
modal: { id: string, modalProps: {} },
|
||||
|
@ -131,6 +132,8 @@ function ModalRouter(props: Props) {
|
|||
return <ModalRepost {...modalProps} />;
|
||||
case MODALS.SIGN_OUT:
|
||||
return <ModalSignOut {...modalProps} />;
|
||||
case MODALS.LIQUIDATE_SUPPORTS:
|
||||
return <ModalLiquidateSupports {...modalProps} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
15
ui/modal/modalSupportsLiquidate/index.js
Normal file
15
ui/modal/modalSupportsLiquidate/index.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import { doAbandonClaim, selectTransactionItems } from 'lbry-redux';
|
||||
import ModalSupportsLiquidate from './view';
|
||||
|
||||
const select = state => ({
|
||||
transactionItems: selectTransactionItems(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
closeModal: () => dispatch(doHideModal()),
|
||||
abandonClaim: (txid, nout) => dispatch(doAbandonClaim(txid, nout)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(ModalSupportsLiquidate);
|
19
ui/modal/modalSupportsLiquidate/view.jsx
Normal file
19
ui/modal/modalSupportsLiquidate/view.jsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Modal } from 'modal/modal';
|
||||
import SupportsLiquidate from 'component/supportsLiquidate';
|
||||
|
||||
type Props = {
|
||||
closeModal: () => void,
|
||||
uri: string,
|
||||
};
|
||||
|
||||
export default function ModalSupportsLiquidate(props: Props) {
|
||||
const { closeModal, uri } = props;
|
||||
|
||||
return (
|
||||
<Modal isOpen contentLabel={'Unlock Tips'} type="card" confirmButtonLabel="done" onAborted={closeModal}>
|
||||
<SupportsLiquidate uri={uri} handleClose={closeModal} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -10,6 +10,9 @@ import FileRenderInline from 'component/fileRenderInline';
|
|||
import FileRenderDownload from 'component/fileRenderDownload';
|
||||
import Card from 'component/common/card';
|
||||
import FileDetails from 'component/fileDetails';
|
||||
import FileValues from 'component/fileValues';
|
||||
import FileDescription from 'component/fileDescription';
|
||||
|
||||
import RecommendedContent from 'component/recommendedContent';
|
||||
import CommentsList from 'component/commentsList';
|
||||
import CommentCreate from 'component/commentCreate';
|
||||
|
@ -129,7 +132,12 @@ class FilePage extends React.Component<Props> {
|
|||
</div>
|
||||
<div className="section columns">
|
||||
<div className="card-stack">
|
||||
<FileDescription uri={uri} />
|
||||
|
||||
<FileValues uri={uri} />
|
||||
|
||||
<FileDetails uri={uri} />
|
||||
|
||||
<Card
|
||||
title={__('Leave a Comment')}
|
||||
actions={
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
@import 'component/dat-gui';
|
||||
@import 'component/embed-player';
|
||||
@import 'component/expandable';
|
||||
@import 'component/expanding-details';
|
||||
@import 'component/file-properties';
|
||||
@import 'component/file-render';
|
||||
@import 'component/form-field';
|
||||
|
|
|
@ -63,6 +63,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.card__section--flex {
|
||||
@extend .section__flex;
|
||||
padding: var(--spacing-medium) var(--spacing-large);
|
||||
}
|
||||
|
||||
.card__actions--inline {
|
||||
@extend .card__actions;
|
||||
margin-top: 0;
|
||||
|
@ -138,6 +143,10 @@
|
|||
.card__title--between {
|
||||
@extend .card__title;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
& > *:not(:last-child) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card__media--nsfw {
|
||||
|
@ -145,7 +154,7 @@
|
|||
}
|
||||
|
||||
.card__header {
|
||||
margin: var(--spacing-medium) var(--spacing-large);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
|
@ -154,6 +163,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.card__header--between {
|
||||
@extend .card__header;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card__title {
|
||||
font-size: var(--font-title);
|
||||
font-weight: var(--font-weight-light);
|
||||
|
|
|
@ -75,16 +75,27 @@
|
|||
}
|
||||
}
|
||||
|
||||
.table--file-details {
|
||||
margin-top: var(--spacing-large);
|
||||
.table--details {
|
||||
font-size: var(--font-small);
|
||||
}
|
||||
.table--file-details {
|
||||
@extend .table--details;
|
||||
|
||||
td:nth-of-type(1) {
|
||||
width: 30%;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
td:nth-of-type(2) {
|
||||
width: 70%;
|
||||
width: 75%;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.table--lbc-details {
|
||||
@extend .table--details;
|
||||
|
||||
td:nth-of-type(2) {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
15
ui/scss/component/expanding-details.scss
Normal file
15
ui/scss/component/expanding-details.scss
Normal file
|
@ -0,0 +1,15 @@
|
|||
.expanding-details__header {
|
||||
background-color: var(--color-card-background);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-left: var(--spacing-small);
|
||||
}
|
||||
|
||||
.expanding-details {
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.expanding-details__body {
|
||||
border-radius: var(--card-radius);
|
||||
padding: 0 var(--spacing-small);
|
||||
}
|
|
@ -6139,9 +6139,9 @@ lazy-val@^1.0.4:
|
|||
yargs "^13.2.2"
|
||||
zstd-codec "^0.1.1"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#90ba18d0602956016ded63e816734713cfd2023a:
|
||||
lbry-redux@lbryio/lbry-redux#1097a63d44a20b87e443fbaa48f95fe3ea5e3f70:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/90ba18d0602956016ded63e816734713cfd2023a"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/1097a63d44a20b87e443fbaa48f95fe3ea5e3f70"
|
||||
dependencies:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
|
Loading…
Add table
Reference in a new issue