group similar settings in preparation of tabular settings page

This commit is contained in:
Sean Yesmunt 2020-07-27 13:43:30 -04:00
parent 0b3dbfc8f8
commit d76ece04bc
7 changed files with 674 additions and 479 deletions

View file

@ -1269,5 +1269,9 @@
"These changes will appear shortly.": "These changes will appear shortly.",
"Block Channel": "Block Channel",
"Manage Notifications": "Manage Notifications",
"Uploads": "Uploads",
"Advanced Settings": "Advanced Settings",
"Browse": "Browse",
"Automatic Transcoding": "Automatic Transcoding",
"--end--": "--end--"
}

View file

@ -4,6 +4,7 @@ import React, { useEffect } from 'react';
import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
import SettingsPage from 'page/settings';
import SettingsNotificationsPage from 'page/settingsNotifications';
import SettingsAdvancedPage from 'page/settingsAdvanced';
import HelpPage from 'page/help';
// @if TARGET='app'
import BackupPage from 'page/backup';
@ -204,6 +205,7 @@ function AppRouter(props: Props) {
<Route path={`/$/${PAGES.TOP}`} exact component={TopPage} />
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
<Route path={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`} exact component={SettingsNotificationsPage} />
<Route path={`/$/${PAGES.SETTINGS_ADVANCED}`} exact component={SettingsAdvancedPage} />
<Route path={`/$/${PAGES.INVITE}/:referrer`} exact component={InvitedPage} />
<Route path={`/$/${PAGES.CHECKOUT}`} exact component={CheckoutPage} />

View file

@ -21,6 +21,7 @@ exports.REWARDS_VERIFY = 'rewards/verify';
exports.SEND = 'send';
exports.SETTINGS = 'settings';
exports.SETTINGS_NOTIFICATIONS = 'settings/notifications';
exports.SETTINGS_ADVANCED = 'settings/advanced';
exports.SHOW = 'show';
exports.ACCOUNT = 'account';
exports.SEARCH = 'search';

View file

@ -1,28 +1,15 @@
import { connect } from 'react-redux';
import {
doClearCache,
doNotifyEncryptWallet,
doNotifyDecryptWallet,
doNotifyForgetPassword,
doToggle3PAnalytics,
doOpenModal,
} from 'redux/actions/app';
import { doClearCache, doNotifyForgetPassword, doToggle3PAnalytics, doOpenModal } from 'redux/actions/app';
import { selectAllowAnalytics } from 'redux/selectors/app';
import {
doSetDaemonSetting,
doClearDaemonSetting,
doSetClientSetting,
doSetDarkTime,
doFindFFmpeg,
doSyncClientSettings,
} from 'redux/actions/settings';
import { doSetPlayingUri } from 'redux/actions/content';
import {
makeSelectClientSetting,
selectDaemonSettings,
selectFfmpegStatus,
selectFindingFFmpeg,
} from 'redux/selectors/settings';
import { makeSelectClientSetting, selectDaemonSettings } from 'redux/selectors/settings';
import { doWalletStatus, selectWalletIsEncrypted, SETTINGS } from 'lbry-redux';
import { selectBlockedChannelsCount } from 'redux/selectors/blocked';
import SettingsPage from './view';
@ -33,8 +20,6 @@ const select = state => ({
allowAnalytics: selectAllowAnalytics(state),
isAuthenticated: selectUserVerifiedEmail(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
instantPurchaseEnabled: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_ENABLED)(state),
instantPurchaseMax: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_MAX)(state),
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
themes: makeSelectClientSetting(SETTINGS.THEMES)(state),
automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state),
@ -46,8 +31,6 @@ const select = state => ({
floatingPlayer: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state),
hideReposts: makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state),
darkModeTimes: makeSelectClientSetting(SETTINGS.DARK_MODE_TIMES)(state),
ffmpegStatus: selectFfmpegStatus(state),
findingFFmpeg: selectFindingFFmpeg(state),
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
});
@ -59,13 +42,10 @@ const perform = dispatch => ({
toggle3PAnalytics: allow => dispatch(doToggle3PAnalytics(allow)),
clearCache: () => dispatch(doClearCache()),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
encryptWallet: () => dispatch(doNotifyEncryptWallet()),
decryptWallet: () => dispatch(doNotifyDecryptWallet()),
updateWalletStatus: () => dispatch(doWalletStatus()),
confirmForgetPassword: modalProps => dispatch(doNotifyForgetPassword(modalProps)),
clearPlayingUri: () => dispatch(doSetPlayingUri(null)),
setDarkTime: (time, options) => dispatch(doSetDarkTime(time, options)),
findFFmpeg: () => dispatch(doFindFFmpeg()),
openModal: (id, params) => dispatch(doOpenModal(id, params)),
});

View file

@ -1,39 +1,27 @@
// @flow
/* eslint react/no-unescaped-entities:0 */
/* eslint react/jsx-no-comment-textnodes:0 */
import * as PAGES from 'constants/pages';
import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons';
import * as React from 'react';
import { SETTINGS } from 'lbry-redux';
import { FormField, FormFieldPrice } from 'component/common/form';
import { FormField } from 'component/common/form';
import Button from 'component/button';
import I18nMessage from 'component/i18nMessage';
import Page from 'component/page';
import SettingLanguage from 'component/settingLanguage';
import SettingWalletServer from 'component/settingWalletServer';
import SettingAutoLaunch from 'component/settingAutoLaunch';
import FileSelector from 'component/common/file-selector';
import SyncToggle from 'component/syncToggle';
import { SETTINGS } from 'lbry-redux';
import Card from 'component/common/card';
import { getPasswordFromCookie } from 'util/saved-passwords';
import Spinner from 'component/spinner';
import SettingAccountPassword from 'component/settingAccountPassword';
import { getPasswordFromCookie } from 'util/saved-passwords';
import { Lbryio } from 'lbryinc';
import { withRouter } from 'react-router-dom';
// @if TARGET='app'
export const IS_MAC = process.platform === 'darwin';
// @endif
type Price = {
currency: string,
amount: number,
};
type SetDaemonSettingArg = boolean | string | number | Price;
type SetDaemonSettingArg = boolean | string | number;
type DarkModeTimes = {
from: { hour: string, min: string, formattedTime: string },
@ -48,11 +36,6 @@ type OptionTimes = {
type DaemonSettings = {
download_dir: string,
share_usage_data: boolean,
max_key_fee?: Price,
max_connections_per_download?: number,
save_files: boolean,
save_blobs: boolean,
ffmpeg_path: string,
};
type Props = {
@ -71,25 +54,17 @@ type Props = {
themes: Array<string>,
automaticDarkModeEnabled: boolean,
autoplay: boolean,
// autoDownload: boolean,
encryptWallet: () => void,
decryptWallet: () => void,
updateWalletStatus: () => void,
walletEncrypted: boolean,
userBlockedChannelsCount?: number,
hideBalance: boolean,
confirmForgetPassword: ({}) => void,
floatingPlayer: boolean,
hideReposts: ?boolean,
clearPlayingUri: () => void,
darkModeTimes: DarkModeTimes,
setDarkTime: (string, {}) => void,
ffmpegStatus: { available: boolean, which: string },
findingFFmpeg: boolean,
findFFmpeg: () => void,
openModal: string => void,
language?: string,
history: { goBack: () => void },
syncEnabled: boolean,
syncSettings: () => void,
};
@ -108,10 +83,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
storedPassword: false,
};
(this: any).onKeyFeeChange = this.onKeyFeeChange.bind(this);
(this: any).onMaxConnectionsChange = this.onMaxConnectionsChange.bind(this);
(this: any).onKeyFeeDisableChange = this.onKeyFeeDisableChange.bind(this);
(this: any).onInstantPurchaseMaxChange = this.onInstantPurchaseMaxChange.bind(this);
(this: any).onThemeChange = this.onThemeChange.bind(this);
(this: any).onAutomaticDarkModeChange = this.onAutomaticDarkModeChange.bind(this);
(this: any).onChangeTime = this.onChangeTime.bind(this);
@ -120,17 +91,8 @@ class SettingsPage extends React.PureComponent<Props, State> {
}
componentDidMount() {
const { isAuthenticated, ffmpegStatus, daemonSettings, findFFmpeg } = this.props;
// @if TARGET='app'
const { available } = ffmpegStatus;
const { ffmpeg_path: ffmpegPath } = daemonSettings;
if (!available) {
if (ffmpegPath) {
this.clearDaemonSetting('ffmpeg_path');
}
findFFmpeg();
}
// @endif
const { isAuthenticated } = this.props;
if (isAuthenticated || !IS_WEB) {
this.props.updateWalletStatus();
getPasswordFromCookie().then(p => {
@ -149,24 +111,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
}
}
onFFmpegFolder(path: string) {
this.setDaemonSetting('ffmpeg_path', path);
this.findFFmpeg();
}
onKeyFeeChange(newValue: Price) {
this.setDaemonSetting('max_key_fee', newValue);
}
onMaxConnectionsChange(event: SyntheticInputEvent<*>) {
const { value } = event.target;
this.setDaemonSetting('max_connections_per_download', value);
}
onKeyFeeDisableChange(isDisabled: boolean) {
if (isDisabled) this.setDaemonSetting('max_key_fee');
}
onThemeChange(event: SyntheticInputEvent<*>) {
const { value } = event.target;
@ -181,23 +125,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
this.props.setClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, value);
}
onInstantPurchaseEnabledChange(enabled: boolean) {
this.props.setClientSetting(SETTINGS.INSTANT_PURCHASE_ENABLED, enabled);
}
onInstantPurchaseMaxChange(newValue: Price) {
this.props.setClientSetting(SETTINGS.INSTANT_PURCHASE_MAX, newValue);
}
onChangeEncryptWallet() {
const { decryptWallet, walletEncrypted, encryptWallet } = this.props;
if (walletEncrypted) {
decryptWallet();
} else {
encryptWallet();
}
}
onConfirmForgetPassword() {
const { confirmForgetPassword } = this.props;
confirmForgetPassword({
@ -229,18 +156,11 @@ class SettingsPage extends React.PureComponent<Props, State> {
this.props.clearDaemonSetting(name);
}
findFFmpeg(): void {
this.props.findFFmpeg();
}
render() {
const {
daemonSettings,
ffmpegStatus,
allowAnalytics,
showNsfw,
instantPurchaseEnabled,
instantPurchaseMax,
isAuthenticated,
currentTheme,
themes,
@ -251,26 +171,16 @@ class SettingsPage extends React.PureComponent<Props, State> {
setDaemonSetting,
setClientSetting,
toggle3PAnalytics,
hideBalance,
userBlockedChannelsCount,
floatingPlayer,
hideReposts,
clearPlayingUri,
darkModeTimes,
clearCache,
findingFFmpeg,
openModal,
language,
} = this.props;
const { storedPassword } = this.state;
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
// @if TARGET='app'
const { available: ffmpegAvailable, which: ffmpegPath } = ffmpegStatus;
// @endif
const defaultMaxKeyFee = { currency: 'USD', amount: 50 };
const disableMaxKeyFee = !(daemonSettings && daemonSettings.max_key_fee);
const connectionOptions = [1, 2, 4, 6, 10, 20];
const startHours = ['18', '19', '20', '21'];
const endHours = ['5', '6', '7', '8'];
@ -294,15 +204,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
<Card title={__('Language')} actions={<SettingLanguage />} />
{isAuthenticated && <SettingAccountPassword />}
{/* @if TARGET='app' */}
<Card
title={__('Sync')}
subtitle={
walletEncrypted && !storedPassword
? __("To enable this feature, check 'Save Password' the next time you start the app.")
: null
}
actions={<SyncToggle disabled={walletEncrypted && !storedPassword} />}
/>
<Card
title={__('Download Directory')}
actions={
@ -318,121 +219,16 @@ class SettingsPage extends React.PureComponent<Props, State> {
</React.Fragment>
}
/>
<Card
title={__('Network and Data Settings')}
actions={
<React.Fragment>
<FormField
type="checkbox"
name="save_files"
onChange={() => setDaemonSetting('save_files', !daemonSettings.save_files)}
checked={daemonSettings.save_files}
label={__('Save all viewed content to your downloads directory')}
helper={__(
'Paid content and some file types are saved by default. Changing this setting will not affect previously downloaded content.'
)}
/>
<FormField
type="checkbox"
name="save_blobs"
onChange={() => setDaemonSetting('save_blobs', !daemonSettings.save_blobs)}
checked={daemonSettings.save_blobs}
label={__('Save hosting data to help the LBRY network')}
helper={
<React.Fragment>
{__("If disabled, LBRY will be very sad and you won't be helping improve the network.")}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/host-content" />.
</React.Fragment>
title={__('Sync')}
subtitle={
walletEncrypted && !storedPassword
? __("To enable this feature, check 'Save Password' the next time you start the app.")
: null
}
actions={<SyncToggle disabled={walletEncrypted && !storedPassword} />}
/>
</React.Fragment>
}
/>
<Card
title={__('Max Purchase Price')}
actions={
<React.Fragment>
<FormField
type="radio"
name="no_max_purchase_no_limit"
checked={disableMaxKeyFee}
label={__('No Limit')}
onChange={() => {
this.onKeyFeeDisableChange(true);
}}
/>
<FormField
type="radio"
name="max_purchase_limit"
checked={!disableMaxKeyFee}
onChange={() => {
this.onKeyFeeDisableChange(false);
this.onKeyFeeChange(defaultMaxKeyFee);
}}
label={__('Choose limit')}
/>
{!disableMaxKeyFee && (
<FormFieldPrice
language={language}
name="max_key_fee"
min={0}
onChange={this.onKeyFeeChange}
price={daemonSettings.max_key_fee ? daemonSettings.max_key_fee : defaultMaxKeyFee}
/>
)}
<p className="help">
{__('This will prevent you from purchasing any content over a certain cost, as a safety measure.')}
</p>
</React.Fragment>
}
/>
{/* @endif */}
<Card
title={__('Purchase and Tip Confirmations')}
actions={
<React.Fragment>
<FormField
type="radio"
name="confirm_all_purchases"
checked={!instantPurchaseEnabled}
label={__('Always confirm before purchasing content or tipping')}
onChange={() => {
this.onInstantPurchaseEnabledChange(false);
}}
/>
<FormField
type="radio"
name="instant_purchases"
checked={instantPurchaseEnabled}
label={__('Only confirm purchases or tips over a certain amount')}
onChange={() => {
this.onInstantPurchaseEnabledChange(true);
}}
/>
{instantPurchaseEnabled && (
<FormFieldPrice
name="confirmation_price"
min={0.1}
onChange={this.onInstantPurchaseMaxChange}
price={instantPurchaseMax}
/>
)}
<p className="help">
{__(
"When this option is chosen, LBRY won't ask you to confirm downloads or tips below your chosen amount."
)}
</p>
</React.Fragment>
}
/>
<Card
title={__('Content Settings')}
@ -504,84 +300,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
}
/>
{(isAuthenticated || !IS_WEB) && (
<>
<Card
title={__('Notifications')}
actions={
<div className="section__actions">
<Button
button="secondary"
label={__('Manage')}
icon={ICONS.SETTINGS}
navigate={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`}
/>
</div>
}
/>
<Card
title={__('Blocked Channels')}
subtitle={
userBlockedChannelsCount === 0
? __("You don't have blocked channels.")
: userBlockedChannelsCount === 1
? __('You have one blocked channel.')
: __('You have %channels% blocked channels.', { channels: userBlockedChannelsCount })
}
actions={
<div className="section__actions">
<Button
button="secondary"
label={__('Manage')}
icon={ICONS.SETTINGS}
navigate={`/$/${PAGES.BLOCKED}`}
/>
</div>
}
/>
</>
)}
{/* @if TARGET='app' */}
<Card
title={__('Share Usage and Diagnostic Data')}
subtitle={
<React.Fragment>
{__(
`This is information like error logging, performance tracking, and usage statistics. It includes your IP address and basic system details, but no other identifying information (unless you sign in to lbry.tv)`
)}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/privacypolicy" />
</React.Fragment>
}
actions={
<>
<FormField
type="checkbox"
name="share_internal"
onChange={() => setDaemonSetting('share_usage_data', !daemonSettings.share_usage_data)}
checked={daemonSettings.share_usage_data}
label={<React.Fragment>{__('Allow the app to share data to LBRY.inc')}</React.Fragment>}
helper={
isAuthenticated
? __('Internal sharing is required while signed in.')
: __('Internal sharing is required to participate in rewards programs.')
}
disabled={isAuthenticated && daemonSettings.share_usage_data}
/>
<FormField
type="checkbox"
name="share_third_party"
onChange={e => toggle3PAnalytics(e.target.checked)}
checked={allowAnalytics}
label={__('Allow the app to access third party analytics platforms')}
helper={__('We use detailed analytics to improve all aspects of the LBRY experience.')}
/>
</>
}
/>
{/* @endif */}
<Card
title={__('Appearance')}
actions={
@ -645,177 +363,97 @@ class SettingsPage extends React.PureComponent<Props, State> {
}
/>
{(isAuthenticated || !IS_WEB) && (
<Card
title={__('Wallet Security')}
actions={
<React.Fragment>
{/* @if TARGET='app' */}
<Card
title={__('Share Usage and Diagnostic Data')}
subtitle={
<React.Fragment>
{__(
`This is information like error logging, performance tracking, and usage statistics. It includes your IP address and basic system details, but no other identifying information (unless you sign in to lbry.tv)`
)}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/privacypolicy" />
</React.Fragment>
}
actions={
<>
<FormField
disabled
type="checkbox"
name="encrypt_wallet"
onChange={() => this.onChangeEncryptWallet()}
checked={walletEncrypted}
label={__('Encrypt my wallet with a custom password')}
name="share_internal"
onChange={() => setDaemonSetting('share_usage_data', !daemonSettings.share_usage_data)}
checked={daemonSettings.share_usage_data}
label={<React.Fragment>{__('Allow the app to share data to LBRY.inc')}</React.Fragment>}
helper={
<React.Fragment>
<I18nMessage
tokens={{
learn_more: (
<Button
button="link"
label={__('Learn more')}
href="https://lbry.com/faq/account-sync"
/>
),
}}
>
Wallet encryption is currently unavailable until it's supported for synced accounts. It will
be added back soon. %learn_more%.
</I18nMessage>
{/* {__('Secure your local wallet data with a custom password.')}{' '}
<strong>{__('Lost passwords cannot be recovered.')} </strong>
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />. */}
</React.Fragment>
isAuthenticated
? __('Internal sharing is required while signed in.')
: __('Internal sharing is required to participate in rewards programs.')
}
disabled={isAuthenticated && daemonSettings.share_usage_data}
/>
{walletEncrypted && this.state.storedPassword && (
<FormField
type="checkbox"
name="save_password"
onChange={this.onConfirmForgetPassword}
checked={this.state.storedPassword}
label={__('Save Password')}
helper={<React.Fragment>{__('Automatically unlock your wallet on startup')}</React.Fragment>}
name="share_third_party"
onChange={e => toggle3PAnalytics(e.target.checked)}
checked={allowAnalytics}
label={__('Allow the app to access third party analytics platforms')}
helper={__('We use detailed analytics to improve all aspects of the LBRY experience.')}
/>
</>
}
/>
)}
{/* @endif */}
<FormField
type="checkbox"
name="hide_balance"
onChange={() => setClientSetting(SETTINGS.HIDE_BALANCE, !hideBalance)}
checked={hideBalance}
label={__('Hide wallet balance in header')}
{(isAuthenticated || !IS_WEB) && (
<>
<Card
title={__('Notifications')}
actions={
<div className="section__actions">
<Button
button="secondary"
label={__('Manage')}
icon={ICONS.SETTINGS}
navigate={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`}
/>
</React.Fragment>
</div>
}
/>
)}
{/* @if TARGET='app' */}
<Card
title={
<span>
{__('Experimental Transcoding')}
{findingFFmpeg && <Spinner type="small" />}
</span>
title={__('Blocked Channels')}
subtitle={
userBlockedChannelsCount === 0
? __("You don't have blocked channels.")
: userBlockedChannelsCount === 1
? __('You have one blocked channel.')
: __('You have %channels% blocked channels.', { channels: userBlockedChannelsCount })
}
actions={
<React.Fragment>
<FileSelector
type="openDirectory"
placeholder={__('A Folder containing FFmpeg')}
currentPath={ffmpegPath || daemonSettings.ffmpeg_path}
onFileChosen={(newDirectory: WebFile) => {
this.onFFmpegFolder(newDirectory.path);
}}
disabled={Boolean(ffmpegPath)}
/>
<p className="help">
{ffmpegAvailable ? (
<I18nMessage
tokens={{
learn_more: (
<div className="section__actions">
<Button
button="link"
label={__('Learn more')}
href="https://lbry.com/faq/video-publishing-guide#automatic"
button="secondary"
label={__('Manage')}
icon={ICONS.SETTINGS}
navigate={`/$/${PAGES.BLOCKED}`}
/>
),
}}
>
FFmpeg is correctly configured. %learn_more%
</I18nMessage>
) : (
<I18nMessage
tokens={{
check_again: (
<Button
button="link"
label={__('Check again')}
onClick={() => this.findFFmpeg()}
disabled={findingFFmpeg}
/>
),
learn_more: (
<Button
button="link"
label={__('Learn more')}
href="https://lbry.com/faq/video-publishing-guide#automatic"
/>
),
}}
>
FFmpeg could not be found. Navigate to it or Install, Then %check_again% or quit and restart the
app. %learn_more%
</I18nMessage>
)}
</p>
</React.Fragment>
</div>
}
/>
{/* @endif */}
{!IS_WEB && (
<Card
title={__('Experimental Settings')}
actions={
<React.Fragment>
{/* @if TARGET='app' */}
{/*
Disabling below until we get downloads to work with shared subscriptions code
<FormField
type="checkbox"
name="auto_download"
onChange={() => setClientSetting(SETTINGS.AUTO_DOWNLOAD, !autoDownload)}
checked={autoDownload}
label={__('Automatically download new content from my subscriptions')}
helper={__(
"The latest file from each of your subscriptions will be downloaded for quick access as soon as it's published."
)}
/> */}
<fieldset-section>
<FormField
name="max_connections"
type="select"
label={__('Max Connections')}
helper={__(
'For users with good bandwidth, try a higher value to improve streaming and download speeds. Low bandwidth users may benefit from a lower setting. Default is 4.'
)}
min={1}
max={100}
onChange={this.onMaxConnectionsChange}
value={daemonSettings.max_connections_per_download}
>
{connectionOptions.map(connectionOption => (
<option key={connectionOption} value={connectionOption}>
{connectionOption}
</option>
))}
</FormField>
</fieldset-section>
<SettingWalletServer />
{/* @endif */}
</React.Fragment>
}
/>
)}
{/* @if TARGET='app' */}
{/* Auto launch in a hidden state doesn't work on mac https://github.com/Teamwork/node-auto-launch/issues/81 */}
{!IS_MAC && <Card title={__('Startup Preferences')} actions={<SettingAutoLaunch />} />}
{/* @endif */}
<Card
title={__('Advanced Settings')}
actions={
<div className="section__actions">
<Button
button="secondary"
label={__('Manage')}
icon={ICONS.SETTINGS}
navigate={`/$/${PAGES.SETTINGS_ADVANCED}`}
/>
</div>
}
/>
</>
)}
<Card
title={__('Application Cache')}
@ -829,6 +467,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
actions={
<Button
button="secondary"
icon={ICONS.ALERT}
label={this.state.clearingCache ? __('Clearing') : __('Clear Cache')}
onClick={clearCache}
disabled={this.state.clearingCache}
@ -842,4 +481,4 @@ class SettingsPage extends React.PureComponent<Props, State> {
}
}
export default withRouter(SettingsPage);
export default SettingsPage;

View file

@ -0,0 +1,48 @@
import { connect } from 'react-redux';
import { doClearCache, doNotifyEncryptWallet, doNotifyDecryptWallet, doNotifyForgetPassword } from 'redux/actions/app';
import { selectAllowAnalytics } from 'redux/selectors/app';
import {
doSetDaemonSetting,
doClearDaemonSetting,
doSetClientSetting,
doFindFFmpeg,
doSyncClientSettings,
} from 'redux/actions/settings';
import {
makeSelectClientSetting,
selectDaemonSettings,
selectFfmpegStatus,
selectFindingFFmpeg,
} from 'redux/selectors/settings';
import { doWalletStatus, selectWalletIsEncrypted, SETTINGS } from 'lbry-redux';
import SettingsPage from './view';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
const select = state => ({
daemonSettings: selectDaemonSettings(state),
allowAnalytics: selectAllowAnalytics(state),
isAuthenticated: selectUserVerifiedEmail(state),
instantPurchaseEnabled: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_ENABLED)(state),
instantPurchaseMax: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_MAX)(state),
walletEncrypted: selectWalletIsEncrypted(state),
hideBalance: makeSelectClientSetting(SETTINGS.HIDE_BALANCE)(state),
ffmpegStatus: selectFfmpegStatus(state),
findingFFmpeg: selectFindingFFmpeg(state),
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
});
const perform = dispatch => ({
setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)),
syncSettings: () => dispatch(doSyncClientSettings()),
clearDaemonSetting: key => dispatch(doClearDaemonSetting(key)),
clearCache: () => dispatch(doClearCache()),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
encryptWallet: () => dispatch(doNotifyEncryptWallet()),
decryptWallet: () => dispatch(doNotifyDecryptWallet()),
updateWalletStatus: () => dispatch(doWalletStatus()),
confirmForgetPassword: modalProps => dispatch(doNotifyForgetPassword(modalProps)),
findFFmpeg: () => dispatch(doFindFFmpeg()),
});
export default connect(select, perform)(SettingsPage);

View file

@ -0,0 +1,521 @@
// @flow
import * as React from 'react';
import { FormField, FormFieldPrice } from 'component/common/form';
import Button from 'component/button';
import I18nMessage from 'component/i18nMessage';
import Page from 'component/page';
import SettingWalletServer from 'component/settingWalletServer';
import SettingAutoLaunch from 'component/settingAutoLaunch';
import FileSelector from 'component/common/file-selector';
import { SETTINGS } from 'lbry-redux';
import Card from 'component/common/card';
import { getPasswordFromCookie } from 'util/saved-passwords';
import Spinner from 'component/spinner';
// @if TARGET='app'
const IS_MAC = process.platform === 'darwin';
// @endif
type Price = {
currency: string,
amount: number,
};
type SetDaemonSettingArg = boolean | string | number | Price;
type DaemonSettings = {
download_dir: string,
share_usage_data: boolean,
max_key_fee?: Price,
max_connections_per_download?: number,
save_files: boolean,
save_blobs: boolean,
ffmpeg_path: string,
};
type Props = {
setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
clearDaemonSetting: string => void,
setClientSetting: (string, SetDaemonSettingArg) => void,
daemonSettings: DaemonSettings,
isAuthenticated: boolean,
instantPurchaseEnabled: boolean,
instantPurchaseMax: Price,
encryptWallet: () => void,
decryptWallet: () => void,
updateWalletStatus: () => void,
walletEncrypted: boolean,
hideBalance: boolean,
confirmForgetPassword: ({}) => void,
ffmpegStatus: { available: boolean, which: string },
findingFFmpeg: boolean,
findFFmpeg: () => void,
language?: string,
syncEnabled: boolean,
syncSettings: () => void,
};
type State = {
clearingCache: boolean,
storedPassword: boolean,
};
class SettingsPage extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
clearingCache: false,
storedPassword: false,
};
(this: any).onKeyFeeChange = this.onKeyFeeChange.bind(this);
(this: any).onMaxConnectionsChange = this.onMaxConnectionsChange.bind(this);
(this: any).onKeyFeeDisableChange = this.onKeyFeeDisableChange.bind(this);
(this: any).onInstantPurchaseMaxChange = this.onInstantPurchaseMaxChange.bind(this);
(this: any).onThemeChange = this.onThemeChange.bind(this);
(this: any).onAutomaticDarkModeChange = this.onAutomaticDarkModeChange.bind(this);
(this: any).onConfirmForgetPassword = this.onConfirmForgetPassword.bind(this);
(this: any).onDone = this.onDone.bind(this);
}
componentDidMount() {
const { isAuthenticated, ffmpegStatus, daemonSettings, findFFmpeg } = this.props;
// @if TARGET='app'
const { available } = ffmpegStatus;
const { ffmpeg_path: ffmpegPath } = daemonSettings;
if (!available) {
if (ffmpegPath) {
this.clearDaemonSetting('ffmpeg_path');
}
findFFmpeg();
}
// @endif
if (isAuthenticated || !IS_WEB) {
this.props.updateWalletStatus();
getPasswordFromCookie().then(p => {
if (typeof p === 'string') {
this.setState({ storedPassword: true });
}
});
}
}
onDone() {
const { syncSettings } = this.props;
if (this.props.syncEnabled) {
syncSettings();
}
}
onFFmpegFolder(path: string) {
this.setDaemonSetting('ffmpeg_path', path);
this.findFFmpeg();
}
onKeyFeeChange(newValue: Price) {
this.setDaemonSetting('max_key_fee', newValue);
}
onMaxConnectionsChange(event: SyntheticInputEvent<*>) {
const { value } = event.target;
this.setDaemonSetting('max_connections_per_download', value);
}
onKeyFeeDisableChange(isDisabled: boolean) {
if (isDisabled) this.setDaemonSetting('max_key_fee');
}
onThemeChange(event: SyntheticInputEvent<*>) {
const { value } = event.target;
if (value === 'dark') {
this.onAutomaticDarkModeChange(false);
}
this.props.setClientSetting(SETTINGS.THEME, value);
}
onAutomaticDarkModeChange(value: boolean) {
this.props.setClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, value);
}
onInstantPurchaseEnabledChange(enabled: boolean) {
this.props.setClientSetting(SETTINGS.INSTANT_PURCHASE_ENABLED, enabled);
}
onInstantPurchaseMaxChange(newValue: Price) {
this.props.setClientSetting(SETTINGS.INSTANT_PURCHASE_MAX, newValue);
}
onChangeEncryptWallet() {
const { decryptWallet, walletEncrypted, encryptWallet } = this.props;
if (walletEncrypted) {
decryptWallet();
} else {
encryptWallet();
}
}
onConfirmForgetPassword() {
const { confirmForgetPassword } = this.props;
confirmForgetPassword({
callback: () => {
this.setState({ storedPassword: false });
},
});
}
setDaemonSetting(name: string, value: ?SetDaemonSettingArg): void {
this.props.setDaemonSetting(name, value);
}
clearDaemonSetting(name: string): void {
this.props.clearDaemonSetting(name);
}
findFFmpeg(): void {
this.props.findFFmpeg();
}
render() {
const {
daemonSettings,
ffmpegStatus,
instantPurchaseEnabled,
instantPurchaseMax,
isAuthenticated,
walletEncrypted,
setDaemonSetting,
setClientSetting,
hideBalance,
findingFFmpeg,
language,
} = this.props;
const { storedPassword } = this.state;
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
const defaultMaxKeyFee = { currency: 'USD', amount: 50 };
const disableMaxKeyFee = !(daemonSettings && daemonSettings.max_key_fee);
const connectionOptions = [1, 2, 4, 6, 10, 20];
// @if TARGET='app'
const { available: ffmpegAvailable, which: ffmpegPath } = ffmpegStatus;
// @endif
return (
<Page
noFooter
noSideNavigation
backout={{
backCB: () => this.onDone(),
title: __('Advanced Settings'),
backLabel: __('Done'),
}}
className="card-stack"
>
{!IS_WEB && noDaemonSettings ? (
<section className="card card--section">
<div className="card__title card__title--deprecated">{__('Failed to load settings.')}</div>
</section>
) : (
<div>
<Card
title={__('Network and Data Settings')}
actions={
<React.Fragment>
<FormField
type="checkbox"
name="save_files"
onChange={() => setDaemonSetting('save_files', !daemonSettings.save_files)}
checked={daemonSettings.save_files}
label={__('Save all viewed content to your downloads directory')}
helper={__(
'Paid content and some file types are saved by default. Changing this setting will not affect previously downloaded content.'
)}
/>
<FormField
type="checkbox"
name="save_blobs"
onChange={() => setDaemonSetting('save_blobs', !daemonSettings.save_blobs)}
checked={daemonSettings.save_blobs}
label={__('Save hosting data to help the LBRY network')}
helper={
<React.Fragment>
{__("If disabled, LBRY will be very sad and you won't be helping improve the network.")}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/host-content" />.
</React.Fragment>
}
/>
</React.Fragment>
}
/>
{/* @if TARGET='app' */}
<Card
title={__('Max Purchase Price')}
actions={
<React.Fragment>
<FormField
type="radio"
name="no_max_purchase_no_limit"
checked={disableMaxKeyFee}
label={__('No Limit')}
onChange={() => {
this.onKeyFeeDisableChange(true);
}}
/>
<FormField
type="radio"
name="max_purchase_limit"
checked={!disableMaxKeyFee}
onChange={() => {
this.onKeyFeeDisableChange(false);
this.onKeyFeeChange(defaultMaxKeyFee);
}}
label={__('Choose limit')}
/>
{!disableMaxKeyFee && (
<FormFieldPrice
language={language}
name="max_key_fee"
min={0}
onChange={this.onKeyFeeChange}
price={daemonSettings.max_key_fee ? daemonSettings.max_key_fee : defaultMaxKeyFee}
/>
)}
<p className="help">
{__('This will prevent you from purchasing any content over a certain cost, as a safety measure.')}
</p>
</React.Fragment>
}
/>
{/* @endif */}
<Card
title={__('Purchase and Tip Confirmations')}
actions={
<React.Fragment>
<FormField
type="radio"
name="confirm_all_purchases"
checked={!instantPurchaseEnabled}
label={__('Always confirm before purchasing content or tipping')}
onChange={() => {
this.onInstantPurchaseEnabledChange(false);
}}
/>
<FormField
type="radio"
name="instant_purchases"
checked={instantPurchaseEnabled}
label={__('Only confirm purchases or tips over a certain amount')}
onChange={() => {
this.onInstantPurchaseEnabledChange(true);
}}
/>
{instantPurchaseEnabled && (
<FormFieldPrice
name="confirmation_price"
min={0.1}
onChange={this.onInstantPurchaseMaxChange}
price={instantPurchaseMax}
/>
)}
<p className="help">
{__(
"When this option is chosen, LBRY won't ask you to confirm downloads or tips below your chosen amount."
)}
</p>
</React.Fragment>
}
/>
{(isAuthenticated || !IS_WEB) && (
<Card
title={__('Wallet Security')}
actions={
<React.Fragment>
{/* @if TARGET='app' */}
<FormField
disabled
type="checkbox"
name="encrypt_wallet"
onChange={() => this.onChangeEncryptWallet()}
checked={walletEncrypted}
label={__('Encrypt my wallet with a custom password')}
helper={
<React.Fragment>
<I18nMessage
tokens={{
learn_more: (
<Button
button="link"
label={__('Learn more')}
href="https://lbry.com/faq/account-sync"
/>
),
}}
>
Wallet encryption is currently unavailable until it's supported for synced accounts. It will
be added back soon. %learn_more%.
</I18nMessage>
{/* {__('Secure your local wallet data with a custom password.')}{' '}
<strong>{__('Lost passwords cannot be recovered.')} </strong>
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/wallet-encryption" />. */}
</React.Fragment>
}
/>
{walletEncrypted && storedPassword && (
<FormField
type="checkbox"
name="save_password"
onChange={this.onConfirmForgetPassword}
checked={storedPassword}
label={__('Save Password')}
helper={<React.Fragment>{__('Automatically unlock your wallet on startup')}</React.Fragment>}
/>
)}
{/* @endif */}
<FormField
type="checkbox"
name="hide_balance"
onChange={() => setClientSetting(SETTINGS.HIDE_BALANCE, !hideBalance)}
checked={hideBalance}
label={__('Hide wallet balance in header')}
/>
</React.Fragment>
}
/>
)}
{/* @if TARGET='app' */}
<Card
title={
<span>
{__('Automatic Transcoding')}
{findingFFmpeg && <Spinner type="small" />}
</span>
}
actions={
<React.Fragment>
<FileSelector
type="openDirectory"
placeholder={__('A Folder containing FFmpeg')}
currentPath={ffmpegPath || daemonSettings.ffmpeg_path}
onFileChosen={(newDirectory: WebFile) => {
this.onFFmpegFolder(newDirectory.path);
}}
disabled={Boolean(ffmpegPath)}
/>
<p className="help">
{ffmpegAvailable ? (
<I18nMessage
tokens={{
learn_more: (
<Button
button="link"
label={__('Learn more')}
href="https://lbry.com/faq/video-publishing-guide#automatic"
/>
),
}}
>
FFmpeg is correctly configured. %learn_more%
</I18nMessage>
) : (
<I18nMessage
tokens={{
check_again: (
<Button
button="link"
label={__('Check again')}
onClick={() => this.findFFmpeg()}
disabled={findingFFmpeg}
/>
),
learn_more: (
<Button
button="link"
label={__('Learn more')}
href="https://lbry.com/faq/video-publishing-guide#automatic"
/>
),
}}
>
FFmpeg could not be found. Navigate to it or Install, Then %check_again% or quit and restart the
app. %learn_more%
</I18nMessage>
)}
</p>
</React.Fragment>
}
/>
{/* @endif */}
{!IS_WEB && (
<Card
title={__('Experimental Settings')}
actions={
<React.Fragment>
{/* @if TARGET='app' */}
{/*
Disabling below until we get downloads to work with shared subscriptions code
<FormField
type="checkbox"
name="auto_download"
onChange={() => setClientSetting(SETTINGS.AUTO_DOWNLOAD, !autoDownload)}
checked={autoDownload}
label={__('Automatically download new content from my subscriptions')}
helper={__(
"The latest file from each of your subscriptions will be downloaded for quick access as soon as it's published."
)}
/> */}
<fieldset-section>
<FormField
name="max_connections"
type="select"
label={__('Max Connections')}
helper={__(
'For users with good bandwidth, try a higher value to improve streaming and download speeds. Low bandwidth users may benefit from a lower setting. Default is 4.'
)}
min={1}
max={100}
onChange={this.onMaxConnectionsChange}
value={daemonSettings.max_connections_per_download}
>
{connectionOptions.map(connectionOption => (
<option key={connectionOption} value={connectionOption}>
{connectionOption}
</option>
))}
</FormField>
</fieldset-section>
<SettingWalletServer />
{/* @endif */}
</React.Fragment>
}
/>
)}
{/* @if TARGET='app' */}
{/* Auto launch in a hidden state doesn't work on mac https://github.com/Teamwork/node-auto-launch/issues/81 */}
{!IS_MAC && <Card title={__('Startup Preferences')} actions={<SettingAutoLaunch />} />}
{/* @endif */}
</div>
)}
</Page>
);
}
}
export default SettingsPage;