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

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);
};
}