Use Preact for settings and show validation
This commit is contained in:
parent
44459e39c3
commit
d33b37cdb9
4 changed files with 99 additions and 18 deletions
|
@ -1,5 +1,6 @@
|
||||||
import './site.scss';
|
import './site.scss';
|
||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
|
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';
|
||||||
|
|
||||||
|
@ -10,6 +11,9 @@ class SettingsComponent extends Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
settings: {
|
settings: {
|
||||||
json: '',
|
json: '',
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
json: '',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.context.store.subscribe(this.stateChanged.bind(this));
|
this.context.store.subscribe(this.stateChanged.bind(this));
|
||||||
|
@ -35,39 +39,47 @@ class SettingsComponent extends Component {
|
||||||
<h1>Configure Vim-Vixen</h1>
|
<h1>Configure Vim-Vixen</h1>
|
||||||
<form className='vimvixen-settings-form'>
|
<form className='vimvixen-settings-form'>
|
||||||
|
|
||||||
<p>Load settings from:</p>
|
<Input
|
||||||
<input type='radio' id='setting-source-json'
|
type='radio'
|
||||||
name='source'
|
name='source'
|
||||||
value='json'
|
label='Use plain JSON'
|
||||||
onChange={this.bindAndSave.bind(this)}
|
checked={this.state.settings.source === 'json'}
|
||||||
checked={this.state.settings.source === 'json'} />
|
value='json' />
|
||||||
<label htmlFor='settings-source-json'>JSON</label>
|
|
||||||
|
|
||||||
<textarea name='json' spellCheck='false'
|
<Input
|
||||||
onInput={this.validate.bind(this)}
|
type='textarea'
|
||||||
|
name='json'
|
||||||
|
label='Plane JSON'
|
||||||
|
spellCheck='false'
|
||||||
|
error={this.state.errors.json}
|
||||||
onChange={this.bindValue.bind(this)}
|
onChange={this.bindValue.bind(this)}
|
||||||
onBlur={this.bindAndSave.bind(this)}
|
onBlur={this.bindAndSave.bind(this)}
|
||||||
value={this.state.settings.json} />
|
value={this.state.settings.json}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(e) {
|
validate(target) {
|
||||||
try {
|
if (target.name === 'json') {
|
||||||
let settings = JSON.parse(e.target.value);
|
let settings = JSON.parse(target.value);
|
||||||
validator.validate(settings);
|
validator.validate(settings);
|
||||||
e.target.setCustomValidity('');
|
|
||||||
} catch (err) {
|
|
||||||
e.target.setCustomValidity(err.message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bindValue(e) {
|
bindValue(e) {
|
||||||
let nextSettings = Object.assign({}, this.state.settings);
|
let next = Object.assign({}, this.state);
|
||||||
nextSettings[e.target.name] = e.target.value;
|
|
||||||
|
|
||||||
this.setState({ settings: nextSettings });
|
next.errors.json = '';
|
||||||
|
try {
|
||||||
|
this.validate(e.target);
|
||||||
|
} catch (err) {
|
||||||
|
next.errors.json = err.message;
|
||||||
|
}
|
||||||
|
next.settings[e.target.name] = e.target.value;
|
||||||
|
|
||||||
|
this.setState(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
bindAndSave(e) {
|
bindAndSave(e) {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
.vimvixen-settings-form {
|
.vimvixen-settings-form {
|
||||||
|
padding: 2px;
|
||||||
|
|
||||||
textarea[name=json] {
|
textarea[name=json] {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
50
src/settings/components/ui/input.jsx
Normal file
50
src/settings/components/ui/input.jsx
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { h, Component } from 'preact';
|
||||||
|
import './input.scss';
|
||||||
|
|
||||||
|
class Input extends Component {
|
||||||
|
|
||||||
|
renderRadio(props) {
|
||||||
|
let inputClasses = 'form-field-input';
|
||||||
|
if (props.error) {
|
||||||
|
inputClasses += ' input-error';
|
||||||
|
}
|
||||||
|
return <div>
|
||||||
|
<label>
|
||||||
|
<input className={ inputClasses } type='radio' {...props} />
|
||||||
|
{ props.label }
|
||||||
|
</label>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTextArea(props) {
|
||||||
|
let inputClasses = 'form-field-input';
|
||||||
|
if (props.error) {
|
||||||
|
inputClasses += ' input-error';
|
||||||
|
}
|
||||||
|
return <div>
|
||||||
|
<label
|
||||||
|
className='form-field-label'
|
||||||
|
htmlFor={props.id}
|
||||||
|
>{ props.label }</label>
|
||||||
|
<textarea className={inputClasses} {...props} />
|
||||||
|
|
||||||
|
<p className='form-field-error'>{ this.props.error }</p>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { type } = this.props;
|
||||||
|
|
||||||
|
switch (this.props.type) {
|
||||||
|
case 'radio':
|
||||||
|
return this.renderRadio(this.props);
|
||||||
|
case 'textarea':
|
||||||
|
return this.renderTextArea(this.props);
|
||||||
|
default:
|
||||||
|
console.warn(`Unsupported input type ${type}`);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Input;
|
17
src/settings/components/ui/input.scss
Normal file
17
src/settings/components/ui/input.scss
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
.form-field-label {
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field-error {
|
||||||
|
font-weight: bold;
|
||||||
|
color: red;
|
||||||
|
min-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field-input {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field-input.input-error {
|
||||||
|
box-shadow: 0 0 2px red;
|
||||||
|
}
|
Reference in a new issue