diff --git a/package-lock.json b/package-lock.json
index eac091f..4841e70 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,17 +11,19 @@
"license": "ISC",
"dependencies": {
"@babel/core": "^7.12.10",
- "@hotwired/turbo": "^7.1.0",
+ "@hotwired/turbo": "^8.0.2",
"@koa/router": "^12.0.1",
"@playwright/test": "^1.36.1",
- "@sealcode/jdd": "^0.2.4",
- "@sealcode/sealgen": "^0.11.5",
+ "@sealcode/jdd": "^0.2.10",
+ "@sealcode/sealgen": "^0.11.6",
"@sealcode/ts-predicates": "^0.4.3",
"@types/kill-port": "^2.0.0",
"get-port": "^7.0.0",
+ "js-convert-case": "^4.2.0",
"locreq": "^3.0.0",
"multiple-scripts-tmux": "^1.0.4",
"nodemon": "^3.0.1",
+ "object-path": "^0.11.8",
"sealious": "^0.17.48",
"stimulus": "^2.0.0",
"tempstream": "^0.3.2",
@@ -31,6 +33,7 @@
"@sealcode/ansi-html-stream": "^1.0.1",
"@types/koa__router": "^12.0.4",
"@types/node": "^20.8.4",
+ "@types/object-path": "^0.11.4",
"@types/tedious": "^4.0.7",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.2",
@@ -807,9 +810,9 @@
}
},
"node_modules/@hotwired/turbo": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/@hotwired/turbo/-/turbo-7.3.0.tgz",
- "integrity": "sha512-Dcu+NaSvHLT7EjrDrkEmH4qET2ZJZ5IcCWmNXxNQTBwlnE5tBZfN6WxZ842n5cHV52DH/AKNirbPBtcEXDLW4g==",
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@hotwired/turbo/-/turbo-8.0.2.tgz",
+ "integrity": "sha512-3K6QZkwWfosAV8zuM5bY+kKF02jp1lMQGsWfSE6wXdZBRBP3ah+Vj26YNqYtkEomBwRWA0QKhZgyJP7xOQkVEg==",
"engines": {
"node": ">= 14"
}
@@ -1275,9 +1278,9 @@
}
},
"node_modules/@sealcode/jdd": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@sealcode/jdd/-/jdd-0.2.4.tgz",
- "integrity": "sha512-Lf/UIgY0N8zNHHDonvF4WQufITjWhih9+FAbb+NO21pbygrZyIaXfKPW0Vp+Eh9blTZY6QEG40H7zouuVF55ew==",
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@sealcode/jdd/-/jdd-0.2.10.tgz",
+ "integrity": "sha512-8dQfskMUqotrh9Fbnk2sBcXJ12gXNM1ENPvrQOOX6VabXgE7eQc9gAZgmkcgA2prEwn1vbfpo+Lz9wxzpHOLDQ==",
"dependencies": {
"@sealcode/ts-predicates": "^0.5.3",
"marked": "^12.0.0",
@@ -1301,9 +1304,9 @@
}
},
"node_modules/@sealcode/sealgen": {
- "version": "0.11.5",
- "resolved": "https://registry.npmjs.org/@sealcode/sealgen/-/sealgen-0.11.5.tgz",
- "integrity": "sha512-7mb8zuz2Z3KHVcVeWTRN+f4c9IVPhHKX3OzIhNv1ZY/BfkWifU5lFsBrYJUaT4Zd8EYXQIU0DAsZy4WO1miJFQ==",
+ "version": "0.11.6",
+ "resolved": "https://registry.npmjs.org/@sealcode/sealgen/-/sealgen-0.11.6.tgz",
+ "integrity": "sha512-6GGZi59aia7ou2bDmejQedDNLyzfoo05bFnGVlsWXuCOMCUhBXuWGlFe3wqkSr+340iyvZiXJvBSfXg3DatX2Q==",
"dependencies": {
"@koa/router": "^12.0.1",
"@sealcode/ts-predicates": "^0.4.3",
@@ -2100,6 +2103,12 @@
"resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-1.3.4.tgz",
"integrity": "sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA=="
},
+ "node_modules/@types/object-path": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/@types/object-path/-/object-path-0.11.4.tgz",
+ "integrity": "sha512-4tgJ1Z3elF/tOMpA8JLVuR9spt9Ynsf7+JjqsQ2IqtiPJtcLoHoXcT6qU4E10cPFqyXX5HDm9QwIzZhBSkLxsw==",
+ "dev": true
+ },
"node_modules/@types/qs": {
"version": "6.9.11",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz",
@@ -8086,6 +8095,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object-path": {
+ "version": "0.11.8",
+ "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz",
+ "integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==",
+ "engines": {
+ "node": ">= 10.12.0"
+ }
+ },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
diff --git a/package.json b/package.json
index 11455f8..5369dc2 100644
--- a/package.json
+++ b/package.json
@@ -32,17 +32,19 @@
"license": "ISC",
"dependencies": {
"@babel/core": "^7.12.10",
- "@hotwired/turbo": "^7.1.0",
+ "@hotwired/turbo": "^8.0.2",
"@koa/router": "^12.0.1",
"@playwright/test": "^1.36.1",
- "@sealcode/jdd": "^0.2.4",
- "@sealcode/sealgen": "^0.11.5",
+ "@sealcode/jdd": "^0.2.10",
+ "@sealcode/sealgen": "^0.11.6",
"@sealcode/ts-predicates": "^0.4.3",
"@types/kill-port": "^2.0.0",
"get-port": "^7.0.0",
+ "js-convert-case": "^4.2.0",
"locreq": "^3.0.0",
"multiple-scripts-tmux": "^1.0.4",
"nodemon": "^3.0.1",
+ "object-path": "^0.11.8",
"sealious": "^0.17.48",
"stimulus": "^2.0.0",
"tempstream": "^0.3.2",
@@ -52,6 +54,7 @@
"@sealcode/ansi-html-stream": "^1.0.1",
"@types/koa__router": "^12.0.4",
"@types/node": "^20.8.4",
+ "@types/object-path": "^0.11.4",
"@types/tedious": "^4.0.7",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.2",
diff --git a/src/back/html.ts b/src/back/html.ts
index 3fe104a..728d6fd 100644
--- a/src/back/html.ts
+++ b/src/back/html.ts
@@ -1,7 +1,8 @@
-import { Templatable, tempstream } from "tempstream";
+import { FlatTemplatable, Templatable, tempstream } from "tempstream";
import { Readable } from "stream";
import { BaseContext } from "koa";
-import navbar from "./routes/common/navbar.js";
+import { default as default_navbar } from "./routes/common/navbar.js";
+import { toKebabCase } from "js-convert-case";
export const defaultHead = (ctx: BaseContext, title: string) => /* HTML */ `
${title} · ${ctx.$app.manifest.name}
@@ -10,20 +11,31 @@ export const defaultHead = (ctx: BaseContext, title: string) => /* HTML */ `
`;
+export type HTMLOptions = {
+ preserveScroll?: boolean;
+ morphing?: boolean;
+ navbar?: (ctx: BaseContext) => FlatTemplatable;
+};
+
export default function html(
ctx: BaseContext,
title: string,
body: Templatable,
+ { preserveScroll, morphing, navbar }: HTMLOptions = {},
makeHead: (ctx: BaseContext, title: string) => Templatable = defaultHead
): Readable {
ctx.set("content-type", "text/html;charset=utf-8");
return tempstream/* HTML */ `
-
+
${makeHead(ctx, title)}
+ ${morphing ? `` : ""}
+ ${preserveScroll
+ ? ``
+ : ""}
- ${navbar(ctx)} ${body}
+ ${(navbar || default_navbar)(ctx)} ${body}
`;
}
diff --git a/src/back/jdd-components/components.ts b/src/back/jdd-components/components.ts
index 9a89599..c63b7a9 100644
--- a/src/back/jdd-components/components.ts
+++ b/src/back/jdd-components/components.ts
@@ -5,6 +5,3 @@ export const registry = new Registry();
import { NiceBox } from "./nice-box/nice-box.jdd.js";
registry.add("nice-box", NiceBox);
-
-import { UsingImages } from "./using-images/using-images.jdd.js";
-registry.add("using-images", UsingImages);
diff --git a/src/back/routes/components.css b/src/back/routes/components.css
new file mode 100644
index 0000000..3e5f341
--- /dev/null
+++ b/src/back/routes/components.css
@@ -0,0 +1,27 @@
+.title--components {
+ body {
+ max-width: none;
+ }
+
+ .two-column {
+ display: grid;
+ grid-template-columns: min-content 15px 1fr;
+ }
+
+ .resize-gutter {
+ background-color: gray;
+ cursor: ew-resize;
+ height: 100%;
+ }
+
+ .resizable {
+ width: var(--resizable-column-width);
+ overflow-x: auto;
+ }
+}
+
+.component-preview-parameters {
+ fieldset {
+ background-color: #80808024;
+ }
+}
diff --git a/src/back/routes/components.sreact.tsx b/src/back/routes/components.sreact.tsx
index 1e6ef37..a4e56ac 100644
--- a/src/back/routes/components.sreact.tsx
+++ b/src/back/routes/components.sreact.tsx
@@ -1,13 +1,53 @@
-import { TempstreamJSX, Templatable } from "tempstream";
+import { TempstreamJSX, Templatable, FlatTemplatable, tempstream } from "tempstream";
import { BaseContext } from "koa";
import { StatefulPage } from "@sealcode/sealgen";
import html from "../html.js";
import { registry } from "../jdd-components/components.js";
-import { render, simpleJDDContext } from "@sealcode/jdd";
+import {
+ ComponentArgument,
+ Enum,
+ List,
+ render,
+ simpleJDDContext,
+ Structured,
+} from "@sealcode/jdd";
+import objectPath from "object-path";
export const actionName = "Components";
-const actions = {} as const;
+const actions = {
+ add_array_item: (
+ state: State,
+ _: Record,
+ arg_path: string[],
+ empty_value: unknown
+ ) => {
+ const args = state.args;
+ objectPath.insert(
+ args,
+ arg_path,
+ empty_value,
+ ((objectPath.get(args, arg_path) as unknown[]) || []).length
+ );
+ return {
+ ...state,
+ args,
+ };
+ },
+ remove_array_item: (
+ state: State,
+ _: Record,
+ arg_path: string[],
+ index_to_remove: number
+ ) => {
+ const args = state.args;
+ objectPath.del(args, [...arg_path, index_to_remove]);
+ return {
+ ...state,
+ args,
+ };
+ },
+} as const;
type State = {
component: string;
@@ -22,7 +62,150 @@ export default new (class ComponentsPage extends StatefulPage ``,
+ });
+ }
+
+ renderListArgument(
+ state: State,
+ arg_path: string[],
+ arg: List>,
+ value: T[] = []
+ ): FlatTemplatable {
+ return (
+
+ );
+ }
+
+ renderStructuredArgument<
+ T extends Structured>>
+ >(
+ state: State,
+ arg_path: string[],
+ arg: T,
+ value: Record
+ ): FlatTemplatable {
+ return (
+
+ );
+ }
+
+ printArgPath(path: string[]): string {
+ return path.map((e) => `[${e}]`).join("");
+ }
+
+ renderEnumArgument>(
+ state: State,
+ arg_path: string[],
+ arg: T,
+ value: string
+ ): FlatTemplatable {
+ return (
+
+
+
+ );
+ }
+
+ renderArgumentInput(
+ state: State,
+ arg_path: string[],
+ arg: ComponentArgument,
+ value: T
+ ): FlatTemplatable {
+ if (value === undefined) {
+ value = arg.getEmptyValue();
+ }
+ if (arg instanceof List) {
+ return this.renderListArgument(state, arg_path, arg, value as T[]);
+ }
+
+ if (arg instanceof Structured) {
+ return this.renderStructuredArgument(
+ state,
+ arg_path,
+ arg,
+ value as Record
+ );
+ }
+
+ if (arg instanceof Enum) {
+ return this.renderEnumArgument(state, arg_path, arg, value as string);
+ }
+ return (
+
+
+
+ );
}
render(ctx: BaseContext, state: State, inputs: Record) {
@@ -30,46 +213,87 @@ export default new (class ComponentsPage extends StatefulPage
- {JSON.stringify(state)}
-
-