You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
7.7 KiB
TypeScript
325 lines
7.7 KiB
TypeScript
import {
|
|
FieldKind,
|
|
Field as FieldModel,
|
|
FieldOptions as FieldOptionsModels,
|
|
} from "../../../common/schema.ts";
|
|
import { ValidationErrors } from "../../../common/validation.ts";
|
|
import { swel } from "../../deps/swel.ts";
|
|
import { Dispatch, OnChange } from "../../state/lib/store.ts";
|
|
import {
|
|
CmpPaintComponent,
|
|
ComponentModel,
|
|
Paint,
|
|
ShouldPaint,
|
|
} from "../component.ts";
|
|
import { TextInput } from "../lib/input/TextInput.ts";
|
|
import { Validated } from "../lib/input/Validated.ts";
|
|
import { Action, setFieldOptionProp, setFieldProp } from "./actions.ts";
|
|
import { RecyclerList } from "../lib/RecyclerList.ts";
|
|
import { Select } from "../lib/input/Select.ts";
|
|
import { NumberInput } from "../lib/input/NumberInput.ts";
|
|
import { Callback } from "../../utils.ts";
|
|
import { LabeledCheckbox } from "../lib/input/LabeledCheckbox.ts";
|
|
import { ShowHide, ShowHideModel } from "../lib/ShowHide.ts";
|
|
import { Labeled } from "../lib/Labeled.ts";
|
|
|
|
class ToggleField<
|
|
Child extends CmpPaintComponent<any>,
|
|
> extends CmpPaintComponent<ShowHideModel<ComponentModel<Child>>> {
|
|
private container: HTMLDivElement;
|
|
private toggle: LabeledCheckbox;
|
|
private field: ShowHide<Child>;
|
|
|
|
constructor(
|
|
public child: Child,
|
|
p: { label: string; onToggle: Callback<[boolean]> },
|
|
) {
|
|
super();
|
|
|
|
this.toggle = new LabeledCheckbox(p.label, p);
|
|
this.field = new ShowHide(child);
|
|
|
|
this.container = swel("div", { className: "toggle-field" }, [
|
|
this.toggle.el,
|
|
this.field.el,
|
|
]);
|
|
}
|
|
|
|
protected paint_: Paint<ShowHideModel<ComponentModel<Child>>> = (cur) => {
|
|
this.field.paint(cur);
|
|
this.toggle.paint(cur.show);
|
|
};
|
|
|
|
get el(): HTMLElement {
|
|
return this.container;
|
|
}
|
|
}
|
|
|
|
type ValidatedFieldOptions<Kind extends FieldKind> = {
|
|
kind: Kind;
|
|
options: FieldOptionsModels[Kind];
|
|
errors?: ValidationErrors<FieldOptionsModels[Kind]>;
|
|
};
|
|
|
|
class NumberFieldOptions extends CmpPaintComponent<
|
|
ValidatedFieldOptions<"number">
|
|
> {
|
|
private container: HTMLDivElement;
|
|
private minInput: Validated<ToggleField<NumberInput>>;
|
|
private maxInput: Validated<ToggleField<NumberInput>>;
|
|
|
|
constructor(fieldIndex: number, dispatch: Dispatch<Action>) {
|
|
super();
|
|
|
|
this.minInput = new Validated(
|
|
new ToggleField(
|
|
new NumberInput({
|
|
onInput: (n) => {
|
|
dispatch(setFieldOptionProp(fieldIndex, "min", n));
|
|
},
|
|
}),
|
|
{
|
|
label: "Min?",
|
|
onToggle: (b) => {
|
|
dispatch(setFieldOptionProp(fieldIndex, "min", b ? 0 : undefined));
|
|
},
|
|
},
|
|
),
|
|
);
|
|
|
|
this.maxInput = new Validated(
|
|
new ToggleField(
|
|
new NumberInput({
|
|
onInput: (n) => {
|
|
dispatch(setFieldOptionProp(fieldIndex, "max", n));
|
|
},
|
|
}),
|
|
{
|
|
label: "Max?",
|
|
onToggle: (b) => {
|
|
dispatch(setFieldOptionProp(fieldIndex, "max", b ? 0 : undefined));
|
|
},
|
|
},
|
|
),
|
|
);
|
|
|
|
this.container = swel("div", { className: "number-field-options" }, [
|
|
this.minInput.el,
|
|
this.maxInput.el,
|
|
]);
|
|
}
|
|
|
|
get el(): HTMLElement {
|
|
return this.container;
|
|
}
|
|
|
|
protected paint_: Paint<ValidatedFieldOptions<"number">> = (cur) => {
|
|
this.maxInput.paint({
|
|
model: {
|
|
show: cur.options.max != undefined,
|
|
model: cur.options.max,
|
|
},
|
|
error: cur.errors?.max?.[0],
|
|
});
|
|
|
|
this.minInput.paint({
|
|
model: {
|
|
show: cur.options.min != undefined,
|
|
model: cur.options.min,
|
|
},
|
|
error: cur.errors?.min?.[0],
|
|
});
|
|
|
|
if (cur.options.max) {
|
|
this.maxInput.child.child.setMin(cur.options.min);
|
|
}
|
|
|
|
if (cur.options.min) {
|
|
this.minInput.child.child.setMax(cur.options.max);
|
|
}
|
|
};
|
|
}
|
|
|
|
class StringFieldOptions extends CmpPaintComponent<
|
|
ValidatedFieldOptions<"string">
|
|
> {
|
|
private container: HTMLDivElement;
|
|
private displayAs: Select<FieldOptionsModels["string"]["displayAs"]>;
|
|
private minLength: Validated<Labeled<NumberInput>>;
|
|
private maxLength: Validated<ToggleField<NumberInput>>;
|
|
|
|
constructor(fieldIndex: number, dispatch: Dispatch<Action>) {
|
|
super();
|
|
|
|
this.displayAs = new Select(
|
|
{
|
|
input: "input",
|
|
textarea: "textarea",
|
|
},
|
|
(v) => dispatch(setFieldOptionProp(fieldIndex, "displayAs", v)),
|
|
);
|
|
|
|
this.minLength = new Validated(
|
|
new Labeled(
|
|
"Min length",
|
|
new NumberInput({
|
|
onInput: (v) =>
|
|
dispatch(setFieldOptionProp(fieldIndex, "minLength", v)),
|
|
}),
|
|
),
|
|
);
|
|
this.minLength.child.child.setMin(0);
|
|
|
|
this.maxLength = new Validated(
|
|
new ToggleField(
|
|
new NumberInput({
|
|
onInput: (n) =>
|
|
dispatch(setFieldOptionProp(fieldIndex, "maxLength", n)),
|
|
}),
|
|
{
|
|
label: "Max length?",
|
|
onToggle: (b) =>
|
|
dispatch(
|
|
setFieldOptionProp(fieldIndex, "maxLength", b ? 0 : undefined),
|
|
),
|
|
},
|
|
),
|
|
);
|
|
|
|
this.container = swel("div", { className: "string-field-options" }, [
|
|
this.displayAs.el,
|
|
this.minLength.el,
|
|
this.maxLength.el,
|
|
]);
|
|
}
|
|
|
|
get el(): HTMLElement {
|
|
return this.container;
|
|
}
|
|
|
|
protected paint_: Paint<ValidatedFieldOptions<"string">> = (cur) => {
|
|
this.displayAs.paint(cur.options.displayAs);
|
|
this.minLength.paint({
|
|
model: cur.options.minLength,
|
|
error: cur.errors?.minLength?.[0],
|
|
});
|
|
this.minLength.child.child.setMax(cur.options.maxLength);
|
|
|
|
this.maxLength.paint({
|
|
model: {
|
|
show: cur.options.maxLength != undefined,
|
|
model: cur.options.maxLength,
|
|
},
|
|
error: cur.errors?.maxLength?.[0],
|
|
});
|
|
this.maxLength.child.child.setMin(cur.options.minLength);
|
|
};
|
|
}
|
|
|
|
type FieldOptionComponentRegistry = {
|
|
[K in FieldKind]: CmpPaintComponent<ValidatedFieldOptions<K>>;
|
|
};
|
|
|
|
class FieldOptions extends CmpPaintComponent<ValidatedFieldOptions<FieldKind>> {
|
|
private fieldOptionComponents: FieldOptionComponentRegistry;
|
|
private container = swel("div", { className: "field-options" });
|
|
|
|
constructor(fieldIndex: number, dispatch: Dispatch<Action>) {
|
|
super();
|
|
|
|
this.fieldOptionComponents = {
|
|
number: new NumberFieldOptions(fieldIndex, dispatch),
|
|
string: new StringFieldOptions(fieldIndex, dispatch),
|
|
};
|
|
}
|
|
|
|
get el(): HTMLElement {
|
|
return this.container;
|
|
}
|
|
|
|
protected paint_: Paint<ValidatedFieldOptions<FieldKind>> = (cur) => {
|
|
const view = this.fieldOptionComponents[cur.kind];
|
|
if (!this.previousModel || this.previousModel.kind != cur.kind) {
|
|
this.container.replaceChildren(view.el);
|
|
}
|
|
|
|
view.paint(cur as any);
|
|
};
|
|
}
|
|
|
|
export type ValidatedField = {
|
|
field: FieldModel;
|
|
errors?: ValidationErrors<FieldModel>;
|
|
};
|
|
|
|
class Field extends CmpPaintComponent<ValidatedField> {
|
|
private container: HTMLDivElement;
|
|
private nameInput: Validated<TextInput>;
|
|
private kindSelect: Select<FieldKind>;
|
|
private fieldOptions: FieldOptions;
|
|
|
|
constructor(fieldIndex: number, dispatch: Dispatch<Action>) {
|
|
super();
|
|
|
|
this.nameInput = new Validated(
|
|
new TextInput({
|
|
onInput: (s) => dispatch(setFieldProp(fieldIndex, "name", s)),
|
|
}),
|
|
);
|
|
|
|
this.kindSelect = new Select(
|
|
{ string: "string", number: "number" },
|
|
(v) => {
|
|
dispatch(setFieldProp(fieldIndex, "kind", v));
|
|
},
|
|
);
|
|
|
|
this.fieldOptions = new FieldOptions(fieldIndex, dispatch);
|
|
|
|
this.container = swel("div", { className: "field" }, [
|
|
this.nameInput.el,
|
|
this.kindSelect.el,
|
|
this.fieldOptions.el,
|
|
]);
|
|
}
|
|
|
|
get el() {
|
|
return this.container;
|
|
}
|
|
|
|
paint_: Paint<ValidatedField> = (cur) => {
|
|
this.nameInput.paint({
|
|
model: cur.field.name,
|
|
error: cur.errors?.["name"]?.[0],
|
|
});
|
|
|
|
this.kindSelect.paint(cur.field.kind);
|
|
|
|
this.fieldOptions.paint({
|
|
kind: cur.field.kind,
|
|
options: cur.field.options,
|
|
errors: cur.errors?.options,
|
|
});
|
|
};
|
|
}
|
|
|
|
export class Fields extends CmpPaintComponent<ValidatedField[]> {
|
|
private container: HTMLDivElement;
|
|
private list: RecyclerList<ValidatedField>;
|
|
|
|
constructor(dispatch: Dispatch<Action>) {
|
|
super();
|
|
|
|
this.list = new RecyclerList((i) => new Field(i, dispatch));
|
|
this.container = swel("div", { className: "fields" }, [this.list.el]);
|
|
}
|
|
|
|
get el() {
|
|
return this.container;
|
|
}
|
|
|
|
paint_: Paint<ValidatedField[]> = (cur) => {
|
|
this.list.paint(cur);
|
|
};
|
|
}
|