Skip to content

Commit

Permalink
Merge pull request #230 from BrainMaestro/add-plugin-settings
Browse files Browse the repository at this point in the history
Add plugin settings
  • Loading branch information
KELiON authored Apr 7, 2017
2 parents ee8eefa + 7d644e0 commit 19c50bb
Show file tree
Hide file tree
Showing 24 changed files with 388 additions and 137 deletions.
3 changes: 2 additions & 1 deletion app/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const defaultSettings = memoize(() => {
developerMode: false,
cleanOnHide: true,
skipDonateDialog: false,
lastShownDonateDialog: null
lastShownDonateDialog: null,
plugins: {},
}
})

Expand Down
1 change: 1 addition & 0 deletions app/lib/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ export const ensureFiles = () => {
}

export const client = npm(pluginsPath)
export { default as settings } from './settings'
3 changes: 3 additions & 0 deletions app/lib/plugins/settings/get.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import config from 'lib/config'

export default (pluginName) => config.get('plugins')[pluginName]
4 changes: 4 additions & 0 deletions app/lib/plugins/settings/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import get from './get'
import validate from './validate'

export default { get, validate }
24 changes: 24 additions & 0 deletions app/lib/plugins/settings/validate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { every } from 'lodash/fp'

const VALID_TYPES = new Set([
'array',
'string',
'number',
'bool',
'option',
])

const validSetting = ({ type, options }) => {
// General validation of settings
if (!type || !VALID_TYPES.has(type)) return false

// Type-specific validations
if (type === 'option') return Array.isArray(options) && options.length

return true
}

export default ({ settings }) => {
if (!settings) return true
return every(validSetting)(settings)
}
4 changes: 3 additions & 1 deletion app/main/actions/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import plugins from '../plugins/'
import config from 'lib/config'
import { shell, clipboard, remote } from 'electron'
import store from '../store'
import { settings as pluginSettings } from 'lib/plugins'

import {
UPDATE_TERM,
Expand Down Expand Up @@ -44,7 +45,8 @@ const eachPlugin = (term, display) => {
term,
hide: (id) => store.dispatch(hideElement(`${name}-${id}`)),
update: (id, result) => store.dispatch(updateElement(`${name}-${id}`, result)),
display: (payload) => display(name, payload)
display: (payload) => display(name, payload),
settings: pluginSettings.get(name),
})
} catch (error) {
// Do not fail on plugin errors, just log them to console
Expand Down
28 changes: 28 additions & 0 deletions app/main/components/Form/Checkbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { PropTypes } from 'react'
import styles from './styles.css'

const Checkbox = ({ label, value, onChange, description }) => (
<div className={styles.item}>
<div className={styles.itemValueWithoutLabel}>
<label>
<input
type="checkbox"
checked={value}
onChange={({ target }) => onChange(target.checked)}
className={styles.checkbox}
/>
{label}
</label>
<div className={styles.itemNotice}>{description}</div>
</div>
</div>
)

Checkbox.propTypes = {
label: PropTypes.string,
value: PropTypes.bool,
onChange: PropTypes.func.isRequired,
description: PropTypes.string,
}

export default Checkbox
41 changes: 41 additions & 0 deletions app/main/components/Form/Select.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { PropTypes } from 'react'
import ReactSelect, { Creatable } from 'react-select'
import Wrapper from './Wrapper'

const Select = ({ label, value, onChange, description, options, multi, clearable, creatable }) => {
const Component = creatable ? Creatable : ReactSelect
return (
<Wrapper label={label} description={description}>
<Component
multi={multi}
value={value}
clearable={clearable}
options={options}
onChange={newValue => {
if (!newValue) {
return newValue
}
const changedValue = multi ? newValue.map(val => val.value) : newValue.value
onChange(changedValue)
}}
/>
</Wrapper>
)
}

Select.propTypes = {
label: PropTypes.string,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.array,
]),
onChange: PropTypes.func.isRequired,
description: PropTypes.string,
options: PropTypes.array.isRequired,
multi: PropTypes.bool,
clearable: PropTypes.bool,
creatable: PropTypes.bool
}

export default Select
27 changes: 27 additions & 0 deletions app/main/components/Form/Text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { PropTypes } from 'react'
import Wrapper from './Wrapper'
import styles from './styles.css'

const Input = ({ label, value, onChange, description, type }) => (
<Wrapper label={label} description={description}>
<input
type={type}
value={value || ''}
className={styles.input}
onChange={({ target }) => onChange(target.value)}
/>
</Wrapper>
)

Input.propTypes = {
label: PropTypes.string,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
onChange: PropTypes.func.isRequired,
description: PropTypes.string,
type: PropTypes.string.isRequired,
}

export default Input
20 changes: 20 additions & 0 deletions app/main/components/Form/Wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { PropTypes } from 'react'
import styles from './styles.css'

const Wrapper = ({ label, description, children }) => (
<div className={styles.item}>
{label && <label className={styles.label}>{label}:</label>}
<div className={label ? styles.itemValue : styles.itemValueWithoutLabel}>
{children}
<div className={styles.itemNotice}>{description}</div>
</div>
</div>
)

Wrapper.propTypes = {
label: PropTypes.string,
description: PropTypes.string,
children: PropTypes.any
}

export default Wrapper
7 changes: 7 additions & 0 deletions app/main/components/Form/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Checkbox from './Checkbox'
import Text from './Text'
import Select from './Select'

export {
Checkbox, Text, Select
}
48 changes: 48 additions & 0 deletions app/main/components/Form/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.item {
display: flex;
flex-direction: row;
width: 250px;
margin-top: 20px;
&:first-child {
margin-top: 0;
}
}

.itemValue {
display: flex;
flex-direction: column;
flex-grow: 2;
}

.itemValueWithoutLabel {
composes: itemValue;
margin-left: 75px;
}

.itemNotice{
color: var(--secondary-font-color);
font-size: .8em;
margin-top: 5px;
}

.label {
margin-right: 15px;
margin-top: 8px;
min-width: 60px;
max-width: 60px;
}

.input {
font-size: 16px;
line-height: 34px;
padding: 0 10px;
box-sizing: border-box;
width: 100%;
border-color: #d9d9d9 #ccc #b3b3b3;
border-radius: 4px;
border: 1px solid #ccc;
}

.checkbox {
margin-right: 5px;
}
23 changes: 23 additions & 0 deletions app/main/plugins/core/cerebro/plugins/Preview/FormItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { PropTypes } from 'react'
import { Select, Text, Checkbox } from 'main/components/Form'

const components = {
bool: Checkbox,
option: Select,
array: Select,
}

const FormItem = ({ type, ...props }) => {
const Component = components[type] || Text

return (
<Component type={type} {...props} />
)
}

FormItem.propTypes = {
value: PropTypes.any,
type: PropTypes.string.isRequired,
}

export default FormItem
59 changes: 59 additions & 0 deletions app/main/plugins/core/cerebro/plugins/Preview/Settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { PropTypes, Component } from 'react'
import config from 'lib/config'
import FormItem from './FormItem'
import styles from './styles.css'

export default class Settings extends Component {
constructor(props) {
super(props)
this.state = {
values: config.get('plugins')[props.name],
}
this.renderSetting = this.renderSetting.bind(this)
this.changeSetting = this.changeSetting.bind(this)
}

changeSetting(plugin, label, value) {
const values = {
...this.state.values,
[label]: value,
}

this.setState({ values })
config.set('plugins', {
...config.get('plugins'),
[this.props.name]: values,
})
}

renderSetting(key) {
const setting = this.props.settings[key]
const { defaultValue, label, ...restProps } = setting
const value = key in this.state.values ? this.state.values[key] : defaultValue

return (
<FormItem
key={key}
label={label || key}
value={value}
onChange={newValue => this.changeSetting(this.props.name, key, newValue)}
{...restProps}
/>
)
}

render() {
return (
<div className={styles.settingsWrapper}>
{
Object.keys(this.props.settings).map(this.renderSetting)
}
</div>
)
}
}

Settings.propTypes = {
name: PropTypes.string.isRequired,
settings: PropTypes.object.isRequired,
}
18 changes: 16 additions & 2 deletions app/main/plugins/core/cerebro/plugins/Preview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import Preload from 'main/components/Preload'
import KeyboardNav from 'main/components/KeyboardNav'
import KeyboardNavItem from 'main/components/KeyboardNavItem'
import ActionButton from './ActionButton.js'
import Settings from './Settings'
import getReadme from '../getReadme'
import ReactMarkdown from 'react-markdown'
import styles from './styles.css'
import trackEvent from 'lib/trackEvent'
import * as format from '../format'
import { client } from 'lib/plugins'
import plugins from 'main/plugins'

const isRelative = (src) => !src.match(/^(https?:|data:)/)
const urlTransform = (repo, src) => {
Expand All @@ -24,6 +26,7 @@ class Preview extends Component {
this.onComplete = this.onComplete.bind(this)
this.state = {
showDescription: false,
showSettings: false,
}
}

Expand Down Expand Up @@ -71,13 +74,23 @@ class Preview extends Component {
isUpdateAvailable
} = this.props
const githubRepo = repo && repo.match(/^.+github.com\/([^\/]+\/[^\/]+).*?/)
const runningAction = this.state.runningAction
const { runningAction, showSettings } = this.state
const settings = plugins[name] ? plugins[name].settings : null
return (
<div className={styles.preview} key={name}>
<h2>{format.name(name)} ({version})</h2>
<p>{format.description(description)}</p>
<KeyboardNav>
<div className={styles.header}>
{
settings &&
<KeyboardNavItem
onSelect={() => this.setState({ showSettings: !this.state.showSettings })}
>
Settings
</KeyboardNavItem>
}
{showSettings && <Settings name={name} settings={settings} />}
{
!isInstalled &&
<ActionButton
Expand Down Expand Up @@ -109,7 +122,7 @@ class Preview extends Component {
{
githubRepo &&
<KeyboardNavItem
onSelect={() => this.setState({ showDescription: true })}
onSelect={() => this.setState({ showDescription: !this.state.showDescription })}
>
Details
</KeyboardNavItem>
Expand All @@ -124,6 +137,7 @@ class Preview extends Component {

Preview.propTypes = {
name: PropTypes.string.isRequired,
settings: PropTypes.object,
version: PropTypes.string.isRequired,
description: PropTypes.string,
repo: PropTypes.string,
Expand Down
Loading

0 comments on commit 19c50bb

Please sign in to comment.