UNPKG

@kform/scaffolder

Version:

Scaffolding utilities for KForm projects.

1,431 lines (1,430 loc) 55.3 kB
"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 index = require("zustand/index"); 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 index2 = sortBefore === null ? oldChildren.length : oldChildren.findIndex((c) => c.id === sortBefore); return { ...schematic, children: [ ...oldChildren.slice(0, index2), toAdd, ...oldChildren.slice(index2) ] }; } 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 () => { 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); }, title: "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 () => { 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 ); }, title: "Save schematic", children: "💾" } ); } 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, getRootSchematic } = useSchematicBuilderContext(); return React__namespace.useCallback(() => { const schematic = getRootSchematic(); return scaffold( schematic, typeof scaffolders === "function" ? scaffolders(schematic) : scaffolders, { schematicKinds, basePath, basePackage, baseDir: typeof baseDir === "function" ? baseDir(schematic) : baseDir } ); }, [ baseDir, basePackage, basePath, getRootSchematic, scaffolders, 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 ); } catch (err) { alert(err); } }, children: "Scaffold 🏗️" } ); } function configScaffolder(scaffolder, data) { return (schematic, originalData) => scaffolder(schematic, { ...originalData, ...data }); } 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(annotation, statement, data) { data.currentFile.imports.add(annotation); return `@${simpleKtName(annotation)} ${statement}`; } function jsExport(statement, data) { return annotate(JS_EXPORT, statement, data); } function serializable(statement, data) { return annotate(SERIALIZABLE, statement, data); } 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) { var _a; 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 = ((_a = schematicKind.scaffoldModel) == null ? void 0 : _a.call(schematicKind, schematic, data)) ?? `typealias ${schematic.name} = ${scaffoldType(schematic, data)}`; data.currentFile.declarations[nDecls] = schematicKind.scaffoldModel ? jsExport(serializable(decl, data), data) : decl; } function scaffoldType(schematic, data) { var _a, _b; 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, (_a = schematic.packageSuffix) == null ? void 0 : _a.replace(".", "/") ) }); } else if (schematicKind.scaffoldModel) { scaffoldModelDeclaration(schematic, data); } return (((_b = schematicKind.scaffoldType) == null ? void 0 : _b.call(schematicKind, 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() { var _a; const { schematicKinds, useSchematic, setSchematic } = useSchematicBuilderContext(); const kind = useSchematic((schematic) => schematic.kind); const collapsed = useSchematic((schematic) => schematic.collapsed); const Builder = (_a = schematicKinds.get(kind)) == null ? void 0 : _a.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) => { var _a, _b, _c; const schematicKind = schematicKinds.get(evt.target.value); setSchematic({ kind: evt.target.value, packageSuffix: (_a = schematicKind == null ? void 0 : schematicKind.defaultPackageSuffix) == null ? void 0 : _a.call( schematicKind, parentChildName ), name: (_b = schematicKind == null ? void 0 : schematicKind.defaultName) == null ? void 0 : _b.call(schematicKind, parentChildName), collapsed: false, nullable: (schematicKind == null ? void 0 : schematicKind.nullable) ?? (schematicKind == null ? void 0 : schematicKind.defaultNullable), children: (_c = schematicKind == null ? void 0 : schematicKind.initChildren) == null ? void 0 : _c.call(schematicKind, 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() { var _a; 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 === ((_a = fields.at(-1)) == null ? void 0 : _a.id); const { setNodeRef: setDroppableRef, isOver } = core.useDroppable({ id: `${id}-append`, data: { parentId: id, sortBefore: null, ignored: shouldIgnoreDrop }, disabled: disableDrop || (draggedSchematic == null ? void 0 : 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) => { var _a2; return /* @__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 === ((_a2 = fields[i - 1]) == null ? void 0 : _a2.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() { var _a; 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 = (_a = schematicKinds.get(kind)) == null ? void 0 : _a.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 == null ? void 0 : 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() { var _a; 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 === ((_a = fields.at(-1)) == null ? void 0 : _a.id); const { setNodeRef: setDroppableRef, isOver } = core.useDroppable({ id: `${id}-append`, data: { parentId: id, sortBefore: null, ignored: shouldIgnoreDrop }, disabled: disableDrop || (draggedSchematic == null ? void 0 : 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) => { var _a2; return /* @__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 === ((_a2 = fields[i - 1]) == null ? void 0 : _a2.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() { var _a; 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 = (_a = schematicKinds.get(kind)) == null ? void 0 : _a.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 == null ? void 0 : 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 }) { var _a; const { schematicKinds, useSchematic } = useSchematicBuilderContext(); const kind = useSchematic((schematic) => schematic.kind); const Builder = (_a = schematicKinds.get(kind)) == null ? void 0 : _a.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) => { var _a; return code` data class ${schematic.name}( ${(_a = schematic.children) == null ? void 0 : _a.map( (childSchematic) => `var ${childSchematic.childName}: ${scaffoldType( childSchematic, { ...data, currentPath: joinPaths( data.currentPath, childSchematic.childName ) } )} = ${scaffoldDefaultValue(childSchematic, { ...data, currentPath: joinPaths( data.currentPath, childSchematic.childName ) })}` ).join(",\n")} ) `; }, scaffoldSchema: (schematic, data) => { var _a; return code` ClassSchema { ${(_a = schematic.children) == null ? void 0 : _a.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) => { var _a; return `${schematic.name}.${(_a = schematic.children) == null ? void 0 : _a[0].childName}`; }, builder: EnumBuilder, initChildren: () => [], defaultName: (childName) => changeCase.pascalCase(childName), scaffoldModel: (schematic) => { var _a; return code` enum class ${schematic.name} { ${(_a = schematic.children) == null ? void 0 : _a.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.emplyPlaceholderFile"); return "emplyPlaceholderFile()"; } }; const floatSchematicKind = { kind: "Float", package: "kotlin", name: "Float", schema: "io.kform.schemas.FloatSchema", defaultNullable: true, defaultValue: "0f" }; const instantSchematicKind = { kind: "Instant", package: "kotlinx.datetime", name: "Instant", schema: "io.kform.schemas.InstantSchema", defaultNullable: true, defaultValue: "Instant.fromEpochMilliseconds(0)" }; 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: (_schematic, data) => { data.currentFile.imports.add("kotlinx.datetime.Instant"); data.currentFile.imports.add("kotlinx.datetime.TimeZone"); return "Instant.fromEpochMilliseconds(0).toLocalDateTime(TimeZone.UTC).date"; } }; const localDateTimeSchematicKind = { kind: "LocalDateTime", package: "kotlinx.datetime", name: "LocalDateTime", schema: "io.kform.schemas.LocalDateTimeSchema", defaultNullable: true, defaultValue: (_schematic, data) => { data.currentFile.imports.add("kotlinx.datetime.Instant"); data.currentFile.imports.add("kotlinx.datetime.TimeZone"); return "Instant.fromEpochMilliseconds(0).toLocalDateTime(TimeZone.UTC)"; } }; 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()], 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) { var _a; 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 = ${((_a = schematicKind.scaffoldSchema) == null ? void 0 : _a.call(schematicKind, 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) { var _a, _b; 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, (_a = schematic.packageSuffix) == null ? void 0 : _a.replace(".", "/") ) }); } else { scaffoldedSchema = ((_b = schematicKind.scaffoldSchema) == null ? void 0 : _b.call(schematicKind, 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 kotlinx.serialization.decodeFromString\nimport kotlinx.serialization.encodeToString\nimport kotlinx.serialization.json.Json\n\n@JsExport\nfun encode<%= formClass %>ToJsonString(<%= formVar %>: <%= formClass %>): String = Json.encodeToString(<%= formVar %>)\n\n@JsExport\nfun decode<%= formClass %>FromJsonString(json: String): <%= formClass %> = Json.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 }); } function SchematicBuilder({ name = "kform", title = document.title, basePath, basePackage, baseDir, defaultRootClassPackage = "org.example", defaultRootClassName = "MyForm", schematicKinds = defaultSchematicKinds, scaffolders = [ configScaffolder(scaffoldSchemas, { currentDir: "src/commonMain/kotlin" }), configScaffolder(scaffoldModels, { currentDir: "src/commonMain/kotlin" }), configScaffolder(scaffoldSerializer, { currentDir: "src/commonMain/kotlin" }), configScaffolder(scaffoldValidator, { currentDir: "src/commonTest/kotlin" }) ], actions = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx(LoadSchematic, {}), /* @__PURE__ */ jsxRuntime.jsx(SaveSchematic, {}), /* @__PURE__ */ jsxRuntime.jsx(ScaffoldToZip, {}) ] }) }) { const [useSchematic] = React__namespace.useState( () => index.create( middleware.persist( () => createSchematic({ kind: classSchematicKind.kind, packageSuffix: defaultRootClassPackage, name: defaultRootClassName, children: [] }), { name } ) ) ); const schematicKindsMap = React__namespace.useMemo( () => new Map( schematicKinds.sort((t1, t2) => t1.kind.localeCompare(t2.kind)).map((t) => [t.kind, t]) ), [schematicKinds] ); const latestSchematicFileHandle = React__namespace.useRef( null ); const [draggedSchematic, setDraggedSchematic] = React__namespace.useState(null); const sensors = core.useSensors( core.useSensor(core.PointerSensor, { activationConstraint: { distance: 10, delay: 400, tolerance: 20 } }), core.useSensor(core.KeyboardSensor) ); return /* @__PURE__ */ jsxRuntime.jsx( SchematicBuilderContext.Provider, { value: { name, basePath, basePackage, baseDir, schematicKinds: schematicKindsMap, scaffolders, getRootSchematic: useSchematic.getState, useSchematic, setSchematic: useSchematic.setState, removeSchematic: () => { }, parentPackage: basePackage, draggedSchematic, latestSchematicFileHandle }, children: /* @__PURE__ */ jsxRuntime.jsxs( core.DndContext, { sensors, collisionDetection: core.closestCenter, onDragStart: (evt) => setDraggedSchematic({ id: evt.active.id, ...evt.active.data.current }), onDragEnd: (evt) => { var _a; const { parentId, sortBefore, ignored } = ((_a = evt.over) == null ? void 0 : _a.data.current) ?? {}; if (!ignored) { useSchematic.setState( (schematic) => moveSchematic( schematic, evt.active.id, parentId, sortBefore ) ); } setDraggedSchematic(null); }, children: [ /* @__PURE__ */ jsxRuntime.jsxs("form", { className: "builder-root", children: [ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "builder-header", children: [ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "builder-title", children: title }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "builder-actions", children: actions }) ] }), /* @__PURE__ */ jsxRuntime.jsx(ClassBuilder, {}) ] }), /* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { children: draggedSchematic && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "builder-dragged", children: draggedSchematic.node }) }) ] } ) } ); } function templateFile(content, options) { return { ...options, content,