@sealcode/sealgen 0.1.7 → 0.2.2

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.
Files changed (165) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.log/ti-101379.log +524 -0
  3. package/.log/ti-131883.log +505 -0
  4. package/.log/ti-138140.log +505 -0
  5. package/.log/ti-138288.log +526 -0
  6. package/.log/ti-292609.log +508 -0
  7. package/.log/ti-304933.log +524 -0
  8. package/.log/ti-312312.log +508 -0
  9. package/.log/ti-314240.log +1617 -0
  10. package/.log/ti-62627.log +508 -0
  11. package/.log/ti-64785.log +508 -0
  12. package/.log/ti-65911.log +533 -0
  13. package/.log/ti-69885.log +548 -0
  14. package/.log/ti-72106.log +551 -0
  15. package/.log/ti-74858.log +508 -0
  16. package/.log/tsserver.log +1610 -902
  17. package/@types/build.d.ts +1 -0
  18. package/@types/cli.d.ts +2 -0
  19. package/@types/forms/controls/checkboxed-list-input.d.ts +16 -0
  20. package/@types/forms/controls/controls.d.ts +9 -0
  21. package/@types/forms/controls/dropdown.d.ts +20 -0
  22. package/@types/forms/controls/editable-collection-subset.d.ts +21 -0
  23. package/@types/forms/controls/form-control.d.ts +8 -0
  24. package/@types/forms/controls/form-field-control.d.ts +13 -0
  25. package/@types/forms/controls/form-header.d.ts +9 -0
  26. package/@types/forms/controls/form-paragraph.d.ts +7 -0
  27. package/@types/forms/controls/frame.d.ts +17 -0
  28. package/@types/forms/controls/simple-input.d.ts +28 -0
  29. package/@types/forms/controls.d.ts +0 -0
  30. package/@types/forms/field.d.ts +42 -0
  31. package/@types/forms/form-fields-list.d.ts +4 -0
  32. package/@types/forms/form.d.ts +42 -0
  33. package/@types/forms/validator.d.ts +3 -0
  34. package/@types/index.d.ts +14 -2
  35. package/@types/page/access-control.d.ts +6 -0
  36. package/@types/page/list.d.ts +59 -0
  37. package/@types/page/page.d.ts +26 -0
  38. package/@types/page/props-parser.d.ts +20 -0
  39. package/@types/run.d.ts +0 -0
  40. package/@types/templates/form.d.ts +1 -1
  41. package/@types/templates/list.d.ts +1 -0
  42. package/@types/templates/page.d.ts +1 -1
  43. package/@types/utils/list-collections.d.ts +1 -0
  44. package/@types/utils/natural-numbers.d.ts +1 -0
  45. package/@types/utils/sleep.d.ts +1 -0
  46. package/@types/utils/url-with-new-params.d.ts +2 -0
  47. package/@types/utils/utils.d.ts +9 -0
  48. package/@types/utils/walk.d.ts +1 -1
  49. package/lib/add-collection.js +13 -24
  50. package/lib/add-collection.js.map +1 -1
  51. package/lib/add-route.js +25 -32
  52. package/lib/add-route.js.map +1 -1
  53. package/lib/build.js +108 -0
  54. package/lib/build.js.map +1 -0
  55. package/lib/cli.js +30 -0
  56. package/lib/cli.js.map +1 -0
  57. package/lib/find-scss-includes.js +10 -20
  58. package/lib/find-scss-includes.js.map +1 -1
  59. package/lib/forms/controls/checkboxed-list-input.js +43 -0
  60. package/lib/forms/controls/checkboxed-list-input.js.map +1 -0
  61. package/lib/forms/controls/controls.js +22 -0
  62. package/lib/forms/controls/controls.js.map +1 -0
  63. package/lib/forms/controls/dropdown.js +40 -0
  64. package/lib/forms/controls/dropdown.js.map +1 -0
  65. package/lib/forms/controls/editable-collection-subset.js +73 -0
  66. package/lib/forms/controls/editable-collection-subset.js.map +1 -0
  67. package/lib/forms/controls/form-control.js +7 -0
  68. package/lib/forms/controls/form-control.js.map +1 -0
  69. package/lib/forms/controls/form-field-control.js +22 -0
  70. package/lib/forms/controls/form-field-control.js.map +1 -0
  71. package/lib/forms/controls/form-header.js +18 -0
  72. package/lib/forms/controls/form-header.js.map +1 -0
  73. package/lib/forms/controls/form-paragraph.js +16 -0
  74. package/lib/forms/controls/form-paragraph.js.map +1 -0
  75. package/lib/forms/controls/frame.js +29 -0
  76. package/lib/forms/controls/frame.js.map +1 -0
  77. package/lib/forms/controls/simple-input.js +50 -0
  78. package/lib/forms/controls/simple-input.js.map +1 -0
  79. package/lib/forms/controls.js +1 -0
  80. package/lib/forms/controls.js.map +1 -0
  81. package/lib/forms/field.js +80 -0
  82. package/lib/forms/field.js.map +1 -0
  83. package/lib/forms/form-fields-list.js +10 -0
  84. package/lib/forms/form-fields-list.js.map +1 -0
  85. package/lib/forms/form.js +162 -0
  86. package/lib/forms/form.js.map +1 -0
  87. package/lib/forms/validator.js +11 -0
  88. package/lib/forms/validator.js.map +1 -0
  89. package/lib/generate-collections.js +30 -43
  90. package/lib/generate-collections.js.map +1 -1
  91. package/lib/generate-routes.js +29 -42
  92. package/lib/generate-routes.js.map +1 -1
  93. package/lib/index.js +16 -37
  94. package/lib/index.js.map +1 -1
  95. package/lib/page/access-control.js +20 -0
  96. package/lib/page/access-control.js.map +1 -0
  97. package/lib/page/list.js +148 -0
  98. package/lib/page/list.js.map +1 -0
  99. package/lib/page/page.js +20 -0
  100. package/lib/page/page.js.map +1 -0
  101. package/lib/page/props-parser.js +74 -0
  102. package/lib/page/props-parser.js.map +1 -0
  103. package/lib/run.js +1 -0
  104. package/lib/run.js.map +1 -0
  105. package/lib/templates/form.js +2 -2
  106. package/lib/templates/form.js.map +1 -1
  107. package/lib/templates/list.js +61 -0
  108. package/lib/templates/list.js.map +1 -0
  109. package/lib/templates/page.js +3 -1
  110. package/lib/templates/page.js.map +1 -1
  111. package/lib/utils/exec.js +16 -27
  112. package/lib/utils/exec.js.map +1 -1
  113. package/lib/utils/list-collections.js +20 -0
  114. package/lib/utils/list-collections.js.map +1 -0
  115. package/lib/utils/natural-numbers.js +10 -0
  116. package/lib/utils/natural-numbers.js.map +1 -0
  117. package/lib/utils/question.js +15 -26
  118. package/lib/utils/question.js.map +1 -1
  119. package/lib/utils/sleep.js +8 -0
  120. package/lib/utils/sleep.js.map +1 -0
  121. package/lib/utils/url-with-new-params.js +12 -0
  122. package/lib/utils/url-with-new-params.js.map +1 -0
  123. package/lib/utils/utils.js +19 -0
  124. package/lib/utils/utils.js.map +1 -0
  125. package/lib/utils/walk.js +16 -27
  126. package/lib/utils/walk.js.map +1 -1
  127. package/package.json +19 -4
  128. package/src/add-route.ts +14 -9
  129. package/src/build.ts +82 -0
  130. package/src/cli.ts +34 -0
  131. package/src/find-scss-includes.ts +3 -2
  132. package/src/forms/controls/checkboxed-list-input.ts +50 -0
  133. package/src/forms/controls/controls.ts +9 -0
  134. package/src/forms/controls/dropdown.ts +57 -0
  135. package/src/forms/controls/editable-collection-subset.ts +91 -0
  136. package/src/forms/controls/form-control.ts +13 -0
  137. package/src/forms/controls/form-field-control.ts +39 -0
  138. package/src/forms/controls/form-header.ts +16 -0
  139. package/src/forms/controls/form-paragraph.ts +11 -0
  140. package/src/forms/controls/frame.ts +26 -0
  141. package/src/forms/controls/simple-input.ts +73 -0
  142. package/src/forms/controls.ts +0 -0
  143. package/src/forms/field.ts +119 -0
  144. package/src/forms/form-fields-list.ts +7 -0
  145. package/src/forms/form.ts +242 -0
  146. package/src/forms/forms.scss +18 -0
  147. package/src/forms/validator.ts +10 -0
  148. package/src/generate-routes.ts +8 -3
  149. package/src/index.ts +14 -28
  150. package/src/page/access-control.ts +18 -0
  151. package/src/page/list.ts +214 -0
  152. package/src/page/page.ts +31 -0
  153. package/src/page/props-parser.ts +99 -0
  154. package/src/run.ts +0 -0
  155. package/src/templates/form.ts +5 -2
  156. package/src/templates/list.ts +65 -0
  157. package/src/templates/page.ts +4 -2
  158. package/src/utils/list-collections.ts +16 -0
  159. package/src/utils/natural-numbers.ts +5 -0
  160. package/src/utils/sleep.ts +3 -0
  161. package/src/utils/url-with-new-params.ts +9 -0
  162. package/src/utils/utils.ts +10 -0
  163. package/src/utils/walk.ts +2 -2
  164. package/tsconfig.json +23 -25
  165. 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.walk = void 0;
3
+ exports.walkDir = void 0;
13
4
  const fs_1 = require("fs");
14
5
  const path_1 = require("path");
15
- function walk(dir) {
16
- return __awaiter(this, void 0, void 0, function* () {
17
- const entries = yield fs_1.promises.readdir(dir);
18
- let ret = [];
19
- for (const entry of entries) {
20
- const fullpath = path_1.resolve(dir, entry);
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
- const info = yield fs_1.promises.stat(fullpath);
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
- return ret;
32
- });
17
+ else {
18
+ ret = [...ret, fullpath];
19
+ }
20
+ }
21
+ return ret;
33
22
  }
34
- exports.walk = walk;
23
+ exports.walkDir = walkDir;
35
24
  //# sourceMappingURL=walk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"walk.js","sourceRoot":"","sources":["../../src/utils/walk.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2BAAoC;AACpC,+BAA+B;AAE/B,SAAsB,IAAI,CAAC,GAAW;;QACrC,MAAM,OAAO,GAAG,MAAM,aAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,GAAG,GAAa,EAAE,CAAC;QACvB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE;YAC5B,MAAM,QAAQ,GAAG,cAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACrC,4CAA4C;YAC5C,MAAM,IAAI,GAAG,MAAM,aAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAErC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE;gBACvB,4CAA4C;gBAC5C,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;aAC1C;iBAAM;gBACN,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,QAAQ,CAAC,CAAC;aACzB;SACD;QACD,OAAO,GAAG,CAAC;IACZ,CAAC;CAAA;AAhBD,oBAgBC"}
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.7",
3
+ "version": "0.2.2",
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/index.js"
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
- "@sealcode/ts-predicates": "^0.2.1",
36
- "locreq": "^2.0.2"
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> {
@@ -16,22 +17,26 @@ export async function addRoute(): Promise<void> {
16
17
  (s: string) => s.length > 3
17
18
  );
18
19
  const url = await question(
19
- "Enter a full absolute answer for the new route (e.g. /admin/users/add): ",
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 mode =
23
- (await question("Is this a page (p) or a form (f)? ", (a: string) =>
24
- ["p", "f"].includes(a.toLowerCase())
25
- )) == "p"
26
- ? "page"
27
- : "form";
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 = mode == "page" ? pageTemplate : formTemplate;
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 { walk } from "./utils/walk";
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 walk(target_locreq.resolve("src/")))
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,11 @@
1
+ import { FormControl } from "./controls";
2
+
3
+ export class FormParagraph extends FormControl {
4
+ role = <const>"decoration";
5
+ constructor(public text: string) {
6
+ super();
7
+ }
8
+ render() {
9
+ return `<p>${this.text}</p>`;
10
+ }
11
+ }
@@ -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
+ }