change SelectOrInput to 2 fields
This commit is contained in:
parent
e94a7b1058
commit
f1101a18a2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,14 +1,15 @@
|
||||||
import type Mithril from 'mithril';
|
import type Mithril from 'mithril';
|
||||||
import type Stream from 'mithril/stream';
|
import Stream from 'flarum/common/utils/Stream';
|
||||||
import app from 'flarum/admin/app';
|
import classList from 'flarum/common/utils/classList';
|
||||||
|
import withAttr from 'flarum/common/utils/withAttr';
|
||||||
import AdminPage, { CommonSettingsItemOptions } from 'flarum/admin/components/AdminPage';
|
import AdminPage, { CommonSettingsItemOptions } from 'flarum/admin/components/AdminPage';
|
||||||
import SelectOrInput from "../../common/Components/SelectOrInput";
|
import Select from 'flarum/common/components/Select';
|
||||||
import generateElementId from 'flarum/admin/utils/generateElementId';
|
import generateElementId from 'flarum/admin/utils/generateElementId';
|
||||||
|
|
||||||
// forgive me, but this interface is just what its name implies
|
// forgive me, but this interface is just what its name implies
|
||||||
interface SelectOrInputSettingComponentOptionsWithoutType extends CommonSettingsItemOptions {
|
interface SelectOrInputSettingComponentOptionsWithoutType extends CommonSettingsItemOptions {
|
||||||
options: { [value: string]: Mithril.Children };
|
options: { [value: string]: Mithril.Children };
|
||||||
checkboxDescription: string;
|
inputDescription: string;
|
||||||
default: string;
|
default: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,53 +17,76 @@ export interface SelectOrInputSettingComponentOptions extends SelectOrInputSetti
|
||||||
type: 'select-or-input';
|
type: 'select-or-input';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StreamWithMark<T> extends Stream<T> {
|
interface StreamWithUpstream<T> extends Stream<T> {
|
||||||
mark?: true;
|
upstreams?: Array<Stream<unknown>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeSelectOrInput(
|
||||||
|
selectStream: Stream<string>, inputStream: Stream<string>,
|
||||||
|
changed: Array<Stream<unknown>>
|
||||||
|
) {
|
||||||
|
return inputStream() || selectStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (this: AdminPage, _entry: CommonSettingsItemOptions) {
|
export default function (this: AdminPage, _entry: CommonSettingsItemOptions) {
|
||||||
// do this cast because function signature is restricted
|
// do this cast because function signature is restricted
|
||||||
const entry = _entry as SelectOrInputSettingComponentOptionsWithoutType;
|
const entry = _entry as SelectOrInputSettingComponentOptionsWithoutType;
|
||||||
const [inputId, helpTextId] = [generateElementId(), generateElementId()];
|
const [selectId, inputId, helpTextId] = [generateElementId(), generateElementId(), generateElementId()];
|
||||||
const { setting, help, label, ...componentAttrs } = entry;
|
const { setting, help, label, ...componentAttrs } = entry;
|
||||||
const { default: defaultValue, options, checkboxDescription, ...otherAttrs } = componentAttrs;
|
const { default: defaultValue, options, inputDescription, className, ...otherAttrs } = componentAttrs;
|
||||||
|
|
||||||
|
let settingStream = this.settings[setting] as StreamWithUpstream<string>;
|
||||||
|
|
||||||
let settingElement: Mithril.Children;
|
if (!settingStream.upstreams) {
|
||||||
// Trick: add a marker property to the AdminPage's setting stream,
|
let value = settingStream() || defaultValue;
|
||||||
// which contructs and destructs with AdminPage so that default is
|
let selectStream = Stream();
|
||||||
// only loaded when the page is created.
|
let inputStream = Stream();
|
||||||
// Default should only be loaded once in a page because when checkbox
|
let upstreams = [selectStream, inputStream]
|
||||||
// is checked, SelectOrInput will set its value to empty string so that
|
settingStream = Stream.combine(mergeSelectOrInput, upstreams);
|
||||||
// it becomes an empty input. If default is loaded every time when value
|
settingStream.upstreams = upstreams;
|
||||||
// is falsy, SelectOrInput will not work properly.
|
let useSelect = Object.keys(options).indexOf(value) > -1;
|
||||||
const settingStream = this.settings[setting] as StreamWithMark<string>;
|
if (useSelect) {
|
||||||
let value = settingStream();
|
selectStream(value);
|
||||||
if (!settingStream.mark) {
|
inputStream('');
|
||||||
settingStream.mark = true;
|
} else {
|
||||||
if (!value) {
|
selectStream(defaultValue);
|
||||||
app.data.settings[setting] = defaultValue;
|
inputStream(value);
|
||||||
settingStream(defaultValue);
|
|
||||||
value = settingStream();
|
|
||||||
}
|
}
|
||||||
|
this.settings[setting] = settingStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
settingElement = (
|
let [selectStream, inputStream] = settingStream.upstreams!;
|
||||||
<SelectOrInput
|
|
||||||
id={inputId}
|
let selectElement = (
|
||||||
|
<Select
|
||||||
|
className={className}
|
||||||
|
id={selectId}
|
||||||
aria-describedby={helpTextId}
|
aria-describedby={helpTextId}
|
||||||
value={value}
|
value={selectStream()}
|
||||||
options={options}
|
options={options}
|
||||||
onchange={settingStream}
|
onchange={selectStream}
|
||||||
checkboxDescription={checkboxDescription}
|
{...otherAttrs}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
let inputElement = (
|
||||||
|
<input
|
||||||
|
className={classList('FormControl', className)}
|
||||||
|
id={inputId}
|
||||||
|
type='text'
|
||||||
|
onchange={withAttr('value', inputStream)}
|
||||||
|
value={inputStream()}
|
||||||
{...otherAttrs}
|
{...otherAttrs}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Form-group">
|
<div className="Form-group">
|
||||||
{label && <label for={inputId}>{label}</label>}
|
{label && <label for={selectId}>{label}</label>}
|
||||||
<div id={helpTextId} className="helpText">{help}</div>
|
<div id={helpTextId} className="helpText">{help}</div>
|
||||||
{settingElement}
|
{selectElement}
|
||||||
|
{inputDescription && <label style={{'padding-top': '10px'}} for={inputId}>{inputDescription}</label>}
|
||||||
|
{inputElement}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ app.initializers.add('blomstra-search', () => {
|
||||||
help: app.translator.trans('blomstra-search.admin.analyzer.help'),
|
help: app.translator.trans('blomstra-search.admin.analyzer.help'),
|
||||||
type: 'select-or-input',
|
type: 'select-or-input',
|
||||||
options: Object.fromEntries(languages.entries()),
|
options: Object.fromEntries(languages.entries()),
|
||||||
checkboxDescription: app.translator.trans('blomstra-search.admin.analyzer.custom'),
|
inputDescription: app.translator.trans('blomstra-search.admin.analyzer.custom'),
|
||||||
default: 'english',
|
default: 'english',
|
||||||
})
|
})
|
||||||
.registerSetting({
|
.registerSetting({
|
||||||
|
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
import Mithril from 'mithril';
|
|
||||||
import classList from 'flarum/common/utils/classList';
|
|
||||||
import Component, { ComponentAttrs } from 'flarum/common/Component';
|
|
||||||
import Select from 'flarum/common/components/Select';
|
|
||||||
import Checkbox, { ICheckboxAttrs } from 'flarum/common/components/Checkbox';
|
|
||||||
|
|
||||||
type visibility = 'visible' | 'hidden';
|
|
||||||
|
|
||||||
interface ISelectAttrs extends ComponentAttrs {
|
|
||||||
options: { [k: string]: Mithril.Children };
|
|
||||||
onchange: (value: string) => void;
|
|
||||||
value?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
wrapperAttrs?: ComponentAttrs;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ISubSelectAttrs extends ComponentAttrs {
|
|
||||||
wrapperAttrs?: ComponentAttrs;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISelectOrInputAttrs<
|
|
||||||
SelectAttrs extends ISubSelectAttrs = ISubSelectAttrs
|
|
||||||
> extends ComponentAttrs {
|
|
||||||
onchange: (value: string) => void;
|
|
||||||
options: { [k: string]: Mithril.Children };
|
|
||||||
value?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
checkboxDescription?: string;
|
|
||||||
selectAttrs?: SelectAttrs;
|
|
||||||
rawTextInputAttrs?: ComponentAttrs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// when value is `undefined`, choose the first item of select
|
|
||||||
// when value is in select, use value as value of select
|
|
||||||
// otherwise, use value as value of input
|
|
||||||
export default class SelectOrInput<CustomAttrs extends ISelectOrInputAttrs> extends Component<CustomAttrs> {
|
|
||||||
private onCheckboxChange(checked: boolean, component: Checkbox) {
|
|
||||||
this.attrs.value = checked ? '' : undefined;
|
|
||||||
this.attrs.onchange(this.attrs.value!);
|
|
||||||
m.redraw();
|
|
||||||
}
|
|
||||||
private onSelectChange(value: string) {
|
|
||||||
this.attrs.value = value;
|
|
||||||
this.attrs.onchange(value);
|
|
||||||
}
|
|
||||||
private onInputChange(event: Event) {
|
|
||||||
const value = (event.target as HTMLInputElement).value;
|
|
||||||
this.attrs.value = value;
|
|
||||||
this.attrs.onchange(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
view(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
|
||||||
const {
|
|
||||||
class: _class,
|
|
||||||
className,
|
|
||||||
onchange,
|
|
||||||
options,
|
|
||||||
value: _value,
|
|
||||||
disabled,
|
|
||||||
selectAttrs,
|
|
||||||
rawTextInputAttrs,
|
|
||||||
...domAttrs
|
|
||||||
} = this.attrs;
|
|
||||||
|
|
||||||
var useinput: boolean; var value: string;
|
|
||||||
const selectOptionValues = Object.keys(options);
|
|
||||||
|
|
||||||
if (typeof _value === 'undefined') {
|
|
||||||
value = selectOptionValues[0];
|
|
||||||
this.attrs.value = value;
|
|
||||||
useinput = false;
|
|
||||||
} else {
|
|
||||||
value = _value;
|
|
||||||
useinput = !(selectOptionValues.indexOf(value!) > -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: inputClass,
|
|
||||||
className: inputClassName,
|
|
||||||
...otherInputAttrs
|
|
||||||
} = rawTextInputAttrs ?? {};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classList('SelectOrInput', className, _class)}
|
|
||||||
{...domAttrs}
|
|
||||||
>
|
|
||||||
{this.attrs.checkboxDescription ?? ''}
|
|
||||||
<div style='display: inline-block;'>
|
|
||||||
<Checkbox
|
|
||||||
state={useinput}
|
|
||||||
onchange={this.onCheckboxChange.bind(this)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style='display: inline-block;'>
|
|
||||||
{!useinput
|
|
||||||
? <Select
|
|
||||||
options={options}
|
|
||||||
onchange={this.onSelectChange.bind(this)}
|
|
||||||
disabled={disabled}
|
|
||||||
value={value}
|
|
||||||
{...selectAttrs}
|
|
||||||
/>
|
|
||||||
: <input
|
|
||||||
className={classList('FormControl', inputClassName, inputClass)}
|
|
||||||
type='text'
|
|
||||||
onchange={this.onInputChange.bind(this)}
|
|
||||||
disabled={disabled}
|
|
||||||
value={value}
|
|
||||||
{...otherInputAttrs}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue