diff options
author | Shin'ya Ueoka <ueokande@i-beam.org> | 2017-10-09 17:35:10 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-09 17:35:10 +0900 |
commit | 447466808f484d4baa6b285f2dbcaf1920db5498 (patch) | |
tree | aba110eb78b4ce3eb6cefb8100f167e17a23fcc3 /src | |
parent | 892eb8a6a6d9080213f461f19a8b8435a6482237 (diff) | |
parent | 805d1395fc869235f079438b5b4884a521c0230e (diff) |
Merge pull request #27 from ueokande/react-settings
Use React in settings
Diffstat (limited to 'src')
-rw-r--r-- | src/background/actions/settings.js | 0 | ||||
-rw-r--r-- | src/background/components/background.js | 12 | ||||
-rw-r--r-- | src/content/actions/index.js | 5 | ||||
-rw-r--r-- | src/content/actions/input.js | 9 | ||||
-rw-r--r-- | src/content/components/keymapper.js | 16 | ||||
-rw-r--r-- | src/content/index.js | 4 | ||||
-rw-r--r-- | src/content/reducers/index.js | 3 | ||||
-rw-r--r-- | src/content/reducers/input.js | 5 | ||||
-rw-r--r-- | src/settings/actions/setting.js | 16 | ||||
-rw-r--r-- | src/settings/components/index.jsx | 91 | ||||
-rw-r--r-- | src/settings/components/setting.js | 45 | ||||
-rw-r--r-- | src/settings/components/site.scss (renamed from src/settings/site.scss) | 2 | ||||
-rw-r--r-- | src/settings/index.html | 10 | ||||
-rw-r--r-- | src/settings/index.js | 15 | ||||
-rw-r--r-- | src/settings/index.jsx | 18 | ||||
-rw-r--r-- | src/settings/reducers/setting.js | 12 | ||||
-rw-r--r-- | src/shared/default-settings.js | 1 | ||||
-rw-r--r-- | src/shared/store/provider.jsx | 18 |
18 files changed, 168 insertions, 114 deletions
diff --git a/src/background/actions/settings.js b/src/background/actions/settings.js new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/background/actions/settings.js diff --git a/src/background/components/background.js b/src/background/components/background.js index 0570a5a..06b6900 100644 --- a/src/background/components/background.js +++ b/src/background/components/background.js @@ -22,15 +22,7 @@ export default class BackgroundComponent { } update() { - let state = this.store.getState(); - this.updateSettings(state); - } - - updateSettings(setting) { - if (!setting.settings.json) { - return; - } - this.settings = JSON.parse(setting.settings.json); + this.settings = this.store.getState(); } onMessage(message, sender) { @@ -58,7 +50,7 @@ export default class BackgroundComponent { }); }); case messages.SETTINGS_QUERY: - return Promise.resolve(this.store.getState().settings); + return Promise.resolve(this.store.getState().value); case messages.CONSOLE_QUERY_COMPLETIONS: return commands.complete(message.text, this.settings); case messages.SETTINGS_RELOAD: diff --git a/src/content/actions/index.js b/src/content/actions/index.js index 0b3749d..f8db948 100644 --- a/src/content/actions/index.js +++ b/src/content/actions/index.js @@ -2,16 +2,13 @@ export default { // User input INPUT_KEY_PRESS: 'input.key,press', INPUT_CLEAR_KEYS: 'input.clear.keys', - INPUT_SET_KEYMAPS: 'input.set,keymaps', + INPUT_SET_KEYMAPS: 'input.set.keymaps', // Completion COMPLETION_SET_ITEMS: 'completion.set.items', COMPLETION_SELECT_NEXT: 'completions.select.next', COMPLETION_SELECT_PREV: 'completions.select.prev', - // Settings - SETTING_SET_SETTINGS: 'setting.set.settings', - // Follow FOLLOW_ENABLE: 'follow.enable', FOLLOW_DISABLE: 'follow.disable', diff --git a/src/content/actions/input.js b/src/content/actions/input.js index cc4efac..10ff835 100644 --- a/src/content/actions/input.js +++ b/src/content/actions/input.js @@ -20,4 +20,11 @@ const clearKeys = () => { }; }; -export { keyPress, clearKeys }; +const setKeymaps = (keymaps) => { + return { + type: actions.INPUT_SET_KEYMAPS, + keymaps, + }; +}; + +export { keyPress, clearKeys, setKeymaps }; diff --git a/src/content/components/keymapper.js b/src/content/components/keymapper.js index 8f2cead..655c3f2 100644 --- a/src/content/components/keymapper.js +++ b/src/content/components/keymapper.js @@ -10,14 +10,10 @@ export default class KeymapperComponent { } key(key, ctrl) { - let keymaps = this.keymaps(); - if (!keymaps) { - return; - } this.store.dispatch(inputActions.keyPress(key, ctrl)); let input = this.store.getState().input; - let matched = Object.keys(keymaps).filter((keyStr) => { + let matched = Object.keys(input.keymaps).filter((keyStr) => { return keyStr.startsWith(input.keys); }); if (matched.length === 0) { @@ -27,17 +23,9 @@ export default class KeymapperComponent { matched.length === 1 && input.keys !== matched[0]) { return true; } - let operation = keymaps[matched]; + let operation = input.keymaps[matched]; this.store.dispatch(operationActions.exec(operation)); this.store.dispatch(inputActions.clearKeys()); return true; } - - keymaps() { - let settings = this.store.getState().setting.settings; - if (!settings || !settings.json) { - return null; - } - return JSON.parse(settings.json).keymaps; - } } diff --git a/src/content/index.js b/src/content/index.js index 63bbf77..64d86bb 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -1,6 +1,6 @@ import './console-frame.scss'; import * as consoleFrames from './console-frames'; -import * as settingActions from 'settings/actions/setting'; +import * as inputActions from './actions/input'; import { createStore } from 'shared/store'; import ContentInputComponent from 'content/components/content-input'; import KeymapperComponent from 'content/components/keymapper'; @@ -34,7 +34,7 @@ const reloadSettings = () => { return browser.runtime.sendMessage({ type: messages.SETTINGS_QUERY, }).then((settings) => { - store.dispatch(settingActions.set(settings)); + store.dispatch(inputActions.setKeymaps(settings.keymaps)); }); }; diff --git a/src/content/reducers/index.js b/src/content/reducers/index.js index a62217f..c026a19 100644 --- a/src/content/reducers/index.js +++ b/src/content/reducers/index.js @@ -1,18 +1,15 @@ -import settingReducer from 'settings/reducers/setting'; import inputReducer from './input'; import followReducer from './follow'; // Make setting reducer instead of re-use const defaultState = { input: inputReducer(undefined, {}), - setting: settingReducer(undefined, {}), follow: followReducer(undefined, {}), }; export default function reducer(state = defaultState, action = {}) { return Object.assign({}, state, { input: inputReducer(state.input, action), - setting: settingReducer(state.setting, action), follow: followReducer(state.follow, action), }); } diff --git a/src/content/reducers/input.js b/src/content/reducers/input.js index 802020f..c79b206 100644 --- a/src/content/reducers/input.js +++ b/src/content/reducers/input.js @@ -2,6 +2,7 @@ import actions from 'content/actions'; const defaultState = { keys: '', + keymaps: {}, }; export default function reducer(state = defaultState, action = {}) { @@ -14,6 +15,10 @@ export default function reducer(state = defaultState, action = {}) { return Object.assign({}, state, { keys: '', }); + case actions.INPUT_SET_KEYMAPS: + return Object.assign({}, state, { + keymaps: action.keymaps, + }); default: return state; } diff --git a/src/settings/actions/setting.js b/src/settings/actions/setting.js index 697bcf0..c1b27c8 100644 --- a/src/settings/actions/setting.js +++ b/src/settings/actions/setting.js @@ -3,9 +3,9 @@ import messages from 'shared/messages'; import DefaultSettings from 'shared/default-settings'; const load = () => { - return browser.storage.local.get('settings').then((value) => { - if (value.settings) { - return set(value.settings); + return browser.storage.local.get('settings').then(({ settings }) => { + if (settings) { + return set(settings); } return set(DefaultSettings); }, console.error); @@ -13,10 +13,12 @@ const load = () => { const save = (settings) => { return browser.storage.local.set({ - settings + settings, }).then(() => { return browser.runtime.sendMessage({ type: messages.SETTINGS_RELOAD + }).then(() => { + return set(settings); }); }); }; @@ -24,8 +26,10 @@ const save = (settings) => { const set = (settings) => { return { type: actions.SETTING_SET_SETTINGS, - settings, + source: settings.source, + json: settings.json, + value: JSON.parse(settings.json), }; }; -export { load, save, set }; +export { load, save }; diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx new file mode 100644 index 0000000..4418942 --- /dev/null +++ b/src/settings/components/index.jsx @@ -0,0 +1,91 @@ +import './site.scss'; +import React from 'react'; +import PropTypes from 'prop-types'; +import * as settingActions from 'settings/actions/setting'; +import * as validator from 'shared/validators/setting'; + +class SettingsComponent extends React.Component { + constructor(props, context) { + super(props, context); + + this.state = { + settings: { + json: '', + } + }; + this.context.store.subscribe(this.stateChanged.bind(this)); + } + + componentDidMount() { + this.context.store.dispatch(settingActions.load()); + } + + stateChanged() { + let settings = this.context.store.getState(); + this.setState({ + settings: { + source: settings.source, + json: settings.json, + } + }); + } + + render() { + return ( + <div> + <h1>Configure Vim-Vixen</h1> + <form className='vimvixen-settings-form'> + + <p>Load settings from:</p> + <input type='radio' id='setting-source-json' + name='source' + value='json' + onChange={this.bindAndSave.bind(this)} + checked={this.state.settings.source === 'json'} /> + <label htmlFor='settings-source-json'>JSON</label> + + <textarea name='json' spellCheck='false' + onInput={this.validate.bind(this)} + onChange={this.bindValue.bind(this)} + onBlur={this.bindAndSave.bind(this)} + value={this.state.settings.json} /> + </form> + </div> + ); + } + + validate(e) { + try { + let settings = JSON.parse(e.target.value); + validator.validate(settings); + e.target.setCustomValidity(''); + } catch (err) { + e.target.setCustomValidity(err.message); + } + } + + bindValue(e) { + let nextSettings = Object.assign({}, this.state.settings); + nextSettings[e.target.name] = e.target.value; + + this.setState({ settings: nextSettings }); + } + + bindAndSave(e) { + this.bindValue(e); + + try { + let json = this.state.settings.json; + validator.validate(JSON.parse(json)); + this.context.store.dispatch(settingActions.save(this.state.settings)); + } catch (err) { + // error already shown + } + } +} + +SettingsComponent.contextTypes = { + store: PropTypes.any, +}; + +export default SettingsComponent; diff --git a/src/settings/components/setting.js b/src/settings/components/setting.js deleted file mode 100644 index 14482a3..0000000 --- a/src/settings/components/setting.js +++ /dev/null @@ -1,45 +0,0 @@ -import * as settingActions from 'settings/actions/setting'; -import { validate } from 'shared/validators/setting'; - -export default class SettingComponent { - constructor(wrapper, store) { - this.wrapper = wrapper; - this.store = store; - - let doc = wrapper.ownerDocument; - let form = doc.getElementById('vimvixen-settings-form'); - form.addEventListener('submit', this.onSubmit.bind(this)); - - let plainJson = form.elements['plain-json']; - plainJson.addEventListener('input', this.onPlainJsonChanged.bind(this)); - - store.dispatch(settingActions.load()); - } - - onSubmit(e) { - let settings = { - json: e.target.elements['plain-json'].value, - }; - this.store.dispatch(settingActions.save(settings)); - e.preventDefault(); - } - - onPlainJsonChanged(e) { - try { - let settings = JSON.parse(e.target.value); - validate(settings); - e.target.setCustomValidity(''); - } catch (err) { - e.target.setCustomValidity(err.message); - } - } - - update() { - let { settings } = this.store.getState(); - - let doc = this.wrapper.ownerDocument; - let form = doc.getElementById('vimvixen-settings-form'); - let plainJsonInput = form.elements['plain-json']; - plainJsonInput.value = settings.json; - } -} diff --git a/src/settings/site.scss b/src/settings/components/site.scss index 5707c8a..fae9c39 100644 --- a/src/settings/site.scss +++ b/src/settings/components/site.scss @@ -1,5 +1,5 @@ .vimvixen-settings-form { - textarea[name=plain-json] { + textarea[name=json] { font-family: monospace; width: 100%; min-height: 64ex; diff --git a/src/settings/index.html b/src/settings/index.html index 99d6c6b..6fe00df 100644 --- a/src/settings/index.html +++ b/src/settings/index.html @@ -4,15 +4,7 @@ <meta charset='utf-8'> </head> <body> - <h1>Configure</h1> - - <h2>Home page</h2> - <form id='vimvixen-settings-form' class='vimvixen-settings-form'> - <label for='load-from-json'>Load from JSON:</label> - <textarea name='plain-json' spellcheck='false'></textarea> - - <button type='submit'>Save</button> - </form> + <div id='vimvixen-settings'></div> <script src='settings.js'></script> </body> </html> diff --git a/src/settings/index.js b/src/settings/index.js deleted file mode 100644 index c8d6cc4..0000000 --- a/src/settings/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import './site.scss'; -import SettingComponent from 'settings/components/setting'; -import settingReducer from 'settings/reducers/setting'; -import { createStore } from 'shared/store'; - -const store = createStore(settingReducer); -let settingComponent = null; - -store.subscribe(() => { - settingComponent.update(); -}); - -document.addEventListener('DOMContentLoaded', () => { - settingComponent = new SettingComponent(document.body, store); -}); diff --git a/src/settings/index.jsx b/src/settings/index.jsx new file mode 100644 index 0000000..7516fb7 --- /dev/null +++ b/src/settings/index.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import SettingsComponent from './components'; +import reducer from 'settings/reducers/setting'; +import Provider from 'shared/store/provider'; +import { createStore } from 'shared/store'; + +const store = createStore(reducer); + +document.addEventListener('DOMContentLoaded', () => { + let wrapper = document.getElementById('vimvixen-settings'); + ReactDOM.render( + <Provider store={store}> + <SettingsComponent /> + </Provider>, + wrapper + ); +}); diff --git a/src/settings/reducers/setting.js b/src/settings/reducers/setting.js index f7d9242..a61c09f 100644 --- a/src/settings/reducers/setting.js +++ b/src/settings/reducers/setting.js @@ -1,15 +1,19 @@ import actions from 'settings/actions'; const defaultState = { - settings: {} + source: '', + json: '', + value: {} }; export default function reducer(state = defaultState, action = {}) { switch (action.type) { case actions.SETTING_SET_SETTINGS: - return Object.assign({}, state, { - settings: action.settings, - }); + return { + source: action.source, + json: action.json, + value: action.value, + }; default: return state; } diff --git a/src/shared/default-settings.js b/src/shared/default-settings.js index 24ac536..f287b7a 100644 --- a/src/shared/default-settings.js +++ b/src/shared/default-settings.js @@ -1,4 +1,5 @@ export default { + source: 'json', json: `{ "keymaps": { "0": { "type": "scroll.home" }, diff --git a/src/shared/store/provider.jsx b/src/shared/store/provider.jsx new file mode 100644 index 0000000..743f656 --- /dev/null +++ b/src/shared/store/provider.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +class Provider extends React.PureComponent { + getChildContext() { + return { store: this.props.store }; + } + + render() { + return React.Children.only(this.props.children); + } +} + +Provider.childContextTypes = { + store: PropTypes.any, +}; + +export default Provider; |