commit
dc2f7f172a
46 changed files with 831 additions and 689 deletions
|
@ -20,5 +20,6 @@ module.name_mapper='^page\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/page\1'
|
||||||
module.name_mapper='^lbry\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/lbry\1'
|
module.name_mapper='^lbry\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/lbry\1'
|
||||||
module.name_mapper='^rewards\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/rewards\1'
|
module.name_mapper='^rewards\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/rewards\1'
|
||||||
module.name_mapper='^modal\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/modal\1'
|
module.name_mapper='^modal\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/modal\1'
|
||||||
|
module.name_mapper='^app\(.*\)$' -> '<PROJECT_ROOT>/src/renderer/app\1'
|
||||||
|
|
||||||
[strict]
|
[strict]
|
||||||
|
|
3
flow-typed/react-markdown.js
vendored
Normal file
3
flow-typed/react-markdown.js
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
declare module 'react-markdown' {
|
||||||
|
declare module.exports: any;
|
||||||
|
}
|
3
flow-typed/render-media.js
vendored
Normal file
3
flow-typed/render-media.js
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
declare module 'render-media' {
|
||||||
|
declare module.exports: any;
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ class App extends React.PureComponent<Props> {
|
||||||
const { currentStackIndex: prevStackIndex } = prevProps;
|
const { currentStackIndex: prevStackIndex } = prevProps;
|
||||||
const { currentStackIndex, currentPageAttributes } = this.props;
|
const { currentStackIndex, currentPageAttributes } = this.props;
|
||||||
|
|
||||||
if (this.mainContent && currentStackIndex !== prevStackIndex) {
|
if (this.mainContent && currentStackIndex !== prevStackIndex && currentPageAttributes) {
|
||||||
this.mainContent.scrollTop = currentPageAttributes.scrollY || 0;
|
this.mainContent.scrollTop = currentPageAttributes.scrollY || 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,62 +43,4 @@ export class CurrencySymbol extends React.PureComponent {
|
||||||
return <span>LBC</span>;
|
return <span>LBC</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Thumbnail extends React.PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
src: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleError() {
|
|
||||||
if (this.state.imageUrl != this._defaultImageUri) {
|
|
||||||
this.setState({
|
|
||||||
imageUri: this._defaultImageUri,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this._defaultImageUri = lbry.imagePath('default-thumb.svg');
|
|
||||||
this._maxLoadTime = 10000;
|
|
||||||
this._isMounted = false;
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
imageUri: this.props.src || this._defaultImageUri,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this._isMounted = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this._isMounted && !this.refs.img.complete) {
|
|
||||||
this.setState({
|
|
||||||
imageUri: this._defaultImageUri,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, this._maxLoadTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this._isMounted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const className = this.props.className ? this.props.className : '',
|
|
||||||
otherProps = Object.assign({}, this.props);
|
|
||||||
delete otherProps.className;
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
ref="img"
|
|
||||||
onError={() => {
|
|
||||||
this.handleError();
|
|
||||||
}}
|
|
||||||
{...otherProps}
|
|
||||||
className={className}
|
|
||||||
src={this.state.imageUri}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
|
@ -2,15 +2,23 @@
|
||||||
/* eslint-disable react/no-multi-comp */
|
/* eslint-disable react/no-multi-comp */
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import Button from 'component/link';
|
import Button from 'component/link';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
type FormRowProps = {
|
type FormRowProps = {
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
|
padded?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FormRow = (props: FormRowProps) => {
|
export class FormRow extends React.PureComponent<FormRowProps> {
|
||||||
const { children } = props;
|
static defaultProps = {
|
||||||
return <div className="form-row">{children}</div>;
|
padded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children, padded } = this.props;
|
||||||
|
return <div className={classnames('form-row', { 'form-row--padded': padded })}>{children}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type FormFieldProps = {
|
type FormFieldProps = {
|
||||||
render: () => React.Node,
|
render: () => React.Node,
|
||||||
|
@ -18,18 +26,18 @@ type FormFieldProps = {
|
||||||
prefix?: string,
|
prefix?: string,
|
||||||
postfix?: string,
|
postfix?: string,
|
||||||
error?: string | boolean,
|
error?: string | boolean,
|
||||||
|
helper?: string | React.Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FormField extends React.PureComponent<FormFieldProps> {
|
export class FormField extends React.PureComponent<FormFieldProps> {
|
||||||
render() {
|
render() {
|
||||||
const { render, label, prefix, postfix, error } = this.props;
|
const { render, label, prefix, postfix, error, helper } = this.props;
|
||||||
|
/* eslint-disable jsx-a11y/label-has-for */
|
||||||
|
// Will come back to this on the settings page
|
||||||
|
// Need htmlFor on the label
|
||||||
return (
|
return (
|
||||||
<div className="form-field">
|
<div className="form-field">
|
||||||
{label && (
|
{label && <label className="form-field__label">{label}</label>}
|
||||||
<label className="form-field__label">
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
<div className="form-field__wrapper">
|
<div className="form-field__wrapper">
|
||||||
{prefix && <span className="form-field__prefix">{prefix}</span>}
|
{prefix && <span className="form-field__prefix">{prefix}</span>}
|
||||||
{render()}
|
{render()}
|
||||||
|
@ -40,8 +48,10 @@ export class FormField extends React.PureComponent<FormFieldProps> {
|
||||||
{typeof error === 'string' ? error : __('There was an error')}
|
{typeof error === 'string' ? error : __('There was an error')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{helper && <div className="form-field__help">{helper}</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
/* eslint-enable jsx-a11y/label-has-for */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
export default ({ dark, className }) => (
|
const Spinner = () => (
|
||||||
<div
|
<div className="spinner">
|
||||||
className={classnames(
|
<div className="rect1" />
|
||||||
'spinner',
|
<div className="rect2" />
|
||||||
{
|
<div className="rect3" />
|
||||||
'spinner--dark': dark,
|
<div className="rect4" />
|
||||||
},
|
<div className="rect5" />
|
||||||
className
|
</div>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export default Spinner;
|
||||||
|
|
23
src/renderer/component/common/thumbnail.jsx
Normal file
23
src/renderer/component/common/thumbnail.jsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
src: string,
|
||||||
|
shouldObscure: boolean,
|
||||||
|
className: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Thumbnail = (props: Props) => {
|
||||||
|
const { className, src, shouldObscure } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
alt={__('Image thumbnail')}
|
||||||
|
className={classnames({ 'card--obscured': shouldObscure }, className)}
|
||||||
|
src={src}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Thumbnail;
|
|
@ -1,48 +1,53 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'component/link';
|
import Button from 'component/link';
|
||||||
import FileDownloadLink from 'component/fileDownloadLink';
|
import FileDownloadLink from 'component/fileDownloadLink';
|
||||||
import * as modals from 'constants/modal_types';
|
import * as modals from 'constants/modal_types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
class FileActions extends React.PureComponent {
|
type FileInfo = {
|
||||||
|
claim_id: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
uri: string,
|
||||||
|
openModal: (string, any) => void,
|
||||||
|
claimIsMine: boolean,
|
||||||
|
fileInfo: FileInfo,
|
||||||
|
vertical?: boolean, // should the buttons be stacked vertically?
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileActions extends React.PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { fileInfo, uri, openModal, claimIsMine } = this.props;
|
const { fileInfo, uri, openModal, claimIsMine, vertical } = this.props;
|
||||||
|
|
||||||
const claimId = fileInfo ? fileInfo.claim_id : null,
|
const claimId = fileInfo ? fileInfo.claim_id : '';
|
||||||
showDelete = fileInfo && Object.keys(fileInfo).length > 0;
|
// showDelete = fileInfo && Object.keys(fileInfo).length > 0;
|
||||||
|
|
||||||
|
const showDelete = true;
|
||||||
return (
|
return (
|
||||||
<section className="card__actions">
|
<section className={classnames('card__actions', { 'card__actions--vertical': vertical })}>
|
||||||
<FileDownloadLink uri={uri} />
|
<FileDownloadLink uri={uri} />
|
||||||
{showDelete && (
|
{showDelete && (
|
||||||
<Link
|
<Button
|
||||||
button="text"
|
alt
|
||||||
icon="icon-trash"
|
icon="Trash"
|
||||||
label={__('Remove')}
|
label={__('Remove')}
|
||||||
className="no-underline"
|
|
||||||
onClick={() => openModal(modals.CONFIRM_FILE_REMOVE, { uri })}
|
onClick={() => openModal(modals.CONFIRM_FILE_REMOVE, { uri })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!claimIsMine && (
|
{!claimIsMine && (
|
||||||
<Link
|
<Button
|
||||||
button="text"
|
alt
|
||||||
icon="icon-flag"
|
icon="Flag"
|
||||||
href={`https://lbry.io/dmca?claim_id=${claimId}`}
|
href={`https://lbry.io/dmca?claim_id=${claimId}`}
|
||||||
className="no-underline"
|
label={__('Report')}
|
||||||
label={__('report')}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Link
|
|
||||||
button="primary"
|
|
||||||
icon="icon-gift"
|
|
||||||
label={__('Support')}
|
|
||||||
navigate="/show"
|
|
||||||
className="card__action--right"
|
|
||||||
navigateParams={{ uri, tab: 'tip' }}
|
|
||||||
/>
|
|
||||||
{claimIsMine && (
|
{claimIsMine && (
|
||||||
<Link
|
<Button
|
||||||
button="alt"
|
icon="Edit3"
|
||||||
icon="icon-edit"
|
|
||||||
label={__('Edit')}
|
label={__('Edit')}
|
||||||
navigate="/publish"
|
navigate="/publish"
|
||||||
className="card__action--right"
|
className="card__action--right"
|
||||||
|
|
|
@ -1,70 +1,73 @@
|
||||||
import React from 'react';
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import lbry from 'lbry.js';
|
import lbry from 'lbry';
|
||||||
import FileActions from 'component/fileActions';
|
import Button from 'component/link';
|
||||||
import Link from 'component/link';
|
import path from 'path';
|
||||||
import DateTime from 'component/dateTime';
|
|
||||||
|
|
||||||
const path = require('path');
|
type Props = {
|
||||||
|
claim: {},
|
||||||
|
fileInfo: {
|
||||||
|
download_path: string,
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
description: string,
|
||||||
|
language: string,
|
||||||
|
license: string,
|
||||||
|
},
|
||||||
|
openFolder: string => void,
|
||||||
|
contentType: string,
|
||||||
|
};
|
||||||
|
|
||||||
class FileDetails extends React.PureComponent {
|
const FileDetails = (props: Props) => {
|
||||||
render() {
|
const { claim, contentType, fileInfo, metadata, openFolder } = props;
|
||||||
const { claim, contentType, fileInfo, metadata, openFolder, uri } = this.props;
|
|
||||||
|
|
||||||
if (!claim || !metadata) {
|
|
||||||
return (
|
|
||||||
<div className="card__content">
|
|
||||||
<span className="empty">{__('Empty claim or metadata info.')}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { description, language, license } = metadata;
|
|
||||||
const mediaType = lbry.getMediaType(contentType);
|
|
||||||
|
|
||||||
const downloadPath = fileInfo ? path.normalize(fileInfo.download_path) : null;
|
|
||||||
|
|
||||||
|
if (!claim || !metadata) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="card__content">
|
||||||
<div className="divider__horizontal" />
|
<span className="empty">{__('Empty claim or metadata info.')}</span>
|
||||||
<FileActions uri={uri} />
|
</div>
|
||||||
<div className="divider__horizontal" />
|
);
|
||||||
<div className="card__content card__subtext card__subtext--allow-newlines">
|
}
|
||||||
|
|
||||||
|
const { description, language, license } = metadata;
|
||||||
|
const mediaType = lbry.getMediaType(contentType);
|
||||||
|
|
||||||
|
const downloadPath = fileInfo ? path.normalize(fileInfo.download_path) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="card__content">
|
||||||
|
<div className="card__subtext-title">About</div>
|
||||||
|
<div className="card__subtext">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
source={description || ''}
|
source={description || ''}
|
||||||
escapeHtml
|
escapeHtml
|
||||||
disallowedTypes={['Heading', 'HtmlInline', 'HtmlBlock']}
|
disallowedTypes={['Heading', 'HtmlInline', 'HtmlBlock']}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="card__content">
|
<div className="card__subtext-title">Info</div>
|
||||||
<table className="table-standard table-stretch">
|
<div className="card__subtext">
|
||||||
<tbody>
|
<dl>
|
||||||
<tr>
|
<dt>{__('Content-Type')}</dt>
|
||||||
<td>{__('Content-Type')}</td>
|
<dd>{mediaType}</dd>
|
||||||
<td>{mediaType}</td>
|
<dt>{__('Language')}</dt>
|
||||||
</tr>
|
<dd>{language}</dd>
|
||||||
<tr>
|
<dt>{__('License')}</dt>
|
||||||
<td>{__('Language')}</td>
|
<dd>{license}</dd>
|
||||||
<td>{language}</td>
|
{downloadPath && (
|
||||||
</tr>
|
<React.Fragment>
|
||||||
<tr>
|
<dt>{__('Downloaded to')}</dt>
|
||||||
<td>{__('License')}</td>
|
<dd>
|
||||||
<td>{license}</td>
|
<Button fakeLink onClick={() => openFolder(downloadPath)} label={downloadPath} />
|
||||||
</tr>
|
</dd>
|
||||||
{downloadPath && (
|
</React.Fragment>
|
||||||
<tr>
|
)}
|
||||||
<td>{__('Downloaded to')}</td>
|
</dl>
|
||||||
<td>
|
|
||||||
<Link onClick={() => openFolder(downloadPath)}>{downloadPath}</Link>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default FileDetails;
|
export default FileDetails;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// I'll come back to this
|
||||||
|
/* eslint-disable */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BusyMessage } from 'component/common';
|
import { BusyMessage } from 'component/common';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
|
@ -81,9 +83,8 @@ class FileDownloadLink extends React.PureComponent {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
button="text"
|
|
||||||
label={__('Download')}
|
label={__('Download')}
|
||||||
icon="icon-download"
|
icon="DownloadCloud"
|
||||||
className="no-underline"
|
className="no-underline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
purchaseUri(uri);
|
purchaseUri(uri);
|
||||||
|
@ -91,15 +92,7 @@ class FileDownloadLink extends React.PureComponent {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (fileInfo && fileInfo.download_path) {
|
} else if (fileInfo && fileInfo.download_path) {
|
||||||
return (
|
return <Link label={__('Open')} icon="BookOpen" onClick={() => openFile()} />;
|
||||||
<Link
|
|
||||||
label={__('Open')}
|
|
||||||
button="text"
|
|
||||||
icon="icon-external-link-square"
|
|
||||||
className="no-underline"
|
|
||||||
onClick={() => openFile()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -107,3 +100,4 @@ class FileDownloadLink extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FileDownloadLink;
|
export default FileDownloadLink;
|
||||||
|
/* eslint-enable */
|
||||||
|
|
|
@ -38,7 +38,7 @@ class FilePrice extends React.PureComponent<Props> {
|
||||||
const isEstimate = costInfo ? !costInfo.includesData : false;
|
const isEstimate = costInfo ? !costInfo.includesData : false;
|
||||||
|
|
||||||
if (!costInfo) {
|
if (!costInfo) {
|
||||||
return <span className="credit-amount">???</span>;
|
return <span className="credit-amount">PRICE</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -25,6 +25,7 @@ type Props = {
|
||||||
noStyle: ?boolean,
|
noStyle: ?boolean,
|
||||||
noUnderline: ?boolean,
|
noUnderline: ?boolean,
|
||||||
description: ?string,
|
description: ?string,
|
||||||
|
secondary: ?boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Button = (props: Props) => {
|
const Button = (props: Props) => {
|
||||||
|
@ -49,6 +50,7 @@ const Button = (props: Props) => {
|
||||||
description,
|
description,
|
||||||
noStyle,
|
noStyle,
|
||||||
noUnderline,
|
noUnderline,
|
||||||
|
secondary,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
@ -58,7 +60,8 @@ const Button = (props: Props) => {
|
||||||
? 'btn--no-style'
|
? 'btn--no-style'
|
||||||
: {
|
: {
|
||||||
'btn--link': fakeLink,
|
'btn--link': fakeLink,
|
||||||
'btn--primary': !alt && !fakeLink,
|
'btn--primary': !alt && !fakeLink && !secondary, // default to primary
|
||||||
|
'btn--secondary': secondary,
|
||||||
'btn--alt': alt,
|
'btn--alt': alt,
|
||||||
'btn--inverse': inverse,
|
'btn--inverse': inverse,
|
||||||
'btn--disabled': disabled,
|
'btn--disabled': disabled,
|
||||||
|
|
|
@ -1,7 +1,23 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'component/link';
|
import Button from 'component/link';
|
||||||
|
import type { Subscription } from 'redux/reducers/subscriptions';
|
||||||
|
|
||||||
export default ({ channelName, uri, subscriptions, doChannelSubscribe, doChannelUnsubscribe }) => {
|
type SubscribtionArgs = {
|
||||||
|
channelName: string,
|
||||||
|
uri: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channelName: ?string,
|
||||||
|
uri: ?string,
|
||||||
|
subscriptions: Array<Subscription>,
|
||||||
|
doChannelSubscribe: ({ channelName: string, uri: string }) => void,
|
||||||
|
doChannelUnsubscribe: SubscribtionArgs => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
const { channelName, uri, subscriptions, doChannelSubscribe, doChannelUnsubscribe } = props;
|
||||||
const isSubscribed =
|
const isSubscribed =
|
||||||
subscriptions.map(subscription => subscription.channelName).indexOf(channelName) !== -1;
|
subscriptions.map(subscription => subscription.channelName).indexOf(channelName) !== -1;
|
||||||
|
|
||||||
|
@ -10,18 +26,16 @@ export default ({ channelName, uri, subscriptions, doChannelSubscribe, doChannel
|
||||||
const subscriptionLabel = isSubscribed ? __('Unsubscribe') : __('Subscribe');
|
const subscriptionLabel = isSubscribed ? __('Unsubscribe') : __('Subscribe');
|
||||||
|
|
||||||
return channelName && uri ? (
|
return channelName && uri ? (
|
||||||
<div className="card__actions">
|
<Button
|
||||||
<Link
|
iconRight="AtSign"
|
||||||
iconRight={isSubscribed ? '' : 'at'}
|
alt={isSubscribed}
|
||||||
button={isSubscribed ? 'alt' : 'primary'}
|
label={subscriptionLabel}
|
||||||
label={subscriptionLabel}
|
onClick={() =>
|
||||||
onClick={() =>
|
subscriptionHandler({
|
||||||
subscriptionHandler({
|
channelName,
|
||||||
channelName,
|
uri,
|
||||||
uri,
|
})
|
||||||
})
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,27 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Spinner from 'component/common/spinner';
|
import Spinner from 'component/common/spinner';
|
||||||
|
|
||||||
const LoadingScreen = ({ status, spinner = true }) => (
|
type Props = {
|
||||||
<div className="video__loading-screen">
|
spinner: boolean,
|
||||||
<div>
|
status: string,
|
||||||
{spinner && <Spinner />}
|
};
|
||||||
|
|
||||||
<div className="video__loading-status">{status}</div>
|
class LoadingScreen extends React.PureComponent<Props> {
|
||||||
</div>
|
static defaultProps = {
|
||||||
</div>
|
spinner: true,
|
||||||
);
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { status, spinner } = this.props;
|
||||||
|
return (
|
||||||
|
<div className="video__loading-screen">
|
||||||
|
{spinner && <Spinner />}
|
||||||
|
|
||||||
|
<span className="video__loading-text">{status}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default LoadingScreen;
|
export default LoadingScreen;
|
||||||
|
|
|
@ -1,47 +1,42 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'component/link';
|
import Button from 'component/link';
|
||||||
|
|
||||||
class VideoPlayButton extends React.PureComponent {
|
type Props = {
|
||||||
componentDidMount() {
|
play: string => void,
|
||||||
this.keyDownListener = this.onKeyDown.bind(this);
|
isLoading: boolean,
|
||||||
document.addEventListener('keydown', this.keyDownListener);
|
uri: string,
|
||||||
}
|
mediaType: string,
|
||||||
|
fileInfo: ?{},
|
||||||
componentWillUnmount() {
|
};
|
||||||
document.removeEventListener('keydown', this.keyDownListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
onKeyDown(event) {
|
|
||||||
if (event.target.tagName.toLowerCase() !== 'input' && event.code === 'Space') {
|
|
||||||
event.preventDefault();
|
|
||||||
this.watch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
class VideoPlayButton extends React.PureComponent<Props> {
|
||||||
watch() {
|
watch() {
|
||||||
this.props.play(this.props.uri);
|
this.props.play(this.props.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { button, label, isLoading, fileInfo, mediaType } = this.props;
|
const { isLoading, fileInfo, mediaType } = this.props;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
title={
|
TODO: Add title back to button
|
||||||
isLoading ? "Video is Loading" :
|
title={
|
||||||
!costInfo ? "Waiting on cost info..." :
|
isLoading ? "Video is Loading" :
|
||||||
fileInfo === undefined ? "Waiting on file info..." : ""
|
!costInfo ? "Waiting on cost info..." :
|
||||||
}
|
fileInfo === undefined ? "Waiting on file info..." : ""
|
||||||
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const disabled = isLoading || fileInfo === undefined;
|
const disabled = isLoading || fileInfo === undefined;
|
||||||
const icon = ['audio', 'video'].indexOf(mediaType) !== -1 ? 'icon-play' : 'icon-folder-o';
|
const doesPlayback = ['audio', 'video'].indexOf(mediaType) !== -1;
|
||||||
|
const icon = doesPlayback ? 'Play' : 'Folder';
|
||||||
|
const label = doesPlayback ? 'Play' : 'View';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Button
|
||||||
button={button || null}
|
secondary
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
label={label || ''}
|
label={label}
|
||||||
className="video__play-button"
|
|
||||||
icon={icon}
|
icon={icon}
|
||||||
onClick={() => this.watch()}
|
onClick={() => this.watch()}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
const { remote } = require('electron');
|
// This file has a lot going on.
|
||||||
|
// I will add flow when I come back to fix the issue where the video player doesn't scale
|
||||||
|
/* eslint-disable */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { remote } from 'electron';
|
||||||
import { Thumbnail } from 'component/common';
|
import { Thumbnail } from 'component/common';
|
||||||
import player from 'render-media';
|
import player from 'render-media';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
@ -21,9 +23,9 @@ class VideoPlayer extends React.PureComponent {
|
||||||
this.togglePlayListener = this.togglePlay.bind(this);
|
this.togglePlayListener = this.togglePlay.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(next) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const el = this.refs.media.children[0];
|
const el = this.refs.media.children[0];
|
||||||
if (!this.props.paused && next.paused && !el.paused) el.pause();
|
if (!this.props.paused && nextProps.paused && !el.paused) el.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -171,7 +173,7 @@ class VideoPlayer extends React.PureComponent {
|
||||||
const needsMetadata = this.playableType();
|
const needsMetadata = this.playableType();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<React.Fragment>
|
||||||
{['audio', 'application'].indexOf(mediaType) !== -1 &&
|
{['audio', 'application'].indexOf(mediaType) !== -1 &&
|
||||||
(!this.playableType() || hasMetadata) &&
|
(!this.playableType() || hasMetadata) &&
|
||||||
!unplayable && <Thumbnail src={poster} className="video-embedded" />}
|
!unplayable && <Thumbnail src={poster} className="video-embedded" />}
|
||||||
|
@ -180,9 +182,10 @@ class VideoPlayer extends React.PureComponent {
|
||||||
!unplayable && <LoadingScreen status={noMetadataMessage} />}
|
!unplayable && <LoadingScreen status={noMetadataMessage} />}
|
||||||
{unplayable && <LoadingScreen status={unplayableMessage} spinner={false} />}
|
{unplayable && <LoadingScreen status={unplayableMessage} spinner={false} />}
|
||||||
<div ref="media" className="media" />
|
<div ref="media" className="media" />
|
||||||
</div>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VideoPlayer;
|
export default VideoPlayer;
|
||||||
|
/* eslint-disable */
|
||||||
|
|
|
@ -1,23 +1,48 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from 'lbry';
|
import lbry from 'lbry';
|
||||||
|
import classnames from 'classnames';
|
||||||
import VideoPlayer from './internal/player';
|
import VideoPlayer from './internal/player';
|
||||||
import VideoPlayButton from './internal/play-button';
|
import VideoPlayButton from './internal/play-button';
|
||||||
import LoadingScreen from './internal/loading-screen';
|
import LoadingScreen from './internal/loading-screen';
|
||||||
import NsfwOverlay from 'component/nsfwOverlay';
|
|
||||||
|
|
||||||
class Video extends React.PureComponent {
|
type Props = {
|
||||||
constructor(props) {
|
cancelPlay: () => void,
|
||||||
super(props);
|
fileInfo: {
|
||||||
this.state = {
|
outpoint: string,
|
||||||
showNsfwHelp: false,
|
file_name: string,
|
||||||
};
|
written_bytes: number,
|
||||||
}
|
download_path: string,
|
||||||
|
completed: boolean,
|
||||||
|
},
|
||||||
|
metadata: ?{
|
||||||
|
nsfw: boolean,
|
||||||
|
thumbnail: string,
|
||||||
|
},
|
||||||
|
isLoading: boolean,
|
||||||
|
isDownloading: boolean,
|
||||||
|
playingUri: ?string,
|
||||||
|
contentType: string,
|
||||||
|
changeVolume: number => void,
|
||||||
|
volume: number,
|
||||||
|
claim: {},
|
||||||
|
uri: string,
|
||||||
|
doPlay: () => void,
|
||||||
|
doPause: () => void,
|
||||||
|
savePosition: (string, number) => void,
|
||||||
|
mediaPaused: boolean,
|
||||||
|
mediaPosition: ?number,
|
||||||
|
className: ?string,
|
||||||
|
obscureNsfw: boolean,
|
||||||
|
play: string => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Video extends React.PureComponent<Props> {
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.cancelPlay();
|
this.props.cancelPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
isMediaSame(nextProps) {
|
isMediaSame(nextProps: Props) {
|
||||||
return (
|
return (
|
||||||
this.props.fileInfo &&
|
this.props.fileInfo &&
|
||||||
nextProps.fileInfo &&
|
nextProps.fileInfo &&
|
||||||
|
@ -25,22 +50,6 @@ class Video extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseOver() {
|
|
||||||
if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) {
|
|
||||||
this.setState({
|
|
||||||
showNsfwHelp: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseOut() {
|
|
||||||
if (this.state.showNsfwHelp) {
|
|
||||||
this.setState({
|
|
||||||
showNsfwHelp: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
metadata,
|
metadata,
|
||||||
|
@ -58,11 +67,14 @@ class Video extends React.PureComponent {
|
||||||
savePosition,
|
savePosition,
|
||||||
mediaPaused,
|
mediaPaused,
|
||||||
mediaPosition,
|
mediaPosition,
|
||||||
|
className,
|
||||||
|
obscureNsfw,
|
||||||
|
play,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const isPlaying = playingUri === uri;
|
const isPlaying = playingUri === uri;
|
||||||
const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0;
|
const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0;
|
||||||
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
const shouldObscureNsfw = obscureNsfw && metadata && metadata.nsfw;
|
||||||
const mediaType = lbry.getMediaType(contentType, fileInfo && fileInfo.file_name);
|
const mediaType = lbry.getMediaType(contentType, fileInfo && fileInfo.file_name);
|
||||||
|
|
||||||
let loadStatusMessage = '';
|
let loadStatusMessage = '';
|
||||||
|
@ -77,23 +89,10 @@ class Video extends React.PureComponent {
|
||||||
loadStatusMessage = __('Downloading stream... not long left now!');
|
loadStatusMessage = __('Downloading stream... not long left now!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const klasses = [];
|
const poster = metadata && metadata.thumbnail;
|
||||||
klasses.push(obscureNsfw ? 'video--obscured ' : '');
|
|
||||||
if (isLoading || isDownloading) klasses.push('video-embedded', 'video');
|
|
||||||
if (mediaType === 'video') {
|
|
||||||
klasses.push('video-embedded', 'video');
|
|
||||||
klasses.push(isPlaying ? 'video--active' : 'video--hidden');
|
|
||||||
} else if (mediaType === 'application') {
|
|
||||||
klasses.push('video-embedded');
|
|
||||||
} else if (!isPlaying) klasses.push('video-embedded');
|
|
||||||
const poster = metadata.thumbnail;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={classnames('video', {}, className)}>
|
||||||
className={klasses.join(' ')}
|
|
||||||
onMouseEnter={this.handleMouseOver.bind(this)}
|
|
||||||
onMouseLeave={this.handleMouseOut.bind(this)}
|
|
||||||
>
|
|
||||||
{isPlaying &&
|
{isPlaying &&
|
||||||
(!isReadyToPlay ? (
|
(!isReadyToPlay ? (
|
||||||
<LoadingScreen status={loadStatusMessage} />
|
<LoadingScreen status={loadStatusMessage} />
|
||||||
|
@ -117,11 +116,19 @@ class Video extends React.PureComponent {
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{!isPlaying && (
|
{!isPlaying && (
|
||||||
<div className="video__cover" style={{ backgroundImage: `url("${metadata.thumbnail}")` }}>
|
<div
|
||||||
<VideoPlayButton {...this.props} mediaType={mediaType} />
|
className={classnames('video__cover', { 'card--obscured': shouldObscureNsfw })}
|
||||||
|
style={!shouldObscureNsfw && poster ? { backgroundImage: `url("${poster}")` } : {}}
|
||||||
|
>
|
||||||
|
<VideoPlayButton
|
||||||
|
play={play}
|
||||||
|
fileInfo={fileInfo}
|
||||||
|
uri={uri}
|
||||||
|
isLoading={isLoading}
|
||||||
|
mediaType={mediaType}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{this.state.showNsfwHelp && <NsfwOverlay />}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +1,92 @@
|
||||||
// I'll come back to this
|
// @flow
|
||||||
/* eslint-disable */
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'component/link';
|
import Button from 'component/link';
|
||||||
import { FormRow } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import UriIndicator from 'component/uriIndicator';
|
import UriIndicator from 'component/uriIndicator';
|
||||||
|
|
||||||
class WalletSendTip extends React.PureComponent {
|
type Props = {
|
||||||
constructor(props) {
|
claim_id: string,
|
||||||
|
uri: string,
|
||||||
|
title: string,
|
||||||
|
errorMessage: string,
|
||||||
|
isPending: boolean,
|
||||||
|
sendSupport: (number, string, string) => void,
|
||||||
|
onCancel: () => void,
|
||||||
|
sendTipCallback?: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
amount: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
class WalletSendTip extends React.PureComponent<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
amount: 0.0,
|
amount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(this: any).handleSendButtonClicked = this.handleSendButtonClicked.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSendButtonClicked() {
|
handleSendButtonClicked() {
|
||||||
const { claim_id, uri } = this.props;
|
const { claim_id: claimId, uri, sendSupport, sendTipCallback } = this.props;
|
||||||
const amount = this.state.amount;
|
const { amount } = this.state;
|
||||||
this.props.sendSupport(amount, claim_id, uri);
|
|
||||||
|
sendSupport(amount, claimId, uri);
|
||||||
|
|
||||||
|
// ex: close modal
|
||||||
|
if (sendTipCallback) {
|
||||||
|
sendTipCallback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSupportPriceChange(event) {
|
handleSupportPriceChange(event: SyntheticInputEvent<*>) {
|
||||||
this.setState({
|
this.setState({
|
||||||
amount: Number(event.target.value),
|
amount: Number(event.target.value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { errorMessage, isPending, title, uri } = this.props;
|
const { errorMessage, isPending, title, uri, onCancel } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="card__title-primary">
|
<div className="card__title-primary">
|
||||||
<h1>
|
<h1>
|
||||||
{__('Support')} <UriIndicator uri={uri} />
|
{__('Send a tip to')} <UriIndicator uri={uri} />
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<FormRow
|
<FormField
|
||||||
label={__('Amount')}
|
label={__('Amount')}
|
||||||
postfix={__('LBC')}
|
postfix={__('LBC')}
|
||||||
min="0"
|
error={errorMessage}
|
||||||
step="any"
|
|
||||||
type="number"
|
|
||||||
errorMessage={errorMessage}
|
|
||||||
helper={
|
helper={
|
||||||
<span>
|
<span>
|
||||||
{`${__('This will appear as a tip for "%s" located at %s.', title, uri)} `}
|
{__(`This will appear as a tip for ${title} located at ${uri}.`)}
|
||||||
<Link label={__('Learn more')} href="https://lbry.io/faq/tipping" />
|
{" "}
|
||||||
|
<Button label={__('Learn more')} fakeLink href="https://lbry.io/faq/tipping" />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
placeholder="1.00"
|
render={() => (
|
||||||
onChange={event => this.handleSupportPriceChange(event)}
|
<input
|
||||||
|
min="0"
|
||||||
|
step="any"
|
||||||
|
type="number"
|
||||||
|
placeholder="1.00"
|
||||||
|
onChange={event => this.handleSupportPriceChange(event)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="form-row-submit">
|
<div className="card__actions">
|
||||||
<Link
|
<Button
|
||||||
label={__('Send')}
|
label={__('Send')}
|
||||||
button="primary"
|
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
onClick={this.handleSendButtonClicked.bind(this)}
|
onClick={this.handleSendButtonClicked}
|
||||||
/>
|
/>
|
||||||
<Link label={__('Cancel')} button="alt" navigate="/show" navigateParams={{ uri }} />
|
<Button alt label={__('Cancel')} onClick={onCancel} navigateParams={{ uri }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,4 +95,3 @@ class WalletSendTip extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WalletSendTip;
|
export default WalletSendTip;
|
||||||
/* eslint-enable */
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ class WunderBar extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(e: SyntheticInputEvent<*>) {
|
handleChange(e: SyntheticInputEvent<*>) {
|
||||||
const { updateSearchQuery, getSearchSuggestions } = this.props;
|
const { updateSearchQuery } = this.props;
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
|
|
||||||
updateSearchQuery(value);
|
updateSearchQuery(value);
|
||||||
|
@ -74,7 +74,6 @@ class WunderBar extends React.PureComponent<Props> {
|
||||||
|
|
||||||
input: ?HTMLInputElement;
|
input: ?HTMLInputElement;
|
||||||
throttledGetSearchSuggestions: string => void;
|
throttledGetSearchSuggestions: string => void;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { searchQuery, isActive, address, suggestions } = this.props;
|
const { searchQuery, isActive, address, suggestions } = this.props;
|
||||||
|
|
||||||
|
|
|
@ -14,3 +14,4 @@ export const TRANSACTION_FAILED = 'transaction_failed';
|
||||||
export const REWARD_APPROVAL_REQUIRED = 'reward_approval_required';
|
export const REWARD_APPROVAL_REQUIRED = 'reward_approval_required';
|
||||||
export const AFFIRM_PURCHASE = 'affirm_purchase';
|
export const AFFIRM_PURCHASE = 'affirm_purchase';
|
||||||
export const CONFIRM_CLAIM_REVOKE = 'confirmClaimRevoke';
|
export const CONFIRM_CLAIM_REVOKE = 'confirmClaimRevoke';
|
||||||
|
export const SEND_TIP = 'sendTip';
|
||||||
|
|
|
@ -1,26 +1,36 @@
|
||||||
import React from 'react';
|
// @flow
|
||||||
import PropTypes from 'prop-types';
|
/* eslint-disable react/no-multi-comp */
|
||||||
|
// These should probably just be combined into one modal component
|
||||||
|
import * as React from 'react';
|
||||||
import ReactModal from 'react-modal';
|
import ReactModal from 'react-modal';
|
||||||
import Link from 'component/link/index';
|
import Button from 'component/link/index';
|
||||||
import app from 'app';
|
import app from 'app';
|
||||||
|
|
||||||
export class Modal extends React.PureComponent {
|
type ModalProps = {
|
||||||
static propTypes = {
|
type: string,
|
||||||
type: PropTypes.oneOf(['alert', 'confirm', 'custom']),
|
overlay: boolean,
|
||||||
overlay: PropTypes.bool,
|
confirmButtonLabel: string,
|
||||||
onConfirmed: PropTypes.func,
|
abortButtonLabel: string,
|
||||||
onAborted: PropTypes.func,
|
confirmButtonDisabled: boolean,
|
||||||
confirmButtonLabel: PropTypes.string,
|
abortButtonDisabled: boolean,
|
||||||
abortButtonLabel: PropTypes.string,
|
onConfirmed?: any => any,
|
||||||
confirmButtonDisabled: PropTypes.bool,
|
onAborted?: any => any,
|
||||||
abortButtonDisabled: PropTypes.bool,
|
className?: string,
|
||||||
};
|
overlayClassName?: string,
|
||||||
|
children?: React.Node,
|
||||||
|
extraContent?: React.Node,
|
||||||
|
expandButtonLabel?: string,
|
||||||
|
hideButtonLabel?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Modal extends React.PureComponent<ModalProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
type: 'alert',
|
type: 'alert',
|
||||||
overlay: true,
|
overlay: true,
|
||||||
|
/* eslint-disable no-underscore-dangle */
|
||||||
confirmButtonLabel: app.i18n.__('OK'),
|
confirmButtonLabel: app.i18n.__('OK'),
|
||||||
abortButtonLabel: app.i18n.__('Cancel'),
|
abortButtonLabel: app.i18n.__('Cancel'),
|
||||||
|
/* eslint-enable no-underscore-dangle */
|
||||||
confirmButtonDisabled: false,
|
confirmButtonDisabled: false,
|
||||||
abortButtonDisabled: false,
|
abortButtonDisabled: false,
|
||||||
};
|
};
|
||||||
|
@ -38,20 +48,17 @@ export class Modal extends React.PureComponent {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div>{this.props.children}</div>
|
<div>{this.props.children}</div>
|
||||||
{this.props.type == 'custom' ? null : ( // custom modals define their own buttons
|
{this.props.type === 'custom' ? null : ( // custom modals define their own buttons
|
||||||
<div className="modal__buttons">
|
<div className="card__actions card__actions--center">
|
||||||
<Link
|
<Button
|
||||||
button="primary"
|
|
||||||
label={this.props.confirmButtonLabel}
|
label={this.props.confirmButtonLabel}
|
||||||
className="modal__button"
|
|
||||||
disabled={this.props.confirmButtonDisabled}
|
disabled={this.props.confirmButtonDisabled}
|
||||||
onClick={this.props.onConfirmed}
|
onClick={this.props.onConfirmed}
|
||||||
/>
|
/>
|
||||||
{this.props.type == 'confirm' ? (
|
{this.props.type === 'confirm' ? (
|
||||||
<Link
|
<Button
|
||||||
button="alt"
|
alt
|
||||||
label={this.props.abortButtonLabel}
|
label={this.props.abortButtonLabel}
|
||||||
className="modal__button"
|
|
||||||
disabled={this.props.abortButtonDisabled}
|
disabled={this.props.abortButtonDisabled}
|
||||||
onClick={this.props.onAborted}
|
onClick={this.props.onAborted}
|
||||||
/>
|
/>
|
||||||
|
@ -63,19 +70,20 @@ export class Modal extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExpandableModal extends React.PureComponent {
|
type State = {
|
||||||
static propTypes = {
|
expanded: boolean,
|
||||||
expandButtonLabel: PropTypes.string,
|
};
|
||||||
extraContent: PropTypes.element,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export class ExpandableModal extends React.PureComponent<ModalProps, State> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
/* eslint-disable no-underscore-dangle */
|
||||||
confirmButtonLabel: app.i18n.__('OK'),
|
confirmButtonLabel: app.i18n.__('OK'),
|
||||||
expandButtonLabel: app.i18n.__('Show More...'),
|
expandButtonLabel: app.i18n.__('Show More...'),
|
||||||
hideButtonLabel: app.i18n.__('Show Less'),
|
hideButtonLabel: app.i18n.__('Show Less'),
|
||||||
|
/* eslint-enable no-underscore-dangle */
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: ModalProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -95,13 +103,13 @@ export class ExpandableModal extends React.PureComponent {
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
{this.state.expanded ? this.props.extraContent : null}
|
{this.state.expanded ? this.props.extraContent : null}
|
||||||
<div className="modal__buttons">
|
<div className="modal__buttons">
|
||||||
<Link
|
<Button
|
||||||
button="primary"
|
button="primary"
|
||||||
label={this.props.confirmButtonLabel}
|
label={this.props.confirmButtonLabel}
|
||||||
className="modal__button"
|
className="modal__button"
|
||||||
onClick={this.props.onConfirmed}
|
onClick={this.props.onConfirmed}
|
||||||
/>
|
/>
|
||||||
<Link
|
<Button
|
||||||
button="alt"
|
button="alt"
|
||||||
label={!this.state.expanded ? this.props.expandButtonLabel : this.props.hideButtonLabel}
|
label={!this.state.expanded ? this.props.expandButtonLabel : this.props.hideButtonLabel}
|
||||||
className="modal__button"
|
className="modal__button"
|
||||||
|
@ -116,3 +124,4 @@ export class ExpandableModal extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Modal;
|
export default Modal;
|
||||||
|
/* eslint-enable react/no-multi-comp */
|
|
@ -1,26 +1,49 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Modal } from 'modal/modal';
|
import { Modal } from 'modal/modal';
|
||||||
import FormField from 'component/formField/index';
|
import { FormRow, FormField } from 'component/common/form';
|
||||||
|
|
||||||
class ModalRemoveFile extends React.PureComponent {
|
type Props = {
|
||||||
constructor(props) {
|
claimIsMine: boolean,
|
||||||
|
closeModal: () => void,
|
||||||
|
deleteFile: (string, boolean, boolean) => void,
|
||||||
|
title: string,
|
||||||
|
fileInfo: {
|
||||||
|
outpoint: string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
deleteChecked: boolean,
|
||||||
|
abandonClaimChecked: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModalRemoveFile extends React.PureComponent<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
deleteChecked: false,
|
deleteChecked: true,
|
||||||
abandonClaimChecked: false,
|
abandonClaimChecked: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(this: any).handleDeleteCheckboxClicked = this.handleDeleteCheckboxClicked.bind(this);
|
||||||
|
(this: any).handleAbandonClaimCheckboxClicked = this.handleAbandonClaimCheckboxClicked.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteCheckboxClicked(event) {
|
handleDeleteCheckboxClicked() {
|
||||||
|
const { deleteChecked } = this.state;
|
||||||
this.setState({
|
this.setState({
|
||||||
deleteChecked: event.target.checked,
|
deleteChecked: !deleteChecked,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAbandonClaimCheckboxClicked(event) {
|
handleAbandonClaimCheckboxClicked() {
|
||||||
|
const { abandonClaimChecked } = this.state;
|
||||||
this.setState({
|
this.setState({
|
||||||
abandonClaimChecked: event.target.checked,
|
abandonClaimChecked: !abandonClaimChecked,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,26 +61,34 @@ class ModalRemoveFile extends React.PureComponent {
|
||||||
onAborted={closeModal}
|
onAborted={closeModal}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
{__("Are you sure you'd like to remove")} <cite>{title}</cite> {__('from LBRY?')}
|
{__("Are you sure you'd like to remove")} <cite>{title}</cite> {__('from the LBRY app?')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<section>
|
<FormRow padded>
|
||||||
<FormField
|
<FormField
|
||||||
type="checkbox"
|
prefix={__('Also delete this file from my computer')}
|
||||||
checked={deleteChecked}
|
render={() => (
|
||||||
onClick={this.handleDeleteCheckboxClicked.bind(this)}
|
<input
|
||||||
label={__('Delete this file from my computer')}
|
type="checkbox"
|
||||||
|
checked={deleteChecked}
|
||||||
|
onChange={this.handleDeleteCheckboxClicked}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</section>
|
</FormRow>
|
||||||
{claimIsMine && (
|
{!claimIsMine && (
|
||||||
<section>
|
<FormRow>
|
||||||
<FormField
|
<FormField
|
||||||
type="checkbox"
|
prefix={__('Abandon the claim for this URI')}
|
||||||
checked={abandonClaimChecked}
|
render={() => (
|
||||||
onClick={this.handleAbandonClaimCheckboxClicked.bind(this)}
|
<input
|
||||||
label={__('Abandon the claim for this URI')}
|
type="checkbox"
|
||||||
|
checked={abandonClaimChecked}
|
||||||
|
onChange={this.handleAbandonClaimCheckboxClicked}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</section>
|
</FormRow>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,6 +14,7 @@ import ModalAffirmPurchase from 'modal/modalAffirmPurchase';
|
||||||
import ModalRevokeClaim from 'modal/modalRevokeClaim';
|
import ModalRevokeClaim from 'modal/modalRevokeClaim';
|
||||||
import ModalEmailCollection from '../modalEmailCollection';
|
import ModalEmailCollection from '../modalEmailCollection';
|
||||||
import ModalPhoneCollection from '../modalPhoneCollection';
|
import ModalPhoneCollection from '../modalPhoneCollection';
|
||||||
|
import ModalSendTip from '../modalSendTip';
|
||||||
import * as modals from 'constants/modal_types';
|
import * as modals from 'constants/modal_types';
|
||||||
|
|
||||||
class ModalRouter extends React.PureComponent {
|
class ModalRouter extends React.PureComponent {
|
||||||
|
@ -129,6 +130,8 @@ class ModalRouter extends React.PureComponent {
|
||||||
return <ModalPhoneCollection {...modalProps} />;
|
return <ModalPhoneCollection {...modalProps} />;
|
||||||
case modals.EMAIL_COLLECTION:
|
case modals.EMAIL_COLLECTION:
|
||||||
return <ModalEmailCollection {...modalProps} />;
|
return <ModalEmailCollection {...modalProps} />;
|
||||||
|
case modals.SEND_TIP:
|
||||||
|
return <ModalSendTip {...modalProps} />;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
9
src/renderer/modal/modalSendTip/index.js
Normal file
9
src/renderer/modal/modalSendTip/index.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doCloseModal } from 'redux/actions/app';
|
||||||
|
import ModalSendTip from './view';
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(null, perform)(ModalSendTip);
|
23
src/renderer/modal/modalSendTip/view.jsx
Normal file
23
src/renderer/modal/modalSendTip/view.jsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { Modal } from 'modal/modal';
|
||||||
|
import SendTip from 'component/walletSendTip';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
closeModal: () => void,
|
||||||
|
uri: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModalSendTip extends React.PureComponent<Props> {
|
||||||
|
render() {
|
||||||
|
const { closeModal, uri } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen type="custom">
|
||||||
|
<SendTip uri={uri} onCancel={closeModal} sendTipCallback={closeModal} />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalSendTip;
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
||||||
import { doNavigate } from 'redux/actions/navigation';
|
import { doNavigate } from 'redux/actions/navigation';
|
||||||
import { doFetchFileInfo } from 'redux/actions/file_info';
|
import { doFetchFileInfo } from 'redux/actions/file_info';
|
||||||
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
||||||
import { selectRewardContentClaimIds } from 'redux/selectors/content';
|
import { selectRewardContentClaimIds, selectPlayingUri } from 'redux/selectors/content';
|
||||||
import { doFetchCostInfoForUri } from 'redux/actions/cost_info';
|
import { doFetchCostInfoForUri } from 'redux/actions/cost_info';
|
||||||
import {
|
import {
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
|
@ -11,8 +11,9 @@ import {
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
|
import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
|
||||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||||
|
import { selectMediaPaused } from 'redux/selectors/media';
|
||||||
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import FilePage from './view';
|
import FilePage from './view';
|
||||||
import { makeSelectCurrentParam } from 'redux/selectors/navigation';
|
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
@ -20,15 +21,17 @@ const select = (state, props) => ({
|
||||||
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||||
metadata: makeSelectMetadataForUri(props.uri)(state),
|
metadata: makeSelectMetadataForUri(props.uri)(state),
|
||||||
obscureNsfw: !selectShowNsfw(state),
|
obscureNsfw: !selectShowNsfw(state),
|
||||||
tab: makeSelectCurrentParam('tab')(state),
|
|
||||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||||
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
||||||
|
playingUri: selectPlayingUri(state),
|
||||||
|
isPaused: selectMediaPaused(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||||
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||||
|
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(FilePage);
|
export default connect(select, perform)(FilePage);
|
||||||
|
|
|
@ -1,37 +1,69 @@
|
||||||
/* eslint-disable */
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from 'lbry';
|
import lbry from 'lbry';
|
||||||
import { buildURI, normalizeURI } from 'lbryURI';
|
import { buildURI, normalizeURI } from 'lbryURI';
|
||||||
import Video from 'component/video';
|
import Video from 'component/video';
|
||||||
import { Thumbnail } from 'component/common';
|
import Thumbnail from 'component/common/thumbnail';
|
||||||
import FilePrice from 'component/filePrice';
|
import FilePrice from 'component/filePrice';
|
||||||
import FileDetails from 'component/fileDetails';
|
import FileDetails from 'component/fileDetails';
|
||||||
|
import FileActions from 'component/fileActions';
|
||||||
import UriIndicator from 'component/uriIndicator';
|
import UriIndicator from 'component/uriIndicator';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import WalletSendTip from 'component/walletSendTip';
|
import WalletSendTip from 'component/walletSendTip';
|
||||||
import DateTime from 'component/dateTime';
|
import DateTime from 'component/dateTime';
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
import Link from 'component/link';
|
import Button from 'component/link';
|
||||||
import SubscribeButton from 'component/subscribeButton';
|
import SubscribeButton from 'component/subscribeButton';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import player from 'render-media';
|
||||||
|
import * as modals from 'constants/modal_types';
|
||||||
|
|
||||||
class FilePage extends React.PureComponent {
|
type Props = {
|
||||||
|
claim: {
|
||||||
|
claim_id: string,
|
||||||
|
height: number,
|
||||||
|
channel_name: string,
|
||||||
|
value: {
|
||||||
|
publisherSignature: ?{
|
||||||
|
certificateId: ?string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fileInfo: {},
|
||||||
|
metadata: {
|
||||||
|
title: string,
|
||||||
|
thumbnail: string,
|
||||||
|
nsfw: boolean
|
||||||
|
},
|
||||||
|
contentType: string,
|
||||||
|
uri: string,
|
||||||
|
rewardedContentClaimIds: Array<string>,
|
||||||
|
obscureNsfw: boolean,
|
||||||
|
playingUri: ?string,
|
||||||
|
isPaused: boolean,
|
||||||
|
openModal: (string, any) => void,
|
||||||
|
fetchFileInfo: (string) => void,
|
||||||
|
fetchCostInfo: (string) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilePage extends React.Component<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.fetchFileInfo(this.props);
|
this.fetchFileInfo(this.props);
|
||||||
this.fetchCostInfo(this.props);
|
this.fetchCostInfo(this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps: Props) {
|
||||||
this.fetchFileInfo(nextProps);
|
this.fetchFileInfo(nextProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchFileInfo(props) {
|
fetchFileInfo(props: Props) {
|
||||||
if (props.fileInfo === undefined) {
|
if (props.fileInfo === undefined) {
|
||||||
props.fetchFileInfo(props.uri);
|
props.fetchFileInfo(props.uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchCostInfo(props) {
|
fetchCostInfo(props: Props) {
|
||||||
if (props.costInfo === undefined) {
|
if (props.costInfo === undefined) {
|
||||||
props.fetchCostInfo(props.uri);
|
props.fetchCostInfo(props.uri);
|
||||||
}
|
}
|
||||||
|
@ -43,74 +75,85 @@ class FilePage extends React.PureComponent {
|
||||||
fileInfo,
|
fileInfo,
|
||||||
metadata,
|
metadata,
|
||||||
contentType,
|
contentType,
|
||||||
tab,
|
|
||||||
uri,
|
uri,
|
||||||
rewardedContentClaimIds,
|
rewardedContentClaimIds,
|
||||||
|
obscureNsfw,
|
||||||
|
playingUri,
|
||||||
|
isPaused,
|
||||||
|
openModal,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const showTipBox = tab == 'tip';
|
// This should be included below in the page
|
||||||
|
// Come back to me
|
||||||
if (!claim || !metadata) {
|
if (!claim || !metadata) {
|
||||||
return <span className="empty">{__('Empty claim or metadata info.')}</span>;
|
return <span className="empty">{__('Empty claim or metadata info.')}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// File info
|
||||||
const title = metadata.title;
|
const title = metadata.title;
|
||||||
const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
|
const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
|
||||||
|
const shouldObscureThumbnail = obscureNsfw && metadata.nsfw;
|
||||||
|
const thumbnail = metadata.thumbnail;
|
||||||
|
const { height, channel_name: channelName, value } = claim;
|
||||||
const mediaType = lbry.getMediaType(contentType);
|
const mediaType = lbry.getMediaType(contentType);
|
||||||
const player = require('render-media');
|
|
||||||
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
|
||||||
const isPlayable =
|
const isPlayable =
|
||||||
Object.values(player.mime).indexOf(contentType) !== -1 || mediaType === 'audio';
|
Object.values(player.mime).indexOf(contentType) !== -1 || mediaType === 'audio';
|
||||||
const { height, channel_name: channelName, value } = claim;
|
|
||||||
const channelClaimId =
|
const channelClaimId =
|
||||||
value && value.publisherSignature && value.publisherSignature.certificateId;
|
value && value.publisherSignature && value.publisherSignature.certificateId;
|
||||||
|
|
||||||
let subscriptionUri;
|
let subscriptionUri;
|
||||||
if (channelName && channelClaimId) {
|
if (channelName && channelClaimId) {
|
||||||
subscriptionUri = buildURI({ channelName, claimId: channelClaimId }, false);
|
subscriptionUri = buildURI({ channelName, claimId: channelClaimId }, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isPlaying = playingUri === uri && !isPaused;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<section className={`card ${obscureNsfw ? 'card--obscured ' : ''}`}>
|
<section className="card">
|
||||||
<div className="show-page-media">
|
<div>
|
||||||
{isPlayable ? (
|
{isPlayable ? (
|
||||||
<Video className="video-embedded" uri={uri} />
|
<Video className="video__embedded" uri={uri} />
|
||||||
) : metadata && metadata.thumbnail ? (
|
|
||||||
<Thumbnail src={metadata.thumbnail} />
|
|
||||||
) : (
|
) : (
|
||||||
<Thumbnail />
|
<Thumbnail
|
||||||
|
shouldObscure={shouldObscureThumbnail}
|
||||||
|
className="video__embedded"
|
||||||
|
src={thumbnail}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
{!isPlaying && (
|
||||||
<div className="card__inner">
|
<div className="card-media__internal-links">
|
||||||
{(!tab || tab === 'details') && (
|
<FileActions uri={uri} vertical />
|
||||||
<div>
|
|
||||||
{' '}
|
|
||||||
<div className="card__title-identity">
|
|
||||||
{!fileInfo || fileInfo.written_bytes <= 0 ? (
|
|
||||||
<span style={{ float: 'right' }}>
|
|
||||||
<FilePrice uri={normalizeURI(uri)} />
|
|
||||||
{isRewardContent && (
|
|
||||||
<span>
|
|
||||||
{' '}
|
|
||||||
<Icon icon={icons.FEATURED} />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
<h1>{title}</h1>
|
|
||||||
<div className="card__subtitle card--file-subtitle">
|
|
||||||
<UriIndicator uri={uri} link />
|
|
||||||
<span className="card__publish-date">
|
|
||||||
Published on <DateTime block={height} show={DateTime.SHOW_DATE} />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SubscribeButton uri={subscriptionUri} channelName={channelName} />
|
|
||||||
<FileDetails uri={uri} />
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{tab === 'tip' && <WalletSendTip claim_id={claim.claim_id} uri={uri} />}
|
</div>
|
||||||
|
<div className="card--content">
|
||||||
|
<div className="card__title-identity--file">
|
||||||
|
<h1 className="card__title">{title}</h1>
|
||||||
|
<div className="card__title-identity-icons">
|
||||||
|
<FilePrice uri={normalizeURI(uri)} />
|
||||||
|
{isRewardContent && <Icon icon={icons.FEATURED} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="card__subtitle card__subtitle--file">
|
||||||
|
{__('Published on')} <DateTime block={height} show={DateTime.SHOW_DATE} />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div className="card__channel-info">
|
||||||
|
<UriIndicator uri={uri} link />
|
||||||
|
<div className="card__actions card__actions--no-margin">
|
||||||
|
<Button
|
||||||
|
alt
|
||||||
|
iconRight="Send"
|
||||||
|
label={__('Enjoy this? Send a tip')}
|
||||||
|
onClick={() => openModal(modals.SEND_TIP, { uri })}
|
||||||
|
/>
|
||||||
|
<SubscribeButton uri={subscriptionUri} channelName={channelName} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card--content">
|
||||||
|
<FileDetails uri={uri} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</Page>
|
</Page>
|
||||||
|
@ -119,4 +162,3 @@ class FilePage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilePage;
|
export default FilePage;
|
||||||
/* eslint-enable */
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as actions from 'constants/action_types';
|
|
||||||
import type { Dispatch } from 'redux/reducers/video';
|
|
||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
|
||||||
export const setVideoPause = (data: boolean) => (dispatch: Dispatch) =>
|
|
||||||
dispatch({
|
|
||||||
type: actions.SET_VIDEO_PAUSE,
|
|
||||||
data,
|
|
||||||
});
|
|
|
@ -3,6 +3,7 @@ import * as ACTIONS from 'constants/action_types';
|
||||||
const reducers = {};
|
const reducers = {};
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
playingUri: null,
|
playingUri: null,
|
||||||
|
currentlyIsPlaying: false,
|
||||||
rewardedContentClaimIds: [],
|
rewardedContentClaimIds: [],
|
||||||
channelClaimCounts: {},
|
channelClaimCounts: {},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as ACTIONS from 'constants/action_types';
|
|
||||||
import { handleActions } from 'util/redux-utils';
|
|
||||||
|
|
||||||
export type VideoState = { videoPause: boolean };
|
|
||||||
|
|
||||||
type setVideoPause = {
|
|
||||||
type: ACTIONS.SET_VIDEO_PAUSE,
|
|
||||||
data: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Action = setVideoPause;
|
|
||||||
export type Dispatch = (action: Action) => any;
|
|
||||||
|
|
||||||
const defaultState = { videoPause: false };
|
|
||||||
|
|
||||||
export default handleActions(
|
|
||||||
{
|
|
||||||
[ACTIONS.SET_VIDEO_PAUSE]: (state: VideoState, action: setVideoPause): VideoState => ({
|
|
||||||
...state,
|
|
||||||
videoPause: action.data,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
defaultState
|
|
||||||
);
|
|
|
@ -42,15 +42,12 @@ export const selectActiveHistoryEntry = createSelector(
|
||||||
state => state.stack[state.index]
|
state => state.stack[state.index]
|
||||||
);
|
);
|
||||||
|
|
||||||
export const selectPageTitle = createSelector(
|
export const selectPageTitle = createSelector(selectCurrentPage, page => {
|
||||||
selectCurrentPage,
|
switch (page) {
|
||||||
(page) => {
|
default:
|
||||||
switch (page) {
|
return '';
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
export const selectNavLinks = createSelector(
|
export const selectNavLinks = createSelector(
|
||||||
selectCurrentPage,
|
selectCurrentPage,
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import {
|
import { selectCurrentPage, selectCurrentParams } from 'redux/selectors/navigation';
|
||||||
selectCurrentPage,
|
|
||||||
selectCurrentParams,
|
|
||||||
selectPageTitle,
|
|
||||||
} from 'redux/selectors/navigation';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
export const selectState = state => state.search || {};
|
export const selectState = state => state.search || {};
|
||||||
|
@ -26,13 +22,13 @@ export const makeSelectSearchUris = query =>
|
||||||
|
|
||||||
export const selectWunderBarAddress = createSelector(
|
export const selectWunderBarAddress = createSelector(
|
||||||
selectCurrentPage,
|
selectCurrentPage,
|
||||||
selectPageTitle,
|
|
||||||
selectSearchQuery,
|
selectSearchQuery,
|
||||||
(page, title, query) => {
|
selectCurrentParams,
|
||||||
|
(page, query, params) => {
|
||||||
// only populate the wunderbar address if we are on the file/channel pages
|
// only populate the wunderbar address if we are on the file/channel pages
|
||||||
// or show the search query
|
// or show the search query
|
||||||
if (page === 'show') {
|
if (page === 'show') {
|
||||||
return title;
|
return params.uri;
|
||||||
} else if (page === 'search') {
|
} else if (page === 'search') {
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
const selectState = state => state.video || {};
|
|
||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
|
||||||
export const selectVideoPause = createSelector(selectState, state => state.videoPause);
|
|
|
@ -6,6 +6,14 @@
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
|
src: url('../../../static/font/metropolis/Metropolis-Medium.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Metropolis';
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
src: url('../../../static/font/metropolis/Metropolis-Regular.woff2') format('woff2');
|
src: url('../../../static/font/metropolis/Metropolis-Regular.woff2') format('woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,12 +94,15 @@ ul {
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
width: 100%;
|
cursor: pointer;
|
||||||
cursor: text;
|
|
||||||
border-bottom: var(--input-border-size) solid var(--input-border-color);
|
border-bottom: var(--input-border-size) solid var(--input-border-color);
|
||||||
color: var(--input-color);
|
color: var(--input-color);
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
||||||
|
&[type='text'] {
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
&.input-copyable {
|
&.input-copyable {
|
||||||
background: var(--input-bg);
|
background: var(--input-bg);
|
||||||
color: var(--input-disabled-color);
|
color: var(--input-disabled-color);
|
||||||
|
@ -101,8 +112,28 @@ input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
dl {
|
||||||
*/
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
float: left;
|
||||||
|
width: 20%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
float: left;
|
||||||
|
width: 80%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: var(--header-height) calc(100vh - var(--header-height));
|
grid-template-rows: var(--header-height) calc(100vh - var(--header-height));
|
||||||
|
@ -176,11 +207,11 @@ input {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 0.7em;
|
font-size: 0.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credit-amount--free {
|
.credit-amount--free {
|
||||||
color: var(--color-black);
|
color: var(--color-primary);
|
||||||
background-color: var(--color-secondary);
|
background-color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,6 +235,11 @@ input {
|
||||||
// font-weight: 700;
|
// font-weight: 700;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
.divider__horizontal {
|
||||||
|
border-top: var(--color-divider);
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -259,16 +295,16 @@ input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sort-section {
|
// .sort-section {
|
||||||
display: block;
|
// display: block;
|
||||||
margin-bottom: $spacing-vertical * 2/3;
|
// margin-bottom: $spacing-vertical * 2/3;
|
||||||
|
//
|
||||||
text-align: right;
|
// text-align: right;
|
||||||
line-height: 1;
|
// line-height: 1;
|
||||||
font-size: 0.85em;
|
// font-size: 0.85em;
|
||||||
color: var(--color-help);
|
// color: var(--color-help);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
section.section-spaced {
|
// section.section-spaced {
|
||||||
margin-bottom: $spacing-vertical;
|
// margin-bottom: $spacing-vertical;
|
||||||
}
|
// }
|
||||||
|
|
|
@ -68,7 +68,7 @@ select {
|
||||||
border: 0 none;
|
border: 0 none;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
width: auto\9;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
-ms-interpolation-mode: bicubic;
|
-ms-interpolation-mode: bicubic;
|
||||||
|
|
|
@ -18,7 +18,7 @@ $width-page-constrained: 800px;
|
||||||
|
|
||||||
--text-color: var(--color-black);
|
--text-color: var(--color-black);
|
||||||
--color-brand: #155b4a;
|
--color-brand: #155b4a;
|
||||||
// --color-dark-overlay: rgba(32, 32, 32, 0.9);
|
--color-dark-overlay: rgba(32, 32, 32, 0.9);
|
||||||
--color-help: rgba(0, 0, 0, 0.54);
|
--color-help: rgba(0, 0, 0, 0.54);
|
||||||
// --color-notice: #8a6d3b;
|
// --color-notice: #8a6d3b;
|
||||||
--color-error: #a94442;
|
--color-error: #a94442;
|
||||||
|
@ -32,14 +32,18 @@ $width-page-constrained: 800px;
|
||||||
--color-placeholder: #ececec;
|
--color-placeholder: #ececec;
|
||||||
--color-nav-bg: #f6f6f6;
|
--color-nav-bg: #f6f6f6;
|
||||||
|
|
||||||
/* Misc */
|
/* Video */
|
||||||
|
--height-video-embedded: 450px;
|
||||||
|
--height-video-embedded-min: 325px;
|
||||||
|
--width-video-embedded: 700px;
|
||||||
|
|
||||||
|
// --width-video-embedded: var(--height-video-embedded) * 16/9;
|
||||||
// --content-max-width: 1000px;
|
// --content-max-width: 1000px;
|
||||||
// --nsfw-blur-intensity: 20px;
|
// --nsfw-blur-intensity: 20px;
|
||||||
// --height-video-embedded: $width-page-constrained * 9 / 16;
|
|
||||||
|
|
||||||
/* Font */
|
/* Font */
|
||||||
--font-size: 16px;
|
--font-size: 16px;
|
||||||
--font-line-height: 1.3333;
|
--font-line-height: 1.7;
|
||||||
--font-size-subtext-multiple: 0.82;
|
--font-size-subtext-multiple: 0.82;
|
||||||
|
|
||||||
/* Shadows */
|
/* Shadows */
|
||||||
|
|
|
@ -21,11 +21,9 @@
|
||||||
@import 'component/_pagination.scss';
|
@import 'component/_pagination.scss';
|
||||||
@import 'component/_markdown-editor.scss';
|
@import 'component/_markdown-editor.scss';
|
||||||
@import 'component/_scrollbar.scss';
|
@import 'component/_scrollbar.scss';
|
||||||
@import 'component/_tabs.scss';
|
|
||||||
@import 'component/_divider.scss';
|
@import 'component/_divider.scss';
|
||||||
@import 'component/_checkbox.scss';
|
@import 'component/_checkbox.scss';
|
||||||
@import 'component/_radio.scss';
|
@import 'component/_radio.scss';
|
||||||
@import 'component/_shapeshift.scss';
|
@import 'component/_shapeshift.scss';
|
||||||
@import 'component/_spinner.scss';
|
@import 'component/_spinner.scss';
|
||||||
@import 'component/_nav.scss';
|
@import 'component/_nav.scss';
|
||||||
@import 'page/_show.scss';
|
|
||||||
|
|
|
@ -1,8 +1,29 @@
|
||||||
/*
|
// This will go away
|
||||||
TODO:
|
// It's for the download progress "button"
|
||||||
Determine [disabled] or .disabled
|
.faux-button-block {
|
||||||
Add <a> support (probably just get rid of button prefix)
|
display: inline-block;
|
||||||
*/
|
height: var(--button-height);
|
||||||
|
line-height: var(--button-height);
|
||||||
|
text-decoration: none;
|
||||||
|
border: 0 none;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: var(--button-radius);
|
||||||
|
text-transform: uppercase;
|
||||||
|
.icon {
|
||||||
|
top: 0em;
|
||||||
|
}
|
||||||
|
.icon:first-child {
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
.icon:last-child {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
.icon:only-child {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
border: none;
|
border: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -19,6 +40,7 @@ Add <a> support (probably just get rid of button prefix)
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
fill: currentColor; // for proper icon color
|
fill: currentColor; // for proper icon color
|
||||||
|
font-size: 0.8em;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: var(--box-shadow-layer);
|
box-shadow: var(--box-shadow-layer);
|
||||||
|
@ -71,6 +93,11 @@ Add <a> support (probably just get rid of button prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn--secondary {
|
||||||
|
background-color: var(--color-secondary);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.btn--no-style {
|
.btn--no-style {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
font-weight: inherit;
|
font-weight: inherit;
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
user-select: text;
|
user-select: text;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card--section {
|
.card--section {
|
||||||
flex-direction: column;
|
|
||||||
background-color: var(--color-white);
|
background-color: var(--color-white);
|
||||||
padding: $spacing-vertical;
|
padding: $spacing-vertical;
|
||||||
margin-top: $spacing-vertical * 2/3;
|
margin-top: $spacing-vertical * 2/3;
|
||||||
|
@ -51,8 +51,27 @@
|
||||||
margin-top: $spacing-vertical * 1/3;
|
margin-top: $spacing-vertical * 1/3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: regular .card__title
|
.card__title-identity--file {
|
||||||
// maybe not needed?
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.credit-amount,
|
||||||
|
.icon {
|
||||||
|
margin-left: $spacing-vertical * 2/3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card__title-identity-icons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card__title {
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: 800;
|
||||||
|
padding: $spacing-vertical / 3 0;
|
||||||
|
}
|
||||||
|
|
||||||
.card__title--small {
|
.card__title--small {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
@ -64,10 +83,26 @@
|
||||||
padding-top: $spacing-vertical * 1/3;
|
padding-top: $spacing-vertical * 1/3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card__subtitle--file {
|
||||||
|
font-size: 1em;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.card-media__internal-links {
|
.card-media__internal-links {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5px;
|
top: $spacing-vertical * 2/3;
|
||||||
right: 5px;
|
right: $spacing-vertical * 2/3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel info with buttons on the right side
|
||||||
|
.card__channel-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.card__actions .btn:not(:first-of-type) {
|
||||||
|
margin-left: $spacing-vertical / 3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__content {
|
.card__content {
|
||||||
|
@ -75,11 +110,47 @@
|
||||||
margin-bottom: var(--card-margin);
|
margin-bottom: var(--card-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card__subtext-title {
|
||||||
|
color: var(--color-black);
|
||||||
|
margin-top: $spacing-vertical * 3/2;
|
||||||
|
font-size: calc(var(--font-size-subtext-multiple) * 1.5em);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card__subtext {
|
||||||
|
color: var(--color-grey-dark);
|
||||||
|
font-size: calc(var(--font-size-subtext-multiple) * 1em);
|
||||||
|
padding-top: $spacing-vertical * 1/3;
|
||||||
|
word-break: break-word;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
.card__actions {
|
.card__actions {
|
||||||
margin-top: var(--card-margin);
|
margin-top: var(--card-margin);
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card__actions--no-margin {
|
||||||
|
magin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card__actions--vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
|
.btn:not(:first-child) {
|
||||||
|
margin-top: $spacing-vertical * 2/3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card__actions--center {
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin: 0 $spacing-vertical / 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
.card-row is used on the discover/subscriptions page
|
.card-row is used on the discover/subscriptions page
|
||||||
It is a list of cards that extend past the right edge of the screen
|
It is a list of cards that extend past the right edge of the screen
|
||||||
|
@ -135,3 +206,9 @@
|
||||||
margin-right: $spacing-vertical * 2/3;
|
margin-right: $spacing-vertical * 2/3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: come back to this
|
||||||
|
// Do we want an opacity over the image?
|
||||||
|
.card--obscured {
|
||||||
|
background-color: #e09898;
|
||||||
|
}
|
||||||
|
|
|
@ -5,11 +5,21 @@
|
||||||
.form-field:not(:first-of-type) {
|
.form-field:not(:first-of-type) {
|
||||||
padding-left: $spacing-vertical;
|
padding-left: $spacing-vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.form-row--padded {
|
||||||
|
padding: $spacing-vertical * 2/3 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field__wrapper {
|
.form-field__wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: $spacing-vertical / 3 0;
|
padding: $spacing-vertical / 3 0;
|
||||||
|
|
||||||
|
// Hmm, not sure about this
|
||||||
|
// without this the checkbox isn't on the same line as other form-field text
|
||||||
|
input[type='checkbox'] {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-field__error {
|
.form-field__error {
|
||||||
|
|
|
@ -1,58 +1,43 @@
|
||||||
.spinner {
|
.spinner {
|
||||||
position: relative;
|
margin: $spacing-vertical * 1/3;
|
||||||
width: 11em;
|
width: 50px;
|
||||||
height: 11em;
|
height: 40px;
|
||||||
margin: 20px auto;
|
text-align: center;
|
||||||
font-size: 3px;
|
font-size: 10px;
|
||||||
border-radius: 50%;
|
}
|
||||||
|
|
||||||
background: linear-gradient(to right, #fff 10%, rgba(255, 255, 255, 0) 50%);
|
.spinner > div {
|
||||||
animation: spin 1.4s infinite linear;
|
display: inline-block;
|
||||||
transform: translateZ(0);
|
height: 100%;
|
||||||
|
width: 6px;
|
||||||
|
margin: 0 2px;
|
||||||
|
background-color: var(--color-white);
|
||||||
|
animation: sk-stretchdelay 1.2s infinite ease-in-out;
|
||||||
|
|
||||||
@keyframes spin {
|
&.rect2 {
|
||||||
from {
|
animation-delay: -1.1s;
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:before,
|
&.rect3 {
|
||||||
&:after {
|
animation-delay: -1s;
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:before {
|
&.rect4 {
|
||||||
width: 50%;
|
animation-delay: -0.9s;
|
||||||
height: 50%;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 100% 0 0 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:after {
|
&.rect5 {
|
||||||
height: 75%;
|
animation-delay: -0.8s;
|
||||||
width: 75%;
|
|
||||||
margin: auto;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
background: #000;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner.spinner--dark {
|
@keyframes sk-stretchdelay {
|
||||||
background: linear-gradient(to right, var(--button-primary-bg) 10%, var(--color-bg) 50%);
|
0%,
|
||||||
|
40%,
|
||||||
&:before {
|
100% {
|
||||||
background: var(--button-primary-bg);
|
transform: scaleY(0.4);
|
||||||
}
|
}
|
||||||
|
20% {
|
||||||
&:after {
|
transform: scaleY(1);
|
||||||
background: var(--color-bg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
/* Tabs */
|
|
||||||
|
|
||||||
nav.sub-header {
|
|
||||||
text-transform: uppercase;
|
|
||||||
max-width: $width-page-constrained;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
border-bottom: var(--divider);
|
|
||||||
user-select: none;
|
|
||||||
> a {
|
|
||||||
height: 38px;
|
|
||||||
line-height: 38px;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 500;
|
|
||||||
text-transform: uppercase;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: baseline;
|
|
||||||
margin: 0 12px;
|
|
||||||
padding: 0 8px;
|
|
||||||
color: var(--tab-color);
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
&.sub-header-selected {
|
|
||||||
color: var(--tab-active-color);
|
|
||||||
&:before {
|
|
||||||
width: 100%;
|
|
||||||
height: var(--tab-border-size);
|
|
||||||
background: var(--tab-active-color);
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
content: '';
|
|
||||||
animation-name: activeTab;
|
|
||||||
animation-duration: var(--animation-duration);
|
|
||||||
animation-timing-function: var(--animation-style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
color: var(--tab-active-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.sub-header--full-width {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.sub-header--small-margin {
|
|
||||||
margin-bottom: $spacing-vertical;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes activeTab {
|
|
||||||
from {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +1,11 @@
|
||||||
$height-video-embedded: $width-page-constrained * 9 / 16;
|
.video__embedded {
|
||||||
|
|
||||||
video {
|
|
||||||
object-fit: contain;
|
|
||||||
box-sizing: border-box;
|
|
||||||
max-height: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
background-size: contain;
|
|
||||||
background-position: center center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video {
|
|
||||||
background: #000;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-embedded {
|
|
||||||
max-width: $width-page-constrained;
|
|
||||||
max-height: $height-video-embedded;
|
|
||||||
height: $height-video-embedded;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
background-color: var(--color-black);
|
||||||
|
min-height: var(--height-video-embedded-min);
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
video {
|
video {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -27,57 +13,36 @@ video {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
&.video--hidden {
|
|
||||||
height: $height-video-embedded;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.video--obscured .video__cover {
|
|
||||||
position: relative;
|
// Video thumbnail with play/download button
|
||||||
filter: blur(var(--nsfw-blur-intensity));
|
.video__cover {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-size: auto 100%;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:not(.card--obscured) {
|
||||||
|
background-color: var(--color-black);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.video__loading-screen {
|
.video__loading-screen {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video__loading-status {
|
.video__loading-text {
|
||||||
padding-top: 20px;
|
color: var(--color-white);
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video__cover {
|
|
||||||
text-align: center;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-size: auto 100%;
|
|
||||||
background-position: center center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
position: relative;
|
|
||||||
.video__play-button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.video__play-button {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
display: none;
|
|
||||||
font-size: $spacing-vertical * 3;
|
|
||||||
color: white;
|
|
||||||
z-index: 1;
|
|
||||||
background: var(--color-dark-overlay);
|
|
||||||
opacity: 0.6;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity var(--transition-duration) var(--transition-type);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
.show-page-media {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
overflow: auto;
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 500px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -96,7 +96,7 @@ const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
|
||||||
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
||||||
|
|
||||||
const persistOptions = {
|
const persistOptions = {
|
||||||
whitelist: ['claims', 'subscriptions'],
|
whitelist: ['claims', 'subscriptions', 'navigation'],
|
||||||
// Order is important. Needs to be compressed last or other transforms can't
|
// Order is important. Needs to be compressed last or other transforms can't
|
||||||
// read the data
|
// read the data
|
||||||
transforms: [saveClaimsFilter, subscriptionsFilter, compressor],
|
transforms: [saveClaimsFilter, subscriptionsFilter, compressor],
|
||||||
|
@ -106,7 +106,7 @@ const persistOptions = {
|
||||||
|
|
||||||
window.cacheStore = persistStore(store, persistOptions, err => {
|
window.cacheStore = persistStore(store, persistOptions, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Unable to load saved SETTINGS');
|
console.error('Unable to load saved settings'); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue