diff options
author | Shin'ya Ueoka <ueokande@i-beam.org> | 2019-05-07 21:16:47 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-07 21:16:47 +0900 |
commit | 05ef6a8ca35aaa801c11eb6b4896caa3690058af (patch) | |
tree | 2c7708ca91ac2b462cc86aa28612e3d3943496f3 /src/settings/components | |
parent | 457d954e08923b4accd28a919c72d0b61db1bb98 (diff) | |
parent | 27d0a7f37d24a0ad68a8ccb7dee18fc1d00eea58 (diff) |
Merge pull request #578 from ueokande/move-to-typescript
Move to TypeScript
Diffstat (limited to 'src/settings/components')
-rw-r--r-- | src/settings/components/form/BlacklistForm.tsx (renamed from src/settings/components/form/BlacklistForm.jsx) | 28 | ||||
-rw-r--r-- | src/settings/components/form/KeymapsForm.tsx (renamed from src/settings/components/form/KeymapsForm.jsx) | 41 | ||||
-rw-r--r-- | src/settings/components/form/PropertiesForm.tsx (renamed from src/settings/components/form/PropertiesForm.jsx) | 32 | ||||
-rw-r--r-- | src/settings/components/form/SearchForm.tsx (renamed from src/settings/components/form/SearchForm.jsx) | 50 | ||||
-rw-r--r-- | src/settings/components/index.jsx | 153 | ||||
-rw-r--r-- | src/settings/components/index.tsx | 199 | ||||
-rw-r--r-- | src/settings/components/ui/AddButton.tsx (renamed from src/settings/components/ui/AddButton.jsx) | 5 | ||||
-rw-r--r-- | src/settings/components/ui/DeleteButton.tsx (renamed from src/settings/components/ui/DeleteButton.jsx) | 5 | ||||
-rw-r--r-- | src/settings/components/ui/Input.jsx | 60 | ||||
-rw-r--r-- | src/settings/components/ui/Input.tsx | 82 |
10 files changed, 361 insertions, 294 deletions
diff --git a/src/settings/components/form/BlacklistForm.jsx b/src/settings/components/form/BlacklistForm.tsx index c470758..637bc1e 100644 --- a/src/settings/components/form/BlacklistForm.jsx +++ b/src/settings/components/form/BlacklistForm.tsx @@ -2,9 +2,19 @@ import './BlacklistForm.scss'; import AddButton from '../ui/AddButton'; import DeleteButton from '../ui/DeleteButton'; import React from 'react'; -import PropTypes from 'prop-types'; -class BlacklistForm extends React.Component { +interface Props { + value: string[]; + onChange: (value: string[]) => void; + onBlur: () => void; +} + +class BlacklistForm extends React.Component<Props> { + public static defaultProps: Props = { + value: [], + onChange: () => {}, + onBlur: () => {}, + }; render() { return <div className='form-blacklist-form'> @@ -28,7 +38,7 @@ class BlacklistForm extends React.Component { </div>; } - bindValue(e) { + bindValue(e: any) { let name = e.target.name; let index = e.target.getAttribute('data-index'); let next = this.props.value ? this.props.value.slice() : []; @@ -48,16 +58,4 @@ class BlacklistForm extends React.Component { } } -BlacklistForm.propTypes = { - value: PropTypes.arrayOf(PropTypes.string), - onChange: PropTypes.func, - onBlur: PropTypes.func, -}; - -BlacklistForm.defaultProps = { - value: [], - onChange: () => {}, - onBlur: () => {}, -}; - export default BlacklistForm; diff --git a/src/settings/components/form/KeymapsForm.jsx b/src/settings/components/form/KeymapsForm.tsx index 01acf61..ad4d0e7 100644 --- a/src/settings/components/form/KeymapsForm.jsx +++ b/src/settings/components/form/KeymapsForm.tsx @@ -1,25 +1,35 @@ import './KeymapsForm.scss'; import React from 'react'; -import PropTypes from 'prop-types'; import Input from '../ui/Input'; import keymaps from '../../keymaps'; +import { FormKeymaps } from '../../../shared/SettingData'; -class KeymapsForm extends React.Component { +interface Props { + value: FormKeymaps; + onChange: (e: FormKeymaps) => void; + onBlur: () => void; +} + +class KeymapsForm extends React.Component<Props> { + public static defaultProps: Props = { + value: FormKeymaps.valueOf({}), + onChange: () => {}, + onBlur: () => {}, + } render() { + let values = this.props.value.toJSON(); return <div className='form-keymaps-form'> { keymaps.fields.map((group, index) => { return <div key={index} className='form-keymaps-form-field-group'> { - group.map((field) => { - let name = field[0]; - let label = field[1]; - let value = this.props.value[name] || ''; + group.map(([name, label]) => { + let value = values[name] || ''; return <Input type='text' id={name} name={name} key={name} label={label} value={value} - onChange={this.bindValue.bind(this)} + onValueChange={this.bindValue.bind(this)} onBlur={this.props.onBlur} />; }) @@ -30,22 +40,9 @@ class KeymapsForm extends React.Component { </div>; } - bindValue(e) { - let next = { ...this.props.value }; - next[e.target.name] = e.target.value; - - this.props.onChange(next); + bindValue(name: string, value: string) { + this.props.onChange(this.props.value.buildWithOverride(name, value)); } } -KeymapsForm.propTypes = { - value: PropTypes.objectOf(PropTypes.string), - onChange: PropTypes.func, -}; - -KeymapsForm.defaultProps = { - value: {}, - onChange: () => {}, -}; - export default KeymapsForm; diff --git a/src/settings/components/form/PropertiesForm.jsx b/src/settings/components/form/PropertiesForm.tsx index 979fdd8..0be5f5c 100644 --- a/src/settings/components/form/PropertiesForm.jsx +++ b/src/settings/components/form/PropertiesForm.tsx @@ -1,8 +1,20 @@ import './PropertiesForm.scss'; import React from 'react'; -import PropTypes from 'prop-types'; -class PropertiesForm extends React.Component { +interface Props { + types: {[key: string]: string}; + value: {[key: string]: any}; + onChange: (value: any) => void; + onBlur: () => void; +} + +class PropertiesForm extends React.Component<Props> { + public static defaultProps: Props = { + types: {}, + value: {}, + onChange: () => {}, + onBlur: () => {}, + }; render() { let types = this.props.types; @@ -12,13 +24,15 @@ class PropertiesForm extends React.Component { { Object.keys(types).map((name) => { let type = types[name]; - let inputType = null; + let inputType = ''; if (type === 'string') { inputType = 'text'; } else if (type === 'number') { inputType = 'number'; } else if (type === 'boolean') { inputType = 'checkbox'; + } else { + return null; } return <div key={name} className='form-properties-form-row'> <label> @@ -37,7 +51,7 @@ class PropertiesForm extends React.Component { </div>; } - bindValue(e) { + bindValue(e: React.ChangeEvent<HTMLInputElement>) { let name = e.target.name; let next = { ...this.props.value }; if (e.target.type.toLowerCase() === 'checkbox') { @@ -52,14 +66,4 @@ class PropertiesForm extends React.Component { } } -PropertiesForm.propTypes = { - value: PropTypes.objectOf(PropTypes.any), - onChange: PropTypes.func, -}; - -PropertiesForm.defaultProps = { - value: {}, - onChange: () => {}, -}; - export default PropertiesForm; diff --git a/src/settings/components/form/SearchForm.jsx b/src/settings/components/form/SearchForm.tsx index 6b0bd01..67dbeba 100644 --- a/src/settings/components/form/SearchForm.jsx +++ b/src/settings/components/form/SearchForm.tsx @@ -1,17 +1,24 @@ import './SearchForm.scss'; import React from 'react'; -import PropTypes from 'prop-types'; import AddButton from '../ui/AddButton'; import DeleteButton from '../ui/DeleteButton'; +import { FormSearch } from '../../../shared/SettingData'; -class SearchForm extends React.Component { +interface Props { + value: FormSearch; + onChange: (value: FormSearch) => void; + onBlur: () => void; +} - render() { - let value = this.props.value; - if (!value.engines) { - value.engines = []; - } +class SearchForm extends React.Component<Props> { + public static defaultProps: Props = { + value: FormSearch.valueOf({ default: '', engines: []}), + onChange: () => {}, + onBlur: () => {}, + } + render() { + let value = this.props.value.toJSON(); return <div className='form-search-form'> <div className='form-search-form-header'> <div className='column-name'>Name</div> @@ -47,46 +54,33 @@ class SearchForm extends React.Component { </div>; } - bindValue(e) { - let value = this.props.value; + bindValue(e: any) { + let value = this.props.value.toJSON(); let name = e.target.name; - let index = e.target.getAttribute('data-index'); - let next = { + let index = Number(e.target.getAttribute('data-index')); + let next: typeof value = { default: value.default, - engines: value.engines ? value.engines.slice() : [], + engines: value.engines.slice(), }; if (name === 'name') { next.engines[index][0] = e.target.value; - next.default = this.props.value.engines[index][0]; + next.default = value.engines[index][0]; } else if (name === 'url') { next.engines[index][1] = e.target.value; } else if (name === 'default') { - next.default = this.props.value.engines[index][0]; + next.default = value.engines[index][0]; } else if (name === 'add') { next.engines.push(['', '']); } else if (name === 'delete') { next.engines.splice(index, 1); } - this.props.onChange(next); + this.props.onChange(FormSearch.valueOf(next)); if (name === 'delete' || name === 'default') { this.props.onBlur(); } } } -SearchForm.propTypes = { - value: PropTypes.shape({ - default: PropTypes.string, - engines: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)), - }), - onChange: PropTypes.func, -}; - -SearchForm.defaultProps = { - value: { default: '', engines: []}, - onChange: () => {}, -}; - export default SearchForm; diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx deleted file mode 100644 index 4ef59d7..0000000 --- a/src/settings/components/index.jsx +++ /dev/null @@ -1,153 +0,0 @@ -import './site.scss'; -import React from 'react'; -import { connect } from 'react-redux'; -import Input from './ui/Input'; -import SearchForm from './form/SearchForm'; -import KeymapsForm from './form/KeymapsForm'; -import BlacklistForm from './form/BlacklistForm'; -import PropertiesForm from './form/PropertiesForm'; -import * as properties from 'shared/settings/properties'; -import * as settingActions from 'settings/actions/setting'; - -const DO_YOU_WANT_TO_CONTINUE = - 'Some settings in JSON can be lost when migrating. ' + - 'Do you want to continue?'; - -class SettingsComponent extends React.Component { - componentDidMount() { - this.props.dispatch(settingActions.load()); - } - - renderFormFields(form) { - return <div> - <fieldset> - <legend>Keybindings</legend> - <KeymapsForm - value={form.keymaps} - onChange={value => this.bindForm('keymaps', value)} - onBlur={this.save.bind(this)} - /> - </fieldset> - <fieldset> - <legend>Search Engines</legend> - <SearchForm - value={form.search} - onChange={value => this.bindForm('search', value)} - onBlur={this.save.bind(this)} - /> - </fieldset> - <fieldset> - <legend>Blacklist</legend> - <BlacklistForm - value={form.blacklist} - onChange={value => this.bindForm('blacklist', value)} - onBlur={this.save.bind(this)} - /> - </fieldset> - <fieldset> - <legend>Properties</legend> - <PropertiesForm - types={properties.types} - value={form.properties} - onChange={value => this.bindForm('properties', value)} - onBlur={this.save.bind(this)} - /> - </fieldset> - </div>; - } - - renderJsonFields(json, error) { - return <div> - <Input - type='textarea' - name='json' - label='Plain JSON' - spellCheck='false' - error={error} - onChange={this.bindJson.bind(this)} - onBlur={this.save.bind(this)} - value={json} - /> - </div>; - } - - render() { - let fields = null; - let disabled = this.props.error.length > 0; - if (this.props.source === 'form') { - fields = this.renderFormFields(this.props.form); - } else if (this.props.source === 'json') { - fields = this.renderJsonFields(this.props.json, this.props.error); - } - return ( - <div> - <h1>Configure Vim-Vixen</h1> - <form className='vimvixen-settings-form'> - <Input - type='radio' - id='setting-source-form' - name='source' - label='Use form' - checked={this.props.source === 'form'} - value='form' - onChange={this.bindSource.bind(this)} - disabled={disabled} /> - - <Input - type='radio' - name='source' - label='Use plain JSON' - checked={this.props.source === 'json'} - value='json' - onChange={this.bindSource.bind(this)} - disabled={disabled} /> - { fields } - </form> - </div> - ); - } - - bindForm(name, value) { - let settings = { - source: this.props.source, - json: this.props.json, - form: { ...this.props.form }, - }; - settings.form[name] = value; - this.props.dispatch(settingActions.set(settings)); - } - - bindJson(e) { - let settings = { - source: this.props.source, - json: e.target.value, - form: this.props.form, - }; - this.props.dispatch(settingActions.set(settings)); - } - - bindSource(e) { - let from = this.props.source; - let to = e.target.value; - - if (from === 'form' && to === 'json') { - this.props.dispatch(settingActions.switchToJson(this.props.form)); - } else if (from === 'json' && to === 'form') { - let b = window.confirm(DO_YOU_WANT_TO_CONTINUE); - if (!b) { - this.forceUpdate(); - return; - } - this.props.dispatch(settingActions.switchToForm(this.props.json)); - } - } - - save() { - let settings = this.props.store.getState(); - this.props.dispatch(settingActions.save(settings)); - } -} - -const mapStateToProps = state => state; - -export default connect(mapStateToProps)(SettingsComponent); diff --git a/src/settings/components/index.tsx b/src/settings/components/index.tsx new file mode 100644 index 0000000..b4a0866 --- /dev/null +++ b/src/settings/components/index.tsx @@ -0,0 +1,199 @@ +import './site.scss'; +import React from 'react'; +import { connect } from 'react-redux'; +import Input from './ui/Input'; +import SearchForm from './form/SearchForm'; +import KeymapsForm from './form/KeymapsForm'; +import BlacklistForm from './form/BlacklistForm'; +import PropertiesForm from './form/PropertiesForm'; +import * as settingActions from '../../settings/actions/setting'; +import SettingData, { + JSONSettings, FormKeymaps, FormSearch, FormSettings, +} from '../../shared/SettingData'; +import { State as AppState } from '../reducers/setting'; +import * as settings from '../../shared/Settings'; +import * as PropertyDefs from '../../shared/property-defs'; + +const DO_YOU_WANT_TO_CONTINUE = + 'Some settings in JSON can be lost when migrating. ' + + 'Do you want to continue?'; + +type StateProps = ReturnType<typeof mapStateToProps>; +interface DispatchProps { + dispatch: (action: any) => void, +} +type Props = StateProps & DispatchProps & { + // FIXME + store: any; +}; + +class SettingsComponent extends React.Component<Props> { + componentDidMount() { + this.props.dispatch(settingActions.load()); + } + + renderFormFields(form: any) { + let types = PropertyDefs.defs.reduce( + (o: {[key: string]: string}, def) => { + o[def.name] = def.type; + return o; + }, {}); + return <div> + <fieldset> + <legend>Keybindings</legend> + <KeymapsForm + value={form.keymaps} + onChange={this.bindKeymapsForm.bind(this)} + onBlur={this.save.bind(this)} + /> + </fieldset> + <fieldset> + <legend>Search Engines</legend> + <SearchForm + value={form.search} + onChange={this.bindSearchForm.bind(this)} + onBlur={this.save.bind(this)} + /> + </fieldset> + <fieldset> + <legend>Blacklist</legend> + <BlacklistForm + value={form.blacklist} + onChange={this.bindBlacklistForm.bind(this)} + onBlur={this.save.bind(this)} + /> + </fieldset> + <fieldset> + <legend>Properties</legend> + <PropertiesForm + types={types} + value={form.properties} + onChange={this.bindPropertiesForm.bind(this)} + onBlur={this.save.bind(this)} + /> + </fieldset> + </div>; + } + + renderJsonFields(json: JSONSettings, error: string) { + return <div> + <Input + type='textarea' + name='json' + label='Plain JSON' + spellCheck={false} + error={error} + onValueChange={this.bindJson.bind(this)} + onBlur={this.save.bind(this)} + value={json.toJSON()} + /> + </div>; + } + + render() { + let fields = null; + let disabled = this.props.error.length > 0; + if (this.props.source === 'form') { + fields = this.renderFormFields(this.props.form); + } else if (this.props.source === 'json') { + fields = this.renderJsonFields( + this.props.json as JSONSettings, this.props.error); + } + return ( + <div> + <h1>Configure Vim-Vixen</h1> + <form className='vimvixen-settings-form'> + <Input + type='radio' + id='setting-source-form' + name='source' + label='Use form' + checked={this.props.source === 'form'} + value='form' + onValueChange={this.bindSource.bind(this)} + disabled={disabled} /> + + <Input + type='radio' + name='source' + label='Use plain JSON' + checked={this.props.source === 'json'} + value='json' + onValueChange={this.bindSource.bind(this)} + disabled={disabled} /> + { fields } + </form> + </div> + ); + } + + bindKeymapsForm(value: FormKeymaps) { + let data = new SettingData({ + source: this.props.source, + form: (this.props.form as FormSettings).buildWithKeymaps(value), + }); + this.props.dispatch(settingActions.set(data)); + } + + bindSearchForm(value: any) { + let data = new SettingData({ + source: this.props.source, + form: (this.props.form as FormSettings).buildWithSearch( + FormSearch.valueOf(value)), + }); + this.props.dispatch(settingActions.set(data)); + } + + bindBlacklistForm(value: any) { + let data = new SettingData({ + source: this.props.source, + form: (this.props.form as FormSettings).buildWithBlacklist( + settings.blacklistValueOf(value)), + }); + this.props.dispatch(settingActions.set(data)); + } + + bindPropertiesForm(value: any) { + let data = new SettingData({ + source: this.props.source, + form: (this.props.form as FormSettings).buildWithProperties( + settings.propertiesValueOf(value)), + }); + this.props.dispatch(settingActions.set(data)); + } + + bindJson(_name: string, value: string) { + let data = new SettingData({ + source: this.props.source, + json: JSONSettings.valueOf(value), + }); + this.props.dispatch(settingActions.set(data)); + } + + bindSource(_name: string, value: string) { + let from = this.props.source; + if (from === 'form' && value === 'json') { + this.props.dispatch(settingActions.switchToJson( + this.props.form as FormSettings)); + } else if (from === 'json' && value === 'form') { + let b = window.confirm(DO_YOU_WANT_TO_CONTINUE); + if (!b) { + this.forceUpdate(); + return; + } + this.props.dispatch( + settingActions.switchToForm(this.props.json as JSONSettings)); + } + } + + save() { + let { source, json, form } = this.props.store.getState(); + this.props.dispatch(settingActions.save( + new SettingData({ source, json, form }), + )); + } +} + +const mapStateToProps = (state: AppState) => ({ ...state }); + +export default connect(mapStateToProps)(SettingsComponent); diff --git a/src/settings/components/ui/AddButton.jsx b/src/settings/components/ui/AddButton.tsx index 185a03b..0577068 100644 --- a/src/settings/components/ui/AddButton.jsx +++ b/src/settings/components/ui/AddButton.tsx @@ -1,7 +1,10 @@ import './AddButton.scss'; import React from 'react'; -class AddButton extends React.Component { +interface Props extends React.AllHTMLAttributes<HTMLInputElement> { +} + +class AddButton extends React.Component<Props> { render() { return <input className='ui-add-button' type='button' value='✚' diff --git a/src/settings/components/ui/DeleteButton.jsx b/src/settings/components/ui/DeleteButton.tsx index 75811cd..f0ef6c9 100644 --- a/src/settings/components/ui/DeleteButton.jsx +++ b/src/settings/components/ui/DeleteButton.tsx @@ -1,7 +1,10 @@ import './DeleteButton.scss'; import React from 'react'; -class DeleteButton extends React.Component { +interface Props extends React.AllHTMLAttributes<HTMLInputElement> { +} + +class DeleteButton extends React.Component<Props> { render() { return <input className='ui-delete-button' type='button' value='✖' diff --git a/src/settings/components/ui/Input.jsx b/src/settings/components/ui/Input.jsx deleted file mode 100644 index 13a246b..0000000 --- a/src/settings/components/ui/Input.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Input.scss'; - -class Input extends React.Component { - - renderText(props) { - let inputClassName = props.error ? 'input-error' : ''; - return <div className='settings-ui-input'> - <label htmlFor={props.id}>{ props.label }</label> - <input type='text' className={inputClassName} {...props} /> - </div>; - } - - renderRadio(props) { - let inputClassName = props.error ? 'input-error' : ''; - return <div className='settings-ui-input'> - <label> - <input type='radio' className={inputClassName} {...props} /> - { props.label } - </label> - </div>; - } - - renderTextArea(props) { - let inputClassName = props.error ? 'input-error' : ''; - return <div className='settings-ui-input'> - <label - htmlFor={props.id} - >{ props.label }</label> - <textarea className={inputClassName} {...props} /> - <p className='settings-ui-input-error'>{ this.props.error }</p> - </div>; - } - - render() { - let { type } = this.props; - - switch (this.props.type) { - case 'text': - return this.renderText(this.props); - case 'radio': - return this.renderRadio(this.props); - case 'textarea': - return this.renderTextArea(this.props); - default: - console.warn(`Unsupported input type ${type}`); - } - return null; - } -} - -Input.propTypes = { - type: PropTypes.string, - error: PropTypes.string, - label: PropTypes.string, - value: PropTypes.string, -}; - -export default Input; diff --git a/src/settings/components/ui/Input.tsx b/src/settings/components/ui/Input.tsx new file mode 100644 index 0000000..b7593b9 --- /dev/null +++ b/src/settings/components/ui/Input.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import './Input.scss'; + +interface Props extends React.AllHTMLAttributes<HTMLElement> { + name: string; + type: string; + error?: string; + label: string; + value: string; + onValueChange?: (name: string, value: string) => void; + onBlur?: (e: React.FocusEvent<Element>) => void; +} + +class Input extends React.Component<Props> { + renderText(props: Props) { + let inputClassName = props.error ? 'input-error' : ''; + let pp = { ...props }; + delete pp.onValueChange; + return <div className='settings-ui-input'> + <label htmlFor={props.id}>{ props.label }</label> + <input + type='text' className={inputClassName} + onChange={this.bindOnChange.bind(this)} + { ...pp } /> + </div>; + } + + renderRadio(props: Props) { + let inputClassName = props.error ? 'input-error' : ''; + let pp = { ...props }; + delete pp.onValueChange; + return <div className='settings-ui-input'> + <label> + <input + type='radio' className={inputClassName} + onChange={this.bindOnChange.bind(this)} + { ...pp } /> + { props.label } + </label> + </div>; + } + + renderTextArea(props: Props) { + let inputClassName = props.error ? 'input-error' : ''; + let pp = { ...props }; + delete pp.onValueChange; + return <div className='settings-ui-input'> + <label + htmlFor={props.id} + >{ props.label }</label> + <textarea + className={inputClassName} + onChange={this.bindOnChange.bind(this)} + { ...pp } /> + <p className='settings-ui-input-error'>{ this.props.error }</p> + </div>; + } + + render() { + let { type } = this.props; + + switch (this.props.type) { + case 'text': + return this.renderText(this.props); + case 'radio': + return this.renderRadio(this.props); + case 'textarea': + return this.renderTextArea(this.props); + default: + console.warn(`Unsupported input type ${type}`); + } + return null; + } + + bindOnChange(e: React.ChangeEvent<HTMLInputElement|HTMLTextAreaElement>) { + if (this.props.onValueChange) { + this.props.onValueChange(e.target.name, e.target.value); + } + } +} + +export default Input; |