diff --git a/ElectronClient/app/app.js b/ElectronClient/app/app.js index fb1dbb524..83b153e86 100644 --- a/ElectronClient/app/app.js +++ b/ElectronClient/app/app.js @@ -333,7 +333,6 @@ class Application extends BaseApplication { const importItems = []; const exportItems = []; - const preferencesItems = []; const toolsItemsFirst = []; const templateItems = []; const ioService = new InteropService(); @@ -480,33 +479,6 @@ class Application extends BaseApplication { }, }; - preferencesItems.push({ - label: _('General Options'), - accelerator: 'CommandOrControl+,', - click: () => { - this.dispatch({ - type: 'NAV_GO', - routeName: 'Config', - }); - }, - }, { - label: _('Encryption options'), - click: () => { - this.dispatch({ - type: 'NAV_GO', - routeName: 'EncryptionConfig', - }); - }, - }, { - label: _('Web clipper options'), - click: () => { - this.dispatch({ - type: 'NAV_GO', - routeName: 'ClipperConfig', - }); - }, - }); - toolsItemsFirst.push(syncStatusItem, { type: 'separator', screens: ['Main'], @@ -563,7 +535,17 @@ class Application extends BaseApplication { }, }); - const toolsItems = toolsItemsFirst.concat(preferencesItems); + const toolsItems = toolsItemsFirst.push({ + label: _('Options'), + visible: !shim.isMac(), + accelerator: 'CommandOrControl+,', + click: () => { + this.dispatch({ + type: 'NAV_GO', + routeName: 'Config', + }); + }, + }); function _checkForUpdates(ctx) { bridge().checkForUpdates(false, bridge().window(), ctx.checkForUpdateLoggerPath(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') }); @@ -608,7 +590,13 @@ class Application extends BaseApplication { }, { label: _('Preferences...'), visible: shim.isMac() ? true : false, - submenu: preferencesItems, + accelerator: 'CommandOrControl+,', + click: () => { + this.dispatch({ + type: 'NAV_GO', + routeName: 'Config', + }); + }, }, { label: _('Check for updates...'), visible: shim.isMac() ? true : false, diff --git a/ElectronClient/app/bridge.js b/ElectronClient/app/bridge.js index e2e9ca1ca..4a42a61ea 100644 --- a/ElectronClient/app/bridge.js +++ b/ElectronClient/app/bridge.js @@ -122,6 +122,10 @@ class Bridge { checkForUpdates(inBackground, window, logFilePath, options); } + buildDir() { + return this.electronApp().buildDir(); + } + } let bridge_ = null; diff --git a/ElectronClient/app/build/images/chrome-logo.svg b/ElectronClient/app/build/images/chrome-logo.svg new file mode 100644 index 000000000..2d00856cc --- /dev/null +++ b/ElectronClient/app/build/images/chrome-logo.svg @@ -0,0 +1,2 @@ + +image/svg+xml \ No newline at end of file diff --git a/ElectronClient/app/build/images/firefox-logo.svg b/ElectronClient/app/build/images/firefox-logo.svg new file mode 100644 index 000000000..4b330869b --- /dev/null +++ b/ElectronClient/app/build/images/firefox-logo.svg @@ -0,0 +1 @@ +firefox-logo \ No newline at end of file diff --git a/ElectronClient/app/gui/ClipperConfigScreen.jsx b/ElectronClient/app/gui/ClipperConfigScreen.jsx index e845d117f..80060ed17 100644 --- a/ElectronClient/app/gui/ClipperConfigScreen.jsx +++ b/ElectronClient/app/gui/ClipperConfigScreen.jsx @@ -1,12 +1,12 @@ const React = require('react'); const { connect } = require('react-redux'); const { bridge } = require('electron').remote.require('./bridge'); -const { Header } = require('./Header.min.js'); const { themeStyle } = require('../theme.js'); const { _ } = require('lib/locale.js'); const ClipperServer = require('lib/ClipperServer'); const Setting = require('lib/models/Setting'); const { clipboard } = require('electron'); +const ExtensionBadge = require('./ExtensionBadge.min'); class ClipperConfigScreenComponent extends React.Component { constructor() { @@ -40,20 +40,17 @@ class ClipperConfigScreenComponent extends React.Component { } render() { - const style = this.props.style; const theme = themeStyle(this.props.theme); - const headerStyle = Object.assign({}, theme.headerStyle, { width: style.width }); - const containerStyle = Object.assign({}, theme.containerStyle, { overflowY: 'scroll', - height: style.height, }); const buttonStyle = Object.assign({}, theme.buttonStyle, { marginRight: 10 }); const stepBoxStyle = { - border: '1px solid #ccc', + border: '1px solid', + borderColor: theme.dividerColor, padding: 15, paddingTop: 0, marginBottom: 15, @@ -82,7 +79,7 @@ class ClipperConfigScreenComponent extends React.Component { ); } webClipperStatusComps.push( - ); @@ -108,7 +105,6 @@ class ClipperConfigScreenComponent extends React.Component { return (
-

{_('Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.')}

@@ -123,17 +119,9 @@ class ClipperConfigScreenComponent extends React.Component {

{_('Step 2: Install the extension')}

{_('Download and install the relevant extension for your browser:')}

-
-

- - - -

-

- - - -

+
+ +
diff --git a/ElectronClient/app/gui/ConfigMenuBar.jsx b/ElectronClient/app/gui/ConfigMenuBar.jsx new file mode 100644 index 000000000..204cdb003 --- /dev/null +++ b/ElectronClient/app/gui/ConfigMenuBar.jsx @@ -0,0 +1,44 @@ +const React = require('react'); +const styleSelector = require('./style/ConfigMenuBar'); +const Setting = require('lib/models/Setting'); + +function ConfigMenuBarButton(props) { + const style = styleSelector(null, props); + + const iconStyle = props.selected ? style.buttonIconSelected : style.buttonIcon; + const labelStyle = props.selected ? style.buttonLabelSelected : style.buttonLabel; + + return ( + + ); +} + +function ConfigMenuBar(props) { + const buttons = []; + + const style = styleSelector(null, props); + + for (const section of props.sections) { + buttons.push( { props.onSelectionChange({ section: section });}} + />); + } + + return ( +
+
+ {buttons} +
+
+ ); +} + +module.exports = ConfigMenuBar; diff --git a/ElectronClient/app/gui/ConfigScreen.jsx b/ElectronClient/app/gui/ConfigScreen.jsx index 4496905d1..a7b30fe1b 100644 --- a/ElectronClient/app/gui/ConfigScreen.jsx +++ b/ElectronClient/app/gui/ConfigScreen.jsx @@ -7,6 +7,9 @@ const pathUtils = require('lib/path-utils.js'); const { _ } = require('lib/locale.js'); const SyncTargetRegistry = require('lib/SyncTargetRegistry'); const shared = require('lib/components/shared/config-shared.js'); +const ConfigMenuBar = require('./ConfigMenuBar.min.js'); +const { EncryptionConfigScreen } = require('./EncryptionConfigScreen.min'); +const { ClipperConfigScreen } = require('./ClipperConfigScreen.min'); class ConfigScreenComponent extends React.Component { constructor() { @@ -14,6 +17,9 @@ class ConfigScreenComponent extends React.Component { shared.init(this); + this.state.selectedSectionName = 'general'; + this.state.screenName = ''; + this.checkSyncConfig_ = async () => { await shared.checkSyncConfig(this, this.state.settings); }; @@ -21,12 +27,57 @@ class ConfigScreenComponent extends React.Component { this.rowStyle_ = { marginBottom: 10, }; + + this.configMenuBar_selectionChange = this.configMenuBar_selectionChange.bind(this); } componentWillMount() { this.setState({ settings: this.props.settings }); } + componentDidMount() { + if (this.props.defaultSection) { + this.setState({ selectedSectionName: this.props.defaultSection }, () => { + this.switchSection(this.props.defaultSection); + }); + } + } + + sectionByName(name) { + const sections = shared.settingsSections({ device: 'desktop', settings: this.state.settings }); + for (const section of sections) { + if (section.name === name) return section; + } + + throw new Error('Invalid section name: ' + name); + } + + screenFromName(screenName) { + if (screenName === 'encryption') return ; + if (screenName === 'server') return ; + + throw new Error('Invalid screen name: ' + screenName); + } + + switchSection(name) { + const section = this.sectionByName(name); + let screenName = ''; + if (section.isScreen) { + screenName = section.name; + + if (this.hasChanges()) { + const ok = confirm(_('This will open a new screen. Save your current changes?')); + if (ok) shared.saveSettings(this); + } + } + + this.setState({ selectedSectionName: section.name, screenName: screenName }); + } + + configMenuBar_selectionChange(event) { + this.switchSection(event.section.name); + } + keyValueToArray(kv) { let output = []; for (let k in kv) { @@ -40,7 +91,7 @@ class ConfigScreenComponent extends React.Component { return output; } - sectionToComponent(key, section, settings) { + sectionToComponent(key, section, settings, selected) { const theme = themeStyle(this.props.theme); const settingComps = []; @@ -52,15 +103,11 @@ class ConfigScreenComponent extends React.Component { } const sectionStyle = { + marginTop: 20, marginBottom: 20, }; - const headerStyle = Object.assign({}, theme.headerStyle, { - borderBottomWidth: 1, - borderBottomColor: theme.dividerColor, - borderBottomStyle: 'solid', - paddingBottom: '.4em', - }); + if (!selected) sectionStyle.display = 'none'; if (section.name === 'general') { sectionStyle.borderTopWidth = 0; @@ -94,7 +141,6 @@ class ConfigScreenComponent extends React.Component { return (
-

{Setting.sectionNameToLabel(section.name)}

{noteComp}
{settingComps}
@@ -123,6 +169,10 @@ class ConfigScreenComponent extends React.Component { opacity: 0, }); + const checkboxLabelStyle = Object.assign({}, labelStyle, { + marginLeft: 8, + }); + const controlStyle = { display: 'inline-block', color: theme.color, @@ -136,6 +186,13 @@ class ConfigScreenComponent extends React.Component { maxWidth: '70em', }); + const textInputBaseStyle = Object.assign({}, controlStyle, { + border: '1px solid', + padding: '4px 6px', + borderColor: theme.dividerColor, + borderRadius: 4, + }); + const updateSettingValue = (key, value) => { // console.info(key + ' = ' + value); return shared.updateSettingValue(this, key, value); @@ -161,6 +218,8 @@ class ConfigScreenComponent extends React.Component { ); } + const selectStyle = Object.assign({}, controlStyle, { height: 22, borderColor: theme.dividerColor }); + return (
@@ -168,7 +227,7 @@ class ConfigScreenComponent extends React.Component {
{ onNumChange(event); @@ -373,6 +433,10 @@ class ConfigScreenComponent extends React.Component { this.props.dispatch({ type: 'NAV_BACK' }); } + hasChanges() { + return !!this.state.changedSettingKeys.length; + } + render() { const theme = themeStyle(this.props.theme); @@ -390,9 +454,9 @@ class ConfigScreenComponent extends React.Component { let settings = this.state.settings; - const containerStyle = Object.assign({}, theme.containerStyle, { padding: 10, paddingTop: 0 }); + const containerStyle = Object.assign({}, theme.containerStyle, { padding: 10, paddingTop: 0, display: 'flex', flex: 1 }); - const hasChanges = !!this.state.changedSettingKeys.length; + const hasChanges = this.hasChanges(); const buttonStyle = Object.assign({}, theme.buttonStyle, { display: 'inline-block', @@ -403,19 +467,33 @@ class ConfigScreenComponent extends React.Component { opacity: hasChanges ? 1 : theme.disabledOpacity, }); - const settingComps = shared.settingsToComponents2(this, 'desktop', settings); + const settingComps = shared.settingsToComponents2(this, 'desktop', settings, this.state.selectedSectionName); const buttonBarStyle = { display: 'flex', alignItems: 'center', - padding: 15, - borderBottomWidth: 1, - borderBottomStyle: 'solid', - borderBottomColor: theme.dividerColor, + padding: 10, + borderTopWidth: 1, + borderTopStyle: 'solid', + borderTopColor: theme.dividerColor, }; + const screenComp = this.state.screenName ?
{this.screenFromName(this.state.screenName)}
: null; + + if (screenComp) containerStyle.display = 'none'; + + const sections = shared.settingsSections({ device: 'desktop', settings }); + return (
+ + {screenComp} +
{settingComps}
- - + { !screenComp && ( +
+ + +
+ )}
-
{settingComps}
); } diff --git a/ElectronClient/app/gui/EncryptionConfigScreen.jsx b/ElectronClient/app/gui/EncryptionConfigScreen.jsx index 69c4a5770..f1c99611e 100644 --- a/ElectronClient/app/gui/EncryptionConfigScreen.jsx +++ b/ElectronClient/app/gui/EncryptionConfigScreen.jsx @@ -2,7 +2,6 @@ const React = require('react'); const { connect } = require('react-redux'); const Setting = require('lib/models/Setting'); const EncryptionService = require('lib/services/EncryptionService'); -const { Header } = require('./Header.min.js'); const { themeStyle } = require('../theme.js'); const { _ } = require('lib/locale.js'); const { time } = require('lib/time-utils.js'); @@ -85,17 +84,13 @@ class EncryptionConfigScreenComponent extends React.Component { } render() { - const style = this.props.style; const theme = themeStyle(this.props.theme); const masterKeys = this.state.masterKeys; const containerPadding = 10; - const headerStyle = Object.assign({}, theme.headerStyle, { width: style.width }); - const containerStyle = Object.assign({}, theme.containerStyle, { padding: containerPadding, overflow: 'auto', - height: style.height - theme.headerHeight - containerPadding * 2, }); const mkComps = []; @@ -200,7 +195,6 @@ class EncryptionConfigScreenComponent extends React.Component { return (
-
{
diff --git a/ElectronClient/app/gui/ExtensionBadge.jsx b/ElectronClient/app/gui/ExtensionBadge.jsx new file mode 100644 index 000000000..326086257 --- /dev/null +++ b/ElectronClient/app/gui/ExtensionBadge.jsx @@ -0,0 +1,45 @@ +const React = require('react'); +const { bridge } = require('electron').remote.require('./bridge'); +const styleSelector = require('./style/ExtensionBadge'); +const { _ } = require('lib/locale.js'); + +function platformAssets(type) { + if (type === 'firefox') { + return { + logoImage: bridge().buildDir() + '/images/firefox-logo.svg', + locationLabel: _('Firefox Extension'), + }; + } + + if (type === 'chrome') { + return { + logoImage: bridge().buildDir() + '/images/chrome-logo.svg', + locationLabel: _('Chrome Web Store'), + }; + } + + throw new Error('Invalid type:' + type); +} + +function ExtensionBadge(props) { + const style = styleSelector(null, props); + const assets = platformAssets(props.type); + + const onClick = () => { + bridge().openExternal(props.url); + }; + + const rootStyle = props.style ? Object.assign({}, style.root, props.style) : style.root; + + return ( + + +
+
{_('Get it now:')}
+
{assets.locationLabel}
+
+
+ ); +} + +module.exports = ExtensionBadge; diff --git a/ElectronClient/app/gui/MainScreen.jsx b/ElectronClient/app/gui/MainScreen.jsx index 6222918e5..c59013a5e 100644 --- a/ElectronClient/app/gui/MainScreen.jsx +++ b/ElectronClient/app/gui/MainScreen.jsx @@ -485,7 +485,10 @@ class MainScreenComponent extends React.Component { const onViewMasterKeysClick = () => { this.props.dispatch({ type: 'NAV_GO', - routeName: 'EncryptionConfig', + routeName: 'Config', + props: { + defaultSection: 'encryption', + }, }); }; diff --git a/ElectronClient/app/gui/Navigator.jsx b/ElectronClient/app/gui/Navigator.jsx index b69883376..abc3b4681 100644 --- a/ElectronClient/app/gui/Navigator.jsx +++ b/ElectronClient/app/gui/Navigator.jsx @@ -17,10 +17,7 @@ class NavigatorComponent extends Component { updateWindowTitle(title) { try { - if (bridge().window()) - bridge() - .window() - .setTitle(title); + if (bridge().window()) bridge().window().setTitle(title); } catch (error) { console.warn('updateWindowTitle', error); } diff --git a/ElectronClient/app/gui/NoteText.jsx b/ElectronClient/app/gui/NoteText.jsx index 222752669..06e0ca026 100644 --- a/ElectronClient/app/gui/NoteText.jsx +++ b/ElectronClient/app/gui/NoteText.jsx @@ -415,10 +415,10 @@ class NoteTextComponent extends React.Component { } componentDidUpdate() { - if (Setting.value('env') === 'dev' && this.webviewRef()) { - this.webviewRef().openDevTools(); - return; - } + // if (Setting.value('env') === 'dev' && this.webviewRef()) { + // this.webviewRef().openDevTools(); + // return; + // } if (this.webviewRef() && this.props.noteDevToolsVisible !== this.webviewRef().isDevToolsOpened()) { if (this.props.noteDevToolsVisible) { diff --git a/ElectronClient/app/gui/Root.jsx b/ElectronClient/app/gui/Root.jsx index f6d9a340d..bc01c024c 100644 --- a/ElectronClient/app/gui/Root.jsx +++ b/ElectronClient/app/gui/Root.jsx @@ -11,8 +11,6 @@ const { DropboxLoginScreen } = require('./DropboxLoginScreen.min.js'); const { StatusScreen } = require('./StatusScreen.min.js'); const { ImportScreen } = require('./ImportScreen.min.js'); const { ConfigScreen } = require('./ConfigScreen.min.js'); -const { EncryptionConfigScreen } = require('./EncryptionConfigScreen.min.js'); -const { ClipperConfigScreen } = require('./ClipperConfigScreen.min.js'); const { Navigator } = require('./Navigator.min.js'); const WelcomeUtils = require('lib/WelcomeUtils'); @@ -89,8 +87,6 @@ class RootComponent extends React.Component { Import: { screen: ImportScreen, title: () => _('Import') }, Config: { screen: ConfigScreen, title: () => _('Options') }, Status: { screen: StatusScreen, title: () => _('Synchronisation Status') }, - EncryptionConfig: { screen: EncryptionConfigScreen, title: () => _('Encryption Options') }, - ClipperConfig: { screen: ClipperConfigScreen, title: () => _('Clipper Options') }, }; return ; diff --git a/ElectronClient/app/gui/style/ConfigMenuBar.js b/ElectronClient/app/gui/style/ConfigMenuBar.js new file mode 100644 index 000000000..543b99f5d --- /dev/null +++ b/ElectronClient/app/gui/style/ConfigMenuBar.js @@ -0,0 +1,55 @@ +const { createSelector } = require('reselect'); +const { themeStyle } = require('../../theme.js'); + +const themeSelector = (state, props) => themeStyle(props.theme); + +const style = createSelector( + themeSelector, + (theme) => { + const output = { + button: { + fontFamily: theme.fontFamily, + minWidth: 52, + border: 'none', + flexDirection: 'column', + display: 'flex', + alignItems: 'center', + padding: 9, + backgroundColor: theme.backgroundColor, + }, + buttonIcon: { + fontSize: 24, + color: theme.colorFaded, + }, + buttonLabel: { + display: 'flex', + flex: 1, + alignItems: 'flex-end', + color: theme.colorFaded, + }, + root: { + minHeight: 58, + display: 'flex', + borderBottomWidth: 1, + borderBottomStyle: 'solid', + borderBottomColor: theme.dividerColor, + }, + barButtons: { + display: 'flex', + flexDirection: 'row', + }, + }; + + output.buttonIconSelected = Object.assign({}, output.buttonIcon, { + color: theme.highlightedColor, + }); + + output.buttonLabelSelected = Object.assign({}, output.buttonLabel, { + color: theme.color, + }); + + return output; + } +); + +module.exports = style; diff --git a/ElectronClient/app/gui/style/ExtensionBadge.js b/ElectronClient/app/gui/style/ExtensionBadge.js new file mode 100644 index 000000000..66ee4095e --- /dev/null +++ b/ElectronClient/app/gui/style/ExtensionBadge.js @@ -0,0 +1,49 @@ +const { createSelector } = require('reselect'); +const { themeStyle } = require('../../theme.js'); + +const themeSelector = (state, props) => themeStyle(props.theme); + +const style = createSelector( + themeSelector, + (theme) => { + const output = { + root: { + width: 220, + height: 60, + borderRadius: 4, + border: '1px solid', + borderColor: theme.dividerColor, + backgroundColor: theme.backgroundColor, + paddingLeft: 14, + paddingRight: 14, + paddingTop: 8, + paddingBottom: 8, + boxSizing: 'border-box', + display: 'flex', + flexDirection: 'row', + boxShadow: '0px 1px 1px rgba(0,0,0,0.3)', + }, + logo: { + width: 42, + height: 42, + }, + labelGroup: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + marginLeft: 14, + fontFamily: theme.fontFamily, + color: theme.color, + fontSize: theme.fontSize, + }, + locationLabel: { + fontSize: theme.fontSize * 1.2, + fontWeight: 'bold', + }, + }; + + return output; + } +); + +module.exports = style; diff --git a/ElectronClient/app/package-lock.json b/ElectronClient/app/package-lock.json index 63b5e7110..2249d4026 100644 --- a/ElectronClient/app/package-lock.json +++ b/ElectronClient/app/package-lock.json @@ -5932,6 +5932,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz", + "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==" + }, "resolve": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", diff --git a/ElectronClient/app/package.json b/ElectronClient/app/package.json index d13fda287..f99557842 100644 --- a/ElectronClient/app/package.json +++ b/ElectronClient/app/package.json @@ -24,7 +24,8 @@ "appId": "net.cozic.joplin-desktop", "npmRebuild": false, "extraResources": [ - "build/icons/*" + "build/icons/*", + "build/images/*" ], "win": { "rfc3161TimeStampServer": "http://sha256timestamp.ws.symantec.com/sha256/timestamp", @@ -141,6 +142,7 @@ "read-chunk": "^2.1.0", "readability-node": "^0.1.0", "redux": "^3.7.2", + "reselect": "^4.0.0", "server-destroy": "^1.0.1", "smalltalk": "^2.5.1", "sprintf-js": "^1.1.1", diff --git a/ElectronClient/app/style.css b/ElectronClient/app/style.css index f05d36184..03ffb2de6 100644 --- a/ElectronClient/app/style.css +++ b/ElectronClient/app/style.css @@ -22,6 +22,10 @@ table td, table th { border: 1px solid #ccc; } +a { + text-decoration: none; +} + ::-webkit-scrollbar { width: 7px; height: 7px; @@ -140,3 +144,8 @@ table td, table th { display: none; } } + +.config-menu-bar button:focus { + outline: 0 none; +} + diff --git a/ElectronClient/app/theme.js b/ElectronClient/app/theme.js index bb036471f..e0bca0f78 100644 --- a/ElectronClient/app/theme.js +++ b/ElectronClient/app/theme.js @@ -65,7 +65,11 @@ globalStyle.buttonStyle = { maxWidth: 160, paddingLeft: 12, paddingRight: 12, + paddingTop: 6, + paddingBottom: 6, boxShadow: '0px 1px 1px rgba(0,0,0,0.3)', + fontSize: globalStyle.fontSize, + borderRadius: 4, }; const lightStyle = { @@ -140,6 +144,8 @@ const darkStyle = { editorTheme: 'twilight', codeThemeCss: 'atom-one-dark-reasonable.css', + + highlightedColor: '#0066C7', }; // Solarized Styles @@ -314,6 +320,11 @@ function addExtraStyles(style) { style.dropdownList = Object.assign({}, style.inputStyle); + // In general the highlighted color, used to highlight text or icons, should be the same as selectedColor2 + // but some times, depending on the theme, it might be too dark or too light, so it can be + // specified directly by the theme too. + if (!style.highlightedColor) style.highlightedColor = style.selectedColor2; + return style; } diff --git a/ReactNativeClient/lib/components/shared/config-shared.js b/ReactNativeClient/lib/components/shared/config-shared.js index af96fc575..c2b732538 100644 --- a/ReactNativeClient/lib/components/shared/config-shared.js +++ b/ReactNativeClient/lib/components/shared/config-shared.js @@ -2,6 +2,7 @@ const Setting = require('lib/models/Setting.js'); const SyncTargetRegistry = require('lib/SyncTargetRegistry'); const ObjectUtils = require('lib/ObjectUtils'); const { _ } = require('lib/locale.js'); +const { createSelector } = require('reselect'); const shared = {}; @@ -79,26 +80,51 @@ shared.settingsToComponents = function(comp, device, settings) { return settingComps; }; -shared.settingsToComponents2 = function(comp, device, settings) { - const keys = Setting.keys(true, device); - const sectionComps = []; - const metadatas = []; +const deviceSelector = (state) => state.device; +const settingsSelector = (state) => state.settings; - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (!Setting.isPublic(key)) continue; +shared.settingsSections = createSelector( + deviceSelector, + settingsSelector, + (device, settings) => { + const keys = Setting.keys(true, device); + const metadatas = []; - const md = Setting.settingMetadata(key); - if (md.show && !md.show(settings)) continue; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (!Setting.isPublic(key)) continue; - metadatas.push(md); + const md = Setting.settingMetadata(key); + if (md.show && !md.show(settings)) continue; + + metadatas.push(md); + } + + const output = Setting.groupMetadatasBySections(metadatas); + + output.push({ + name: 'encryption', + metadatas: [], + isScreen: true, + }); + + output.push({ + name: 'server', + metadatas: [], + isScreen: true, + }); + + return output; } +); - const sections = Setting.groupMetadatasBySections(metadatas); +shared.settingsToComponents2 = function(comp, device, settings, selectedSectionName = '') { + const sectionComps = []; + const sections = shared.settingsSections({ device, settings }); for (let i = 0; i < sections.length; i++) { const section = sections[i]; - const sectionComp = comp.sectionToComponent(section.name, section, settings); + const sectionComp = comp.sectionToComponent(section.name, section, settings, selectedSectionName === section.name); if (!sectionComp) continue; sectionComps.push(sectionComp); } diff --git a/ReactNativeClient/lib/models/Setting.js b/ReactNativeClient/lib/models/Setting.js index c57b80a97..32d3b6d47 100644 --- a/ReactNativeClient/lib/models/Setting.js +++ b/ReactNativeClient/lib/models/Setting.js @@ -811,6 +811,21 @@ class Setting extends BaseModel { if (name === 'plugins') return _('Plugins'); if (name === 'application') return _('Application'); if (name === 'revisionService') return _('Note History'); + if (name === 'encryption') return _('Encryption'); + if (name === 'server') return _('Web Clipper'); + return name; + } + + static sectionNameToIcon(name, platform) { + if (name === 'general') return 'fa-sliders'; + if (name === 'sync') return 'fa-refresh'; + if (name === 'appearance') return 'fa-pencil'; + if (name === 'note') return 'fa-file-text-o'; + if (name === 'plugins') return 'fa-puzzle-piece'; + if (name === 'application') return 'fa-cog'; + if (name === 'revisionService') return 'fa-archive-org'; + if (name === 'encryption') return 'fa-key-modern'; + if (name === 'server') return 'fa-hand-scissors-o'; return name; }