diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..d83e491 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,45 @@ +{ + 'extends': [ + 'xo', + 'plugin:react/recommended' + ], + "rules": { + "array-bracket-spacing": [ + "error", + "always" + ], + "padded-blocks": [ + "error", + { + "blocks": "always", + "switches": "always", + "classes": "always" + } + ], + "padding-line-between-statements": [ + "error", + { + "blankLine": "always", + "prev": "multiline-block-like", + "next": "*" + }, + { + "blankLine": "always", + "prev": "*", + "next": "return" + } + ], + "object-curly-spacing": [ + "error", + "always" + ], + "semi": [ + 2, + "never" + ], + "space-in-parens": [ + "error", + "always" + ] + } +} diff --git a/README.md b/README.md index 264397e..fd383a6 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ - [Electron Preferences](#electron-preferences) - [Introduction](#introduction) - [Getting Started](#getting-started) - - [Initializing the Preferences Service](#initializing-the-preferences-service) - - [Interacting with the Preferences Service from the Main Process](#interacting-with-the-preferences-service-from-the-main-process) - - [Interacting with the Preferences Service from the Renderer Process](#interacting-with-the-preferences-service-from-the-renderer-process) + - [Initializing the Preferences Service](#initializing-the-preferences-service) + - [Interacting with the Preferences Service from the Main Process](#interacting-with-the-preferences-service-from-the-main-process) + - [Interacting with the Preferences Service from the Renderer Process](#interacting-with-the-preferences-service-from-the-renderer-process) - [Field Types](#field-types) - [Icons](#icons) @@ -24,11 +24,13 @@ Using the API, developers can: To see the library in action, clone this repository and see the demo application that is included within the `example` folder: - $ git clone https://github.com/tkambler/electron-preferences.git - $ cd electron-preferences - $ npm i - $ npm run build - $ npm run example + $ git clone https://github.com/tkambler/electron-preferences.git + $ cd electron-preferences + $ npm i + $ npm run build + $ npm run example + $ npm run lint + @@ -47,200 +49,200 @@ const os = require('os'); const ElectronPreferences = require('electron-preferences'); const preferences = new ElectronPreferences({ - /** - * Where should preferences be saved? - */ - 'dataStore': path.resolve(app.getPath('userData'), 'preferences.json'), - /** - * Default values. - */ - 'defaults': { - 'notes': { - 'folder': path.resolve(os.homedir(), 'Notes') - }, - 'markdown': { - 'auto_format_links': true, - 'show_gutter': false - }, - 'preview': { - 'show': true - }, - 'drawer': { - 'show': true - } - }, - /** - * The preferences window is divided into sections. Each section has a label, an icon, and one or - * more fields associated with it. Each section should also be given a unique ID. - */ - 'sections': [ - { - 'id': 'about', - 'label': 'About You', - /** - * See the list of available icons below. - */ - 'icon': 'single-01', - 'form': { - 'groups': [ - { - /** - * Group heading is optional. - */ - 'label': 'About You', - 'fields': [ - { - 'label': 'First Name', - 'key': 'first_name', - 'type': 'text', - /** - * Optional text to be displayed beneath the field. - */ - 'help': 'What is your first name?' - }, - { - 'label': 'Last Name', - 'key': 'last_name', - 'type': 'text', - 'help': 'What is your last name?' - }, - { - 'label': 'Gender', - 'key': 'gender', - 'type': 'dropdown', - 'options': [ - {'label': 'Male', 'value': 'male'}, - {'label': 'Female', 'value': 'female'}, - {'label': 'Unspecified', 'value': 'unspecified'}, - ], - 'help': 'What is your gender?' - }, - { - 'label': 'Which of the following foods do you like?', - 'key': 'foods', - 'type': 'checkbox', - 'options': [ - { 'label': 'Ice Cream', 'value': 'ice_cream' }, - { 'label': 'Carrots', 'value': 'carrots' }, - { 'label': 'Cake', 'value': 'cake' }, - { 'label': 'Spinach', 'value': 'spinach' } - ], - 'help': 'Select one or more foods that you like.' - }, - { - 'label': 'Coolness', - 'key': 'coolness', - 'type': 'slider', - 'min': 0, - 'max': 9001 - }, - { - 'label': 'Eye Color', - 'key': 'eye_color', - 'type': 'color', - 'format': 'hex', // can be hex, hsl or rgb - 'help': 'Your eye color' - }, - { - 'label': 'Hair Color', - 'key': 'hair_color', - 'type': 'color', - 'format': 'rgb', - 'help': 'Your hair color' - } - ] - } - ] - } - }, - { - 'id': 'notes', - 'label': 'Notes', - 'icon': 'folder-15', - 'form': { - 'groups': [ - { - 'label': 'Stuff', - 'fields': [ - { - 'label': 'Read notes from folder', - 'key': 'folder', - 'type': 'directory', - 'help': 'The location where your notes will be stored.' - }, - { - 'heading': 'Important Message', - 'content': '

The quick brown fox jumps over the long white fence. The quick brown fox jumps over the long white fence. The quick brown fox jumps over the long white fence. The quick brown fox jumps over the long white fence.

', - 'type': 'message', - } - ] - } - ] - } - }, - { - 'id': 'space', - 'label': 'Other Settings', - 'icon': 'spaceship', - 'form': { - 'groups': [ - { - 'label': 'Other Settings', - 'fields': [ - { - 'label': 'Phone Number', - 'key': 'phone_number', - 'type': 'text', - 'help': 'What is your phone number?' - }, - { - 'label': "Foo or Bar?", - 'key': 'foobar', - 'type': 'radio', - 'options': [ - {'label': 'Foo', 'value': 'foo'}, - {'label': 'Bar', 'value': 'bar'}, - {'label': 'FooBar', 'value': 'foobar'}, - ], - 'help': 'Foo? Bar?' - } - ] - } - ] - } - } - ], - /** - * These parameters on the preference window settings can be overwrinten - */ - browserWindowOpts: { - 'title': 'My custom preferences title', - 'width': 900, - 'maxWidth': 1000, - 'height': 700, - 'maxHeight': 1000, - 'resizable': true, - 'maximizable': false, - //... - }, - /** - * These parameters create an optional menu bar - */ - menu: Menu.buildFromTemplate( - [ - { - label: 'Window', - role: 'window', - submenu: [ - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - } - ] - } - ] - ) + /** + * Where should preferences be saved? + */ + 'dataStore': path.resolve(app.getPath('userData'), 'preferences.json'), + /** + * Default values. + */ + 'defaults': { + 'notes': { + 'folder': path.resolve(os.homedir(), 'Notes') + }, + 'markdown': { + 'auto_format_links': true, + 'show_gutter': false + }, + 'preview': { + 'show': true + }, + 'drawer': { + 'show': true + } + }, + /** + * The preferences window is divided into sections. Each section has a label, an icon, and one or + * more fields associated with it. Each section should also be given a unique ID. + */ + 'sections': [ + { + 'id': 'about', + 'label': 'About You', + /** + * See the list of available icons below. + */ + 'icon': 'single-01', + 'form': { + 'groups': [ + { + /** + * Group heading is optional. + */ + 'label': 'About You', + 'fields': [ + { + 'label': 'First Name', + 'key': 'first_name', + 'type': 'text', + /** + * Optional text to be displayed beneath the field. + */ + 'help': 'What is your first name?' + }, + { + 'label': 'Last Name', + 'key': 'last_name', + 'type': 'text', + 'help': 'What is your last name?' + }, + { + 'label': 'Gender', + 'key': 'gender', + 'type': 'dropdown', + 'options': [ + {'label': 'Male', 'value': 'male'}, + {'label': 'Female', 'value': 'female'}, + {'label': 'Unspecified', 'value': 'unspecified'}, + ], + 'help': 'What is your gender?' + }, + { + 'label': 'Which of the following foods do you like?', + 'key': 'foods', + 'type': 'checkbox', + 'options': [ + { 'label': 'Ice Cream', 'value': 'ice_cream' }, + { 'label': 'Carrots', 'value': 'carrots' }, + { 'label': 'Cake', 'value': 'cake' }, + { 'label': 'Spinach', 'value': 'spinach' } + ], + 'help': 'Select one or more foods that you like.' + }, + { + 'label': 'Coolness', + 'key': 'coolness', + 'type': 'slider', + 'min': 0, + 'max': 9001 + }, + { + 'label': 'Eye Color', + 'key': 'eye_color', + 'type': 'color', + 'format': 'hex', // can be hex, hsl or rgb + 'help': 'Your eye color' + }, + { + 'label': 'Hair Color', + 'key': 'hair_color', + 'type': 'color', + 'format': 'rgb', + 'help': 'Your hair color' + } + ] + } + ] + } + }, + { + 'id': 'notes', + 'label': 'Notes', + 'icon': 'folder-15', + 'form': { + 'groups': [ + { + 'label': 'Stuff', + 'fields': [ + { + 'label': 'Read notes from folder', + 'key': 'folder', + 'type': 'directory', + 'help': 'The location where your notes will be stored.' + }, + { + 'heading': 'Important Message', + 'content': '

The quick brown fox jumps over the long white fence. The quick brown fox jumps over the long white fence. The quick brown fox jumps over the long white fence. The quick brown fox jumps over the long white fence.

', + 'type': 'message', + } + ] + } + ] + } + }, + { + 'id': 'space', + 'label': 'Other Settings', + 'icon': 'spaceship', + 'form': { + 'groups': [ + { + 'label': 'Other Settings', + 'fields': [ + { + 'label': 'Phone Number', + 'key': 'phone_number', + 'type': 'text', + 'help': 'What is your phone number?' + }, + { + 'label': "Foo or Bar?", + 'key': 'foobar', + 'type': 'radio', + 'options': [ + {'label': 'Foo', 'value': 'foo'}, + {'label': 'Bar', 'value': 'bar'}, + {'label': 'FooBar', 'value': 'foobar'}, + ], + 'help': 'Foo? Bar?' + } + ] + } + ] + } + } + ], + /** + * These parameters on the preference window settings can be overwrinten + */ + browserWindowOpts: { + 'title': 'My custom preferences title', + 'width': 900, + 'maxWidth': 1000, + 'height': 700, + 'maxHeight': 1000, + 'resizable': true, + 'maximizable': false, + //... + }, + /** + * These parameters create an optional menu bar + */ + menu: Menu.buildFromTemplate( + [ + { + label: 'Window', + role: 'window', + submenu: [ + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close' + } + ] + } + ] + ) }); ```` @@ -258,7 +260,7 @@ preferences.value('some.nested.key', 'my-value'); // Subscribing to preference changes. preferences.on('save', (preferences) => { - console.log(`Preferences were saved.`, JSON.stringify(preferences, null, 4)); + console.log(`Preferences were saved.`, JSON.stringify(preferences, null, 4)); }); ``` @@ -275,7 +277,7 @@ ipcRenderer.send('showPreferences'); // Listen to the `preferencesUpdated` event to be notified when preferences are changed. ipcRenderer.on('preferencesUpdated', (e, preferences) => { - console.log('Preferences were updated', preferences); + console.log('Preferences were updated', preferences); }); // Instruct the preferences service to update the preferences object from within the renderer. @@ -311,255 +313,255 @@ The following icons come packaged with the library and can be specified when you - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameIcon
NameIcon
archive-2
archive-paper
award-48
badge-13
bag-09
barcode-qr
bear-2
bell-53
bookmark-2
brightness-6
briefcase-24
calendar-60
camera-20
cart-simple
chat-46
check-circle-07
cloud-26
compass-05
dashboard-level
diamond
edit-78
email-84
eye-19
favourite-31
flag-points-32
flash-21
folder-15
gift-2
grid-45
handout
heart-2
home-52
image
key-25
layers-3
like-2
link-72
lock-open
lock
multiple-11
notes
pencil
phone-2
preferences
send-2
settings-gear-63
single-01
single-folded-content
skull-2
spaceship
square-download
square-upload
support-16
trash-simple
turtle
vector
video-66
wallet-43
widget
world
zoom-2
archive-2
archive-paper
award-48
badge-13
bag-09
barcode-qr
bear-2
bell-53
bookmark-2
brightness-6
briefcase-24
calendar-60
camera-20
cart-simple
chat-46
check-circle-07
cloud-26
compass-05
dashboard-level
diamond
edit-78
email-84
eye-19
favourite-31
flag-points-32
flash-21
folder-15
gift-2
grid-45
handout
heart-2
home-52
image
key-25
layers-3
like-2
link-72
lock-open
lock
multiple-11
notes
pencil
phone-2
preferences
send-2
settings-gear-63
single-01
single-folded-content
skull-2
spaceship
square-download
square-upload
support-16
trash-simple
turtle
vector
video-66
wallet-43
widget
world
zoom-2
diff --git a/index.js b/index.js index af4544a..fb13e62 100644 --- a/index.js +++ b/index.js @@ -1,232 +1,296 @@ -'use strict'; - -const electron = require('electron'); -const { BrowserWindow, ipcMain, webContents, dialog } = electron; -const path = require('path'); -const url = require('url'); -const fs = require('fs'); -const _ = require('lodash'); -const { EventEmitter2 } = require('eventemitter2'); -const loadJsonFile = require('load-json-file'); -const writeJsonFile = require('write-json-file'); +'use strict' + +const electron = require( 'electron' ) +const { BrowserWindow, ipcMain, webContents, dialog } = electron +const path = require( 'path' ) +const url = require( 'url' ) +const fs = require( 'fs' ) +const _ = require( 'lodash' ) +const { EventEmitter2 } = require( 'eventemitter2' ) +const loadJsonFile = require( 'load-json-file' ) +const writeJsonFile = require( 'write-json-file' ) class ElectronPreferences extends EventEmitter2 { - constructor(options = {}) { - - super(); - - _.defaultsDeep(options, { - 'sections': [], - 'webPreferences': { - 'devTools': false - } - }); - - options.sections.forEach((section, sectionIdx) => { - _.defaultsDeep(section, { - 'form': { - 'groups': [] - } - }); - section.form.groups = section.form.groups.map((group, groupIdx) => { - group.id = 'group' + sectionIdx + groupIdx; - return group; - }); - }); - - this.options = options; - - if (!this.dataStore) { - throw new Error(`The 'dataStore' option is required.`); - } - - // Load preferences file if exists - try { - if (fs.existsSync(this.dataStore)) { - this.preferences = loadJsonFile.sync(this.dataStore) + constructor( options = {} ) { + + super() + + _.defaultsDeep( options, { + sections: [], + webPreferences: { + devTools: false, + }, + } ) + + options.sections.forEach( ( section, sectionIdx ) => { + + _.defaultsDeep( section, { + form: { + groups: [], + }, + } ) + section.form.groups = section.form.groups.map( ( group, groupIdx ) => { + + group.id = 'group' + sectionIdx + groupIdx + + return group + + } ) + + } ) + + this.options = options + + if ( !this.dataStore ) { + + throw new Error( 'The \'dataStore\' option is required.' ) + + } + + // Load preferences file if exists + try { + + if ( fs.existsSync( this.dataStore ) ) { + + this.preferences = loadJsonFile.sync( this.dataStore ) + } - } catch(err) { - console.error(err) + + } catch ( err ) { + + console.error( err ) this.preferences = null - } - if (!this.preferences) { - this.preferences = this.defaults; - } else { + } + + if ( !this.preferences ) { + + this.preferences = this.defaults + + } else { + // Set default preference values - _.keys(this.defaults).forEach(prefDefault => { - if (!(prefDefault in this.preferences)) { - this.preferences[prefDefault] = this.defaults[prefDefault] - } - }) - } + _.keys( this.defaults ).forEach( prefDefault => { + + if ( !( prefDefault in this.preferences ) ) { + + this.preferences[prefDefault] = this.defaults[prefDefault] + + } + + } ) + + } + + if ( _.isFunction( options.onLoad ) ) { + + this.preferences = options.onLoad( this.preferences ) + + } + + this.save() + + ipcMain.on( 'showPreferences', event => { + + this.show() + + } ) + + ipcMain.on( 'getSections', event => { + + event.returnValue = this.options.sections + + } ) + + ipcMain.on( 'restoreDefaults', event => { + + this.preferences = this.defaults + this.save() + this.broadcast() + + } ) + + ipcMain.on( 'getDefaults', event => { + + event.returnValue = this.defaults + + } ) + + ipcMain.on( 'getPreferences', event => { + + event.returnValue = this.preferences + + } ) + + ipcMain.on( 'setPreferences', ( event, value ) => { + + this.preferences = value + this.save() + this.broadcast() + this.emit( 'save', Object.freeze( _.cloneDeep( this.preferences ) ) ) + event.returnValue = null + + } ) + + ipcMain.on( 'showOpenDialog', ( event, dialogOptions ) => { + + event.returnValue = dialog.showOpenDialogSync( dialogOptions ) + + } ) + + if ( _.isFunction( options.afterLoad ) ) { + + options.afterLoad( this ) + + } + + } + + get dataStore() { + + return this.options.dataStore + + } + + get defaults() { + + return this.options.defaults || {} + + } + + get preferences() { + + return this._preferences + + } + + set preferences( value ) { + + this._preferences = value + + } + + save() { + + writeJsonFile( this.dataStore, this.preferences, { + indent: 4, + } ) - if (_.isFunction(options.onLoad)) { - this.preferences = options.onLoad(this.preferences); - } + } - this.save(); + value( key, value ) { - ipcMain.on('showPreferences', (event) => { - this.show(); - }); + if ( _.isArray( key ) ) { - ipcMain.on('getSections', (event) => { - event.returnValue = this.options.sections; - }); + key.forEach( ( { key, value } ) => { - ipcMain.on('restoreDefaults', (event) => { - this.preferences = this.defaults; - this.save(); - this.broadcast(); - }); + _.set( this.preferences, key, value ) - ipcMain.on('getDefaults', (event) => { - event.returnValue = this.defaults; - }); + } ) + this.save() + this.broadcast() - ipcMain.on('getPreferences', (event) => { - event.returnValue = this.preferences; - }); + } else if ( !_.isUndefined( key ) && !_.isUndefined( value ) ) { - ipcMain.on('setPreferences', (event, value) => { - this.preferences = value; - this.save(); - this.broadcast(); - this.emit('save', Object.freeze(_.cloneDeep(this.preferences))); - event.returnValue = null; - }); + _.set( this.preferences, key, value ) + this.save() + this.broadcast() - ipcMain.on('showOpenDialog', (event, dialogOptions) => { - event.returnValue = dialog.showOpenDialogSync(dialogOptions); - }); + } else if ( !_.isUndefined( key ) ) { - if (_.isFunction(options.afterLoad)) { - options.afterLoad(this); - } + return _.cloneDeep( _.get( this.preferences, key ) ) - } + } else { - get dataStore() { + return _.cloneDeep( this.preferences ) - return this.options.dataStore; + } - } + } - get defaults() { + broadcast() { - return this.options.defaults || {}; + webContents.getAllWebContents() + .forEach( wc => { - } + wc.send( 'preferencesUpdated', this.preferences ) - get preferences() { + } ) - return this._preferences; + } - } + show() { - set preferences(value) { + if ( this.prefsWindow ) { - this._preferences = value; + return - } + } - save() { + let browserWindowOpts = { + title: 'Preferences', + width: 800, + maxWidth: 800, + height: 600, + maxHeight: 600, + resizable: false, + acceptFirstMouse: true, + maximizable: false, + backgroundColor: '#E7E7E7', + show: true, + webPreferences: this.options.webPreferences, + } - writeJsonFile(this.dataStore, this.preferences, { - indent: 4 - }); + const defaultWebPreferences = { + nodeIntegration: false, + enableRemoteModule: false, + contextIsolation: true, + preload: path.join( __dirname, './preload.js' ), + } - } + if ( this.options.browserWindowOverrides ) { - value(key, value) { + browserWindowOpts = Object.assign( browserWindowOpts, this.options.browserWindowOverrides ) - if (_.isArray(key)) { - key.forEach(({ key, value }) => { - _.set(this.preferences, key, value); - }); - this.save(); - this.broadcast(); - } else if (!_.isUndefined(key) && !_.isUndefined(value)) { - _.set(this.preferences, key, value); - this.save(); - this.broadcast(); - } else if (!_.isUndefined(key)) { - return _.cloneDeep(_.get(this.preferences, key)); - } else { - return _.cloneDeep(this.preferences); - } + } - } + if ( browserWindowOpts.webPreferences ) { - broadcast() { + browserWindowOpts.webPreferences = Object.assign( defaultWebPreferences, browserWindowOpts.webPreferences ) - webContents.getAllWebContents() - .forEach((wc) => { - wc.send('preferencesUpdated', this.preferences); - }); + } else { - } + browserWindowOpts.webPreferences = defaultWebPreferences - show() { + } - if (this.prefsWindow) { - return; - } + this.prefsWindow = new BrowserWindow( browserWindowOpts ) - let browserWindowOpts = { - title: 'Preferences', - width: 800, - maxWidth: 800, - height: 600, - maxHeight: 600, - resizable: false, - acceptFirstMouse: true, - maximizable: false, - backgroundColor: '#E7E7E7', - show: true, - webPreferences: this.options.webPreferences - }; + if ( this.options.menuBar ) { - const defaultWebPreferences = { - nodeIntegration: false, - enableRemoteModule: false, - contextIsolation: true, - preload: path.join(__dirname, './preload.js') - } + this.prefsWindow.setMenu( this.options.menuBar ) - if (this.options.browserWindowOverrides) { - browserWindowOpts = Object.assign(browserWindowOpts, this.options.browserWindowOverrides); - } + } else { - if (browserWindowOpts.webPreferences) { - browserWindowOpts.webPreferences = Object.assign(defaultWebPreferences, browserWindowOpts.webPreferences) - } else { - browserWindowOpts.webPreferences = defaultWebPreferences; - } + this.prefsWindow.removeMenu() - this.prefsWindow = new BrowserWindow(browserWindowOpts); + } - if (this.options.menuBar) { - this.prefsWindow.setMenu(this.options.menuBar); - } else { - this.prefsWindow.removeMenu(); - } + this.prefsWindow.loadURL( url.format( { + pathname: path.join( __dirname, 'build/index.html' ), + protocol: 'file:', + slashes: true, + } ) ) - this.prefsWindow.loadURL(url.format({ - 'pathname': path.join(__dirname, 'build/index.html'), - 'protocol': 'file:', - 'slashes': true - })); + this.prefsWindow.on( 'closed', () => { - this.prefsWindow.on('closed', () => { - this.prefsWindow = null; - }); + this.prefsWindow = null + } ) - } + } } -module.exports = ElectronPreferences; +module.exports = ElectronPreferences diff --git a/package.json b/package.json index c25f899..245eb12 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "preferences" ], "scripts": { + "lint": "eslint . --fix --ext .js,.jsx,.ts,.tsx", "test": "echo \"Error: argh, you called my bluff. We don't have any tests yet :(\" && exit 1", "example": "cd example && electron main.js", "prepare": "npm run build", @@ -42,11 +43,56 @@ "webpack-cli": "^4.6.0" }, "dependencies": { + "eslint": "^7.30.0", + "eslint-plugin-react": "^7.24.0", "eventemitter2": "^5.0.1", "load-json-file": "^6.2.0", "lodash": "^4.17.21", "react-color": "^2.14.1", "react-modal": "^3.13.1", - "write-json-file": "^4.3.0" + "write-json-file": "^4.3.0", + "xo": "^0.41.0" + }, + "xo": { + "envs": [ + "node", + "browser" + ], + "rules": { + "array-bracket-spacing": [ + "error", + "always" + ], + "padded-blocks": [ + "error", + { + "blocks": "always", + "switches": "always", + "classes": "always" + } + ], + "padding-line-between-statements": [ + "error", + { + "blankLine": "always", + "prev": "multiline-block-like", + "next": "*" + }, + { + "blankLine": "always", + "prev": "*", + "next": "return" + } + ], + "object-curly-spacing": [ + "error", + "always" + ], + "space-in-parens": [ + "error", + "always" + ] + }, + "semicolon": false } } diff --git a/src/app/components/main/index.js b/src/app/components/main/index.js index 9a11f6b..8b4ea8d 100644 --- a/src/app/components/main/index.js +++ b/src/app/components/main/index.js @@ -1,65 +1,71 @@ -'use strict'; +'use strict' -import React from 'react'; -import Group from './components/group'; -import _ from 'lodash'; +import React from 'react' +import PropTypes from 'prop-types' +import Group from './components/group' +import _ from 'lodash' class Main extends React.Component { - render() { + render() { - const groups = this.form.groups.map((group, idx) => { - return ( - - ); - }); + const groups = this.form.groups.map( ( group, idx ) => ( + + ) ) - return ( -
- { groups } -
- ); + return ( +
+ { groups } +
+ ) - } + } - get sections() { + get sections() { - return this.props.sections; + return this.props.sections - } + } - get form() { + get form() { - return this.section.form; + return this.section.form - } + } - get preferences() { + get preferences() { - return this.props.preferences; + return this.props.preferences - } + } - get activeSection() { + get activeSection() { - return this.props.activeSection; + return this.props.activeSection - } + } - get section() { + get section() { - return _.find(this.sections, { - 'id': this.activeSection - }); + return _.find( this.sections, { + id: this.activeSection, + } ) - } + } - get onFieldChange() { + get onFieldChange() { - return this.props.onFieldChange; + return this.props.onFieldChange - } + } } -module.exports = Main; +Main.propTypes = { + sections: PropTypes.string, + preferences: PropTypes.string, + activeSection: PropTypes.string, + onFieldChange: PropTypes.func, +} + +module.exports = Main diff --git a/src/app/components/sidebar/index.js b/src/app/components/sidebar/index.js index 3212e35..5a7bcf1 100644 --- a/src/app/components/sidebar/index.js +++ b/src/app/components/sidebar/index.js @@ -1,65 +1,77 @@ -'use strict'; +'use strict' -import React from 'react'; +import React from 'react' +import PropTypes from 'prop-types' class Sidebar extends React.Component { - render() { + render() { - const sections = this.sections.map((section) => { - let className = 'sidebar-section'; - if (this.activeSection === section.id) { - className += ' active'; - } - - const style = { - mask: `url("svg/${section.icon}.svg") no-repeat center / contain`, - webkitMask: `url("svg/${section.icon}.svg") no-repeat center / contain` - } - return ( -
-
- { section.label } -
- ); - }); + const sections = this.sections.map( section => { - return ( -
- { sections } -
- ); + let className = 'sidebar-section' + if ( this.activeSection === section.id ) { - } + className += ' active' - get sections() { + } - return this.props.sections; + const style = { + mask: `url("svg/${section.icon}.svg") no-repeat center / contain`, + webkitMask: `url("svg/${section.icon}.svg") no-repeat center / contain`, + } - } + return ( +
+
+ { section.label } +
+ ) - get activeSection() { + } ) - return this.props.activeSection; + return ( +
+ { sections } +
+ ) - } + } - get onSelectSection() { + get sections() { - return this.props.onSelectSection; + return this.props.sections - } + } - selectSection(sectionId) { + get activeSection() { - this.setState({ - 'activeSection': sectionId - }); + return this.props.activeSection - this.onSelectSection(sectionId); + } - } + get onSelectSection() { + return this.props.onSelectSection + + } + + selectSection( sectionId ) { + + this.setState( { + activeSection: sectionId, + } ) + + this.onSelectSection( sectionId ) + + } + +} + +Sidebar.propTypes = { + sections: PropTypes.string, + activeSection: PropTypes.string, + onSelectSection: PropTypes.funct, } -module.exports = Sidebar; +module.exports = Sidebar diff --git a/webpack.config.js b/webpack.config.js index 6ec641b..7b53ab6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,64 +1,63 @@ -'use strict'; +'use strict' -const webpack = require('webpack'); -const path = require('path'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); +const path = require( 'path' ) +const CopyWebpackPlugin = require( 'copy-webpack-plugin' ) module.exports = { - 'mode': 'production', - 'entry': './src/app/index.js', - 'watchOptions': { - 'ignored': /node_modules/ - }, - 'output': { - 'path': path.resolve(__dirname, 'build'), - 'filename': 'app.js' - }, - 'resolve': { - 'extensions': [ - '.js', '.scss' - ], - 'modules': [ - path.resolve(__dirname, 'src'), - path.resolve(__dirname, 'scss'), - path.resolve(__dirname, 'node_modules') - ], - 'alias': { - } - }, - 'plugins': [ - new CopyWebpackPlugin({ - patterns: [ - { from: 'assets' } - ] - }) - ], - 'module': { - 'rules': [ - { - 'test': /\.js$/, - 'exclude': /node_modules/, - 'use': [ - { - 'loader': 'cache-loader' - }, - { - 'loader': 'babel-loader', - 'options': { - 'presets': ['env', 'react'], - 'plugins': ['transform-object-rest-spread', 'transform-class-properties'] - } - } - ] - }, - { - 'test': /\.scss$/, - 'use': [ - "style-loader", - "css-loader", - "sass-loader" - ] - } - ] - } -}; + mode: 'production', + entry: './src/app/index.js', + watchOptions: { + ignored: /node_modules/, + }, + output: { + path: path.resolve( __dirname, 'build' ), + filename: 'app.js', + }, + resolve: { + extensions: [ + '.js', '.scss', + ], + modules: [ + path.resolve( __dirname, 'src' ), + path.resolve( __dirname, 'scss' ), + path.resolve( __dirname, 'node_modules' ), + ], + alias: { + }, + }, + plugins: [ + new CopyWebpackPlugin( { + patterns: [ + { from: 'assets' }, + ], + } ), + ], + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: [ + { + loader: 'cache-loader', + }, + { + loader: 'babel-loader', + options: { + presets: [ 'env', 'react' ], + plugins: [ 'transform-object-rest-spread', 'transform-class-properties' ], + }, + }, + ], + }, + { + test: /\.scss$/, + use: [ + 'style-loader', + 'css-loader', + 'sass-loader', + ], + }, + ], + }, +}