implement settings redesign

This commit is contained in:
Sean Yesmunt 2018-02-06 01:31:47 -08:00
parent b07c332c0f
commit e49ad6a4bb
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 export { Form } from './form-components/form';
/* eslint-disable react/no-multi-comp */ export { FormRow } from './form-components/form-row';
import * as React from 'react'; export { FormField } from './form-components/form-field';
import Button from 'component/link'; export { FormFieldPrice } from './form-components/form-field-price';
import classnames from 'classnames'; export { Submit } from './form-components/submit';
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 */

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 { fileInfo, uri, openModal, claimIsMine, vertical } = this.props;
const claimId = fileInfo ? fileInfo.claim_id : ''; 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 ( return (
<section className={classnames('card__actions', { 'card__actions--vertical': vertical })}> <section className={classnames('card__actions', { 'card__actions--vertical': vertical })}>
<FileDownloadLink uri={uri} /> <FileDownloadLink uri={uri} />

View file

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

View file

@ -2,7 +2,7 @@
/* eslint-disable */ /* eslint-disable */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; 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 SimpleMDE from 'react-simplemde-editor';
import { formFieldNestedLabelTypes, formFieldId } from 'component/common/form'; import { formFieldNestedLabelTypes, formFieldId } from 'component/common/form';
import style from 'react-simplemde-editor/dist/simplemde.min.css'; import style from 'react-simplemde-editor/dist/simplemde.min.css';

View file

@ -1,63 +1,5 @@
import React from 'react'; // This just exists so the app builds. It will be removed
import FormField from 'component/formField';
class FormFieldPrice extends React.PureComponent { const FormFieldPrice = () => null;
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>
);
}
}
export default FormFieldPrice; export default FormFieldPrice;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,19 +1,112 @@
import React from 'react'; // @flow
import FormField from 'component/formField'; import * as React from 'react';
import { FormRow } from 'component/common/form'; import { FormField, FormFieldPrice } from 'component/common/form';
import * as settings from 'constants/settings'; import * as settings from 'constants/settings';
import lbry from 'lbry.js'; import Button from 'component/link';
import Link from 'component/link';
import FormFieldPrice from 'component/formFieldPrice';
import Page from 'component/page'; import Page from 'component/page';
import FileSelector from 'component/common/file-selector';
class SettingsPage extends React.PureComponent { export type Price = {
constructor(props) { 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); super(props);
this.state = { this.state = {
clearingCache: false, 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() { clearCache() {
@ -24,265 +117,136 @@ class SettingsPage extends React.PureComponent {
this.setState({ clearingCache: false }); this.setState({ clearingCache: false });
window.location.href = 'index.html'; 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 }); 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() { render() {
const { const {
daemonSettings, daemonSettings,
language,
languages,
showNsfw, showNsfw,
instantPurchaseEnabled, instantPurchaseEnabled,
instantPurchaseMax, instantPurchaseMax,
showUnavailable, showUnavailable,
theme, currentTheme,
themes, themes,
automaticDarkModeEnabled, automaticDarkModeEnabled,
} = this.props; } = this.props;
if (!daemonSettings || Object.keys(daemonSettings).length === 0) { const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
return (
<main className="main--single-column">
<span className="empty">{__('Failed to load settings.')}</span>
</main>
);
}
return ( return (
<Page> <Page>
{/* {noDaemonSettings ? (
<section className="card"> <section className="card card--section">
<div className="card__content"> <div className="card__title">{__('Failed to load settings.')}</div>
<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>
<section className="card"> ) : (
<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}
/>
</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"> <div className="card__content">
<h4>{__('Max Purchase Price')}</h4> <FormField
</div>
<div className="card__content">
<FormRow
type="radio" type="radio"
name="max_key_fee" name="no_max_purchase_limit"
onClick={() => { checked={daemonSettings.disable_max_key_fee}
postfix={__('No Limit')}
onChange={() => {
this.onKeyFeeDisableChange(true); this.onKeyFeeDisableChange(true);
}} }}
defaultChecked={daemonSettings.disable_max_key_fee}
label={__('No Limit')}
/> />
<div className="form-row">
<FormField <FormField
type="radio" type="radio"
name="max_key_fee" name="max_purchase_limit"
onClick={() => { onChange={() => {
this.onKeyFeeDisableChange(false); this.onKeyFeeDisableChange(false);
}} }}
defaultChecked={!daemonSettings.disable_max_key_fee} checked={!daemonSettings.disable_max_key_fee}
label={daemonSettings.disable_max_key_fee ? __('Choose limit') : __('Limit to')} postfix={__('Choose limit')}
/> />
{!daemonSettings.disable_max_key_fee && (
<FormFieldPrice <FormFieldPrice
min="0" name="max_key_fee"
onChange={this.onKeyFeeChange.bind(this)} label="Max purchase price"
defaultValue={ min={0}
onChange={this.onKeyFeeChange}
disabled={daemonSettings.disable_max_key_fee}
price={
daemonSettings.max_key_fee daemonSettings.max_key_fee
? daemonSettings.max_key_fee ? daemonSettings.max_key_fee
: { currency: 'USD', amount: 50 } : { 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> </div>
</section> </section>
<section className="card"> <section className="card card--section">
<div className="card__content"> <div className="card__title">{__('Purchase Confirmations')}</div>
<h4>{__('Purchase Confirmations')}</h4> <div className="card__subtitle">
{__(
"When this option is chosen, LBRY won't ask you to confirm downloads below your chosen price."
)}
</div> </div>
<div className="card__content"> <div className="card__content">
<FormRow <FormField
type="radio" type="radio"
name="instant_purchase_max" name="confirm_all_purchases"
defaultChecked={!instantPurchaseEnabled} checked={!instantPurchaseEnabled}
label={__('Ask for confirmation of all purchases')} postfix={__('Always confirm before purchasing content')}
onClick={e => { onChange={() => {
this.onInstantPurchaseEnabledChange(false); this.onInstantPurchaseEnabledChange(false);
}} }}
/> />
<div className="form-row">
<FormField <FormField
type="radio" type="radio"
name="instant_purchase_max" name="instant_purchases"
defaultChecked={instantPurchaseEnabled} checked={instantPurchaseEnabled}
label={`Single-click purchasing of content less than${ postfix={__('Only confirm purchases over a certain price')}
instantPurchaseEnabled ? '' : '...' onChange={() => {
}`}
onClick={e => {
this.onInstantPurchaseEnabledChange(true); this.onInstantPurchaseEnabledChange(true);
}} }}
/> />
{instantPurchaseEnabled && (
<FormFieldPrice <FormFieldPrice
min="0.1" label={__('Confirmation price')}
onChange={val => this.onInstantPurchaseMaxChange(val)} disabled={!instantPurchaseEnabled}
defaultValue={instantPurchaseMax} min={0.1}
onChange={this.onInstantPurchaseMaxChange}
price={instantPurchaseMax}
/> />
)}
</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> </div>
</section> </section>
<section className="card"> <section className="card card--section">
<div className="card__title">{__('Content Settings')}</div>
<div className="card__content"> <div className="card__content">
<h4>{__('Content')}</h4> <FormField
</div>
<div className="card__content">
<FormRow
type="checkbox" type="checkbox"
onChange={this.onShowUnavailableChange.bind(this)} name="show_unavailable"
defaultChecked={showUnavailable} onChange={this.onShowUnavailableChange}
label={__('Show unavailable content in search results')} checked={showUnavailable}
postfix={__('Show unavailable content in search results')}
/> />
<FormRow <FormField
label={__('Show NSFW content')}
type="checkbox" type="checkbox"
onChange={this.onShowNsfwChange.bind(this)} name="show_nsfw"
defaultChecked={showNsfw} onChange={this.onShowNsfwChange}
checked={showNsfw}
postfix={__('Show NSFW content')}
helper={__( 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. ' '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. '
)} )}
@ -290,63 +254,69 @@ class SettingsPage extends React.PureComponent {
</div> </div>
</section> </section>
<section className="card"> <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"> <div className="card__content">
<h4>{__('Share Diagnostic Data')}</h4> <FormField
</div>
<div className="card__content">
<FormRow
type="checkbox" type="checkbox"
onChange={this.onShareDataChange.bind(this)} name="share_usage_data"
defaultChecked={daemonSettings.share_usage_data} onChange={this.onShareDataChange}
label={__('Help make LBRY better by contributing diagnostic data about my usage')} checked={daemonSettings.share_usage_data}
postfix={__(
'Help make LBRY better by contributing diagnostic data about my usage'
)}
/> />
</div> </div>
</section> </section>
<section className="card"> <section className="card card--section">
<div className="card__content"> <div className="card__title">{__('Theme')}</div>
<h4>{__('Theme')}</h4>
</div>
<div className="card__content">
<FormField <FormField
type="select" name="theme_select"
onChange={this.onThemeChange.bind(this)} render={() => (
defaultValue={theme} <select
className="form-field__input--inline" name="theme_select"
id="theme_select"
onChange={this.onThemeChange}
value={currentTheme}
disabled={automaticDarkModeEnabled}
> >
{themes.map((theme, index) => ( {themes.map(theme => (
<option key={theme} value={theme}> <option key={theme} value={theme}>
{theme} {theme}
</option> </option>
))} ))}
</FormField> </select>
)}
<FormRow />
type="checkbox"
onChange={this.onAutomaticDarkModeChange.bind(this)} <FormField
defaultChecked={automaticDarkModeEnabled} type="checkbox"
label={__('Automatic dark mode (9pm to 8am)')} name="automatic_dark_mode"
onChange={this.onAutomaticDarkModeChange}
checked={automaticDarkModeEnabled}
postfix={__('Automatic dark mode (9pm to 8am)')}
/> />
</div>
</section> </section>
<section className="card"> <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"> <div className="card__content">
<h4>{__('Application Cache')}</h4> <Button
</div>
<div className="card__content">
<p>
<Link
label={this.state.clearingCache ? __('Clearing') : __('Clear the cache')} label={this.state.clearingCache ? __('Clearing') : __('Clear the cache')}
icon="icon-trash" icon="AlertCircle"
button="alt" onClick={this.clearCache}
onClick={this.clearCache.bind(this)}
disabled={this.state.clearingCache} disabled={this.state.clearingCache}
/> />
</p>
</div> </div>
</section> </section>
</React.Fragment>
)}
</Page> </Page>
); );
} }

View file

@ -94,22 +94,39 @@ ul {
} }
input { input {
cursor: pointer;
border-bottom: var(--input-border-size) solid var(--input-border-color); border-bottom: var(--input-border-size) solid var(--input-border-color);
color: var(--input-color); color: var(--input-color);
line-height: 1; line-height: 1;
&[type='text'] {
cursor: text; cursor: text;
&[type='radio'],
&[type='checkbox'],
&[type='file'],
&[type='select'] {
cursor: pointer;
}
&[type='file'] {
border-bottom: none;
} }
&.input-copyable { &.input-copyable {
background: var(--input-bg); background: var(--input-bg);
color: var(--input-disabled-color); color: var(--input-disabled-color);
width: 100%;
font-family: 'Consolas', 'Lucida Console', 'Adobe Source Code Pro', monospace; font-family: 'Consolas', 'Lucida Console', 'Adobe Source Code Pro', monospace;
border-bottom: 1px dotted var(--color-divider); 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 { dl {

View file

@ -6,7 +6,6 @@
@import 'component/_button.scss'; @import 'component/_button.scss';
@import 'component/_card.scss'; @import 'component/_card.scss';
@import 'component/_file-download.scss'; @import 'component/_file-download.scss';
@import 'component/_file-selector.scss';
@import 'component/_file-tile.scss'; @import 'component/_file-tile.scss';
@import 'component/_form-field.scss'; @import 'component/_form-field.scss';
@import 'component/_header.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" // It's for the download progress "button"
.faux-button-block { .faux-button-block {
display: inline-block; display: inline-block;
@ -9,6 +9,8 @@
text-align: center; text-align: center;
border-radius: var(--button-radius); border-radius: var(--button-radius);
text-transform: uppercase; text-transform: uppercase;
color: var(--color-white);
.icon { .icon {
top: 0em; top: 0em;
} }

View file

@ -69,7 +69,7 @@
.card__title { .card__title {
font-size: 1.5em; font-size: 1.5em;
font-weight: 800; font-weight: 800;
padding: $spacing-vertical / 3 0; padding: 0;
} }
.card__title--small { .card__title--small {
@ -88,11 +88,21 @@
padding-top: 0; padding-top: 0;
} }
// .card-media__internal__links should always be inside a card
.card {
.card-media__internal-links { .card-media__internal-links {
position: absolute; position: absolute;
top: $spacing-vertical * 2/3; top: $spacing-vertical * 2/3;
right: $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 // Channel info with buttons on the right side
.card__channel-info { .card__channel-info {
@ -107,7 +117,6 @@
.card__content { .card__content {
margin-top: var(--card-margin); margin-top: var(--card-margin);
margin-bottom: var(--card-margin);
} }
.card__subtext-title { .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 { .form-row {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: flex-end;
.form-field:not(:first-of-type) { .form-field:not(:first-of-type) {
padding-left: $spacing-vertical; padding-left: $spacing-vertical;
} }
&.form-row--padded { &.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; display: flex;
padding: $spacing-vertical / 3 0; padding-top: $spacing-vertical / 3;
// Hmm, not sure about this input[type='checkbox'],
// without this the checkbox isn't on the same line as other form-field text input[type='radio'] {
input[type='checkbox'] {
margin-top: 5px; margin-top: 5px;
} }
} }
.form-field {
label {
cursor: pointer;
}
}
.form-field__error { .form-field__error {
color: var(--color-error); color: var(--color-error);
} }
@ -30,18 +40,24 @@
color: var(--color-grey-dark); 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 { .form-field__prefix {
padding-right: 5px; padding-right: $spacing-vertical * 1/3;
} }
.form-field__postfix { .form-field__postfix {
padding-left: 5px; padding-left: $spacing-vertical * 1/3;
} }
// Not sure if I like these // Not sure if I like these
// Maybe this should be in gui.scss? // Maybe this should be in gui.scss?
.input--lbc-amount { .input--price-amount {
width: 75px; width: 50px;
font-weight: 700; font-weight: 700;
} }

View file

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