Merge branch 'master' into patch-2

This commit is contained in:
Sean Yesmunt 2018-10-09 16:38:36 -04:00 committed by GitHub
commit b16d5e87de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 622 additions and 990 deletions

View file

@ -40,6 +40,8 @@
"react/require-default-props": 0, "react/require-default-props": 0,
"react/jsx-closing-tag-location": 0, "react/jsx-closing-tag-location": 0,
"jsx-a11y/no-noninteractive-element-to-interactive-role": 0, "jsx-a11y/no-noninteractive-element-to-interactive-role": 0,
"class-methods-use-this": 0 "class-methods-use-this": 0,
"jsx-a11y/interactive-supports-focus": 0,
"jsx-a11y/click-events-have-key-events": 0
} }
} }

View file

@ -6,14 +6,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
## [Unreleased] ## [Unreleased]
### Added ### Added
* Allow typing of encryption password without clicking entry box ([#1977](https://github.com/lbryio/lbry-desktop/pull/1977)) * Allow typing of encryption password without clicking entry box ([#1977](https://github.com/lbryio/lbry-desktop/pull/1977))
* Focus on search bar with {cmd,ctrl} + "l" ([#2003](https://github.com/lbryio/lbry-desktop/pull/2003))
### Changed ### Changed
* Make tooltip smarter ([#1979](https://github.com/lbryio/lbry-desktop/pull/1979)) * Make tooltip smarter ([#1979](https://github.com/lbryio/lbry-desktop/pull/1979))
* Change channel pages to have 48 items instead of 10 ([#2002](https://github.com/lbryio/lbry-desktop/pull/2002))
* Update to https ([#2016](https://github.com/lbryio/lbry-desktop/pull/2016))
* Simplify FileCard and FileTile component styling ([#2011](https://github.com/lbryio/lbry-desktop/pull/2011))
### Fixed ### Fixed
* Invite table cutoff with large number of invites ([#1985](https://github.com/lbryio/lbry-desktop/pull/1985)) * Fixed Transactions filter menu collides with transaction table ([#2005](https://github.com/lbryio/lbry-desktop/pull/2005))
* Fixed Transactions filter menu collides with transaction table ([#2005](https://github.com/lbryio/lbry-desktop/pull/2005)) * Invite table cutoff with large number of invites ([#1985](https://github.com/lbryio/lbry-desktop/pull/1985))
* History styling on large screens and link issue with claims ([#1999](https://github.com/lbryio/lbry-desktop/pull/1999))
* Satisfy console warnings in publishForm and validation messaging ([#2010](https://github.com/lbryio/lbry-desktop/pull/2010))
## [0.25.1] - 2018-09-18 ## [0.25.1] - 2018-09-18

View file

@ -23,12 +23,10 @@
"extract-langs": "node build/extractLocals.js", "extract-langs": "node build/extractLocals.js",
"compile": "electron-webpack && yarn extract-langs", "compile": "electron-webpack && yarn extract-langs",
"build": "yarn compile && electron-builder build", "build": "yarn compile && electron-builder build",
"build:dir": "yarn build -- --dir -c.compression=store -c.mac.identity=null",
"dev": "electron-webpack dev", "dev": "electron-webpack dev",
"lint": "eslint 'src/**/*.{js,jsx}' --fix && flow", "lint": "eslint 'src/**/*.{js,jsx}' --fix && flow",
"format": "prettier 'src/**/*.{js,jsx,scss,json}' --write", "format": "prettier 'src/**/*.{js,jsx,scss,json}' --write",
"flow-defs": "flow-typed install", "flow-defs": "flow-typed install",
"release": "yarn compile && electron-builder build",
"precommit": "lint-staged", "precommit": "lint-staged",
"preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc", "preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc",
"postinstall": "electron-builder install-app-deps && node build/downloadDaemon.js" "postinstall": "electron-builder install-app-deps && node build/downloadDaemon.js"
@ -50,7 +48,7 @@
"formik": "^0.10.4", "formik": "^0.10.4",
"hast-util-sanitize": "^1.1.2", "hast-util-sanitize": "^1.1.2",
"keytar": "^4.2.1", "keytar": "^4.2.1",
"lbry-redux": "lbryio/lbry-redux#c079b108c3bc4ba2b4fb85fb112b52cfc040c301", "lbry-redux": "lbryio/lbry-redux#4ee6c376e5f2c3e3e96d199a56970e2621a84af1",
"lbryinc": "lbryio/lbryinc#de7ff055605b02a24821f0f9bab1d206eb7f235d", "lbryinc": "lbryio/lbryinc#de7ff055605b02a24821f0f9bab1d206eb7f235d",
"localforage": "^1.7.1", "localforage": "^1.7.1",
"mammoth": "^1.4.6", "mammoth": "^1.4.6",

View file

@ -1,5 +1,5 @@
// @flow
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import Button from 'component/button'; import Button from 'component/button';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
@ -7,11 +7,9 @@ let scriptLoading = false;
let scriptLoaded = false; let scriptLoaded = false;
let scriptDidError = false; let scriptDidError = false;
class CardVerify extends React.Component { type Props = {
static propTypes = { disabled: boolean,
disabled: PropTypes.bool, label: ?string,
label: PropTypes.string,
// ===================================================== // =====================================================
// Required by stripe // Required by stripe
@ -21,16 +19,17 @@ class CardVerify extends React.Component {
// Your publishable key (test or live). // Your publishable key (test or live).
// can't use "key" as a prop in react, so have to change the keyname // can't use "key" as a prop in react, so have to change the keyname
stripeKey: PropTypes.string.isRequired, stripeKey: string,
// The callback to invoke when the Checkout process is complete. // The callback to invoke when the Checkout process is complete.
// function(token) // function(token)
// token is the token object created. // token is the token object created.
// token.id can be used to create a charge or customer. // token.id can be used to create a charge or customer.
// token.email contains the email address entered by the user. // token.email contains the email address entered by the user.
token: PropTypes.func.isRequired, token: string,
}; };
class CardVerify extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {

View file

@ -60,32 +60,13 @@ class ChannelTile extends React.PureComponent<Props> {
> >
<CardMedia title={channelName} thumbnail={null} /> <CardMedia title={channelName} thumbnail={null} />
<div className="file-tile__info"> <div className="file-tile__info">
{isResolvingUri && ( {isResolvingUri && <div className="file-tile__title">{__('Loading...')}</div>}
<div
className={classnames({
'card__title--small': size !== 'large',
'card__title--large': size === 'large',
})}
>
{__('Loading...')}
</div>
)}
{!isResolvingUri && ( {!isResolvingUri && (
<React.Fragment> <React.Fragment>
<div <div className="file-tile__title">
className={classnames({
'card__title--file': size === 'regular',
'card__title--x-small': size === 'small',
'card__title--large': size === 'large',
})}
>
<TruncatedText text={channelName || uri} lines={1} /> <TruncatedText text={channelName || uri} lines={1} />
</div> </div>
<div <div className="card__subtitle">
className={classnames('card__subtitle', {
'card__subtitle--large': size === 'large',
})}
>
{totalItems > 0 && ( {totalItems > 0 && (
<span> <span>
{totalItems} {totalItems === 1 ? 'file' : 'files'} {totalItems} {totalItems === 1 ? 'file' : 'files'}

View file

@ -14,6 +14,7 @@ type Props = {
icon: string, icon: string,
tooltip?: string, // tooltip direction tooltip?: string, // tooltip direction
iconColor?: string, iconColor?: string,
size?: number,
}; };
class IconComponent extends React.PureComponent<Props> { class IconComponent extends React.PureComponent<Props> {
@ -42,7 +43,7 @@ class IconComponent extends React.PureComponent<Props> {
}; };
render() { render() {
const { icon, tooltip, iconColor } = this.props; const { icon, tooltip, iconColor, size } = this.props;
const Icon = FeatherIcons[icon]; const Icon = FeatherIcons[icon];
if (!Icon) { if (!Icon) {
@ -54,16 +55,17 @@ class IconComponent extends React.PureComponent<Props> {
color = this.getIconColor(iconColor); color = this.getIconColor(iconColor);
} }
let size = 14; let iconSize = size || 14;
// Arrow icons are quite a bit smaller than the other icons we use
if (icon === icons.ARROW_LEFT || icon === icons.ARROW_RIGHT) { if (icon === icons.ARROW_LEFT || icon === icons.ARROW_RIGHT) {
size = 20; iconSize = 20;
} }
let tooltipText; let tooltipText;
if (tooltip) { if (tooltip) {
tooltipText = this.getTooltip(icon); tooltipText = this.getTooltip(icon);
} }
const inner = <Icon size={size} className="icon" color={color} />; const inner = <Icon size={iconSize} className="icon" color={color} />;
return tooltipText ? ( return tooltipText ? (
<Tooltip icon body={tooltipText} direction={tooltip}> <Tooltip icon body={tooltipText} direction={tooltip}>

View file

@ -0,0 +1,10 @@
import { connect } from 'react-redux';
import { doNotify } from 'lbry-redux';
import CopyableText from './view';
export default connect(
null,
{
doNotify,
}
)(CopyableText);

View file

@ -0,0 +1,63 @@
// @flow
import * as React from 'react';
import { clipboard } from 'electron';
import { FormRow } from 'component/common/form';
import Button from 'component/button';
import * as icons from 'constants/icons';
/*
noSnackbar added due to issue 1945
https://github.com/lbryio/lbry-desktop/issues/1945
"Snackbars and modals can't be displayed at the same time"
*/
type Props = {
copyable: string,
noSnackbar: boolean,
doNotify: ({ message: string, displayType: Array<string> }) => void,
};
export default class CopyableText extends React.PureComponent<Props> {
constructor() {
super();
this.input = null;
}
input: ?HTMLInputElement;
render() {
const { copyable, doNotify, noSnackbar } = this.props;
return (
<FormRow verticallyCentered padded stretch>
<input
className="input-copyable form-field__input"
readOnly
value={copyable || ''}
ref={input => {
this.input = input;
}}
onFocus={() => {
if (this.input) {
this.input.select();
}
}}
/>
<Button
noPadding
button="secondary"
icon={icons.CLIPBOARD}
onClick={() => {
clipboard.writeText(copyable);
if (!noSnackbar) {
doNotify({
message: __('Text copied'),
displayType: ['snackbar'],
});
}
}}
/>
</FormRow>
);
}
}

View file

@ -91,8 +91,7 @@ class FileCard extends React.PureComponent<Props> {
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
> >
<CardMedia thumbnail={thumbnail} /> <CardMedia thumbnail={thumbnail} />
<div className="card__title-identity"> <div className="card__title card__title--file-card">
<div className="card__title--small card__title--file-card">
<TruncatedText text={title} lines={2} /> <TruncatedText text={title} lines={2} />
</div> </div>
<div className="card__subtitle"> <div className="card__subtitle">
@ -103,7 +102,6 @@ class FileCard extends React.PureComponent<Props> {
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />} {isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
{fileInfo && <Icon icon={icons.LOCAL} />} {fileInfo && <Icon icon={icons.LOCAL} />}
</div> </div>
</div>
</section> </section>
); );
/* eslint-enable jsx-a11y/click-events-have-key-events */ /* eslint-enable jsx-a11y/click-events-have-key-events */

View file

@ -108,36 +108,14 @@ class FileTile extends React.PureComponent<Props> {
> >
<CardMedia title={title || name} thumbnail={thumbnail} /> <CardMedia title={title || name} thumbnail={thumbnail} />
<div className="file-tile__info"> <div className="file-tile__info">
{isResolvingUri && ( {isResolvingUri && <div className="file-tile__title">{__('Loading...')}</div>}
<div
className={classnames({
'card__title--small': size !== 'large',
'card__title--large': size === 'large',
})}
>
{__('Loading...')}
</div>
)}
{!isResolvingUri && ( {!isResolvingUri && (
<React.Fragment> <React.Fragment>
<div <div className="file-tile__title">
className={classnames({
'card__title--file': size === 'regular',
'card__title--x-small': size === 'small',
'card__title--large': size === 'large',
})}
>
<TruncatedText text={title || name} lines={size === 'small' ? 2 : 3} /> <TruncatedText text={title || name} lines={size === 'small' ? 2 : 3} />
</div> </div>
<div <div className="card__subtitle">
className={classnames('card__subtitle', {
'card__subtitle--x-small': size === 'small',
'card__subtitle--large': size === 'large',
})}
>
<span className="file-tile__channel">
{showUri ? uri : <UriIndicator uri={uri} link />} {showUri ? uri : <UriIndicator uri={uri} link />}
</span>
</div> </div>
<div className="card__file-properties"> <div className="card__file-properties">
<FilePrice hideFree uri={uri} /> <FilePrice hideFree uri={uri} />
@ -145,12 +123,7 @@ class FileTile extends React.PureComponent<Props> {
{showLocal && isDownloaded && <Icon icon={icons.LOCAL} />} {showLocal && isDownloaded && <Icon icon={icons.LOCAL} />}
</div> </div>
{displayDescription && ( {displayDescription && (
<div <div className="card__subtext">
className={classnames('card__subtext', {
'card__subtext--small': size !== 'small',
'card__subtext--large': size === 'large',
})}
>
<TruncatedText text={description} lines={size === 'large' ? 4 : 3} /> <TruncatedText text={description} lines={size === 'large' ? 4 : 3} />
</div> </div>
)} )}

View file

@ -1,5 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import FormField from './view';
export default connect(null, null, null, { withRef: true })(FormField);

View file

@ -1,200 +0,0 @@
// This file is going to die
/* eslint-disable */
import React from 'react';
import PropTypes from 'prop-types';
import FileSelector from 'component/common/file-selector';
import SimpleMDE from 'react-simplemde-editor';
import { formFieldNestedLabelTypes, formFieldId } from 'component/common/form';
import style from 'react-simplemde-editor/dist/simplemde.min.css';
const formFieldFileSelectorTypes = ['file', 'directory'];
class FormField extends React.PureComponent {
static propTypes = {
type: PropTypes.string.isRequired,
prefix: PropTypes.string,
postfix: PropTypes.string,
hasError: PropTypes.bool,
trim: PropTypes.bool,
regexp: PropTypes.oneOfType([PropTypes.instanceOf(RegExp), PropTypes.string]),
};
static defaultProps = {
trim: false,
};
constructor(props) {
super(props);
this._fieldRequiredText = __('This field is required');
this._type = null;
this._element = null;
this._extraElementProps = {};
this.state = {
isError: null,
errorMessage: null,
};
}
componentWillMount() {
if (['text', 'number', 'radio', 'checkbox'].includes(this.props.type)) {
this._element = 'input';
this._type = this.props.type;
} else if (this.props.type == 'text-number') {
this._element = 'input';
this._type = 'text';
} else if (this.props.type == 'SimpleMDE') {
this._element = SimpleMDE;
this._type = 'textarea';
this._extraElementProps.options = {
placeholder: this.props.placeholder,
hideIcons: ['heading', 'image', 'fullscreen', 'side-by-side'],
};
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
this._element = 'input';
this._type = 'hidden';
} else {
// Non <input> field, e.g. <select>, <textarea>
this._element = this.props.type;
}
}
componentDidMount() {
/**
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
* https://github.com/facebook/react/issues/3468
*/
if (this.props.type == 'directory') {
this.refs.field.webkitdirectory = true;
}
}
handleFileChosen(path) {
this.refs.field.value = path;
if (this.props.onChange) {
// Updating inputs programmatically doesn't generate an event, so we have to make our own
const event = new Event('change', { bubbles: true });
this.refs.field.dispatchEvent(event); // This alone won't generate a React event, but we use it to attach the field as a target
this.props.onChange(event);
}
}
showError(text) {
this.setState({
isError: true,
errorMessage: text,
});
}
clearError() {
this.setState({
isError: false,
errorMessage: '',
});
}
getValue() {
if (this.props.type == 'checkbox') {
return this.refs.field.checked;
} else if (this.props.type == 'SimpleMDE') {
return this.refs.field.simplemde.value();
}
return this.props.trim ? this.refs.field.value.trim() : this.refs.field.value;
}
getSelectedElement() {
return this.refs.field.options[this.refs.field.selectedIndex];
}
getOptions() {
return this.refs.field.options;
}
validate() {
if ('regexp' in this.props) {
if (!this.getValue().match(this.props.regexp)) {
this.showError(__('Invalid format.'));
} else {
this.clearError();
}
}
this.props.onBlur && this.props.onBlur();
}
focus() {
this.refs.field.focus();
}
render() {
// Pass all unhandled props to the field element
const otherProps = Object.assign({}, this.props),
isError = this.state.isError !== null ? this.state.isError : this.props.hasError,
elementId = this.props.elementId ? this.props.elementId : formFieldId(),
renderElementInsideLabel =
this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
delete otherProps.type;
delete otherProps.label;
delete otherProps.hasError;
delete otherProps.className;
delete otherProps.postfix;
delete otherProps.prefix;
delete otherProps.dispatch;
delete otherProps.regexp;
delete otherProps.trim;
const element = (
<this._element
id={elementId}
type={this._type}
name={this.props.name}
ref="field"
placeholder={this.props.placeholder}
onBlur={() => this.validate()}
onFocus={() => this.props.onFocus && this.props.onFocus()}
className={`form-field__input form-field__input-${this.props.type} ${this.props.className ||
''}${isError ? 'form-field__input--error' : ''}`}
{...otherProps}
{...this._extraElementProps}
>
{this.props.children}
</this._element>
);
return (
<div className={`form-field form-field--${this.props.type}`}>
{this.props.prefix ? <span className="form-field__prefix">{this.props.prefix}</span> : ''}
{element}
{renderElementInsideLabel && (
<label
htmlFor={elementId}
className={`form-field__label ${isError ? 'form-field__label--error' : ''}`}
>
{this.props.label}
</label>
)}
{formFieldFileSelectorTypes.includes(this.props.type) ? (
<FileSelector
type={this.props.type}
onFileChosen={this.handleFileChosen.bind(this)}
{...(this.props.defaultValue ? { initPath: this.props.defaultValue } : {})}
/>
) : null}
{this.props.postfix ? (
<span className="form-field__postfix">{this.props.postfix}</span>
) : (
''
)}
{isError && this.state.errorMessage ? (
<div className="form-field__error">{this.state.errorMessage}</div>
) : (
''
)}
</div>
);
}
}
export default FormField;
/* eslint-enable */

View file

@ -5,13 +5,11 @@ import { CC_LICENSES, COPYRIGHT, OTHER, PUBLIC_DOMAIN, NONE } from 'constants/li
type Props = { type Props = {
licenseType: string, licenseType: string,
copyrightNotice: ?string,
licenseUrl: ?string, licenseUrl: ?string,
otherLicenseDescription: ?string, otherLicenseDescription: ?string,
handleLicenseChange: (string, string) => void, handleLicenseChange: (string, string) => void,
handleLicenseDescriptionChange: (SyntheticInputEvent<*>) => void, handleLicenseDescriptionChange: (SyntheticInputEvent<*>) => void,
handleLicenseUrlChange: (SyntheticInputEvent<*>) => void, handleLicenseUrlChange: (SyntheticInputEvent<*>) => void,
handleCopyrightNoticeChange: (SyntheticInputEvent<*>) => void,
}; };
class LicenseType extends React.PureComponent<Props> { class LicenseType extends React.PureComponent<Props> {
@ -38,11 +36,8 @@ class LicenseType extends React.PureComponent<Props> {
licenseType, licenseType,
otherLicenseDescription, otherLicenseDescription,
licenseUrl, licenseUrl,
copyrightNotice,
handleLicenseChange,
handleLicenseDescriptionChange, handleLicenseDescriptionChange,
handleLicenseUrlChange, handleLicenseUrlChange,
handleCopyrightNoticeChange,
} = this.props; } = this.props;
return ( return (
@ -72,8 +67,8 @@ class LicenseType extends React.PureComponent<Props> {
label={__('Copyright notice')} label={__('Copyright notice')}
type="text" type="text"
name="copyright-notice" name="copyright-notice"
value={copyrightNotice} value={otherLicenseDescription}
onChange={handleCopyrightNoticeChange} onChange={handleLicenseDescriptionChange}
/> />
</FormRow> </FormRow>
)} )}

View file

@ -37,7 +37,6 @@ type Props = {
name: ?string, name: ?string,
tosAccepted: boolean, tosAccepted: boolean,
updatePublishForm: UpdatePublishFormData => void, updatePublishForm: UpdatePublishFormData => void,
bid: number,
nameError: ?string, nameError: ?string,
isResolvingUri: boolean, isResolvingUri: boolean,
winningBidForClaimUri: number, winningBidForClaimUri: number,
@ -45,7 +44,6 @@ type Props = {
licenseType: string, licenseType: string,
otherLicenseDescription: ?string, otherLicenseDescription: ?string,
licenseUrl: ?string, licenseUrl: ?string,
copyrightNotice: ?string,
uri: ?string, uri: ?string,
bidError: ?string, bidError: ?string,
publishing: boolean, publishing: boolean,
@ -200,7 +198,6 @@ class PublishForm extends React.PureComponent<Props> {
handlePublish() { handlePublish() {
const { const {
filePath, filePath,
copyrightNotice,
licenseType, licenseType,
licenseUrl, licenseUrl,
otherLicenseDescription, otherLicenseDescription,
@ -211,8 +208,6 @@ class PublishForm extends React.PureComponent<Props> {
let publishingLicense; let publishingLicense;
switch (licenseType) { switch (licenseType) {
case COPYRIGHT: case COPYRIGHT:
publishingLicense = copyrightNotice;
break;
case OTHER: case OTHER:
publishingLicense = otherLicenseDescription; publishingLicense = otherLicenseDescription;
break; break;
@ -233,7 +228,6 @@ class PublishForm extends React.PureComponent<Props> {
license: publishingLicense, license: publishingLicense,
licenseUrl: publishingLicenseUrl, licenseUrl: publishingLicenseUrl,
otherLicenseDescription, otherLicenseDescription,
copyrightNotice,
name: this.props.name, name: this.props.name,
contentIsFree: this.props.contentIsFree, contentIsFree: this.props.contentIsFree,
price: this.props.price, price: this.props.price,
@ -301,7 +295,7 @@ class PublishForm extends React.PureComponent<Props> {
{!title && <div>{__('A title is required')}</div>} {!title && <div>{__('A title is required')}</div>}
{!name && <div>{__('A URL is required')}</div>} {!name && <div>{__('A URL is required')}</div>}
{name && nameError && <div>{__('The URL you created is not valid')}</div>} {name && nameError && <div>{__('The URL you created is not valid')}</div>}
{!bid && <div>{__('A bid amount is required')}</div>} {!bid && <div>{__('A deposit amount is required')}</div>}
{!!bid && bidError && <div>{bidError}</div>} {!!bid && bidError && <div>{bidError}</div>}
{uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS && ( {uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS && (
<div>{__('Please wait for thumbnail to finish uploading')}</div> <div>{__('Please wait for thumbnail to finish uploading')}</div>
@ -339,7 +333,6 @@ class PublishForm extends React.PureComponent<Props> {
licenseType, licenseType,
otherLicenseDescription, otherLicenseDescription,
licenseUrl, licenseUrl,
copyrightNotice,
uri, uri,
bidError, bidError,
publishing, publishing,
@ -535,7 +528,7 @@ class PublishForm extends React.PureComponent<Props> {
step="any" step="any"
label={__('Deposit')} label={__('Deposit')}
postfix="LBC" postfix="LBC"
value={bid} value={bid || ''}
error={bidError} error={bidError}
min="0" min="0"
disabled={!name} disabled={!name}
@ -585,7 +578,6 @@ class PublishForm extends React.PureComponent<Props> {
licenseType={licenseType} licenseType={licenseType}
otherLicenseDescription={otherLicenseDescription} otherLicenseDescription={otherLicenseDescription}
licenseUrl={licenseUrl} licenseUrl={licenseUrl}
copyrightNotice={copyrightNotice}
handleLicenseChange={(newLicenseType, newLicenseUrl) => handleLicenseChange={(newLicenseType, newLicenseUrl) =>
updatePublishForm({ updatePublishForm({
licenseType: newLicenseType, licenseType: newLicenseType,
@ -600,9 +592,6 @@ class PublishForm extends React.PureComponent<Props> {
handleLicenseUrlChange={event => handleLicenseUrlChange={event =>
updatePublishForm({ licenseUrl: event.target.value }) updatePublishForm({ licenseUrl: event.target.value })
} }
handleCopyrightNoticeChange={event =>
updatePublishForm({ copyrightNotice: event.target.value })
}
/> />
</section> </section>

View file

@ -1,6 +1,10 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUnclaimedRewardValue, selectFetchingRewards, doRewardList } from 'lbryinc'; import {
import { doFetchRewardedContent } from 'redux/actions/content'; selectUnclaimedRewardValue,
selectFetchingRewards,
doRewardList,
doFetchRewardedContent,
} from 'lbryinc';
import RewardSummary from './view'; import RewardSummary from './view';
const select = state => ({ const select = state => ({

View file

@ -89,7 +89,7 @@ class SelectThumbnail extends React.PureComponent<Props, State> {
type="text" type="text"
name="content_thumbnail" name="content_thumbnail"
label="URL" label="URL"
placeholder="http://spee.ch/mylogo" placeholder="https://spee.ch/mylogo"
value={thumbnail} value={thumbnail}
disabled={formDisabled} disabled={formDisabled}
onChange={this.handleThumbnailChange} onChange={this.handleThumbnailChange}

View file

@ -101,7 +101,7 @@ class ActiveShapeShift extends React.PureComponent<Props> {
)} )}
{shiftState === statuses.RECEIVED && ( {shiftState === statuses.RECEIVED && (
<div className="card__content--extra-vertical-space"> <div>
<p> <p>
{__('ShapeShift has received your payment! Sending the funds to your LBRY wallet.')} {__('ShapeShift has received your payment! Sending the funds to your LBRY wallet.')}
</p> </p>
@ -110,7 +110,7 @@ class ActiveShapeShift extends React.PureComponent<Props> {
)} )}
{shiftState === statuses.COMPLETE && ( {shiftState === statuses.COMPLETE && (
<div className="card__content--extra-vertical-space"> <div>
<p>{__('Transaction complete! You should see the new LBC in your wallet.')}</p> <p>{__('Transaction complete! You should see the new LBC in your wallet.')}</p>
</div> </div>
)} )}

View file

@ -3,12 +3,13 @@ import React from 'react';
import type { Claim } from 'types/claim'; import type { Claim } from 'types/claim';
import Button from 'component/button'; import Button from 'component/button';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
import Tooltip from 'component/common/tooltip'; import CopyableText from 'component/copyableText';
import Address from 'component/address'; import ToolTip from 'component/common/tooltip';
type Props = { type Props = {
claim: Claim, claim: Claim,
onDone: () => void, onDone: () => void,
speechShareable: boolean,
}; };
class SocialShare extends React.PureComponent<Props> { class SocialShare extends React.PureComponent<Props> {
@ -27,20 +28,27 @@ class SocialShare extends React.PureComponent<Props> {
channel_name: channelName, channel_name: channelName,
value, value,
} = this.props.claim; } = this.props.claim;
const { speechShareable, onDone } = this.props;
const channelClaimId = const channelClaimId =
value && value.publisherSignature && value.publisherSignature.certificateId; value && value.publisherSignature && value.publisherSignature.certificateId;
const { onDone } = this.props; const speechPrefix = 'https://spee.ch/';
const speechPrefix = 'http://spee.ch/'; const lbryPrefix = 'https://open.lbry.io/';
const speechURL = const speechURL =
channelName && channelClaimId channelName && channelClaimId
? `${speechPrefix}${channelName}:${channelClaimId}/${claimName}` ? `${speechPrefix}${channelName}:${channelClaimId}/${claimName}`
: `${speechPrefix}${claimName}#${claimId}`; : `${speechPrefix}${claimName}#${claimId}`;
const lbryURL = `${lbryPrefix}${claimName}#${claimId}`;
return ( return (
<section className="card__content"> <section className="card__content">
<Address address={speechURL} noSnackbar /> {speechShareable && (
<div className="card__content">
<label className="card__subtitle">{__('Web link')}</label>
<CopyableText copyable={speechURL} noSnackbar />
<div className="card__actions card__actions--center"> <div className="card__actions card__actions--center">
<Tooltip onComponent body={__('Facebook')}> <ToolTip onComponent body={__('Facebook')}>
<Button <Button
iconColor="blue" iconColor="blue"
icon={icons.FACEBOOK} icon={icons.FACEBOOK}
@ -48,8 +56,8 @@ class SocialShare extends React.PureComponent<Props> {
label={__('')} label={__('')}
href={`https://facebook.com/sharer/sharer.php?u=${speechURL}`} href={`https://facebook.com/sharer/sharer.php?u=${speechURL}`}
/> />
</Tooltip> </ToolTip>
<Tooltip onComponent body={__('Twitter')}> <ToolTip onComponent body={__('Twitter')}>
<Button <Button
iconColor="blue" iconColor="blue"
icon={icons.TWITTER} icon={icons.TWITTER}
@ -57,8 +65,8 @@ class SocialShare extends React.PureComponent<Props> {
label={__('')} label={__('')}
href={`https://twitter.com/home?status=${speechURL}`} href={`https://twitter.com/home?status=${speechURL}`}
/> />
</Tooltip> </ToolTip>
<Tooltip onComponent body={__('View on Spee.ch')}> <ToolTip onComponent body={__('View on Spee.ch')}>
<Button <Button
icon={icons.GLOBE} icon={icons.GLOBE}
iconColor="blue" iconColor="blue"
@ -66,7 +74,33 @@ class SocialShare extends React.PureComponent<Props> {
label={__('')} label={__('')}
href={`${speechURL}`} href={`${speechURL}`}
/> />
</Tooltip> </ToolTip>
</div>
</div>
)}
<div className="card__content">
<label className="card__subtitle">{__('LBRY App link')}</label>
<CopyableText copyable={lbryURL} noSnackbar />
<div className="card__actions card__actions--center">
<ToolTip onComponent body={__('Facebook')}>
<Button
iconColor="blue"
icon={icons.FACEBOOK}
button="alt"
label={__('')}
href={`https://facebook.com/sharer/sharer.php?u=${lbryURL}`}
/>
</ToolTip>
<ToolTip onComponent body={__('Twitter')}>
<Button
iconColor="blue"
icon={icons.TWITTER}
button="alt"
label={__('')}
href={`https://twitter.com/home?status=${lbryURL}`}
/>
</ToolTip>
</div>
</div> </div>
<div className="card__actions"> <div className="card__actions">
<Button button="link" label={__('Done')} onClick={onDone} /> <Button button="link" label={__('Done')} onClick={onDone} />

View file

@ -24,6 +24,10 @@ class TransactionListRecent extends React.PureComponent<Props> {
return ( return (
<section className="card card--section"> <section className="card card--section">
<div className="card__title">{__('Recent Transactions')}</div> <div className="card__title">{__('Recent Transactions')}</div>
<div className="card__subtitle">
{__('To view all of your transactions, navigate to the')}{' '}
<Button button="link" navigate="/history" label={__('transactions page')} />.
</div>
{fetchingTransactions && ( {fetchingTransactions && (
<div className="card__content"> <div className="card__content">
<BusyIndicator message={__('Loading transactions')} /> <BusyIndicator message={__('Loading transactions')} />

View file

@ -1,5 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import TruncatedMarkdown from './view';
export default connect()(TruncatedMarkdown);

View file

@ -1,38 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactMarkdown from 'react-markdown';
import ReactDOMServer from 'react-dom/server';
class TruncatedMarkdown extends React.PureComponent {
static propTypes = {
lines: PropTypes.number,
};
static defaultProps = {
lines: null,
};
transformMarkdown(text) {
// render markdown to html string then trim html tag
const htmlString = ReactDOMServer.renderToStaticMarkup(
<ReactMarkdown source={this.props.children} />
);
const txt = document.createElement('textarea');
txt.innerHTML = htmlString;
return txt.value.replace(/<(?:.|\n)*?>/gm, '');
}
render() {
const content =
this.props.children && typeof this.props.children === 'string'
? this.transformMarkdown(this.props.children)
: this.props.children;
return (
<span className="truncated-text" style={{ WebkitLineClamp: this.props.lines }}>
{content}
</span>
);
}
}
export default TruncatedMarkdown;

View file

@ -2,7 +2,6 @@
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import { buildURI } from 'lbry-redux'; import { buildURI } from 'lbry-redux';
import classnames from 'classnames';
import type { Claim } from 'types/claim'; import type { Claim } from 'types/claim';
type Props = { type Props = {
@ -59,17 +58,7 @@ class UriIndicator extends React.PureComponent<Props> {
channelLink = link ? buildURI({ channelName, claimId: channelClaimId }) : false; channelLink = link ? buildURI({ channelName, claimId: channelClaimId }) : false;
} }
const inner = ( const inner = <span className="channel-name">{channelName}</span>;
<span>
<span
className={classnames('channel-name', {
'button-text no-underline': link,
})}
>
{channelName}
</span>{' '}
</span>
);
if (!channelLink) { if (!channelLink) {
return inner; return inner;

View file

@ -99,7 +99,8 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
const allSelected = Object.keys(itemsSelected).length === history.length; const allSelected = Object.keys(itemsSelected).length === history.length;
const selectHandler = allSelected ? this.unselectAll : this.selectAll; const selectHandler = allSelected ? this.unselectAll : this.selectAll;
return (
return history.length ? (
<React.Fragment> <React.Fragment>
<div className="card__actions card__actions--between"> <div className="card__actions card__actions--between">
{Object.keys(itemsSelected).length ? ( {Object.keys(itemsSelected).length ? (
@ -109,7 +110,6 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
{/* Using an empty span so spacing stays the same if the button isn't rendered */} {/* Using an empty span so spacing stays the same if the button isn't rendered */}
</span> </span>
)} )}
<Button <Button
button="link" button="link"
label={allSelected ? __('Cancel') : __('Select All')} label={allSelected ? __('Cancel') : __('Select All')}
@ -117,8 +117,7 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
/> />
</div> </div>
{!!history.length && ( {!!history.length && (
<table className="card--section table table--stretch table--history"> <section className="item-list">
<tbody>
{history.map(item => ( {history.map(item => (
<UserHistoryItem <UserHistoryItem
key={item.uri} key={item.uri}
@ -130,8 +129,7 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
}} }}
/> />
))} ))}
</tbody> </section>
</table>
)} )}
{pageCount > 1 && ( {pageCount > 1 && (
<FormRow padded verticallyCentered centered> <FormRow padded verticallyCentered centered>
@ -161,6 +159,13 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
</FormRow> </FormRow>
)} )}
</React.Fragment> </React.Fragment>
) : (
<div className="page__empty">
{__("You don't have anything saved in history yet, go check out some content on LBRY!")}
<div className="card__actions card__actions--center">
<Button button="primary" navigate="/discover" label={__('Explore new content')} />
</div>
</div>
); );
} }
} }

View file

@ -36,18 +36,16 @@ class UserHistoryItem extends React.PureComponent<Props> {
} }
return ( return (
<tr <div
role="button"
onClick={onSelect} onClick={onSelect}
className={classnames({ className={classnames('item-list__item', {
history__selected: selected, 'item-list__item--selected': selected,
})} })}
> >
<td>
<input checked={selected} type="checkbox" onClick={onSelect} /> <input checked={selected} type="checkbox" onClick={onSelect} />
</td> <span className="time time--ago">{moment(lastViewed).from(moment())}</span>
<td>{moment(lastViewed).from(moment())}</td> <span className="item-list__item--cutoff">{title}</span>
<td>{title}</td>
<td>
<Button <Button
tourniquet tourniquet
button="link" button="link"
@ -55,8 +53,7 @@ class UserHistoryItem extends React.PureComponent<Props> {
navigate="/show" navigate="/show"
navigateParams={{ uri }} navigateParams={{ uri }}
/> />
</td> </div>
</tr>
); );
} }
} }

View file

@ -2,7 +2,7 @@
import * as React from 'react'; import * as React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import CardVerify from 'component/cardVerify'; import CardVerify from 'component/cardVerify';
import Lbryio from 'lbryinc'; import { Lbryio } from 'lbryinc';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
type Props = { type Props = {

View file

@ -19,7 +19,7 @@ export default (props: Props) => {
icon={icons.GLOBE} icon={icons.GLOBE}
button="alt" button="alt"
label={__('Share')} label={__('Share')}
href={`http://spee.ch/${speechURL}`} href={`https://spee.ch/${speechURL}`}
/> />
) : null; ) : null;
}; };

View file

@ -16,7 +16,6 @@ https://github.com/reactjs/react-autocomplete/issues/239
/* eslint-disable */ /* eslint-disable */
const React = require('react'); const React = require('react');
const PropTypes = require('prop-types');
const { findDOMNode } = require('react-dom'); const { findDOMNode } = require('react-dom');
const scrollIntoView = require('dom-scroll-into-view'); const scrollIntoView = require('dom-scroll-into-view');
@ -45,129 +44,129 @@ function getScrollOffset() {
} }
export default class Autocomplete extends React.Component { export default class Autocomplete extends React.Component {
static propTypes = { // static propTypes = {
/** // /**
* The items to display in the dropdown menu // * The items to display in the dropdown menu
*/ // */
items: PropTypes.array.isRequired, // items: PropTypes.array.isRequired,
/** // /**
* The value to display in the input field // * The value to display in the input field
*/ // */
value: PropTypes.any, // value: PropTypes.any,
/** // /**
* Arguments: `event: Event, value: String` // * Arguments: `event: Event, value: String`
* // *
* Invoked every time the user changes the input's value. // * Invoked every time the user changes the input's value.
*/ // */
onChange: PropTypes.func, // onChange: PropTypes.func,
/** // /**
* Arguments: `value: String, item: Any` // * Arguments: `value: String, item: Any`
* // *
* Invoked when the user selects an item from the dropdown menu. // * Invoked when the user selects an item from the dropdown menu.
*/ // */
onSelect: PropTypes.func, // onSelect: PropTypes.func,
/** // /**
* Arguments: `item: Any, value: String` // * Arguments: `item: Any, value: String`
* // *
* Invoked for each entry in `items` and its return value is used to // * Invoked for each entry in `items` and its return value is used to
* determine whether or not it should be displayed in the dropdown menu. // * determine whether or not it should be displayed in the dropdown menu.
* By default all items are always rendered. // * By default all items are always rendered.
*/ // */
shouldItemRender: PropTypes.func, // shouldItemRender: PropTypes.func,
/** // /**
* Arguments: `itemA: Any, itemB: Any, value: String` // * Arguments: `itemA: Any, itemB: Any, value: String`
* // *
* The function which is used to sort `items` before display. // * The function which is used to sort `items` before display.
*/ // */
sortItems: PropTypes.func, // sortItems: PropTypes.func,
/** // /**
* Arguments: `item: Any` // * Arguments: `item: Any`
* // *
* Used to read the display value from each entry in `items`. // * Used to read the display value from each entry in `items`.
*/ // */
getItemValue: PropTypes.func.isRequired, // getItemValue: PropTypes.func.isRequired,
/** // /**
* Arguments: `item: Any, isHighlighted: Boolean, styles: Object` // * Arguments: `item: Any, isHighlighted: Boolean, styles: Object`
* // *
* Invoked for each entry in `items` that also passes `shouldItemRender` to // * Invoked for each entry in `items` that also passes `shouldItemRender` to
* generate the render tree for each item in the dropdown menu. `styles` is // * generate the render tree for each item in the dropdown menu. `styles` is
* an optional set of styles that can be applied to improve the look/feel // * an optional set of styles that can be applied to improve the look/feel
* of the items in the dropdown menu. // * of the items in the dropdown menu.
*/ // */
renderItem: PropTypes.func.isRequired, // renderItem: PropTypes.func.isRequired,
/** // /**
* Arguments: `items: Array<Any>, value: String, styles: Object` // * Arguments: `items: Array<Any>, value: String, styles: Object`
* // *
* Invoked to generate the render tree for the dropdown menu. Ensure the // * Invoked to generate the render tree for the dropdown menu. Ensure the
* returned tree includes every entry in `items` or else the highlight order // * returned tree includes every entry in `items` or else the highlight order
* and keyboard navigation logic will break. `styles` will contain // * and keyboard navigation logic will break. `styles` will contain
* { top, left, minWidth } which are the coordinates of the top-left corner // * { top, left, minWidth } which are the coordinates of the top-left corner
* and the width of the dropdown menu. // * and the width of the dropdown menu.
*/ // */
renderMenu: PropTypes.func, // renderMenu: PropTypes.func,
/** // /**
* Styles that are applied to the dropdown menu in the default `renderMenu` // * Styles that are applied to the dropdown menu in the default `renderMenu`
* implementation. If you override `renderMenu` and you want to use // * implementation. If you override `renderMenu` and you want to use
* `menuStyle` you must manually apply them (`this.props.menuStyle`). // * `menuStyle` you must manually apply them (`this.props.menuStyle`).
*/ // */
menuStyle: PropTypes.object, // menuStyle: PropTypes.object,
/** // /**
* Arguments: `props: Object` // * Arguments: `props: Object`
* // *
* Invoked to generate the input element. The `props` argument is the result // * Invoked to generate the input element. The `props` argument is the result
* of merging `props.inputProps` with a selection of props that are required // * of merging `props.inputProps` with a selection of props that are required
* both for functionality and accessibility. At the very least you need to // * both for functionality and accessibility. At the very least you need to
* apply `props.ref` and all `props.on<event>` event handlers. Failing to do // * apply `props.ref` and all `props.on<event>` event handlers. Failing to do
* this will cause `Autocomplete` to behave unexpectedly. // * this will cause `Autocomplete` to behave unexpectedly.
*/ // */
renderInput: PropTypes.func, // renderInput: PropTypes.func,
/** // /**
* Props passed to `props.renderInput`. By default these props will be // * Props passed to `props.renderInput`. By default these props will be
* applied to the `<input />` element rendered by `Autocomplete`, unless you // * applied to the `<input />` element rendered by `Autocomplete`, unless you
* have specified a custom value for `props.renderInput`. Any properties // * have specified a custom value for `props.renderInput`. Any properties
* supported by `HTMLInputElement` can be specified, apart from the // * supported by `HTMLInputElement` can be specified, apart from the
* following which are set by `Autocomplete`: value, autoComplete, role, // * following which are set by `Autocomplete`: value, autoComplete, role,
* aria-autocomplete. `inputProps` is commonly used for (but not limited to) // * aria-autocomplete. `inputProps` is commonly used for (but not limited to)
* placeholder, event handlers (onFocus, onBlur, etc.), autoFocus, etc.. // * placeholder, event handlers (onFocus, onBlur, etc.), autoFocus, etc..
*/ // */
inputProps: PropTypes.object, // inputProps: PropTypes.object,
/** // /**
* Props that are applied to the element which wraps the `<input />` and // * Props that are applied to the element which wraps the `<input />` and
* dropdown menu elements rendered by `Autocomplete`. // * dropdown menu elements rendered by `Autocomplete`.
*/ // */
wrapperProps: PropTypes.object, // wrapperProps: PropTypes.object,
/** // /**
* This is a shorthand for `wrapperProps={{ style: <your styles> }}`. // * This is a shorthand for `wrapperProps={{ style: <your styles> }}`.
* Note that `wrapperStyle` is applied before `wrapperProps`, so the latter // * Note that `wrapperStyle` is applied before `wrapperProps`, so the latter
* will win if it contains a `style` entry. // * will win if it contains a `style` entry.
*/ // */
wrapperStyle: PropTypes.object, // wrapperStyle: PropTypes.object,
/** // /**
* Whether or not to automatically highlight the top match in the dropdown // * Whether or not to automatically highlight the top match in the dropdown
* menu. // * menu.
*/ // */
autoHighlight: PropTypes.bool, // autoHighlight: PropTypes.bool,
/** // /**
* Whether or not to automatically select the highlighted item when the // * Whether or not to automatically select the highlighted item when the
* `<input>` loses focus. // * `<input>` loses focus.
*/ // */
selectOnBlur: PropTypes.bool, // selectOnBlur: PropTypes.bool,
/** // /**
* Arguments: `isOpen: Boolean` // * Arguments: `isOpen: Boolean`
* // *
* Invoked every time the dropdown menu's visibility changes (i.e. every // * Invoked every time the dropdown menu's visibility changes (i.e. every
* time it is displayed/hidden). // * time it is displayed/hidden).
*/ // */
onMenuVisibilityChange: PropTypes.func, // onMenuVisibilityChange: PropTypes.func,
/** // /**
* Used to override the internal logic which displays/hides the dropdown // * Used to override the internal logic which displays/hides the dropdown
* menu. This is useful if you want to force a certain state based on your // * menu. This is useful if you want to force a certain state based on your
* UX/business logic. Use it together with `onMenuVisibilityChange` for // * UX/business logic. Use it together with `onMenuVisibilityChange` for
* fine-grained control over the dropdown menu dynamics. // * fine-grained control over the dropdown menu dynamics.
*/ // */
open: PropTypes.bool, // open: PropTypes.bool,
debug: PropTypes.bool, // debug: PropTypes.bool,
}; // };
static defaultProps = { static defaultProps = {
value: '', value: '',

View file

@ -7,24 +7,36 @@ import { parseQueryParams } from 'util/query_params';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
import Autocomplete from './internal/autocomplete'; import Autocomplete from './internal/autocomplete';
const L_KEY_CODE = 76;
const ESC_KEY_CODE = 27;
type Props = { type Props = {
updateSearchQuery: string => void, updateSearchQuery: string => void,
onSearch: string => void, onSearch: (string, ?number) => void,
onSubmit: (string, {}) => void, onSubmit: (string, {}) => void,
wunderbarValue: ?string, wunderbarValue: ?string,
suggestions: Array<string>, suggestions: Array<string>,
doFocus: () => void, doFocus: () => void,
doBlur: () => void, doBlur: () => void,
resultCount: number, resultCount: number,
focused: boolean,
}; };
class WunderBar extends React.PureComponent<Props> { class WunderBar extends React.PureComponent<Props> {
constructor(props: Props) { constructor() {
super(props); super();
(this: any).handleSubmit = this.handleSubmit.bind(this); (this: any).handleSubmit = this.handleSubmit.bind(this);
(this: any).handleChange = this.handleChange.bind(this); (this: any).handleChange = this.handleChange.bind(this);
this.input = undefined; (this: any).handleKeyDown = this.handleKeyDown.bind(this);
}
componentDidMount() {
window.addEventListener('keydown', this.handleKeyDown);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.handleKeyDown);
} }
getSuggestionIcon = (type: string) => { getSuggestionIcon = (type: string) => {
@ -38,6 +50,31 @@ class WunderBar extends React.PureComponent<Props> {
} }
}; };
handleKeyDown(event: SyntheticKeyboardEvent<*>) {
const { ctrlKey, metaKey, keyCode } = event;
const { doFocus, doBlur, focused } = this.props;
if (!this.input) {
return;
}
if (focused && keyCode === ESC_KEY_CODE) {
doBlur();
this.input.blur();
return;
}
const shouldFocus =
process.platform === 'darwin'
? keyCode === L_KEY_CODE && metaKey
: keyCode === L_KEY_CODE && ctrlKey;
if (shouldFocus) {
doFocus();
this.input.focus();
}
}
handleChange(e: SyntheticInputEvent<*>) { handleChange(e: SyntheticInputEvent<*>) {
const { updateSearchQuery } = this.props; const { updateSearchQuery } = this.props;
const { value } = e.target; const { value } = e.target;
@ -106,6 +143,10 @@ class WunderBar extends React.PureComponent<Props> {
renderInput={props => ( renderInput={props => (
<input <input
{...props} {...props}
ref={el => {
props.ref(el);
this.input = el;
}}
className="wunderbar__input" className="wunderbar__input"
placeholder="Enter LBRY URL here or search for videos, music, games and more" placeholder="Enter LBRY URL here or search for videos, music, games and more"
/> />

View file

@ -6,14 +6,15 @@ import SocialShare from 'component/socialShare';
type Props = { type Props = {
closeModal: () => void, closeModal: () => void,
uri: string, uri: string,
speechShareable: boolean,
}; };
class ModalSocialShare extends React.PureComponent<Props> { class ModalSocialShare extends React.PureComponent<Props> {
render() { render() {
const { closeModal, uri } = this.props; const { closeModal, uri, speechShareable } = this.props;
return ( return (
<Modal isOpen onAborted={closeModal} type="custom" title={__('Share')}> <Modal isOpen onAborted={closeModal} type="custom" title={__('Share')}>
<SocialShare uri={uri} onDone={closeModal} /> <SocialShare uri={uri} onDone={closeModal} speechShareable={speechShareable} />
</Modal> </Modal>
); );
} }

View file

@ -80,17 +80,17 @@ class ChannelPage extends React.PureComponent<Props> {
return ( return (
<Page notContained> <Page notContained>
<section className="card__channel-info card__channel-info--large"> <section>
<h1> <h1>
{name} {name}
{fetching && <BusyIndicator />} {fetching && <BusyIndicator />}
</h1> </h1>
<div className="card__actions card__actions--no-margin"> </section>
<div className="card__actions">
<SubscribeButton uri={permanentUrl} channelName={name} /> <SubscribeButton uri={permanentUrl} channelName={name} />
<ViewOnWebButton claimId={claimId} claimName={name} /> <ViewOnWebButton claimId={claimId} claimName={name} />
</div> </div>
</section> <section className="card__content">{contentList}</section>
<section>{contentList}</section>
{(!fetching || (claimsInChannel && claimsInChannel.length)) && {(!fetching || (claimsInChannel && claimsInChannel.length)) &&
totalPages > 1 && ( totalPages > 1 && (
<FormRow verticallyCentered centered> <FormRow verticallyCentered centered>

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doFetchFeaturedUris, doFetchRewardedContent } from 'redux/actions/content'; import { selectFeaturedUris, selectFetchingFeaturedUris, doFetchFeaturedUris } from 'lbry-redux';
import { selectFeaturedUris, selectFetchingFeaturedUris } from 'redux/selectors/content'; import { doFetchRewardedContent } from 'lbryinc';
import DiscoverPage from './view'; import DiscoverPage from './view';
const select = state => ({ const select = state => ({

View file

@ -145,12 +145,11 @@ class FilePage extends React.Component<Props> {
if (channelName && channelClaimId) { if (channelName && channelClaimId) {
subscriptionUri = buildURI({ channelName, claimId: channelClaimId }, false); subscriptionUri = buildURI({ channelName, claimId: channelClaimId }, false);
} }
const speechSharable = const speechShareable =
costInfo && costInfo &&
costInfo.cost === 0 && costInfo.cost === 0 &&
contentType && contentType &&
['video', 'image'].includes(contentType.split('/')[0]); ['video', 'image'].includes(contentType.split('/')[0]);
// We want to use the short form uri for editing // We want to use the short form uri for editing
// This is what the user is used to seeing, they don't care about the claim id // This is what the user is used to seeing, they don't care about the claim id
// We will select the claim id before they publish // We will select the claim id before they publish
@ -184,21 +183,20 @@ class FilePage extends React.Component<Props> {
))} ))}
<div className="card__content"> <div className="card__content">
<div className="card__title-identity--file"> <div className="card__title__space-between">
<h1 className="card__title card__title--file">{title}</h1> <h1>{title}</h1>
<div className="card__title-identity-icons"> <div className="card__title-identity-icons">
{isRewardContent && <Icon iconColor="red" tooltip="bottom" icon={icons.FEATURED} />} {isRewardContent && (
<Icon size={20} iconColor="red" tooltip="bottom" icon={icons.FEATURED} />
)}
<FilePrice filePage uri={normalizeURI(uri)} /> <FilePrice filePage uri={normalizeURI(uri)} />
</div> </div>
</div> </div>
<span className="card__subtitle card__subtitle--file"> <span className="card__subtitle">
{__('Published on')}&nbsp; <UriIndicator uri={uri} link /> {__('published on')}{' '}
<DateTime block={height} show={DateTime.SHOW_DATE} /> <DateTime block={height} show={DateTime.SHOW_DATE} />
</span> </span>
{metadata.nsfw && <div>NSFW</div>} {metadata.nsfw && <div>NSFW</div>}
<div className="card__channel-info">
<UriIndicator uri={uri} link />
</div>
<div className="card__actions card__actions--no-margin card__actions--between"> <div className="card__actions card__actions--no-margin card__actions--between">
<div className="card__actions"> <div className="card__actions">
{claimIsMine ? ( {claimIsMine ? (
@ -222,7 +220,7 @@ class FilePage extends React.Component<Props> {
onClick={() => openModal({ id: MODALS.SEND_TIP }, { uri })} onClick={() => openModal({ id: MODALS.SEND_TIP }, { uri })}
/> />
)} )}
{speechSharable && ( {speechShareable && (
<Button <Button
button="alt" button="alt"
icon={icons.GLOBE} icon={icons.GLOBE}
@ -237,7 +235,7 @@ class FilePage extends React.Component<Props> {
<FileActions uri={uri} claimId={claim.claim_id} /> <FileActions uri={uri} claimId={claim.claim_id} />
</div> </div>
</div> </div>
<FormRow padded> <FormRow>
<ToolTip direction="right" body={__('Automatically download and play free content.')}> <ToolTip direction="right" body={__('Automatically download and play free content.')}>
<FormField <FormField
name="autoplay" name="autoplay"
@ -248,7 +246,7 @@ class FilePage extends React.Component<Props> {
/> />
</ToolTip> </ToolTip>
</FormRow> </FormRow>
<div className="card__content--extra-padding"> <div className="card__content">
<FileDetails uri={uri} /> <FileDetails uri={uri} />
</div> </div>
</div> </div>

View file

@ -199,7 +199,7 @@ class HelpPage extends React.PureComponent<Props, State> {
)} )}
{this.state.uiVersion && ver ? ( {this.state.uiVersion && ver ? (
<table className="table table--stretch table--help"> <table className="card__content table table--stretch table--help">
<tbody> <tbody>
<tr> <tr>
<td>{__('App')}</td> <td>{__('App')}</td>

View file

@ -42,8 +42,8 @@ class ShowPage extends React.PureComponent<Props> {
if ((isResolvingUri && !claim) || !claim) { if ((isResolvingUri && !claim) || !claim) {
const { claimName } = parseURI(uri); const { claimName } = parseURI(uri);
innerContent = ( innerContent = (
<Page> <Page notContained>
<section className="card"> <section>
<h1>{claimName}</h1> <h1>{claimName}</h1>
<div className="card__content"> <div className="card__content">
{isResolvingUri && <BusyIndicator message={__('Loading decentralized data...')} />} {isResolvingUri && <BusyIndicator message={__('Loading decentralized data...')} />}

View file

@ -13,7 +13,6 @@ import {
doHideNotification, doHideNotification,
} from 'lbry-redux'; } from 'lbry-redux';
import Native from 'native'; import Native from 'native';
import { doFetchRewardedContent } from 'redux/actions/content';
import { doFetchDaemonSettings } from 'redux/actions/settings'; import { doFetchDaemonSettings } from 'redux/actions/settings';
import { doAuthNavigate } from 'redux/actions/navigation'; import { doAuthNavigate } from 'redux/actions/navigation';
import { doCheckSubscriptionsInit } from 'redux/actions/subscriptions'; import { doCheckSubscriptionsInit } from 'redux/actions/subscriptions';
@ -27,7 +26,7 @@ import {
selectRemoteVersion, selectRemoteVersion,
selectUpgradeTimer, selectUpgradeTimer,
} from 'redux/selectors/app'; } from 'redux/selectors/app';
import { doAuthenticate } from 'lbryinc'; import { doAuthenticate, doFetchRewardedContent } from 'lbryinc';
import { lbrySettings as config, version as appVersion } from 'package.json'; import { lbrySettings as config, version as appVersion } from 'package.json';
const { autoUpdater } = remote.require('electron-updater'); const { autoUpdater } = remote.require('electron-updater');

View file

@ -6,7 +6,6 @@ import { doNavigate } from 'redux/actions/navigation';
import { import {
setSubscriptionLatest, setSubscriptionLatest,
setSubscriptionNotification, setSubscriptionNotification,
setSubscriptionNotifications,
} from 'redux/actions/subscriptions'; } from 'redux/actions/subscriptions';
import { selectNotifications } from 'redux/selectors/subscriptions'; import { selectNotifications } from 'redux/selectors/subscriptions';
import { selectBadgeNumber } from 'redux/selectors/app'; import { selectBadgeNumber } from 'redux/selectors/app';
@ -16,8 +15,6 @@ import {
Lbry, Lbry,
Lbryapi, Lbryapi,
buildURI, buildURI,
batchActions,
doResolveUris,
doFetchClaimListMine, doFetchClaimListMine,
makeSelectCostInfoForUri, makeSelectCostInfoForUri,
makeSelectFileInfoForUri, makeSelectFileInfoForUri,
@ -31,74 +28,9 @@ import { makeSelectClientSetting, selectosNotificationsEnabled } from 'redux/sel
import setBadge from 'util/setBadge'; import setBadge from 'util/setBadge';
import setProgressBar from 'util/setProgressBar'; import setProgressBar from 'util/setProgressBar';
import analytics from 'analytics'; import analytics from 'analytics';
import { Lbryio } from 'lbryinc';
const DOWNLOAD_POLL_INTERVAL = 250; const DOWNLOAD_POLL_INTERVAL = 250;
export function doFetchFeaturedUris() {
return dispatch => {
dispatch({
type: ACTIONS.FETCH_FEATURED_CONTENT_STARTED,
});
const success = ({ Uris }) => {
const urisToResolve = Object.keys(Uris).reduce(
(resolve, category) => [...resolve, ...Uris[category]],
[]
);
const actions = [
doResolveUris(urisToResolve),
{
type: ACTIONS.FETCH_FEATURED_CONTENT_COMPLETED,
data: {
uris: Uris,
success: true,
},
},
];
dispatch(batchActions(...actions));
};
const failure = () => {
dispatch({
type: ACTIONS.FETCH_FEATURED_CONTENT_COMPLETED,
data: {
uris: {},
},
});
};
Lbryio.call('file', 'list_homepage').then(success, failure);
};
}
export function doFetchRewardedContent() {
return dispatch => {
const success = nameToClaimId => {
dispatch({
type: ACTIONS.FETCH_REWARD_CONTENT_COMPLETED,
data: {
claimIds: Object.values(nameToClaimId),
success: true,
},
});
};
const failure = () => {
dispatch({
type: ACTIONS.FETCH_REWARD_CONTENT_COMPLETED,
data: {
claimIds: [],
success: false,
},
});
};
Lbryio.call('reward', 'list_featured').then(success, failure);
};
}
export function doUpdateLoadStatus(uri, outpoint) { export function doUpdateLoadStatus(uri, outpoint) {
return (dispatch, getState) => { return (dispatch, getState) => {
Lbry.file_list({ Lbry.file_list({
@ -360,13 +292,13 @@ export function doPurchaseUri(uri, specificCostInfo, shouldRecordViewEvent) {
} }
export function doFetchClaimsByChannel(uri, page) { export function doFetchClaimsByChannel(uri, page) {
return (dispatch, getState) => { return dispatch => {
dispatch({ dispatch({
type: ACTIONS.FETCH_CHANNEL_CLAIMS_STARTED, type: ACTIONS.FETCH_CHANNEL_CLAIMS_STARTED,
data: { uri, page }, data: { uri, page },
}); });
Lbry.claim_list_by_channel({ uri, page: page || 1 }).then(result => { Lbry.claim_list_by_channel({ uri, page: page || 1, page_size: 48 }).then(result => {
const claimResult = result[uri] || {}; const claimResult = result[uri] || {};
const { claims_in_channel: claimsInChannel, returned_page: returnedPage } = claimResult; const { claims_in_channel: claimsInChannel, returned_page: returnedPage } = claimResult;

View file

@ -8,6 +8,14 @@ export function doNavigate(path, params = {}, options = {}) {
return; return;
} }
// ensure uri always has "lbry://" prefix
const navigationParams = params;
if (path === '/show') {
if (navigationParams.uri && !navigationParams.uri.startsWith('lbry://')) {
navigationParams.uri = `lbry://${navigationParams.uri}`;
}
}
let url = path; let url = path;
if (params && Object.values(params).length) { if (params && Object.values(params).length) {
url += `?${toQueryString(params)}`; url += `?${toQueryString(params)}`;

View file

@ -18,25 +18,13 @@ import { selectosNotificationsEnabled } from 'redux/selectors/settings';
import { doNavigate } from 'redux/actions/navigation'; import { doNavigate } from 'redux/actions/navigation';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { CC_LICENSES, COPYRIGHT, OTHER } from 'constants/licenses';
type Action = UpdatePublishFormAction | { type: ACTIONS.CLEAR_PUBLISH }; type Action = UpdatePublishFormAction | { type: ACTIONS.CLEAR_PUBLISH };
type PromiseAction = Promise<Action>; type PromiseAction = Promise<Action>;
type Dispatch = (action: Action | PromiseAction | Array<Action>) => any; type Dispatch = (action: Action | PromiseAction | Array<Action>) => any;
type GetState = () => {}; type GetState = () => {};
export const doClearPublish = () => (dispatch: Dispatch): PromiseAction => {
dispatch({ type: ACTIONS.CLEAR_PUBLISH });
return dispatch(doResetThumbnailStatus());
};
export const doUpdatePublishForm = (publishFormValue: UpdatePublishFormData) => (
dispatch: Dispatch
): UpdatePublishFormAction =>
dispatch({
type: ACTIONS.UPDATE_PUBLISH_FORM,
data: { ...publishFormValue },
});
export const doResetThumbnailStatus = () => (dispatch: Dispatch): PromiseAction => { export const doResetThumbnailStatus = () => (dispatch: Dispatch): PromiseAction => {
dispatch({ dispatch({
type: ACTIONS.UPDATE_PUBLISH_FORM, type: ACTIONS.UPDATE_PUBLISH_FORM,
@ -73,6 +61,19 @@ export const doResetThumbnailStatus = () => (dispatch: Dispatch): PromiseAction
); );
}; };
export const doClearPublish = () => (dispatch: Dispatch): PromiseAction => {
dispatch({ type: ACTIONS.CLEAR_PUBLISH });
return dispatch(doResetThumbnailStatus());
};
export const doUpdatePublishForm = (publishFormValue: UpdatePublishFormData) => (
dispatch: Dispatch
): UpdatePublishFormAction =>
dispatch({
type: ACTIONS.UPDATE_PUBLISH_FORM,
data: { ...publishFormValue },
});
export const doUploadThumbnail = (filePath: string, nsfw: boolean) => (dispatch: Dispatch) => { export const doUploadThumbnail = (filePath: string, nsfw: boolean) => (dispatch: Dispatch) => {
const thumbnail = fs.readFileSync(filePath); const thumbnail = fs.readFileSync(filePath);
const fileExt = path.extname(filePath); const fileExt = path.extname(filePath);
@ -164,15 +165,27 @@ export const doPrepareEdit = (claim: any, uri: string) => (dispatch: Dispatch) =
description, description,
fee, fee,
language, language,
licenseType: license,
licenseUrl,
nsfw, nsfw,
thumbnail, thumbnail,
title, title,
uri, uri,
uploadThumbnailStatus: thumbnail ? THUMBNAIL_STATUSES.MANUAL : undefined, uploadThumbnailStatus: thumbnail ? THUMBNAIL_STATUSES.MANUAL : undefined,
licenseUrl,
}; };
// Make sure custom liscence's are mapped properly
if (!CC_LICENSES.some(({ value }) => value === license)) {
if (!licenseUrl) {
publishData.licenseType = COPYRIGHT;
} else {
publishData.licenseType = OTHER;
}
publishData.otherLicenseDescription = license;
} else {
publishData.licenseType = license;
}
dispatch({ type: ACTIONS.DO_PREPARE_EDIT, data: publishData }); dispatch({ type: ACTIONS.DO_PREPARE_EDIT, data: publishData });
}; };

View file

@ -28,7 +28,6 @@ type PublishState = {
bidError: ?string, bidError: ?string,
otherLicenseDescription: string, otherLicenseDescription: string,
licenseUrl: string, licenseUrl: string,
copyrightNotice: string,
pendingPublishes: Array<any>, pendingPublishes: Array<any>,
}; };
@ -54,7 +53,6 @@ export type UpdatePublishFormData = {
bidError?: string, bidError?: string,
otherLicenseDescription?: string, otherLicenseDescription?: string,
licenseUrl?: string, licenseUrl?: string,
copyrightNotice?: string,
}; };
export type UpdatePublishFormAction = { export type UpdatePublishFormAction = {
@ -114,9 +112,8 @@ const defaultState: PublishState = {
bid: 0.1, bid: 0.1,
bidError: undefined, bidError: undefined,
licenseType: 'None', licenseType: 'None',
otherLicenseDescription: '', otherLicenseDescription: 'All rights reserved',
licenseUrl: '', licenseUrl: '',
copyrightNotice: 'All rights reserved',
publishing: false, publishing: false,
publishSuccess: false, publishSuccess: false,
publishError: undefined, publishError: undefined,

View file

@ -4,13 +4,6 @@ import { HISTORY_ITEMS_PER_PAGE } from 'constants/content';
export const selectState = state => state.content || {}; export const selectState = state => state.content || {};
export const selectFeaturedUris = createSelector(selectState, state => state.featuredUris);
export const selectFetchingFeaturedUris = createSelector(
selectState,
state => state.fetchingFeaturedContent
);
export const selectPlayingUri = createSelector(selectState, state => state.playingUri); export const selectPlayingUri = createSelector(selectState, state => state.playingUri);
export const selectChannelClaimCounts = createSelector( export const selectChannelClaimCounts = createSelector(

View file

@ -39,7 +39,7 @@ html {
} }
body { body {
font-family: 'metropolis-semibold'; font-family: 'metropolis-medium';
font-weight: 400; font-weight: 400;
font-size: 16px; font-size: 16px;
line-height: 1.5; line-height: 1.5;
@ -74,7 +74,6 @@ input {
line-height: 1; line-height: 1;
cursor: text; cursor: text;
background-color: transparent; background-color: transparent;
font-family: 'metropolis-medium';
&[type='radio'], &[type='radio'],
&[type='checkbox'], &[type='checkbox'],
@ -92,6 +91,7 @@ input {
color: var(--input-copyable-color); color: var(--input-copyable-color);
padding: 10px 16px; padding: 10px 16px;
border: 1px dashed var(--input-copyable-border); border: 1px dashed var(--input-copyable-border);
text-overflow: ellipsis;
} }
&:not(.input-copyable):not(.wunderbar__input):not(:placeholder-shown):not(:disabled) { &:not(.input-copyable):not(.wunderbar__input):not(:placeholder-shown):not(:disabled) {
@ -109,7 +109,6 @@ input {
} }
textarea { textarea {
font-family: 'metropolis-medium';
border: 1px solid var(--color-divider); border: 1px solid var(--color-divider);
font-size: 0.8em; font-size: 0.8em;
width: 100%; width: 100%;
@ -152,8 +151,6 @@ dd {
} }
p { p {
font-family: 'metropolis-medium';
&:not(:first-of-type) { &:not(:first-of-type) {
margin-top: $spacing-vertical * 1/3; margin-top: $spacing-vertical * 1/3;
} }
@ -227,7 +224,6 @@ p {
.page__empty { .page__empty {
margin-top: 200px; margin-top: 200px;
text-align: center; text-align: center;
font-family: 'metropolis-medium';
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@ -262,7 +258,7 @@ p {
} }
.credit-amount { .credit-amount {
font-size: 10px; font-size: 0.9em;
white-space: nowrap; white-space: nowrap;
} }
@ -300,7 +296,6 @@ p {
color: inherit; color: inherit;
font-weight: inherit; font-weight: inherit;
font-size: inherit; font-size: inherit;
font-family: 'metropolis-medium';
padding: 0; padding: 0;
} }
@ -333,10 +328,6 @@ p {
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
.busy-indicator {
font-family: 'metropolis-medium';
}
.busy-indicator__loader { .busy-indicator__loader {
background: url('../../../static/img/busy.gif') no-repeat center center; background: url('../../../static/img/busy.gif') no-repeat center center;
display: inline-block; display: inline-block;
@ -355,7 +346,6 @@ p {
.help { .help {
font-size: 12px; font-size: 12px;
font-family: 'metropolis-medium';
color: var(--color-help); color: var(--color-help);
} }
@ -364,7 +354,6 @@ p {
} }
.meta { .meta {
font-family: 'metropolis-medium';
font-size: 0.8em; font-size: 0.8em;
color: var(--color-meta-light); color: var(--color-meta-light);
} }

View file

@ -52,6 +52,8 @@ $large-breakpoint: 1921px;
--color-search-placeholder: var(--color-placeholder); --color-search-placeholder: var(--color-placeholder);
--color-credit-free: var(--color-dark-blue); --color-credit-free: var(--color-dark-blue);
--color-credit-price: var(--card-text-color); --color-credit-price: var(--card-text-color);
--color-text-black: #444;
--color-text-white: #efefef;
/* Shadows */ /* Shadows */
--box-shadow-layer: transparent; // 0 2px 4px rgba(0,0,0,0.25); --box-shadow-layer: transparent; // 0 2px 4px rgba(0,0,0,0.25);
@ -60,8 +62,8 @@ $large-breakpoint: 1921px;
--box-shadow-header: 0px 6px 20px 1px rgba(0, 0, 0, 0.05); --box-shadow-header: 0px 6px 20px 1px rgba(0, 0, 0, 0.05);
/* Text */ /* Text */
--text-color: var(--color-black); --text-color: var(--color-text-black);
--text-color-inverse: var(--color-white); --text-color-inverse: var(--color-text-white);
--text-help-color: var(--color-help); --text-help-color: var(--color-help);
--text-max-width: 660px; --text-max-width: 660px;
--text-link-padding: 4px; --text-link-padding: 4px;

View file

@ -29,3 +29,6 @@
@import 'component/_toggle.scss'; @import 'component/_toggle.scss';
@import 'component/_search.scss'; @import 'component/_search.scss';
@import 'component/_dat-gui.scss'; @import 'component/_dat-gui.scss';
@import 'component/_item-list.scss';
@import 'component/_time.scss';
@import 'component/_icon.scss';

View file

@ -19,7 +19,6 @@ button:disabled {
fill: currentColor; // for proper icon color fill: currentColor; // for proper icon color
font-size: 12px; font-size: 12px;
transition: all var(--animation-duration) var(--animation-style); transition: all var(--animation-duration) var(--animation-style);
font-family: 'metropolis-medium';
&:not(:disabled) { &:not(:disabled) {
box-shadow: var(--box-shadow-button); box-shadow: var(--box-shadow-button);
@ -161,6 +160,7 @@ button:disabled {
.btn--uri-indicator { .btn--uri-indicator {
transition: color var(--animation-duration) var(--animation-style); transition: color var(--animation-duration) var(--animation-style);
display: inline-block;
&:hover { &:hover {
color: var(--btn-color-inverse); color: var(--btn-color-inverse);
@ -172,7 +172,6 @@ button:disabled {
} }
.btn.btn--header-balance { .btn.btn--header-balance {
font-family: 'metropolis-medium';
font-size: 14px; font-size: 14px;
color: var(--header-primary-color); color: var(--header-primary-color);

View file

@ -6,10 +6,18 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-width: var(--card-max-width); max-width: var(--card-max-width);
} white-space: normal;
.card > h1 { .card__media {
word-wrap: break-word; padding-top: var(--video-aspect-ratio);
}
// Text that is shown if a piece of content has no thumbnail
// We need to do this because the thumbnail uses padding-top: var(--video-aspect-ratio); for dynamic height
// this lets the text sit in the middle instead of the bottom
.card__media-text {
margin-top: calc(var(--video-aspect-ratio) * -1);
}
} }
.card--section { .card--section {
@ -20,25 +28,25 @@
} }
.card--small { .card--small {
white-space: normal; font-size: 13px;
}
.card__media { .card__media {
padding-top: var(--video-aspect-ratio); background-size: cover;
} background-repeat: no-repeat;
background-position: 50% 50%;
background-color: var(--color-placeholder);
}
.card__media-text { .card__media--no-img {
// for the weird padding required for dynamic height display: flex;
// this lets the text sit in the middle instead of the bottom justify-content: center;
margin-top: calc(var(--video-aspect-ratio) * -1); align-items: center;
} position: relative;
}
.channel-name { .card__media--nsfw {
font-size: 12px; background-color: var(--color-error);
@media only screen and (min-width: $medium-breakpoint) {
font-size: 14px;
}
}
} }
.card--link { .card--link {
@ -66,31 +74,29 @@
pointer-events: none; pointer-events: none;
} }
.card__media { .card__title {
background-size: cover; font-size: 1.5em;
background-repeat: no-repeat;
background-position: 50% 50%;
background-color: var(--color-placeholder);
}
.card__media--no-img {
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.card__media--nsfw {
background-color: var(--color-error);
}
.card__title-identity {
color: var(--text-color); color: var(--text-color);
padding-top: $spacing-vertical * 1/3;
@media (min-width: $large-breakpoint) {
font-size: 1.7em;
}
} }
.card__title-identity--file { .card__title--file-card {
// FileCard is slightly different where the title is only slightly bigger than the subtitle
// Slightly bigger than 2 lines for consistent channel placement
font-size: 1.1em;
height: 4em;
@media only screen and (min-width: $large-breakpoint) {
font-size: 1.3em;
}
}
.card__title__space-between {
display: flex; display: flex;
align-items: center;
justify-content: space-between; justify-content: space-between;
.icon { .icon {
@ -104,73 +110,26 @@
align-self: flex-start; align-self: flex-start;
} }
.card__title {
font-size: 18px;
color: var(--text-color);
}
.card__title--small {
font-size: 12px;
line-height: 12px;
@media only screen and (min-width: $medium-breakpoint) {
font-size: 14px;
line-height: 14px;
}
}
.card__title--x-small {
font-size: 12px;
line-height: 12px;
}
.card__title--large {
font-size: 22px;
}
.card__title--file {
font-family: 'metropolis-bold';
font-size: 28px;
margin-bottom: 0;
padding-top: 0;
padding-bottom: 5px;
font-size: 18px;
}
.card__title--file-card {
padding-top: $spacing-vertical * 1/3;
// height is the same height that two lines of title fill
// doing this so content below the title is inline accross the row
height: 30px;
@media only screen and (min-width: $medium-breakpoint) {
height: 36px;
}
}
.card__subtitle { .card__subtitle {
margin: 0;
font-size: 14px;
font-family: 'metropolis-medium';
color: var(--card-text-color); color: var(--card-text-color);
font-size: 1em;
line-height: 1em;
@media (min-width: $large-breakpoint) {
font-size: 1.2em;
}
} }
.card__subtitle--x-small { .card__subtext-title {
font-size: 12px; font-size: 1.1em;
&:not(:first-of-type) {
margin-top: $spacing-vertical * 3/2;
}
} }
.card__subtitle--large { .card__subtext {
font-size: 18px; font-size: 0.85em;
padding-bottom: $spacing-vertical * 1/3;
}
.card__subtitle-price {
padding-top: $spacing-vertical * 1/3;
}
.card__title--small + .card__subtitle,
.card__title--x-small + .card__subtitle {
padding-top: $spacing-vertical * 1/3;
} }
.card__meta { .card__meta {
@ -185,11 +144,6 @@
align-items: center; align-items: center;
padding-top: $spacing-vertical * 1/3; padding-top: $spacing-vertical * 1/3;
color: var(--card-text-color); color: var(--card-text-color);
.icon + .icon,
.credit-amount + .icon {
margin-left: $spacing-vertical * 1/3;
}
} }
// .card-media__internal__links should always be inside a card // .card-media__internal__links should always be inside a card
@ -201,58 +155,10 @@
} }
} }
.card--small {
.card-media__internal-links {
top: $spacing-vertical * 1/3;
right: $spacing-vertical * 1/3;
}
}
// Channel info with buttons on the right side
.card__channel-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: $spacing-width 0 0;
}
.card__channel-info--large {
padding-top: 0;
padding-bottom: $spacing-vertical * 2/3;
}
.card__content { .card__content {
margin-top: $spacing-vertical * 2/3; margin-top: $spacing-vertical * 2/3;
} }
.card__content--extra-padding {
margin-top: $spacing-vertical * 3/2;
}
.card__subtext-title {
color: var(--text-color);
font-size: calc(var(--font-size-subtext-multiple) * 1.2em);
&:not(:first-of-type) {
margin-top: $spacing-vertical * 3/2;
}
}
.card__subtext {
font-size: calc(var(--font-size-subtext-multiple) * 1em);
padding-top: $spacing-vertical * 1/3;
word-break: break-word;
font-family: 'metropolis-medium';
}
.card__subtext--small {
font-size: calc(var(--font-size-subtext-multiple) * 0.8em);
}
.card__subtext--large {
font-size: calc(var(--font-size-subtext-multiple) * 0.9em);
}
.card__actions { .card__actions {
margin-top: $spacing-vertical * 2/3; margin-top: $spacing-vertical * 2/3;
display: flex; display: flex;
@ -335,6 +241,7 @@
} }
.card-row__title { .card-row__title {
font-family: 'metropolis-semibold';
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 18px; font-size: 18px;
@ -462,43 +369,3 @@
padding: $spacing-vertical * 1/3; padding: $spacing-vertical * 1/3;
margin: $spacing-vertical * 1/3 0; margin: $spacing-vertical * 1/3 0;
} }
.card__media--autothumb {
color: red !important;
}
.card__media {
&.card__media--autothumb.purple {
background-color: #9c27b0;
}
&.card__media--autothumb.red {
background-color: #e53935;
}
&.card__media--autothumb.pink {
background-color: #e91e63;
}
&.card__media--autothumb.indigo {
background-color: #3f51b5;
}
&.card__media--autothumb.blue {
background-color: #2196f3;
}
&.card__media--autothumb.light-blue {
background-color: #039be5;
}
&.card__media--autothumb.cyan {
background-color: #00acc1;
}
&.card__media--autothumb.teal {
background-color: #009688;
}
&.card__media--autothumb.green {
background-color: #43a047;
}
&.card__media--autothumb.yellow {
background-color: #ffeb3b;
}
&.card__media--autothumb.orange {
background-color: #ffa726;
}
}

View file

@ -4,7 +4,3 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.channel-indicator__icon--invalid {
color: var(--color-error);
}

View file

@ -95,10 +95,6 @@
&.content__empty--nsfw { &.content__empty--nsfw {
background-color: var(--color-nsfw); background-color: var(--color-nsfw);
} }
.card__media-text {
margin-top: calc(var(--video-aspect-ratio) * -1);
}
} }
img { img {

View file

@ -22,25 +22,32 @@
.file-tile { .file-tile {
display: flex; display: flex;
font-size: 14px;
padding-top: $spacing-vertical; padding-top: $spacing-vertical;
.card__media { .card__media {
height: var(--file-tile-media-height); height: var(--file-tile-media-height);
flex: 0 0 var(--file-tile-media-width); flex: 0 0 var(--file-tile-media-width);
} }
}
.card__subtitle { .file-tile--small {
line-height: 1; font-size: 12px;
display: flex;
align-items: center; .card__media {
height: var(--file-tile-media-height-small);
flex: 0 0 var(--file-tile-media-width-small);
} }
} }
.file-tile__channel { .file-tile--large {
padding-right: $spacing-width * 1/4; font-size: 16px;
} }
.file-tile.file-tile--small { .file-tile__title {
}
.file-tile--small {
padding-top: $spacing-vertical * 2/3; padding-top: $spacing-vertical * 2/3;
.card__media { .card__media {
@ -49,7 +56,7 @@
} }
} }
.file-tile.file-tile--large { .file-tile--large {
.card__media { .card__media {
height: var(--file-tile-media-height-large); height: var(--file-tile-media-height-large);
flex: 0 0 var(--file-tile-media-width-large); flex: 0 0 var(--file-tile-media-width-large);

View file

@ -0,0 +1,6 @@
// Icons with icons directly following should have a margin-right for proper spacing
// Same for prices on cards
.icon + .icon,
.credit-amount + .icon {
margin-left: $spacing-vertical * 1/3;
}

View file

@ -0,0 +1,26 @@
.item-list {
background-color: var(--card-bg);
margin-top: $spacing-vertical;
}
.item-list__item {
display: flex;
align-items: center;
padding: $spacing-vertical * 1/3;
input,
.item-list__item--cutoff {
margin-right: $spacing-vertical;
}
}
.item-list__item--selected {
background-color: var(--table-item-odd);
}
.item-list__item--cutoff {
white-space: nowrap;
text-overflow: ellipsis;
overflow-x: hidden;
max-width: 350px;
}

View file

@ -39,7 +39,6 @@
/* Paragraphs */ /* Paragraphs */
p { p {
margin: $spacing-vertical * 2/3 0;
white-space: pre-line; white-space: pre-line;
} }

View file

@ -24,7 +24,6 @@
padding: 10px; padding: 10px;
padding-left: 30px; padding-left: 30px;
font-size: 13px; font-size: 13px;
font-family: 'metropolis-medium';
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -54,7 +53,6 @@
flex-direction: row; flex-direction: row;
justify-items: flex-start; justify-items: flex-start;
align-items: center; align-items: center;
font-family: 'metropolis-medium';
&:not(:first-of-type) { &:not(:first-of-type) {
border-top: 1px solid var(--search-item-border-color); border-top: 1px solid var(--search-item-border-color);
@ -75,7 +73,6 @@
.wunderbar__suggestion-label--action { .wunderbar__suggestion-label--action {
margin-left: $spacing-vertical * 1/3; margin-left: $spacing-vertical * 1/3;
white-space: nowrap; white-space: nowrap;
font-family: 'metropolis-medium';
font-size: 12px; font-size: 12px;
line-height: 0.1; // to vertically align because the font size is smaller line-height: 0.1; // to vertically align because the font size is smaller
} }

View file

@ -3,7 +3,6 @@ table.table,
word-wrap: break-word; word-wrap: break-word;
max-width: 100%; max-width: 100%;
text-align: left; text-align: left;
margin-top: $spacing-vertical * 2/3;
tr td:first-of-type, tr td:first-of-type,
tr th:first-of-type { tr th:first-of-type {
@ -107,48 +106,3 @@ table.table--transactions {
width: 15%; width: 15%;
} }
} }
table.table--history {
margin-top: $spacing-vertical * 1/3;
tbody {
tr {
&:nth-child(even),
&:nth-child(odd) {
background-color: var(--table-item-even);
&.history__selected {
color: red;
background-color: var(--table-item-odd);
}
}
}
td {
cursor: default;
padding: $spacing-vertical * 1/3 0;
}
td:nth-of-type(1) {
width: 7.5%;
}
td:nth-of-type(2) {
width: 17.5%;
}
td:nth-of-type(3) {
width: 40%;
max-width: 30vw;
padding-right: $spacing-vertical * 2/3;
}
td:nth-of-type(4) {
width: 30%;
}
td:nth-of-type(3),
td:nth-of-type(4) {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}

View file

@ -0,0 +1,9 @@
// All CSS for date & time ui
.time {
color: var(--color-help);
}
.time--ago {
min-width: 160px;
}

View file

@ -12,7 +12,7 @@
--color-credit-free: var(--color-secondary); --color-credit-free: var(--color-secondary);
/* Text */ /* Text */
--text-color: var(--color-white); --text-color: var(--color-text-white);
--text-help-color: var(--color-help); --text-help-color: var(--color-help);
/* Form */ /* Form */