@kform/scaffolder
Version:
Scaffolding utilities for KForm projects.
1,412 lines • 60.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const jsxRuntime = require("react/jsx-runtime");
const core = require("@dnd-kit/core");
const React = require("react");
const zustand = require("zustand");
const middleware = require("zustand/middleware");
const browserFsAccess = require("browser-fs-access");
const JSZip = require("jszip");
const changeCase = require("change-case");
const ejs = require("ejs");
function _interopNamespaceDefault(e) {
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
if (e) {
for (const k in e) {
if (k !== "default") {
const d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: () => e[k]
});
}
}
}
n.default = e;
return Object.freeze(n);
}
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
function createSchematic(schematic) {
return {
id: self.crypto.randomUUID(),
kind: "",
...schematic
};
}
function moveSchematic(schematic, moveId, parentId, sortBefore) {
const { schematic: schematicWithoutFrom, removed: fromSchematic } = remove(
schematic,
moveId
);
return add(schematicWithoutFrom, fromSchematic, parentId, sortBefore);
}
function remove(schematic, id) {
if (schematic.id === id) {
return { schematic: null, removed: schematic };
}
if (!schematic.children) {
return { schematic, removed: null };
}
const newChildren = [];
let removed = null;
for (const child of schematic.children) {
const { schematic: newChild, removed: removedChild } = remove(child, id);
if (newChild !== null) {
newChildren.push(newChild);
}
if (removedChild !== null) {
removed = removedChild;
}
}
return { schematic: { ...schematic, children: newChildren }, removed };
}
function add(schematic, toAdd, parentId, sortBefore) {
if (schematic.id === parentId) {
const oldChildren = schematic.children ?? [];
const index = sortBefore === null ? oldChildren.length : oldChildren.findIndex((c) => c.id === sortBefore);
return {
...schematic,
children: [
...oldChildren.slice(0, index),
toAdd,
...oldChildren.slice(index)
]
};
}
if (!schematic.children) {
return schematic;
}
return {
...schematic,
children: schematic.children.map(
(child) => add(child, toAdd, parentId, sortBefore)
)
};
}
const SchematicBuilderContext = React__namespace.createContext(null);
function useSchematicBuilderContext() {
const context = React__namespace.useContext(SchematicBuilderContext);
if (context === null) {
throw new Error("Schematic builder not in context.");
}
return context;
}
function LoadSchematic() {
const { name, setSchematic, latestSchematicFileHandle } = useSchematicBuilderContext();
return /* @__PURE__ */ jsxRuntime.jsx(
"button",
{
className: "builder-icon-button builder-action builder-load-schematic",
type: "button",
onClick: async () => {
try {
const file = await browserFsAccess.fileOpen({
extensions: [".json"],
mimeTypes: ["application/json"],
description: "Form schematic",
id: name
});
if (file.handle) {
latestSchematicFileHandle.current = file.handle;
}
const content = JSON.parse(await file.text());
setSchematic(() => content);
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") {
return;
}
alert(`Unable to load schematic: ${err?.toString()}`);
}
},
title: "Load schematic",
"aria-label": "Load schematic",
children: "📂"
}
);
}
function SaveSchematic() {
const { name, getRootSchematic, latestSchematicFileHandle } = useSchematicBuilderContext();
return /* @__PURE__ */ jsxRuntime.jsx(
"button",
{
className: "builder-icon-button builder-action builder-save-schematic",
type: "button",
onClick: async () => {
try {
const schematic = getRootSchematic();
latestSchematicFileHandle.current = await browserFsAccess.fileSave(
new Blob([JSON.stringify(schematic, null, 2)]),
{
fileName: `${schematic.name}-schematic.json`,
extensions: [".json"],
mimeTypes: ["application/json"],
description: "Form schematic",
id: name
},
latestSchematicFileHandle.current
);
if (browserFsAccess.supported) {
alert(
`Schematic saved successfully as “${latestSchematicFileHandle.current.name}”.`
);
}
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") {
return;
}
alert(`Unable to save schematic: ${err?.toString()}`);
}
},
title: "Save schematic",
"aria-label": "Save schematic",
children: "💾"
}
);
}
function configScaffolder(scaffolder, data) {
return (schematic, originalData) => scaffolder(schematic, { ...originalData, ...data });
}
function join(s1, s2, separator) {
return !s1 || !s2 ? s1 || s2 || "" : `${s1}${s1.endsWith(separator) ? "" : separator}${s2}`;
}
function joinPaths(path1, path2) {
return join(path1, path2, "/");
}
function joinPackages(pkg1, pkg2) {
return join(pkg1, pkg2, ".");
}
function scaffold(schematic, scaffolders, options) {
const files = /* @__PURE__ */ new Map();
const data = {
schematicKinds: options.schematicKinds,
rootSchematic: schematic,
files,
currentPath: options.basePath ?? "/",
currentPackage: joinPackages(options.basePackage, schematic.packageSuffix),
currentDir: options.baseDir
};
for (const scaffolder of scaffolders) {
scaffolder(schematic, data);
}
return Array.from(files).map(([path, file]) => ({
path,
content: file.getContent(),
base64: file.base64,
binary: file.binary,
executable: file.executable
}));
}
function useScaffolder() {
const {
basePath,
basePackage,
baseDir,
schematicKinds,
scaffolders,
scaffoldingData,
getRootSchematic
} = useSchematicBuilderContext();
return React__namespace.useCallback(() => {
const schematic = getRootSchematic();
let actualScaffolders = typeof scaffolders === "function" ? scaffolders(schematic) : scaffolders;
const actualScaffoldingData = typeof scaffoldingData === "function" ? scaffoldingData(schematic) : scaffoldingData;
if (actualScaffoldingData) {
actualScaffolders = actualScaffolders.map(
(scaffolder) => configScaffolder(scaffolder, actualScaffoldingData)
);
}
return scaffold(schematic, actualScaffolders, {
schematicKinds,
basePath,
basePackage,
baseDir: typeof baseDir === "function" ? baseDir(schematic) : baseDir
});
}, [
baseDir,
basePackage,
basePath,
getRootSchematic,
scaffolders,
scaffoldingData,
schematicKinds
]);
}
let latestZipFileHandle = null;
function ScaffoldToZip() {
const { getRootSchematic, name } = useSchematicBuilderContext();
const scaffold2 = useScaffolder();
return /* @__PURE__ */ jsxRuntime.jsx(
"button",
{
className: "builder-input builder-action builder-scaffold-zip",
type: "submit",
onClick: async (evt) => {
evt.preventDefault();
try {
const schematic = getRootSchematic();
const files = scaffold2();
const zip = new JSZip();
for (const { path, content, base64, binary, executable } of files) {
zip.file(path, content, {
base64,
binary,
unixPermissions: executable ? "755" : void 0
});
}
latestZipFileHandle = await browserFsAccess.fileSave(
zip.generateAsync({ platform: "UNIX", type: "blob" }),
{
fileName: `${schematic.name}.zip`,
extensions: [".zip"],
mimeTypes: ["application/zip"],
description: "Form scaffolding",
id: name
},
latestZipFileHandle
);
if (browserFsAccess.supported) {
alert(
`Project scaffolded successfully in “${latestZipFileHandle.name}”.`
);
}
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") {
return;
}
alert(`Unable to scaffold project: ${err?.toString()}`);
}
},
children: "Scaffold 🏗️"
}
);
}
function useConfig(configName, defaultValue) {
const { useSchematic, setSchematic } = useSchematicBuilderContext();
const configValue = useSchematic(
(schematic) => schematic.config?.[configName] ?? defaultValue
);
const setConfigValue = React__namespace.useCallback(
(configValue2) => setSchematic((schematic) => ({
config: {
...schematic.config,
[configName]: typeof configValue2 === "function" ? configValue2(schematic.config?.[configName]) : configValue2
}
})),
[configName, setSchematic]
);
return React__namespace.useMemo(
() => [configValue, setConfigValue],
[configValue, setConfigValue]
);
}
function UseFileBase64SerializerConfig({
disabled
}) {
const [config, setConfig] = useConfig("useFileBase64Serializer", false);
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "builder-config-field", children: /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
/* @__PURE__ */ jsxRuntime.jsx(
"input",
{
type: "checkbox",
checked: config,
onChange: (evt) => setConfig(evt.target.checked),
disabled
}
),
"Use ",
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "File.Base64Serializer" })
] }) });
}
function UseTableValuesSerializerConfig({
disabled
}) {
const [config, setConfig] = useConfig("useTableValuesSerializer", false);
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "builder-config-field", children: /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
/* @__PURE__ */ jsxRuntime.jsx(
"input",
{
type: "checkbox",
checked: config,
onChange: (evt) => setConfig(evt.target.checked),
disabled
}
),
"Use ",
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "Table.ValuesSerializer" })
] }) });
}
const DEFAULT_IMPORTS = [
/^kotlin\.[^.]+$/,
/^kotlin\.annotation\.[^.]+$/,
/^kotlin\.collections\.[^.]+$/,
/^kotlin\.comparisons\.[^.]+$/,
/^kotlin\.io\.[^.]+$/,
/^kotlin\.ranges\.[^.]+$/,
/^kotlin\.sequences\.[^.]+$/,
/^kotlin\.text\.[^.]+$/
];
function isDefaultKtImport(qualifiedName) {
return DEFAULT_IMPORTS.some((i) => i.test(qualifiedName));
}
function ktFile(data) {
return {
package: data.currentPackage ?? "",
imports: /* @__PURE__ */ new Set(),
declarations: [],
getContent: ktFileContent
};
}
function ktFileContent() {
return [
// Package
this.package && `package ${this.package}`,
// Imports
Array.from(this.imports).filter((i) => !isDefaultKtImport(i)).sort().map((i) => `import ${i}`).join("\n"),
// Declarations
...this.declarations
].filter((s) => s).join("\n\n") + "\n";
}
function simpleKtName(qualifiedName) {
return qualifiedName.substring(qualifiedName.lastIndexOf(".") + 1);
}
const JS_EXPORT = "kotlin.js.JsExport";
const SERIALIZABLE = "kotlinx.serialization.Serializable";
function annotate(annotations, code2) {
if (!annotations || annotations.length === 0) {
return code2;
}
return [
...typeof annotations === "string" ? [annotations] : annotations,
code2
].join("\n");
}
function jsExport(code2, data) {
data.currentFile.imports.add(JS_EXPORT);
return annotate(`@${simpleKtName(JS_EXPORT)}`, code2);
}
function serializable(code2, data) {
data.currentFile.imports.add(SERIALIZABLE);
return annotate(`@${simpleKtName(SERIALIZABLE)}`, code2);
}
function scaffoldModels(schematic, data) {
const fileName = joinPaths(data.currentDir, `${schematic.name}.kt`);
let file = data.files.get(fileName);
if (!file) {
data.files.set(fileName, file = ktFile(data));
}
scaffoldModelDeclaration(schematic, { ...data, currentFile: file });
}
function scaffoldModelDeclaration(schematic, data) {
const schematicKind = data.schematicKinds.get(schematic.kind);
if (!schematicKind) {
throw new Error(`Unknown schematic kind ${schematic.kind}`);
}
const nDecls = data.currentFile.declarations.length;
data.currentFile.declarations.push("");
const decl = schematicKind.scaffoldModel?.(schematic, data) ?? `typealias ${schematic.name} = ${scaffoldType(schematic, data)}`;
data.currentFile.declarations[nDecls] = schematicKind.scaffoldModel ? jsExport(serializable(decl, data), data) : decl;
}
function scaffoldPropertyAnnotations(schematic, data) {
const schematicKind = data.schematicKinds.get(schematic.kind);
if (!schematicKind) {
throw new Error(`Unknown schematic kind ${schematic.kind}`);
}
return typeof schematicKind.propertyAnnotations === "function" ? schematicKind.propertyAnnotations(schematic, data) : schematicKind.propertyAnnotations;
}
function scaffoldType(schematic, data) {
const schematicKind = data.schematicKinds.get(schematic.kind);
if (!schematicKind) {
throw new Error(`Unknown schematic kind ${schematic.kind}`);
}
const schematicPackage = schematicKind.package ?? joinPackages(data.currentPackage, schematic.packageSuffix);
const schematicName = schematic.name || schematicKind.name;
if (schematicPackage !== data.currentPackage) {
data.currentFile.imports.add(joinPackages(schematicPackage, schematicName));
}
if (schematicKind.package == null && schematicPackage !== data.currentPackage) {
scaffoldModels(schematic, {
...data,
currentPackage: joinPackages(
data.currentPackage,
schematic.packageSuffix
),
currentDir: joinPaths(
data.currentDir,
schematic.packageSuffix?.replace(".", "/")
)
});
} else if (schematicKind.scaffoldModel) {
scaffoldModelDeclaration(schematic, data);
}
return (schematicKind.scaffoldType?.(schematic, data) ?? schematicName) + (schematic.nullable ? "?" : "");
}
function scaffoldDefaultValue(schematic, data) {
const schematicKind = data.schematicKinds.get(schematic.kind);
if (!schematicKind) {
throw new Error(`Unknown schematic kind ${schematic.kind}`);
}
return schematic.nullable ? "null" : typeof schematicKind.defaultValue === "function" ? schematicKind.defaultValue(schematic, data) : schematicKind.defaultValue;
}
function boolDataAttr(condition) {
return condition ? "" : void 0;
}
const preventDrag = {
onKeyDown: (evt) => evt.stopPropagation(),
onPointerDown: (evt) => evt.stopPropagation()
};
function ChildMarker() {
const { schematicKinds, useSchematic, setSchematic } = useSchematicBuilderContext();
const kind = useSchematic((schematic) => schematic.kind);
const collapsed = useSchematic((schematic) => schematic.collapsed);
const Builder = schematicKinds.get(kind)?.builder;
return Builder ? /* @__PURE__ */ jsxRuntime.jsx(
"button",
{
type: "button",
className: "builder-child-marker",
title: collapsed ? "Expand" : "Collapse",
onClick: () => setSchematic((schematic) => ({
collapsed: !schematic.collapsed
})),
...preventDrag,
children: collapsed ? "▸" : "▾"
}
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "builder-child-marker", children: "•" });
}
function ChildRemove() {
const { removeSchematic } = useSchematicBuilderContext();
return /* @__PURE__ */ jsxRuntime.jsx(
"button",
{
className: "builder-icon-button builder-child-remove",
type: "button",
title: "Remove",
onClick: removeSchematic,
...preventDrag,
children: "×"
}
);
}
const KIND_PLACEHOLDER = "--KIND--";
function KindSelect() {
const { schematicKinds, useSchematic, setSchematic, parentChildName } = useSchematicBuilderContext();
const kind = useSchematic((schematic) => schematic.kind);
return /* @__PURE__ */ jsxRuntime.jsxs(
"select",
{
className: "builder-input builder-schema",
value: kind,
onChange: (evt) => {
const schematicKind = schematicKinds.get(evt.target.value);
setSchematic({
kind: evt.target.value,
packageSuffix: schematicKind?.defaultPackageSuffix?.(
parentChildName
),
name: schematicKind?.defaultName?.(parentChildName),
collapsed: false,
nullable: schematicKind?.nullable ?? schematicKind?.defaultNullable,
children: schematicKind?.initChildren?.(parentChildName)
});
},
required: true,
style: {
width: `calc(${(kind || KIND_PLACEHOLDER).length}ch + 1.85rem)`
},
...preventDrag,
children: [
/* @__PURE__ */ jsxRuntime.jsx("option", { children: KIND_PLACEHOLDER }),
Array.from(schematicKinds.values()).filter((kind2) => !kind2.internal).map((kind2) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: kind2.kind, children: kind2.kind }, kind2.kind))
]
}
);
}
function NullableInput() {
const { schematicKinds, useSchematic, setSchematic } = useSchematicBuilderContext();
const kind = useSchematic((schematic) => schematic.kind);
const nullable = useSchematic((schematic) => !!schematic.nullable);
const schematicKind = schematicKinds.get(kind);
const showInput = schematicKind && schematicKind.nullable === void 0;
return showInput && /* @__PURE__ */ jsxRuntime.jsx(
"input",
{
className: "builder-input builder-nullable",
type: "checkbox",
title: "Nullable?",
checked: nullable,
onChange: (evt) => setSchematic({ nullable: evt.target.checked }),
...preventDrag
}
);
}
const PACKAGE_PLACEHOLDER = "package.suffix";
const NAME_PLACEHOLDER = "ClassName";
function PackageNameInput() {
const { useSchematic, setSchematic, parentPackage } = useSchematicBuilderContext();
const pkgRef = React__namespace.useRef(null);
const packageSuffix = useSchematic((schematic) => schematic.packageSuffix);
const name = useSchematic((schematic) => schematic.name);
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "builder-package-name", children: [
parentPackage && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "builder-package-prefix", children: [
parentPackage,
"."
] }),
/* @__PURE__ */ jsxRuntime.jsx(
"input",
{
className: "builder-input builder-package",
placeholder: PACKAGE_PLACEHOLDER,
value: packageSuffix ?? "",
onChange: (evt) => setSchematic({ packageSuffix: evt.target.value }),
pattern: "(([a-z_][a-z0-9_]*)(\\.([a-z_][a-z0-9_]*))*)?",
style: { width: `${(packageSuffix || PACKAGE_PLACEHOLDER).length}ch` },
...preventDrag,
ref: pkgRef
}
),
/* @__PURE__ */ jsxRuntime.jsx(
"button",
{
type: "button",
className: "builder-icon-button builder-edit-package",
onClick: () => {
const pkgInput = pkgRef.current;
pkgInput.style.display = "initial";
pkgInput.focus();
pkgInput.style.display = "";
},
children: "🖉"
}
),
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "builder-operator", children: "." }),
/* @__PURE__ */ jsxRuntime.jsx(
"input",
{
className: "builder-input builder-name",
placeholder: NAME_PLACEHOLDER,
value: name ?? "",
onChange: (evt) => setSchematic({ name: evt.target.value }),
required: true,
pattern: "[a-zA-Z_][a-zA-Z0-9_]*",
style: { width: `${(name || NAME_PLACEHOLDER).length}ch` },
...preventDrag
}
)
] });
}
const CLASS_DND_TYPE = "class-field";
function ClassBuilder() {
const schematicBuilderContext = useSchematicBuilderContext();
const { useSchematic, setSchematic, draggedSchematic, disableDrop } = schematicBuilderContext;
const id = useSchematic((schematic) => schematic.id);
const packageSuffix = useSchematic((schematic) => schematic.packageSuffix);
const fields = useSchematic((schematic) => schematic.children) ?? [];
const shouldIgnoreDrop = draggedSchematic != null && draggedSchematic.id === fields.at(-1)?.id;
const { setNodeRef: setDroppableRef, isOver } = core.useDroppable({
id: `${id}-append`,
data: { parentId: id, sortBefore: null, ignored: shouldIgnoreDrop },
disabled: disableDrop || draggedSchematic?.type !== CLASS_DND_TYPE
});
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "class-builder builder", children: [
/* @__PURE__ */ jsxRuntime.jsx(PackageNameInput, {}),
fields.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: "builder-empty",
"data-over": boolDataAttr(isOver),
ref: setDroppableRef,
children: "No fields."
}
),
fields.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "builder-children", children: [
fields.map((field, i) => /* @__PURE__ */ jsxRuntime.jsx(
SchematicBuilderContext.Provider,
{
value: {
...schematicBuilderContext,
useSchematic: (selector) => useSchematic((schematic) => selector(schematic.children[i])),
setSchematic: (toSet) => setSchematic((schematic) => ({
...schematic,
children: schematic.children.map(
(f) => field === f ? {
...f,
...typeof toSet === "function" ? toSet(f) : toSet
} : f
)
})),
removeSchematic: () => setSchematic((schematic) => ({
...schematic,
children: schematic.children.filter((f) => f !== field)
})),
parentPackage: joinPackages(
schematicBuilderContext.parentPackage,
packageSuffix
),
parentId: id,
ignoreDrop: draggedSchematic != null && draggedSchematic.id === fields[i - 1]?.id
},
children: /* @__PURE__ */ jsxRuntime.jsx(ClassFieldBuilder, {})
},
field.id
)),
/* @__PURE__ */ jsxRuntime.jsx(
"li",
{
className: "builder-child-droppable",
"data-over": boolDataAttr(isOver && !shouldIgnoreDrop),
ref: setDroppableRef
}
)
] }),
/* @__PURE__ */ jsxRuntime.jsx(
"button",
{
className: "builder-input builder-new-child",
type: "button",
onClick: () => {
setSchematic((schematic) => ({
...schematic,
children: [
...schematic.children ?? [],
createSchematic({ childName: "" })
]
}));
},
...preventDrag,
children: "Add field"
}
)
] });
}
const CHILD_NAME_PLACEHOLDER$1 = "fieldName";
function ClassFieldBuilder() {
const schematicBuilderContext = useSchematicBuilderContext();
const {
schematicKinds,
useSchematic,
setSchematic,
draggedSchematic,
parentId,
disableDrop,
ignoreDrop
} = schematicBuilderContext;
const id = useSchematic((schematic) => schematic.id);
const childName = useSchematic((schematic) => schematic.childName);
const kind = useSchematic((schematic) => schematic.kind);
const nullable = useSchematic((schematic) => schematic.nullable);
const collapsed = useSchematic((schematic) => schematic.collapsed);
const Builder = schematicKinds.get(kind)?.builder;
const {
attributes,
listeners,
setNodeRef: setDraggableRef,
isDragging
} = core.useDraggable({
id,
data: {
type: CLASS_DND_TYPE,
node: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
[childName, kind].filter((s) => s).join(": "),
nullable ? "?" : ""
] })
}
});
const shouldIgnoreDrop = isDragging || ignoreDrop;
const { setNodeRef: setDroppableRef, isOver } = core.useDroppable({
id,
data: { parentId, sortBefore: id, ignored: shouldIgnoreDrop },
disabled: disableDrop || draggedSchematic?.type !== CLASS_DND_TYPE
});
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
/* @__PURE__ */ jsxRuntime.jsx(
"li",
{
className: "builder-child-droppable",
"data-over": boolDataAttr(isOver && !shouldIgnoreDrop),
ref: setDroppableRef
}
),
/* @__PURE__ */ jsxRuntime.jsxs(
"li",
{
className: "builder-child class-builder-field",
"data-dragging": boolDataAttr(isDragging),
...listeners,
...attributes,
ref: setDraggableRef,
"data-collapsible": boolDataAttr(Builder != null),
children: [
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "builder-child-content", children: [
/* @__PURE__ */ jsxRuntime.jsx(ChildMarker, {}),
/* @__PURE__ */ jsxRuntime.jsx(
"input",
{
className: "builder-input class-builder-field-id",
placeholder: CHILD_NAME_PLACEHOLDER$1,
value: childName,
onChange: (evt) => setSchematic({ childName: evt.target.value }),
required: true,
pattern: "[a-zA-Z_][a-zA-Z0-9_]*",
style: {
width: `${(childName || CHILD_NAME_PLACEHOLDER$1).length}ch`
},
...preventDrag
}
),
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "builder-operator", children: "∶" }),
/* @__PURE__ */ jsxRuntime.jsx(
SchematicBuilderContext.Provider,
{
value: { ...schematicBuilderContext, parentChildName: childName },
children: /* @__PURE__ */ jsxRuntime.jsx(KindSelect, {})
}
),
/* @__PURE__ */ jsxRuntime.jsx(NullableInput, {}),
/* @__PURE__ */ jsxRuntime.jsx(ChildRemove, {})
] }),
Builder && !collapsed && /* @__PURE__ */ jsxRuntime.jsx(
SchematicBuilderContext.Provider,
{
value: {
...schematicBuilderContext,
parentChildName: childName,
disableDrop: disableDrop || isDragging
},
children: /* @__PURE__ */ jsxRuntime.jsx(Builder, {})
}
)
]
}
)
] });
}
const ENUM_DND_TYPE = "enum-entry";
function EnumBuilder() {
const schematicBuilderContext = useSchematicBuilderContext();
const { useSchematic, setSchematic, draggedSchematic, disableDrop } = schematicBuilderContext;
const id = useSchematic((schematic) => schematic.id);
const fields = useSchematic((schematic) => schematic.children) ?? [];
const shouldIgnoreDrop = draggedSchematic != null && draggedSchematic.id === fields.at(-1)?.id;
const { setNodeRef: setDroppableRef, isOver } = core.useDroppable({
id: `${id}-append`,
data: { parentId: id, sortBefore: null, ignored: shouldIgnoreDrop },
disabled: disableDrop || draggedSchematic?.type !== ENUM_DND_TYPE
});
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "enum-builder builder", children: [
/* @__PURE__ */ jsxRuntime.jsx(PackageNameInput, {}),
fields.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(
"div",
{
className: "builder-empty",
"data-over": boolDataAttr(isOver),
ref: setDroppableRef,
children: "No entries."
}
),
fields.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "builder-children", children: [
fields.map((field, i) => /* @__PURE__ */ jsxRuntime.jsx(
SchematicBuilderContext.Provider,
{
value: {
...schematicBuilderContext,
useSchematic: (selector) => useSchematic((schematic) => selector(schematic.children[i])),
setSchematic: (toSet) => setSchematic((schematic) => ({
...schematic,
children: schematic.children.map(
(f) => field === f ? {
...f,
...typeof toSet === "function" ? toSet(f) : toSet
} : f
)
})),
removeSchematic: () => setSchematic((schematic) => ({
...schematic,
children: schematic.children.filter((f) => f !== field)
})),
parentId: id,
ignoreDrop: draggedSchematic != null && draggedSchematic.id === fields[i - 1]?.id
},
children: /* @__PURE__ */ jsxRuntime.jsx(EnumEntryBuilder, {})
},
field.id
)),
/* @__PURE__ */ jsxRuntime.jsx(
"li",
{
className: "builder-child-droppable",
"data-over": boolDataAttr(isOver && !shouldIgnoreDrop),
ref: setDroppableRef
}
)
] }),
/* @__PURE__ */ jsxRuntime.jsx(
"button",
{
className: "builder-input builder-new-child",
type: "button",
onClick: () => {
setSchematic((schematic) => ({
...schematic,
children: [
...schematic.children ?? [],
createSchematic({ childName: "" })
]
}));
},
...preventDrag,
children: "Add entry"
}
)
] });
}
const CHILD_NAME_PLACEHOLDER = "ENTRY_NAME";
function EnumEntryBuilder() {
const schematicBuilderContext = useSchematicBuilderContext();
const {
schematicKinds,
useSchematic,
setSchematic,
draggedSchematic,
parentId,
disableDrop,
ignoreDrop
} = schematicBuilderContext;
const id = useSchematic((schematic) => schematic.id);
const childName = useSchematic((schematic) => schematic.childName);
const kind = useSchematic((schematic) => schematic.kind);
const Builder = schematicKinds.get(kind)?.builder;
const {
attributes,
listeners,
setNodeRef: setDraggableRef,
isDragging
} = core.useDraggable({
id,
data: {
type: ENUM_DND_TYPE,
node: /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: [childName, kind].filter((s) => s).join(": ") })
}
});
const shouldIgnoreDrop = isDragging || ignoreDrop;
const { setNodeRef: setDroppableRef, isOver } = core.useDroppable({
id,
data: { parentId, sortBefore: id, ignored: shouldIgnoreDrop },
disabled: disableDrop || draggedSchematic?.type !== ENUM_DND_TYPE
});
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
/* @__PURE__ */ jsxRuntime.jsx(
"li",
{
className: "builder-child-droppable",
"data-over": boolDataAttr(isOver && !shouldIgnoreDrop),
ref: setDroppableRef
}
),
/* @__PURE__ */ jsxRuntime.jsx(
"li",
{
className: "builder-child",
"data-dragging": boolDataAttr(isDragging),
...listeners,
...attributes,
ref: setDraggableRef,
"data-collapsible": boolDataAttr(Builder != null),
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "builder-child-content", children: [
/* @__PURE__ */ jsxRuntime.jsx(ChildMarker, {}),
/* @__PURE__ */ jsxRuntime.jsx(
"input",
{
className: "builder-input enum-builder-entry-id",
placeholder: CHILD_NAME_PLACEHOLDER,
value: childName,
onChange: (evt) => setSchematic({ childName: evt.target.value }),
required: true,
pattern: "[a-zA-Z_][a-zA-Z0-9_]*",
style: {
width: `${(childName || CHILD_NAME_PLACEHOLDER).length}ch`
},
...preventDrag
}
),
/* @__PURE__ */ jsxRuntime.jsx(ChildRemove, {})
] })
}
)
] });
}
function ListableBuilder({
showKindSelect = true
}) {
const schematicBuilderContext = useSchematicBuilderContext();
const { useSchematic, setSchematic } = schematicBuilderContext;
const id = useSchematic((schematic) => schematic.id);
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "listable-builder builder", children: /* @__PURE__ */ jsxRuntime.jsx(
SchematicBuilderContext.Provider,
{
value: {
...schematicBuilderContext,
useSchematic: (selector) => useSchematic((schematic) => selector(schematic.children[0])),
setSchematic: (toSet) => setSchematic((schematic) => ({
...schematic,
children: [
{
...schematic.children[0],
...typeof toSet === "function" ? toSet(schematic.children[0]) : toSet
}
]
})),
removeSchematic: () => {
},
parentId: id
},
children: /* @__PURE__ */ jsxRuntime.jsx(ListableItemBuilder, { showKindSelect })
}
) });
}
function ListableBuilderWithoutKindSelect() {
return /* @__PURE__ */ jsxRuntime.jsx(ListableBuilder, { showKindSelect: false });
}
function ListableItemBuilder({
showKindSelect = true
}) {
const { schematicKinds, useSchematic } = useSchematicBuilderContext();
const kind = useSchematic((schematic) => schematic.kind);
const Builder = schematicKinds.get(kind)?.builder;
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
showKindSelect && /* @__PURE__ */ jsxRuntime.jsx(KindSelect, {}),
Builder && /* @__PURE__ */ jsxRuntime.jsx(Builder, {})
] });
}
const LF = /\r?\n/;
const EMPTY_LINE = /^\s*$/;
const SPACE = /\s/;
const LF_AFFIX = /(^\r?\n)|(\r?\n$)/g;
function code(strings, ...values) {
const minIndent = Math.min(
...strings.join("").split(LF).filter(isNotEmpty).map(indentWidth)
);
let result = "";
for (let i = 0; i < values.length; ++i) {
const trimmedString = trimNoninitialLines(strings[i], minIndent);
result += trimmedString;
result += indentNoninitialLines(
String(values[i]),
indentWidth(trimmedString.split(LF).at(-1))
);
}
result += trimNoninitialLines(strings.at(-1), minIndent);
return result.replace(LF_AFFIX, "");
}
function isNotEmpty(line) {
return !EMPTY_LINE.test(line);
}
function indentWidth(line) {
let width = 0;
for (const c of line) {
if (!SPACE.test(c)) {
break;
}
++width;
}
return width;
}
function indentNoninitialLines(str, indentWidth2) {
const lines = str.split(LF);
const resultLines = [lines[0]];
for (let i = 1; i < lines.length; ++i) {
const line = lines[i];
resultLines.push(line && " ".repeat(indentWidth2) + line);
}
return resultLines.join("\n");
}
function trimNoninitialLines(str, indentWidth2) {
const lines = str.split(LF);
const resultLines = [lines[0]];
for (let i = 1; i < lines.length; ++i) {
resultLines.push(lines[i].slice(indentWidth2));
}
return resultLines.join("\n");
}
const NULLABLE_SCHEMA = "io.kform.schemas.NullableSchema";
const anySchematicKind = {
kind: "Any",
package: "kotlin",
name: "Any",
schema: "io.kform.schemas.AnySchema",
nullable: false,
defaultValue: "null",
scaffoldType: () => "Any?"
};
const bigDecimalSchematicKind = {
kind: "BigDecimal",
package: "io.kform.datatypes",
name: "BigDecimal",
schema: "io.kform.schemas.BigDecimalSchema",
defaultNullable: true,
defaultValue: "BigDecimal.ZERO"
};
const bigIntegerSchematicKind = {
kind: "BigInteger",
package: "io.kform.datatypes",
name: "BigInteger",
schema: "io.kform.schemas.BigIntegerSchema",
defaultNullable: true,
defaultValue: "BigInteger.ZERO"
};
const booleanSchematicKind = {
kind: "Boolean",
package: "kotlin",
name: "Boolean",
schema: "io.kform.schemas.BooleanSchema",
defaultValue: "false"
};
const byteSchematicKind = {
kind: "Byte",
package: "kotlin",
name: "Byte",
schema: "io.kform.schemas.ByteSchema",
defaultNullable: true,
defaultValue: "0.toByte()"
};
const charSchematicKind = {
kind: "Char",
package: "kotlin",
name: "Char",
schema: "io.kform.schemas.CharSchema",
defaultNullable: true,
defaultValue: "0.toChar()"
};
const classSchematicKind = {
kind: "Class",
schema: "io.kform.schemas.ClassSchema",
defaultValue: (schematic) => `${schematic.name}()`,
builder: ClassBuilder,
initChildren: () => [],
defaultPackageSuffix: (childName) => childName.toLowerCase(),
defaultName: (childName) => changeCase.pascalCase(childName),
scaffoldModel: (schematic, data) => code`
data class ${schematic.name}(
${schematic.children?.map((childSchematic) => {
const childData = {
...data,
currentPath: joinPaths(
data.currentPath,
childSchematic.childName
)
};
return annotate(
scaffoldPropertyAnnotations(childSchematic, childData),
`var ${childSchematic.childName}: ${scaffoldType(
childSchematic,
childData
)} = ${scaffoldDefaultValue(childSchematic, childData)}`
);
}).join(",\n")}
)
`,
scaffoldSchema: (schematic, data) => code`
ClassSchema {
${schematic.children?.map(
(childSchematic) => `${schematic.name}::${childSchematic.childName} { ${scaffoldSchema(
childSchematic,
{
...data,
currentPath: joinPaths(
data.currentPath,
childSchematic.childName
)
}
)} }`
).join("\n")}
}
`
};
const doubleSchematicKind = {
kind: "Double",
package: "kotlin",
name: "Double",
schema: "io.kform.schemas.DoubleSchema",
defaultNullable: true,
defaultValue: "0.0"
};
const enumSchematicKind = {
kind: "Enum",
schema: "io.kform.schemas.EnumSchema",
defaultNullable: true,
defaultValue: (schematic) => `${schematic.name}.${schematic.children?.[0].childName}`,
builder: EnumBuilder,
initChildren: () => [],
defaultName: (childName) => changeCase.pascalCase(childName),
scaffoldModel: (schematic) => code`
enum class ${schematic.name} {
${schematic.children?.map((childSchematic) => childSchematic.childName).join(",\n")}
}
`
};
const fileSchematicKind = {
kind: "File",
package: "io.kform.datatypes",
name: "File",
schema: "io.kform.schemas.FileSchema",
defaultNullable: true,
defaultValue: (_schematic, data) => {
data.currentFile.imports.add("io.kform.datatypes.emptyPlaceholderFile");
return "emptyPlaceholderFile()";
},
propertyAnnotations: (_schematic, data) => data.useFileBase64Serializer ? "@Serializable(with = File.Base64Serializer::class)" : void 0
};
const floatSchematicKind = {
kind: "Float",
package: "kotlin",
name: "Float",
schema: "io.kform.schemas.FloatSchema",
defaultNullable: true,
defaultValue: "0f"
};
const instantSchematicKind = {
kind: "Instant",
package: "kotlin.time",
name: "Instant",
schema: "io.kform.schemas.InstantSchema",
defaultNullable: true,
defaultValue: "Instant.fromEpochMilliseconds(0)",
propertyAnnotations: (_schematic, data) => {
data.currentFile.imports.add("kotlin.OptIn").add("kotlin.time.ExperimentalTime");
return "@OptIn(ExperimentalTime::class)";
}
};
const intSchematicKind = {
kind: "Int",
package: "kotlin",
name: "Int",
schema: "io.kform.schemas.IntSchema",
defaultNullable: true,
defaultValue: "0"
};
const listSchematicKind = {
kind: "List",
package: "kotlin.collections",
name: "List",
schema: "io.kform.schemas.ListSchema",
defaultValue: "emptyList()",
builder: ListableBuilder,
initChildren: () => [createSchematic()],
scaffoldType: (schematic, data) => `List<${scaffoldType(schematic.children[0], {
...data,
currentPath: joinPaths(data.currentPath, "*")
})}>`,
scaffoldSchema: (schematic, data) => `ListSchema { ${scaffoldSchema(schematic.children[0], {
...data,
currentPath: joinPaths(data.currentPath, "*")
})} }`
};
const localDateSchematicKind = {
kind: "LocalDate",
package: "kotlinx.datetime",
name: "LocalDate",
schema: "io.kform.schemas.LocalDateSchema",
defaultNullable: true,
defaultValue: "LocalDate(1970, 1, 1)"
};
const localDateTimeSchematicKind = {
kind: "LocalDateTime",
package: "kotlinx.datetime",
name: "LocalDateTime",
schema: "io.kform.schemas.LocalDateTimeSchema",
defaultNullable: true,
defaultValue: "LocalDateTime(1970, 1, 1, 0, 0)"
};
const longSchematicKind = {
kind: "Long",
package: "kotlin",
name: "Long",
schema: "io.kform.schemas.LongSchema",
defaultNullable: true,
defaultValue: "0L"
};
const shortSchematicKind = {
kind: "Short",
package: "kotlin",
name: "Short",
schema: "io.kform.schemas.ShortSchema",
defaultNullable: true,
defaultValue: "0.toShort()"
};
const stringSchematicKind = {
kind: "String",
package: "kotlin",
name: "String",
schema: "io.kform.schemas.StringSchema",
defaultValue: '""'
};
const tableSchematicKind = {
kind: "Table",
package: "io.kform.datatypes",
name: "Table",
schema: "io.kform.schemas.TableSchema",
defaultValue: (_schematic, data) => {
data.currentFile.imports.add("io.kform.datatypes.tableOf");
return "tableOf()";
},
builder: ListableBuilder,
initChildren: () => [createSchematic()],
propertyAnnotations: (_schematic, data) => data.useTableValuesSerializer ? "@Serializable(with = Table.ValuesSerializer::class)" : void 0,
scaffoldType: (schematic, data) => `Table<${scaffoldType(schematic.children[0], {
...data,
currentPath: joinPaths(data.currentPath, "*")
})}>`,
scaffoldSchema: (schematic, data) => `TableSchema { ${scaffoldSchema(schematic.children[0], {
...data,
currentPath: joinPaths(data.currentPath, "*")
})} }`
};
const defaultSchematicKinds = [
anySchematicKind,
bigDecimalSchematicKind,
bigIntegerSchematicKind,
booleanSchematicKind,
byteSchematicKind,
charSchematicKind,
classSchematicKind,
doubleSchematicKind,
enumSchematicKind,
fileSchematicKind,
floatSchematicKind,
instantSchematicKind,
intSchematicKind,
listSchematicKind,
localDateSchematicKind,
localDateTimeSchematicKind,
longSchematicKind,
shortSchematicKind,
stringSchematicKind,
tableSchematicKind
];
function scaffoldSchemas(schematic, data) {
const fileName = joinPaths(data.currentDir, `${schematic.name}Schema.kt`);
let file = data.files.get(fileName);
if (!file) {
data.files.set(fileName, file = ktFile(data));
data.files.set(
joinPaths(data.currentDir, `${schematic.name}Validations.kt`),
ktFile(data)
);
}
scaffoldSchemaDeclaration(schematic, { ...data, currentFile: file });
}
function scaffoldSchemaDeclaration(schematic, data) {
const schematicKind = data.schematicKinds.get(schematic.kind);
if (!schematicKind) {
throw new Error(`Unknown schematic kind ${schematic.kind}`);
}
const nDecls = data.currentFile.declarations.length;
data.currentFile.declarations.push("");
const decl = `val ${schematic.name}Schema = ${schematicKind.scaffoldSchema?.(schematic, data) ?? `${simpleKtName(schematicKind.schema)}()`}`;
data.currentFile.imports.add(schematicKind.schema);
data.currentFile.declarations[nDecls] = data.currentPath === "/" ? jsExport(decl, data) : decl;
}
function scaffoldSchema(schematic, data) {
const schematicKind = data.schematicKinds.get(schematic.kind);
if (!schematicKind) {
throw new Error(`Unknown schematic kind ${schematic.kind}`);
}
const schematicPackage = schematicKind.package ?? joinPackages(data.currentPackage, schematic.packageSuffix);
let scaffoldedSchema;
if (schematicKind.package == null && schematicPackage !== data.currentPackage) {
scaffoldedSchema = `${schematic.name}Schema`;
data.currentFile.imports.add(`${schematicPackage}.${scaffoldedSchema}`);
scaffoldSchemas(schematic, {
...data,
currentPackage: joinPackages(
data.currentPackage,
schematic.packageSuffix
),
currentDir: joinPaths(
data.currentDir,
schematic.packageSuffix?.replace(".", "/")
)
});
} else {
scaffoldedSchema = schematicKind.scaffoldSchema?.(schematic, data) ?? `${simpleKtName(schematicKind.schema)}()`;
data.currentFile.imports.add(schematicKind.schema);
}
if (schematic.nullable) {
data.currentFile.imports.add(NULLABLE_SCHEMA);
return `${simpleKtName(NULLABLE_SCHEMA)} { ${scaffoldedSchema} }`;
}
return scaffoldedSchema;
}
const serializerKt = "package <%= filePackage %>\n\nimport kotlin.js.JsExport\nimport kotlin.jvm.JvmOverloads\nimport kotlinx.serialization.json.Json\n\n/** Base JSON configuration. */\nprivate val JSON_CONFIG = Json.Default\n/** Pretty JSON configuration. */\nprivate val JSON_CONFIG_PRETTY = Json(JSON_CONFIG) { prettyPrint = true }\n\n/** JSON configuration. */\n@JvmOverloads\nfun jsonConfig(prettyPrint: Boolean = false): Json =\n if (prettyPrint) JSON_CONFIG_PRETTY else JSON_CONFIG\n\n@JsExport\n@JvmOverloads\nfun encode<%= formClass %>ToString(<%= formVar %>: <%= formClass %>, prettyPrint: Boolean = false): String =\n jsonConfig(prettyPrint).encodeToString(<%= formVar %>)\n\n@JsExport\nfun decode<%= formClass %>FromString(json: String): <%= formClass %> =\n jsonConfig().decodeFromString(json)\n";
function ejsTemplateFile(content, ejsData, { base64, binary, executable, ...ejsOptions } = {}) {
return {
content,
ejsData,
base64,
binary,
executable,
ejsOptions,
getContent: ejsTemplateFileContent
};
}
function ejsTemplateFileContent() {
return ejs.render(this.content, this.ejsData, {
strict: true,
destructuredLocals: Object.keys(this.ejsData ?? {}),
...this.ejsOptions
});
}
function addEjsTemplateFile(data, fileName, content, ejsData, options) {
data.files.set(
joinPaths(data.currentDir, fileName),
ejsTemplateFile(content, ejsData, options)
);
}
function scaffoldSerializer(schematic, data) {
addEjsTemplateFile(data, `${schematic.name}Serializer.kt`, serializerKt, {
filePackage: data.currentPackage,
formVar: changeCase.camelCase(schematic.name),
formClass: schematic.name
});
}
const validatorKt = "package <%= filePackage %>\n\nimport io.kform.ExternalContexts\nimport io.kform.FormValidator\nimport io.kform.LocatedValidationError\nimport io.kform.LocatedValidationWarning\nimport io.kform.test.assertContainsMatchingIssue\nimport io.kform.test.assertNotContainsMatchingIssue\n\nval <%= formVar %>Validator by lazy { FormValidator(<%= formClass %>Schema) }\n\nfun validate<%= formClass %>(\n <%= formVar %>: <%= formClass %>,\n path: String,\n externalContexts: ExternalContexts = emptyMap(),\n) = <%= formVar %>Validator.validate(<%= formVar %>, path, externalContexts)\n\nsuspend fun assert<%= formClass %>ContainsError(\n path: String,\n code: String,\n <%= formVar %>: <%= formClass %>,\n externalContexts: ExternalContexts = emptyMap(),\n) =\n assertContainsMatchingIssue(\n LocatedValidationError(path, code),\n validate<%= formClass %>(<%= formVar %>, path, externalContexts),\n )\n\nsuspend fun assert<%= formClass %>NotContainsError(\n path: String,\n code: String,\n <%= formVar %>: <%= formClass %>,\n externalContexts: ExternalContexts = emptyMap(),\n) =\n assertNotContainsMatchingIssue(\n LocatedValidationError(path, code),\n validate<%= formClass %>(<%= formVar %>, path, externalContexts),\n )\n\nsuspend fun assert<%= formClass %>ContainsWarning(\n path: String,\n code: String,\n <%= formVar %>: <%= formClass %>,\n externalContexts: ExternalContexts = emptyMap(),\n) =\n assertContainsMatchingIssue(\n LocatedValidationWarning(path, code),\n validate<%= formClass %>(<%= formVar %>, path, externalContexts),\n )\n\nsuspend fun assert<%= formClass %>NotContainsWarning(\n path: String,\n code: String,\n <%= formVar %>: <%= formClass %>,\n externalContexts: ExternalContexts = emptyMap(),\n) =\n assertNotContainsMatchingIssue(\n LocatedValidationWarning(path, code),\n validate<%= formClass %>(<%= formVar %>, path, externalContexts),\n )\n";
function scaffoldValidator(schematic, data) {
addEjsTemplateFile(data, `${schematic.name}Validator.kt`, validatorKt, {
filePackage: data.currentPackage,
formVar: changeCase.camelCase(schematic.name),
formClass: schematic.name
});
}
const useLayoutEffect = typeof document !== "undefined" ? React__namespace.useLayoutEffect : () => {
};
function useMeasure(element, setMeasurement) {
useLayoutEffect(() => {
if (!element) {
setMeasurement(void 0);
return;
}
const cb = () => setMeasurement(element.getBoundingClientRect());
cb();
const observer = new ResizeObserver(([entry]) => entry && cb());
observer.observe(element);
window.addEventListener("resize", cb);
return () => {
observer.disconnect();
window.removeEventListener("resize", cb);
setMeasurement(void 0);
};
}, [element, setMeasurement]);
}
function SchematicBuilderConfig({
children
}) {
const [anchorEl, setAnchorEl] = React__namespace.useState(
null
);
const [popoverEl, setPopoverEl] = React__namespace.useState(null);
useMeasure(anchorEl, (measurement) => {
if (popoverEl && measurement) {
popoverEl.style.setProperty("--anchor-top", `${measurement.top}px`);
popoverEl.style.setProperty("--anchor-left", `${measurement.left}px`);
popoverEl.style.setProperty("--anchor-bottom", `${measurement.bottom}px`);
popoverEl.style.setProperty("--anchor-right", `${measurement.right}px`);
popoverEl.style.setProperty