@elaraai/e3-ui 1.0.102 → 1.0.104
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/functions.d.ts +2 -1
- package/dist/src/allocation/functions.d.ts.map +1 -1
- package/dist/src/allocation/functions.js +3 -2
- package/dist/src/allocation/functions.js.map +1 -1
- package/dist/src/allocation/index.d.ts +120 -1
- package/dist/src/allocation/index.d.ts.map +1 -1
- package/dist/src/allocation/index.js +24 -4
- package/dist/src/allocation/index.js.map +1 -1
- package/dist/src/bind/data-indexed.d.ts +25 -4
- package/dist/src/bind/data-indexed.d.ts.map +1 -1
- package/dist/src/bind/data-indexed.js +58 -7
- package/dist/src/bind/data-indexed.js.map +1 -1
- package/dist/src/bind/data.d.ts +12 -14
- package/dist/src/bind/data.d.ts.map +1 -1
- package/dist/src/bind/data.js +11 -11
- package/dist/src/bind/data.js.map +1 -1
- package/dist/src/bind/func.d.ts +7 -2
- package/dist/src/bind/func.d.ts.map +1 -1
- package/dist/src/bind/func.js +10 -3
- package/dist/src/bind/func.js.map +1 -1
- package/dist/src/bind/record.d.ts +6 -6
- package/dist/src/bind/record.d.ts.map +1 -1
- package/dist/src/bind/record.js.map +1 -1
- package/dist/src/calendar/index.d.ts +148 -1
- package/dist/src/calendar/index.d.ts.map +1 -1
- package/dist/src/calendar/index.js +55 -5
- package/dist/src/calendar/index.js.map +1 -1
- package/dist/src/decision/bind.d.ts.map +1 -1
- package/dist/src/decision/index.d.ts +218 -218
- package/dist/src/decision/index.d.ts.map +1 -1
- package/dist/src/decision/types.d.ts.map +1 -1
- package/dist/src/flowchart/canvas.d.ts +32 -14
- package/dist/src/flowchart/canvas.d.ts.map +1 -1
- package/dist/src/flowchart/canvas.js +21 -6
- package/dist/src/flowchart/canvas.js.map +1 -1
- package/dist/src/flowchart/details.d.ts +469 -0
- package/dist/src/flowchart/details.d.ts.map +1 -0
- package/dist/src/flowchart/details.js +1035 -0
- package/dist/src/flowchart/details.js.map +1 -0
- package/dist/src/flowchart/flowchart.d.ts +16 -72
- package/dist/src/flowchart/flowchart.d.ts.map +1 -1
- package/dist/src/flowchart/flowchart.js +10 -1
- package/dist/src/flowchart/flowchart.js.map +1 -1
- package/dist/src/flowchart/index.d.ts +282 -251
- package/dist/src/flowchart/index.d.ts.map +1 -1
- package/dist/src/flowchart/index.js +234 -220
- package/dist/src/flowchart/index.js.map +1 -1
- package/dist/src/flowchart/kinds.d.ts +438 -0
- package/dist/src/flowchart/kinds.d.ts.map +1 -0
- package/dist/src/flowchart/kinds.js +799 -0
- package/dist/src/flowchart/kinds.js.map +1 -0
- package/dist/src/flowchart/library.d.ts +155 -740
- package/dist/src/flowchart/library.d.ts.map +1 -1
- package/dist/src/flowchart/library.js +191 -673
- package/dist/src/flowchart/library.js.map +1 -1
- package/dist/src/flowchart/payload.d.ts +3654 -7845
- package/dist/src/flowchart/payload.d.ts.map +1 -1
- package/dist/src/flowchart/payload.js +331 -469
- package/dist/src/flowchart/payload.js.map +1 -1
- package/dist/src/flowchart/templates.d.ts +657 -0
- package/dist/src/flowchart/templates.d.ts.map +1 -0
- package/dist/src/flowchart/templates.js +658 -0
- package/dist/src/flowchart/templates.js.map +1 -0
- package/dist/src/flowchart/types.d.ts +2258 -214
- package/dist/src/flowchart/types.d.ts.map +1 -1
- package/dist/src/flowchart/types.js +374 -124
- package/dist/src/flowchart/types.js.map +1 -1
- package/dist/src/index.d.ts +13 -15
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +24 -29
- package/dist/src/index.js.map +1 -1
- package/dist/src/internal.d.ts +5 -7
- package/dist/src/internal.d.ts.map +1 -1
- package/dist/src/internal.js +12 -19
- package/dist/src/internal.js.map +1 -1
- package/dist/src/ontology/index.d.ts +44 -15
- package/dist/src/ontology/index.d.ts.map +1 -1
- package/dist/src/ontology/index.js +19 -11
- package/dist/src/ontology/index.js.map +1 -1
- package/dist/src/plan/builders.d.ts +26 -26
- package/dist/src/plan/builders.d.ts.map +1 -1
- package/dist/src/plan/builders.js.map +1 -1
- package/dist/src/plan/event-rows.d.ts +59 -58
- package/dist/src/plan/event-rows.d.ts.map +1 -1
- package/dist/src/plan/event-rows.js +6 -4
- package/dist/src/plan/event-rows.js.map +1 -1
- package/dist/src/plan/factories.js.map +1 -1
- package/dist/src/plan/ir.d.ts +1 -0
- package/dist/src/plan/ir.d.ts.map +1 -1
- package/dist/src/plan/library.d.ts.map +1 -1
- package/dist/src/plan/nested-checks.js.map +1 -1
- package/dist/src/plan/plan.d.ts +567 -423
- package/dist/src/plan/plan.d.ts.map +1 -1
- package/dist/src/plan/plan.js +67 -3
- package/dist/src/plan/plan.js.map +1 -1
- package/dist/src/plan/series.d.ts.map +1 -1
- package/dist/src/plan/series.js.map +1 -1
- package/dist/src/plan/types.d.ts +173 -172
- package/dist/src/plan/types.d.ts.map +1 -1
- package/dist/src/query/builder.d.ts +368 -321
- package/dist/src/query/builder.d.ts.map +1 -1
- package/dist/src/query/builder.js +12 -1
- package/dist/src/query/builder.js.map +1 -1
- package/dist/src/query/index.d.ts +5 -1
- package/dist/src/query/index.d.ts.map +1 -1
- package/dist/src/query/index.js +3 -1
- package/dist/src/query/index.js.map +1 -1
- package/dist/src/query/library.d.ts +6 -6
- package/dist/src/query/queries.d.ts +3 -3
- package/dist/src/roster/fit.d.ts +5 -2
- package/dist/src/roster/fit.d.ts.map +1 -1
- package/dist/src/roster/fit.js +5 -3
- package/dist/src/roster/fit.js.map +1 -1
- package/dist/src/roster/index.d.ts +103 -2
- package/dist/src/roster/index.d.ts.map +1 -1
- package/dist/src/roster/index.js +45 -6
- package/dist/src/roster/index.js.map +1 -1
- package/dist/src/runtime/index.d.ts +1 -3
- package/dist/src/runtime/index.d.ts.map +1 -1
- package/dist/src/runtime/index.js +1 -3
- package/dist/src/runtime/index.js.map +1 -1
- package/dist/src/schedule/fields.js.map +1 -1
- package/dist/src/schedule/library.js.map +1 -1
- package/dist/src/schedule/record-window.d.ts +27 -26
- package/dist/src/schedule/record-window.d.ts.map +1 -1
- package/dist/src/schedule/types.d.ts +196 -196
- package/dist/src/schematic/index.d.ts +1 -1
- package/dist/src/schematic/index.d.ts.map +1 -1
- package/dist/src/schematic/index.js +1 -1
- package/dist/src/schematic/index.js.map +1 -1
- package/dist/src/schematic/kinds.d.ts +4 -24
- package/dist/src/schematic/kinds.d.ts.map +1 -1
- package/dist/src/schematic/kinds.js +8 -212
- package/dist/src/schematic/kinds.js.map +1 -1
- package/dist/src/schematic/library.d.ts.map +1 -1
- package/dist/src/schematic/payload.d.ts +1688 -1549
- package/dist/src/schematic/payload.d.ts.map +1 -1
- package/dist/src/schematic/payload.js +38 -1
- package/dist/src/schematic/payload.js.map +1 -1
- package/dist/src/schematic/shapes.d.ts.map +1 -1
- package/dist/src/schematic/types.d.ts +978 -978
- package/dist/src/schematic/types.d.ts.map +1 -1
- package/dist/src/schematic/types.js +4 -36
- package/dist/src/schematic/types.js.map +1 -1
- package/dist/src/schematic/window.d.ts +9 -42
- package/dist/src/schematic/window.d.ts.map +1 -1
- package/dist/src/schematic/window.js +10 -115
- package/dist/src/schematic/window.js.map +1 -1
- package/dist/src/sheet/actions.d.ts +388 -0
- package/dist/src/sheet/actions.d.ts.map +1 -0
- package/dist/src/sheet/actions.js +594 -0
- package/dist/src/sheet/actions.js.map +1 -0
- package/dist/src/sheet/bridge.d.ts +12 -1
- package/dist/src/sheet/bridge.d.ts.map +1 -1
- package/dist/src/sheet/bridge.js +30 -6
- package/dist/src/sheet/bridge.js.map +1 -1
- package/dist/src/sheet/columns.d.ts +5 -2
- package/dist/src/sheet/columns.d.ts.map +1 -1
- package/dist/src/sheet/columns.js.map +1 -1
- package/dist/src/sheet/draft-bridge.d.ts +9 -4
- package/dist/src/sheet/draft-bridge.d.ts.map +1 -1
- package/dist/src/sheet/draft-bridge.js +22 -7
- package/dist/src/sheet/draft-bridge.js.map +1 -1
- package/dist/src/sheet/editing-types.d.ts +71 -71
- package/dist/src/sheet/fields.d.ts +13 -0
- package/dist/src/sheet/fields.d.ts.map +1 -1
- package/dist/src/sheet/fields.js +3 -0
- package/dist/src/sheet/fields.js.map +1 -1
- package/dist/src/sheet/group.js.map +1 -1
- package/dist/src/sheet/index.d.ts +32 -2
- package/dist/src/sheet/index.d.ts.map +1 -1
- package/dist/src/sheet/index.js +18 -3
- package/dist/src/sheet/index.js.map +1 -1
- package/dist/src/sheet/library.d.ts +8 -0
- package/dist/src/sheet/library.d.ts.map +1 -1
- package/dist/src/sheet/library.js.map +1 -1
- package/dist/src/sheet/link.d.ts.map +1 -1
- package/dist/src/sheet/record.js.map +1 -1
- package/dist/src/sheet/root.d.ts +23 -1
- package/dist/src/sheet/root.d.ts.map +1 -1
- package/dist/src/sheet/root.js +40 -5
- package/dist/src/sheet/root.js.map +1 -1
- package/dist/src/sheet/sheet.d.ts +9332 -5621
- package/dist/src/sheet/sheet.d.ts.map +1 -1
- package/dist/src/sheet/sheet.js +41 -2
- package/dist/src/sheet/sheet.js.map +1 -1
- package/dist/src/sheet/templates.d.ts +4 -0
- package/dist/src/sheet/templates.d.ts.map +1 -1
- package/dist/src/sheet/types.d.ts +13020 -5839
- package/dist/src/sheet/types.d.ts.map +1 -1
- package/dist/src/sheet/types.js +114 -2
- package/dist/src/sheet/types.js.map +1 -1
- package/dist/src/studio/builder.d.ts +6 -6
- package/dist/src/studio/library.d.ts +9 -9
- package/dist/src/studio/pages.d.ts +6 -6
- package/dist/src/studio/publish.d.ts +3 -3
- package/dist/src/utils/derive.d.ts +1 -1
- package/dist/src/utils/derive.js +1 -1
- package/dist/src/utils/derive.js.map +1 -1
- package/dist/src/utils/key-visibility.d.ts +1 -1
- package/dist/src/utils/record-kind.d.ts +625 -0
- package/dist/src/utils/record-kind.d.ts.map +1 -0
- package/dist/src/utils/record-kind.js +450 -0
- package/dist/src/utils/record-kind.js.map +1 -0
- package/dist/src/utils/views.d.ts +55 -0
- package/dist/src/utils/views.d.ts.map +1 -0
- package/dist/src/utils/views.js +72 -0
- package/dist/src/utils/views.js.map +1 -0
- package/dist/src/utils/vocabulary.d.ts +48 -0
- package/dist/src/utils/vocabulary.d.ts.map +1 -0
- package/dist/src/utils/vocabulary.js +47 -0
- package/dist/src/utils/vocabulary.js.map +1 -0
- package/dist/test/allocation/allocation.examples.d.ts +3 -1
- package/dist/test/allocation/allocation.examples.d.ts.map +1 -1
- package/dist/test/allocation/allocation.examples.js +81 -2
- package/dist/test/allocation/allocation.examples.js.map +1 -1
- package/dist/test/bind/data/data.examples.d.ts +1 -1
- package/dist/test/bind/func/func.examples.d.ts +19 -1
- package/dist/test/bind/func/func.examples.d.ts.map +1 -1
- package/dist/test/bind/func/func.examples.js +36 -1
- package/dist/test/bind/func/func.examples.js.map +1 -1
- package/dist/test/calendar/calendar.examples.d.ts +2 -0
- package/dist/test/calendar/calendar.examples.d.ts.map +1 -1
- package/dist/test/calendar/calendar.examples.js +80 -2
- package/dist/test/calendar/calendar.examples.js.map +1 -1
- package/dist/test/decision/queue.examples.d.ts +1 -0
- package/dist/test/decision/queue.examples.d.ts.map +1 -1
- package/dist/test/decision/queue.examples.js +12 -0
- package/dist/test/decision/queue.examples.js.map +1 -1
- package/dist/test/examples-package.d.ts.map +1 -1
- package/dist/test/examples-package.js +5 -4
- package/dist/test/examples-package.js.map +1 -1
- package/dist/test/flowchart/flowchart.examples.d.ts +1250 -4605
- package/dist/test/flowchart/flowchart.examples.d.ts.map +1 -1
- package/dist/test/flowchart/flowchart.examples.js +861 -512
- package/dist/test/flowchart/flowchart.examples.js.map +1 -1
- package/dist/test/ontology/ontology.examples.d.ts +2 -3
- package/dist/test/ontology/ontology.examples.d.ts.map +1 -1
- package/dist/test/ontology/ontology.examples.js +32 -21
- package/dist/test/ontology/ontology.examples.js.map +1 -1
- package/dist/test/plan/plan-events.examples.d.ts +1 -0
- package/dist/test/plan/plan-events.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-events.examples.js +44 -0
- package/dist/test/plan/plan-events.examples.js.map +1 -1
- package/dist/test/plan/plan-nested.examples.d.ts +15 -15
- package/dist/test/plan/plan-nested.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-nested.examples.js +5 -5
- package/dist/test/plan/plan-nested.examples.js.map +1 -1
- package/dist/test/plan/plan-records.examples.d.ts.map +1 -1
- package/dist/test/plan/plan-records.examples.js +1 -1
- package/dist/test/plan/plan-records.examples.js.map +1 -1
- package/dist/test/plan/plan.examples.js.map +1 -1
- package/dist/test/query/query.examples.d.ts +2 -0
- package/dist/test/query/query.examples.d.ts.map +1 -1
- package/dist/test/query/query.examples.js +37 -1
- package/dist/test/query/query.examples.js.map +1 -1
- package/dist/test/roster/domain.examples.d.ts.map +1 -1
- package/dist/test/roster/roster.examples.d.ts +2 -0
- package/dist/test/roster/roster.examples.d.ts.map +1 -1
- package/dist/test/roster/roster.examples.js +76 -15
- package/dist/test/roster/roster.examples.js.map +1 -1
- package/dist/test/schematic/schematic-records.examples.d.ts.map +1 -1
- package/dist/test/schematic/schematic-records.examples.js +6 -4
- package/dist/test/schematic/schematic-records.examples.js.map +1 -1
- package/dist/test/schematic/schematic.examples.d.ts +1 -0
- package/dist/test/schematic/schematic.examples.d.ts.map +1 -1
- package/dist/test/schematic/schematic.examples.js +144 -0
- package/dist/test/schematic/schematic.examples.js.map +1 -1
- package/dist/test/sheet/sheet-actions.examples.d.ts +1264 -0
- package/dist/test/sheet/sheet-actions.examples.d.ts.map +1 -0
- package/dist/test/sheet/sheet-actions.examples.js +394 -0
- package/dist/test/sheet/sheet-actions.examples.js.map +1 -0
- package/dist/test/sheet/sheet-records.examples.d.ts +1 -1
- package/dist/test/sheet/sheet.examples.d.ts +1 -0
- package/dist/test/sheet/sheet.examples.d.ts.map +1 -1
- package/dist/test/sheet/sheet.examples.js +77 -0
- package/dist/test/sheet/sheet.examples.js.map +1 -1
- package/dist/test/studio/studio.examples.d.ts +1 -0
- package/dist/test/studio/studio.examples.d.ts.map +1 -1
- package/dist/test/studio/studio.examples.js +18 -8
- package/dist/test/studio/studio.examples.js.map +1 -1
- package/package.json +14 -13
- package/dist/src/diff/index.d.ts +0 -407
- package/dist/src/diff/index.d.ts.map +0 -1
- package/dist/src/diff/index.js +0 -203
- package/dist/src/diff/index.js.map +0 -1
- package/dist/src/experiment/index.d.ts +0 -1033
- package/dist/src/experiment/index.d.ts.map +0 -1
- package/dist/src/experiment/index.js +0 -197
- package/dist/src/experiment/index.js.map +0 -1
- package/dist/src/experiment/types.d.ts +0 -1093
- package/dist/src/experiment/types.d.ts.map +0 -1
- package/dist/src/experiment/types.js +0 -423
- package/dist/src/experiment/types.js.map +0 -1
- package/dist/src/flowchart/over.d.ts +0 -227
- package/dist/src/flowchart/over.d.ts.map +0 -1
- package/dist/src/flowchart/over.js +0 -213
- package/dist/src/flowchart/over.js.map +0 -1
- package/dist/src/flowchart/patch.d.ts +0 -64
- package/dist/src/flowchart/patch.d.ts.map +0 -1
- package/dist/src/flowchart/patch.js +0 -67
- package/dist/src/flowchart/patch.js.map +0 -1
- package/dist/src/flowchart/values.d.ts +0 -271
- package/dist/src/flowchart/values.d.ts.map +0 -1
- package/dist/src/flowchart/values.js +0 -220
- package/dist/src/flowchart/values.js.map +0 -1
- package/dist/src/flowchart/window.d.ts +0 -306
- package/dist/src/flowchart/window.d.ts.map +0 -1
- package/dist/src/flowchart/window.js +0 -63
- package/dist/src/flowchart/window.js.map +0 -1
- package/dist/src/runtime/diff.d.ts +0 -36
- package/dist/src/runtime/diff.d.ts.map +0 -1
- package/dist/src/runtime/diff.js +0 -34
- package/dist/src/runtime/diff.js.map +0 -1
- package/dist/src/runtime/experiment.d.ts +0 -48
- package/dist/src/runtime/experiment.d.ts.map +0 -1
- package/dist/src/runtime/experiment.js +0 -10
- package/dist/src/runtime/experiment.js.map +0 -1
- package/dist/test/diff/diff.examples.d.ts +0 -313
- package/dist/test/diff/diff.examples.d.ts.map +0 -1
- package/dist/test/diff/diff.examples.js +0 -452
- package/dist/test/diff/diff.examples.js.map +0 -1
- package/dist/test/experiment/experiment.examples.d.ts +0 -1512
- package/dist/test/experiment/experiment.examples.d.ts.map +0 -1
- package/dist/test/experiment/experiment.examples.js +0 -609
- package/dist/test/experiment/experiment.examples.js.map +0 -1
|
@@ -0,0 +1,1035 @@
|
|
|
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 Flowchart's details (#1507, `Flowchart Records Spec.md` §3, R18–R21):
|
|
7
|
+
* `Flowchart.details` and its four reads — `Flowchart.figure`, `.histogram`,
|
|
8
|
+
* `.list` and `.tags` — over the app's rows keyed as a kind's are, read only.
|
|
9
|
+
*
|
|
10
|
+
* - **A read is a frozen description behind a phantom brand**, as the Plan's
|
|
11
|
+
* named reads are (`plan/reads.ts`): the row it reads, its kind, where it
|
|
12
|
+
* shows and, for tags, whether the legend lists them. The row is inferred
|
|
13
|
+
* where the read is placed — in a `Flowchart.details` array — so a field
|
|
14
|
+
* name is checked against the row it reads, and a misfit is a type error
|
|
15
|
+
* naming the fields that fit (R20). A read's return type keeps its show and
|
|
16
|
+
* its legend from inference (`NoInfer`), so each is the one given — `"all"`
|
|
17
|
+
* and `false` unless given — wherever the read is placed, and `details`'
|
|
18
|
+
* keeps its row and its reads from the place it is put, so a kind's `details`
|
|
19
|
+
* never infers them in its stead.
|
|
20
|
+
* - **One array, in order** (R21): each kind's `details` is one array of
|
|
21
|
+
* reads, any mix of the four, drawn in its order and shown in full, in the
|
|
22
|
+
* same order, in the inspector, each where its `show` says. A transition's
|
|
23
|
+
* label draws lines, so its tags and histograms take `show: "inspector"`: a
|
|
24
|
+
* type error otherwise, with its words.
|
|
25
|
+
* - **Read as the kind is** (R19): beside a kind read whole the details bind
|
|
26
|
+
* `Data.bind(record)`, read whole; beside one read through its source,
|
|
27
|
+
* `Data.bindPaged(record)`, read by the drawn rows' keys, every key in one
|
|
28
|
+
* request. They are read only (R18): `Record.bind` is refused.
|
|
29
|
+
* - **The row type never reaches the payload**: `values` turns a row's bytes
|
|
30
|
+
* into one value a read, one type whatever the row holds.
|
|
31
|
+
* - **The rail's picks weigh them** (#1522, R43–R46): details that name a
|
|
32
|
+
* source of the slice, `source`, are bound through their record's entries
|
|
33
|
+
* index, and a pick of its Dict field's keys weighs each read the entries
|
|
34
|
+
* hold, added up by kind in East — a figure adds, a histogram adds bin by
|
|
35
|
+
* bin, a list adds by name and tags join — while `weigh: false` keeps a
|
|
36
|
+
* read's own. `fold` turns a row's bytes and its picked entries' into one
|
|
37
|
+
* value a read.
|
|
38
|
+
*
|
|
39
|
+
* What only the built value shows is refused as the Flowchart is built, in
|
|
40
|
+
* the tag's words (§3, "What the editor and the tag say").
|
|
41
|
+
*
|
|
42
|
+
* @packageDocumentation
|
|
43
|
+
*/
|
|
44
|
+
import { ArrayType, BlobType, DictType, East, Expr, FloatType, IntegerType, OptionType, SetType, StringType, StructType, isTypeEqual, none, some, variant, } from "@elaraai/east";
|
|
45
|
+
import { ColorSchemeType, StyleVariantType, resolveRowSource, resolveTag, } from "@elaraai/east-ui/internal";
|
|
46
|
+
import { fieldOf } from "../schedule/fields.js";
|
|
47
|
+
import { checkEntries, optionOf, typeText } from "../schedule/window.js";
|
|
48
|
+
import { FlowchartDetailRowType, FlowchartDetailsType, FlowchartTagColorType } from "./types.js";
|
|
49
|
+
/** The phantom brand a read carries: what it reads, its kind, where it shows and whether the legend lists it. */
|
|
50
|
+
export const FlowchartReadBrand = Symbol("FlowchartRead");
|
|
51
|
+
const READS = new WeakMap();
|
|
52
|
+
/** A read made: a frozen description, its spec kept beside it. */
|
|
53
|
+
function described(spec) {
|
|
54
|
+
const read = Object.freeze({ kind: spec.kind, field: spec.field });
|
|
55
|
+
READS.set(read, spec);
|
|
56
|
+
return read;
|
|
57
|
+
}
|
|
58
|
+
/** A read's options, as given: an object, or none. */
|
|
59
|
+
function optionsOf(where, options, required = false) {
|
|
60
|
+
if (options === undefined && !required)
|
|
61
|
+
return {};
|
|
62
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
63
|
+
throw new Error(`${where}: its options are an object — { label, unit, show, … }`);
|
|
64
|
+
}
|
|
65
|
+
return options;
|
|
66
|
+
}
|
|
67
|
+
/** A read's field, named. */
|
|
68
|
+
function fieldName(where, field) {
|
|
69
|
+
if (typeof field !== "string" || field === "")
|
|
70
|
+
throw new Error(`${where}: name the field it reads — a field of the details' row`);
|
|
71
|
+
return field;
|
|
72
|
+
}
|
|
73
|
+
/** A read's words: a label, a unit, what is counted. */
|
|
74
|
+
function wordOf(where, prop, given) {
|
|
75
|
+
if (given === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (typeof given !== "string")
|
|
78
|
+
throw new Error(`${where}: \`${prop}\` is a string`);
|
|
79
|
+
return given;
|
|
80
|
+
}
|
|
81
|
+
/** Whether the rail's picks weigh a read (#1522): `undefined` unless given. */
|
|
82
|
+
function weighOf(where, weigh) {
|
|
83
|
+
if (weigh === undefined || typeof weigh === "boolean")
|
|
84
|
+
return weigh;
|
|
85
|
+
throw new Error(`${where}: \`weigh\` is false to keep the row's own beside the rail's picks — a share or an average, which adding would make wrong`);
|
|
86
|
+
}
|
|
87
|
+
/** Where a read shows: "all" unless given. */
|
|
88
|
+
function showOf(where, show) {
|
|
89
|
+
if (show === undefined)
|
|
90
|
+
return "all";
|
|
91
|
+
if (show === "all" || show === "canvas" || show === "inspector")
|
|
92
|
+
return show;
|
|
93
|
+
throw new Error(`${where}: \`show\` is "all", "canvas" or "inspector" — where the read shows`);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A histogram's bins, checked: a literal's edges ascend, refused when built;
|
|
97
|
+
* bins the app binds are checked as they land, by the renderer (R27, R38).
|
|
98
|
+
*/
|
|
99
|
+
function binsOf(where, bins) {
|
|
100
|
+
if (bins === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
if (bins instanceof Expr) {
|
|
103
|
+
const type = Expr.type(bins);
|
|
104
|
+
if (!isTypeEqual(type, ArrayType(FloatType)))
|
|
105
|
+
throw new Error(`${where}: \`bins\` are the app's edges, an Array of Floats — and these are ${typeText(type)}`);
|
|
106
|
+
return bins;
|
|
107
|
+
}
|
|
108
|
+
if (!Array.isArray(bins))
|
|
109
|
+
throw new Error(`${where}: \`bins\` are the app's edges, ascending — [0, …], or an Array the app binds`);
|
|
110
|
+
if (bins.every((edge) => typeof edge === "number")) {
|
|
111
|
+
const edges = bins;
|
|
112
|
+
if (edges.length === 0)
|
|
113
|
+
throw new Error(`${where}: \`bins\` are the app's edges, ascending — give the first, [0, …]`);
|
|
114
|
+
for (const edge of edges)
|
|
115
|
+
if (!Number.isFinite(edge))
|
|
116
|
+
throw new Error(`${where}: the bins' edges are numbers — and one is ${edge}`);
|
|
117
|
+
for (let i = 1; i < edges.length; i++) {
|
|
118
|
+
if (!(edges[i] > edges[i - 1]))
|
|
119
|
+
throw new Error(`${where}: the bins' edges ascend — ${edges[i - 1]}, then ${edges[i]}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return East.value(bins, ArrayType(FloatType));
|
|
123
|
+
}
|
|
124
|
+
/** The axis's ticks, checked: each a number, or `{ at, label }`. */
|
|
125
|
+
function ticksOf(where, ticks) {
|
|
126
|
+
if (ticks === undefined)
|
|
127
|
+
return undefined;
|
|
128
|
+
const tick = (t) => (typeof t === "number" && Number.isFinite(t))
|
|
129
|
+
|| (typeof t === "object" && t !== null && typeof t.at === "number" && Number.isFinite(t.at)
|
|
130
|
+
&& typeof t.label === "string");
|
|
131
|
+
if (!Array.isArray(ticks) || !ticks.every(tick))
|
|
132
|
+
throw new Error(`${where}: \`ticks\` lists the edges the axis names — each a number, or { at, label }`);
|
|
133
|
+
return Object.freeze(ticks.map((t) => typeof t === "number" ? t : Object.freeze({ at: t.at, label: t.label })));
|
|
134
|
+
}
|
|
135
|
+
/** The cases of a variant of nulls, as a refusal names them. */
|
|
136
|
+
function casesOf(type) {
|
|
137
|
+
return type.type === "Variant" ? Object.keys(type.cases) : [];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The tags' looks, checked: a literal — each tag's `{ colorPalette, variant }`,
|
|
141
|
+
* first defined, first shown — or an Array of `Flowchart.Types.TagColor` the
|
|
142
|
+
* app binds.
|
|
143
|
+
*/
|
|
144
|
+
function colorsOf(where, colors) {
|
|
145
|
+
const Colors = ArrayType(FlowchartTagColorType);
|
|
146
|
+
if (colors === undefined)
|
|
147
|
+
return East.value([], Colors);
|
|
148
|
+
if (colors instanceof Expr) {
|
|
149
|
+
const type = Expr.type(colors);
|
|
150
|
+
if (!isTypeEqual(type, Colors)) {
|
|
151
|
+
throw new Error(`${where}: \`colors\` gives each tag its look — { "Hazmat": { colorPalette, variant } }, or an Array of Flowchart.Types.TagColor ` +
|
|
152
|
+
`the app binds — and this is ${typeText(type)}`);
|
|
153
|
+
}
|
|
154
|
+
return colors;
|
|
155
|
+
}
|
|
156
|
+
if (Array.isArray(colors))
|
|
157
|
+
return East.value(colors, Colors);
|
|
158
|
+
if (typeof colors !== "object" || colors === null) {
|
|
159
|
+
throw new Error(`${where}: \`colors\` gives each tag its look — { "Hazmat": { colorPalette, variant } }, or an Array of Flowchart.Types.TagColor the app binds`);
|
|
160
|
+
}
|
|
161
|
+
const palettes = casesOf(ColorSchemeType);
|
|
162
|
+
const variants = casesOf(StyleVariantType);
|
|
163
|
+
const looks = Object.entries(colors).map(([tag, look]) => {
|
|
164
|
+
const given = look;
|
|
165
|
+
if (typeof given !== "object" || given === null || given.colorPalette === undefined) {
|
|
166
|
+
throw new Error(`${where}: "${tag}"'s look is { colorPalette, variant? }, as a Tag takes it`);
|
|
167
|
+
}
|
|
168
|
+
if (typeof given.colorPalette === "string" && !palettes.includes(given.colorPalette)) {
|
|
169
|
+
throw new Error(`${where}: "${tag}"'s colorPalette is one of east-ui's colour schemes — "danger", "red", "brand", … — and this is "${given.colorPalette}"`);
|
|
170
|
+
}
|
|
171
|
+
if (typeof given.variant === "string" && !variants.includes(given.variant)) {
|
|
172
|
+
throw new Error(`${where}: "${tag}"'s variant is one of a Tag's variants — "subtle", "solid", "outline", … — and this is "${given.variant}"`);
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
tag,
|
|
176
|
+
colorPalette: resolveTag(given.colorPalette, ColorSchemeType),
|
|
177
|
+
variant: resolveTag((given.variant ?? "subtle"), StyleVariantType),
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
return East.value(looks, Colors);
|
|
181
|
+
}
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// The four reads (§3)
|
|
184
|
+
// ============================================================================
|
|
185
|
+
/**
|
|
186
|
+
* A figure (R25): a number, or an Option of one — a line on the card or the
|
|
187
|
+
* label, its label and its value with its unit; the inspector's stats.
|
|
188
|
+
*
|
|
189
|
+
* @typeParam R - The details' row type, inferred where the read is placed
|
|
190
|
+
* @typeParam F - The field
|
|
191
|
+
* @typeParam W - Where it shows: `"all"` unless given
|
|
192
|
+
* @param field - The field it reads
|
|
193
|
+
* @param options - Its label — the field's name unless given — its unit, where it shows, and `weigh: false` to keep the
|
|
194
|
+
* row's own beside the rail's picks — a share or an average — which add the entries' otherwise (#1522)
|
|
195
|
+
* @returns The read, for `Flowchart.details`
|
|
196
|
+
* @throws {Error} When the field is named by no string, or an option is of another kind
|
|
197
|
+
*/
|
|
198
|
+
export function flowchartFigure(field, options) {
|
|
199
|
+
const where = "Flowchart.figure";
|
|
200
|
+
const given = optionsOf(where, options);
|
|
201
|
+
return described({
|
|
202
|
+
kind: "figure", field: fieldName(where, field), show: showOf(where, given["show"]),
|
|
203
|
+
label: wordOf(where, "label", given["label"]), unit: wordOf(where, "unit", given["unit"]), weigh: weighOf(where, given["weigh"]),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* A histogram (R26–R28): a Dict from a duration to its count, both numbers —
|
|
208
|
+
* or, beside the app's `bins`, an Array of counts, one a bin. The card's bars
|
|
209
|
+
* on their axis; the inspector's chart.
|
|
210
|
+
*
|
|
211
|
+
* @typeParam R - The details' row type, inferred where the read is placed
|
|
212
|
+
* @typeParam F - The field
|
|
213
|
+
* @typeParam W - Where it shows: `"all"` unless given
|
|
214
|
+
* @param field - The field it reads
|
|
215
|
+
* @param options - Its unit, its label, what it counts, the app's bins — required beside an Array of counts — how the bars
|
|
216
|
+
* space them, the edges the axis names, where it shows, and `weigh: false` to keep the row's own beside the rail's picks
|
|
217
|
+
* @returns The read, for `Flowchart.details`
|
|
218
|
+
* @throws {Error} When the unit, the scale or a tick is none of its words, and when the bins' edges do not ascend —
|
|
219
|
+
* "The bins' edges ascend — 12, then 4"
|
|
220
|
+
*/
|
|
221
|
+
export function flowchartHistogram(field,
|
|
222
|
+
// an Array of counts is a bin each: beside one, the bins are required
|
|
223
|
+
options) {
|
|
224
|
+
const where = "Flowchart.histogram";
|
|
225
|
+
const name = fieldName(where, field);
|
|
226
|
+
const given = optionsOf(where, options, true);
|
|
227
|
+
const unit = given["unit"];
|
|
228
|
+
if (unit !== "minutes" && unit !== "hours" && unit !== "days") {
|
|
229
|
+
throw new Error(`${where}: \`unit\` is the unit "${name}"'s durations are in — "minutes", "hours" or "days"`);
|
|
230
|
+
}
|
|
231
|
+
const scale = given["scale"] ?? "band";
|
|
232
|
+
if (scale !== "band" && scale !== "linear")
|
|
233
|
+
throw new Error(`${where}: \`scale\` is "band", a bar a bin, or "linear", each bar as wide as its span`);
|
|
234
|
+
return described({
|
|
235
|
+
kind: "histogram", field: name, show: showOf(where, given["show"]), label: wordOf(where, "label", given["label"]),
|
|
236
|
+
unit, counts: wordOf(where, "counts", given["counts"]), bins: binsOf(where, given["bins"]), scale, ticks: ticksOf(where, given["ticks"]),
|
|
237
|
+
weigh: weighOf(where, given["weigh"]),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* A list (R29): a Dict from an item's name to a number, or to a struct whose
|
|
242
|
+
* `draw` says which number is the value, and a note. Lines on a card, under
|
|
243
|
+
* the list's title, or under a transition's evidence; the inspector's list.
|
|
244
|
+
*
|
|
245
|
+
* @typeParam R - The details' row type, inferred where the read is placed
|
|
246
|
+
* @typeParam F - The field
|
|
247
|
+
* @typeParam W - Where it shows: `"all"` unless given
|
|
248
|
+
* @param field - The field it reads
|
|
249
|
+
* @param options - Its label, its unit, the lines it draws on the canvas — 2 on a card and 1 on a label unless given, at
|
|
250
|
+
* most 3: the inspector lists every item — where it shows, and `weigh: false` to keep the row's own beside the rail's
|
|
251
|
+
* picks, which add the entries' by name otherwise (#1522)
|
|
252
|
+
* @param draw - An item's value and its note, from the item, its name and the row: required for a Dict of structs.
|
|
253
|
+
* Weighed, it draws the picked entries' items added up, each struct's numbers added and its lists joined
|
|
254
|
+
* @returns The read, for `Flowchart.details`
|
|
255
|
+
* @throws {Error} When `top` is not 1, 2 or 3, or `draw` is no function
|
|
256
|
+
*/
|
|
257
|
+
export function flowchartList(field, options, // top: the card's or label's lines
|
|
258
|
+
draw) {
|
|
259
|
+
const where = "Flowchart.list";
|
|
260
|
+
const given = optionsOf(where, options);
|
|
261
|
+
const top = given["top"];
|
|
262
|
+
if (top !== undefined && top !== 1 && top !== 2 && top !== 3)
|
|
263
|
+
throw new Error(`${where}: \`top\` is 1, 2 or 3 — the inspector lists every item`);
|
|
264
|
+
if (draw !== undefined && typeof draw !== "function")
|
|
265
|
+
throw new Error(`${where}: \`draw\` gives an item's value and note — (item, name, row) => ({ value, note })`);
|
|
266
|
+
return described({
|
|
267
|
+
kind: "list", field: fieldName(where, field), show: showOf(where, given["show"]), label: wordOf(where, "label", given["label"]),
|
|
268
|
+
unit: wordOf(where, "unit", given["unit"]), top, draw: draw, weigh: weighOf(where, given["weigh"]),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* A tags read (R30, R31): an Array or a Set of strings, the row's tags. On a
|
|
273
|
+
* card, a list beside its label, wrapping to three lines; the inspector shows
|
|
274
|
+
* every tag.
|
|
275
|
+
*
|
|
276
|
+
* @typeParam R - The details' row type, inferred where the read is placed
|
|
277
|
+
* @typeParam F - The field
|
|
278
|
+
* @typeParam W - Where it shows: `"all"` unless given
|
|
279
|
+
* @typeParam L - Whether the canvas's legend lists the defined tags
|
|
280
|
+
* @param field - The field it reads
|
|
281
|
+
* @param options - Its label; each tag's look as a Tag takes it — `{ colorPalette, variant }`, first defined, first shown,
|
|
282
|
+
* a literal or an Array the app binds (`Flowchart.Types.TagColor`) — a tag with none neutral and subtle; whether the
|
|
283
|
+
* legend lists the defined tags, for tags on the canvas only; where it shows; and `weigh: false` to keep the row's own
|
|
284
|
+
* beside the rail's picks, whose entries' tags join otherwise (#1522): a Set's as a Set does, in its order, an Array's
|
|
285
|
+
* each once in the entries' key order
|
|
286
|
+
* @returns The read, for `Flowchart.details`
|
|
287
|
+
* @throws {Error} When a look's palette or variant is none of east-ui's, or `legend` is given beside `show: "inspector"` —
|
|
288
|
+
* "Only tags on the canvas go in the legend — show them on the canvas, or drop legend"
|
|
289
|
+
*/
|
|
290
|
+
export function flowchartTags(field, options) {
|
|
291
|
+
const where = "Flowchart.tags";
|
|
292
|
+
const given = optionsOf(where, options);
|
|
293
|
+
const show = showOf(where, given["show"]);
|
|
294
|
+
const legend = given["legend"] ?? false;
|
|
295
|
+
if (typeof legend !== "boolean")
|
|
296
|
+
throw new Error(`${where}: \`legend\` is true to list the defined tags in the canvas's legend`);
|
|
297
|
+
if (legend && show === "inspector")
|
|
298
|
+
throw new Error(`${where}: only tags on the canvas go in the legend — show them on the canvas, or drop legend`);
|
|
299
|
+
return described({
|
|
300
|
+
kind: "tags", field: fieldName(where, field), show, label: wordOf(where, "label", given["label"]),
|
|
301
|
+
colors: colorsOf(where, given["colors"]), legend, weigh: weighOf(where, given["weigh"]),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/** The key a kind's details carry their mark under. */
|
|
305
|
+
export const FLOWCHART_DETAILS = Symbol("Flowchart.details");
|
|
306
|
+
/** The phantom brand a kind's details carry: their row, their reads, and whether a transition's label draws them. */
|
|
307
|
+
export const FlowchartDetailsBrand = Symbol("FlowchartDetails");
|
|
308
|
+
/**
|
|
309
|
+
* Whether something is a kind's details: `Flowchart.details`'.
|
|
310
|
+
*
|
|
311
|
+
* @param value - What a kind's `details` holds
|
|
312
|
+
* @returns Whether it is
|
|
313
|
+
*/
|
|
314
|
+
export function isFlowchartDetails(value) {
|
|
315
|
+
return typeof value === "object" && value !== null && FLOWCHART_DETAILS in value
|
|
316
|
+
&& value[FLOWCHART_DETAILS] === "details";
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The rows an index-selected binding's type pages through (#1522): each
|
|
320
|
+
* entry's joined row and its key, as `Data.bindPaged(record, { indexes })`'s
|
|
321
|
+
* `page` serves them.
|
|
322
|
+
*
|
|
323
|
+
* @param type - The binding's type
|
|
324
|
+
* @returns The row's struct and the key's type; `undefined` for a binding that pages no such entries
|
|
325
|
+
* @internal
|
|
326
|
+
*/
|
|
327
|
+
export function flowchartIndexedRows(type) {
|
|
328
|
+
const fields = type.type === "Struct" ? type.fields : {};
|
|
329
|
+
const page = fields["page"];
|
|
330
|
+
const output = page?.type === "Function" ? page.output : undefined;
|
|
331
|
+
const entry = output?.type === "Variant" ? output.cases["some"] : undefined;
|
|
332
|
+
const held = entry?.type === "Array" ? entry.value : undefined;
|
|
333
|
+
const joined = held?.type === "Struct" ? held.fields["row"] : undefined;
|
|
334
|
+
const row = joined === undefined ? undefined : optionOf(joined);
|
|
335
|
+
const key = held?.type === "Struct" ? held.fields["key"] : undefined;
|
|
336
|
+
return row?.type !== "Struct" || key === undefined ? undefined : { row, key };
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* The details' record bound through its entries index (#1522):
|
|
340
|
+
* `Data.bindPaged(record, { indexes: [its entries index] })`, the rows read by
|
|
341
|
+
* key through its lean rows. Its row type from its entries' joined row, its
|
|
342
|
+
* entries index from the binding's metadata.
|
|
343
|
+
*
|
|
344
|
+
* @throws {Error} When the binding lists no entries index, or several, or holds its rows by another key than a String
|
|
345
|
+
*/
|
|
346
|
+
function indexedOf(record, type, where) {
|
|
347
|
+
const fields = type.fields;
|
|
348
|
+
const paged = flowchartIndexedRows(type);
|
|
349
|
+
if (paged === undefined)
|
|
350
|
+
throw new Error(`${where}: Data.bindPaged(record, { indexes }) reads no rows of structs`);
|
|
351
|
+
const { row, key } = paged;
|
|
352
|
+
if (!isTypeEqual(key, StringType)) {
|
|
353
|
+
throw new Error(`${where}: the details' record holds its rows by String key, keyed as the kind's are — and this one by ${typeText(key)}`);
|
|
354
|
+
}
|
|
355
|
+
const indexes = fields["indexes"];
|
|
356
|
+
const listed = indexes?.type === "Struct" ? Object.entries(indexes.fields) : [];
|
|
357
|
+
const entries = listed.flatMap(([index, meta]) => {
|
|
358
|
+
const parts = meta.type === "Struct" ? meta.fields : {};
|
|
359
|
+
const kind = parts["kind"];
|
|
360
|
+
const led = parts["fields"];
|
|
361
|
+
if (kind?.type !== "Variant" || !Object.hasOwn(kind.cases, "entries") || led?.type !== "Struct")
|
|
362
|
+
return [];
|
|
363
|
+
const field = Object.keys(led.fields)[0];
|
|
364
|
+
const dict = field === undefined ? undefined : row.fields[field];
|
|
365
|
+
return field === undefined || dict?.type !== "Dict" ? [] : [{ index, field, valueType: dict.value }];
|
|
366
|
+
});
|
|
367
|
+
if (entries.length === 0) {
|
|
368
|
+
throw new Error(`${where}: the details are read by key through the record's own entries, Data.bindPaged(record) — or, weighed by the rail's ` +
|
|
369
|
+
"picks, through the record's entries index — and this one reads it through other indexes");
|
|
370
|
+
}
|
|
371
|
+
if (entries.length > 1) {
|
|
372
|
+
throw new Error(`${where}: the details are weighed by one entries index, and this binding lists ${entries.map((e) => `'${e.index}'`).join(", ")} — list one`);
|
|
373
|
+
}
|
|
374
|
+
return { form: "paged", handle: record, rowType: row, entries: entries[0] };
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Tells the details' binding by its handle's East struct (`bind/sources.ts`'
|
|
378
|
+
* rule): a `page` field is `Data.bindPaged`'s, a `mutate` field
|
|
379
|
+
* `Record.bind`'s, which is refused — the details are read only — and any
|
|
380
|
+
* other `Data.bind`'s.
|
|
381
|
+
*/
|
|
382
|
+
function bindingOf(record, where) {
|
|
383
|
+
const forms = "details are read only: bind them with Data.bind(record), or Data.bindPaged(record) beside a kind read through its source";
|
|
384
|
+
if (!(record instanceof Expr))
|
|
385
|
+
throw new Error(`${where}: ${forms}`);
|
|
386
|
+
const type = Expr.type(record);
|
|
387
|
+
const fields = type.type === "Struct" ? type.fields : undefined;
|
|
388
|
+
if (fields === undefined)
|
|
389
|
+
throw new Error(`${where}: ${forms} — and this is ${typeText(type)}`);
|
|
390
|
+
let form;
|
|
391
|
+
let recordType;
|
|
392
|
+
if (fields["page"] !== undefined) {
|
|
393
|
+
form = "paged";
|
|
394
|
+
// Through its entries index, the rail's picks weigh them (#1522).
|
|
395
|
+
if (fields["indexes"] !== undefined)
|
|
396
|
+
return indexedOf(record, type, where);
|
|
397
|
+
const resolved = resolveRowSource(record, where);
|
|
398
|
+
if (resolved.kind !== "paged") {
|
|
399
|
+
throw new Error(`${where}: the details are read by key through the record's own entries, Data.bindPaged(record) — and this one pages an index's window`);
|
|
400
|
+
}
|
|
401
|
+
recordType = resolved.collectionType;
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
if (fields["mutate"] !== undefined)
|
|
405
|
+
throw new Error(`${where}: ${forms}`);
|
|
406
|
+
const read = fields["read"];
|
|
407
|
+
if (read === undefined || read.type !== "Function" || read.inputs.length !== 0 || fields["binding"] === undefined) {
|
|
408
|
+
throw new Error(`${where}: ${forms}`);
|
|
409
|
+
}
|
|
410
|
+
form = "data";
|
|
411
|
+
recordType = read.output;
|
|
412
|
+
}
|
|
413
|
+
if (recordType.type !== "Dict" || !isTypeEqual(recordType.key, StringType) || recordType.value.type !== "Struct") {
|
|
414
|
+
throw new Error(`${where}: the details' record holds its rows by String key, keyed as the kind's are, each a struct — and this one holds ${typeText(recordType)}`);
|
|
415
|
+
}
|
|
416
|
+
const rowType = recordType.value;
|
|
417
|
+
if (form === "paged")
|
|
418
|
+
checkEntries(record, StringType, rowType, `${where}: Data.bindPaged(record)`);
|
|
419
|
+
return { form, handle: record, rowType, entries: undefined };
|
|
420
|
+
}
|
|
421
|
+
/** Whether a type is a number: an Integer or a Float. */
|
|
422
|
+
function isNumber(type) {
|
|
423
|
+
return type !== undefined && (type.type === "Integer" || type.type === "Float");
|
|
424
|
+
}
|
|
425
|
+
/** The fields of a row that fit a read, by what each read takes (R20). */
|
|
426
|
+
const FITS = {
|
|
427
|
+
figure: { fits: (t) => isNumber(t) || isNumber(optionOf(t)), what: "a figure reads a number, or an Option of one" },
|
|
428
|
+
histogram: {
|
|
429
|
+
fits: (t) => (t.type === "Dict" && isNumber(t.key) && isNumber(t.value)) || (t.type === "Array" && isNumber(t.value)),
|
|
430
|
+
what: "a histogram reads a Dict from a duration to its count, both numbers, or an Array of counts beside its bins",
|
|
431
|
+
},
|
|
432
|
+
list: {
|
|
433
|
+
fits: (t) => t.type === "Dict" && isTypeEqual(t.key, StringType) && (isNumber(t.value) || t.value.type === "Struct"),
|
|
434
|
+
what: "a list reads a Dict from an item's name to a number, or to a struct its draw reads",
|
|
435
|
+
},
|
|
436
|
+
tags: {
|
|
437
|
+
fits: (t) => (t.type === "Array" || t.type === "Set") && isTypeEqual((t.type === "Array" ? t.value : t.key), StringType),
|
|
438
|
+
what: "tags read an Array or a Set of strings",
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
/** A number as a Float: an Integer converted, a Float as it is. */
|
|
442
|
+
function asFloat(value, type) {
|
|
443
|
+
return (type.type === "Integer" ? value.toFloat() : value);
|
|
444
|
+
}
|
|
445
|
+
/** What a list's draw gives: an item's value, and its note. */
|
|
446
|
+
const DrawnType = StructType({ value: FloatType, note: OptionType(StringType) });
|
|
447
|
+
/** One bin of a histogram's value. */
|
|
448
|
+
const BinType = StructType({ from: FloatType, count: FloatType });
|
|
449
|
+
/** One item of a list's value. */
|
|
450
|
+
const ItemType = StructType({ name: StringType, value: FloatType, note: OptionType(StringType) });
|
|
451
|
+
/** A list's draw, reified once over the item, its name and the row (`shared/reify.ts`' rule), its value a Float. */
|
|
452
|
+
function drawOf(where, field, itemType, rowType, draw) {
|
|
453
|
+
const words = `${where}: "${field}"'s draw gives { value, note? } — value a number, note an Option of a String`;
|
|
454
|
+
return East.function([itemType, StringType, rowType], DrawnType, (_$, item, name, row) => {
|
|
455
|
+
const given = draw(item, name, row);
|
|
456
|
+
if (typeof given !== "object" || given === null || given.value === undefined)
|
|
457
|
+
throw new Error(words);
|
|
458
|
+
let value;
|
|
459
|
+
if (typeof given.value === "number")
|
|
460
|
+
value = East.value(given.value, FloatType);
|
|
461
|
+
else if (typeof given.value === "bigint")
|
|
462
|
+
value = East.value(given.value, IntegerType).toFloat();
|
|
463
|
+
else if (given.value instanceof Expr && isNumber(Expr.type(given.value))) {
|
|
464
|
+
value = asFloat(given.value, Expr.type(given.value));
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
throw new Error(`${words} — and its value is ${given.value instanceof Expr ? typeText(Expr.type(given.value)) : String(given.value)}`);
|
|
468
|
+
}
|
|
469
|
+
if (given.note instanceof Expr && !isTypeEqual(Expr.type(given.note), OptionType(StringType))) {
|
|
470
|
+
throw new Error(`${words} — and its note is ${typeText(Expr.type(given.note))}`);
|
|
471
|
+
}
|
|
472
|
+
const note = given.note === undefined ? East.value(none, OptionType(StringType)) : East.value(given.note, OptionType(StringType));
|
|
473
|
+
return East.value({ value, note }, DrawnType);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* A read resolved against the details' row (R20): its field checked, and the
|
|
478
|
+
* value a row holds for it.
|
|
479
|
+
*
|
|
480
|
+
* @throws {Error} Naming the read and the field: a field the row lacks, or one of another type, naming those that fit;
|
|
481
|
+
* an Array of counts without bins; a Dict of structs without a draw
|
|
482
|
+
*/
|
|
483
|
+
function resolve(spec, rowType) {
|
|
484
|
+
const where = `Flowchart.${spec.kind}`;
|
|
485
|
+
const fields = rowType.fields;
|
|
486
|
+
const type = fields[spec.field];
|
|
487
|
+
if (type === undefined)
|
|
488
|
+
throw new Error(`${where}: "${spec.field}" is no field of the details' row (${Object.keys(fields).join(", ")})`);
|
|
489
|
+
const { fits, what } = FITS[spec.kind];
|
|
490
|
+
if (!fits(type)) {
|
|
491
|
+
const fitting = Object.keys(fields).filter((name) => fits(fields[name])).map((name) => `"${name}"`);
|
|
492
|
+
throw new Error(`${where}: "${spec.field}" holds ${typeText(type)} — ${what}: ${fitting.length > 0 ? fitting.join(", ") : "the details' row has none"}`);
|
|
493
|
+
}
|
|
494
|
+
switch (spec.kind) {
|
|
495
|
+
case "figure": {
|
|
496
|
+
const inner = optionOf(type);
|
|
497
|
+
return {
|
|
498
|
+
spec,
|
|
499
|
+
value: (_$, row) => {
|
|
500
|
+
const held = fieldOf(row, spec.field);
|
|
501
|
+
if (inner === undefined)
|
|
502
|
+
return variant("figure", some(asFloat(held, type)));
|
|
503
|
+
if (inner.type === "Float")
|
|
504
|
+
return variant("figure", held);
|
|
505
|
+
return variant("figure", held.match({
|
|
506
|
+
none: (_$2) => East.value(none, OptionType(FloatType)),
|
|
507
|
+
some: (_$2, n) => East.value(some(n.toFloat()), OptionType(FloatType)),
|
|
508
|
+
}));
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
case "histogram": {
|
|
513
|
+
if (type.type === "Array") {
|
|
514
|
+
const bins = spec.bins;
|
|
515
|
+
if (bins === undefined)
|
|
516
|
+
throw new Error(`${where}: "${spec.field}" is an Array of counts — give the bins it counts in, bins: [0, …]`);
|
|
517
|
+
const countType = type.value;
|
|
518
|
+
return {
|
|
519
|
+
spec,
|
|
520
|
+
// An Array's counts are a bin each, in the bins' order: each at its bin's edge — or, another number of
|
|
521
|
+
// them, each at NaN, which no bin holds, so the row is listed in Issues and its bars drawn absent (R27).
|
|
522
|
+
value: ($, row) => {
|
|
523
|
+
const counts = $.const(fieldOf(row, spec.field));
|
|
524
|
+
const edges = $.const(bins);
|
|
525
|
+
const bars = counts.size().equal(edges.size()).ifElse((_$2) => counts.map((_$3, count, i) => East.value({ from: edges.get(i), count: asFloat(count, countType) }, BinType)), (_$2) => counts.size().equal(0n).ifElse((_$3) => East.value([{ from: NaN, count: 0 }], ArrayType(BinType)), (_$3) => counts.map((_$4, count) => East.value({ from: NaN, count: asFloat(count, countType) }, BinType))));
|
|
526
|
+
return variant("histogram", bars);
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
const durations = type;
|
|
531
|
+
const keyType = durations.key;
|
|
532
|
+
const countType = durations.value;
|
|
533
|
+
return {
|
|
534
|
+
spec,
|
|
535
|
+
// A Dict's durations as they are, keyed: the renderer counts each into the bin that holds it (R27).
|
|
536
|
+
value: (_$, row) => variant("histogram", fieldOf(row, spec.field).toArray((_$2, count, from) => East.value({ from: asFloat(from, keyType), count: asFloat(count, countType) }, BinType))),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
case "list": {
|
|
540
|
+
const itemType = type.value;
|
|
541
|
+
if (itemType.type === "Struct" && spec.draw === undefined) {
|
|
542
|
+
const number = Object.entries(itemType.fields).find(([, t]) => isNumber(t))?.[0];
|
|
543
|
+
throw new Error(`${where}: "${spec.field}" holds structs — give a draw, (item) => ({ value: item.${number ?? "…"} })`);
|
|
544
|
+
}
|
|
545
|
+
if (spec.draw !== undefined) {
|
|
546
|
+
const draw = drawOf(where, spec.field, itemType, rowType, spec.draw);
|
|
547
|
+
return {
|
|
548
|
+
spec,
|
|
549
|
+
drawn: draw,
|
|
550
|
+
value: ($, row) => {
|
|
551
|
+
const drawn = $.const(draw);
|
|
552
|
+
return variant("list", fieldOf(row, spec.field).toArray(($2, item, name) => {
|
|
553
|
+
const given = $2.const(drawn(item, name, row));
|
|
554
|
+
return East.value({ name, value: given.value, note: given.note }, ItemType);
|
|
555
|
+
}));
|
|
556
|
+
},
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
spec,
|
|
561
|
+
value: (_$, row) => variant("list", fieldOf(row, spec.field).toArray((_$2, item, name) => East.value({ name, value: asFloat(item, itemType), note: none }, ItemType))),
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
case "tags":
|
|
565
|
+
return {
|
|
566
|
+
spec,
|
|
567
|
+
value: (_$, row) => {
|
|
568
|
+
const held = fieldOf(row, spec.field);
|
|
569
|
+
return variant("tags", type.type === "Set" ? held.toArray() : held);
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/** Arrays of strings joined, each once, in their order: what tags join by, and a struct's lists. */
|
|
575
|
+
const joinOnce = East.function([ArrayType(ArrayType(StringType))], ArrayType(StringType), ($, lists) => {
|
|
576
|
+
const joined = $.let([], ArrayType(StringType));
|
|
577
|
+
const seen = $.let(new Set(), SetType(StringType));
|
|
578
|
+
$.for(lists, ($2, list) => {
|
|
579
|
+
$2.for(list, ($3, item) => {
|
|
580
|
+
$3.if(seen.tryInsert(item), ($4) => { $4(joined.pushLast(item)); });
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
return joined;
|
|
584
|
+
});
|
|
585
|
+
/** How one field of a list's struct adds across entries: numbers and Options of them add, Sets and Arrays of Strings join; undefined for any other. */
|
|
586
|
+
function addOf(type) {
|
|
587
|
+
if (type.type === "Integer") {
|
|
588
|
+
return East.function([IntegerType, IntegerType], IntegerType, (_$, a, b) => a.add(b));
|
|
589
|
+
}
|
|
590
|
+
if (type.type === "Float")
|
|
591
|
+
return East.function([FloatType, FloatType], FloatType, (_$, a, b) => a.add(b));
|
|
592
|
+
const inner = optionOf(type);
|
|
593
|
+
if (inner !== undefined && isNumber(inner)) {
|
|
594
|
+
// An Option adds its values: none where both are none.
|
|
595
|
+
const add = addOf(inner);
|
|
596
|
+
return East.function([type, type], type, ($, a, b) => {
|
|
597
|
+
const plus = $.const(add);
|
|
598
|
+
return a.match({
|
|
599
|
+
none: (_$2) => b,
|
|
600
|
+
some: (_$2, x) => b.match({
|
|
601
|
+
none: (_$3) => a,
|
|
602
|
+
some: (_$3, y) => East.value(some(plus(x, y)), type),
|
|
603
|
+
}),
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
if (type.type === "Set" && isTypeEqual(type.key, StringType)) {
|
|
608
|
+
return East.function([SetType(StringType), SetType(StringType)], SetType(StringType), (_$, a, b) => a.union(b));
|
|
609
|
+
}
|
|
610
|
+
if (type.type === "Array" && isTypeEqual(type.value, StringType)) {
|
|
611
|
+
return East.function([ArrayType(StringType), ArrayType(StringType)], ArrayType(StringType), ($, a, b) => {
|
|
612
|
+
const join = $.const(joinOnce);
|
|
613
|
+
return join(East.value([a, b], ArrayType(ArrayType(StringType))));
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
return undefined;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Two of a list's structs added, field by field, by name: numbers and Options
|
|
620
|
+
* of them added, Sets and Arrays of Strings joined.
|
|
621
|
+
*
|
|
622
|
+
* @throws {Error} Naming the field that neither adds nor joins, and `weigh: false`
|
|
623
|
+
*/
|
|
624
|
+
function addedStructOf(where, field, itemType) {
|
|
625
|
+
const fields = Object.entries(itemType.fields).map(([name, type]) => {
|
|
626
|
+
const add = addOf(type);
|
|
627
|
+
if (add === undefined) {
|
|
628
|
+
throw new Error(`${where}: "${field}"'s items hold "${name}", ${typeText(type)}, which neither adds nor joins — numbers and Options of them ` +
|
|
629
|
+
"add, Sets and Arrays of Strings join: give the list weigh: false");
|
|
630
|
+
}
|
|
631
|
+
return { name, add };
|
|
632
|
+
});
|
|
633
|
+
return East.function([itemType, itemType], itemType, ($, a, b) => {
|
|
634
|
+
const added = fields.map(({ name, add }) => {
|
|
635
|
+
const plus = $.const(add);
|
|
636
|
+
return [name, plus(fieldOf(a, name), fieldOf(b, name))];
|
|
637
|
+
});
|
|
638
|
+
return East.value(Object.fromEntries(added), itemType);
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* A read weighed (R45): what it draws from a row's picked entries, the
|
|
643
|
+
* entries' own field added up by kind — a figure adds, an Option adding its
|
|
644
|
+
* values and `none` where every entry's is; a histogram adds bin by bin, an
|
|
645
|
+
* Array of counts position by position and a Dict of durations duration by
|
|
646
|
+
* duration; a list adds by name, a list of structs each struct's numbers by
|
|
647
|
+
* name before its draw; tags join, each once, in the entries' order.
|
|
648
|
+
*
|
|
649
|
+
* @param resolved - The read, resolved against the row
|
|
650
|
+
* @param held - The entries' field's type
|
|
651
|
+
* @param rowType - The details' row type
|
|
652
|
+
* @returns Its weighed value
|
|
653
|
+
* @throws {Error} When the entries hold the field in a type the read does not take, an Array of counts without bins,
|
|
654
|
+
* a list of structs in another type than the row's, or one of whose fields neither adds nor joins
|
|
655
|
+
*/
|
|
656
|
+
function weighed(resolved, held, rowType) {
|
|
657
|
+
const spec = resolved.spec;
|
|
658
|
+
const where = `Flowchart.${spec.kind}`;
|
|
659
|
+
const { fits, what } = FITS[spec.kind];
|
|
660
|
+
if (!fits(held))
|
|
661
|
+
throw new Error(`${where}: the entries hold "${spec.field}" as ${typeText(held)} — ${what}: give it weigh: false to draw the row's own`);
|
|
662
|
+
const at = (entry) => fieldOf(entry, spec.field);
|
|
663
|
+
switch (spec.kind) {
|
|
664
|
+
case "figure": {
|
|
665
|
+
const inner = optionOf(held);
|
|
666
|
+
if (inner === undefined) {
|
|
667
|
+
return (_$, entries) => variant("figure", some(entries.reduce((_$2, total, entry) => total.add(asFloat(at(entry), held)), East.value(0, FloatType))));
|
|
668
|
+
}
|
|
669
|
+
return ($, entries) => {
|
|
670
|
+
const values = $.const(entries.filterMap((_$2, entry) => at(entry).match({
|
|
671
|
+
none: (_$3) => East.value(none, OptionType(FloatType)),
|
|
672
|
+
some: (_$3, n) => East.value(some(asFloat(n, inner)), OptionType(FloatType)),
|
|
673
|
+
})));
|
|
674
|
+
return variant("figure", values.size().equal(0n).ifElse((_$2) => East.value(none, OptionType(FloatType)), (_$2) => East.value(some(values.reduce((_$3, total, n) => total.add(n), East.value(0, FloatType))), OptionType(FloatType))));
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
case "histogram": {
|
|
678
|
+
if (held.type === "Array") {
|
|
679
|
+
const bins = spec.bins;
|
|
680
|
+
if (bins === undefined)
|
|
681
|
+
throw new Error(`${where}: the entries hold "${spec.field}" as an Array of counts — give the bins it counts in, bins: [0, …]`);
|
|
682
|
+
const countType = held.value;
|
|
683
|
+
// Position by position, each at its bin's edge — or, where an entry holds another number of counts, the total at
|
|
684
|
+
// NaN, which no bin holds, so the row is listed in Issues as the row's own would be (R27).
|
|
685
|
+
return ($, entries) => {
|
|
686
|
+
const edges = $.const(bins);
|
|
687
|
+
const counts = $.const(entries.map((_$2, entry) => at(entry)));
|
|
688
|
+
const fitting = $.const(counts.every((_$2, held_) => held_.size().equal(edges.size())));
|
|
689
|
+
return variant("histogram", fitting.ifElse((_$2) => edges.map((_$3, edge, i) => East.value({
|
|
690
|
+
from: edge,
|
|
691
|
+
count: counts.reduce((_$4, total, held_) => total.add(asFloat(held_.get(i), countType)), East.value(0, FloatType)),
|
|
692
|
+
}, BinType)), (_$2) => East.value([East.value({
|
|
693
|
+
from: NaN,
|
|
694
|
+
count: counts.reduce((_$3, total, held_) => total.add(held_.reduce((_$4, sum, n) => sum.add(asFloat(n, countType)), East.value(0, FloatType))), East.value(0, FloatType)),
|
|
695
|
+
}, BinType)], ArrayType(BinType))));
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
const durations = held;
|
|
699
|
+
const keyType = durations.key;
|
|
700
|
+
const countType = durations.value;
|
|
701
|
+
return ($, entries) => {
|
|
702
|
+
const added = $.let(new Map(), DictType(keyType, FloatType));
|
|
703
|
+
$.for(entries, ($2, entry) => {
|
|
704
|
+
const counts = at(entry)
|
|
705
|
+
.map((_$3, count) => asFloat(count, countType));
|
|
706
|
+
$2(added.unionInPlace(counts, (_$3, a, b) => a.add(b)));
|
|
707
|
+
});
|
|
708
|
+
return variant("histogram", added.toArray((_$2, count, from) => East.value({ from: asFloat(from, keyType), count }, BinType)));
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
case "list": {
|
|
712
|
+
const itemType = held.value;
|
|
713
|
+
if (itemType.type === "Struct") {
|
|
714
|
+
const rowItem = resolved.drawn;
|
|
715
|
+
if (rowItem === undefined)
|
|
716
|
+
throw new Error(`${where}: the entries hold "${spec.field}" as structs — give the list a draw`);
|
|
717
|
+
// Its draw reads the row's items: the entries' are added up, then drawn by it.
|
|
718
|
+
const own = rowType.fields[spec.field].value;
|
|
719
|
+
if (!isTypeEqual(own, itemType)) {
|
|
720
|
+
throw new Error(`${where}: the entries hold "${spec.field}"'s items as ${typeText(itemType)}, and the row as ${typeText(own)} — ` +
|
|
721
|
+
"its draw reads the row's: give the entries the row's items, or the list weigh: false");
|
|
722
|
+
}
|
|
723
|
+
const add = addedStructOf(where, spec.field, itemType);
|
|
724
|
+
return ($, entries, row) => {
|
|
725
|
+
const plus = $.const(add);
|
|
726
|
+
const drawn = $.const(rowItem);
|
|
727
|
+
const added = $.let(new Map(), DictType(StringType, itemType));
|
|
728
|
+
$.for(entries, ($2, entry) => {
|
|
729
|
+
$2(added.unionInPlace(at(entry), (_$3, a, b) => plus(a, b)));
|
|
730
|
+
});
|
|
731
|
+
return variant("list", added.toArray(($2, item, name) => {
|
|
732
|
+
const given = $2.const(drawn(item, name, row));
|
|
733
|
+
return East.value({ name, value: given.value, note: given.note }, ItemType);
|
|
734
|
+
}));
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
return ($, entries) => {
|
|
738
|
+
const added = $.let(new Map(), DictType(StringType, FloatType));
|
|
739
|
+
$.for(entries, ($2, entry) => {
|
|
740
|
+
const values = at(entry)
|
|
741
|
+
.map((_$3, value) => asFloat(value, itemType));
|
|
742
|
+
$2(added.unionInPlace(values, (_$3, a, b) => a.add(b)));
|
|
743
|
+
});
|
|
744
|
+
return variant("list", added.toArray((_$2, value, name) => East.value({ name, value, note: none }, ItemType)));
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
case "tags": {
|
|
748
|
+
if (held.type === "Set") {
|
|
749
|
+
// A Set's tags join as a Set does, in its order: every key picked draws the row's own, whatever the entries' order.
|
|
750
|
+
return (_$, entries) => variant("tags", entries.reduce((_$2, joined, entry) => joined.union(at(entry)), East.value(new Set(), SetType(StringType))).toArray());
|
|
751
|
+
}
|
|
752
|
+
// An Array's each once, in the entries' key order.
|
|
753
|
+
return ($, entries) => {
|
|
754
|
+
const join = $.const(joinOnce);
|
|
755
|
+
return variant("tags", join(entries.map((_$2, entry) => at(entry))));
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/** An ordinal, as a refusal names an element. */
|
|
761
|
+
function ordinal(i) {
|
|
762
|
+
const n = i + 1;
|
|
763
|
+
const tens = n % 100;
|
|
764
|
+
const suffix = tens >= 11 && tens <= 13 ? "th" : (["th", "st", "nd", "rd"][n % 10] ?? "th");
|
|
765
|
+
return `${n}${suffix}`;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* A read on the wire: how it draws, the words it draws in.
|
|
769
|
+
*
|
|
770
|
+
* @param spec - The read
|
|
771
|
+
* @param top - A list's lines unless given: 2 on a card, 1 on a label
|
|
772
|
+
*/
|
|
773
|
+
function wireOf(spec, top) {
|
|
774
|
+
const label = spec.label ?? spec.field;
|
|
775
|
+
const option = (word) => word === undefined ? none : some(word);
|
|
776
|
+
switch (spec.kind) {
|
|
777
|
+
case "figure":
|
|
778
|
+
return variant("figure", { label, unit: option(spec.unit) });
|
|
779
|
+
case "histogram":
|
|
780
|
+
return variant("histogram", {
|
|
781
|
+
label,
|
|
782
|
+
unit: variant(spec.unit, null),
|
|
783
|
+
counts: option(spec.counts),
|
|
784
|
+
bins: spec.bins === undefined ? none : some(spec.bins),
|
|
785
|
+
scale: variant(spec.scale, null),
|
|
786
|
+
ticks: spec.ticks === undefined ? none
|
|
787
|
+
: some(spec.ticks.map((t) => typeof t === "number" ? { at: t, label: none } : { at: t.at, label: some(t.label) })),
|
|
788
|
+
});
|
|
789
|
+
case "list":
|
|
790
|
+
return variant("list", { label, unit: option(spec.unit), top: BigInt(spec.top ?? top) });
|
|
791
|
+
case "tags":
|
|
792
|
+
return variant("tags", { label, colors: spec.colors, legend: spec.legend });
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* One kind's details over the app's rows (R18, R19, R21), keyed as the kind's
|
|
797
|
+
* are, read only: `Data.bind(record)`, read whole beside a kind read whole,
|
|
798
|
+
* or `Data.bindPaged(record)`, read by the drawn rows' keys beside one read
|
|
799
|
+
* through its source.
|
|
800
|
+
*
|
|
801
|
+
* Bound through an entries index — `Data.bindPaged(record, { indexes: [it] })`
|
|
802
|
+
* — and naming the rail's source over that binding with `source`, the details
|
|
803
|
+
* are weighed by the source's picks of the Dict field the index files (#1522,
|
|
804
|
+
* R44, R45): read by key whatever the kind's read, each row with that field
|
|
805
|
+
* emptied, and each read whose field the entries hold drawing the picked keys'
|
|
806
|
+
* entries added up by kind — a figure adds, a histogram adds bin by bin, a
|
|
807
|
+
* list adds by name, tags join. A read given `weigh: false`, or one whose field
|
|
808
|
+
* the entries lack, draws the row's own; with no pick, every read does. A share
|
|
809
|
+
* or an average is given `weigh: false`, since adding two would make it wrong:
|
|
810
|
+
* `Flowchart.figure("onTime", { label: "On time", weigh: false })` draws each
|
|
811
|
+
* state's own share of its parcels through on time, whatever the picks.
|
|
812
|
+
*
|
|
813
|
+
* @typeParam R - The details' row type, inferred from the binding
|
|
814
|
+
* @typeParam S - The reads, as given
|
|
815
|
+
* @typeParam P - Through an entries index, an index entry's value, inferred from the binding
|
|
816
|
+
* @typeParam M - Through an entries index, the binding's indexes, inferred from it
|
|
817
|
+
* @typeParam IK - Through an entries index, the binding's index keys, inferred from it
|
|
818
|
+
* @typeParam N - Through an entries index, the binding's nested part, inferred from it
|
|
819
|
+
* @typeParam X - Through an entries index, the binding's maintenance part, inferred from it
|
|
820
|
+
* @param binding - The details' record: `Data.bind(record)`, `Data.bindPaged(record)`, or `Data.bindPaged(record, { indexes: [its entries index] })`
|
|
821
|
+
* @param spec - `{ source?, details: [ … ] }`: the rail's source whose picks weigh them, and every read, in the order drawn
|
|
822
|
+
* @returns The details, for a state's or a transition's `details`
|
|
823
|
+
* @throws {Error} Naming the binding or the read (R18–R20): `Record.bind` — "Details are read only: bind them with
|
|
824
|
+
* Data.bind(record), or Data.bindPaged(record) beside a kind read through its source" — or anything but a binding of
|
|
825
|
+
* a record of structs keyed by String; an element that is no read; a read's field the row lacks, or one of another
|
|
826
|
+
* type; an Array of counts without bins; a Dict of structs without a draw. Weighed (#1522): `source` naming the
|
|
827
|
+
* states' or the transitions' own; a source named while bound without an entries index, or an entries index bound
|
|
828
|
+
* with no source named; `weigh` beside details that name no source; a read of the entries' own field; a read whose
|
|
829
|
+
* field the entries hold in a type it doesn't take; a list of structs one of whose fields neither adds nor joins,
|
|
830
|
+
* unless it says `weigh: false`. The Flowchart refuses a source its slice lacks, and one over rows of another type
|
|
831
|
+
* (`Flowchart.payload`)
|
|
832
|
+
*
|
|
833
|
+
* @example
|
|
834
|
+
* ```tsx
|
|
835
|
+
* // .tsx file with the `@jsxImportSource @elaraai/e3-ui` pragma
|
|
836
|
+
* import { ArrayType, DictType, East, IntegerType, StringType, StructType, variant } from "@elaraai/east";
|
|
837
|
+
* import { Box, Reactive, UIComponentType } from "@elaraai/east-ui";
|
|
838
|
+
* import { Data, Flowchart } from "@elaraai/e3-ui";
|
|
839
|
+
* import e3 from "@elaraai/e3";
|
|
840
|
+
*
|
|
841
|
+
* // A support desk's tickets: the states they pass through, the transitions between them, and the minutes
|
|
842
|
+
* // each spent in each state. The app's task counts each state's tickets into the app's own bins — minutes at
|
|
843
|
+
* // first, then hours, a shift and a day — an Array of counts, one a bin, keyed as the states are.
|
|
844
|
+
* export const DeskState = StructType({ name: StringType, stage: StringType });
|
|
845
|
+
* export const DeskTransition = StructType({ from: StringType, to: StringType });
|
|
846
|
+
* export const DeskStage = StructType({ label: StringType, seq: IntegerType });
|
|
847
|
+
* export const deskStates = e3.input("flowchart_desk_states", DictType(StringType, DeskState), variant("value", new Map([
|
|
848
|
+
* ["NEW", { name: "New", stage: "open" }], ["TRI", { name: "Triaged", stage: "open" }],
|
|
849
|
+
* ["WIP", { name: "In progress", stage: "work" }], ["WAI", { name: "Waiting on customer", stage: "work" }],
|
|
850
|
+
* ["RES", { name: "Resolved", stage: "close" }],
|
|
851
|
+
* ])));
|
|
852
|
+
* export const deskTransitions = e3.input("flowchart_desk_transitions", DictType(StringType, DeskTransition), variant("value", new Map([
|
|
853
|
+
* ["t1", { from: "NEW", to: "TRI" }], ["t2", { from: "TRI", to: "WIP" }], ["t3", { from: "WIP", to: "WAI" }],
|
|
854
|
+
* ["t4", { from: "WAI", to: "WIP" }], ["t5", { from: "WIP", to: "RES" }],
|
|
855
|
+
* ])));
|
|
856
|
+
* export const deskStages = e3.input("flowchart_desk_stages", DictType(StringType, DeskStage), variant("value", new Map([
|
|
857
|
+
* ["open", { label: "Open", seq: 1n }], ["work", { label: "Work", seq: 2n }], ["close", { label: "Close", seq: 3n }],
|
|
858
|
+
* ])));
|
|
859
|
+
* export const TicketMinutes = StructType({ minutes: ArrayType(IntegerType) });
|
|
860
|
+
* export const ticketMinutes = e3.input("flowchart_ticket_minutes", DictType(StringType, TicketMinutes), variant("value", new Map([
|
|
861
|
+
* ["NEW", { minutes: [140n, 96n, 120n, 64n, 30n, 22n, 9n, 4n, 2n, 1n, 0n, 0n] }],
|
|
862
|
+
* ["TRI", { minutes: [62n, 88n, 131n, 90n, 41n, 25n, 12n, 6n, 3n, 1n, 0n, 0n] }],
|
|
863
|
+
* ["WIP", { minutes: [3n, 8n, 41n, 96n, 120n, 150n, 132n, 88n, 40n, 18n, 9n, 4n] }],
|
|
864
|
+
* ["WAI", { minutes: [0n, 1n, 2n, 6n, 9n, 14n, 30n, 58n, 74n, 66n, 41n, 22n] }],
|
|
865
|
+
* // A resolved ticket stays resolved: the app counts no time in it, so its row is absent and its card says no data.
|
|
866
|
+
* ])));
|
|
867
|
+
*
|
|
868
|
+
* const flowchartTicketMinutes = East.function([], UIComponentType, (_$) => (
|
|
869
|
+
* <Reactive>{$ => {
|
|
870
|
+
* const states = $.let(Data.bind(deskStates));
|
|
871
|
+
* const transitions = $.let(Data.bind(deskTransitions));
|
|
872
|
+
* const stages = $.let(Data.bind(deskStages));
|
|
873
|
+
* const minutes = $.let(Data.bind(ticketMinutes));
|
|
874
|
+
* return (
|
|
875
|
+
* <Box height="560px">
|
|
876
|
+
* <Flowchart
|
|
877
|
+
* states={Flowchart.states(states, {
|
|
878
|
+
* label: "name", lane: "stage",
|
|
879
|
+
* details: Flowchart.details(minutes, {
|
|
880
|
+
* details: [Flowchart.histogram("minutes", {
|
|
881
|
+
* unit: "minutes",
|
|
882
|
+
* label: "Time in state",
|
|
883
|
+
* counts: "tickets",
|
|
884
|
+
* // The edges, in minutes: an Array of counts is a bin each, in this order.
|
|
885
|
+
* bins: [0, 1, 2, 5, 10, 15, 30, 60, 120, 240, 480, 1440],
|
|
886
|
+
* // A bar a bin, whatever its span — the default; "linear" draws them to scale.
|
|
887
|
+
* scale: "band",
|
|
888
|
+
* // The edges the axis names besides 0: 5m and 1h printed from the unit,
|
|
889
|
+
* // the last two in the app's own words.
|
|
890
|
+
* ticks: [5, 60, { at: 480, label: "a shift" }, { at: 1440, label: "a day" }],
|
|
891
|
+
* })],
|
|
892
|
+
* }),
|
|
893
|
+
* })}
|
|
894
|
+
* transitions={Flowchart.transitions(transitions, { from: "from", to: "to" })}
|
|
895
|
+
* lanes={Flowchart.lanes(stages, { label: "label", order: "seq" })}
|
|
896
|
+
* name="ticketMinutes"
|
|
897
|
+
* />
|
|
898
|
+
* </Box>
|
|
899
|
+
* );
|
|
900
|
+
* }}</Reactive>
|
|
901
|
+
* ));
|
|
902
|
+
* ```
|
|
903
|
+
*/
|
|
904
|
+
export function flowchartDetails(binding, spec) {
|
|
905
|
+
const where = "Flowchart.details";
|
|
906
|
+
const bound = bindingOf(binding, where);
|
|
907
|
+
const rowType = bound.rowType;
|
|
908
|
+
const given = typeof spec === "object" && spec !== null ? spec.details : undefined;
|
|
909
|
+
if (!Array.isArray(given))
|
|
910
|
+
throw new Error(`${where}: give the reads as { details: [ … ] } — Flowchart.figure, .histogram, .list or .tags, in the order drawn`);
|
|
911
|
+
// The source whose picks weigh them (#1522, R44): named beside an entries index, and only there.
|
|
912
|
+
const named = spec.source;
|
|
913
|
+
if (named !== undefined && (typeof named !== "string" || named.length === 0)) {
|
|
914
|
+
throw new Error(`${where}: \`source\` names the rail's source whose picks weigh the details — a source of the Flowchart's slice`);
|
|
915
|
+
}
|
|
916
|
+
const source = named;
|
|
917
|
+
const entries = bound.entries;
|
|
918
|
+
if (source !== undefined && entries === undefined) {
|
|
919
|
+
throw new Error(`${where}: the details name the source "${source}", whose picks weigh them through their record's entries index — bind them ` +
|
|
920
|
+
"with Data.bindPaged(record, { indexes: [its entries index] })");
|
|
921
|
+
}
|
|
922
|
+
if (source === undefined && entries !== undefined) {
|
|
923
|
+
throw new Error(`${where}: the details are bound through the entries index '${entries.index}', whose picks weigh them — name the rail's ` +
|
|
924
|
+
`source that offers '${entries.field}', source: "…"`);
|
|
925
|
+
}
|
|
926
|
+
if (entries !== undefined && entries.valueType.type !== "Struct") {
|
|
927
|
+
throw new Error(`${where}: the entries index '${entries.index}' files "${entries.field}", whose entries are ${typeText(entries.valueType)} — ` +
|
|
928
|
+
"the picks weigh the fields of an entry's struct, each named as the row's is");
|
|
929
|
+
}
|
|
930
|
+
const held = entries === undefined ? {} : entries.valueType.fields;
|
|
931
|
+
const reads = given.map((read, i) => {
|
|
932
|
+
const found = typeof read === "object" && read !== null ? READS.get(read) : undefined;
|
|
933
|
+
if (found === undefined) {
|
|
934
|
+
throw new Error(`${where}: \`details\` holds reads — Flowchart.figure, .histogram, .list or .tags — and the ${ordinal(i)} is none`);
|
|
935
|
+
}
|
|
936
|
+
const at = `Flowchart.${found.kind}`;
|
|
937
|
+
if (entries === undefined && found.weigh !== undefined) {
|
|
938
|
+
throw new Error(`${at}: "${found.field}" says weigh, and the details name no source whose picks weigh them — give the details ` +
|
|
939
|
+
"source: \"…\", or leave weigh out");
|
|
940
|
+
}
|
|
941
|
+
if (entries !== undefined && found.field === entries.field) {
|
|
942
|
+
throw new Error(`${at}: "${found.field}" is what the entries index '${entries.index}' files, entry by entry, and each row is read with it ` +
|
|
943
|
+
"emptied — read the fields its entries hold");
|
|
944
|
+
}
|
|
945
|
+
if (found.weigh === true && held[found.field] === undefined) {
|
|
946
|
+
throw new Error(`${at}: "${found.field}" says weigh: true, and the entries of "${entries.field}" hold no "${found.field}" ` +
|
|
947
|
+
`(${Object.keys(held).join(", ")}) — leave weigh out to draw the row's own`);
|
|
948
|
+
}
|
|
949
|
+
return resolve(found, rowType);
|
|
950
|
+
});
|
|
951
|
+
// Each read the picks weigh (R45): one the entries hold, unless it says weigh: false. Any other draws the row's own.
|
|
952
|
+
const weighers = reads.map((read) => {
|
|
953
|
+
const type = held[read.spec.field];
|
|
954
|
+
return type === undefined || read.spec.weigh === false ? undefined : weighed(read, type, rowType);
|
|
955
|
+
});
|
|
956
|
+
// A row's values, from its bytes: one a read, in the reads' order.
|
|
957
|
+
const values = East.function([BlobType], FlowchartDetailRowType, ($, bytes) => {
|
|
958
|
+
const row = $.const(bytes.decodeBeast(rowType, "v2"));
|
|
959
|
+
return East.value(reads.map((read) => read.value($, row)), FlowchartDetailRowType);
|
|
960
|
+
});
|
|
961
|
+
// A row's values weighed by the picks (R45), from its bytes and its picked entries' — each an entry's value, in the
|
|
962
|
+
// entries' key order: a read the picks weigh adds its entries up by kind, any other draws the row's own.
|
|
963
|
+
const entryType = entries?.valueType;
|
|
964
|
+
const fold = entryType === undefined ? undefined : East.function([BlobType, ArrayType(BlobType)], FlowchartDetailRowType, ($, bytes, picked) => {
|
|
965
|
+
const row = $.const(bytes.decodeBeast(rowType, "v2"));
|
|
966
|
+
const taken = $.const(picked.map((_$2, entry) => entry.decodeBeast(entryType, "v2")));
|
|
967
|
+
return East.value(reads.map((read, i) => {
|
|
968
|
+
const weigh = weighers[i];
|
|
969
|
+
return weigh === undefined ? read.value($, row) : weigh($, taken, row);
|
|
970
|
+
}), FlowchartDetailRowType);
|
|
971
|
+
});
|
|
972
|
+
// The details read whole, beside a kind read whole: each row's bytes, by key — built only then, since a paged
|
|
973
|
+
// read has no whole to read.
|
|
974
|
+
const Rows = DictType(StringType, BlobType);
|
|
975
|
+
const whole = () => {
|
|
976
|
+
const read = bound.handle.read;
|
|
977
|
+
return East.function([], OptionType(Rows), (_$) => East.value(some(read().map((_$2, row) => East.Blob.encodeBeast(row, "v2"))), OptionType(Rows)));
|
|
978
|
+
};
|
|
979
|
+
return {
|
|
980
|
+
[FLOWCHART_DETAILS]: "details",
|
|
981
|
+
rowType,
|
|
982
|
+
source,
|
|
983
|
+
inspectorReads: reads.some((r) => r.spec.show === "inspector"),
|
|
984
|
+
check(noun, at) {
|
|
985
|
+
if (noun !== "transitions")
|
|
986
|
+
return;
|
|
987
|
+
// A transition's label draws lines under its evidence: figures and lists, three at most (R21, R32).
|
|
988
|
+
let lines = 0;
|
|
989
|
+
for (const { spec: r } of reads) {
|
|
990
|
+
if (r.show === "inspector")
|
|
991
|
+
continue;
|
|
992
|
+
if (r.kind === "histogram" || r.kind === "tags") {
|
|
993
|
+
throw new Error(`${at}: a transition's label draws lines: a figure or a list — give the ${r.kind === "tags" ? "tags" : "histogram"} show: "inspector"`);
|
|
994
|
+
}
|
|
995
|
+
lines += r.kind === "list" ? (r.top ?? 1) : 1;
|
|
996
|
+
}
|
|
997
|
+
if (lines > 3) {
|
|
998
|
+
throw new Error(`${at}: a transition's label draws three lines at most under its evidence, and these draw ${lines} — ` +
|
|
999
|
+
"give one show: \"inspector\", or lower its top");
|
|
1000
|
+
}
|
|
1001
|
+
},
|
|
1002
|
+
build(noun, at, throughSource) {
|
|
1003
|
+
const top = noun === "states" ? 2 : 1;
|
|
1004
|
+
const wire = reads.map((r) => ({ show: variant(r.spec.show, null), read: wireOf(r.spec, top) }));
|
|
1005
|
+
if (entries !== undefined && fold !== undefined && source !== undefined) {
|
|
1006
|
+
// Weighed by the picks, the details are read by key whatever their kind's read (R44): the drawn rows' keys
|
|
1007
|
+
// through the entries index's lean rows, and the picked keys' ranges through the index.
|
|
1008
|
+
return East.value({
|
|
1009
|
+
reads: wire,
|
|
1010
|
+
rows: none,
|
|
1011
|
+
paged: some(fieldOf(bound.handle, "id")),
|
|
1012
|
+
values,
|
|
1013
|
+
weighed: some({
|
|
1014
|
+
source, field: entries.field, index: entries.index, reads: weighers.map((w) => w !== undefined), fold,
|
|
1015
|
+
}),
|
|
1016
|
+
}, FlowchartDetailsType);
|
|
1017
|
+
}
|
|
1018
|
+
if (throughSource && bound.form === "data") {
|
|
1019
|
+
throw new Error(`${at}: the ${noun} are read through their source, so their details are read by key: bind them with Data.bindPaged(record)`);
|
|
1020
|
+
}
|
|
1021
|
+
if (!throughSource && bound.form === "paged") {
|
|
1022
|
+
throw new Error(`${at}: the ${noun} are read whole, so their details are read whole: bind them with Data.bind(record)`);
|
|
1023
|
+
}
|
|
1024
|
+
return East.value({
|
|
1025
|
+
reads: wire,
|
|
1026
|
+
rows: throughSource ? none : some(whole()),
|
|
1027
|
+
// The paged read the details are read by key through: a reader asks the drawn rows' keys of it, in one request.
|
|
1028
|
+
paged: throughSource ? some(fieldOf(bound.handle, "id")) : none,
|
|
1029
|
+
values,
|
|
1030
|
+
weighed: none,
|
|
1031
|
+
}, FlowchartDetailsType);
|
|
1032
|
+
},
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
//# sourceMappingURL=details.js.map
|