@elaraai/e3-ui 1.0.97 → 1.0.98
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/dist/src/allocation/index.d.ts +258 -7
- package/dist/src/allocation/index.d.ts.map +1 -1
- package/dist/src/allocation/index.js +30 -1
- package/dist/src/allocation/index.js.map +1 -1
- package/dist/src/allocation/types.d.ts +41 -0
- package/dist/src/allocation/types.d.ts.map +1 -1
- package/dist/src/allocation/types.js +35 -0
- package/dist/src/allocation/types.js.map +1 -1
- package/dist/src/bind/data-indexed.d.ts +31 -4
- package/dist/src/bind/data-indexed.d.ts.map +1 -1
- package/dist/src/bind/data-indexed.js +54 -10
- package/dist/src/bind/data-indexed.js.map +1 -1
- package/dist/src/calendar/index.d.ts +540 -14
- package/dist/src/calendar/index.d.ts.map +1 -1
- package/dist/src/calendar/index.js +43 -7
- package/dist/src/calendar/index.js.map +1 -1
- package/dist/src/calendar/paged.d.ts +89 -0
- package/dist/src/calendar/paged.d.ts.map +1 -0
- package/dist/src/calendar/paged.js +148 -0
- package/dist/src/calendar/paged.js.map +1 -0
- package/dist/src/flowchart/payload.d.ts +156 -0
- package/dist/src/flowchart/payload.d.ts.map +1 -1
- package/dist/src/flowchart/window.d.ts +12 -0
- package/dist/src/flowchart/window.d.ts.map +1 -1
- package/dist/src/index.d.ts +11 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/internal.d.ts +11 -5
- package/dist/src/internal.d.ts.map +1 -1
- package/dist/src/internal.js +28 -7
- package/dist/src/internal.js.map +1 -1
- package/dist/src/plan/builders.d.ts +12 -8
- package/dist/src/plan/builders.d.ts.map +1 -1
- package/dist/src/plan/builders.js +12 -8
- package/dist/src/plan/builders.js.map +1 -1
- package/dist/src/plan/event-rows.d.ts +228 -156
- package/dist/src/plan/event-rows.d.ts.map +1 -1
- package/dist/src/plan/event-rows.js +239 -54
- package/dist/src/plan/event-rows.js.map +1 -1
- package/dist/src/plan/index.d.ts +5 -0
- package/dist/src/plan/index.d.ts.map +1 -1
- package/dist/src/plan/index.js +3 -0
- package/dist/src/plan/index.js.map +1 -1
- package/dist/src/plan/ir.d.ts +70 -0
- package/dist/src/plan/ir.d.ts.map +1 -1
- package/dist/src/plan/ordered-source.d.ts +2038 -1
- package/dist/src/plan/ordered-source.d.ts.map +1 -1
- package/dist/src/plan/ordered-source.js +19 -11
- package/dist/src/plan/ordered-source.js.map +1 -1
- package/dist/src/plan/plan.d.ts +513 -32
- package/dist/src/plan/plan.d.ts.map +1 -1
- package/dist/src/plan/plan.js +119 -32
- package/dist/src/plan/plan.js.map +1 -1
- package/dist/src/plan/root.d.ts +4 -2
- package/dist/src/plan/root.d.ts.map +1 -1
- package/dist/src/plan/root.js +6 -4
- package/dist/src/plan/root.js.map +1 -1
- package/dist/src/plan/series.d.ts +13 -0
- package/dist/src/plan/series.d.ts.map +1 -1
- package/dist/src/plan/series.js +61 -7
- package/dist/src/plan/series.js.map +1 -1
- package/dist/src/plan/types.d.ts +14 -0
- package/dist/src/plan/types.d.ts.map +1 -1
- package/dist/src/plan/types.js +8 -0
- package/dist/src/plan/types.js.map +1 -1
- package/dist/src/roster/index.d.ts +608 -20
- package/dist/src/roster/index.d.ts.map +1 -1
- package/dist/src/roster/index.js +103 -29
- package/dist/src/roster/index.js.map +1 -1
- package/dist/src/roster/library.d.ts +12 -2
- package/dist/src/roster/library.d.ts.map +1 -1
- package/dist/src/roster/library.js +8 -0
- package/dist/src/roster/library.js.map +1 -1
- package/dist/src/roster/lists.d.ts +59 -12
- package/dist/src/roster/lists.d.ts.map +1 -1
- package/dist/src/roster/lists.js +51 -58
- package/dist/src/roster/lists.js.map +1 -1
- package/dist/src/roster/times.d.ts +402 -0
- package/dist/src/roster/times.d.ts.map +1 -1
- package/dist/src/roster/types.d.ts +913 -1
- package/dist/src/roster/types.d.ts.map +1 -1
- package/dist/src/roster/types.js +43 -3
- package/dist/src/roster/types.js.map +1 -1
- package/dist/src/schedule/cell-checks.d.ts +85 -0
- package/dist/src/schedule/cell-checks.d.ts.map +1 -0
- package/dist/src/schedule/cell-checks.js +211 -0
- package/dist/src/schedule/cell-checks.js.map +1 -0
- package/dist/src/schedule/events.d.ts +42 -10
- package/dist/src/schedule/events.d.ts.map +1 -1
- package/dist/src/schedule/events.js +349 -155
- package/dist/src/schedule/events.js.map +1 -1
- package/dist/src/schedule/fields.d.ts +134 -0
- package/dist/src/schedule/fields.d.ts.map +1 -0
- package/dist/src/schedule/fields.js +207 -0
- package/dist/src/schedule/fields.js.map +1 -0
- package/dist/src/schedule/library.d.ts +11 -3
- package/dist/src/schedule/library.d.ts.map +1 -1
- package/dist/src/schedule/library.js +9 -6
- package/dist/src/schedule/library.js.map +1 -1
- package/dist/src/schedule/optimise.d.ts +124 -0
- package/dist/src/schedule/optimise.d.ts.map +1 -0
- package/dist/src/schedule/optimise.js +140 -0
- package/dist/src/schedule/optimise.js.map +1 -0
- package/dist/src/schedule/pages.d.ts +76 -0
- package/dist/src/schedule/pages.d.ts.map +1 -0
- package/dist/src/schedule/pages.js +86 -0
- package/dist/src/schedule/pages.js.map +1 -0
- package/dist/src/schedule/record-window.d.ts +119 -6
- package/dist/src/schedule/record-window.d.ts.map +1 -1
- package/dist/src/schedule/record-window.js +139 -60
- package/dist/src/schedule/record-window.js.map +1 -1
- package/dist/src/schedule/resources.d.ts +8 -4
- package/dist/src/schedule/resources.d.ts.map +1 -1
- package/dist/src/schedule/resources.js +35 -15
- package/dist/src/schedule/resources.js.map +1 -1
- package/dist/src/schedule/session.d.ts +14 -0
- package/dist/src/schedule/session.d.ts.map +1 -1
- package/dist/src/schedule/session.js +29 -17
- package/dist/src/schedule/session.js.map +1 -1
- package/dist/src/schedule/types.d.ts +426 -36
- package/dist/src/schedule/types.d.ts.map +1 -1
- package/dist/src/schedule/types.js +63 -17
- package/dist/src/schedule/types.js.map +1 -1
- package/dist/src/schedule/window.d.ts +30 -9
- package/dist/src/schedule/window.d.ts.map +1 -1
- package/dist/src/schedule/window.js +101 -49
- package/dist/src/schedule/window.js.map +1 -1
- package/dist/src/schematic/index.d.ts +277 -0
- package/dist/src/schematic/index.d.ts.map +1 -0
- package/dist/src/schematic/index.js +206 -0
- package/dist/src/schematic/index.js.map +1 -0
- package/dist/src/schematic/kinds.d.ts +501 -0
- package/dist/src/schematic/kinds.d.ts.map +1 -0
- package/dist/src/schematic/kinds.js +1027 -0
- package/dist/src/schematic/kinds.js.map +1 -0
- package/dist/src/schematic/layers.d.ts +74 -0
- package/dist/src/schematic/layers.d.ts.map +1 -0
- package/dist/src/schematic/layers.js +99 -0
- package/dist/src/schematic/layers.js.map +1 -0
- package/dist/src/schematic/library.d.ts +202 -0
- package/dist/src/schematic/library.d.ts.map +1 -0
- package/dist/src/schematic/library.js +244 -0
- package/dist/src/schematic/library.js.map +1 -0
- package/dist/src/schematic/payload.d.ts +8360 -0
- package/dist/src/schematic/payload.d.ts.map +1 -0
- package/dist/src/schematic/payload.js +446 -0
- package/dist/src/schematic/payload.js.map +1 -0
- package/dist/src/schematic/schematic.d.ts +27 -0
- package/dist/src/schematic/schematic.d.ts.map +1 -0
- package/dist/src/schematic/schematic.js +17 -0
- package/dist/src/schematic/schematic.js.map +1 -0
- package/dist/src/schematic/shapes.d.ts +150 -0
- package/dist/src/schematic/shapes.d.ts.map +1 -0
- package/dist/src/schematic/shapes.js +171 -0
- package/dist/src/schematic/shapes.js.map +1 -0
- package/dist/src/schematic/templates.d.ts +96 -0
- package/dist/src/schematic/templates.d.ts.map +1 -0
- package/dist/src/schematic/templates.js +91 -0
- package/dist/src/schematic/templates.js.map +1 -0
- package/dist/src/schematic/types.d.ts +5766 -0
- package/dist/src/schematic/types.d.ts.map +1 -0
- package/dist/src/schematic/types.js +688 -0
- package/dist/src/schematic/types.js.map +1 -0
- package/dist/src/schematic/window.d.ts +212 -0
- package/dist/src/schematic/window.d.ts.map +1 -0
- package/dist/src/schematic/window.js +337 -0
- package/dist/src/schematic/window.js.map +1 -0
- package/dist/src/sheet/editing-bridge.d.ts +4 -2
- package/dist/src/sheet/editing-bridge.d.ts.map +1 -1
- package/dist/src/sheet/editing-bridge.js +8 -2
- package/dist/src/sheet/editing-bridge.js.map +1 -1
- package/dist/src/sheet/editing-types.d.ts +9 -5
- package/dist/src/sheet/editing-types.d.ts.map +1 -1
- package/dist/src/sheet/editing-types.js +9 -5
- package/dist/src/sheet/editing-types.js.map +1 -1
- package/dist/src/sheet/index.d.ts +18 -8
- package/dist/src/sheet/index.d.ts.map +1 -1
- package/dist/src/sheet/index.js +15 -7
- package/dist/src/sheet/index.js.map +1 -1
- package/dist/src/sheet/optimise.d.ts +69 -0
- package/dist/src/sheet/optimise.d.ts.map +1 -0
- package/dist/src/sheet/optimise.js +81 -0
- package/dist/src/sheet/optimise.js.map +1 -0
- package/dist/src/sheet/registers.d.ts +5 -5
- package/dist/src/sheet/registers.js +5 -5
- package/dist/src/sheet/root.d.ts +2 -0
- package/dist/src/sheet/root.d.ts.map +1 -1
- package/dist/src/sheet/root.js +2 -2
- package/dist/src/sheet/root.js.map +1 -1
- package/dist/src/sheet/sheet.d.ts +753 -573
- package/dist/src/sheet/sheet.d.ts.map +1 -1
- package/dist/src/sheet/sheet.js +45 -13
- package/dist/src/sheet/sheet.js.map +1 -1
- package/dist/src/sheet/types.d.ts +92 -12
- package/dist/src/sheet/types.d.ts.map +1 -1
- package/dist/src/sheet/types.js +4 -8
- package/dist/src/sheet/types.js.map +1 -1
- package/dist/src/utils/keyed-rows.d.ts +41 -0
- package/dist/src/utils/keyed-rows.d.ts.map +1 -0
- package/dist/src/utils/keyed-rows.js +64 -0
- package/dist/src/utils/keyed-rows.js.map +1 -0
- package/dist/src/utils/optimise.d.ts +79 -0
- package/dist/src/utils/optimise.d.ts.map +1 -0
- package/dist/src/utils/optimise.js +46 -0
- package/dist/src/utils/optimise.js.map +1 -0
- package/dist/test/allocation/allocation.examples.d.ts +6 -18
- package/dist/test/allocation/allocation.examples.d.ts.map +1 -1
- package/dist/test/allocation/allocation.examples.js +3 -3
- package/dist/test/allocation/allocation.examples.js.map +1 -1
- package/dist/test/allocation/optimise.examples.d.ts +109 -0
- package/dist/test/allocation/optimise.examples.d.ts.map +1 -0
- package/dist/test/allocation/optimise.examples.js +223 -0
- package/dist/test/allocation/optimise.examples.js.map +1 -0
- package/dist/test/bind/data/data.examples.d.ts +540 -36
- package/dist/test/bind/data/data.examples.d.ts.map +1 -1
- package/dist/test/bind/data/data.examples.js +66 -2
- package/dist/test/bind/data/data.examples.js.map +1 -1
- package/dist/test/bind/record/record.examples.d.ts +1 -3
- package/dist/test/bind/record/record.examples.d.ts.map +1 -1
- package/dist/test/calendar/calendar-records.examples.d.ts.map +1 -1
- package/dist/test/calendar/calendar-records.examples.js +3 -3
- package/dist/test/calendar/calendar-records.examples.js.map +1 -1
- package/dist/test/calendar/calendar-windows.examples.d.ts +9 -0
- package/dist/test/calendar/calendar-windows.examples.d.ts.map +1 -0
- package/dist/test/calendar/calendar-windows.examples.js +111 -0
- package/dist/test/calendar/calendar-windows.examples.js.map +1 -0
- package/dist/test/calendar/calendar.examples.d.ts +7 -21
- package/dist/test/calendar/calendar.examples.d.ts.map +1 -1
- package/dist/test/calendar/calendar.examples.js +7 -7
- package/dist/test/calendar/calendar.examples.js.map +1 -1
- package/dist/test/calendar/optimise.examples.d.ts +132 -0
- package/dist/test/calendar/optimise.examples.d.ts.map +1 -0
- package/dist/test/calendar/optimise.examples.js +75 -0
- package/dist/test/calendar/optimise.examples.js.map +1 -0
- package/dist/test/examples-package.d.ts +46 -10
- package/dist/test/examples-package.d.ts.map +1 -1
- package/dist/test/examples-package.js +142 -32
- package/dist/test/examples-package.js.map +1 -1
- package/dist/test/flowchart/flowchart.examples.d.ts +10 -30
- package/dist/test/flowchart/flowchart.examples.d.ts.map +1 -1
- package/dist/test/plan/optimise.examples.d.ts +207 -0
- package/dist/test/plan/optimise.examples.d.ts.map +1 -0
- package/dist/test/plan/optimise.examples.js +98 -0
- package/dist/test/plan/optimise.examples.js.map +1 -0
- package/dist/test/plan/plan-events.examples.d.ts +8 -24
- package/dist/test/plan/plan-events.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-events.examples.js +3 -3
- package/dist/test/plan/plan-events.examples.js.map +1 -1
- package/dist/test/plan/plan-records.examples.d.ts +200 -19
- package/dist/test/plan/plan-records.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-records.examples.js +125 -28
- package/dist/test/plan/plan-records.examples.js.map +1 -1
- package/dist/test/plan/plan-windows.examples.d.ts +853 -36
- package/dist/test/plan/plan-windows.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-windows.examples.js +166 -29
- package/dist/test/plan/plan-windows.examples.js.map +1 -1
- package/dist/test/plan/plan.examples.d.ts +7 -21
- package/dist/test/plan/plan.examples.d.ts.map +1 -1
- package/dist/test/plan/plan.examples.js +11 -9
- package/dist/test/plan/plan.examples.js.map +1 -1
- package/dist/test/roster/domain.examples.d.ts +804 -0
- package/dist/test/roster/domain.examples.d.ts.map +1 -1
- package/dist/test/roster/domain.examples.js +3 -3
- package/dist/test/roster/domain.examples.js.map +1 -1
- package/dist/test/roster/optimise.examples.d.ts +485 -0
- package/dist/test/roster/optimise.examples.d.ts.map +1 -0
- package/dist/test/roster/optimise.examples.js +155 -0
- package/dist/test/roster/optimise.examples.js.map +1 -0
- package/dist/test/roster/roster-records.examples.d.ts +6 -18
- package/dist/test/roster/roster-records.examples.d.ts.map +1 -1
- package/dist/test/roster/roster-records.examples.js +1 -1
- package/dist/test/roster/roster-records.examples.js.map +1 -1
- package/dist/test/roster/roster.examples.d.ts +3391 -21
- package/dist/test/roster/roster.examples.d.ts.map +1 -1
- package/dist/test/roster/roster.examples.js +54 -7
- package/dist/test/roster/roster.examples.js.map +1 -1
- package/dist/test/roster/warehouse.d.ts +150 -7
- package/dist/test/roster/warehouse.d.ts.map +1 -1
- package/dist/test/roster/warehouse.js +36 -11
- package/dist/test/roster/warehouse.js.map +1 -1
- package/dist/test/schedule/schedule.examples.d.ts +1 -3
- package/dist/test/schedule/schedule.examples.d.ts.map +1 -1
- package/dist/test/schematic/schematic-records.examples.d.ts +444 -0
- package/dist/test/schematic/schematic-records.examples.d.ts.map +1 -0
- package/dist/test/schematic/schematic-records.examples.js +205 -0
- package/dist/test/schematic/schematic-records.examples.js.map +1 -0
- package/dist/test/schematic/schematic.examples.d.ts +2289 -0
- package/dist/test/schematic/schematic.examples.d.ts.map +1 -0
- package/dist/test/schematic/schematic.examples.js +453 -0
- package/dist/test/schematic/schematic.examples.js.map +1 -0
- package/dist/test/sheet/optimise.examples.d.ts +177 -0
- package/dist/test/sheet/optimise.examples.d.ts.map +1 -0
- package/dist/test/sheet/optimise.examples.js +75 -0
- package/dist/test/sheet/optimise.examples.js.map +1 -0
- package/dist/test/sheet/sheet-records.examples.d.ts +95 -204
- package/dist/test/sheet/sheet-records.examples.d.ts.map +1 -1
- package/dist/test/sheet/sheet-records.examples.js +42 -17
- package/dist/test/sheet/sheet-records.examples.js.map +1 -1
- package/dist/test/sheet/sheet.examples.d.ts +17 -33
- package/dist/test/sheet/sheet.examples.d.ts.map +1 -1
- package/dist/test/sheet/sheet.examples.js +18 -15
- package/dist/test/sheet/sheet.examples.js.map +1 -1
- package/package.json +11 -11
|
@@ -0,0 +1,1027 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The Schematic's kinds (#1386, #1401, `Schematic Builder Spec.md` §1, §4.1–§4.5):
|
|
7
|
+
* `Schematic.items`, `zones`, `links`, `nets` and `details`. Each takes the
|
|
8
|
+
* app's own record and maps its fields to the Schematic's roles, as Schedule's
|
|
9
|
+
* kinds do (#1365): the Schematic stores nothing in a shape of its own.
|
|
10
|
+
*
|
|
11
|
+
* - **A role is a field of the app's row, named** (SC1). Its type is checked
|
|
12
|
+
* against the role when the code compiles, and again at build: a field the
|
|
13
|
+
* row lacks, or one of another type, is refused, naming the role, the field
|
|
14
|
+
* and the field's type. A role the app computes — a sub-label, a meter, a
|
|
15
|
+
* tone — is an accessor `(row, key) => …`, reified once and called per row.
|
|
16
|
+
* So is a role whose value is one of the Schematic's own types — a
|
|
17
|
+
* footprint, a zone's shape, a pattern, a style: the accessor builds it with
|
|
18
|
+
* the shape helpers from the row's own fields, so the app's record holds
|
|
19
|
+
* only its own types (D35).
|
|
20
|
+
* - **A gesture writes only the fields its roles map** (SC2): a move writes
|
|
21
|
+
* an item's `x` and `y`, a Connect a link's `from` and `to`, a net's Shift
|
|
22
|
+
* session its `sources` and `destinations`, and every other field of the row
|
|
23
|
+
* reads back as it was.
|
|
24
|
+
* - **Geometry is drawn about its entity's anchor** (D22, SC35): a footprint
|
|
25
|
+
* needs the item's box, `width` and `height`, or it is refused.
|
|
26
|
+
* - **How a kind is bound follows how it is read** (§1, rule 4; SC33):
|
|
27
|
+
* editable bound `Record.bind(record, [e3.mutation.patch(record)])` — read
|
|
28
|
+
* whole, or through its source of the slice with `entries:
|
|
29
|
+
* Data.bindPaged(record)` beside it — and read only bound `Data.bind(record)`,
|
|
30
|
+
* read whole, or `Data.bindPaged(record)`, read through its source. Every
|
|
31
|
+
* other form is refused, naming the one to use. A kind's details are bound
|
|
32
|
+
* as their kind is read.
|
|
33
|
+
*
|
|
34
|
+
* A kind closes the record behind East functions over beast2 bytes (§4.8): its
|
|
35
|
+
* stored rows a read holds, each one's roles beside its bytes; a drafted row's
|
|
36
|
+
* roles; a gesture's write and a new row; the fields an author's card sets,
|
|
37
|
+
* any field at any depth (#1390); the shared editing session over the
|
|
38
|
+
* record with its patch door; the record read by key; its history, its form,
|
|
39
|
+
* its own inspector and the app's check. So the payload is one type whatever
|
|
40
|
+
* the rows hold, and the renderer needs no row type. A kind's record is keyed
|
|
41
|
+
* by String: its entities are named by their keys, and a link's ends hold
|
|
42
|
+
* them.
|
|
43
|
+
*
|
|
44
|
+
* @packageDocumentation
|
|
45
|
+
*/
|
|
46
|
+
import { ArrayType, AsyncFunctionType, BlobType, DictType, East, Expr, FloatType, OptionType, PatchType, SetType, StringType, StructType, defaultValue, isTypeEqual, none, printType, some, toEastTypeValue, variant, } from "@elaraai/east";
|
|
47
|
+
import { EditingReadinessType, Fields, FieldSpecType, resolveRowSource } from "@elaraai/east-ui/internal";
|
|
48
|
+
import { RecordOutcomeType } from "../bind/record.js";
|
|
49
|
+
import { scheduleAccessor } from "../schedule/accessor.js";
|
|
50
|
+
import { checkKeyField, fieldOf, fieldSetters, keyFieldRef, keyFieldSlots, keyFieldValue } from "../schedule/fields.js";
|
|
51
|
+
import { scheduleEntriesSeam, scheduleRecordSession } from "../schedule/session.js";
|
|
52
|
+
import { ScheduleEntriesType, ScheduleFieldWriteType, ScheduleReadyEntryType } from "../schedule/types.js";
|
|
53
|
+
import { checkEntries, optionOf, scheduleEntryByKey, scheduleLastKey, typeText } from "../schedule/window.js";
|
|
54
|
+
import { RowInspectorType, rowInspector } from "../utils/row-inspector.js";
|
|
55
|
+
import { checkedSchematicTemplates, isSchematicTemplates } from "./templates.js";
|
|
56
|
+
import { SchematicDetailsEntryType, SchematicDetailsRolesType, SchematicKindDetailsType, SchematicEndsType, SchematicGeometryType, SchematicItemEntryType, SchematicItemKindType, SchematicItemType, SchematicLinkEntryType, SchematicLinkKindType, SchematicLinkStyleType, SchematicLinkType, SchematicMeterType, SchematicNetEntryType, SchematicNetKindType, SchematicNetType, SchematicPointType, SchematicRefType, SchematicRouteType, SchematicSidesType, SchematicStatusCasesFor, SchematicStatusType, SchematicTemplateType, SchematicToneType, SchematicZoneEntryType, SchematicZoneKindType, SchematicZonePatternType, SchematicZoneType, } from "./types.js";
|
|
57
|
+
import { schematicSourceRows, schematicWholeRows } from "./window.js";
|
|
58
|
+
/** The binding forms, as the refusals name them. */
|
|
59
|
+
const FORMS = "Record.bind(record, [e3.mutation.patch(record)]) to edit it, or Data.bind(record) or Data.bindPaged(record) to read it only";
|
|
60
|
+
/**
|
|
61
|
+
* Tells a kind's binding by its handle's East struct (`bind/sources.ts`'
|
|
62
|
+
* rule): a `page` field is `Data.bindPaged`'s, a `mutate` field
|
|
63
|
+
* `Record.bind`'s — checked for its patch door — and any other `Data.bind`'s.
|
|
64
|
+
*
|
|
65
|
+
* @param record - What the kind was given
|
|
66
|
+
* @param where - The kind, as a refusal names it
|
|
67
|
+
* @returns The binding
|
|
68
|
+
* @throws {Error} Naming the forms to use: anything but a binding of a record of structs keyed by String, a paged read of
|
|
69
|
+
* an index's window or one naming no snapshot, and a `Record.bind` without its patch door
|
|
70
|
+
*/
|
|
71
|
+
function boundOf(record, where) {
|
|
72
|
+
const refuse = (why) => { throw new Error(`${where}: \`record\` is the app's record, bound — ${FORMS}${why}`); };
|
|
73
|
+
if (!(record instanceof Expr))
|
|
74
|
+
return refuse("");
|
|
75
|
+
const type = Expr.type(record);
|
|
76
|
+
const fields = type.type === "Struct" ? type.fields : undefined;
|
|
77
|
+
if (fields === undefined)
|
|
78
|
+
return refuse(` — and this is ${printType(type)}`);
|
|
79
|
+
let form;
|
|
80
|
+
let recordType;
|
|
81
|
+
if (fields["page"] !== undefined) {
|
|
82
|
+
form = "paged";
|
|
83
|
+
if (fields["indexes"] !== undefined) {
|
|
84
|
+
return refuse(" — and this paged read serves the record's indexes: bind its own entries, Data.bindPaged(record), and name the indexed read " +
|
|
85
|
+
"as the kind's source of the slice");
|
|
86
|
+
}
|
|
87
|
+
const resolved = resolveRowSource(record, where);
|
|
88
|
+
if (resolved.kind !== "paged") {
|
|
89
|
+
return refuse(" — and this paged read serves an index's window: bind the record's own entries, Data.bindPaged(record)");
|
|
90
|
+
}
|
|
91
|
+
recordType = resolved.collectionType;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
form = fields["mutate"] !== undefined ? "record" : "data";
|
|
95
|
+
const read = fields["read"];
|
|
96
|
+
if (read === undefined || read.type !== "Function" || read.inputs.length !== 0 || fields["binding"] === undefined)
|
|
97
|
+
return refuse("");
|
|
98
|
+
recordType = read.output;
|
|
99
|
+
}
|
|
100
|
+
if (recordType.type !== "Dict" || !isTypeEqual(recordType.key, StringType) || recordType.value.type !== "Struct") {
|
|
101
|
+
throw new Error(`${where}: a Schematic kind's record holds its entities by String key, each a struct — and this one holds ${typeText(recordType)}`);
|
|
102
|
+
}
|
|
103
|
+
const rowType = recordType.value;
|
|
104
|
+
if (form === "paged") {
|
|
105
|
+
// A paged read of the record's own entries, at the snapshot every read of it is at.
|
|
106
|
+
checkEntries(record, StringType, rowType, `${where}: \`record\``);
|
|
107
|
+
}
|
|
108
|
+
if (form === "record") {
|
|
109
|
+
// The patch door: e3.mutation.patch's, the mutation named `patch` taking the record's patch.
|
|
110
|
+
const commit = fields["commit"];
|
|
111
|
+
const door = commit?.type === "Struct" ? commit.fields["patch"] : undefined;
|
|
112
|
+
if (door === undefined || !isTypeEqual(door, AsyncFunctionType([StringType, PatchType(recordType)], RecordOutcomeType))) {
|
|
113
|
+
throw new Error(`${where}: \`record\` is bound without its patch door — ${FORMS}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { form, handle: record, rowType };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A kind's reads, as its binding and its source of the slice decide them
|
|
120
|
+
* (SC33): whole, or through its source; and the record read by key.
|
|
121
|
+
*
|
|
122
|
+
* @param bound - The kind's binding
|
|
123
|
+
* @param entries - Its `entries`, checked
|
|
124
|
+
* @param slot - Its slot
|
|
125
|
+
* @param part - Its source of the slice, when the slice names one after its slot
|
|
126
|
+
* @param where - The kind, as a refusal names it
|
|
127
|
+
* @param every - Whether, read through its source, it reads every row: under `narrowed="dim"` (§10.3)
|
|
128
|
+
* @returns `()` → its rows, by key, and the paged read its rows are read by key through, if any
|
|
129
|
+
* @throws {Error} Naming the form to use, for each binding its read does not take
|
|
130
|
+
*/
|
|
131
|
+
function readsOf(bound, entries, slot, part, where, every) {
|
|
132
|
+
if (part === undefined) {
|
|
133
|
+
if (bound.form === "paged") {
|
|
134
|
+
throw new Error(`${where}: a paged binding has no whole read — read it through its source of the slice, slot "${slot}": ` +
|
|
135
|
+
`slice={{ slice: Slice.bindRecords(key, { ${slot}: { source: Data.bindPaged(record, { indexes }) } }) }} — or bind Data.bind(record) to read it whole`);
|
|
136
|
+
}
|
|
137
|
+
if (entries !== undefined) {
|
|
138
|
+
throw new Error(`${where}: \`entries\` reads the record by key beside its source of the slice — name one "${slot}", ` +
|
|
139
|
+
`or leave \`entries\` out: a kind read whole reads its rows from the record`);
|
|
140
|
+
}
|
|
141
|
+
return { rows: schematicWholeRows(bound.handle, bound.rowType), byKey: undefined };
|
|
142
|
+
}
|
|
143
|
+
if (bound.form === "data") {
|
|
144
|
+
throw new Error(`${where}: Data.bind(record) reads the record whole, which a source of the slice exists to avoid — ` +
|
|
145
|
+
`bind it Data.bindPaged(record) to read it through its source "${slot}", or Record.bind(record, [e3.mutation.patch(record)]) with entries to edit it`);
|
|
146
|
+
}
|
|
147
|
+
if (bound.form === "record" && entries === undefined) {
|
|
148
|
+
throw new Error(`${where}: the slice's source "${slot}" reads only its narrowing — and a row is read by its key, the inspector's, a gesture's ` +
|
|
149
|
+
"original and Save's confirmation: give `entries: Data.bindPaged(record)` beside it, the record's own entries");
|
|
150
|
+
}
|
|
151
|
+
return { rows: schematicSourceRows(part, slot, bound.rowType, where, every), byKey: bound.form === "paged" ? bound.handle : entries };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Checks a kind's `entries` where it declares them: for a record bound to be
|
|
155
|
+
* edited, a paged read of its own entries.
|
|
156
|
+
*
|
|
157
|
+
* @param bound - The kind's binding
|
|
158
|
+
* @param entries - What the kind was given
|
|
159
|
+
* @param where - The kind, as a refusal names it
|
|
160
|
+
* @returns The paged read, checked; `undefined` for none
|
|
161
|
+
* @throws {Error} When a read-only binding is given `entries`, or they are no paged read of the record's own entries
|
|
162
|
+
*/
|
|
163
|
+
function entriesOf(bound, entries, where) {
|
|
164
|
+
if (entries === undefined)
|
|
165
|
+
return undefined;
|
|
166
|
+
if (bound.form !== "record") {
|
|
167
|
+
throw new Error(`${where}: \`entries\` reads an editable record by key — a kind bound ${bound.form === "data" ? "Data.bind" : "Data.bindPaged"} ` +
|
|
168
|
+
`${bound.form === "data" ? "is read whole" : "reads by key through its own binding"}: leave \`entries\` out`);
|
|
169
|
+
}
|
|
170
|
+
return checkEntries(entries, StringType, bound.rowType, `${where}: \`entries\``);
|
|
171
|
+
}
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Shared parts
|
|
174
|
+
// ============================================================================
|
|
175
|
+
/** A field's type by name, refused naming the role when the row lacks it. */
|
|
176
|
+
function fieldTypeOf(rowType, where) {
|
|
177
|
+
const fields = rowType.fields;
|
|
178
|
+
return (role, name) => {
|
|
179
|
+
const type = fields[name];
|
|
180
|
+
if (type === undefined)
|
|
181
|
+
throw new Error(`${where}: \`${role}\` names "${name}", a field the row does not have (${Object.keys(fields).join(", ")})`);
|
|
182
|
+
return type;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** A role naming a field of one type: the field's name, refused naming the role, the field and its type (SC1). */
|
|
186
|
+
function roleField(rowType, where) {
|
|
187
|
+
const typeOf = fieldTypeOf(rowType, where);
|
|
188
|
+
return (role, name, type, what) => {
|
|
189
|
+
if (name === undefined)
|
|
190
|
+
return undefined;
|
|
191
|
+
const held = typeOf(role, name);
|
|
192
|
+
if (!isTypeEqual(held, type))
|
|
193
|
+
throw new Error(`${where}: \`${role}\` names ${what} — "${name}" holds ${typeText(held)}`);
|
|
194
|
+
return name;
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/** An accessor role, reified once and held to the type it returns, naming the role and what it returns (SC1). */
|
|
198
|
+
function accessorRole(rowType, where, role, out, what, fn) {
|
|
199
|
+
if (fn === undefined)
|
|
200
|
+
return undefined;
|
|
201
|
+
if (typeof fn !== "function")
|
|
202
|
+
throw new Error(`${where}: \`${role}\` is an accessor, (row, key) => ${what}`);
|
|
203
|
+
return scheduleAccessor(rowType, StringType, out, fn, (returns) => `${where}: \`${role}\` returns ${what} — and this one returns ${returns}`);
|
|
204
|
+
}
|
|
205
|
+
/** A row of the record's type, field by field: each field `set` names from it, every other from `source`. */
|
|
206
|
+
function rowWith(rowType, source, set) {
|
|
207
|
+
return East.value(Object.fromEntries(Object.keys(rowType.fields).map((f) => [f, set[f] ?? fieldOf(source, f)])), rowType);
|
|
208
|
+
}
|
|
209
|
+
/** The row's fields less those a drop places: what a template gives. */
|
|
210
|
+
function valuesTypeOf(rowType, placed) {
|
|
211
|
+
return StructType(Object.fromEntries(Object.entries(rowType.fields).filter(([name]) => !placed.includes(name))));
|
|
212
|
+
}
|
|
213
|
+
/** Checks a `status` role: a variant field every row holds, a Status per case. */
|
|
214
|
+
function statusOf(rowType, where, config) {
|
|
215
|
+
if (config === undefined)
|
|
216
|
+
return undefined;
|
|
217
|
+
const type = fieldTypeOf(rowType, where)("status.field", config.field);
|
|
218
|
+
if (type.type !== "Variant" || optionOf(type) !== undefined) {
|
|
219
|
+
throw new Error(`${where}: \`status.field\` names a variant field every row holds, a Status per case — "${config.field}" holds ${typeText(type)}`);
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
field: config.field,
|
|
223
|
+
cases: Object.keys(type.cases),
|
|
224
|
+
table: East.value(config.cases, SchematicStatusCasesFor(type)),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/** The status a row shows, by its case: the table's Status for it, the table bound once in the block. */
|
|
228
|
+
function statusShown($, status, row) {
|
|
229
|
+
if (status === undefined)
|
|
230
|
+
return East.value(none, OptionType(SchematicStatusType));
|
|
231
|
+
const table = $.const(status.table);
|
|
232
|
+
return fieldOf(row, status.field).match(Object.fromEntries(status.cases.map((c) => [c, (_$) => East.value(some(fieldOf(table, c)), OptionType(SchematicStatusType))])));
|
|
233
|
+
}
|
|
234
|
+
/** An accessor's value for a row, bound in the block; `otherwise` for a kind with no such accessor. */
|
|
235
|
+
function called($, fn, row, key, wrap, otherwise) {
|
|
236
|
+
if (fn === undefined)
|
|
237
|
+
return otherwise;
|
|
238
|
+
const bound = $.const(fn);
|
|
239
|
+
return wrap(bound(row, key));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* A kind's stored rows a read holds, by key: each one's roles, read in the
|
|
243
|
+
* pass that reads the row, beside its bytes. The renderer puts its drafts in
|
|
244
|
+
* place itself, as the Roster's and the Allocation's do.
|
|
245
|
+
*/
|
|
246
|
+
function storedRows(read, rolesOf, EntryType) {
|
|
247
|
+
const Rows = DictType(StringType, EntryType);
|
|
248
|
+
return East.function([], OptionType(Rows), ($) => {
|
|
249
|
+
const readRows = $.const(read);
|
|
250
|
+
const roles = $.const(rolesOf);
|
|
251
|
+
const result = $.let(East.value(none, OptionType(Rows)), OptionType(Rows));
|
|
252
|
+
$.match(readRows(), {
|
|
253
|
+
some: ($2, held) => {
|
|
254
|
+
const out = $2.let(new Map(), Rows);
|
|
255
|
+
$2.for(held, ($3, row, key) => {
|
|
256
|
+
$3(out.insert(key, East.value({ roles: roles(row, key), row: East.Blob.encodeBeast(row, "v2") }, EntryType)));
|
|
257
|
+
});
|
|
258
|
+
$2.assign(result, some(out));
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
return result;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The rows a reader read of a record by area or by key, made its stored rows
|
|
266
|
+
* (#1393): each row's roles, read in the pass that reads it, beside its bytes,
|
|
267
|
+
* as `rows` gives them — from the rows as beast2 bytes of an `Array` of
|
|
268
|
+
* `{ key, row }` at the record's row type, which the reader encodes from what
|
|
269
|
+
* it read.
|
|
270
|
+
*
|
|
271
|
+
* @param rowType - The record's row type
|
|
272
|
+
* @param rolesOf - A row's roles, from it and its key
|
|
273
|
+
* @param EntryType - A stored row's type: its roles beside its bytes
|
|
274
|
+
* @returns `(bytes)` → the rows, by key
|
|
275
|
+
*/
|
|
276
|
+
function rowsReadOf(rowType, rolesOf, EntryType) {
|
|
277
|
+
const Read = ArrayType(StructType({ key: StringType, row: rowType }));
|
|
278
|
+
const Rows = DictType(StringType, EntryType);
|
|
279
|
+
return East.function([BlobType], Rows, ($, bytes) => {
|
|
280
|
+
const roles = $.const(rolesOf);
|
|
281
|
+
const read = $.const(bytes.decodeBeast(Read, "v2"));
|
|
282
|
+
const out = $.let(new Map(), Rows);
|
|
283
|
+
$.for(read, ($2, entry) => {
|
|
284
|
+
$2(out.insert(entry.key, East.value({ roles: roles(entry.row, entry.key), row: East.Blob.encodeBeast(entry.row, "v2") }, EntryType)));
|
|
285
|
+
});
|
|
286
|
+
return out;
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/** A drafted row's roles, from its bytes and its key: what `rows` gives a stored one. */
|
|
290
|
+
function readOf(rowType, rolesOf, RolesType) {
|
|
291
|
+
return East.function([BlobType, StringType], RolesType, ($, bytes, key) => {
|
|
292
|
+
const roles = $.const(rolesOf);
|
|
293
|
+
return roles(bytes.decodeBeast(rowType, "v2"), key);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/** The app's check over drafted rows, one result each; a check that throws refuses its own row alone. */
|
|
297
|
+
function readyOf(rowType, ready) {
|
|
298
|
+
if (ready === undefined)
|
|
299
|
+
return undefined;
|
|
300
|
+
const author = East.function([rowType, StringType], EditingReadinessType, ($, row, key) => $.const(ready(row, key), EditingReadinessType));
|
|
301
|
+
return East.function([ArrayType(ScheduleReadyEntryType)], ArrayType(EditingReadinessType), ($, batch) => {
|
|
302
|
+
const check = $.const(author);
|
|
303
|
+
return batch.map(($2, item) => {
|
|
304
|
+
const result = $2.let(variant("ready", null), EditingReadinessType);
|
|
305
|
+
$2.try(($3) => {
|
|
306
|
+
$3.assign(result, check(item.entry.decodeBeast(rowType, "v2"), item.id));
|
|
307
|
+
}).catch(($3, message) => {
|
|
308
|
+
$3.assign(result, variant("invalid", [{ field: "", message: East.str `Readiness check failed: ${message}` }]));
|
|
309
|
+
});
|
|
310
|
+
return result;
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* The row with each of a card's field writes applied in order, and every other
|
|
316
|
+
* field as it was (#1390): what an author's card dropped on an entity sets —
|
|
317
|
+
* the fields its `Schematic.patch` sets, each a field at any depth, its value
|
|
318
|
+
* as bytes at the field's type — through the setter Schedule's kinds write an
|
|
319
|
+
* inspector's edit with (`fieldSetters`).
|
|
320
|
+
*
|
|
321
|
+
* @param rowType - The record's row type
|
|
322
|
+
* @returns `(bytes, writes)` → the row written; `none` when a write names no field of the row
|
|
323
|
+
*/
|
|
324
|
+
function rowSetter(rowType) {
|
|
325
|
+
const setters = fieldSetters(rowType);
|
|
326
|
+
return East.function([BlobType, ArrayType(ScheduleFieldWriteType)], OptionType(BlobType), ($, bytes, writes) => {
|
|
327
|
+
// Each setter's path as East holds it: a field is named by its steps, never by text a dotted name could mimic.
|
|
328
|
+
const fns = setters.map((setter) => ({ path: $.const([...setter.path], ArrayType(StringType)), set: $.const(setter.set) }));
|
|
329
|
+
const row = $.let(bytes.decodeBeast(rowType, "v2"), rowType);
|
|
330
|
+
const named = $.let(true);
|
|
331
|
+
$.for(writes, ($2, write) => {
|
|
332
|
+
const found = $2.let(false);
|
|
333
|
+
for (const fn of fns) {
|
|
334
|
+
$2.if(East.equal(write.path, fn.path), ($3) => {
|
|
335
|
+
$3.assign(row, fn.set(row, write.value));
|
|
336
|
+
$3.assign(found, true);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
$2.if(found.not(), ($3) => { $3.assign(named, false); });
|
|
340
|
+
});
|
|
341
|
+
return named.ifElse((_$2) => East.value(some(East.Blob.encodeBeast(row, "v2")), OptionType(BlobType)), (_$2) => East.value(none, OptionType(BlobType)));
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* A record's side of a kind on the wire: whether it is editable, its session
|
|
346
|
+
* and its read by key, its history, its form, its own Details, the app's
|
|
347
|
+
* check, and the setter an author's card writes its fields through.
|
|
348
|
+
*
|
|
349
|
+
* @param bound - The record's binding
|
|
350
|
+
* @param entries - Its `entries`, checked
|
|
351
|
+
* @param byKey - The paged read its rows are read by key through, if any
|
|
352
|
+
* @param config - Its form's hints, its check and its own Details
|
|
353
|
+
* @param omitted - The fields its roles write, which its form leaves out
|
|
354
|
+
* @param where - The kind, as a refusal names it
|
|
355
|
+
* @returns The fields every kind and details record carry
|
|
356
|
+
* @throws {Error} As `Fields.specs`, `rowInspector` and the patch door's check refuse
|
|
357
|
+
*/
|
|
358
|
+
function recordSide(bound, entries, byKey, config, omitted, where) {
|
|
359
|
+
const rowType = bound.rowType;
|
|
360
|
+
const form = Fields.specs(rowType, (config.fields ?? {}), [...new Set(omitted.filter((name) => name !== undefined))]);
|
|
361
|
+
const inspector = config.inspector === undefined ? undefined : rowInspector(config.inspector, rowType, `${where}: \`inspector\` is the kind's own Details — an East.function over the row and its writer: ` +
|
|
362
|
+
"East.function([RowType, FunctionType([RowType], NullType)], UIComponentType, ($, row, update) => …), RowType the record's row");
|
|
363
|
+
// Typed over the kind's row, as its author wrote it: called over the row the check decodes.
|
|
364
|
+
const ready = readyOf(rowType, config.ready);
|
|
365
|
+
let editing = none;
|
|
366
|
+
let seam;
|
|
367
|
+
let history = none;
|
|
368
|
+
if (bound.form === "record") {
|
|
369
|
+
// The shared session over the record (Schedule's `session.ts`): whole-entry drafts, keyed batches, Save through
|
|
370
|
+
// its patch door — its base the record's snapshot, or, read through a source, its revision.
|
|
371
|
+
const session = scheduleRecordSession(bound.handle, StringType, rowType, byKey === undefined ? undefined : entries);
|
|
372
|
+
const built = session.build();
|
|
373
|
+
editing = some(built.editing);
|
|
374
|
+
seam = built.entries;
|
|
375
|
+
history = some(session.history);
|
|
376
|
+
}
|
|
377
|
+
else if (byKey !== undefined) {
|
|
378
|
+
// Read only through `Data.bindPaged`: the record read by key through that binding itself.
|
|
379
|
+
seam = scheduleEntriesSeam(byKey, scheduleEntryByKey(byKey, StringType, rowType), scheduleLastKey(byKey, StringType));
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
editable: bound.form === "record",
|
|
383
|
+
editing,
|
|
384
|
+
entries: seam ?? East.value(none, OptionType(ScheduleEntriesType)),
|
|
385
|
+
history,
|
|
386
|
+
fields: East.value(form, ArrayType(FieldSpecType)),
|
|
387
|
+
inspector: inspector === undefined ? East.value(none, OptionType(RowInspectorType)) : some(inspector),
|
|
388
|
+
ready: ready === undefined ? none : some(ready),
|
|
389
|
+
set: rowSetter(rowType),
|
|
390
|
+
rowType: toEastTypeValue(rowType),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* A shape role, an accessor over the row's own fields (D35): refused when it
|
|
395
|
+
* names a field, as the Schematic's own type would then live in the app's row.
|
|
396
|
+
*
|
|
397
|
+
* @param rowType - The kind's row type
|
|
398
|
+
* @param where - The kind, as a refusal names it
|
|
399
|
+
* @param role - `footprint` or `geometry`
|
|
400
|
+
* @param fn - What the kind was given
|
|
401
|
+
* @returns The accessor, reified; `undefined` when not given
|
|
402
|
+
* @throws {Error} When it names a field, is no function, or returns another type, naming the accessor to write
|
|
403
|
+
*/
|
|
404
|
+
function shapeRole(rowType, where, role, fn) {
|
|
405
|
+
const example = role === "footprint" ? "footprint: (unit) => some(Schematic.circle(unit.radius))" : "geometry: (area) => some(Schematic.polygon(area.corners))";
|
|
406
|
+
if (typeof fn === "string") {
|
|
407
|
+
throw new Error(`${where}: \`${role}\` is an accessor over the row's own fields, built with Schematic.circle, polygon, polyline or rect — ` +
|
|
408
|
+
`${example}: a role naming "${fn}", a field that holds Schematic.Types.Geometry, is gone, so the app's record keeps its own types`);
|
|
409
|
+
}
|
|
410
|
+
return accessorRole(rowType, where, role, OptionType(SchematicGeometryType), `an Option of a Schematic.Types.Geometry, built with Schematic.circle, polygon, polyline or rect (${example})`, fn);
|
|
411
|
+
}
|
|
412
|
+
/** The card roles a kind's details may give: refused when the kind gives them too. */
|
|
413
|
+
const CARD_ROLES = ["status", "meter", "metric", "sublabel"];
|
|
414
|
+
// ============================================================================
|
|
415
|
+
// Schematic.details
|
|
416
|
+
// ============================================================================
|
|
417
|
+
/** The key a Schematic's declarations carry their sort under. */
|
|
418
|
+
export const SCHEMATIC_DEF = Symbol("Schematic.def");
|
|
419
|
+
/**
|
|
420
|
+
* Declares a kind's details (§4.5): a record of the app's keyed as its kind
|
|
421
|
+
* is — a rack's power, a cooling unit's alarm — which the inspector shows
|
|
422
|
+
* beside the row and the cards on screen draw: its status, its bar, its figure
|
|
423
|
+
* and its second line. A kind's row with no details row shows none, and its
|
|
424
|
+
* card draws no bar.
|
|
425
|
+
*
|
|
426
|
+
* @typeParam R - The details record's row type
|
|
427
|
+
* @typeParam SF - Its status field
|
|
428
|
+
* @param record - The details record, bound as its kind is read: read whole beside a kind read whole —
|
|
429
|
+
* `Data.bind(record)`, or `Record.bind(record, [patch])`; by key beside a kind read through its source —
|
|
430
|
+
* `Data.bindPaged(record)`, or `Record.bind(record, [patch])` with `entries`
|
|
431
|
+
* @param config - Its card roles, its form, its own Details and its `entries`
|
|
432
|
+
* @returns The details, for a kind's `details`
|
|
433
|
+
* @throws {Error} Naming the role or the binding: a role naming a field the row lacks or of another type, an accessor
|
|
434
|
+
* returning another type, and a record that is no binding of a record of structs keyed by String
|
|
435
|
+
*/
|
|
436
|
+
export function schematicDetails(record, config) {
|
|
437
|
+
const where = "Schematic.details";
|
|
438
|
+
const bound = boundOf(record, where);
|
|
439
|
+
const rowType = bound.rowType;
|
|
440
|
+
const entries = entriesOf(bound, config.entries, where);
|
|
441
|
+
const status = statusOf(rowType, where, config.status);
|
|
442
|
+
const meter = accessorRole(rowType, where, "meter", SchematicMeterType, "a Schematic.Types.Meter, { value, max }", config.meter);
|
|
443
|
+
const metric = accessorRole(rowType, where, "metric", OptionType(StringType), "an Option of a String", config.metric);
|
|
444
|
+
const sublabel = accessorRole(rowType, where, "sublabel", StringType, "a String", config.sublabel);
|
|
445
|
+
const rolesOf = East.function([rowType, StringType], SchematicDetailsRolesType, ($, row, key) => East.value({
|
|
446
|
+
status: statusShown($, status, row),
|
|
447
|
+
meter: called($, meter, row, key, (m) => some(m), none),
|
|
448
|
+
metric: called($, metric, row, key, (m) => m, none),
|
|
449
|
+
sublabel: called($, sublabel, row, key, (s) => some(s), none),
|
|
450
|
+
}, SchematicDetailsRolesType));
|
|
451
|
+
return {
|
|
452
|
+
[SCHEMATIC_DEF]: "details",
|
|
453
|
+
roles: CARD_ROLES.filter((role) => config[role] !== undefined),
|
|
454
|
+
build(kind, throughSource) {
|
|
455
|
+
const at = `${where} of ${kind}`;
|
|
456
|
+
if (!throughSource) {
|
|
457
|
+
if (bound.form === "paged") {
|
|
458
|
+
throw new Error(`${at}: beside a kind read whole, its details are read whole — bind them Data.bind(record), ` +
|
|
459
|
+
"or Record.bind(record, [e3.mutation.patch(record)]) to edit them");
|
|
460
|
+
}
|
|
461
|
+
if (entries !== undefined) {
|
|
462
|
+
throw new Error(`${at}: \`entries\` reads the details by key beside a kind read through its source — this kind is read whole, ` +
|
|
463
|
+
"and its details with it: leave `entries` out");
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
if (bound.form === "data") {
|
|
468
|
+
throw new Error(`${at}: beside a kind read through its source, its details are read by key — bind them Data.bindPaged(record), ` +
|
|
469
|
+
"or Record.bind(record, [e3.mutation.patch(record)]) with entries: Data.bindPaged(record) to edit them");
|
|
470
|
+
}
|
|
471
|
+
if (bound.form === "record" && entries === undefined) {
|
|
472
|
+
throw new Error(`${at}: beside a kind read through its source, its details are read by key — give \`entries: Data.bindPaged(record)\` ` +
|
|
473
|
+
"beside Record.bind, the details record's own entries");
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const byKey = throughSource ? (bound.form === "paged" ? bound.handle : entries) : undefined;
|
|
477
|
+
const side = recordSide(bound, entries, byKey, config, [], at);
|
|
478
|
+
return East.value({
|
|
479
|
+
...side,
|
|
480
|
+
rows: throughSource ? none : some(storedRows(schematicWholeRows(bound.handle, rowType), rolesOf, SchematicDetailsEntryType)),
|
|
481
|
+
rowsOf: rowsReadOf(rowType, rolesOf, SchematicDetailsEntryType),
|
|
482
|
+
// The paged read the details are read by key through: a reader asks a window's cards' rows of it, in one request (#1393).
|
|
483
|
+
paged: byKey === undefined ? none : some(fieldOf(byKey, "id")),
|
|
484
|
+
read: readOf(rowType, rolesOf, SchematicDetailsRolesType),
|
|
485
|
+
// The card roles they give: what a card keeps the place of while its details row is read (#1398).
|
|
486
|
+
roles: { status: status !== undefined, meter: meter !== undefined, metric: metric !== undefined, sublabel: sublabel !== undefined },
|
|
487
|
+
}, SchematicKindDetailsType);
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/** Whether a kind's `details` is `Schematic.details`'. */
|
|
492
|
+
function isDetails(details) {
|
|
493
|
+
return typeof details === "object" && details !== null && SCHEMATIC_DEF in details && details[SCHEMATIC_DEF] === "details";
|
|
494
|
+
}
|
|
495
|
+
/** Checks a kind's details: `Schematic.details`', and no card role given in both places. */
|
|
496
|
+
function detailsOf(details, given, where) {
|
|
497
|
+
if (details === undefined)
|
|
498
|
+
return undefined;
|
|
499
|
+
if (!isDetails(details))
|
|
500
|
+
throw new Error(`${where}: \`details\` is Schematic.details(record, { status, meter, metric, sublabel }) over a record of the app's keyed as the kind is`);
|
|
501
|
+
const both = details.roles.filter((role) => given.includes(role));
|
|
502
|
+
if (both.length > 0) {
|
|
503
|
+
throw new Error(`${where}: \`${both[0]}\` is given here and by the kind's details — a card draws one; give it in one place`);
|
|
504
|
+
}
|
|
505
|
+
return details;
|
|
506
|
+
}
|
|
507
|
+
/** Checks a kind's templates: a templates record's, `Schematic.templates`', never a list written in the code. */
|
|
508
|
+
function templatesOf(templates, where) {
|
|
509
|
+
if (templates === undefined)
|
|
510
|
+
return undefined;
|
|
511
|
+
if (!isSchematicTemplates(templates)) {
|
|
512
|
+
throw new Error(`${where}: templates are records — Schematic.templates(templates.read(), { name, values, source }) over a record of the app's, ` +
|
|
513
|
+
"its source one of the slice's");
|
|
514
|
+
}
|
|
515
|
+
return templates;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Declares an item kind (see the module docs and §4.1).
|
|
519
|
+
*
|
|
520
|
+
* @typeParam R - The record's row type
|
|
521
|
+
* @typeParam X - The `x` field
|
|
522
|
+
* @typeParam Y - The `y` field
|
|
523
|
+
* @typeParam SF - The status field, when it has one
|
|
524
|
+
* @param record - The app's record of items: `Record.bind(record, [e3.mutation.patch(record)])` to edit it, or `Data.bind(record)`
|
|
525
|
+
* or `Data.bindPaged(record)` to read it only (§1, rule 4)
|
|
526
|
+
* @param config - Its name and icon, the fields its roles are, its accessors, and its details, templates, form, Details and check
|
|
527
|
+
* @returns The kind, for the Schematic's `items`, by slot
|
|
528
|
+
* @throws {Error} Naming the role or the binding (SC1, SC33, SC35): a role naming a field the row lacks, or one of another
|
|
529
|
+
* type; `x` and `y` of different types; an accessor returning another type; a `footprint` naming a field rather than
|
|
530
|
+
* an accessor building the shape (D35), or without `width` and `height`; a status, a meter, a metric or a second line given both here and by its details; templates written in
|
|
531
|
+
* the code, or giving other fields than every field but `x` and `y`; and a record that is no binding of a record of
|
|
532
|
+
* structs keyed by String, a `Record.bind` without its patch door, or `entries` beside a read-only binding
|
|
533
|
+
*/
|
|
534
|
+
export function schematicItems(record, config) {
|
|
535
|
+
const where = `Schematic.items: "${config.name}"`;
|
|
536
|
+
const bound = boundOf(record, where);
|
|
537
|
+
const rowType = bound.rowType;
|
|
538
|
+
const typeOf = fieldTypeOf(rowType, where);
|
|
539
|
+
const role = roleField(rowType, where);
|
|
540
|
+
const entries = entriesOf(bound, config.entries, where);
|
|
541
|
+
if (typeof config.icon !== "string" && typeof config.icon !== "function")
|
|
542
|
+
throw new Error(`${where}: \`icon\` is a Font Awesome solid icon's name, or an accessor giving each card's`);
|
|
543
|
+
// The centre: Float fields, or both Options of Float, which give the kind a backlog.
|
|
544
|
+
const xType = typeOf("x", config.x);
|
|
545
|
+
const yType = typeOf("y", config.y);
|
|
546
|
+
const isPlace = (t) => isTypeEqual(t, FloatType) || isTypeEqual(t, OptionType(FloatType));
|
|
547
|
+
for (const [prop, name, type] of [["x", config.x, xType], ["y", config.y, yType]]) {
|
|
548
|
+
if (!isPlace(type))
|
|
549
|
+
throw new Error(`${where}: \`${prop}\` names a Float field, or an Option of one — "${name}" holds ${typeText(type)}`);
|
|
550
|
+
}
|
|
551
|
+
if (!isTypeEqual(xType, yType)) {
|
|
552
|
+
throw new Error(`${where}: \`x\` and \`y\` are both Float fields, or both Options of Float — "${config.x}" holds ${typeText(xType)} and "${config.y}" ${typeText(yType)}`);
|
|
553
|
+
}
|
|
554
|
+
if (config.x === config.y)
|
|
555
|
+
throw new Error(`${where}: \`x\` and \`y\` both name "${config.x}" — an item's centre is two fields`);
|
|
556
|
+
const backlog = optionOf(xType) !== undefined;
|
|
557
|
+
// The roles that are fields.
|
|
558
|
+
const labelField = typeof config.label === "string" ? role("label", config.label, StringType, "a String field") : undefined;
|
|
559
|
+
const labelOf = typeof config.label === "function" ? accessorRole(rowType, where, "label", StringType, "a String", config.label) : undefined;
|
|
560
|
+
if (labelField === undefined && labelOf === undefined)
|
|
561
|
+
throw new Error(`${where}: \`label\` names a String field, or is an accessor giving a String`);
|
|
562
|
+
const widthField = role("width", config.width, FloatType, "a Float field");
|
|
563
|
+
const heightField = role("height", config.height, FloatType, "a Float field");
|
|
564
|
+
const footprintOf = shapeRole(rowType, where, "footprint", config.footprint);
|
|
565
|
+
// Geometry is drawn about the item's centre, inside its box (D22, SC35): a footprint needs the box.
|
|
566
|
+
if (footprintOf !== undefined && (widthField === undefined || heightField === undefined)) {
|
|
567
|
+
throw new Error(`${where}: \`footprint\` is drawn about the item's centre, inside its box — give \`width\` and \`height\` with it, the box's Float fields`);
|
|
568
|
+
}
|
|
569
|
+
const layerField = role("layer", config.layer, StringType, "a String field");
|
|
570
|
+
const status = statusOf(rowType, where, config.status);
|
|
571
|
+
// The roles the app computes.
|
|
572
|
+
const iconOf = typeof config.icon === "function" ? accessorRole(rowType, where, "icon", StringType, "a String, a Font Awesome solid icon's name", config.icon) : undefined;
|
|
573
|
+
const sublabelOf = accessorRole(rowType, where, "sublabel", StringType, "a String", config.sublabel);
|
|
574
|
+
const metricOf = accessorRole(rowType, where, "metric", OptionType(StringType), "an Option of a String", config.metric);
|
|
575
|
+
const meterOf = accessorRole(rowType, where, "meter", SchematicMeterType, "a Schematic.Types.Meter, { value, max }", config.meter);
|
|
576
|
+
const toneOf = accessorRole(rowType, where, "tone", OptionType(SchematicToneType), "an Option of a Schematic.Types.Tone", config.tone);
|
|
577
|
+
const colorOf = accessorRole(rowType, where, "color", OptionType(StringType), "an Option of a String, a theme colour token", config.color);
|
|
578
|
+
const weightOf = accessorRole(rowType, where, "weight", OptionType(FloatType), "an Option of a Float", config.weight);
|
|
579
|
+
const fillOf = accessorRole(rowType, where, "fillOpacity", OptionType(FloatType), "an Option of a Float", config.fillOpacity);
|
|
580
|
+
const details = detailsOf(config.details, CARD_ROLES.filter((r) => config[r] !== undefined), where);
|
|
581
|
+
// The templates: every field but the centre.
|
|
582
|
+
const ValuesType = valuesTypeOf(rowType, [config.x, config.y]);
|
|
583
|
+
const templates = templatesOf(config.templates, where);
|
|
584
|
+
const templateRows = templates === undefined ? East.value([], ArrayType(SchematicTemplateType))
|
|
585
|
+
: checkedSchematicTemplates(templates, ValuesType, `\`${config.x}\` and \`${config.y}\``, where);
|
|
586
|
+
// One item's roles, from its row and its key.
|
|
587
|
+
const rolesOf = East.function([rowType, StringType], SchematicItemType, ($, row, key) => {
|
|
588
|
+
const PointOption = OptionType(SchematicPointType);
|
|
589
|
+
let position;
|
|
590
|
+
if (backlog) {
|
|
591
|
+
const xs = fieldOf(row, config.x);
|
|
592
|
+
const ys = fieldOf(row, config.y);
|
|
593
|
+
position = $.let(xs.match({
|
|
594
|
+
none: (_$2) => East.value(none, PointOption),
|
|
595
|
+
some: (_$2, x) => ys.match({
|
|
596
|
+
none: (_$3) => East.value(none, PointOption),
|
|
597
|
+
some: (_$3, y) => East.value(some({ x, y }), PointOption),
|
|
598
|
+
}),
|
|
599
|
+
}), PointOption);
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
position = East.value(some({ x: fieldOf(row, config.x), y: fieldOf(row, config.y) }), PointOption);
|
|
603
|
+
}
|
|
604
|
+
const label = labelField !== undefined ? fieldOf(row, labelField) : called($, labelOf, row, key, (l) => l, "");
|
|
605
|
+
const icon = typeof config.icon === "string" ? some(config.icon) : called($, iconOf, row, key, (i) => some(i), none);
|
|
606
|
+
return East.value({
|
|
607
|
+
position,
|
|
608
|
+
label,
|
|
609
|
+
width: widthField === undefined ? none : some(fieldOf(row, widthField)),
|
|
610
|
+
height: heightField === undefined ? none : some(fieldOf(row, heightField)),
|
|
611
|
+
footprint: called($, footprintOf, row, key, (f) => f, none),
|
|
612
|
+
layer: layerField === undefined ? none : some(fieldOf(row, layerField)),
|
|
613
|
+
icon,
|
|
614
|
+
sublabel: called($, sublabelOf, row, key, (s) => some(s), none),
|
|
615
|
+
metric: called($, metricOf, row, key, (m) => m, none),
|
|
616
|
+
meter: called($, meterOf, row, key, (m) => some(m), none),
|
|
617
|
+
status: statusShown($, status, row),
|
|
618
|
+
tone: called($, toneOf, row, key, (t) => t, none),
|
|
619
|
+
color: called($, colorOf, row, key, (c) => c, none),
|
|
620
|
+
weight: called($, weightOf, row, key, (w) => w, none),
|
|
621
|
+
fillOpacity: called($, fillOf, row, key, (f) => f, none),
|
|
622
|
+
}, SchematicItemType);
|
|
623
|
+
});
|
|
624
|
+
// A row with its centre written from a position, every other field from `source`: none, a backlog kind's row
|
|
625
|
+
// unplaced; `none` for no position on a kind with no backlog.
|
|
626
|
+
const placed = (source, position) => {
|
|
627
|
+
const at = (x, y) => East.value(some(East.Blob.encodeBeast(rowWith(rowType, source, { [config.x]: x, [config.y]: y }), "v2")), OptionType(BlobType));
|
|
628
|
+
if (backlog) {
|
|
629
|
+
return position.match({
|
|
630
|
+
none: (_$2) => at(East.value(none, OptionType(FloatType)), East.value(none, OptionType(FloatType))),
|
|
631
|
+
some: (_$2, p) => at(East.value(some(p.x), OptionType(FloatType)), East.value(some(p.y), OptionType(FloatType))),
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
return position.match({
|
|
635
|
+
none: (_$2) => East.value(none, OptionType(BlobType)),
|
|
636
|
+
some: (_$2, p) => at(p.x, p.y),
|
|
637
|
+
});
|
|
638
|
+
};
|
|
639
|
+
// The row with its centre written — none, unplaced — and every other field as it was (SC2).
|
|
640
|
+
const write = East.function([BlobType, OptionType(SchematicPointType)], OptionType(BlobType), ($, bytes, position) => {
|
|
641
|
+
const row = $.const(bytes.decodeBeast(rowType, "v2"));
|
|
642
|
+
return placed(row, position);
|
|
643
|
+
});
|
|
644
|
+
// A new row at a position, every other field from a template's values, or its type's default.
|
|
645
|
+
const blank = East.value(defaultValue(ValuesType), ValuesType);
|
|
646
|
+
const create = East.function([OptionType(SchematicPointType), OptionType(BlobType)], OptionType(BlobType), ($, position, values) => {
|
|
647
|
+
const given = $.const(values.match({
|
|
648
|
+
some: (_$2, bytes) => bytes.decodeBeast(ValuesType, "v2"),
|
|
649
|
+
none: (_$2) => blank,
|
|
650
|
+
}));
|
|
651
|
+
return placed(given, position);
|
|
652
|
+
});
|
|
653
|
+
return {
|
|
654
|
+
[SCHEMATIC_DEF]: "items",
|
|
655
|
+
rowType,
|
|
656
|
+
name: config.name,
|
|
657
|
+
backlog,
|
|
658
|
+
hasTemplates: templates !== undefined,
|
|
659
|
+
templatesSource: templates?.source,
|
|
660
|
+
build(slot, part, every = false) {
|
|
661
|
+
const { rows, byKey } = readsOf(bound, entries, slot, part, where, every);
|
|
662
|
+
const side = recordSide(bound, entries, byKey, config, [config.x, config.y, labelField, widthField, heightField, layerField, status?.field], where);
|
|
663
|
+
return East.value({
|
|
664
|
+
...side,
|
|
665
|
+
key: slot,
|
|
666
|
+
name: config.name,
|
|
667
|
+
icon: typeof config.icon === "string" ? config.icon : "",
|
|
668
|
+
details: details === undefined ? none : some(details.build(slot, part !== undefined)),
|
|
669
|
+
backlog,
|
|
670
|
+
status: status === undefined ? [] : status.cases.map((c) => ({ case: c, status: fieldOf(status.table, c) })),
|
|
671
|
+
rows: storedRows(rows, rolesOf, SchematicItemEntryType),
|
|
672
|
+
rowsOf: rowsReadOf(rowType, rolesOf, SchematicItemEntryType),
|
|
673
|
+
// The paged read the kind's rows are read by key through: a reader asks the far ends of the links and nets
|
|
674
|
+
// touching its window of it, all of a window's in one request, whether or not its source joins its rows (#1393).
|
|
675
|
+
paged: byKey === undefined ? none : some(fieldOf(byKey, "id")),
|
|
676
|
+
box: {
|
|
677
|
+
x: config.x, y: config.y,
|
|
678
|
+
width: widthField === undefined ? none : some(widthField), height: heightField === undefined ? none : some(heightField),
|
|
679
|
+
},
|
|
680
|
+
read: readOf(rowType, rolesOf, SchematicItemType),
|
|
681
|
+
write,
|
|
682
|
+
create,
|
|
683
|
+
templates: templateRows,
|
|
684
|
+
templatesSource: templates?.source === undefined ? none : some(templates.source),
|
|
685
|
+
}, SchematicItemKindType);
|
|
686
|
+
},
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Declares a zone kind (§4.2).
|
|
691
|
+
*
|
|
692
|
+
* @typeParam R - The record's row type
|
|
693
|
+
* @param record - The app's record of zones, bound as §1, rule 4 says
|
|
694
|
+
* @param config - Its name and icon, the fields its box and label are, its pattern and shape, and its details, form, Details and check
|
|
695
|
+
* @returns The kind, for the Schematic's `zones`, by slot
|
|
696
|
+
* @throws {Error} Naming the role or the binding, as `Schematic.items` does, and a `geometry` naming a field rather than an
|
|
697
|
+
* accessor building the shape (D35)
|
|
698
|
+
*/
|
|
699
|
+
export function schematicZones(record, config) {
|
|
700
|
+
const where = `Schematic.zones: "${config.name}"`;
|
|
701
|
+
const bound = boundOf(record, where);
|
|
702
|
+
const rowType = bound.rowType;
|
|
703
|
+
const role = roleField(rowType, where);
|
|
704
|
+
const entries = entriesOf(bound, config.entries, where);
|
|
705
|
+
if (typeof config.icon !== "string")
|
|
706
|
+
throw new Error(`${where}: \`icon\` is a Font Awesome solid icon's name`);
|
|
707
|
+
if ("templates" in config)
|
|
708
|
+
throw new Error(`${where}: zones take no templates — a zone is added through its record, and edited in the inspector`);
|
|
709
|
+
const xField = role("x", config.x, FloatType, "a Float field");
|
|
710
|
+
const yField = role("y", config.y, FloatType, "a Float field");
|
|
711
|
+
const widthField = role("width", config.width, FloatType, "a Float field");
|
|
712
|
+
const heightField = role("height", config.height, FloatType, "a Float field");
|
|
713
|
+
for (const [prop, name] of [["x", xField], ["y", yField], ["width", widthField], ["height", heightField]]) {
|
|
714
|
+
if (name === undefined)
|
|
715
|
+
throw new Error(`${where}: \`${prop}\` names a Float field of the zone's box`);
|
|
716
|
+
}
|
|
717
|
+
const labelField = typeof config.label === "string" ? role("label", config.label, StringType, "a String field") : undefined;
|
|
718
|
+
const labelOf = typeof config.label === "function" ? accessorRole(rowType, where, "label", StringType, "a String", config.label) : undefined;
|
|
719
|
+
if (labelField === undefined && labelOf === undefined)
|
|
720
|
+
throw new Error(`${where}: \`label\` names a String field, or is an accessor giving a String`);
|
|
721
|
+
const geometryOf = shapeRole(rowType, where, "geometry", config.geometry);
|
|
722
|
+
const layerField = role("layer", config.layer, StringType, "a String field");
|
|
723
|
+
const patternOf = accessorRole(rowType, where, "pattern", SchematicZonePatternType, "a Schematic.Types.ZonePattern", config.pattern);
|
|
724
|
+
const toneOf = accessorRole(rowType, where, "tone", OptionType(SchematicToneType), "an Option of a Schematic.Types.Tone", config.tone);
|
|
725
|
+
const colorOf = accessorRole(rowType, where, "color", OptionType(StringType), "an Option of a String, a theme colour token", config.color);
|
|
726
|
+
const fillOf = accessorRole(rowType, where, "fillOpacity", OptionType(FloatType), "an Option of a Float", config.fillOpacity);
|
|
727
|
+
const weightOf = accessorRole(rowType, where, "weight", OptionType(FloatType), "an Option of a Float", config.weight);
|
|
728
|
+
const details = detailsOf(config.details, [], where);
|
|
729
|
+
const outline = East.value(variant("outline", { tone: none }), SchematicZonePatternType);
|
|
730
|
+
const rolesOf = East.function([rowType, StringType], SchematicZoneType, ($, row, key) => East.value({
|
|
731
|
+
x: fieldOf(row, xField), y: fieldOf(row, yField), width: fieldOf(row, widthField), height: fieldOf(row, heightField),
|
|
732
|
+
label: labelField !== undefined ? fieldOf(row, labelField) : called($, labelOf, row, key, (l) => l, ""),
|
|
733
|
+
pattern: called($, patternOf, row, key, (p) => p, outline),
|
|
734
|
+
geometry: called($, geometryOf, row, key, (g) => g, none),
|
|
735
|
+
layer: layerField === undefined ? none : some(fieldOf(row, layerField)),
|
|
736
|
+
tone: called($, toneOf, row, key, (t) => t, none),
|
|
737
|
+
color: called($, colorOf, row, key, (c) => c, none),
|
|
738
|
+
fillOpacity: called($, fillOf, row, key, (f) => f, none),
|
|
739
|
+
weight: called($, weightOf, row, key, (w) => w, none),
|
|
740
|
+
}, SchematicZoneType));
|
|
741
|
+
return {
|
|
742
|
+
[SCHEMATIC_DEF]: "zones",
|
|
743
|
+
rowType,
|
|
744
|
+
name: config.name,
|
|
745
|
+
hasTemplates: false,
|
|
746
|
+
templatesSource: undefined,
|
|
747
|
+
build(slot, part, every = false) {
|
|
748
|
+
const { rows, byKey } = readsOf(bound, entries, slot, part, where, every);
|
|
749
|
+
const side = recordSide(bound, entries, byKey, config, [xField, yField, widthField, heightField, labelField, layerField], where);
|
|
750
|
+
return East.value({
|
|
751
|
+
...side,
|
|
752
|
+
key: slot,
|
|
753
|
+
name: config.name,
|
|
754
|
+
icon: config.icon,
|
|
755
|
+
details: details === undefined ? none : some(details.build(slot, part !== undefined)),
|
|
756
|
+
rows: storedRows(rows, rolesOf, SchematicZoneEntryType),
|
|
757
|
+
rowsOf: rowsReadOf(rowType, rolesOf, SchematicZoneEntryType),
|
|
758
|
+
box: { x: xField, y: yField, width: some(widthField), height: some(heightField) },
|
|
759
|
+
read: readOf(rowType, rolesOf, SchematicZoneType),
|
|
760
|
+
}, SchematicZoneKindType);
|
|
761
|
+
},
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
/** Checks a link's or a net's drawing roles. */
|
|
765
|
+
function edgeRoles(rowType, where, config) {
|
|
766
|
+
const role = roleField(rowType, where);
|
|
767
|
+
return {
|
|
768
|
+
viaField: role("via", config.via, ArrayType(SchematicPointType), "an Array of { x, y } Floats field"),
|
|
769
|
+
layerField: role("layer", config.layer, StringType, "a String field"),
|
|
770
|
+
labelOf: accessorRole(rowType, where, "label", OptionType(StringType), "an Option of a String", config.label),
|
|
771
|
+
metricOf: accessorRole(rowType, where, "metric", OptionType(StringType), "an Option of a String", config.metric),
|
|
772
|
+
styleOf: accessorRole(rowType, where, "style", SchematicLinkStyleType, "a Schematic.Types.LinkStyle", config.style),
|
|
773
|
+
routeOf: accessorRole(rowType, where, "route", SchematicRouteType, "a Schematic.Types.Route", config.route),
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
/** A link's or a net's drawing roles, for a row. */
|
|
777
|
+
function edgeShown($, edge, row, key) {
|
|
778
|
+
return {
|
|
779
|
+
via: edge.viaField === undefined ? East.value([], ArrayType(SchematicPointType)) : fieldOf(row, edge.viaField),
|
|
780
|
+
label: called($, edge.labelOf, row, key, (l) => l, none),
|
|
781
|
+
metric: called($, edge.metricOf, row, key, (m) => m, none),
|
|
782
|
+
style: called($, edge.styleOf, row, key, (s) => s, East.value(variant("solid", { tone: none, weight: none }), SchematicLinkStyleType)),
|
|
783
|
+
route: called($, edge.routeOf, row, key, (r) => r, East.value(variant("orthogonal", { corner: none }), SchematicRouteType)),
|
|
784
|
+
layer: edge.layerField === undefined ? none : some(fieldOf(row, edge.layerField)),
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Declares a link kind (§4.3).
|
|
789
|
+
*
|
|
790
|
+
* @typeParam R - The record's row type
|
|
791
|
+
* @typeParam FF - The `from` field
|
|
792
|
+
* @typeParam TF - The `to` field
|
|
793
|
+
* @param record - The app's record of links, bound as §1, rule 4 says
|
|
794
|
+
* @param config - Its name and icon, its ends, its drawing roles, and its details, templates, form, Details and check
|
|
795
|
+
* @returns The kind, for the Schematic's `links`, by slot
|
|
796
|
+
* @throws {Error} Naming the role or the binding, as `Schematic.items` does: an end of neither a String field nor a
|
|
797
|
+
* variant of String cases — an Option among them, a link having both its ends — or whose `of` names no item slot,
|
|
798
|
+
* and `from` and `to` naming one field
|
|
799
|
+
*/
|
|
800
|
+
export function schematicLinks(record, config) {
|
|
801
|
+
const where = `Schematic.links: "${config.name}"`;
|
|
802
|
+
const bound = boundOf(record, where);
|
|
803
|
+
const rowType = bound.rowType;
|
|
804
|
+
const fields = rowType.fields;
|
|
805
|
+
const typeOf = fieldTypeOf(rowType, where);
|
|
806
|
+
const entries = entriesOf(bound, config.entries, where);
|
|
807
|
+
if (typeof config.icon !== "string")
|
|
808
|
+
throw new Error(`${where}: \`icon\` is a Font Awesome solid icon's name`);
|
|
809
|
+
const end = (prop, given) => {
|
|
810
|
+
if (given === undefined || typeof given.field !== "string")
|
|
811
|
+
throw new Error(`${where}: \`${prop}\` is { field, of } — the field holding an item's key, and the item slot it holds keys of`);
|
|
812
|
+
const checked = checkKeyField(given.field, typeOf(`${prop}.field`, given.field), given.of, { where, prop, noun: "item" });
|
|
813
|
+
if (checked.optional) {
|
|
814
|
+
throw new Error(`${where}: a link has both its ends — \`${prop}.field\` names a String field or a variant of String cases, ` +
|
|
815
|
+
`and "${given.field}" holds ${typeText(fields[given.field])}`);
|
|
816
|
+
}
|
|
817
|
+
return checked;
|
|
818
|
+
};
|
|
819
|
+
const from = end("from", config.from);
|
|
820
|
+
const to = end("to", config.to);
|
|
821
|
+
if (from.field === to.field)
|
|
822
|
+
throw new Error(`${where}: \`from\` and \`to\` both name "${from.field}" — a link's ends are two fields`);
|
|
823
|
+
const edge = edgeRoles(rowType, where, config);
|
|
824
|
+
const details = detailsOf(config.details, [], where);
|
|
825
|
+
const ValuesType = valuesTypeOf(rowType, [from.field, to.field]);
|
|
826
|
+
const templates = templatesOf(config.templates, where);
|
|
827
|
+
const templateRows = templates === undefined ? East.value([], ArrayType(SchematicTemplateType))
|
|
828
|
+
: checkedSchematicTemplates(templates, ValuesType, `\`${from.field}\` and \`${to.field}\``, where);
|
|
829
|
+
const fromRef = keyFieldRef(from, rowType);
|
|
830
|
+
const toRef = keyFieldRef(to, rowType);
|
|
831
|
+
const fromValue = keyFieldValue(from, fields[from.field]);
|
|
832
|
+
const toValue = keyFieldValue(to, fields[to.field]);
|
|
833
|
+
const rolesOf = East.function([rowType, StringType], SchematicLinkType, ($, row, key) => {
|
|
834
|
+
const fromOf = $.const(fromRef);
|
|
835
|
+
const toOf = $.const(toRef);
|
|
836
|
+
return East.value({
|
|
837
|
+
from: fromOf(row).unwrap(),
|
|
838
|
+
to: toOf(row).unwrap(),
|
|
839
|
+
...edgeShown($, edge, row, key),
|
|
840
|
+
}, SchematicLinkType);
|
|
841
|
+
});
|
|
842
|
+
// A row with its ends written from `ends`, every other field from `source`; `none` for an end of a slot the field holds no keys of.
|
|
843
|
+
const ended = ($, source, ends) => {
|
|
844
|
+
const fromOf = $.const(fromValue);
|
|
845
|
+
const toOf = $.const(toValue);
|
|
846
|
+
const result = $.let(East.value(none, OptionType(BlobType)), OptionType(BlobType));
|
|
847
|
+
$.match(fromOf(some(ends.from)), {
|
|
848
|
+
some: ($2, f) => {
|
|
849
|
+
$2.match(toOf(some(ends.to)), {
|
|
850
|
+
some: ($3, t) => {
|
|
851
|
+
$3.assign(result, East.value(some(East.Blob.encodeBeast(rowWith(rowType, source, { [from.field]: f, [to.field]: t }), "v2")), OptionType(BlobType)));
|
|
852
|
+
},
|
|
853
|
+
});
|
|
854
|
+
},
|
|
855
|
+
});
|
|
856
|
+
return result;
|
|
857
|
+
};
|
|
858
|
+
const write = East.function([BlobType, SchematicEndsType], OptionType(BlobType), ($, bytes, ends) => {
|
|
859
|
+
const row = $.const(bytes.decodeBeast(rowType, "v2"));
|
|
860
|
+
return ended($, row, ends);
|
|
861
|
+
});
|
|
862
|
+
const blank = East.value(defaultValue(ValuesType), ValuesType);
|
|
863
|
+
const create = East.function([SchematicEndsType, OptionType(BlobType)], OptionType(BlobType), ($, ends, values) => {
|
|
864
|
+
const given = $.const(values.match({
|
|
865
|
+
some: (_$2, bytes) => bytes.decodeBeast(ValuesType, "v2"),
|
|
866
|
+
none: (_$2) => blank,
|
|
867
|
+
}));
|
|
868
|
+
return ended($, given, ends);
|
|
869
|
+
});
|
|
870
|
+
return {
|
|
871
|
+
[SCHEMATIC_DEF]: "links",
|
|
872
|
+
rowType,
|
|
873
|
+
name: config.name,
|
|
874
|
+
ends: { from: keyFieldSlots(from), to: keyFieldSlots(to) },
|
|
875
|
+
hasTemplates: templates !== undefined,
|
|
876
|
+
templatesSource: templates?.source,
|
|
877
|
+
build(slot, part, every = false) {
|
|
878
|
+
const { rows, byKey } = readsOf(bound, entries, slot, part, where, every);
|
|
879
|
+
const side = recordSide(bound, entries, byKey, config, [from.field, to.field, edge.viaField, edge.layerField], where);
|
|
880
|
+
return East.value({
|
|
881
|
+
...side,
|
|
882
|
+
key: slot,
|
|
883
|
+
name: config.name,
|
|
884
|
+
icon: config.icon,
|
|
885
|
+
details: details === undefined ? none : some(details.build(slot, part !== undefined)),
|
|
886
|
+
ends: { from: keyFieldSlots(from), to: keyFieldSlots(to) },
|
|
887
|
+
rows: storedRows(rows, rolesOf, SchematicLinkEntryType),
|
|
888
|
+
read: readOf(rowType, rolesOf, SchematicLinkType),
|
|
889
|
+
write,
|
|
890
|
+
create,
|
|
891
|
+
templates: templateRows,
|
|
892
|
+
templatesSource: templates?.source === undefined ? none : some(templates.source),
|
|
893
|
+
}, SchematicLinkKindType);
|
|
894
|
+
},
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Declares a net kind (§4.4).
|
|
899
|
+
*
|
|
900
|
+
* @typeParam R - The record's row type
|
|
901
|
+
* @typeParam SRC - The `sources` field
|
|
902
|
+
* @typeParam DST - The `destinations` field
|
|
903
|
+
* @param record - The app's record of nets, bound as §1, rule 4 says
|
|
904
|
+
* @param config - Its name and icon, its sides, its drawing roles, and its details, templates, form, Details and check
|
|
905
|
+
* @returns The kind, for the Schematic's `nets`, by slot
|
|
906
|
+
* @throws {Error} Naming the role or the binding, as `Schematic.items` does: a side of neither a Set nor an Array of
|
|
907
|
+
* String, whose `of` names no item slot, and both sides naming one field
|
|
908
|
+
*/
|
|
909
|
+
export function schematicNets(record, config) {
|
|
910
|
+
const where = `Schematic.nets: "${config.name}"`;
|
|
911
|
+
const bound = boundOf(record, where);
|
|
912
|
+
const rowType = bound.rowType;
|
|
913
|
+
const fields = rowType.fields;
|
|
914
|
+
const typeOf = fieldTypeOf(rowType, where);
|
|
915
|
+
const entries = entriesOf(bound, config.entries, where);
|
|
916
|
+
if (typeof config.icon !== "string")
|
|
917
|
+
throw new Error(`${where}: \`icon\` is a Font Awesome solid icon's name`);
|
|
918
|
+
const side = (prop, given) => {
|
|
919
|
+
if (given === undefined || typeof given.field !== "string")
|
|
920
|
+
throw new Error(`${where}: \`${prop}\` is { field, of } — a Set or an Array of String field of items' keys, and the item slot they are keys of`);
|
|
921
|
+
const type = typeOf(`${prop}.field`, given.field);
|
|
922
|
+
const set = isTypeEqual(type, SetType(StringType));
|
|
923
|
+
if (!set && !isTypeEqual(type, ArrayType(StringType))) {
|
|
924
|
+
throw new Error(`${where}: \`${prop}.field\` names a Set or an Array of String field, its items' keys — "${given.field}" holds ${typeText(type)}`);
|
|
925
|
+
}
|
|
926
|
+
if (typeof given.of !== "string" || given.of === "")
|
|
927
|
+
throw new Error(`${where}: \`${prop}.of\` names the item slot "${given.field}" holds keys of`);
|
|
928
|
+
return { field: given.field, of: given.of, set };
|
|
929
|
+
};
|
|
930
|
+
const sources = side("sources", config.sources);
|
|
931
|
+
const destinations = side("destinations", config.destinations);
|
|
932
|
+
if (sources.field === destinations.field)
|
|
933
|
+
throw new Error(`${where}: \`sources\` and \`destinations\` both name "${sources.field}" — a net's sides are two fields`);
|
|
934
|
+
const edge = edgeRoles(rowType, where, config);
|
|
935
|
+
const details = detailsOf(config.details, [], where);
|
|
936
|
+
const ValuesType = valuesTypeOf(rowType, [sources.field, destinations.field]);
|
|
937
|
+
const templates = templatesOf(config.templates, where);
|
|
938
|
+
const templateRows = templates === undefined ? East.value([], ArrayType(SchematicTemplateType))
|
|
939
|
+
: checkedSchematicTemplates(templates, ValuesType, `\`${sources.field}\` and \`${destinations.field}\``, where);
|
|
940
|
+
// A side's members, as references to the items of its slot.
|
|
941
|
+
const membersOf = (s, row) => {
|
|
942
|
+
const held = fieldOf(row, s.field);
|
|
943
|
+
const ref = (key) => East.value({ kind: s.of, key }, SchematicRefType);
|
|
944
|
+
return s.set
|
|
945
|
+
? held.toArray((_$, key) => ref(key))
|
|
946
|
+
: held.map((_$, key) => ref(key));
|
|
947
|
+
};
|
|
948
|
+
const rolesOf = East.function([rowType, StringType], SchematicNetType, ($, row, key) => East.value({
|
|
949
|
+
sources: membersOf(sources, row),
|
|
950
|
+
destinations: membersOf(destinations, row),
|
|
951
|
+
...edgeShown($, edge, row, key),
|
|
952
|
+
}, SchematicNetType));
|
|
953
|
+
// A side's field value from its members; `none` when one is of another slot.
|
|
954
|
+
const sideValue = (s) => East.function([ArrayType(SchematicRefType)], OptionType(fields[s.field]), ($, refs) => {
|
|
955
|
+
const taken = $.let(true);
|
|
956
|
+
$.for(refs, ($2, r) => { $2.if(r.kind.notEquals(s.of), ($3) => { $3.assign(taken, false); }); });
|
|
957
|
+
const value = s.set ? refs.toSet((_$2, r) => r.key) : refs.map((_$2, r) => r.key);
|
|
958
|
+
return taken.ifElse((_$2) => East.value(some(value), OptionType(fields[s.field])), (_$2) => East.value(none, OptionType(fields[s.field])));
|
|
959
|
+
});
|
|
960
|
+
const sourcesValue = sideValue(sources);
|
|
961
|
+
const destinationsValue = sideValue(destinations);
|
|
962
|
+
// A row with its sides written from `sides`, every other field from `source`; `none` for a member of another slot.
|
|
963
|
+
const sided = ($, source, sidesOf) => {
|
|
964
|
+
const srcOf = $.const(sourcesValue);
|
|
965
|
+
const dstOf = $.const(destinationsValue);
|
|
966
|
+
const result = $.let(East.value(none, OptionType(BlobType)), OptionType(BlobType));
|
|
967
|
+
$.match(srcOf(sidesOf.sources), {
|
|
968
|
+
some: ($2, s) => {
|
|
969
|
+
$2.match(dstOf(sidesOf.destinations), {
|
|
970
|
+
some: ($3, d) => {
|
|
971
|
+
$3.assign(result, East.value(some(East.Blob.encodeBeast(rowWith(rowType, source, { [sources.field]: s, [destinations.field]: d }), "v2")), OptionType(BlobType)));
|
|
972
|
+
},
|
|
973
|
+
});
|
|
974
|
+
},
|
|
975
|
+
});
|
|
976
|
+
return result;
|
|
977
|
+
};
|
|
978
|
+
const write = East.function([BlobType, SchematicSidesType], OptionType(BlobType), ($, bytes, sidesOf) => {
|
|
979
|
+
const row = $.const(bytes.decodeBeast(rowType, "v2"));
|
|
980
|
+
return sided($, row, sidesOf);
|
|
981
|
+
});
|
|
982
|
+
const blank = East.value(defaultValue(ValuesType), ValuesType);
|
|
983
|
+
const create = East.function([SchematicSidesType, OptionType(BlobType)], OptionType(BlobType), ($, sidesOf, values) => {
|
|
984
|
+
const given = $.const(values.match({
|
|
985
|
+
some: (_$2, bytes) => bytes.decodeBeast(ValuesType, "v2"),
|
|
986
|
+
none: (_$2) => blank,
|
|
987
|
+
}));
|
|
988
|
+
return sided($, given, sidesOf);
|
|
989
|
+
});
|
|
990
|
+
return {
|
|
991
|
+
[SCHEMATIC_DEF]: "nets",
|
|
992
|
+
rowType,
|
|
993
|
+
name: config.name,
|
|
994
|
+
sides: { sources: sources.of, destinations: destinations.of },
|
|
995
|
+
hasTemplates: templates !== undefined,
|
|
996
|
+
templatesSource: templates?.source,
|
|
997
|
+
build(slot, part, every = false) {
|
|
998
|
+
const { rows, byKey } = readsOf(bound, entries, slot, part, where, every);
|
|
999
|
+
const sideOf = recordSide(bound, entries, byKey, config, [sources.field, destinations.field, edge.viaField, edge.layerField], where);
|
|
1000
|
+
return East.value({
|
|
1001
|
+
...sideOf,
|
|
1002
|
+
key: slot,
|
|
1003
|
+
name: config.name,
|
|
1004
|
+
icon: config.icon,
|
|
1005
|
+
details: details === undefined ? none : some(details.build(slot, part !== undefined)),
|
|
1006
|
+
sides: { sources: sources.of, destinations: destinations.of },
|
|
1007
|
+
rows: storedRows(rows, rolesOf, SchematicNetEntryType),
|
|
1008
|
+
read: readOf(rowType, rolesOf, SchematicNetType),
|
|
1009
|
+
write,
|
|
1010
|
+
create,
|
|
1011
|
+
templates: templateRows,
|
|
1012
|
+
templatesSource: templates?.source === undefined ? none : some(templates.source),
|
|
1013
|
+
}, SchematicNetKindType);
|
|
1014
|
+
},
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Whether something is a Schematic kind of the sort given.
|
|
1019
|
+
*
|
|
1020
|
+
* @param value - What a slot holds
|
|
1021
|
+
* @param sort - The sort the slot takes
|
|
1022
|
+
* @returns Whether it is one
|
|
1023
|
+
*/
|
|
1024
|
+
export function isSchematicKind(value, sort) {
|
|
1025
|
+
return typeof value === "object" && value !== null && SCHEMATIC_DEF in value && value[SCHEMATIC_DEF] === sort;
|
|
1026
|
+
}
|
|
1027
|
+
//# sourceMappingURL=kinds.js.map
|