Merge pull request #3933 from lbryio/feat-expandingCards
Tip unlock modal Expanding cards
This commit is contained in:
commit
48d03019d6
29 changed files with 645 additions and 127 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;
|
|
@ -2,11 +2,8 @@
|
|||
import * as PAGES from 'constants/pages';
|
||||
import * as CS from 'constants/claim_search';
|
||||
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 = {
|
||||
|
@ -21,13 +18,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 =
|
||||
|
@ -46,87 +43,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>
|
||||
{claim && claim.meta.reposted > 0 && (
|
||||
<tr>
|
||||
<td>{__('Reposts')}</td>
|
||||
<td>
|
||||
<Button
|
||||
button="link"
|
||||
label={__('View %count% reposts', { count: claim.meta.reposted })}
|
||||
navigate={`/$/${PAGES.DISCOVER}?${CS.REPOSTED_URI_KEY}=${encodeURIComponent(uri)}`}
|
||||
/>
|
||||
</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…
Reference in a new issue