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.

223 lines
4.7 KiB
TypeScript

import { swel } from "./deps/swel.ts";
import {
type MaybePersistedSchema,
validateSchema,
FieldShape,
} from "../common/schema.ts";
import { Client } from "./main.ts";
import { Const } from "../common/utils.ts";
import { Update } from "./utils.ts";
const WithValidationError = (p: { c: HTMLElement }) => {
const errorMsg = swel("div", { className: "error-message" }, "Space");
const wrapper = swel("div", { className: "with-validation-error" }, [
p.c,
errorMsg,
]);
return {
el: wrapper,
paint: (e?: string) => {
if (e) {
errorMsg.innerText = e;
wrapper.classList.add("error");
} else {
wrapper.classList.remove("error");
}
},
};
};
const FieldElement = () => {
const nameInput = swel("input", {
className: "name",
});
const nameInputWithValidationError = WithValidationError({ c: nameInput });
const deleteButton = swel("button", { className: "delete" }, "Delete");
const el = swel("div", { className: "field-element" }, [
nameInputWithValidationError.el,
deleteButton,
]);
const paint = (p: {
field: Const<FieldShape>;
errors: Const<{ [K in keyof FieldShape]?: string[] }>;
updateField: Update<FieldShape>;
deleteField: () => void;
}) => {
nameInput.value = p.field.name;
nameInput.oninput = () => {
p.updateField((f) => (f.name = nameInput.value));
};
deleteButton.onclick = p.deleteField;
nameInputWithValidationError.paint(p.errors.name?.[0]);
};
return {
el,
paint,
};
};
const FieldElements = (p: { updateFields: Update<FieldShape[]> }) => {
const elements: ReturnType<typeof FieldElement>[] = [];
const { updateFields } = p;
let prevLength = 0;
const paint = (p: {
fields: Const<FieldShape[]>;
errors: Const<ReturnType<typeof validateSchema>>;
}) => {
const lenDiff = p.fields.length - prevLength;
if (lenDiff > 0) {
for (let i = prevLength; i < p.fields.length; ++i) {
const fe = FieldElement();
elements[i] = fe;
el.appendChild(fe.el);
}
} else if (lenDiff < 0) {
for (let i = prevLength - 1; i >= p.fields.length; --i) {
el.removeChild(elements[i].el);
delete elements[i];
}
}
for (let i = 0; i < p.fields.length; ++i) {
const field = p.fields[i];
const element = elements[i];
const updateField: Update<FieldShape> = (fn) => {
updateFields((fs) => {
fn?.(fs[i]);
});
};
const deleteField = () => {
updateFields((fs) => {
fs.splice(i, 1);
});
};
element.paint({
field,
updateField,
deleteField,
errors: {
name: p.errors[`fields.${i}.name`],
kind: p.errors[`fields.${i}.kind`],
required: p.errors[`fields.${i}.required`],
},
});
}
prevLength = p.fields.length;
};
const el = swel("div", { className: "field-elements" });
return {
el,
paint,
};
};
export const SchemaEditor = (p: {
updateSchema: Update<MaybePersistedSchema>;
}) => {
const nameInput = swel("input", {
className: "name",
on: {
input: () => {
p.updateSchema((sc) => {
sc.name = nameInput.value;
});
},
},
});
const nameInputWithValidationError = WithValidationError({ c: nameInput });
const fieldElements = FieldElements({
updateFields: (fn) => p.updateSchema((sc) => fn?.(sc.fields)),
});
const addFieldButton = swel(
"button",
{
className: "add-field",
on: {
click: () => {
p.updateSchema((sc) => {
sc.fields.push({
kind: "string",
name: "",
required: true,
});
});
},
},
},
"Add field",
);
const saveButton = swel(
"button",
{
className: "save",
on: { click: () => console.log("save clicked") },
},
"Save",
);
const controls = swel("div", { className: "controls" }, [
addFieldButton,
saveButton,
]);
const el = swel("div", { className: "schema-editor" }, [
nameInputWithValidationError.el,
fieldElements.el,
controls,
]);
const paint = (p: {
schema: Const<MaybePersistedSchema>;
errors: Const<ReturnType<typeof validateSchema>>;
}) => {
nameInput.value = p.schema.name;
saveButton.disabled = Object.keys(p.errors).length > 0;
nameInputWithValidationError.paint(p.errors.name?.[0]);
fieldElements.paint({ fields: p.schema.fields, errors: p.errors });
};
return { paint, el };
};
export const SchemaEditorView = (p: {
maybeSchema?: MaybePersistedSchema;
client: Client;
}) => {
const schema =
p.maybeSchema ??
({
fields: [],
name: "New Schema",
} satisfies MaybePersistedSchema);
const errors = validateSchema(schema);
const updateSchema: Update<MaybePersistedSchema> = (fn) => {
fn?.(schema);
const errors = validateSchema(schema);
paint({ schema, errors });
};
const { paint, el } = SchemaEditor({
updateSchema,
});
paint({ schema, errors });
return el;
};