aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2017-10-09 17:35:10 +0900
committerGitHub <noreply@github.com>2017-10-09 17:35:10 +0900
commit447466808f484d4baa6b285f2dbcaf1920db5498 (patch)
treeaba110eb78b4ce3eb6cefb8100f167e17a23fcc3 /src
parent892eb8a6a6d9080213f461f19a8b8435a6482237 (diff)
parent805d1395fc869235f079438b5b4884a521c0230e (diff)
Merge pull request #27 from ueokande/react-settings
Use React in settings
Diffstat (limited to 'src')
-rw-r--r--src/background/actions/settings.js0
-rw-r--r--src/background/components/background.js12
-rw-r--r--src/content/actions/index.js5
-rw-r--r--src/content/actions/input.js9
-rw-r--r--src/content/components/keymapper.js16
-rw-r--r--src/content/index.js4
-rw-r--r--src/content/reducers/index.js3
-rw-r--r--src/content/reducers/input.js5
-rw-r--r--src/settings/actions/setting.js16
-rw-r--r--src/settings/components/index.jsx91
-rw-r--r--src/settings/components/setting.js45
-rw-r--r--src/settings/components/site.scss (renamed from src/settings/site.scss)2
-rw-r--r--src/settings/index.html10
-rw-r--r--src/settings/index.js15
-rw-r--r--src/settings/index.jsx18
-rw-r--r--src/settings/reducers/setting.js12
-rw-r--r--src/shared/default-settings.js1
-rw-r--r--src/shared/store/provider.jsx18
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;