Merge pull request #3933 from lbryio/feat-expandingCards

Tip unlock modal
Expanding cards
This commit is contained in:
jessopb 2020-04-02 09:50:38 -04:00 committed by GitHub
commit 48d03019d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 645 additions and 127 deletions

View file

@ -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",

View file

@ -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"
}

View file

@ -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."

View file

@ -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>
);

View file

@ -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>

View file

@ -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" />

View 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);

View 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;

View file

@ -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>

View file

@ -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);

View file

@ -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! */}

View 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);

View 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;

View file

@ -249,7 +249,6 @@ function PublishFile(props: Props) {
return (
<Card
actionIconPadding={false}
icon={ICONS.PUBLISH}
disabled={disabled || balance === 0}
title={

View 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);

View 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;

View file

@ -79,7 +79,6 @@ export default function TagsSelect(props: Props) {
return (
((showClose && !hasClosed) || !showClose) && (
<Card
actionIconPadding={false}
icon={ICONS.TAG}
title={
hideHeader ? null : (

View file

@ -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';

View file

@ -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';

View file

@ -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>

View file

@ -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;
}

View 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);

View 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>
);
}

View file

@ -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={

View file

@ -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';

View file

@ -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);

View file

@ -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;
}
}

View 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);
}

View file

@ -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"