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 Stream from 'mithril/stream';
|
||||
import app from 'flarum/admin/app';
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
import withAttr from 'flarum/common/utils/withAttr';
|
||||
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';
|
||||
|
||||
// forgive me, but this interface is just what its name implies
|
||||
interface SelectOrInputSettingComponentOptionsWithoutType extends CommonSettingsItemOptions {
|
||||
options: { [value: string]: Mithril.Children };
|
||||
checkboxDescription: string;
|
||||
inputDescription: string;
|
||||
default: string;
|
||||
}
|
||||
|
||||
|
|
@ -16,53 +17,76 @@ export interface SelectOrInputSettingComponentOptions extends SelectOrInputSetti
|
|||
type: 'select-or-input';
|
||||
}
|
||||
|
||||
interface StreamWithMark<T> extends Stream<T> {
|
||||
mark?: true;
|
||||
interface StreamWithUpstream<T> extends Stream<T> {
|
||||
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) {
|
||||
// do this cast because function signature is restricted
|
||||
const entry = _entry as SelectOrInputSettingComponentOptionsWithoutType;
|
||||
const [inputId, helpTextId] = [generateElementId(), generateElementId()];
|
||||
const [selectId, inputId, helpTextId] = [generateElementId(), generateElementId(), generateElementId()];
|
||||
const { setting, help, label, ...componentAttrs } = entry;
|
||||
const { default: defaultValue, options, checkboxDescription, ...otherAttrs } = componentAttrs;
|
||||
const { default: defaultValue, options, inputDescription, className, ...otherAttrs } = componentAttrs;
|
||||
|
||||
let settingElement: Mithril.Children;
|
||||
// Trick: add a marker property to the AdminPage's setting stream,
|
||||
// which contructs and destructs with AdminPage so that default is
|
||||
// only loaded when the page is created.
|
||||
// Default should only be loaded once in a page because when checkbox
|
||||
// is checked, SelectOrInput will set its value to empty string so that
|
||||
// it becomes an empty input. If default is loaded every time when value
|
||||
// is falsy, SelectOrInput will not work properly.
|
||||
const settingStream = this.settings[setting] as StreamWithMark<string>;
|
||||
let value = settingStream();
|
||||
if (!settingStream.mark) {
|
||||
settingStream.mark = true;
|
||||
if (!value) {
|
||||
app.data.settings[setting] = defaultValue;
|
||||
settingStream(defaultValue);
|
||||
value = settingStream();
|
||||
let settingStream = this.settings[setting] as StreamWithUpstream<string>;
|
||||
|
||||
if (!settingStream.upstreams) {
|
||||
let value = settingStream() || defaultValue;
|
||||
let selectStream = Stream();
|
||||
let inputStream = Stream();
|
||||
let upstreams = [selectStream, inputStream]
|
||||
settingStream = Stream.combine(mergeSelectOrInput, upstreams);
|
||||
settingStream.upstreams = upstreams;
|
||||
let useSelect = Object.keys(options).indexOf(value) > -1;
|
||||
if (useSelect) {
|
||||
selectStream(value);
|
||||
inputStream('');
|
||||
} else {
|
||||
selectStream(defaultValue);
|
||||
inputStream(value);
|
||||
}
|
||||
this.settings[setting] = settingStream;
|
||||
}
|
||||
|
||||
settingElement = (
|
||||
<SelectOrInput
|
||||
id={inputId}
|
||||
let [selectStream, inputStream] = settingStream.upstreams!;
|
||||
|
||||
let selectElement = (
|
||||
<Select
|
||||
className={className}
|
||||
id={selectId}
|
||||
aria-describedby={helpTextId}
|
||||
value={value}
|
||||
value={selectStream()}
|
||||
options={options}
|
||||
onchange={settingStream}
|
||||
checkboxDescription={checkboxDescription}
|
||||
onchange={selectStream}
|
||||
{...otherAttrs}
|
||||
/>
|
||||
);
|
||||
|
||||
let inputElement = (
|
||||
<input
|
||||
className={classList('FormControl', className)}
|
||||
id={inputId}
|
||||
type='text'
|
||||
onchange={withAttr('value', inputStream)}
|
||||
value={inputStream()}
|
||||
{...otherAttrs}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="Form-group">
|
||||
{label && <label for={inputId}>{label}</label>}
|
||||
{label && <label for={selectId}>{label}</label>}
|
||||
<div id={helpTextId} className="helpText">{help}</div>
|
||||
{settingElement}
|
||||
{selectElement}
|
||||
{inputDescription && <label style={{'padding-top': '10px'}} for={inputId}>{inputDescription}</label>}
|
||||
{inputElement}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ app.initializers.add('blomstra-search', () => {
|
|||
help: app.translator.trans('blomstra-search.admin.analyzer.help'),
|
||||
type: 'select-or-input',
|
||||
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',
|
||||
})
|
||||
.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