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