Add keybindings form

jh-changes
Shin'ya Ueoka 7 years ago
parent d33b37cdb9
commit 4e5ddc1d57
  1. 7
      src/settings/actions/setting.js
  2. 139
      src/settings/components/index.jsx
  3. 27
      src/settings/components/site.scss
  4. 35
      src/settings/components/ui/input.jsx
  5. 37
      src/settings/components/ui/input.scss
  6. 2
      src/settings/reducers/setting.js
  7. 66
      src/shared/default-settings.js

@ -4,10 +4,10 @@ import DefaultSettings from 'shared/default-settings';
const load = () => { const load = () => {
return browser.storage.local.get('settings').then(({ settings }) => { return browser.storage.local.get('settings').then(({ settings }) => {
if (settings) { if (!settings) {
return set(settings); return set(DefaultSettings);
} }
return set(DefaultSettings); return set(Object.assign({}, DefaultSettings, settings));
}, console.error); }, console.error);
}; };
@ -28,6 +28,7 @@ const set = (settings) => {
type: actions.SETTING_SET_SETTINGS, type: actions.SETTING_SET_SETTINGS,
source: settings.source, source: settings.source,
json: settings.json, json: settings.json,
form: settings.form,
value: JSON.parse(settings.json), value: JSON.parse(settings.json),
}; };
}; };

@ -4,6 +4,59 @@ import Input from './ui/input';
import * as settingActions from 'settings/actions/setting'; import * as settingActions from 'settings/actions/setting';
import * as validator from 'shared/validators/setting'; import * as validator from 'shared/validators/setting';
const KeyMapFields = [
[
['scroll.vertically?{count:-1}', 'Scroll down'],
['scroll.vertically?{count:1}', 'Scroll up'],
['scroll.horizonally?{count:-1}', 'Scroll left'],
['scroll.horizonally?{count:1}', 'Scroll right'],
['scroll.home', 'Scroll leftmost'],
['scroll.end', 'Scroll last'],
['scroll.pages?{count:-0.5}', 'Scroll up by half of screen'],
['scroll.pages?{count:0.5}', 'Scroll up by half of screen'],
['scroll.pages?{count:-1}', 'Scroll up by a screen'],
['scroll.pages?{count:1}', 'Scroll up by a screen'],
], [
['tabs.close', 'Close a tab'],
['tabs.reopen', 'Reopen closed tab'],
['tabs.next?{count:1}', 'Select next Tab'],
['tabs.prev?{count:1}', 'Select prev Tab'],
['tabs.first', 'Select first tab'],
['tabs.last', 'Select last tab'],
['tabs.reload?{cache:true}', 'Reload current tab'],
['tabs.pin.toggle', 'Toggle pinned state'],
['tabs.duplicate', 'Dupplicate a tab'],
], [
['follow.start?{newTab:false}', 'Follow a link'],
['follow.start?{newTab:true}', 'Follow a link in new tab'],
['navigate.histories.prev', 'Go back in histories'],
['navigate.histories.next', 'Go forward in histories'],
['navigate.link.next', 'Open next link'],
['navigate.link.prev', 'Open previous link'],
['navigate.parent', 'Go to parent directory'],
['navigate.root', 'Go to root directory'],
], [
['find.start', 'Start find mode'],
['find.next', 'Find next word'],
['find.prev', 'Find previous word'],
], [
['command.show', 'Open console'],
['command.show.open?{alter:false}', 'Open URL'],
['command.show.open?{alter:true}', 'Alter URL'],
['command.show.tabopen?{alter:false}', 'Open URL in new Tab'],
['command.show.tabopen?{alter:true}', 'Alter URL in new Tab'],
['command.show.winopen?{alter:false}', 'Open URL in new window'],
['command.show.winopen?{alter:true}', 'Alter URL in new window'],
['command.show.buffer', 'Open buffer command'],
], [
['addon.toggle.enabled', 'Enable or disable'],
['urls.yank', 'Copy current URL'],
['zoom.in', 'Zoom-in'],
['zoom.out', 'Zoom-out'],
['zoom.neutral', 'Reset zoom level'],
]
];
class SettingsComponent extends Component { class SettingsComponent extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
@ -29,33 +82,87 @@ class SettingsComponent extends Component {
settings: { settings: {
source: settings.source, source: settings.source,
json: settings.json, json: settings.json,
form: settings.form,
} }
}); });
} }
renderFormFields() {
let keymapFields = KeyMapFields.map((group, index) => {
return <div key={index} className='keymap-fields-group'>
{
group.map((field) => {
let name = field[0];
let label = field[1];
let value = this.state.settings.form.keymaps[name];
return <Input
type='text'
id={name}
name={name}
key={name}
label={label}
value={value}
onChange={this.bindFormKeymapsValue.bind(this)}
/>;
})
}
</div>;
});
return <div>
<fieldset>
<legend>Keybindings</legend>
<div className='keymap-fields'>
{ keymapFields }
</div>
</fieldset>
</div>;
}
renderJsonFields() {
return <div>
<Input
type='textarea'
name='json'
label='Plane JSON'
spellCheck='false'
error={this.state.errors.json}
onChange={this.bindValue.bind(this)}
onBlur={this.save.bind(this)}
value={this.state.settings.json}
/>
</div>;
}
render() { render() {
let fields = null;
if (this.state.settings.source === 'form') {
fields = this.renderFormFields();
} else if (this.state.settings.source === 'json') {
fields = this.renderJsonFields();
}
return ( return (
<div> <div>
<h1>Configure Vim-Vixen</h1> <h1>Configure Vim-Vixen</h1>
<form className='vimvixen-settings-form'> <form className='vimvixen-settings-form'>
<Input
type='radio'
id='setting-source-form'
name='source'
label='Use form'
checked={this.state.settings.source === 'form'}
value='form'
onChange={this.bindValue.bind(this)} />
<Input <Input
type='radio' type='radio'
name='source' name='source'
label='Use plain JSON' label='Use plain JSON'
checked={this.state.settings.source === 'json'} checked={this.state.settings.source === 'json'}
value='json' /> value='json'
onChange={this.bindValue.bind(this)} />
<Input { fields }
type='textarea'
name='json'
label='Plane JSON'
spellCheck='false'
error={this.state.errors.json}
onChange={this.bindValue.bind(this)}
onBlur={this.bindAndSave.bind(this)}
value={this.state.settings.json}
/>
</form> </form>
</div> </div>
); );
@ -82,9 +189,15 @@ class SettingsComponent extends Component {
this.setState(next); this.setState(next);
} }
bindAndSave(e) { bindFormKeymapsValue(e) {
this.bindValue(e); let next = Object.assign({}, this.state);
next.settings.form.keymaps[e.target.name] = e.target.value;
this.context.store.dispatch(settingActions.save(next.settings));
}
save() {
try { try {
let json = this.state.settings.json; let json = this.state.settings.json;
validator.validate(JSON.parse(json)); validator.validate(JSON.parse(json));

@ -7,4 +7,31 @@
min-height: 64ex; min-height: 64ex;
resize: vertical; resize: vertical;
} }
fieldset {
margin: 0;
padding: 0;
border: none;
margin-top: 1rem;
fieldset:first-of-type {
margin-top: 1rem;
}
legend {
font-size: 1.5rem;
line-height: 1.5rem;
font-weight: bold;
}
}
.keymap-fields{
column-count: 2;
.keymap-fields-group {
margin-top: 24px;
}
.keymap-fields-group:first-of-type {
margin-top: 0;
}
}
} }

@ -3,32 +3,35 @@ import './input.scss';
class Input extends Component { class Input extends Component {
renderText(props) {
let inputClassName = props.error ? 'input-error' : '';
return <div className='settings-ui-input'>
<label
type='text'
htmlFor={props.id}
>{ props.label }</label>
<input type='text' className={inputClassName} {...props} />
</div>;
}
renderRadio(props) { renderRadio(props) {
let inputClasses = 'form-field-input'; let inputClassName = props.error ? 'input-error' : '';
if (props.error) { return <div className='settings-ui-input'>
inputClasses += ' input-error';
}
return <div>
<label> <label>
<input className={ inputClasses } type='radio' {...props} /> <input type='radio' className={inputClassName} {...props} />
{ props.label } { props.label }
</label> </label>
</div>; </div>;
} }
renderTextArea(props) { renderTextArea(props) {
let inputClasses = 'form-field-input'; let inputClassName = props.error ? 'input-error' : '';
if (props.error) { return <div className='settings-ui-input'>
inputClasses += ' input-error';
}
return <div>
<label <label
className='form-field-label'
htmlFor={props.id} htmlFor={props.id}
>{ props.label }</label> >{ props.label }</label>
<textarea className={inputClasses} {...props} /> <textarea className={inputClassName} {...props} />
<p className='settings-ui-input-error'>{ this.props.error }</p>
<p className='form-field-error'>{ this.props.error }</p>
</div>; </div>;
} }
@ -36,6 +39,8 @@ class Input extends Component {
let { type } = this.props; let { type } = this.props;
switch (this.props.type) { switch (this.props.type) {
case 'text':
return this.renderText(this.props);
case 'radio': case 'radio':
return this.renderRadio(this.props); return this.renderRadio(this.props);
case 'textarea': case 'textarea':

@ -1,17 +1,28 @@
.form-field-label { .settings-ui-input {
font-weight: bold page-break-inside: avoid;
}
.form-field-error { * {
font-weight: bold; page-break-inside: avoid;
color: red; }
min-height: 1.5em;
}
.form-field-input { label {
padding: 4px; font-weight: bold;
} min-width: 14rem;
display: inline-block;
}
input {
padding: 4px;
}
input.input-crror,
textarea.input-error {
box-shadow: 0 0 2px red;
}
.form-field-input.input-error { &-error {
box-shadow: 0 0 2px red; font-weight: bold;
color: red;
min-height: 1.5em;
}
} }

@ -3,6 +3,7 @@ import actions from 'settings/actions';
const defaultState = { const defaultState = {
source: '', source: '',
json: '', json: '',
form: null,
value: {} value: {}
}; };
@ -12,6 +13,7 @@ export default function reducer(state = defaultState, action = {}) {
return { return {
source: action.source, source: action.source,
json: action.json, json: action.json,
form: action.form,
value: action.value, value: action.value,
}; };
default: default:

@ -62,5 +62,69 @@ export default {
"wikipedia": "https://en.wikipedia.org/w/index.php?search={}" "wikipedia": "https://en.wikipedia.org/w/index.php?search={}"
} }
} }
}` }`,
'form': {
'keymaps': {
'scroll.vertically?{count:-1}': 'j',
'scroll.vertically?{count:1}': 'k',
'scroll.horizonally?{count:-1}': 'h',
'scroll.horizonally?{count:1}': 'l',
'scroll.home': '0',
'scroll.end': '$',
'scroll.pages?{count:-0.5}': '<C-U>',
'scroll.pages?{count:0.5}': '<C-D>',
'scroll.pages?{count:-1}': '<C-B>',
'scroll.pages?{count:1}': '<C-F>',
'tabs.close': 'd',
'tabs.reopen': 'u',
'tabs.next?{count:1}': 'J',
'tabs.prev?{count:1}': 'K',
'tabs.first': 'g0',
'tabs.last': 'g$',
'tabs.reload?{cache:true}': 'r',
'tabs.pin.toggle': 'zp',
'tabs.duplicate': 'zd',
'follow.start?{newTab:false}': 'f',
'follow.start?{newTab:true}': 'F',
'navigate.histories.prev': 'H',
'navigate.histories.next': 'L',
'navigate.link.next': ']]',
'navigate.link.prev': '[[',
'navigate.parent': 'gu',
'navigate.root': 'gU',
'find.start': '/',
'find.next': 'n',
'find.prev': 'N',
'command.show': ':',
'command.show.open?{alter:false}': 'o',
'command.show.open?{alter:true}': 'O',
'command.show.tabopen?{alter:false}': 't',
'command.show.tabopen?{alter:true}': 'T',
'command.show.winopen?{alter:false}': 'w',
'command.show.winopen?{alter:true}': 'W',
'command.show.buffer': 'b',
'addon.toggle.enabled': '<S-Esc>',
'urls.yank': 'y',
'zoom.in': 'zi',
'zoom.out': 'zo',
'zoom.neutral': 'zz',
},
'search': {
'default': 'google',
'engines': {
'google': 'https://google.com/search?q={}',
'yahoo': 'https://search.yahoo.com/search?p={}',
'bing': 'https://www.bing.com/search?q={}',
'duckduckgo': 'https://duckduckgo.com/?q={}',
'twitter': 'https://twitter.com/search?q={}',
'wikipedia': 'https://en.wikipedia.org/w/index.php?search={}'
}
}
}
}; };