@sealcode/sealgen 0.1.8 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +1 -0
- package/@types/build.d.ts +1 -0
- package/@types/cli.d.ts +2 -0
- package/@types/forms/controls/checkboxed-list-input.d.ts +16 -0
- package/@types/forms/controls/controls.d.ts +9 -0
- package/@types/forms/controls/dropdown.d.ts +20 -0
- package/@types/forms/controls/editable-collection-subset.d.ts +21 -0
- package/@types/forms/controls/form-control.d.ts +8 -0
- package/@types/forms/controls/form-field-control.d.ts +13 -0
- package/@types/forms/controls/form-header.d.ts +9 -0
- package/@types/forms/controls/form-paragraph.d.ts +7 -0
- package/@types/forms/controls/frame.d.ts +17 -0
- package/@types/forms/controls/simple-input.d.ts +28 -0
- package/@types/forms/controls.d.ts +0 -0
- package/@types/forms/field.d.ts +42 -0
- package/@types/forms/form-fields-list.d.ts +4 -0
- package/@types/forms/form.d.ts +42 -0
- package/@types/forms/validator.d.ts +3 -0
- package/@types/index.d.ts +14 -2
- package/@types/page/access-control.d.ts +6 -0
- package/@types/page/list.d.ts +59 -0
- package/@types/page/page.d.ts +26 -0
- package/@types/page/props-parser.d.ts +20 -0
- package/@types/run.d.ts +0 -0
- package/@types/templates/form.d.ts +1 -1
- package/@types/templates/list.d.ts +1 -0
- package/@types/templates/page.d.ts +1 -1
- package/@types/utils/list-collections.d.ts +1 -0
- package/@types/utils/natural-numbers.d.ts +1 -0
- package/@types/utils/sleep.d.ts +1 -0
- package/@types/utils/url-with-new-params.d.ts +2 -0
- package/@types/utils/utils.d.ts +9 -0
- package/@types/utils/walk.d.ts +1 -1
- package/lib/add-collection.js +13 -24
- package/lib/add-collection.js.map +1 -1
- package/lib/add-route.js +25 -32
- package/lib/add-route.js.map +1 -1
- package/lib/build.js +108 -0
- package/lib/build.js.map +1 -0
- package/lib/cli.js +30 -0
- package/lib/cli.js.map +1 -0
- package/lib/find-scss-includes.js +10 -20
- package/lib/find-scss-includes.js.map +1 -1
- package/lib/forms/controls/checkboxed-list-input.js +43 -0
- package/lib/forms/controls/checkboxed-list-input.js.map +1 -0
- package/lib/forms/controls/controls.js +22 -0
- package/lib/forms/controls/controls.js.map +1 -0
- package/lib/forms/controls/dropdown.js +40 -0
- package/lib/forms/controls/dropdown.js.map +1 -0
- package/lib/forms/controls/editable-collection-subset.js +73 -0
- package/lib/forms/controls/editable-collection-subset.js.map +1 -0
- package/lib/forms/controls/form-control.js +7 -0
- package/lib/forms/controls/form-control.js.map +1 -0
- package/lib/forms/controls/form-field-control.js +22 -0
- package/lib/forms/controls/form-field-control.js.map +1 -0
- package/lib/forms/controls/form-header.js +18 -0
- package/lib/forms/controls/form-header.js.map +1 -0
- package/lib/forms/controls/form-paragraph.js +16 -0
- package/lib/forms/controls/form-paragraph.js.map +1 -0
- package/lib/forms/controls/frame.js +29 -0
- package/lib/forms/controls/frame.js.map +1 -0
- package/lib/forms/controls/simple-input.js +50 -0
- package/lib/forms/controls/simple-input.js.map +1 -0
- package/lib/forms/controls.js +1 -0
- package/lib/forms/controls.js.map +1 -0
- package/lib/forms/field.js +80 -0
- package/lib/forms/field.js.map +1 -0
- package/lib/forms/form-fields-list.js +10 -0
- package/lib/forms/form-fields-list.js.map +1 -0
- package/lib/forms/form.js +162 -0
- package/lib/forms/form.js.map +1 -0
- package/lib/forms/validator.js +11 -0
- package/lib/forms/validator.js.map +1 -0
- package/lib/generate-collections.js +30 -43
- package/lib/generate-collections.js.map +1 -1
- package/lib/generate-routes.js +29 -42
- package/lib/generate-routes.js.map +1 -1
- package/lib/index.js +16 -37
- package/lib/index.js.map +1 -1
- package/lib/page/access-control.js +20 -0
- package/lib/page/access-control.js.map +1 -0
- package/lib/page/list.js +148 -0
- package/lib/page/list.js.map +1 -0
- package/lib/page/page.js +20 -0
- package/lib/page/page.js.map +1 -0
- package/lib/page/props-parser.js +74 -0
- package/lib/page/props-parser.js.map +1 -0
- package/lib/run.js +1 -0
- package/lib/run.js.map +1 -0
- package/lib/templates/form.js +2 -2
- package/lib/templates/form.js.map +1 -1
- package/lib/templates/list.js +61 -0
- package/lib/templates/list.js.map +1 -0
- package/lib/templates/page.js +3 -1
- package/lib/templates/page.js.map +1 -1
- package/lib/utils/exec.js +16 -27
- package/lib/utils/exec.js.map +1 -1
- package/lib/utils/list-collections.js +20 -0
- package/lib/utils/list-collections.js.map +1 -0
- package/lib/utils/natural-numbers.js +10 -0
- package/lib/utils/natural-numbers.js.map +1 -0
- package/lib/utils/question.js +15 -26
- package/lib/utils/question.js.map +1 -1
- package/lib/utils/sleep.js +8 -0
- package/lib/utils/sleep.js.map +1 -0
- package/lib/utils/url-with-new-params.js +12 -0
- package/lib/utils/url-with-new-params.js.map +1 -0
- package/lib/utils/utils.js +19 -0
- package/lib/utils/utils.js.map +1 -0
- package/lib/utils/walk.js +16 -27
- package/lib/utils/walk.js.map +1 -1
- package/package.json +19 -4
- package/src/add-route.ts +13 -8
- package/src/build.ts +82 -0
- package/src/cli.ts +34 -0
- package/src/find-scss-includes.ts +3 -2
- package/src/forms/controls/checkboxed-list-input.ts +50 -0
- package/src/forms/controls/controls.ts +9 -0
- package/src/forms/controls/dropdown.ts +57 -0
- package/src/forms/controls/editable-collection-subset.ts +91 -0
- package/src/forms/controls/form-control.ts +13 -0
- package/src/forms/controls/form-field-control.ts +39 -0
- package/src/forms/controls/form-header.ts +16 -0
- package/src/forms/controls/form-paragraph.ts +11 -0
- package/src/forms/controls/frame.ts +26 -0
- package/src/forms/controls/simple-input.ts +73 -0
- package/src/forms/controls.ts +0 -0
- package/src/forms/field.ts +119 -0
- package/src/forms/form-fields-list.ts +7 -0
- package/src/forms/form.ts +242 -0
- package/src/forms/forms.scss +18 -0
- package/src/forms/validator.ts +10 -0
- package/src/generate-routes.ts +8 -3
- package/src/index.ts +14 -28
- package/src/page/access-control.ts +18 -0
- package/src/page/list.ts +214 -0
- package/src/page/page.ts +31 -0
- package/src/page/props-parser.ts +99 -0
- package/src/run.ts +0 -0
- package/src/templates/form.ts +5 -2
- package/src/templates/list.ts +65 -0
- package/src/templates/page.ts +4 -2
- package/src/utils/list-collections.ts +16 -0
- package/src/utils/natural-numbers.ts +5 -0
- package/src/utils/sleep.ts +3 -0
- package/src/utils/url-with-new-params.ts +9 -0
- package/src/utils/utils.ts +10 -0
- package/src/utils/walk.ts +2 -2
- package/tsconfig.json +23 -25
- package/.log/ti-101379.log +0 -505
- package/.log/ti-13490.log +0 -652
- package/.log/ti-46589.log +0 -675
- package/.log/tsserver.log +0 -146
- package/src/tsconfig.json +0 -18
package/lib/utils/walk.js
CHANGED
|
@@ -1,35 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
3
|
+
exports.walkDir = void 0;
|
|
13
4
|
const fs_1 = require("fs");
|
|
14
5
|
const path_1 = require("path");
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
async function walkDir(dir) {
|
|
7
|
+
const entries = await fs_1.promises.readdir(dir);
|
|
8
|
+
let ret = [];
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const fullpath = path_1.resolve(dir, entry);
|
|
11
|
+
// eslint-disable-next-line no-await-in-loop
|
|
12
|
+
const info = await fs_1.promises.stat(fullpath);
|
|
13
|
+
if (info.isDirectory()) {
|
|
21
14
|
// eslint-disable-next-line no-await-in-loop
|
|
22
|
-
|
|
23
|
-
if (info.isDirectory()) {
|
|
24
|
-
// eslint-disable-next-line no-await-in-loop
|
|
25
|
-
ret = [...ret, ...(yield walk(fullpath))];
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
ret = [...ret, fullpath];
|
|
29
|
-
}
|
|
15
|
+
ret = [...ret, ...(await walkDir(fullpath))];
|
|
30
16
|
}
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
else {
|
|
18
|
+
ret = [...ret, fullpath];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return ret;
|
|
33
22
|
}
|
|
34
|
-
exports.
|
|
23
|
+
exports.walkDir = walkDir;
|
|
35
24
|
//# sourceMappingURL=walk.js.map
|
package/lib/utils/walk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"walk.js","sourceRoot":"","sources":["../../src/utils/walk.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"walk.js","sourceRoot":"","sources":["../../src/utils/walk.ts"],"names":[],"mappings":";;;AAAA,2BAAoC;AACpC,+BAA+B;AAExB,KAAK,UAAU,OAAO,CAAC,GAAW;IACxC,MAAM,OAAO,GAAG,MAAM,aAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,GAAG,GAAa,EAAE,CAAC;IACvB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE;QAC5B,MAAM,QAAQ,GAAG,cAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACrC,4CAA4C;QAC5C,MAAM,IAAI,GAAG,MAAM,aAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAErC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE;YACvB,4CAA4C;YAC5C,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;SAC7C;aAAM;YACN,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,QAAQ,CAAC,CAAC;SACzB;KACD;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAhBD,0BAgBC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealcode/sealgen",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Module to automate adding routes and collections to a sealious application",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,12 +10,14 @@
|
|
|
10
10
|
"test-reports": "npm run build && rm -fr .xunit coverage && npm run test -- --cover --test-report"
|
|
11
11
|
},
|
|
12
12
|
"bin": {
|
|
13
|
-
"sealgen": "./lib/
|
|
13
|
+
"sealgen": "./lib/cli.js"
|
|
14
14
|
},
|
|
15
15
|
"author": "",
|
|
16
16
|
"license": "ISC",
|
|
17
17
|
"devDependencies": {
|
|
18
|
+
"@types/koa__router": "^8.0.4",
|
|
18
19
|
"@types/mocha": "^9.0.0",
|
|
20
|
+
"@types/prompts": "^2.0.14",
|
|
19
21
|
"@typescript-eslint/eslint-plugin": "^4.29.3",
|
|
20
22
|
"@typescript-eslint/parser": "^4.29.3",
|
|
21
23
|
"eslint": "^7.32.0",
|
|
@@ -32,7 +34,20 @@
|
|
|
32
34
|
},
|
|
33
35
|
"types": "./@types/index.d.ts",
|
|
34
36
|
"dependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"
|
|
37
|
+
"@koa/router": "^10.1.1",
|
|
38
|
+
"@sealcode/ts-predicates": "^0.4.0",
|
|
39
|
+
"esbuild": "^0.14.54",
|
|
40
|
+
"esbuild-sass-plugin": "^2.3.1",
|
|
41
|
+
"koa": "^2.13.0",
|
|
42
|
+
"locreq": "^2.0.2",
|
|
43
|
+
"merge": "^2.1.1",
|
|
44
|
+
"prompts": "^2.4.2",
|
|
45
|
+
"tempstream": "^0.0.19",
|
|
46
|
+
"tiny-glob": "^0.2.9"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@koa/router": "^10.1.1",
|
|
50
|
+
"koa": "^2.13.0",
|
|
51
|
+
"sealious": "0.14.x"
|
|
37
52
|
}
|
|
38
53
|
}
|
package/src/add-route.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { escape_url_params } from "./utils/escape-url-params";
|
|
|
8
8
|
|
|
9
9
|
import _locreq from "locreq";
|
|
10
10
|
import { generateRoutes } from "./generate-routes";
|
|
11
|
+
import { listTemplate } from "./templates/list";
|
|
11
12
|
const target_locreq = _locreq(process.cwd());
|
|
12
13
|
|
|
13
14
|
export async function addRoute(): Promise<void> {
|
|
@@ -19,19 +20,23 @@ export async function addRoute(): Promise<void> {
|
|
|
19
20
|
"Enter a full absolute path for the new route (e.g. /admin/users/add): ",
|
|
20
21
|
(s: string) => s.trim()[0] == "/"
|
|
21
22
|
);
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const mode_answer = (await question(
|
|
24
|
+
"Is this a page (p), a form (f), or a list (l)? ",
|
|
25
|
+
(a: string) => ["p", "f", "l"].includes(a.toLowerCase())
|
|
26
|
+
)) as "p" | "f" | "l";
|
|
27
|
+
const mode = (<const>{ p: "page", f: "form", l: "list" })[mode_answer];
|
|
28
|
+
|
|
28
29
|
const file_path = target_locreq.resolve(
|
|
29
30
|
`src/back/routes/${escape_url_params(url)}.${mode}.ts`
|
|
30
31
|
);
|
|
31
32
|
const test_file_path = target_locreq.resolve(
|
|
32
33
|
`src/back/routes/${escape_url_params(url)}.test.ts`
|
|
33
34
|
);
|
|
34
|
-
const template =
|
|
35
|
+
const template = {
|
|
36
|
+
page: pageTemplate,
|
|
37
|
+
form: formTemplate,
|
|
38
|
+
list: listTemplate,
|
|
39
|
+
}[mode];
|
|
35
40
|
|
|
36
41
|
if (existsSync(file_path)) {
|
|
37
42
|
// eslint-disable-next-line no-console
|
|
@@ -39,7 +44,7 @@ export async function addRoute(): Promise<void> {
|
|
|
39
44
|
return;
|
|
40
45
|
}
|
|
41
46
|
await fs.mkdir(resolve(file_path, "../"), { recursive: true });
|
|
42
|
-
await fs.writeFile(file_path, template(action, file_path));
|
|
47
|
+
await fs.writeFile(file_path, await template(action, file_path));
|
|
43
48
|
await fs.writeFile(test_file_path, testTemplate(action, file_path));
|
|
44
49
|
// eslint-disable-next-line no-console
|
|
45
50
|
console.log(`${file_path} created`);
|
package/src/build.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { build, BuildResult } from "esbuild";
|
|
2
|
+
import { sassPlugin } from "esbuild-sass-plugin";
|
|
3
|
+
import * as chokidar from "chokidar";
|
|
4
|
+
import { default as glob } from "tiny-glob";
|
|
5
|
+
import { generateScssIncludes } from "./find-scss-includes";
|
|
6
|
+
import { sleep } from "./utils/sleep";
|
|
7
|
+
|
|
8
|
+
async function build_ts() {
|
|
9
|
+
const entryPoints = await glob("./src/back/**/*.ts");
|
|
10
|
+
await build({
|
|
11
|
+
entryPoints,
|
|
12
|
+
sourcemap: true,
|
|
13
|
+
outdir: "./dist/back",
|
|
14
|
+
logLevel: "info",
|
|
15
|
+
platform: "node",
|
|
16
|
+
target: "node16",
|
|
17
|
+
format: "cjs",
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function complex_build(watch: boolean): Promise<void> {
|
|
22
|
+
let scss_build: BuildResult | null = null;
|
|
23
|
+
if (watch) {
|
|
24
|
+
const scss_watcher = chokidar.watch("src", { ignoreInitial: true });
|
|
25
|
+
scss_watcher.on("all", async (_, path) => {
|
|
26
|
+
if (!scss_build) return;
|
|
27
|
+
if (path.endsWith(".scss") && !path.endsWith("/includes.scss")) {
|
|
28
|
+
// refresh the list of all scss files in includes.scss
|
|
29
|
+
await generateScssIncludes();
|
|
30
|
+
try {
|
|
31
|
+
await scss_build?.rebuild?.();
|
|
32
|
+
console.log(`Built main.scss [on ${path}]`);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.error(e);
|
|
35
|
+
await sleep(200);
|
|
36
|
+
scss_build
|
|
37
|
+
?.rebuild?.()
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
39
|
+
.catch((e) => console.error(e?.message));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (path.endsWith(".ts")) {
|
|
43
|
+
await build_ts();
|
|
44
|
+
console.log(`Finished TS biuld [on ${path}]`);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
scss_build = await build({
|
|
51
|
+
entryPoints: ["./src/main.scss"],
|
|
52
|
+
sourcemap: true,
|
|
53
|
+
outfile: "./public/dist/style.css",
|
|
54
|
+
logLevel: "info",
|
|
55
|
+
incremental: watch,
|
|
56
|
+
plugins: [sassPlugin()],
|
|
57
|
+
});
|
|
58
|
+
await build_ts();
|
|
59
|
+
watch && scss_build?.rebuild?.();
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error(e);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function buildProject(...args: string[]): Promise<void> {
|
|
66
|
+
const watch = args.includes("--watch");
|
|
67
|
+
await build({
|
|
68
|
+
entryPoints: ["./src/front/index.ts"],
|
|
69
|
+
sourcemap: true,
|
|
70
|
+
outfile: "./public/dist/bundle.js",
|
|
71
|
+
logLevel: "info",
|
|
72
|
+
bundle: true,
|
|
73
|
+
watch,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await complex_build(watch);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
80
|
+
console.error(e.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { addCollection } from "./add-collection";
|
|
3
|
+
import { addRoute } from "./add-route";
|
|
4
|
+
import { buildProject } from "./build";
|
|
5
|
+
import { generateScssIncludes } from "./find-scss-includes";
|
|
6
|
+
import { generateCollections } from "./generate-collections";
|
|
7
|
+
import { generateRoutes } from "./generate-routes";
|
|
8
|
+
|
|
9
|
+
const actions: Record<
|
|
10
|
+
string,
|
|
11
|
+
(...args: string[]) => Promise<void> | undefined
|
|
12
|
+
> = {
|
|
13
|
+
"add-collection": addCollection,
|
|
14
|
+
"add-route": addRoute,
|
|
15
|
+
"generate-collections": generateCollections,
|
|
16
|
+
"generate-routes": generateRoutes,
|
|
17
|
+
"generate-scss-includes": generateScssIncludes,
|
|
18
|
+
build: buildProject,
|
|
19
|
+
default: async function () {
|
|
20
|
+
console.log("Usage: `sealscript <action>`");
|
|
21
|
+
console.log(
|
|
22
|
+
`Available actions: ${Object.keys(actions)
|
|
23
|
+
.filter((e) => e != "default")
|
|
24
|
+
.join(", ")}`
|
|
25
|
+
);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
void (async function () {
|
|
30
|
+
const action = process.argv.at(2);
|
|
31
|
+
actions;
|
|
32
|
+
const fn = actions[action || "default"] || actions.default;
|
|
33
|
+
await fn(...process.argv.slice(3));
|
|
34
|
+
})();
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
2
|
import _locreq from "locreq";
|
|
3
3
|
import { relative } from "path";
|
|
4
|
-
import {
|
|
4
|
+
import { walkDir } from "./utils/walk";
|
|
5
5
|
|
|
6
6
|
const target_locreq = _locreq(process.cwd());
|
|
7
7
|
|
|
8
8
|
export async function generateScssIncludes(): Promise<void> {
|
|
9
9
|
const files = await Promise.all(
|
|
10
|
-
(await
|
|
10
|
+
(await walkDir(target_locreq.resolve("src/")))
|
|
11
11
|
.filter(
|
|
12
12
|
(f) =>
|
|
13
13
|
f.endsWith(".scss") &&
|
|
@@ -23,6 +23,7 @@ export async function generateScssIncludes(): Promise<void> {
|
|
|
23
23
|
target_locreq.resolve("src/includes.scss"),
|
|
24
24
|
`// DO NOT EDIT! This file is generated automaticaly with npx sealgen generate-scss-includes
|
|
25
25
|
|
|
26
|
+
@import "../node_modules/@sealcode/sealgen/src/forms/forms.scss";
|
|
26
27
|
${files.map((path) => `@import "${path}";`).join("\n")}
|
|
27
28
|
`
|
|
28
29
|
);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { is, predicates } from "@sealcode/ts-predicates";
|
|
2
|
+
import { BaseContext } from "koa";
|
|
3
|
+
import { tempstream } from "tempstream";
|
|
4
|
+
import { ChekboxedListField, FormField } from "../field";
|
|
5
|
+
import { Form, FormData } from "../form";
|
|
6
|
+
import { FormFieldsList } from "../form-fields-list";
|
|
7
|
+
import { FormFieldControl } from "./controls";
|
|
8
|
+
|
|
9
|
+
export class CheckboxedListInput extends FormFieldControl {
|
|
10
|
+
constructor(
|
|
11
|
+
public fieldname: string,
|
|
12
|
+
public options: { label: string } = { label: fieldname }
|
|
13
|
+
) {
|
|
14
|
+
super([fieldname]);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
isValidFieldName(form: Form) {
|
|
18
|
+
return form.fields.some((f) => f.name == this.fieldname);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async _render(ctx: BaseContext, fields: FormField[], data: FormData) {
|
|
22
|
+
// safe to disable this as isValidFieldName takes care of checking if the field is of this type
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
24
|
+
const field = FormFieldsList.getField(
|
|
25
|
+
fields,
|
|
26
|
+
this.fieldname
|
|
27
|
+
) as ChekboxedListField;
|
|
28
|
+
const pickedValues = data.values[field.name] || "";
|
|
29
|
+
if (!is(pickedValues, predicates.array(predicates.string))) {
|
|
30
|
+
throw new Error("picked values is not an array of strings");
|
|
31
|
+
}
|
|
32
|
+
const [options, isVisible] = await Promise.all([
|
|
33
|
+
field.generateOptions(ctx),
|
|
34
|
+
field.isVisible(ctx),
|
|
35
|
+
]);
|
|
36
|
+
return tempstream/* HTML */ `${isVisible
|
|
37
|
+
? Object.entries(options).map(
|
|
38
|
+
([value, text]) => /* HTML */ `<div>
|
|
39
|
+
<input
|
|
40
|
+
type="checkbox"
|
|
41
|
+
id="${field.name}.${value}"
|
|
42
|
+
name="${field.name}.${value}"
|
|
43
|
+
${pickedValues.includes(value) ? "checked" : ""}
|
|
44
|
+
/>
|
|
45
|
+
<label for="${field.name}.${value}">${text}</label>
|
|
46
|
+
</div>`
|
|
47
|
+
)
|
|
48
|
+
: ""}`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { FormControl } from "./form-control";
|
|
2
|
+
export { FormFieldControl } from "./form-field-control";
|
|
3
|
+
export { EditableCollectionSubset } from "./editable-collection-subset";
|
|
4
|
+
export { Frame } from "./frame";
|
|
5
|
+
export { CheckboxedListInput } from "./checkboxed-list-input";
|
|
6
|
+
export { Dropdown } from "./dropdown";
|
|
7
|
+
export { SimpleInput } from "./simple-input";
|
|
8
|
+
export { FormHeader } from "./form-header";
|
|
9
|
+
export { FormParagraph } from "./form-paragraph";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { BaseContext } from "koa";
|
|
2
|
+
import { tempstream } from "tempstream";
|
|
3
|
+
import { FormField, PickFromListField } from "../field";
|
|
4
|
+
import { FormData } from "../form";
|
|
5
|
+
import { FormFieldsList } from "../form-fields-list";
|
|
6
|
+
import { FormFieldControl } from "./controls";
|
|
7
|
+
|
|
8
|
+
export class Dropdown extends FormFieldControl {
|
|
9
|
+
constructor(
|
|
10
|
+
public fieldname: string,
|
|
11
|
+
public options: {
|
|
12
|
+
label: string;
|
|
13
|
+
autosubmit?: boolean;
|
|
14
|
+
autocomplete?: boolean;
|
|
15
|
+
} = {
|
|
16
|
+
label: fieldname,
|
|
17
|
+
autosubmit: false,
|
|
18
|
+
autocomplete: true,
|
|
19
|
+
}
|
|
20
|
+
) {
|
|
21
|
+
super([fieldname]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
areFieldNamesValid(fields: FormField[]) {
|
|
25
|
+
return (
|
|
26
|
+
super.areFieldNamesValid(fields) &&
|
|
27
|
+
FormFieldsList.getField(fields, this.fieldname) instanceof PickFromListField
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_render(ctx: BaseContext, fields: FormField[], data: FormData) {
|
|
32
|
+
// safe to disable this as isValidFieldName takes care of checking if the field is of this type
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
34
|
+
const field = FormFieldsList.getField(
|
|
35
|
+
fields,
|
|
36
|
+
this.fieldname
|
|
37
|
+
) as PickFromListField;
|
|
38
|
+
const picked_value = data.values[field.name] || "";
|
|
39
|
+
const id = field.name;
|
|
40
|
+
return tempstream/* HTML */ `<label for="${id}">${this.options.label}</label
|
|
41
|
+
><select
|
|
42
|
+
name="${this.fieldnames}"
|
|
43
|
+
id="${id}"
|
|
44
|
+
${this.options.autosubmit ? `onchange='this.form.submit()'` : ""}
|
|
45
|
+
${!this.options.autocomplete ? `autocomplete="off"` : ""}
|
|
46
|
+
>
|
|
47
|
+
${Promise.resolve(field.generateOptions(ctx)).then((options) =>
|
|
48
|
+
Object.entries(options).map(
|
|
49
|
+
([value, text]) =>
|
|
50
|
+
`<option value="${value}" ${
|
|
51
|
+
(value || "") == picked_value ? "selected" : ""
|
|
52
|
+
}>${text}</option>`
|
|
53
|
+
)
|
|
54
|
+
)}
|
|
55
|
+
</select>`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { BaseContext } from "koa";
|
|
2
|
+
import { Templatable, tempstream } from "tempstream";
|
|
3
|
+
import { FormField, PickFromListField } from "../field";
|
|
4
|
+
import { FormData } from "../form";
|
|
5
|
+
import { FormFieldsList } from "../form-fields-list";
|
|
6
|
+
import { FormFieldControl } from "./controls";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This control has own forms in it so if you want to use it you
|
|
10
|
+
* probably shouldn't use `await super.render(ctx, data, path)` in
|
|
11
|
+
* render method of you from implementation and you should write
|
|
12
|
+
* your own implementation of this method. See forms that uses
|
|
13
|
+
* this control for reference.
|
|
14
|
+
*/
|
|
15
|
+
export class EditableCollectionSubset extends FormFieldControl {
|
|
16
|
+
constructor(
|
|
17
|
+
public fieldname: string,
|
|
18
|
+
public actionname: string,
|
|
19
|
+
public listLabel?: string,
|
|
20
|
+
public selectLabel?: string
|
|
21
|
+
) {
|
|
22
|
+
super([fieldname]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_render(
|
|
26
|
+
ctx: BaseContext,
|
|
27
|
+
fields: FormField[],
|
|
28
|
+
data: FormData<string>
|
|
29
|
+
): Templatable | Promise<Templatable> {
|
|
30
|
+
// safe to disable this as isValidFieldName takes care of checking if the field is of this type
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
32
|
+
const field = FormFieldsList.getField(
|
|
33
|
+
fields,
|
|
34
|
+
this.fieldname
|
|
35
|
+
) as PickFromListField;
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
37
|
+
const values = data.values[this.fieldname] as string[];
|
|
38
|
+
|
|
39
|
+
return tempstream/* HTML */ `<div>
|
|
40
|
+
<ul>
|
|
41
|
+
${Promise.resolve(field.generateOptions(ctx)).then((options) =>
|
|
42
|
+
Object.entries(options)
|
|
43
|
+
.filter(([value]) => values.includes(value))
|
|
44
|
+
.map(
|
|
45
|
+
([value, text]) => /* HTML */ `
|
|
46
|
+
<li>
|
|
47
|
+
<form method="POST" action="${ctx.path}">
|
|
48
|
+
<span>${text}</span>
|
|
49
|
+
<input
|
|
50
|
+
type="hidden"
|
|
51
|
+
name="${this.fieldname}"
|
|
52
|
+
value="${value}"
|
|
53
|
+
/>
|
|
54
|
+
<input
|
|
55
|
+
type="hidden"
|
|
56
|
+
name="${this.actionname}"
|
|
57
|
+
value="list"
|
|
58
|
+
/>
|
|
59
|
+
<input
|
|
60
|
+
type="submit"
|
|
61
|
+
${this.listLabel
|
|
62
|
+
? `value="${this.listLabel}"`
|
|
63
|
+
: ""}
|
|
64
|
+
/>
|
|
65
|
+
</form>
|
|
66
|
+
</li>
|
|
67
|
+
`
|
|
68
|
+
)
|
|
69
|
+
)}
|
|
70
|
+
</ul>
|
|
71
|
+
<form method="POST" action="${ctx.path}">
|
|
72
|
+
<select name="${this.fieldname}">
|
|
73
|
+
${Promise.resolve(field.generateOptions(ctx)).then((options) =>
|
|
74
|
+
Object.entries(options)
|
|
75
|
+
.filter(([value]) => !values.includes(value))
|
|
76
|
+
.map(
|
|
77
|
+
([value, text]) =>
|
|
78
|
+
`<option value="${value}">${text}</option>`
|
|
79
|
+
)
|
|
80
|
+
)}
|
|
81
|
+
</select>
|
|
82
|
+
<input type="hidden" name="${this.actionname}" value="select" />
|
|
83
|
+
<input
|
|
84
|
+
type="submit"
|
|
85
|
+
${this.selectLabel ? `value="${this.selectLabel}"` : ""}
|
|
86
|
+
/>
|
|
87
|
+
</form>
|
|
88
|
+
</div>`;
|
|
89
|
+
}
|
|
90
|
+
role = <const>"input";
|
|
91
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseContext } from "koa";
|
|
2
|
+
import { Templatable } from "tempstream";
|
|
3
|
+
import { FormField } from "../field";
|
|
4
|
+
import { FormData } from "../form";
|
|
5
|
+
|
|
6
|
+
export abstract class FormControl {
|
|
7
|
+
abstract render(
|
|
8
|
+
ctx: BaseContext,
|
|
9
|
+
formFields: FormField[],
|
|
10
|
+
data: FormData
|
|
11
|
+
): Templatable | Promise<Templatable>;
|
|
12
|
+
abstract role: "input" | "decoration" | "messages" | "submit";
|
|
13
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { BaseContext } from "koa";
|
|
2
|
+
import { Templatable } from "tempstream";
|
|
3
|
+
import { FormField } from "../field";
|
|
4
|
+
import { FormData } from "../form";
|
|
5
|
+
import { FormControl } from "./form-control";
|
|
6
|
+
|
|
7
|
+
export abstract class FormFieldControl extends FormControl {
|
|
8
|
+
role = <const>"input";
|
|
9
|
+
constructor(public fieldnames: string[]) {
|
|
10
|
+
super();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
areFieldNamesValid(fields: FormField[]) {
|
|
14
|
+
return this.fieldnames.every((fieldname) =>
|
|
15
|
+
fields.some((f) => f.name == fieldname)
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
abstract _render(
|
|
20
|
+
ctx: BaseContext,
|
|
21
|
+
fields: FormField[],
|
|
22
|
+
data: FormData
|
|
23
|
+
): Templatable | Promise<Templatable>;
|
|
24
|
+
|
|
25
|
+
render(
|
|
26
|
+
ctx: BaseContext,
|
|
27
|
+
fields: FormField[],
|
|
28
|
+
data: FormData
|
|
29
|
+
): Templatable | Promise<Templatable> {
|
|
30
|
+
if (!this.areFieldNamesValid(fields)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Invalid field names given to form control: "${this.fieldnames.join(
|
|
33
|
+
", "
|
|
34
|
+
)}". Allowed fields are: ${fields.map((f) => f.name).join(", ")}`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return this._render(ctx, fields, data);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseContext } from "koa";
|
|
2
|
+
import { FormControl } from "./controls";
|
|
3
|
+
|
|
4
|
+
export class FormHeader extends FormControl {
|
|
5
|
+
role = <const>"decoration";
|
|
6
|
+
constructor(
|
|
7
|
+
public text: string,
|
|
8
|
+
public isVisible: (ctx: BaseContext) => Promise<boolean> = async () => true
|
|
9
|
+
) {
|
|
10
|
+
super();
|
|
11
|
+
}
|
|
12
|
+
async render(ctx: BaseContext) {
|
|
13
|
+
const isVsbl = await this.isVisible(ctx);
|
|
14
|
+
return isVsbl ? `<h2>${this.text}</h2>` : "";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Templatable } from "tempstream";
|
|
2
|
+
import { FormControl } from "./controls";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This class will render `turbo-frame` tag so that u can
|
|
6
|
+
* embed other route inside your form. This will require
|
|
7
|
+
* to add value to `data: FormData` (inside your master form
|
|
8
|
+
* render function) with key `Frame.FRAME_PATH_KEY`. Value
|
|
9
|
+
* needs to be url to route that you want to embed. If you
|
|
10
|
+
* this value wont be provided frame will redner empty string.
|
|
11
|
+
* See `src/back/routes/profile/[id].form.ts` for an example.
|
|
12
|
+
*/
|
|
13
|
+
export class Frame extends FormControl {
|
|
14
|
+
constructor(public src: string) {
|
|
15
|
+
super();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
render(): Templatable | Promise<Templatable> {
|
|
19
|
+
return /* HTML */ `<turbo-frame
|
|
20
|
+
id="contrahents"
|
|
21
|
+
loading="lazy"
|
|
22
|
+
src="${this.src}"
|
|
23
|
+
></turbo-frame>`;
|
|
24
|
+
}
|
|
25
|
+
role = <const>"decoration";
|
|
26
|
+
}
|