diff options
Diffstat (limited to 'src/settings')
| -rw-r--r-- | src/settings/actions/index.js | 7 | ||||
| -rw-r--r-- | src/settings/actions/index.ts | 36 | ||||
| -rw-r--r-- | src/settings/actions/setting.js | 63 | ||||
| -rw-r--r-- | src/settings/actions/setting.ts | 73 | ||||
| -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 | ||||
| -rw-r--r-- | src/settings/index.tsx (renamed from src/settings/index.jsx) | 0 | ||||
| -rw-r--r-- | src/settings/keymaps.ts (renamed from src/settings/keymaps.js) | 3 | ||||
| -rw-r--r-- | src/settings/reducers/setting.ts (renamed from src/settings/reducers/setting.js) | 28 | ||||
| -rw-r--r-- | src/settings/storage.ts | 15 | 
18 files changed, 505 insertions, 375 deletions
| diff --git a/src/settings/actions/index.js b/src/settings/actions/index.js deleted file mode 100644 index 016f2a5..0000000 --- a/src/settings/actions/index.js +++ /dev/null @@ -1,7 +0,0 @@ -export default { -  // Settings -  SETTING_SET_SETTINGS: 'setting.set.settings', -  SETTING_SHOW_ERROR: 'setting.show.error', -  SETTING_SWITCH_TO_FORM: 'setting.switch.to.form', -  SETTING_SWITCH_TO_JSON: 'setting.switch.to.json', -}; diff --git a/src/settings/actions/index.ts b/src/settings/actions/index.ts new file mode 100644 index 0000000..b1e996e --- /dev/null +++ b/src/settings/actions/index.ts @@ -0,0 +1,36 @@ +import { +  JSONSettings, FormSettings, SettingSource, +} from '../../shared/SettingData'; + +// Settings +export const SETTING_SET_SETTINGS = 'setting.set.settings'; +export const SETTING_SHOW_ERROR = 'setting.show.error'; +export const SETTING_SWITCH_TO_FORM = 'setting.switch.to.form'; +export const SETTING_SWITCH_TO_JSON = 'setting.switch.to.json'; + +interface SettingSetSettingsAcion { +  type: typeof SETTING_SET_SETTINGS; +  source: SettingSource; +  json?: JSONSettings; +  form?: FormSettings; +} + +interface SettingShowErrorAction { +  type: typeof SETTING_SHOW_ERROR; +  error: string; +  json: JSONSettings; +} + +interface SettingSwitchToFormAction { +  type: typeof SETTING_SWITCH_TO_FORM; +  form: FormSettings, +} + +interface SettingSwitchToJsonAction { +  type: typeof SETTING_SWITCH_TO_JSON; +  json: JSONSettings, +} + +export type SettingAction = +  SettingSetSettingsAcion | SettingShowErrorAction | +  SettingSwitchToFormAction | SettingSwitchToJsonAction; diff --git a/src/settings/actions/setting.js b/src/settings/actions/setting.js deleted file mode 100644 index db63a45..0000000 --- a/src/settings/actions/setting.js +++ /dev/null @@ -1,63 +0,0 @@ -import actions from 'settings/actions'; -import * as validator from 'shared/settings/validator'; -import * as settingsValues from 'shared/settings/values'; -import * as settingsStorage from 'shared/settings/storage'; -import keymaps from '../keymaps'; - -const load = async() => { -  let settings = await settingsStorage.loadRaw(); -  return set(settings); -}; - -const save = async(settings) => { -  try { -    if (settings.source === 'json') { -      let value = JSON.parse(settings.json); -      validator.validate(value); -    } -  } catch (e) { -    return { -      type: actions.SETTING_SHOW_ERROR, -      error: e.toString(), -      json: settings.json, -    }; -  } -  await settingsStorage.save(settings); -  return set(settings); -}; - -const switchToForm = (json) => { -  try { -    validator.validate(JSON.parse(json)); -    let form = settingsValues.formFromJson(json, keymaps.allowedOps); -    return { -      type: actions.SETTING_SWITCH_TO_FORM, -      form, -    }; -  } catch (e) { -    return { -      type: actions.SETTING_SHOW_ERROR, -      error: e.toString(), -      json, -    }; -  } -}; - -const switchToJson = (form) => { -  let json = settingsValues.jsonFromForm(form); -  return { -    type: actions.SETTING_SWITCH_TO_JSON, -    json, -  }; -}; - -const set = (settings) => { -  return { -    type: actions.SETTING_SET_SETTINGS, -    source: settings.source, -    json: settings.json, -    form: settings.form, -  }; -}; - -export { load, save, set, switchToForm, switchToJson }; diff --git a/src/settings/actions/setting.ts b/src/settings/actions/setting.ts new file mode 100644 index 0000000..9eb416e --- /dev/null +++ b/src/settings/actions/setting.ts @@ -0,0 +1,73 @@ +import * as actions from './index'; +import * as storages from '../storage'; +import SettingData, { +  JSONSettings, FormSettings, SettingSource, +} from '../../shared/SettingData'; + +const load = async(): Promise<actions.SettingAction> => { +  let data = await storages.load(); +  return set(data); +}; + +const save = async(data: SettingData): Promise<actions.SettingAction> => { +  try { +    if (data.getSource() === SettingSource.JSON) { +      // toSettings exercise validation +      data.toSettings(); +    } +  } catch (e) { +    return { +      type: actions.SETTING_SHOW_ERROR, +      error: e.toString(), +      json: data.getJSON(), +    }; +  } +  await storages.save(data); +  return set(data); +}; + +const switchToForm = (json: JSONSettings): actions.SettingAction => { +  try { +    // toSettings exercise validation +    let form = FormSettings.fromSettings(json.toSettings()); +    return { +      type: actions.SETTING_SWITCH_TO_FORM, +      form, +    }; +  } catch (e) { +    return { +      type: actions.SETTING_SHOW_ERROR, +      error: e.toString(), +      json, +    }; +  } +}; + +const switchToJson = (form: FormSettings): actions.SettingAction => { +  let json = JSONSettings.fromSettings(form.toSettings()); +  return { +    type: actions.SETTING_SWITCH_TO_JSON, +    json, +  }; +}; + +const set = (data: SettingData): actions.SettingAction => { +  let source = data.getSource(); +  switch (source) { +  case SettingSource.JSON: +    return { +      type: actions.SETTING_SET_SETTINGS, +      source: source, +      json: data.getJSON(), +    }; +  case SettingSource.Form: +    return { +      type: actions.SETTING_SET_SETTINGS, +      source: source, +      form: data.getForm(), +    }; +  } +  throw new Error(`unknown source: ${source}`); +}; + +export { load, save, set, switchToForm, switchToJson }; 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; diff --git a/src/settings/index.jsx b/src/settings/index.tsx index 6aec7a0..6aec7a0 100644 --- a/src/settings/index.jsx +++ b/src/settings/index.tsx diff --git a/src/settings/keymaps.js b/src/settings/keymaps.ts index ccfc74c..38045ad 100644 --- a/src/settings/keymaps.js +++ b/src/settings/keymaps.ts @@ -66,9 +66,6 @@ const fields = [    ]  ]; -const allowedOps = [].concat(...fields.map(group => group.map(e => e[0]))); -  export default {    fields, -  allowedOps,  }; diff --git a/src/settings/reducers/setting.js b/src/settings/reducers/setting.ts index 54033aa..c4a21c7 100644 --- a/src/settings/reducers/setting.js +++ b/src/settings/reducers/setting.ts @@ -1,13 +1,25 @@ -import actions from 'settings/actions'; +import * as actions from '../actions'; +import { +  JSONSettings, FormSettings, SettingSource, +} from '../../shared/SettingData'; -const defaultState = { -  source: '', -  json: '', -  form: null, +export interface State { +  source: SettingSource; +  json?: JSONSettings; +  form?: FormSettings; +  error: string; +} + +const defaultState: State = { +  source: SettingSource.JSON, +  json: JSONSettings.valueOf(''),    error: '',  }; -export default function reducer(state = defaultState, action = {}) { +export default function reducer( +  state = defaultState, +  action: actions.SettingAction, +): State {    switch (action.type) {    case actions.SETTING_SET_SETTINGS:      return { ...state, @@ -22,12 +34,12 @@ export default function reducer(state = defaultState, action = {}) {    case actions.SETTING_SWITCH_TO_FORM:      return { ...state,        error: '', -      source: 'form', +      source: SettingSource.Form,        form: action.form, };    case actions.SETTING_SWITCH_TO_JSON:      return { ...state,        error: '', -      source: 'json', +      source: SettingSource.JSON,        json: action.json, };    default:      return state; diff --git a/src/settings/storage.ts b/src/settings/storage.ts new file mode 100644 index 0000000..c0005b7 --- /dev/null +++ b/src/settings/storage.ts @@ -0,0 +1,15 @@ +import SettingData, { DefaultSettingData } from '../shared/SettingData'; + +export const load = async(): Promise<SettingData> => { +  let { settings } = await browser.storage.local.get('settings'); +  if (!settings) { +    return DefaultSettingData; +  } +  return SettingData.valueOf(settings as any); +}; + +export const save = (data: SettingData) => { +  return browser.storage.local.set({ +    settings: data.toJSON(), +  }); +}; | 
