Merge pull request #998 from lbryio/redesign-wip

implement settings redesign
This commit is contained in:
Sean Yesmunt 2018-02-06 11:54:58 -08:00 committed by GitHub
commit 61e393f75e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 642 additions and 608 deletions

View file

@ -0,0 +1,72 @@
// @flow
import React from 'react';
import { remote } from 'electron';
import Button from 'component/link';
import { FormRow } from 'component/common/form';
type Props = {
type: string,
currentPath: string,
onFileChosen: string => void,
};
class FileSelector extends React.PureComponent<Props> {
static defaultProps = {
type: 'file',
};
constructor() {
super();
this.input = null;
}
handleButtonClick() {
remote.dialog.showOpenDialog(
{
properties:
this.props.type === 'file' ? ['openFile'] : ['openDirectory', 'createDirectory'],
},
paths => {
if (!paths) {
// User hit cancel, so do nothing
return;
}
const path = paths[0];
if (this.props.onFileChosen) {
this.props.onFileChosen(path);
}
}
);
}
input: ?HTMLInputElement;
render() {
const { type, currentPath } = this.props;
return (
<FormRow verticallyCentered padded>
<Button
onClick={() => this.handleButtonClick()}
label={type === 'file' ? __('Choose File') : __('Choose Directory')}
/>
<input
webkitdirectory="true"
className="input-copyable"
type="text"
ref={input => {
if (this.input) this.input = input;
}}
onFocus={() => {
if (this.input) this.input.select();
}}
readOnly="readonly"
value={currentPath || __('No File Chosen')}
/>
</FormRow>
);
}
}
export default FileSelector;

View file

@ -0,0 +1,78 @@
// @flow
import * as React from 'react';
import type { Price } from 'page/settings';
import { FormField } from './form-field';
import { FormRow } from './form-row';
type Props = {
price: Price,
onChange: Price => void,
placeholder: number,
min: number,
disabled: boolean,
name: string,
label: string,
};
export class FormFieldPrice extends React.PureComponent<Props> {
constructor(props: Props) {
super(props);
(this: any).handleAmountChange = this.handleAmountChange.bind(this);
(this: any).handleCurrencyChange = this.handleCurrencyChange.bind(this);
}
handleAmountChange(event: SyntheticInputEvent<*>) {
const { price, onChange } = this.props;
onChange({
currency: price.currency,
amount: parseInt(event.target.value, 10),
});
}
handleCurrencyChange(event: SyntheticInputEvent<*>) {
const { price, onChange } = this.props;
onChange({
currency: event.target.value,
amount: price.amount,
});
}
render() {
const { price, placeholder, min, disabled, name, label } = this.props;
return (
<FormRow padded>
<FormField
name={`${name}_amount`}
label={label}
type="number"
className="form-field input--price-amount"
min={min}
value={price.amount || ''}
onChange={this.handleAmountChange}
placeholder={placeholder || 5}
disabled={disabled}
/>
<FormField
name={`${name}_currency`}
render={() => (
<select
id={`${name}_currency`}
className="form-field"
disabled={disabled}
onChange={this.handleCurrencyChange}
defaultValue={price.currency}
>
<option value="LBC">{__('LBRY Credits (LBC)')}</option>
<option value="USD">{__('US Dollars')}</option>
</select>
)}
/>
</FormRow>
);
}
}
export default FormFieldPrice;

View file

@ -0,0 +1,61 @@
// @flow
import * as React from 'react';
type Props = {
name: string,
label?: string,
render?: () => React.Node,
prefix?: string,
postfix?: string,
error?: string | boolean,
helper?: string | React.Node,
type?: string,
onChange?: any => any,
defaultValue?: string | number,
placeholder?: string | number,
};
export class FormField extends React.PureComponent<Props> {
render() {
const { render, label, prefix, postfix, error, helper, name, type, ...inputProps } = this.props;
// Allow a type prop to determine the input or more customizability with a render prop
let Input;
if (type) {
Input = () => <input type={type} id={name} {...inputProps} />;
} else if (render) {
Input = render;
}
return (
<div className="form-field">
{label && (
<label className="form-field__label" htmlFor={name}>
{label}
</label>
)}
<div className="form-field__input">
{prefix && (
<label htmlFor={name} className="form-field__prefix">
{prefix}
</label>
)}
{Input && <Input />}
{postfix && (
<label htmlFor={name} className="form-field__postfix">
{postfix}
</label>
)}
</div>
{error && (
<div className="form-field__error">
{typeof error === 'string' ? error : __('There was an error')}
</div>
)}
{helper && <div className="form-field__help">{helper}</div>}
</div>
);
}
}
export default FormField;

View file

@ -0,0 +1,32 @@
// @flow
// Used as a wrapper for FormField to produce inline form elements
import * as React from 'react';
import classnames from 'classnames';
type Props = {
children: React.Node,
padded?: boolean,
verticallyCentered?: boolean,
};
export class FormRow extends React.PureComponent<Props> {
static defaultProps = {
padded: false,
};
render() {
const { children, padded, verticallyCentered } = this.props;
return (
<div
className={classnames('form-row', {
'form-row--padded': padded,
'form-row--centered': verticallyCentered,
})}
>
{children}
</div>
);
}
}
export default FormRow;

View file

@ -0,0 +1,26 @@
// @flow
import * as React from 'react';
type Props = {
children: React.Node,
onSubmit: any => any,
};
export class Form extends React.PureComponent<Props> {
render() {
const { children, onSubmit } = this.props;
return (
<form
className="form"
onSubmit={event => {
event.preventDefault();
onSubmit(event);
}}
>
{children}
</form>
);
}
}
export default Form;

View file

@ -0,0 +1,21 @@
// @flow
import * as React from 'react';
import Button from 'component/link';
type Props = {
label: string,
disabled: boolean,
};
export class Submit extends React.PureComponent<Props> {
static defaultProps = {
label: 'Submit',
};
render() {
const { label, disabled } = this.props;
return <Button type="submit" label={label} disabled={disabled} />;
}
}
export default Submit;

View file

@ -1,95 +1,5 @@
// @flow
/* eslint-disable react/no-multi-comp */
import * as React from 'react';
import Button from 'component/link';
import classnames from 'classnames';
type FormRowProps = {
children: React.Node,
padded?: boolean,
};
export class FormRow extends React.PureComponent<FormRowProps> {
static defaultProps = {
padded: false,
};
render() {
const { children, padded } = this.props;
return <div className={classnames('form-row', { 'form-row--padded': padded })}>{children}</div>;
}
}
type FormFieldProps = {
render: () => React.Node,
label?: string,
prefix?: string,
postfix?: string,
error?: string | boolean,
helper?: string | React.Node,
};
export class FormField extends React.PureComponent<FormFieldProps> {
render() {
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 (
<div className="form-field">
{label && <label className="form-field__label">{label}</label>}
<div className="form-field__wrapper">
{prefix && <span className="form-field__prefix">{prefix}</span>}
{render()}
{postfix && <span className="form-field__postfix">{postfix}</span>}
</div>
{error && (
<div className="form-field__error">
{typeof error === 'string' ? error : __('There was an error')}
</div>
)}
{helper && <div className="form-field__help">{helper}</div>}
</div>
);
/* eslint-enable jsx-a11y/label-has-for */
}
}
type SubmitProps = {
label: string,
disabled: boolean,
};
export class Submit extends React.PureComponent<SubmitProps> {
static defaultProps = {
label: 'Submit',
};
render() {
const { label, disabled } = this.props;
return <Button type="submit" label={label} disabled={disabled} />;
}
}
type FormProps = {
children: React.Node,
onSubmit: any => any,
};
export class Form extends React.PureComponent<FormProps> {
render() {
const { children, onSubmit } = this.props;
return (
<form
className="form"
onSubmit={event => {
event.preventDefault();
onSubmit(event);
}}
>
{children}
</form>
);
}
}
/* eslint-enable react/no-multi-comp */
export { Form } from './form-components/form';
export { FormRow } from './form-components/form-row';
export { FormField } from './form-components/form-field';
export { FormFieldPrice } from './form-components/form-field-price';
export { Submit } from './form-components/submit';

View file

@ -1,83 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
const { remote } = require('electron');
class FileSelector extends React.PureComponent {
static propTypes = {
type: PropTypes.oneOf(['file', 'directory']),
initPath: PropTypes.string,
onFileChosen: PropTypes.func,
};
static defaultProps = {
type: 'file',
};
constructor(props) {
super(props);
this._inputElem = null;
}
componentWillMount() {
this.setState({
path: this.props.initPath || null,
});
}
handleButtonClick() {
remote.dialog.showOpenDialog(
{
properties: this.props.type == 'file' ? ['openFile'] : ['openDirectory', 'createDirectory'],
},
paths => {
if (!paths) {
// User hit cancel, so do nothing
return;
}
const path = paths[0];
this.setState({
path,
});
if (this.props.onFileChosen) {
this.props.onFileChosen(path);
}
}
);
}
render() {
return (
<div className="file-selector">
<button
type="button"
className="button-block button-alt file-selector__choose-button"
onClick={() => this.handleButtonClick()}
>
<span className="button__content">
<span className="button-label">
{this.props.type == 'file' ? __('Choose File') : __('Choose Directory')}
</span>
</span>
</button>{' '}
<span className="file-selector__path">
<input
className="input-copyable"
type="text"
ref={input => {
this._inputElem = input;
}}
onFocus={() => {
this._inputElem.select();
}}
readOnly="readonly"
value={this.state.path || __('No File Chosen')}
/>
</span>
</div>
);
}
}
export default FileSelector;

View file

@ -22,9 +22,8 @@ class FileActions extends React.PureComponent<Props> {
const { fileInfo, uri, openModal, claimIsMine, vertical } = this.props;
const claimId = fileInfo ? fileInfo.claim_id : '';
// showDelete = fileInfo && Object.keys(fileInfo).length > 0;
const showDelete = fileInfo && Object.keys(fileInfo).length > 0;
const showDelete = true;
return (
<section className={classnames('card__actions', { 'card__actions--vertical': vertical })}>
<FileDownloadLink uri={uri} />

View file

@ -95,7 +95,7 @@ class FileCard extends React.PureComponent<Props> {
</div>
</div>
</div>
{obscureNsfw && <NsfwOverlay />}
{shouldObscureNsfw && <NsfwOverlay />}
</section>
);
/* eslint-enable jsx-a11y/click-events-have-key-events */

View file

@ -2,7 +2,7 @@
/* eslint-disable */
import React from 'react';
import PropTypes from 'prop-types';
import FileSelector from 'component/file-selector.js';
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';

View file

@ -1,63 +1,5 @@
import React from 'react';
import FormField from 'component/formField';
// This just exists so the app builds. It will be removed
class FormFieldPrice extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
amount: props.defaultValue && props.defaultValue.amount ? props.defaultValue.amount : '',
currency:
props.defaultValue && props.defaultValue.currency ? props.defaultValue.currency : 'LBC',
};
}
handleChange(newValues) {
const newState = Object.assign({}, this.state, newValues);
this.setState(newState);
this.props.onChange({
amount: newState.amount,
currency: newState.currency,
});
}
handleFeeAmountChange(event) {
this.handleChange({
amount: event.target.value ? Number(event.target.value) : null,
});
}
handleFeeCurrencyChange(event) {
this.handleChange({ currency: event.target.value });
}
render() {
const { defaultValue, placeholder, min } = this.props;
return (
<span className="form-field">
<FormField
type="number"
name="amount"
min={min}
placeholder={placeholder || null}
step="any" // Unfortunately, you cannot set a step without triggering validation that enforces a multiple of the step
onChange={event => this.handleFeeAmountChange(event)}
defaultValue={defaultValue && defaultValue.amount ? defaultValue.amount : ''}
className="form-field__input--inline"
/>
<FormField
type="select"
name="currency"
onChange={event => this.handleFeeCurrencyChange(event)}
defaultValue={defaultValue && defaultValue.currency ? defaultValue.currency : ''}
className="form-field__input--inline"
>
<option value="LBC">{__('LBRY Credits (LBC)')}</option>
<option value="USD">{__('US Dollars')}</option>
</FormField>
</span>
);
}
}
const FormFieldPrice = () => null;
export default FormFieldPrice;

View file

@ -1,15 +1,11 @@
import React from 'react';
import Link from 'component/link';
import Button from 'component/link';
const NsfwOverlay = props => (
const NsfwOverlay = () => (
<div className="card-overlay">
<p>
{__('This content is Not Safe For Work. To view adult content, please change your')}{' '}
<Link
className="button-text"
onClick={() => props.navigateSettings()}
label={__('Settings')}
/>.
<Button fakeLink navigate="/settings" label={__('settings')} />.
</p>
</div>
);

View file

@ -40,15 +40,7 @@ class WalletSend extends React.PureComponent<Props> {
}}
onSubmit={this.handleSubmit}
validate={validateSendTx}
render={({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
render={({ values, errors, touched, handleChange, handleBlur, handleSubmit }) => (
<Form onSubmit={handleSubmit}>
<FormRow>
<FormField
@ -57,7 +49,7 @@ class WalletSend extends React.PureComponent<Props> {
error={!!values.amount && touched.amount && errors.amount}
render={() => (
<input
className="input--lbc-amount"
className="input--price-amount"
type="number"
name="amount"
min="0"

View file

@ -74,6 +74,7 @@ class WunderBar extends React.PureComponent<Props> {
input: ?HTMLInputElement;
throttledGetSearchSuggestions: string => void;
render() {
const { searchQuery, isActive, address, suggestions } = this.props;

View file

@ -1,4 +1,3 @@
import React from 'react';
import { connect } from 'react-redux';
import * as settings from 'constants/settings';
import { doClearCache } from 'redux/actions/app';
@ -6,7 +5,6 @@ import {
doSetDaemonSetting,
doSetClientSetting,
doGetThemes,
doSetTheme,
doChangeLanguage,
} from 'redux/actions/settings';
import {
@ -23,8 +21,7 @@ const select = state => ({
showUnavailable: makeSelectClientSetting(settings.SHOW_UNAVAILABLE)(state),
instantPurchaseEnabled: makeSelectClientSetting(settings.INSTANT_PURCHASE_ENABLED)(state),
instantPurchaseMax: makeSelectClientSetting(settings.INSTANT_PURCHASE_MAX)(state),
showUnavailable: makeSelectClientSetting(settings.SHOW_UNAVAILABLE)(state),
theme: makeSelectClientSetting(settings.THEME)(state),
currentTheme: makeSelectClientSetting(settings.THEME)(state),
themes: makeSelectClientSetting(settings.THEMES)(state),
language: selectCurrentLanguage(state),
languages: selectLanguages(state),

View file

@ -1,19 +1,112 @@
import React from 'react';
import FormField from 'component/formField';
import { FormRow } from 'component/common/form';
// @flow
import * as React from 'react';
import { FormField, FormFieldPrice } from 'component/common/form';
import * as settings from 'constants/settings';
import lbry from 'lbry.js';
import Link from 'component/link';
import FormFieldPrice from 'component/formFieldPrice';
import Button from 'component/link';
import Page from 'component/page';
import FileSelector from 'component/common/file-selector';
class SettingsPage extends React.PureComponent {
constructor(props) {
export type Price = {
currency: string,
amount: number,
};
type DaemonSettings = {
download_directory: string,
disable_max_key_fee: boolean,
share_usage_data: boolean,
};
type Props = {
setDaemonSetting: (string, boolean | string | Price) => void,
setClientSetting: (string, boolean | string | Price) => void,
clearCache: () => Promise<any>,
getThemes: () => void,
daemonSettings: DaemonSettings,
showNsfw: boolean,
instantPurchaseEnabled: boolean,
instantPurchaseMax: Price,
showUnavailable: boolean,
currentTheme: string,
themes: Array<string>,
automaticDarkModeEnabled: boolean,
};
type State = {
clearingCache: boolean,
};
class SettingsPage extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
clearingCache: false,
};
(this: any).onDownloadDirChange = this.onDownloadDirChange.bind(this);
(this: any).onKeyFeeChange = this.onKeyFeeChange.bind(this);
(this: any).onInstantPurchaseMaxChange = this.onInstantPurchaseMaxChange.bind(this);
(this: any).onShowNsfwChange = this.onShowNsfwChange.bind(this);
(this: any).onShowUnavailableChange = this.onShowUnavailableChange.bind(this);
(this: any).onShareDataChange = this.onShareDataChange.bind(this);
(this: any).onThemeChange = this.onThemeChange.bind(this);
(this: any).onAutomaticDarkModeChange = this.onAutomaticDarkModeChange.bind(this);
(this: any).clearCache = this.clearCache.bind(this);
// (this: any).onLanguageChange = this.onLanguageChange.bind(this)
}
componentDidMount() {
this.props.getThemes();
}
onRunOnStartChange(event: SyntheticInputEvent<*>) {
this.setDaemonSetting('run_on_startup', event.target.checked);
}
onShareDataChange(event: SyntheticInputEvent<*>) {
this.setDaemonSetting('share_usage_data', event.target.checked);
}
onDownloadDirChange(newDirectory: string) {
this.setDaemonSetting('download_directory', newDirectory);
}
onKeyFeeChange(newValue: Price) {
this.setDaemonSetting('max_key_fee', newValue);
}
onKeyFeeDisableChange(isDisabled: boolean) {
this.setDaemonSetting('disable_max_key_fee', isDisabled);
}
onThemeChange(event: SyntheticInputEvent<*>) {
const { value } = event.target;
this.props.setClientSetting(settings.THEME, value);
}
onAutomaticDarkModeChange(event: SyntheticInputEvent<*>) {
this.props.setClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED, event.target.checked);
}
onInstantPurchaseEnabledChange(enabled: boolean) {
this.props.setClientSetting(settings.INSTANT_PURCHASE_ENABLED, enabled);
}
onInstantPurchaseMaxChange(newValue: Price) {
this.props.setClientSetting(settings.INSTANT_PURCHASE_MAX, newValue);
}
onShowNsfwChange(event: SyntheticInputEvent<*>) {
this.props.setClientSetting(settings.SHOW_NSFW, event.target.checked);
}
onShowUnavailableChange(event: SyntheticInputEvent<*>) {
this.props.setClientSetting(settings.SHOW_UNAVAILABLE, event.target.checked);
}
setDaemonSetting(name: string, value: boolean | string | Price) {
this.props.setDaemonSetting(name, value);
}
clearCache() {
@ -24,329 +117,206 @@ class SettingsPage extends React.PureComponent {
this.setState({ clearingCache: false });
window.location.href = 'index.html';
};
const clear = () => this.props.clearCache().then(success.bind(this));
const clear = () => this.props.clearCache().then(success);
setTimeout(clear, 1000, { once: true });
}
setDaemonSetting(name, value) {
this.props.setDaemonSetting(name, value);
}
onRunOnStartChange(event) {
this.setDaemonSetting('run_on_startup', event.target.checked);
}
onShareDataChange(event) {
this.setDaemonSetting('share_usage_data', event.target.checked);
}
onDownloadDirChange(event) {
this.setDaemonSetting('download_directory', event.target.value);
}
onKeyFeeChange(newValue) {
const setting = newValue;
// this is stupid and should be fixed... somewhere
if (setting && (setting.amount === undefined || setting.amount === null)) {
setting.amount = 0;
}
this.setDaemonSetting('max_key_fee', setting);
}
onKeyFeeDisableChange(isDisabled) {
this.setDaemonSetting('disable_max_key_fee', isDisabled);
}
onThemeChange(event) {
const { value } = event.target;
this.props.setClientSetting(settings.THEME, value);
}
onAutomaticDarkModeChange(event) {
this.props.setClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED, event.target.checked);
}
onInstantPurchaseEnabledChange(enabled) {
this.props.setClientSetting(settings.INSTANT_PURCHASE_ENABLED, enabled);
}
onInstantPurchaseMaxChange(newValue) {
this.props.setClientSetting(settings.INSTANT_PURCHASE_MAX, newValue);
}
// onMaxUploadPrefChange(isLimited) {
// if (!isLimited) {
// this.setDaemonSetting("max_upload", 0.0);
// }
// this.setState({
// isMaxUpload: isLimited,
// });
// }
//
// onMaxUploadFieldChange(event) {
// this.setDaemonSetting("max_upload", Number(event.target.value));
// }
//
// onMaxDownloadPrefChange(isLimited) {
// if (!isLimited) {
// this.setDaemonSetting("max_download", 0.0);
// }
// this.setState({
// isMaxDownload: isLimited,
// });
// }
//
// onMaxDownloadFieldChange(event) {
// this.setDaemonSetting("max_download", Number(event.target.value));
// }
onShowNsfwChange(event) {
this.props.setClientSetting(settings.SHOW_NSFW, event.target.checked);
}
onLanguageChange(e) {
this.props.changeLanguage(e.target.value);
this.forceUpdate();
}
onShowUnavailableChange(event) {
this.props.setClientSetting(settings.SHOW_UNAVAILABLE, event.target.checked);
}
componentWillMount() {
this.props.getThemes();
}
componentDidMount() {}
render() {
const {
daemonSettings,
language,
languages,
showNsfw,
instantPurchaseEnabled,
instantPurchaseMax,
showUnavailable,
theme,
currentTheme,
themes,
automaticDarkModeEnabled,
} = this.props;
if (!daemonSettings || Object.keys(daemonSettings).length === 0) {
return (
<main className="main--single-column">
<span className="empty">{__('Failed to load settings.')}</span>
</main>
);
}
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
return (
<Page>
{/*
<section className="card">
<div className="card__content">
<h4>{__("Language")}</h4>
</div>
<div className="card__content">
<div className="form-row">
<FormField
type="select"
name="language"
defaultValue={language}
onChange={this.onLanguageChange.bind(this)}
>
<option value="en">{__("English")}</option>
{Object.keys(languages).map(dLang =>
<option key={dLang} value={dLang}>
{languages[dLang]}
</option>
)}
</FormField>
</div>
</div>
</section> */}
<section className="card">
<div className="card__content">
<h4>{__('Download Directory')}</h4>
</div>
<div className="card__content">
<FormRow
type="directory"
name="download_directory"
defaultValue={daemonSettings.download_directory}
helper={__('LBRY downloads will be saved here.')}
onChange={this.onDownloadDirChange.bind(this)}
/>
</div>
</section>
<section className="card">
<div className="card__content">
<h4>{__('Max Purchase Price')}</h4>
</div>
<div className="card__content">
<FormRow
type="radio"
name="max_key_fee"
onClick={() => {
this.onKeyFeeDisableChange(true);
}}
defaultChecked={daemonSettings.disable_max_key_fee}
label={__('No Limit')}
/>
<div className="form-row">
<FormField
type="radio"
name="max_key_fee"
onClick={() => {
this.onKeyFeeDisableChange(false);
}}
defaultChecked={!daemonSettings.disable_max_key_fee}
label={daemonSettings.disable_max_key_fee ? __('Choose limit') : __('Limit to')}
{noDaemonSettings ? (
<section className="card card--section">
<div className="card__title">{__('Failed to load settings.')}</div>
</section>
) : (
<React.Fragment>
<section className="card card--section">
<div className="card__title">{__('Download Directory')}</div>
<span className="card__subtitle">{__('LBRY downloads will be saved here.')}</span>
<FileSelector
type="openDirectory"
currentPath={daemonSettings.download_directory}
onFileChosen={this.onDownloadDirChange}
/>
{!daemonSettings.disable_max_key_fee && (
</section>
<section className="card card--section">
<div className="card__title">{__('Max Purchase Price')}</div>
<span className="card__subtitle">
{__(
'This will prevent you from purchasing any content over a certain cost, as a safety measure.'
)}
</span>
<div className="card__content">
<FormField
type="radio"
name="no_max_purchase_limit"
checked={daemonSettings.disable_max_key_fee}
postfix={__('No Limit')}
onChange={() => {
this.onKeyFeeDisableChange(true);
}}
/>
<FormField
type="radio"
name="max_purchase_limit"
onChange={() => {
this.onKeyFeeDisableChange(false);
}}
checked={!daemonSettings.disable_max_key_fee}
postfix={__('Choose limit')}
/>
<FormFieldPrice
min="0"
onChange={this.onKeyFeeChange.bind(this)}
defaultValue={
name="max_key_fee"
label="Max purchase price"
min={0}
onChange={this.onKeyFeeChange}
disabled={daemonSettings.disable_max_key_fee}
price={
daemonSettings.max_key_fee
? daemonSettings.max_key_fee
: { currency: 'USD', amount: 50 }
}
/>
)}
</div>
<div className="form-field__helper">
{__(
'This will prevent you from purchasing any content over this cost, as a safety measure.'
)}
</div>
</div>
</section>
</div>
</section>
<section className="card">
<div className="card__content">
<h4>{__('Purchase Confirmations')}</h4>
</div>
<div className="card__content">
<FormRow
type="radio"
name="instant_purchase_max"
defaultChecked={!instantPurchaseEnabled}
label={__('Ask for confirmation of all purchases')}
onClick={e => {
this.onInstantPurchaseEnabledChange(false);
}}
/>
<div className="form-row">
<FormField
type="radio"
name="instant_purchase_max"
defaultChecked={instantPurchaseEnabled}
label={`Single-click purchasing of content less than${
instantPurchaseEnabled ? '' : '...'
}`}
onClick={e => {
this.onInstantPurchaseEnabledChange(true);
}}
/>
{instantPurchaseEnabled && (
<FormFieldPrice
min="0.1"
onChange={val => this.onInstantPurchaseMaxChange(val)}
defaultValue={instantPurchaseMax}
<section className="card card--section">
<div className="card__title">{__('Purchase Confirmations')}</div>
<div className="card__subtitle">
{__(
"When this option is chosen, LBRY won't ask you to confirm downloads below your chosen price."
)}
</div>
<div className="card__content">
<FormField
type="radio"
name="confirm_all_purchases"
checked={!instantPurchaseEnabled}
postfix={__('Always confirm before purchasing content')}
onChange={() => {
this.onInstantPurchaseEnabledChange(false);
}}
/>
)}
</div>
<div className="form-field__helper">
When this option is chosen, LBRY won't ask you to confirm downloads below the given
price.
</div>
</div>
</section>
<section className="card">
<div className="card__content">
<h4>{__('Content')}</h4>
</div>
<div className="card__content">
<FormRow
type="checkbox"
onChange={this.onShowUnavailableChange.bind(this)}
defaultChecked={showUnavailable}
label={__('Show unavailable content in search results')}
/>
<FormRow
label={__('Show NSFW content')}
type="checkbox"
onChange={this.onShowNsfwChange.bind(this)}
defaultChecked={showNsfw}
helper={__(
'NSFW content may include nudity, intense sexuality, profanity, or other adult content. By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. '
)}
/>
</div>
</section>
<FormField
type="radio"
name="instant_purchases"
checked={instantPurchaseEnabled}
postfix={__('Only confirm purchases over a certain price')}
onChange={() => {
this.onInstantPurchaseEnabledChange(true);
}}
/>
<FormFieldPrice
label={__('Confirmation price')}
disabled={!instantPurchaseEnabled}
min={0.1}
onChange={this.onInstantPurchaseMaxChange}
price={instantPurchaseMax}
/>
</div>
</section>
<section className="card">
<div className="card__content">
<h4>{__('Share Diagnostic Data')}</h4>
</div>
<div className="card__content">
<FormRow
type="checkbox"
onChange={this.onShareDataChange.bind(this)}
defaultChecked={daemonSettings.share_usage_data}
label={__('Help make LBRY better by contributing diagnostic data about my usage')}
/>
</div>
</section>
<section className="card card--section">
<div className="card__title">{__('Content Settings')}</div>
<div className="card__content">
<FormField
type="checkbox"
name="show_unavailable"
onChange={this.onShowUnavailableChange}
checked={showUnavailable}
postfix={__('Show unavailable content in search results')}
/>
<FormField
type="checkbox"
name="show_nsfw"
onChange={this.onShowNsfwChange}
checked={showNsfw}
postfix={__('Show NSFW content')}
helper={__(
'NSFW content may include nudity, intense sexuality, profanity, or other adult content. By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. '
)}
/>
</div>
</section>
<section className="card">
<div className="card__content">
<h4>{__('Theme')}</h4>
</div>
<div className="card__content">
<FormField
type="select"
onChange={this.onThemeChange.bind(this)}
defaultValue={theme}
className="form-field__input--inline"
>
{themes.map((theme, index) => (
<option key={theme} value={theme}>
{theme}
</option>
))}
</FormField>
<section className="card card--section">
<div className="card__title">{__('Share Diagnostic Data')}</div>
<div className="card__subtitle">{__('List what we are doing with the data here')}</div>
<div className="card__content">
<FormField
type="checkbox"
name="share_usage_data"
onChange={this.onShareDataChange}
checked={daemonSettings.share_usage_data}
postfix={__(
'Help make LBRY better by contributing diagnostic data about my usage'
)}
/>
</div>
</section>
<FormRow
type="checkbox"
onChange={this.onAutomaticDarkModeChange.bind(this)}
defaultChecked={automaticDarkModeEnabled}
label={__('Automatic dark mode (9pm to 8am)')}
/>
</div>
</section>
<section className="card card--section">
<div className="card__title">{__('Theme')}</div>
<section className="card">
<div className="card__content">
<h4>{__('Application Cache')}</h4>
</div>
<div className="card__content">
<p>
<Link
label={this.state.clearingCache ? __('Clearing') : __('Clear the cache')}
icon="icon-trash"
button="alt"
onClick={this.clearCache.bind(this)}
disabled={this.state.clearingCache}
<FormField
name="theme_select"
render={() => (
<select
name="theme_select"
id="theme_select"
onChange={this.onThemeChange}
value={currentTheme}
disabled={automaticDarkModeEnabled}
>
{themes.map(theme => (
<option key={theme} value={theme}>
{theme}
</option>
))}
</select>
)}
/>
</p>
</div>
</section>
<FormField
type="checkbox"
name="automatic_dark_mode"
onChange={this.onAutomaticDarkModeChange}
checked={automaticDarkModeEnabled}
postfix={__('Automatic dark mode (9pm to 8am)')}
/>
</section>
<section className="card card--section">
<div className="card__title">{__('Application Cache')}</div>
<span className="card__subtitle">
{__('This will delete your subscriptions, ... other stuff')}
</span>
<div className="card__content">
<Button
label={this.state.clearingCache ? __('Clearing') : __('Clear the cache')}
icon="AlertCircle"
onClick={this.clearCache}
disabled={this.state.clearingCache}
/>
</div>
</section>
</React.Fragment>
)}
</Page>
);
}

View file

@ -94,22 +94,39 @@ ul {
}
input {
cursor: pointer;
border-bottom: var(--input-border-size) solid var(--input-border-color);
color: var(--input-color);
line-height: 1;
cursor: text;
&[type='text'] {
cursor: text;
&[type='radio'],
&[type='checkbox'],
&[type='file'],
&[type='select'] {
cursor: pointer;
}
&[type='file'] {
border-bottom: none;
}
&.input-copyable {
background: var(--input-bg);
color: var(--input-disabled-color);
width: 100%;
font-family: 'Consolas', 'Lucida Console', 'Adobe Source Code Pro', monospace;
border-bottom: 1px dotted var(--color-divider);
width: 100%;
flex: 1;
}
&:disabled {
color: var(--input-disabled-color);
border-bottom: var(--input-border-size) solid var(--input-disabled-color);
}
}
button + input {
margin-left: $spacing-vertical * 2/3;
}
dl {

View file

@ -6,7 +6,6 @@
@import 'component/_button.scss';
@import 'component/_card.scss';
@import 'component/_file-download.scss';
@import 'component/_file-selector.scss';
@import 'component/_file-tile.scss';
@import 'component/_form-field.scss';
@import 'component/_header.scss';

View file

@ -1,4 +1,4 @@
// This will go away
// This will be moved and updated once we figure out how it should look
// It's for the download progress "button"
.faux-button-block {
display: inline-block;
@ -9,6 +9,8 @@
text-align: center;
border-radius: var(--button-radius);
text-transform: uppercase;
color: var(--color-white);
.icon {
top: 0em;
}

View file

@ -69,7 +69,7 @@
.card__title {
font-size: 1.5em;
font-weight: 800;
padding: $spacing-vertical / 3 0;
padding: 0;
}
.card__title--small {
@ -88,10 +88,20 @@
padding-top: 0;
}
.card-media__internal-links {
position: absolute;
top: $spacing-vertical * 2/3;
right: $spacing-vertical * 2/3;
// .card-media__internal__links should always be inside a card
.card {
.card-media__internal-links {
position: absolute;
top: $spacing-vertical * 2/3;
right: $spacing-vertical * 2/3;
}
}
.card--small {
.card-media__internal-links {
top: $spacing-vertical * 1/3;
right: $spacing-vertical * 1/3;
}
}
// Channel info with buttons on the right side
@ -107,7 +117,6 @@
.card__content {
margin-top: var(--card-margin);
margin-bottom: var(--card-margin);
}
.card__subtext-title {

View file

@ -1,23 +0,0 @@
.form-field--file,
.form-field--directory {
width: 100%;
}
.file-selector {
display: flex;
}
.file-selector__choose-button {
font-family: inherit;
line-height: 0;
color: inherit;
margin-right: 16px;
}
.file-selector__path {
font-size: 14px;
flex-grow: 2;
.input-copyable {
width: 100%;
}
}

View file

@ -1,27 +1,37 @@
.form-row {
display: flex;
flex-direction: row;
align-items: flex-end;
.form-field:not(:first-of-type) {
padding-left: $spacing-vertical;
}
&.form-row--padded {
padding: $spacing-vertical * 2/3 0;
padding-top: $spacing-vertical * 2/3;
}
&.form-row--centered {
align-items: center;
}
}
.form-field__wrapper {
.form-field__input {
display: flex;
padding: $spacing-vertical / 3 0;
padding-top: $spacing-vertical / 3;
// Hmm, not sure about this
// without this the checkbox isn't on the same line as other form-field text
input[type='checkbox'] {
input[type='checkbox'],
input[type='radio'] {
margin-top: 5px;
}
}
.form-field {
label {
cursor: pointer;
}
}
.form-field__error {
color: var(--color-error);
}
@ -30,18 +40,24 @@
color: var(--color-grey-dark);
}
.form-field__help {
color: var(--color-grey-dark);
font-size: 0.8em;
padding-top: $spacing-vertical * 1/3;
}
.form-field__prefix {
padding-right: 5px;
padding-right: $spacing-vertical * 1/3;
}
.form-field__postfix {
padding-left: 5px;
padding-left: $spacing-vertical * 1/3;
}
// Not sure if I like these
// Maybe this should be in gui.scss?
.input--lbc-amount {
width: 75px;
.input--price-amount {
width: 50px;
font-weight: 700;
}

View file

@ -14,7 +14,7 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px;
padding: 0 5px;
}
.nav__actions-history {