@elaraai/e3-ui 1.0.86 → 1.0.88
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/bind/data-indexed.d.ts +159 -0
- package/dist/src/bind/data-indexed.d.ts.map +1 -0
- package/dist/src/bind/data-indexed.js +105 -0
- package/dist/src/bind/data-indexed.js.map +1 -0
- package/dist/src/bind/data.d.ts +5 -3
- package/dist/src/bind/data.d.ts.map +1 -1
- package/dist/src/bind/data.js +6 -0
- package/dist/src/bind/data.js.map +1 -1
- package/dist/src/bind/record.d.ts +34 -23
- package/dist/src/bind/record.d.ts.map +1 -1
- package/dist/src/bind/record.js +46 -19
- package/dist/src/bind/record.js.map +1 -1
- package/dist/src/calendar/index.d.ts +2923 -0
- package/dist/src/calendar/index.d.ts.map +1 -0
- package/dist/src/calendar/index.js +135 -0
- package/dist/src/calendar/index.js.map +1 -0
- package/dist/src/decision/queue.d.ts +3 -3
- package/dist/src/decision/queue.d.ts.map +1 -1
- package/dist/src/decision/queue.js +2 -2
- package/dist/src/decision/queue.js.map +1 -1
- package/dist/src/decision/types.d.ts +4 -0
- package/dist/src/decision/types.d.ts.map +1 -1
- package/dist/src/decision/types.js +4 -0
- package/dist/src/decision/types.js.map +1 -1
- package/dist/src/flowchart/canvas.d.ts +114 -0
- package/dist/src/flowchart/canvas.d.ts.map +1 -0
- package/dist/src/flowchart/canvas.js +84 -0
- package/dist/src/flowchart/canvas.js.map +1 -0
- package/dist/src/flowchart/flowchart.d.ts +79 -0
- package/dist/src/flowchart/flowchart.d.ts.map +1 -0
- package/dist/src/flowchart/flowchart.js +9 -0
- package/dist/src/flowchart/flowchart.js.map +1 -0
- package/dist/src/flowchart/index.d.ts +305 -0
- package/dist/src/flowchart/index.d.ts.map +1 -0
- package/dist/src/flowchart/index.js +267 -0
- package/dist/src/flowchart/index.js.map +1 -0
- package/dist/src/flowchart/library.d.ts +720 -0
- package/dist/src/flowchart/library.d.ts.map +1 -0
- package/dist/src/flowchart/library.js +611 -0
- package/dist/src/flowchart/library.js.map +1 -0
- package/dist/src/flowchart/over.d.ts +234 -0
- package/dist/src/flowchart/over.d.ts.map +1 -0
- package/dist/src/flowchart/over.js +220 -0
- package/dist/src/flowchart/over.js.map +1 -0
- package/dist/src/flowchart/patch.d.ts +64 -0
- package/dist/src/flowchart/patch.d.ts.map +1 -0
- package/dist/src/flowchart/patch.js +67 -0
- package/dist/src/flowchart/patch.js.map +1 -0
- package/dist/src/flowchart/payload.d.ts +8220 -0
- package/dist/src/flowchart/payload.d.ts.map +1 -0
- package/dist/src/flowchart/payload.js +521 -0
- package/dist/src/flowchart/payload.js.map +1 -0
- package/dist/src/flowchart/types.d.ts +393 -0
- package/dist/src/flowchart/types.d.ts.map +1 -0
- package/dist/src/flowchart/types.js +216 -0
- package/dist/src/flowchart/types.js.map +1 -0
- package/dist/src/flowchart/values.d.ts +267 -0
- package/dist/src/flowchart/values.d.ts.map +1 -0
- package/dist/src/flowchart/values.js +216 -0
- package/dist/src/flowchart/values.js.map +1 -0
- package/dist/src/flowchart/window.d.ts +521 -0
- package/dist/src/flowchart/window.d.ts.map +1 -0
- package/dist/src/flowchart/window.js +69 -0
- package/dist/src/flowchart/window.js.map +1 -0
- package/dist/src/index.d.ts +25 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +48 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/internal.d.ts +35 -3
- package/dist/src/internal.d.ts.map +1 -1
- package/dist/src/internal.js +72 -3
- package/dist/src/internal.js.map +1 -1
- package/dist/src/plan/assemble.d.ts +2224 -0
- package/dist/src/plan/assemble.d.ts.map +1 -0
- package/dist/src/plan/assemble.js +210 -0
- package/dist/src/plan/assemble.js.map +1 -0
- package/dist/src/plan/builders.d.ts +790 -0
- package/dist/src/plan/builders.d.ts.map +1 -0
- package/dist/src/plan/builders.js +749 -0
- package/dist/src/plan/builders.js.map +1 -0
- package/dist/src/plan/event-rows.d.ts +2565 -0
- package/dist/src/plan/event-rows.d.ts.map +1 -0
- package/dist/src/plan/event-rows.js +689 -0
- package/dist/src/plan/event-rows.js.map +1 -0
- package/dist/src/plan/factories.d.ts +458 -0
- package/dist/src/plan/factories.d.ts.map +1 -0
- package/dist/src/plan/factories.js +484 -0
- package/dist/src/plan/factories.js.map +1 -0
- package/dist/src/plan/index.d.ts +672 -0
- package/dist/src/plan/index.d.ts.map +1 -0
- package/dist/src/plan/index.js +532 -0
- package/dist/src/plan/index.js.map +1 -0
- package/dist/src/plan/ir.d.ts +2594 -0
- package/dist/src/plan/ir.d.ts.map +1 -0
- package/dist/src/plan/ir.js +78 -0
- package/dist/src/plan/ir.js.map +1 -0
- package/dist/src/plan/library.d.ts +772 -0
- package/dist/src/plan/library.d.ts.map +1 -0
- package/dist/src/plan/library.js +424 -0
- package/dist/src/plan/library.js.map +1 -0
- package/dist/src/plan/ordered-source.d.ts +27 -0
- package/dist/src/plan/ordered-source.d.ts.map +1 -0
- package/dist/src/plan/ordered-source.js +48 -0
- package/dist/src/plan/ordered-source.js.map +1 -0
- package/dist/src/plan/over.d.ts +247 -0
- package/dist/src/plan/over.d.ts.map +1 -0
- package/dist/src/plan/over.js +266 -0
- package/dist/src/plan/over.js.map +1 -0
- package/dist/src/plan/pick.d.ts +78 -0
- package/dist/src/plan/pick.d.ts.map +1 -0
- package/dist/src/plan/pick.js +146 -0
- package/dist/src/plan/pick.js.map +1 -0
- package/dist/src/plan/plan.d.ts +11611 -0
- package/dist/src/plan/plan.d.ts.map +1 -0
- package/dist/src/plan/plan.js +428 -0
- package/dist/src/plan/plan.js.map +1 -0
- package/dist/src/plan/refs.d.ts +218 -0
- package/dist/src/plan/refs.d.ts.map +1 -0
- package/dist/src/plan/refs.js +226 -0
- package/dist/src/plan/refs.js.map +1 -0
- package/dist/src/plan/root.d.ts +290 -0
- package/dist/src/plan/root.d.ts.map +1 -0
- package/dist/src/plan/root.js +463 -0
- package/dist/src/plan/root.js.map +1 -0
- package/dist/src/plan/series.d.ts +13554 -0
- package/dist/src/plan/series.d.ts.map +1 -0
- package/dist/src/plan/series.js +1921 -0
- package/dist/src/plan/series.js.map +1 -0
- package/dist/src/plan/types.d.ts +12400 -0
- package/dist/src/plan/types.d.ts.map +1 -0
- package/dist/src/plan/types.js +1335 -0
- package/dist/src/plan/types.js.map +1 -0
- package/dist/src/roster/coverage.d.ts +240 -0
- package/dist/src/roster/coverage.d.ts.map +1 -0
- package/dist/src/roster/coverage.js +109 -0
- package/dist/src/roster/coverage.js.map +1 -0
- package/dist/src/roster/index.d.ts +4028 -0
- package/dist/src/roster/index.d.ts.map +1 -0
- package/dist/src/roster/index.js +115 -0
- package/dist/src/roster/index.js.map +1 -0
- package/dist/src/roster/library.d.ts +106 -0
- package/dist/src/roster/library.d.ts.map +1 -0
- package/dist/src/roster/library.js +78 -0
- package/dist/src/roster/library.js.map +1 -0
- package/dist/src/roster/people.d.ts +50 -0
- package/dist/src/roster/people.d.ts.map +1 -0
- package/dist/src/roster/people.js +32 -0
- package/dist/src/roster/people.js.map +1 -0
- package/dist/src/roster/rules.d.ts +275 -0
- package/dist/src/roster/rules.d.ts.map +1 -0
- package/dist/src/roster/rules.js +162 -0
- package/dist/src/roster/rules.js.map +1 -0
- package/dist/src/roster/types.d.ts +672 -0
- package/dist/src/roster/types.d.ts.map +1 -0
- package/dist/src/roster/types.js +248 -0
- package/dist/src/roster/types.js.map +1 -0
- package/dist/src/roster/visibility.d.ts +66 -0
- package/dist/src/roster/visibility.d.ts.map +1 -0
- package/dist/src/roster/visibility.js +25 -0
- package/dist/src/roster/visibility.js.map +1 -0
- package/dist/src/schedule/days.d.ts +54 -0
- package/dist/src/schedule/days.d.ts.map +1 -0
- package/dist/src/schedule/days.js +68 -0
- package/dist/src/schedule/days.js.map +1 -0
- package/dist/src/schedule/events.d.ts +433 -0
- package/dist/src/schedule/events.d.ts.map +1 -0
- package/dist/src/schedule/events.js +851 -0
- package/dist/src/schedule/events.js.map +1 -0
- package/dist/src/schedule/index.d.ts +150 -0
- package/dist/src/schedule/index.d.ts.map +1 -0
- package/dist/src/schedule/index.js +132 -0
- package/dist/src/schedule/index.js.map +1 -0
- package/dist/src/schedule/library.d.ts +70 -0
- package/dist/src/schedule/library.d.ts.map +1 -0
- package/dist/src/schedule/library.js +102 -0
- package/dist/src/schedule/library.js.map +1 -0
- package/dist/src/schedule/patch.d.ts +74 -0
- package/dist/src/schedule/patch.d.ts.map +1 -0
- package/dist/src/schedule/patch.js +68 -0
- package/dist/src/schedule/patch.js.map +1 -0
- package/dist/src/schedule/record-window.d.ts +189 -0
- package/dist/src/schedule/record-window.d.ts.map +1 -0
- package/dist/src/schedule/record-window.js +95 -0
- package/dist/src/schedule/record-window.js.map +1 -0
- package/dist/src/schedule/resources.d.ts +165 -0
- package/dist/src/schedule/resources.d.ts.map +1 -0
- package/dist/src/schedule/resources.js +234 -0
- package/dist/src/schedule/resources.js.map +1 -0
- package/dist/src/schedule/templates.d.ts +47 -0
- package/dist/src/schedule/templates.d.ts.map +1 -0
- package/dist/src/schedule/templates.js +61 -0
- package/dist/src/schedule/templates.js.map +1 -0
- package/dist/src/schedule/types.d.ts +1677 -0
- package/dist/src/schedule/types.d.ts.map +1 -0
- package/dist/src/schedule/types.js +374 -0
- package/dist/src/schedule/types.js.map +1 -0
- package/dist/src/schedule/window.d.ts +154 -0
- package/dist/src/schedule/window.d.ts.map +1 -0
- package/dist/src/schedule/window.js +363 -0
- package/dist/src/schedule/window.js.map +1 -0
- package/dist/src/sheet/bridge.d.ts +385 -0
- package/dist/src/sheet/bridge.d.ts.map +1 -0
- package/dist/src/sheet/bridge.js +809 -0
- package/dist/src/sheet/bridge.js.map +1 -0
- package/dist/src/sheet/columns.d.ts +410 -0
- package/dist/src/sheet/columns.d.ts.map +1 -0
- package/dist/src/sheet/columns.js +99 -0
- package/dist/src/sheet/columns.js.map +1 -0
- package/dist/src/sheet/context-bridge.d.ts +48 -0
- package/dist/src/sheet/context-bridge.d.ts.map +1 -0
- package/dist/src/sheet/context-bridge.js +216 -0
- package/dist/src/sheet/context-bridge.js.map +1 -0
- package/dist/src/sheet/draft-bridge.d.ts +37 -0
- package/dist/src/sheet/draft-bridge.d.ts.map +1 -0
- package/dist/src/sheet/draft-bridge.js +153 -0
- package/dist/src/sheet/draft-bridge.js.map +1 -0
- package/dist/src/sheet/drafts.d.ts +14 -0
- package/dist/src/sheet/drafts.d.ts.map +1 -0
- package/dist/src/sheet/drafts.js +14 -0
- package/dist/src/sheet/drafts.js.map +1 -0
- package/dist/src/sheet/editing-bridge.d.ts +40 -0
- package/dist/src/sheet/editing-bridge.d.ts.map +1 -0
- package/dist/src/sheet/editing-bridge.js +231 -0
- package/dist/src/sheet/editing-bridge.js.map +1 -0
- package/dist/src/sheet/editing-types.d.ts +268 -0
- package/dist/src/sheet/editing-types.d.ts.map +1 -0
- package/dist/src/sheet/editing-types.js +34 -0
- package/dist/src/sheet/editing-types.js.map +1 -0
- package/dist/src/sheet/edits.d.ts +37 -0
- package/dist/src/sheet/edits.d.ts.map +1 -0
- package/dist/src/sheet/edits.js +31 -0
- package/dist/src/sheet/edits.js.map +1 -0
- package/dist/src/sheet/fields.d.ts +693 -0
- package/dist/src/sheet/fields.d.ts.map +1 -0
- package/dist/src/sheet/fields.js +280 -0
- package/dist/src/sheet/fields.js.map +1 -0
- package/dist/src/sheet/group.d.ts +165 -0
- package/dist/src/sheet/group.d.ts.map +1 -0
- package/dist/src/sheet/group.js +52 -0
- package/dist/src/sheet/group.js.map +1 -0
- package/dist/src/sheet/index.d.ts +542 -0
- package/dist/src/sheet/index.d.ts.map +1 -0
- package/dist/src/sheet/index.js +387 -0
- package/dist/src/sheet/index.js.map +1 -0
- package/dist/src/sheet/library.d.ts +284 -0
- package/dist/src/sheet/library.d.ts.map +1 -0
- package/dist/src/sheet/library.js +259 -0
- package/dist/src/sheet/library.js.map +1 -0
- package/dist/src/sheet/link.d.ts +398 -0
- package/dist/src/sheet/link.d.ts.map +1 -0
- package/dist/src/sheet/link.js +228 -0
- package/dist/src/sheet/link.js.map +1 -0
- package/dist/src/sheet/record.d.ts +75 -0
- package/dist/src/sheet/record.d.ts.map +1 -0
- package/dist/src/sheet/record.js +128 -0
- package/dist/src/sheet/record.js.map +1 -0
- package/dist/src/sheet/registers.d.ts +681 -0
- package/dist/src/sheet/registers.d.ts.map +1 -0
- package/dist/src/sheet/registers.js +671 -0
- package/dist/src/sheet/registers.js.map +1 -0
- package/dist/src/sheet/root.d.ts +329 -0
- package/dist/src/sheet/root.d.ts.map +1 -0
- package/dist/src/sheet/root.js +511 -0
- package/dist/src/sheet/root.js.map +1 -0
- package/dist/src/sheet/seed-bridge.d.ts +14 -0
- package/dist/src/sheet/seed-bridge.d.ts.map +1 -0
- package/dist/src/sheet/seed-bridge.js +51 -0
- package/dist/src/sheet/seed-bridge.js.map +1 -0
- package/dist/src/sheet/sheet.d.ts +11819 -0
- package/dist/src/sheet/sheet.d.ts.map +1 -0
- package/dist/src/sheet/sheet.js +236 -0
- package/dist/src/sheet/sheet.js.map +1 -0
- package/dist/src/sheet/sub-rows.d.ts +299 -0
- package/dist/src/sheet/sub-rows.d.ts.map +1 -0
- package/dist/src/sheet/sub-rows.js +272 -0
- package/dist/src/sheet/sub-rows.js.map +1 -0
- package/dist/src/sheet/templates.d.ts +161 -0
- package/dist/src/sheet/templates.d.ts.map +1 -0
- package/dist/src/sheet/templates.js +122 -0
- package/dist/src/sheet/templates.js.map +1 -0
- package/dist/src/sheet/transactions.d.ts +76 -0
- package/dist/src/sheet/transactions.d.ts.map +1 -0
- package/dist/src/sheet/transactions.js +40 -0
- package/dist/src/sheet/transactions.js.map +1 -0
- package/dist/src/sheet/types.d.ts +15226 -0
- package/dist/src/sheet/types.d.ts.map +1 -0
- package/dist/src/sheet/types.js +1070 -0
- package/dist/src/sheet/types.js.map +1 -0
- package/dist/src/sheet/views.d.ts +29 -0
- package/dist/src/sheet/views.d.ts.map +1 -0
- package/dist/src/sheet/views.js +43 -0
- package/dist/src/sheet/views.js.map +1 -0
- package/dist/src/studio/component.d.ts.map +1 -1
- package/dist/src/studio/component.js +11 -8
- package/dist/src/studio/component.js.map +1 -1
- package/dist/src/utils/derive.d.ts +134 -8
- package/dist/src/utils/derive.d.ts.map +1 -1
- package/dist/src/utils/derive.js +409 -7
- package/dist/src/utils/derive.js.map +1 -1
- package/dist/src/utils/ordered-source.d.ts +38 -0
- package/dist/src/utils/ordered-source.d.ts.map +1 -0
- package/dist/src/utils/ordered-source.js +45 -0
- package/dist/src/utils/ordered-source.js.map +1 -0
- package/dist/src/utils/row-inspector.d.ts +36 -0
- package/dist/src/utils/row-inspector.d.ts.map +1 -0
- package/dist/src/utils/row-inspector.js +49 -0
- package/dist/src/utils/row-inspector.js.map +1 -0
- package/dist/test/bind/data/data.examples.d.ts +21 -19
- package/dist/test/bind/data/data.examples.d.ts.map +1 -1
- package/dist/test/bind/data/data.examples.js +122 -122
- package/dist/test/bind/data/data.examples.js.map +1 -1
- package/dist/test/bind/record/record.examples.d.ts.map +1 -1
- package/dist/test/bind/record/record.examples.js +9 -9
- package/dist/test/bind/record/record.examples.js.map +1 -1
- package/dist/test/calendar/calendar-records.examples.d.ts +228 -0
- package/dist/test/calendar/calendar-records.examples.d.ts.map +1 -0
- package/dist/test/calendar/calendar-records.examples.js +91 -0
- package/dist/test/calendar/calendar-records.examples.js.map +1 -0
- package/dist/test/calendar/calendar.examples.d.ts +776 -0
- package/dist/test/calendar/calendar.examples.d.ts.map +1 -0
- package/dist/test/calendar/calendar.examples.js +336 -0
- package/dist/test/calendar/calendar.examples.js.map +1 -0
- package/dist/test/decision/journal.examples.d.ts +28 -28
- package/dist/test/decision/journal.examples.d.ts.map +1 -1
- package/dist/test/decision/journal.examples.js +4 -4
- package/dist/test/decision/journal.examples.js.map +1 -1
- package/dist/test/decision/queue.examples.d.ts.map +1 -1
- package/dist/test/decision/queue.examples.js +5 -7
- package/dist/test/decision/queue.examples.js.map +1 -1
- package/dist/test/flowchart/flowchart.examples.d.ts +3713 -0
- package/dist/test/flowchart/flowchart.examples.d.ts.map +1 -0
- package/dist/test/flowchart/flowchart.examples.js +465 -0
- package/dist/test/flowchart/flowchart.examples.js.map +1 -0
- package/dist/test/plan/plan-events.examples.d.ts +3666 -0
- package/dist/test/plan/plan-events.examples.d.ts.map +1 -0
- package/dist/test/plan/plan-events.examples.js +773 -0
- package/dist/test/plan/plan-events.examples.js.map +1 -0
- package/dist/test/plan/plan-records.examples.d.ts +3 -0
- package/dist/test/plan/plan-records.examples.d.ts.map +1 -0
- package/dist/test/plan/plan-records.examples.js +45 -0
- package/dist/test/plan/plan-records.examples.js.map +1 -0
- package/dist/test/plan/plan-windows.examples.d.ts +588 -0
- package/dist/test/plan/plan-windows.examples.d.ts.map +1 -0
- package/dist/test/plan/plan-windows.examples.js +170 -0
- package/dist/test/plan/plan-windows.examples.js.map +1 -0
- package/dist/test/plan/plan.examples.d.ts +8095 -0
- package/dist/test/plan/plan.examples.d.ts.map +1 -0
- package/dist/test/plan/plan.examples.js +2500 -0
- package/dist/test/plan/plan.examples.js.map +1 -0
- package/dist/test/roster/domain.examples.d.ts +367 -0
- package/dist/test/roster/domain.examples.d.ts.map +1 -0
- package/dist/test/roster/domain.examples.js +72 -0
- package/dist/test/roster/domain.examples.js.map +1 -0
- package/dist/test/roster/roster-records.examples.d.ts +729 -0
- package/dist/test/roster/roster-records.examples.d.ts.map +1 -0
- package/dist/test/roster/roster-records.examples.js +86 -0
- package/dist/test/roster/roster-records.examples.js.map +1 -0
- package/dist/test/roster/roster.examples.d.ts +750 -0
- package/dist/test/roster/roster.examples.d.ts.map +1 -0
- package/dist/test/roster/roster.examples.js +198 -0
- package/dist/test/roster/roster.examples.js.map +1 -0
- package/dist/test/roster/warehouse.d.ts +87 -0
- package/dist/test/roster/warehouse.d.ts.map +1 -0
- package/dist/test/roster/warehouse.js +883 -0
- package/dist/test/roster/warehouse.js.map +1 -0
- package/dist/test/sheet/sheet-link.examples.d.ts +7 -0
- package/dist/test/sheet/sheet-link.examples.d.ts.map +1 -0
- package/dist/test/sheet/sheet-link.examples.js +41 -0
- package/dist/test/sheet/sheet-link.examples.js.map +1 -0
- package/dist/test/sheet/sheet-records.examples.d.ts +421 -0
- package/dist/test/sheet/sheet-records.examples.d.ts.map +1 -0
- package/dist/test/sheet/sheet-records.examples.js +79 -0
- package/dist/test/sheet/sheet-records.examples.js.map +1 -0
- package/dist/test/sheet/sheet-transactions.examples.d.ts +7 -0
- package/dist/test/sheet/sheet-transactions.examples.d.ts.map +1 -0
- package/dist/test/sheet/sheet-transactions.examples.js +58 -0
- package/dist/test/sheet/sheet-transactions.examples.js.map +1 -0
- package/dist/test/sheet/sheet.examples.d.ts +5562 -0
- package/dist/test/sheet/sheet.examples.d.ts.map +1 -0
- package/dist/test/sheet/sheet.examples.js +1178 -0
- package/dist/test/sheet/sheet.examples.js.map +1 -0
- package/dist/test/studio/studio.examples.d.ts.map +1 -1
- package/dist/test/studio/studio.examples.js +11 -11
- package/dist/test/studio/studio.examples.js.map +1 -1
- package/package.json +12 -9
|
@@ -0,0 +1,1921 @@
|
|
|
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
|
+
* Plan series (#822) — the row recipes of a `data` + `series` canvas.
|
|
7
|
+
*
|
|
8
|
+
* A canvas is its `data` (keyed entries) plus a list of series. Each series
|
|
9
|
+
* says which entries it takes (`match`), what kind of row each becomes, and
|
|
10
|
+
* how to read that row's content from the entry; several series over one
|
|
11
|
+
* source give each entry several rows. The top-level list IS the layout: each
|
|
12
|
+
* series contributes its rows in declared order, as BLOCKS kept apart
|
|
13
|
+
* (`PlanBlockType`, #823) — a data series one block of its entries' rows (an
|
|
14
|
+
* ordered stream, `Array<PlanRow>`), which a paged canvas pages on its own; a
|
|
15
|
+
* section its header's fixed block, then its members' blocks; hand-built rows
|
|
16
|
+
* a fixed block.
|
|
17
|
+
*
|
|
18
|
+
* Hierarchy comes only from the data's own nesting. Every data series takes
|
|
19
|
+
* `children`: a bare accessor returns more of THIS series' entries (to any
|
|
20
|
+
* depth — a `RecursiveType` entry), and `Plan.children(of, [series…])` steps
|
|
21
|
+
* down to a child collection of another type, laid out like a top-level list.
|
|
22
|
+
* `Plan.series.section` puts a titled block over series, `Plan.series.views`
|
|
23
|
+
* gives each entry one adjacent row per member series, and `Plan.series.rows`
|
|
24
|
+
* places hand-built rows.
|
|
25
|
+
*
|
|
26
|
+
* Every row has a typed id — `{ series, path }`, the path the entry keys that
|
|
27
|
+
* lead to it — and every series is a REAL EAST VALUE (the `DataBindHandleType`
|
|
28
|
+
* pattern): its `derive` is a reified East function from the source to its
|
|
29
|
+
* rows, so a series list can be bound, picked (`Plan.pick`) and stored. Each
|
|
30
|
+
* builder also records how to BUILD the series for any collection type, which
|
|
31
|
+
* is what lets a step-down compile its child series for the child collection's
|
|
32
|
+
* type, the root compile them for a source keyed by any type, and the root
|
|
33
|
+
* refuse two series sharing a key.
|
|
34
|
+
*
|
|
35
|
+
* @packageDocumentation
|
|
36
|
+
*/
|
|
37
|
+
import { East, Expr, ArrayType, BlobType, BooleanType, DateTimeType, DictType, EastTypeType, FloatType, FunctionType, IntegerType, OptionType, RefType, StringType, StructType, VariantType, isTypeEqual, printFor, toEastTypeValue, variant, some, none, } from "@elaraai/east";
|
|
38
|
+
import { IconType, StatusValueType } from "@elaraai/east-ui";
|
|
39
|
+
import { TableAggregateType, TickFormatType, reifyAccessor, resolveTag } from "@elaraai/east-ui/internal";
|
|
40
|
+
import { PlanAggregateType, PlanExpandType, PlanGroupSummaryType, PlanHeatCellsType, PlanLaneType, PlanBucketEventType, PlanCellMarkerType, PlanChipType, PlanEventMarkType, PlanRunType, PlanDecisionMarkType, PlanPortType, PlanRollupType, PlanRowIdType, PlanRowKindType, PlanRowType, PlanRowsCollectionType, PlanBlockType, PlanBlocksType, PlanGestureType, PlanDropType, PlanInstantType, } from "./types.js";
|
|
41
|
+
import { resolveIcon } from "./builders.js";
|
|
42
|
+
import { planRow, planGutter, normalizeRows, REBASE_ROWS } from "./assemble.js";
|
|
43
|
+
import { spanKind, bucketsKind, chartKind, heatKind, tableKind, cardsKind, eventsKind, groupKind, groupSummary, } from "./factories.js";
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// The series type — a constructor, instantiated per row type
|
|
46
|
+
// ============================================================================
|
|
47
|
+
/**
|
|
48
|
+
* The item a move to another row carries between its two writes (#825) — its
|
|
49
|
+
* bytes, from the source row's write, which takes it out of its list, to the
|
|
50
|
+
* target row's, which puts it into its own; `none` while nothing is in
|
|
51
|
+
* transit. The canvas threads ONE through every row a gesture writes, in
|
|
52
|
+
* order, and an item still carried at the end is a move that landed nowhere.
|
|
53
|
+
*
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export const PlanCarriedType = RefType(OptionType(BlobType));
|
|
57
|
+
/**
|
|
58
|
+
* The Plan series type CONSTRUCTOR — given the entry type (and the entries'
|
|
59
|
+
* key type, `String` by default) it returns the concrete variant type of one
|
|
60
|
+
* series value (the `DataBindHandleType` precedent: the entry type lives
|
|
61
|
+
* structurally in each arm's `derive` signature, so a series built for one
|
|
62
|
+
* entry type is a compile error against data of another).
|
|
63
|
+
*
|
|
64
|
+
* @remarks
|
|
65
|
+
* Every arm carries the series' identity and `derive: Fn(Dict<K, R>) →
|
|
66
|
+
* Array<PlanBlock>` — the blocks the series contributes (#823: a data series'
|
|
67
|
+
* one block of its entries' rows, each followed by its subtree; a section's
|
|
68
|
+
* header block, then its members'), reified once by its builder. The arm is
|
|
69
|
+
* the series' kind, which is all the series library reads beyond the identity.
|
|
70
|
+
*
|
|
71
|
+
* It carries its writer too (#880), `write: Fn(R, K, RowId, Gesture,
|
|
72
|
+
* Ref<Option<Blob>>) → Option<R>` — a gesture on one of its rows written into
|
|
73
|
+
* the entry the row came from, through the fields the series declares
|
|
74
|
+
* (`edit`), at whatever depth the row sits; `none` when no
|
|
75
|
+
* series in its tree takes the gesture on that row. The `Ref` is the item a
|
|
76
|
+
* move to another row carries (#825): the source row's write takes the item
|
|
77
|
+
* out of its list into it, as bytes, and the target row's write puts it into
|
|
78
|
+
* its own list — the two rows may be different series' and different
|
|
79
|
+
* entries'. A series list is an East value (a pick, a stored list), so the
|
|
80
|
+
* writer travels with the series rather than being rebuilt by the canvas.
|
|
81
|
+
*
|
|
82
|
+
* @param r - The entry type value
|
|
83
|
+
* @param k - The entries' key type (default `StringType`)
|
|
84
|
+
* @returns The concrete `VariantType` of a series over entries of `r` keyed by `k`
|
|
85
|
+
*/
|
|
86
|
+
const seriesShape = (r, k = StringType) => {
|
|
87
|
+
const arm = StructType({
|
|
88
|
+
key: StringType,
|
|
89
|
+
title: StringType,
|
|
90
|
+
subtitle: OptionType(StringType),
|
|
91
|
+
icon: OptionType(IconType),
|
|
92
|
+
derive: FunctionType([DictType(k, r)], PlanBlocksType),
|
|
93
|
+
write: FunctionType([r, k, PlanRowIdType, PlanGestureType, PlanCarriedType], OptionType(r)),
|
|
94
|
+
});
|
|
95
|
+
return VariantType({
|
|
96
|
+
span: arm, buckets: arm, chart: arm, heat: arm, table: arm, cards: arm, events: arm,
|
|
97
|
+
group: arm, section: arm, views: arm, rows: arm,
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The Plan series type constructor — `Plan.Types.Series(RowType)` for a list
|
|
102
|
+
* of series over `Dict<String, RowType>` entries, `Plan.Types.Series(RowType,
|
|
103
|
+
* IntegerType)` over `Dict<Integer, RowType>`.
|
|
104
|
+
*/
|
|
105
|
+
export const PlanSeriesType = seriesShape;
|
|
106
|
+
/** Marks a {@link PlanChildren} step-down. */
|
|
107
|
+
const PLAN_CHILDREN = Symbol("PlanChildren");
|
|
108
|
+
/**
|
|
109
|
+
* Step down from an entry to a child collection of another entry type (#822).
|
|
110
|
+
*
|
|
111
|
+
* @typeParam R - The parent entry type (inferred from where the step-down is used)
|
|
112
|
+
* @typeParam KT - The parent entries' key type
|
|
113
|
+
* @param of - The child collection — an `Array` (index order) or a `Dict` (key order) — from the parent entry
|
|
114
|
+
* @param series - The series over the child entries, laid out like a top-level list
|
|
115
|
+
* @returns The step-down, for a series' `children`
|
|
116
|
+
*/
|
|
117
|
+
export function createChildren(of, series) {
|
|
118
|
+
return { [PLAN_CHILDREN]: true, of, series };
|
|
119
|
+
}
|
|
120
|
+
function isStepDown(x) {
|
|
121
|
+
return typeof x === "object" && x !== null && x[PLAN_CHILDREN] === true;
|
|
122
|
+
}
|
|
123
|
+
// ============================================================================
|
|
124
|
+
// Build specs — how each series is built for a collection type
|
|
125
|
+
// ============================================================================
|
|
126
|
+
const PathType = ArrayType(StringType);
|
|
127
|
+
const IdOptType = OptionType(PlanRowIdType);
|
|
128
|
+
/** Every series value a builder made, with its build spec. */
|
|
129
|
+
const SPECS = new WeakMap();
|
|
130
|
+
/** The spec of a series value a `Plan.series.*` builder made, if it was one. */
|
|
131
|
+
function specOf(s) {
|
|
132
|
+
return typeof s === "object" && s !== null ? SPECS.get(s) : undefined;
|
|
133
|
+
}
|
|
134
|
+
/** A series value inside a composite series — it must come from a builder, since the parent builds it for its own entries. */
|
|
135
|
+
function nestedSpec(s, where) {
|
|
136
|
+
const spec = specOf(s);
|
|
137
|
+
if (spec === undefined) {
|
|
138
|
+
throw new Error(`${where}: a nested series must be a \`Plan.series.*\` value written in place — the parent builds it ` +
|
|
139
|
+
"for its own entries, so a series bound to a variable elsewhere cannot be nested here");
|
|
140
|
+
}
|
|
141
|
+
return spec;
|
|
142
|
+
}
|
|
143
|
+
/** A readable name for a series, in messages. */
|
|
144
|
+
function nameOf(spec) {
|
|
145
|
+
return `${spec.arm} "${spec.title}"`;
|
|
146
|
+
}
|
|
147
|
+
/** A collection type's entry and key types — a `Dict`'s value and key, an `Array`'s element and `Integer` index. */
|
|
148
|
+
function shapeOf(collection, where) {
|
|
149
|
+
if (collection.type === "Dict")
|
|
150
|
+
return { entry: collection.value, key: collection.key };
|
|
151
|
+
if (collection.type === "Array")
|
|
152
|
+
return { entry: collection.value, key: IntegerType };
|
|
153
|
+
throw new Error(`${where}: entries come from a Dict (keyed) or an Array (by index) — got a ${collection.type}`);
|
|
154
|
+
}
|
|
155
|
+
/** Refuse a collection whose entries are not the series' entry type. */
|
|
156
|
+
function assertEntry(entry, rowType, where) {
|
|
157
|
+
if (!isTypeEqual(entry, rowType)) {
|
|
158
|
+
throw new Error(`${where}: the series is built over one entry type and applied to entries of another — ` +
|
|
159
|
+
`pass the entries' own type as the series' first argument`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** A path segment from a key — a String key as is, any other key as its `.east` text. */
|
|
163
|
+
function segmentOf(keyType) {
|
|
164
|
+
return keyType.type === "String"
|
|
165
|
+
? (key) => key
|
|
166
|
+
: (key) => East.print(key);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* A series' rows as ONE block (#823) — a data series' entries (`fixed: false`),
|
|
170
|
+
* or rows no entry produces (`fixed: true`: hand-built rows), under the parent
|
|
171
|
+
* the block nests in.
|
|
172
|
+
*
|
|
173
|
+
* @param collection - The collection type the rows are built for
|
|
174
|
+
* @param rows - The series' rows emitter for that collection
|
|
175
|
+
* @param fixed - Whether no entry produces the rows
|
|
176
|
+
* @returns The block list's emitter
|
|
177
|
+
*/
|
|
178
|
+
function oneBlock(collection, rows, fixed) {
|
|
179
|
+
return East.function([collection, PathType, IdOptType], PlanBlocksType, ($, coll, prefix, parent) => {
|
|
180
|
+
const block = $.let(East.value({ fixed, parent, rows: rows(coll, prefix, parent) }, PlanBlockType), PlanBlockType);
|
|
181
|
+
return East.value([block], PlanBlocksType);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/** A cache of built functions by East type. */
|
|
185
|
+
class ByType {
|
|
186
|
+
entries = [];
|
|
187
|
+
get(type, build) {
|
|
188
|
+
const hit = this.entries.find((e) => isTypeEqual(e.type, type));
|
|
189
|
+
if (hit !== undefined)
|
|
190
|
+
return hit.value;
|
|
191
|
+
const value = build();
|
|
192
|
+
this.entries.push({ type, value });
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** Build `fn`, naming the series and the key type when its accessors do not build for it. */
|
|
197
|
+
function building(where, keyType, fn) {
|
|
198
|
+
try {
|
|
199
|
+
return fn();
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
const kt = keyType.type;
|
|
203
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
204
|
+
throw new Error(`${where}: its accessors do not build for entries keyed by ${kt} (${reason}) — the key an accessor ` +
|
|
205
|
+
`receives is the collection's: the source's key, a Dict child's key, or an Array child's Integer ` +
|
|
206
|
+
"index. Declare the key type the accessors expect with `keyType`, or leave the key unread.");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* How `accessor` reaches the child collection it returns, read off the
|
|
211
|
+
* expression it builds (#880): a plain field of the entry (`r => r.children`,
|
|
212
|
+
* `Plan.children(r => r.presses, …)`), or the entry itself (`g => g`). A write
|
|
213
|
+
* follows either back into the entry. Anything else — a filtered or computed
|
|
214
|
+
* collection — is a copy a write would be lost in, and is `undefined`.
|
|
215
|
+
*
|
|
216
|
+
* @param rowType - The entry type
|
|
217
|
+
* @param keyType - The entries' key type
|
|
218
|
+
* @param accessor - The accessor, over the entry (its node, when recursive) and key
|
|
219
|
+
* @returns How it reaches its collection, or `undefined` when a write cannot follow it
|
|
220
|
+
*/
|
|
221
|
+
function childRefOf(rowType, keyType, accessor) {
|
|
222
|
+
const recursive = rowType.type === "Recursive";
|
|
223
|
+
let found;
|
|
224
|
+
East.function([rowType, keyType], undefined, (_$, v, k) => {
|
|
225
|
+
const node = recursive ? v.unwrap() : v;
|
|
226
|
+
const out = accessor(node, k);
|
|
227
|
+
const ast = Expr.ast(out);
|
|
228
|
+
const param = Expr.ast(v);
|
|
229
|
+
const nodeAst = Expr.ast(node);
|
|
230
|
+
if (ast === param || ast === nodeAst)
|
|
231
|
+
found = { kind: "self" };
|
|
232
|
+
else if (ast.ast_type === "GetField" && ast.field !== undefined && (ast.struct === nodeAst || ast.struct === param)) {
|
|
233
|
+
found = { kind: "field", field: ast.field };
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
});
|
|
237
|
+
return found;
|
|
238
|
+
}
|
|
239
|
+
/** Each key type's segment reader, built once. */
|
|
240
|
+
const segmentKeys = new ByType();
|
|
241
|
+
/**
|
|
242
|
+
* A path segment read back as its collection's key (#880) — the inverse of
|
|
243
|
+
* {@link segmentOf}: a String key as it is, any other key its `.east` text
|
|
244
|
+
* parsed (an `Array` child's segment is its Integer index).
|
|
245
|
+
*
|
|
246
|
+
* @param keyType - The collection's key type
|
|
247
|
+
* @returns `(segment) → key`
|
|
248
|
+
*/
|
|
249
|
+
function segmentKeyOf(keyType) {
|
|
250
|
+
return segmentKeys.get(keyType, () => (keyType.type === "String"
|
|
251
|
+
? East.function([StringType], StringType, (_$, s) => s)
|
|
252
|
+
: East.function([StringType], keyType, (_$, s) => s.parse(keyType))));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* A child collection's part of a write (#880) — whether it holds a key, its
|
|
256
|
+
* entry at it, and that entry written back in place. A `Dict` by key; an
|
|
257
|
+
* `Array` by index.
|
|
258
|
+
*/
|
|
259
|
+
function collectionOps(collection) {
|
|
260
|
+
if (collection.type === "Dict") {
|
|
261
|
+
const dict = (c) => c;
|
|
262
|
+
return {
|
|
263
|
+
has: (c, k) => dict(c).has(k),
|
|
264
|
+
get: (c, k) => dict(c).get(k),
|
|
265
|
+
put: (c, k, v) => dict(c).update(k, v),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const array = (c) => c;
|
|
269
|
+
const index = (k) => k;
|
|
270
|
+
return {
|
|
271
|
+
has: (c, k) => index(k).greaterEqual(0n).and(() => index(k).less(array(c).size())),
|
|
272
|
+
get: (c, k) => array(c).get(index(k)),
|
|
273
|
+
put: (c, k, v) => array(c).update(index(k), v),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/** The instant fields a kind's elements have (#825): two ends (a run, a chip) or one instant (a tile, a mark). */
|
|
277
|
+
function editShapeOf(arm) {
|
|
278
|
+
return arm === "span" || arm === "cards" ? "span" : "point";
|
|
279
|
+
}
|
|
280
|
+
/** The item field types a move can write an instant into (#825). */
|
|
281
|
+
const INSTANT_FIELD_TYPES = [DateTimeType, FloatType, IntegerType, StringType, PlanInstantType];
|
|
282
|
+
/** Print an item type as the text rows of one item type share (#825) — East's own printer over its type value. */
|
|
283
|
+
const printType = printFor(EastTypeType);
|
|
284
|
+
/**
|
|
285
|
+
* Check what a series' `edit` declares for a move (#825), against its item
|
|
286
|
+
* type — `undefined` when it declares no move (only `create`).
|
|
287
|
+
*
|
|
288
|
+
* @param edit - The declaration
|
|
289
|
+
* @param itemType - The type of the items in `edit.items`
|
|
290
|
+
* @param arm - The series' kind
|
|
291
|
+
* @param where - The series, for the message
|
|
292
|
+
* @returns The move's fields
|
|
293
|
+
* @throws {Error} Naming the field and what is wrong with it
|
|
294
|
+
*/
|
|
295
|
+
function moveFieldsOf(edit, itemType, arm, where) {
|
|
296
|
+
const shape = editShapeOf(arm);
|
|
297
|
+
if (shape === "span" && edit.at !== undefined) {
|
|
298
|
+
throw new Error(`${where}: \`edit.at\` is a tile's or a mark's instant — a ${arm} element has two ends: name them with \`start\` and \`end\``);
|
|
299
|
+
}
|
|
300
|
+
if (shape === "point" && (edit.start !== undefined || edit.end !== undefined)) {
|
|
301
|
+
throw new Error(`${where}: a ${arm} element has one instant — name it with \`edit.at\`, not \`start\` / \`end\``);
|
|
302
|
+
}
|
|
303
|
+
const start = shape === "span" ? edit.start : edit.at;
|
|
304
|
+
const end = shape === "span" ? edit.end : undefined;
|
|
305
|
+
if (shape === "span" && (start === undefined) !== (end === undefined)) {
|
|
306
|
+
throw new Error(`${where}: a move writes both ends of a ${arm} element — give \`edit.start\` and \`edit.end\` together`);
|
|
307
|
+
}
|
|
308
|
+
if (start === undefined) {
|
|
309
|
+
if (edit.create === undefined) {
|
|
310
|
+
throw new Error(`${where}: \`edit\` declares neither \`create\` (what a dropped card becomes) nor the item fields a move ` +
|
|
311
|
+
`writes (\`key\` and ${shape === "span" ? "`start` / `end`" : "`at`"}) — declare one or both`);
|
|
312
|
+
}
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
const item = itemType;
|
|
316
|
+
if (item.type !== "Struct") {
|
|
317
|
+
throw new Error(`${where}: a move writes fields of the item — the items in "${edit.items}" must be structs`);
|
|
318
|
+
}
|
|
319
|
+
if (edit.key === undefined) {
|
|
320
|
+
throw new Error(`${where}: a move finds its element's item by key — name the item's String key field with \`edit.key\``);
|
|
321
|
+
}
|
|
322
|
+
const keyType = item.fields[edit.key];
|
|
323
|
+
if (keyType === undefined || !isTypeEqual(keyType, StringType)) {
|
|
324
|
+
throw new Error(`${where}: \`edit.key\` names "${edit.key}", which is not a String field of the item`);
|
|
325
|
+
}
|
|
326
|
+
const instant = (name, what) => {
|
|
327
|
+
const type = item.fields[name];
|
|
328
|
+
if (type === undefined || !INSTANT_FIELD_TYPES.some((t) => isTypeEqual(t, type))) {
|
|
329
|
+
throw new Error(`${where}: \`edit.${what}\` names "${name}", which is not an instant field of the item — a DateTime, a Float or ` +
|
|
330
|
+
"an Integer, a String, or a Plan.Types.Instant");
|
|
331
|
+
}
|
|
332
|
+
if (name === edit.key) {
|
|
333
|
+
throw new Error(`${where}: \`edit.${what}\` names the key field "${name}" — a move would change the item's identity; name its instant field`);
|
|
334
|
+
}
|
|
335
|
+
return { name, type };
|
|
336
|
+
};
|
|
337
|
+
if (end !== undefined && end === start) {
|
|
338
|
+
throw new Error(`${where}: \`edit.start\` and \`edit.end\` both name "${start}" — a move writes two different fields`);
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
item,
|
|
342
|
+
key: edit.key,
|
|
343
|
+
start: instant(start, shape === "span" ? "start" : "at"),
|
|
344
|
+
end: end !== undefined ? instant(end, "end") : undefined,
|
|
345
|
+
edits: { items: printType(toEastTypeValue(itemType)), resize: shape === "span" },
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/** Each instant field type's converter, built once (#825). */
|
|
349
|
+
const instantConverters = new ByType();
|
|
350
|
+
/**
|
|
351
|
+
* The converter from an axis instant into an item field's type (#825): the
|
|
352
|
+
* instant on the arm the field holds — a time instant's DateTime, a number
|
|
353
|
+
* instant's Float (an Integer field takes it rounded, half away from zero), an
|
|
354
|
+
* ordinal instant's String, or the instant itself — else `none`.
|
|
355
|
+
*
|
|
356
|
+
* @param fieldType - The field's type (one of {@link INSTANT_FIELD_TYPES})
|
|
357
|
+
* @returns `(instant) → Option<field>`
|
|
358
|
+
*/
|
|
359
|
+
function instantInto(fieldType) {
|
|
360
|
+
return instantConverters.get(fieldType, () => {
|
|
361
|
+
const out = OptionType(fieldType);
|
|
362
|
+
const kind = fieldType.type;
|
|
363
|
+
const whole = isTypeEqual(fieldType, PlanInstantType);
|
|
364
|
+
return East.function([PlanInstantType], out, ($, t) => {
|
|
365
|
+
const result = $.let(none, out);
|
|
366
|
+
if (whole) {
|
|
367
|
+
$.assign(result, some(t));
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
$.match(t, {
|
|
371
|
+
time: ($2, d) => {
|
|
372
|
+
if (kind === "DateTime")
|
|
373
|
+
$2.assign(result, some(d));
|
|
374
|
+
},
|
|
375
|
+
number: ($2, n) => {
|
|
376
|
+
if (kind === "Float") {
|
|
377
|
+
$2.assign(result, some(n));
|
|
378
|
+
}
|
|
379
|
+
else if (kind === "Integer") {
|
|
380
|
+
const rounded = $2.let(n.greaterEqual(0).ifElse(() => n.add(0.5).subtract(n.add(0.5).remainder(1)), () => n.subtract(0.5).subtract(n.subtract(0.5).remainder(1))), FloatType);
|
|
381
|
+
$2.assign(result, some(rounded.toInteger()));
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
ordinal: ($2, s) => {
|
|
385
|
+
if (kind === "String")
|
|
386
|
+
$2.assign(result, some(s));
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
return result;
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* The build spec of a data series (span / buckets / chart / heat / table /
|
|
395
|
+
* cards / events / group).
|
|
396
|
+
*
|
|
397
|
+
* @remarks
|
|
398
|
+
* Per key type it builds ONE entry-row function (the entry's gutter, kind and
|
|
399
|
+
* row facts through the one envelope) and the membership, collapse and
|
|
400
|
+
* children functions, each reified once and CALLED in the block's loop. The
|
|
401
|
+
* block walks the collection in order; each matched entry's row is followed by
|
|
402
|
+
* its children's rows — a bare `children` accessor walks the entry's subtree
|
|
403
|
+
* with an explicit pre-order stack (its depth is data), a step-down calls its
|
|
404
|
+
* child series' blocks on the child collection.
|
|
405
|
+
*/
|
|
406
|
+
function dataSpec(rowType, cfg, recipe) {
|
|
407
|
+
const where = `Plan.series.${recipe.arm} "${cfg.key}"`;
|
|
408
|
+
const children = cfg.children;
|
|
409
|
+
const steps = children === undefined || typeof children === "function"
|
|
410
|
+
? [] : (Array.isArray(children) ? children : [children]);
|
|
411
|
+
for (const step of steps) {
|
|
412
|
+
if (!isStepDown(step)) {
|
|
413
|
+
throw new Error(`${where}: \`children\` is an accessor for more of this series, or \`Plan.children(of, [series…])\` step-downs`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const nested = steps.flatMap((s) => s.series.map((x) => nestedSpec(x, `${where} › children`)));
|
|
417
|
+
// A recursive entry reaches the accessors as its NODE (`PlanEntryExpr`),
|
|
418
|
+
// so they read its fields directly at every depth.
|
|
419
|
+
const recursive = rowType.type === "Recursive";
|
|
420
|
+
const nodeOf = (value) => recursive
|
|
421
|
+
? value.unwrap()
|
|
422
|
+
: value;
|
|
423
|
+
/** An accessor over entries, applied to an entry value (its node, when recursive). */
|
|
424
|
+
const onEntry = (accessor) => (v, k) => accessor(nodeOf(v), k);
|
|
425
|
+
// The removed verdict, named (#1260) — a plain JS caller would otherwise
|
|
426
|
+
// lose it silently.
|
|
427
|
+
for (const removed of ["review", "approval"]) {
|
|
428
|
+
if (removed in cfg) {
|
|
429
|
+
throw new Error(`${where}: \`${removed}\` is removed (#1260) — a Plan approves and rejects nothing: its changes are drafts of the root's \`editing\` session`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// What a gesture writes (#880, #825) — a dropped card into `edit.items`, a
|
|
433
|
+
// moved element into its item's fields — each a FIELD of the entry (its
|
|
434
|
+
// node, when recursive) or of its items, checked here so a misnamed field
|
|
435
|
+
// fails the build.
|
|
436
|
+
const edit = recipe.edit;
|
|
437
|
+
const nodeType = (recursive ? rowType.node : rowType);
|
|
438
|
+
if (edit !== undefined && nodeType.type !== "Struct") {
|
|
439
|
+
throw new Error(`${where}: \`edit\` writes a field of the entry — its entries must be structs (a recursive entry's node included)`);
|
|
440
|
+
}
|
|
441
|
+
const entryFields = nodeType.fields ?? {};
|
|
442
|
+
let itemType;
|
|
443
|
+
let move;
|
|
444
|
+
if (edit !== undefined) {
|
|
445
|
+
const t = entryFields[edit.items];
|
|
446
|
+
if (t === undefined || t.type !== "Array") {
|
|
447
|
+
throw new Error(`${where}: \`edit.items\` names "${edit.items}", which is not an Array field of the entry — a gesture writes a list of items`);
|
|
448
|
+
}
|
|
449
|
+
itemType = t.value;
|
|
450
|
+
move = moveFieldsOf(edit, itemType, recipe.arm, where);
|
|
451
|
+
}
|
|
452
|
+
const edits = edit !== undefined
|
|
453
|
+
? {
|
|
454
|
+
drop: edit.create !== undefined,
|
|
455
|
+
move: move !== undefined ? some(move.edits) : none,
|
|
456
|
+
}
|
|
457
|
+
: undefined;
|
|
458
|
+
const rows = new ByType();
|
|
459
|
+
const matches = new ByType();
|
|
460
|
+
const collapses = new ByType();
|
|
461
|
+
const kids = new ByType();
|
|
462
|
+
const walks = new ByType();
|
|
463
|
+
const emitters = new ByType();
|
|
464
|
+
const blockLists = new ByType();
|
|
465
|
+
const rowFor = (kt) => rows.get(kt, () => building(where, kt, () => East.function([rowType, kt, PlanRowIdType, IdOptType, BooleanType, BooleanType], PlanRowType, ($, value, key, id, parent, hasChildren, collapsed) => {
|
|
466
|
+
// The node every accessor reads, unwrapped once.
|
|
467
|
+
const entry = recursive ? $.let(nodeOf(value)) : value;
|
|
468
|
+
return planRow({
|
|
469
|
+
id, parent,
|
|
470
|
+
gutter: planGutter({
|
|
471
|
+
label: cfg.label(entry, key),
|
|
472
|
+
...(cfg.id !== undefined ? { id: cfg.id } : {}),
|
|
473
|
+
...(cfg.stacked !== undefined ? { stacked: cfg.stacked } : {}),
|
|
474
|
+
...(cfg.sub !== undefined ? { sub: cfg.sub(entry, key) } : {}),
|
|
475
|
+
...(cfg.value !== undefined ? { value: cfg.value(entry, key) } : {}),
|
|
476
|
+
...(recipe.swatches !== undefined ? { swatches: recipe.swatches } : {}),
|
|
477
|
+
}),
|
|
478
|
+
kind: recipe.kind(entry, key, hasChildren),
|
|
479
|
+
collapsed,
|
|
480
|
+
...(recipe.pinned !== undefined ? { pinned: recipe.pinned(entry, key) } : {}),
|
|
481
|
+
...(cfg.status !== undefined ? { status: cfg.status(entry, key) } : {}),
|
|
482
|
+
...(cfg.expand !== undefined ? { expand: cfg.expand(entry, key) } : {}),
|
|
483
|
+
...(edits !== undefined ? { edits } : {}),
|
|
484
|
+
});
|
|
485
|
+
})));
|
|
486
|
+
const matchFor = (kt) => matches.get(kt, () => {
|
|
487
|
+
const match = cfg.match;
|
|
488
|
+
if (match === undefined)
|
|
489
|
+
return undefined;
|
|
490
|
+
return building(where, kt, () => East.function([rowType, kt], BooleanType, (_$, value, key) => match(nodeOf(value), key)));
|
|
491
|
+
});
|
|
492
|
+
const collapsedFor = (kt) => collapses.get(kt, () => {
|
|
493
|
+
const collapsed = cfg.collapsed;
|
|
494
|
+
if (collapsed === undefined)
|
|
495
|
+
return undefined;
|
|
496
|
+
return building(where, kt, () => East.function([rowType, kt], BooleanType, (_$, value, key) => typeof collapsed === "boolean"
|
|
497
|
+
? East.value(collapsed, BooleanType)
|
|
498
|
+
: East.value(collapsed(nodeOf(value), key), BooleanType)));
|
|
499
|
+
});
|
|
500
|
+
/** A bare `children` accessor, built for entries keyed by `kt` — the child collection's type is its output. */
|
|
501
|
+
const childOf = (kt) => building(where, kt, () => reifyAccessor([rowType, kt], onEntry(children)));
|
|
502
|
+
/** The subtree walk below one entry — every level's entries in pre-order, over the child collection type. */
|
|
503
|
+
const walkFor = (cc) => walks.get(cc, () => {
|
|
504
|
+
const at = `${where} › children`;
|
|
505
|
+
const { entry, key: kc } = shapeOf(cc, at);
|
|
506
|
+
assertEntry(entry, rowType, `${at} (a bare accessor returns more of this series' entries — step down to another type with Plan.children)`);
|
|
507
|
+
const next = childOf(kc);
|
|
508
|
+
const nextType = Expr.type(next).output;
|
|
509
|
+
if (!isTypeEqual(nextType, cc)) {
|
|
510
|
+
throw new Error(`${at}: a recursive series' children must be the same collection type at every level`);
|
|
511
|
+
}
|
|
512
|
+
const row = rowFor(kc);
|
|
513
|
+
const match = matchFor(kc);
|
|
514
|
+
const collapsed = collapsedFor(kc);
|
|
515
|
+
const seg = segmentOf(kc);
|
|
516
|
+
const Frame = StructType({ value: rowType, key: kc, path: PathType, parent: PlanRowIdType });
|
|
517
|
+
const Frames = ArrayType(Frame);
|
|
518
|
+
// A level's matched entries as frames, REVERSED — so the stack
|
|
519
|
+
// pops the first of them first.
|
|
520
|
+
const framesOf = East.function([cc, PathType, PlanRowIdType], Frames, ($, coll, path, parentId) => {
|
|
521
|
+
const frames = $.let([], Frames);
|
|
522
|
+
const member = match === undefined ? undefined : $.const(match);
|
|
523
|
+
$.for(coll, ($2, value, key) => {
|
|
524
|
+
const add = ($3) => {
|
|
525
|
+
const segs = $3.let(East.value([seg(key)], PathType), PathType);
|
|
526
|
+
$3(frames.pushLast(East.value({ value, key, path: path.concat(segs), parent: parentId }, Frame)));
|
|
527
|
+
};
|
|
528
|
+
if (member === undefined)
|
|
529
|
+
add($2);
|
|
530
|
+
else
|
|
531
|
+
$2.if(member(value, key), add);
|
|
532
|
+
});
|
|
533
|
+
$(frames.reverseInPlace());
|
|
534
|
+
return frames;
|
|
535
|
+
});
|
|
536
|
+
return East.function([cc, PathType, PlanRowIdType], PlanRowsCollectionType, ($, coll, path0, parent0) => {
|
|
537
|
+
// Every function the loop calls, bound ONCE — a captured function
|
|
538
|
+
// referenced in the loop would be re-inlined per use.
|
|
539
|
+
const frames = $.const(framesOf);
|
|
540
|
+
const childrenOf = $.const(next);
|
|
541
|
+
const entryRow = $.const(row);
|
|
542
|
+
const collapse = collapsed === undefined ? undefined : $.const(collapsed);
|
|
543
|
+
const out = $.let([], PlanRowsCollectionType);
|
|
544
|
+
const stack = $.let(frames(coll, path0, parent0), Frames);
|
|
545
|
+
$.while(stack.size().greater(0n), ($2) => {
|
|
546
|
+
const f = $2.let(stack.popLast(), Frame);
|
|
547
|
+
const id = $2.let(East.value(variant("entry", { series: cfg.key, path: f.path }), PlanRowIdType), PlanRowIdType);
|
|
548
|
+
const below = $2.let(frames(childrenOf(f.value, f.key), f.path, id), Frames);
|
|
549
|
+
const parent = $2.let(East.value(some(f.parent), IdOptType), IdOptType);
|
|
550
|
+
const fold = $2.let(collapse === undefined
|
|
551
|
+
? East.value(false, BooleanType)
|
|
552
|
+
: collapse(f.value, f.key), BooleanType);
|
|
553
|
+
$2(out.pushLast(entryRow(f.value, f.key, id, parent, below.size().greater(0n), fold)));
|
|
554
|
+
$2(stack.append(below));
|
|
555
|
+
});
|
|
556
|
+
return out;
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
const childrenFor = (kt) => kids.get(kt, () => {
|
|
560
|
+
if (children === undefined)
|
|
561
|
+
return undefined;
|
|
562
|
+
if (typeof children === "function") {
|
|
563
|
+
const of = childOf(kt);
|
|
564
|
+
const cc = Expr.type(of).output;
|
|
565
|
+
const walk = walkFor(cc);
|
|
566
|
+
return East.function([rowType, kt, PlanRowIdType, PathType], PlanRowsCollectionType, (_$, value, key, id, path) => walk(of(value, key), path, id));
|
|
567
|
+
}
|
|
568
|
+
const parts = steps.map((step, i) => {
|
|
569
|
+
const at = `${where} › children[${i}]`;
|
|
570
|
+
const of = building(at, kt, () => reifyAccessor([rowType, kt], onEntry(step.of)));
|
|
571
|
+
const cc = Expr.type(of).output;
|
|
572
|
+
const blocks = step.series.map((s) => nestedSpec(s, at).emitter(cc, at));
|
|
573
|
+
return { of, cc, blocks };
|
|
574
|
+
});
|
|
575
|
+
return East.function([rowType, kt, PlanRowIdType, PathType], PlanRowsCollectionType, ($, value, key, id, path) => {
|
|
576
|
+
const out = $.let([], PlanRowsCollectionType);
|
|
577
|
+
const under = $.let(East.value(some(id), IdOptType), IdOptType);
|
|
578
|
+
for (const part of parts) {
|
|
579
|
+
const coll = $.let(part.of(value, key), part.cc);
|
|
580
|
+
for (const block of part.blocks)
|
|
581
|
+
$(out.append(block(coll, path, under)));
|
|
582
|
+
}
|
|
583
|
+
return out;
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
// ── Writing a gesture back (#880) ─────────────────────────────────────
|
|
587
|
+
// The entry a writer is handed is DETACHED (the series value's `write`
|
|
588
|
+
// copies it), so a nested entry is written in place in its collection and
|
|
589
|
+
// a dropped card's item joins its list in place.
|
|
590
|
+
const owns = new ByType();
|
|
591
|
+
const writers = new ByType();
|
|
592
|
+
const entryAccess = (value) => nodeOf(value);
|
|
593
|
+
/** The item a dropped card becomes, for entries keyed by `kt`. */
|
|
594
|
+
const createFor = (kt, create) => building(where, kt, () => East.function([PlanDropType, rowType, kt], itemType, (_$, drop, value, key) => create(drop, nodeOf(value), key)));
|
|
595
|
+
/**
|
|
596
|
+
* An item at a move's instants (#825) — its declared instant fields set
|
|
597
|
+
* from the axis's instants, each on its own arm, every other field as it
|
|
598
|
+
* was; `none` when an instant rides an arm the field cannot hold (a time
|
|
599
|
+
* instant into a Float field).
|
|
600
|
+
*/
|
|
601
|
+
const placed = move === undefined ? undefined : (() => {
|
|
602
|
+
const m = move;
|
|
603
|
+
const startAs = instantInto(m.start.type);
|
|
604
|
+
const endAs = m.end !== undefined ? instantInto(m.end.type) : undefined;
|
|
605
|
+
return East.function([m.item, PlanInstantType, PlanInstantType], OptionType(m.item), ($, item, start, end) => {
|
|
606
|
+
const result = $.let(none, OptionType(m.item));
|
|
607
|
+
const asStart = $.const(startAs);
|
|
608
|
+
const s = $.let(asStart(start), OptionType(m.start.type));
|
|
609
|
+
const read = item;
|
|
610
|
+
const rebuild = (fields) => East.value(Object.fromEntries(Object.keys(m.item.fields).map((f) => [f, fields[f] ?? read[f]])), m.item);
|
|
611
|
+
$.match(s, {
|
|
612
|
+
some: ($2, sv) => {
|
|
613
|
+
if (endAs === undefined || m.end === undefined) {
|
|
614
|
+
$2.assign(result, some(rebuild({ [m.start.name]: sv })));
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const endName = m.end.name;
|
|
618
|
+
const asEnd = $2.const(endAs);
|
|
619
|
+
const e = $2.let(asEnd(end), OptionType(m.end.type));
|
|
620
|
+
$2.match(e, {
|
|
621
|
+
some: ($3, ev) => {
|
|
622
|
+
$3.assign(result, some(rebuild({ [m.start.name]: sv, [endName]: ev })));
|
|
623
|
+
},
|
|
624
|
+
});
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
return result;
|
|
628
|
+
});
|
|
629
|
+
})();
|
|
630
|
+
/**
|
|
631
|
+
* Whether a list holds an item keyed `k` (#825) — the key a move finds its
|
|
632
|
+
* item by, so a row's list keeps its keys unique: a card whose item repeats
|
|
633
|
+
* one, or a move onto a row already holding the moved item's key, is
|
|
634
|
+
* refused rather than leaving two items one key names.
|
|
635
|
+
*/
|
|
636
|
+
const holds = move === undefined ? undefined : (list, k) => list.some((_$, item) => East.equal(item[move.key], k));
|
|
637
|
+
/**
|
|
638
|
+
* This series' own edit on one of its entries — the row at `path` is its
|
|
639
|
+
* own: a dropped card's item into its list, and a moved element's item
|
|
640
|
+
* re-timed where it is, taken out of the list, or put into it (#825).
|
|
641
|
+
*/
|
|
642
|
+
const ownFor = (kt) => owns.get(kt, () => {
|
|
643
|
+
if (edit === undefined)
|
|
644
|
+
return undefined;
|
|
645
|
+
const create = edit.create !== undefined ? createFor(kt, edit.create) : undefined;
|
|
646
|
+
return East.function([rowType, kt, PathType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, key, path, gesture, carried) => {
|
|
647
|
+
const make = create === undefined ? undefined : $.const(create);
|
|
648
|
+
const place = placed === undefined ? undefined : $.const(placed);
|
|
649
|
+
const result = $.let(none, OptionType(rowType));
|
|
650
|
+
$.match(gesture, {
|
|
651
|
+
drop: ($2, drop) => {
|
|
652
|
+
if (make === undefined || edit === undefined)
|
|
653
|
+
return;
|
|
654
|
+
const list = $2.let(entryAccess(value)[edit.items]);
|
|
655
|
+
const item = $2.let(make(drop, value, key), itemType);
|
|
656
|
+
const add = ($3) => {
|
|
657
|
+
$3(list.pushLast(item));
|
|
658
|
+
$3.assign(result, some(value));
|
|
659
|
+
};
|
|
660
|
+
// With the item's key declared, a card whose item
|
|
661
|
+
// repeats a key in the list is refused (#825).
|
|
662
|
+
if (holds === undefined || move === undefined)
|
|
663
|
+
add($2);
|
|
664
|
+
else
|
|
665
|
+
$2.if(holds(list, item[move.key]).not(), add);
|
|
666
|
+
},
|
|
667
|
+
move: ($2, mv) => {
|
|
668
|
+
if (place === undefined || move === undefined || edit === undefined)
|
|
669
|
+
return;
|
|
670
|
+
const keyField = move.key;
|
|
671
|
+
const itemOf = move.item;
|
|
672
|
+
const here = $2.let(East.value(variant("entry", { series: cfg.key, path }), PlanRowIdType), PlanRowIdType);
|
|
673
|
+
const list = $2.let(entryAccess(value)[edit.items]);
|
|
674
|
+
// The element's item, by its key.
|
|
675
|
+
const at = $2.let(list.firstMap(($3, item, i) => East.equal(item[keyField], mv.key).ifElse(() => East.value(some(i), OptionType(IntegerType)), () => East.value(none, OptionType(IntegerType)))), OptionType(IntegerType));
|
|
676
|
+
$2.if(East.equal(here, mv.from).and(() => East.equal(here, mv.to)), ($3) => {
|
|
677
|
+
// Along its row, or resized: its instants set where it is.
|
|
678
|
+
$3.match(at, {
|
|
679
|
+
some: ($4, i) => {
|
|
680
|
+
const moved = $4.let(place(list.get(i), mv.start, mv.end), OptionType(itemOf));
|
|
681
|
+
$4.match(moved, {
|
|
682
|
+
some: ($5, item) => {
|
|
683
|
+
$5(list.update(i, item));
|
|
684
|
+
$5.assign(result, some(value));
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
},
|
|
688
|
+
});
|
|
689
|
+
}).else(($3) => {
|
|
690
|
+
// Its source: taken out of the list, and carried.
|
|
691
|
+
$3.if(East.equal(here, mv.from), ($4) => {
|
|
692
|
+
$4.match(at, {
|
|
693
|
+
some: ($5, i) => {
|
|
694
|
+
$5(carried.update(East.value(some(East.Blob.encodeBeast(list.get(i), "v2")), OptionType(BlobType))));
|
|
695
|
+
const before = $5.let(list.slice(0n, i));
|
|
696
|
+
const after = $5.let(list.slice(i.add(1n), list.size()));
|
|
697
|
+
$5(list.clear());
|
|
698
|
+
$5(list.append(before));
|
|
699
|
+
$5(list.append(after));
|
|
700
|
+
$5.assign(result, some(value));
|
|
701
|
+
},
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
// Its target: the carried item put in, at its new
|
|
705
|
+
// instants — unless the list already holds its key,
|
|
706
|
+
// when it is put nowhere and the move lands nowhere.
|
|
707
|
+
$3.if(East.equal(here, mv.to), ($4) => {
|
|
708
|
+
const held = $4.let(carried.get(), OptionType(BlobType));
|
|
709
|
+
$4.match(held, {
|
|
710
|
+
some: ($5, bytes) => {
|
|
711
|
+
$5.if(holds(list, mv.key).not(), ($6) => {
|
|
712
|
+
const item = $6.let(bytes.decodeBeast(itemOf, "v2"), itemOf);
|
|
713
|
+
const moved = $6.let(place(item, mv.start, mv.end), OptionType(itemOf));
|
|
714
|
+
$6.match(moved, {
|
|
715
|
+
some: ($7, placedItem) => {
|
|
716
|
+
$7(list.pushLast(placedItem));
|
|
717
|
+
$7(carried.update(East.value(none, OptionType(BlobType))));
|
|
718
|
+
$7.assign(result, some(value));
|
|
719
|
+
},
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
return result;
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
/** The walk's writer — a bare `children` accessor: the rows below an entry are this series' own. */
|
|
732
|
+
const walkWriterFor = (kt) => {
|
|
733
|
+
const at = `${where} › children`;
|
|
734
|
+
const of = childOf(kt);
|
|
735
|
+
const cc = Expr.type(of).output;
|
|
736
|
+
const { key: kc } = shapeOf(cc, at);
|
|
737
|
+
const own = ownFor(kc);
|
|
738
|
+
if (own === undefined)
|
|
739
|
+
return undefined;
|
|
740
|
+
const ref = childRefOf(rowType, kt, children);
|
|
741
|
+
if (ref === undefined) {
|
|
742
|
+
throw new Error(`${at}: this series takes gestures, and one on a child row is written back into its entry through ` +
|
|
743
|
+
"`children` — which must read a field of the entry (`r => r.children`), not compute a collection");
|
|
744
|
+
}
|
|
745
|
+
const ops = collectionOps(cc);
|
|
746
|
+
const collOf = (entry) => (ref.kind === "field" ? entryAccess(entry)[ref.field] : nodeOf(entry));
|
|
747
|
+
return East.function([rowType, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, _key, path, depth, series, gesture, carried) => {
|
|
748
|
+
const result = $.let(none, OptionType(rowType));
|
|
749
|
+
// The walk's rows are this series' own, at every depth.
|
|
750
|
+
$.if(series.equal(cfg.key), ($2) => {
|
|
751
|
+
const write = $2.const(own);
|
|
752
|
+
const keyOf = $2.const(segmentKeyOf(kc));
|
|
753
|
+
const cur = $2.let(value, rowType);
|
|
754
|
+
const d = $2.let(depth, IntegerType);
|
|
755
|
+
const last = $2.let(path.size().subtract(1n), IntegerType);
|
|
756
|
+
// Down to the target's parent, then the target itself — a
|
|
757
|
+
// path through an entry the tree lacks writes nothing (#1219).
|
|
758
|
+
const found = $2.let(true, BooleanType);
|
|
759
|
+
$2.while(d.less(last).and(() => found), ($3) => {
|
|
760
|
+
const coll = $3.let(collOf(cur), cc);
|
|
761
|
+
const step = $3.let(keyOf(path.get(d)), kc);
|
|
762
|
+
$3.if(ops.has(coll, step), ($4) => {
|
|
763
|
+
$4.assign(cur, ops.get(coll, step));
|
|
764
|
+
$4.assign(d, d.add(1n));
|
|
765
|
+
}).else(($4) => { $4.assign(found, false); });
|
|
766
|
+
});
|
|
767
|
+
$2.if(found, ($3) => {
|
|
768
|
+
const coll = $3.let(collOf(cur), cc);
|
|
769
|
+
const k = $3.let(keyOf(path.get(last)), kc);
|
|
770
|
+
$3.if(ops.has(coll, k), ($4) => {
|
|
771
|
+
const written = $4.let(write(ops.get(coll, k), k, path, gesture, carried), OptionType(rowType));
|
|
772
|
+
$4.match(written, {
|
|
773
|
+
some: ($5, entry) => {
|
|
774
|
+
$5(ops.put(coll, k, entry));
|
|
775
|
+
$5.assign(result, some(value));
|
|
776
|
+
},
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
return result;
|
|
782
|
+
});
|
|
783
|
+
};
|
|
784
|
+
/** The step-downs' writer: a row below an entry is one of a step-down's series', in that step's collection. */
|
|
785
|
+
const stepsWriterFor = (kt) => {
|
|
786
|
+
const parts = steps.flatMap((step, i) => {
|
|
787
|
+
const at = `${where} › children[${i}]`;
|
|
788
|
+
const accessor = step.of;
|
|
789
|
+
const of = building(at, kt, () => reifyAccessor([rowType, kt], onEntry(accessor)));
|
|
790
|
+
const cc = Expr.type(of).output;
|
|
791
|
+
const writes = step.series
|
|
792
|
+
.map((s) => nestedSpec(s, at).writer(cc, at))
|
|
793
|
+
.filter((w) => w !== undefined);
|
|
794
|
+
if (writes.length === 0)
|
|
795
|
+
return [];
|
|
796
|
+
const ref = childRefOf(rowType, kt, accessor);
|
|
797
|
+
if (ref === undefined) {
|
|
798
|
+
throw new Error(`${at}: a series under this step-down takes gestures, and one on its rows is written back into ` +
|
|
799
|
+
"the entry through `Plan.children(of, …)` — `of` must read a field of the entry (`r => r.presses`) " +
|
|
800
|
+
"or be the entry itself (`g => g`), not compute a collection");
|
|
801
|
+
}
|
|
802
|
+
const { entry: childType, key: kc } = shapeOf(cc, at);
|
|
803
|
+
return [{ ref, cc, kc, childType, writes }];
|
|
804
|
+
});
|
|
805
|
+
if (parts.length === 0)
|
|
806
|
+
return undefined;
|
|
807
|
+
return East.function([rowType, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, _key, path, depth, series, gesture, carried) => {
|
|
808
|
+
const result = $.let(none, OptionType(rowType));
|
|
809
|
+
for (const part of parts) {
|
|
810
|
+
const ops = collectionOps(part.cc);
|
|
811
|
+
const keyOf = $.const(segmentKeyOf(part.kc));
|
|
812
|
+
const writes = part.writes.map((w) => $.const(w));
|
|
813
|
+
$.if(result.hasTag("none"), ($2) => {
|
|
814
|
+
const coll = $2.let(part.ref.kind === "field" ? entryAccess(value)[part.ref.field] : nodeOf(value), part.cc);
|
|
815
|
+
const k = $2.let(keyOf(path.get(depth)), part.kc);
|
|
816
|
+
$2.if(ops.has(coll, k), ($3) => {
|
|
817
|
+
const child = $3.let(ops.get(coll, k), part.childType);
|
|
818
|
+
for (const write of writes) {
|
|
819
|
+
$3.if(result.hasTag("none"), ($4) => {
|
|
820
|
+
const written = $4.let(write(child, k, path, depth.add(1n), series, gesture, carried), OptionType(part.childType));
|
|
821
|
+
$4.match(written, {
|
|
822
|
+
some: ($5, entry) => {
|
|
823
|
+
$5(ops.put(coll, k, entry));
|
|
824
|
+
$5.assign(result, some(value));
|
|
825
|
+
},
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
return result;
|
|
833
|
+
});
|
|
834
|
+
};
|
|
835
|
+
/** This series' writer for entries keyed by `kt`: its own rows' edits, and the rows below them. */
|
|
836
|
+
const writerFor = (kt) => writers.get(kt, () => {
|
|
837
|
+
const own = ownFor(kt);
|
|
838
|
+
const below = children === undefined ? undefined
|
|
839
|
+
: typeof children === "function" ? walkWriterFor(kt) : stepsWriterFor(kt);
|
|
840
|
+
if (own === undefined && below === undefined)
|
|
841
|
+
return undefined;
|
|
842
|
+
return East.function([rowType, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, key, path, depth, series, gesture, carried) => {
|
|
843
|
+
const result = $.let(none, OptionType(rowType));
|
|
844
|
+
$.if(depth.equal(path.size()), ($2) => {
|
|
845
|
+
if (own === undefined)
|
|
846
|
+
return;
|
|
847
|
+
const write = $2.const(own);
|
|
848
|
+
$2.if(series.equal(cfg.key), ($3) => { $3.assign(result, write(value, key, path, gesture, carried)); });
|
|
849
|
+
}).else(($2) => {
|
|
850
|
+
if (below === undefined)
|
|
851
|
+
return;
|
|
852
|
+
const write = $2.const(below);
|
|
853
|
+
$2.assign(result, write(value, key, path, depth, series, gesture, carried));
|
|
854
|
+
});
|
|
855
|
+
return result;
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
const spec = {
|
|
859
|
+
key: cfg.key,
|
|
860
|
+
title: cfg.title,
|
|
861
|
+
arm: recipe.arm,
|
|
862
|
+
nested,
|
|
863
|
+
nests: children !== undefined,
|
|
864
|
+
emitter(collection, at) {
|
|
865
|
+
return emitters.get(collection, () => {
|
|
866
|
+
const { entry, key: kt } = shapeOf(collection, at);
|
|
867
|
+
assertEntry(entry, rowType, where);
|
|
868
|
+
const row = rowFor(kt);
|
|
869
|
+
const match = matchFor(kt);
|
|
870
|
+
const collapsed = collapsedFor(kt);
|
|
871
|
+
const below = childrenFor(kt);
|
|
872
|
+
const seg = segmentOf(kt);
|
|
873
|
+
return East.function([collection, PathType, IdOptType], PlanRowsCollectionType, ($, coll, prefix, parent) => {
|
|
874
|
+
// Every function the loop calls, bound ONCE (see the walk).
|
|
875
|
+
const entryRow = $.const(row);
|
|
876
|
+
const member = match === undefined ? undefined : $.const(match);
|
|
877
|
+
const collapse = collapsed === undefined ? undefined : $.const(collapsed);
|
|
878
|
+
const childRows = below === undefined ? undefined : $.const(below);
|
|
879
|
+
const out = $.let([], PlanRowsCollectionType);
|
|
880
|
+
$.for(coll, ($2, value, key) => {
|
|
881
|
+
const emit = ($3) => {
|
|
882
|
+
const segs = $3.let(East.value([seg(key)], PathType), PathType);
|
|
883
|
+
const path = $3.let(prefix.concat(segs), PathType);
|
|
884
|
+
const id = $3.let(East.value(variant("entry", { series: cfg.key, path }), PlanRowIdType), PlanRowIdType);
|
|
885
|
+
const fold = $3.let(collapse === undefined
|
|
886
|
+
? East.value(false, BooleanType)
|
|
887
|
+
: collapse(value, key), BooleanType);
|
|
888
|
+
if (childRows === undefined) {
|
|
889
|
+
$3(out.pushLast(entryRow(value, key, id, parent, false, fold)));
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
// The children are built first: a row rolls up
|
|
893
|
+
// (span bands) only when it has child rows.
|
|
894
|
+
const sub = $3.let(childRows(value, key, id, path), PlanRowsCollectionType);
|
|
895
|
+
$3(out.pushLast(entryRow(value, key, id, parent, sub.size().greater(0n), fold)));
|
|
896
|
+
$3(out.append(sub));
|
|
897
|
+
};
|
|
898
|
+
if (member === undefined)
|
|
899
|
+
emit($2);
|
|
900
|
+
else
|
|
901
|
+
$2.if(member(value, key), emit);
|
|
902
|
+
});
|
|
903
|
+
return out;
|
|
904
|
+
});
|
|
905
|
+
});
|
|
906
|
+
},
|
|
907
|
+
// One block of the entries' rows — what a paged canvas pages (#823).
|
|
908
|
+
blocks(collection, at) {
|
|
909
|
+
return blockLists.get(collection, () => oneBlock(collection, spec.emitter(collection, at), false));
|
|
910
|
+
},
|
|
911
|
+
entryParts(entry, kt, at) {
|
|
912
|
+
assertEntry(entry, rowType, at);
|
|
913
|
+
return { match: matchFor(kt), row: rowFor(kt) };
|
|
914
|
+
},
|
|
915
|
+
childrenFor,
|
|
916
|
+
collapsedFor,
|
|
917
|
+
writer(collection, at) {
|
|
918
|
+
const { entry, key: kt } = shapeOf(collection, at);
|
|
919
|
+
assertEntry(entry, rowType, where);
|
|
920
|
+
return writerFor(kt);
|
|
921
|
+
},
|
|
922
|
+
ownFor,
|
|
923
|
+
};
|
|
924
|
+
return spec;
|
|
925
|
+
}
|
|
926
|
+
/** The build spec of a `views` series. */
|
|
927
|
+
function viewsSpec(rowType, cfg, members) {
|
|
928
|
+
const where = `Plan.series.views "${cfg.key}"`;
|
|
929
|
+
if (members.length === 0)
|
|
930
|
+
throw new Error(`${where}: a views series lists at least one member series`);
|
|
931
|
+
const memberSpecs = members.map((m, i) => {
|
|
932
|
+
const spec = nestedSpec(m, `${where}[${i}]`);
|
|
933
|
+
if (spec.entryParts === undefined || spec.arm === "group") {
|
|
934
|
+
throw new Error(`${where}: member ${nameOf(spec)} is not a row series — a view is one row per entry (span, buckets, chart, heat, table, cards or events)`);
|
|
935
|
+
}
|
|
936
|
+
if (spec.nests) {
|
|
937
|
+
throw new Error(`${where}: member ${nameOf(spec)} declares \`children\` — an entry's children belong to the views (they nest under its first view row); move \`children\` there`);
|
|
938
|
+
}
|
|
939
|
+
return spec;
|
|
940
|
+
});
|
|
941
|
+
// The views' own membership, collapse and children are a data series'
|
|
942
|
+
// envelope without a row of its own — built by the same machinery. A
|
|
943
|
+
// step-down's children are its own series' rows; a bare accessor's are more
|
|
944
|
+
// of the views' entries, which the views walk themselves below (#1219), so
|
|
945
|
+
// each child entry draws as its members' rows, never as the host's.
|
|
946
|
+
const host = dataSpec(rowType, {
|
|
947
|
+
key: cfg.key, title: cfg.title, label: () => "",
|
|
948
|
+
...(cfg.keyType !== undefined ? { keyType: cfg.keyType } : {}),
|
|
949
|
+
...(cfg.match !== undefined ? { match: cfg.match } : {}),
|
|
950
|
+
...(cfg.children !== undefined ? { children: cfg.children } : {}),
|
|
951
|
+
...(cfg.collapsed !== undefined ? { collapsed: cfg.collapsed } : {}),
|
|
952
|
+
}, { arm: "views", kind: () => groupKind(East.value(variant("none", null), PlanGroupSummaryType)) });
|
|
953
|
+
const emitters = new ByType();
|
|
954
|
+
const blockLists = new ByType();
|
|
955
|
+
const writerLists = new ByType();
|
|
956
|
+
// ── A bare `children` accessor: more of the views' own entries (#1219) ──
|
|
957
|
+
const bare = typeof cfg.children === "function"
|
|
958
|
+
? cfg.children
|
|
959
|
+
: undefined;
|
|
960
|
+
const recursive = rowType.type === "Recursive";
|
|
961
|
+
const nodeOf = (value) => recursive
|
|
962
|
+
? value.unwrap()
|
|
963
|
+
: value;
|
|
964
|
+
const entryAccess = (value) => nodeOf(value);
|
|
965
|
+
const childOfs = new ByType();
|
|
966
|
+
/** The bare accessor, built for entries keyed by `kt` — the child collection's type is its output. */
|
|
967
|
+
const childOf = (kt) => childOfs.get(kt, () => building(where, kt, () => reifyAccessor([rowType, kt], (v, k) => bare(nodeOf(v), k))));
|
|
968
|
+
const walks = new ByType();
|
|
969
|
+
/**
|
|
970
|
+
* The subtree below one entry, over the child collection type `cc`: every
|
|
971
|
+
* level's matched entries in pre-order, each drawn as its members' rows —
|
|
972
|
+
* adjacent, in declared order, those whose own `match` takes it — with its
|
|
973
|
+
* children after all of them, nested under the first that shows.
|
|
974
|
+
*/
|
|
975
|
+
const walkFor = (cc) => walks.get(cc, () => {
|
|
976
|
+
const at = `${where} › children`;
|
|
977
|
+
const { entry, key: kc } = shapeOf(cc, at);
|
|
978
|
+
assertEntry(entry, rowType, `${at} (a bare accessor returns more of this series' entries — step down to another type with Plan.children)`);
|
|
979
|
+
const next = childOf(kc);
|
|
980
|
+
const nextType = Expr.type(next).output;
|
|
981
|
+
if (!isTypeEqual(nextType, cc)) {
|
|
982
|
+
throw new Error(`${at}: a recursive series' children must be the same collection type at every level`);
|
|
983
|
+
}
|
|
984
|
+
const parts = memberSpecs.map((m) => m.entryParts(entry, kc, `${where} › ${nameOf(m)}`));
|
|
985
|
+
const match = host.entryParts(entry, kc, where).match;
|
|
986
|
+
const collapsed = host.collapsedFor(kc);
|
|
987
|
+
const seg = segmentOf(kc);
|
|
988
|
+
const Frame = StructType({ value: rowType, key: kc, path: PathType, parent: PlanRowIdType });
|
|
989
|
+
const Frames = ArrayType(Frame);
|
|
990
|
+
// A level's matched entries as frames, REVERSED — so the stack
|
|
991
|
+
// pops the first of them first.
|
|
992
|
+
const framesOf = East.function([cc, PathType, PlanRowIdType], Frames, ($, coll, path, parentId) => {
|
|
993
|
+
const frames = $.let([], Frames);
|
|
994
|
+
const member = match === undefined ? undefined : $.const(match);
|
|
995
|
+
$.for(coll, ($2, value, key) => {
|
|
996
|
+
const add = ($3) => {
|
|
997
|
+
const segs = $3.let(East.value([seg(key)], PathType), PathType);
|
|
998
|
+
$3(frames.pushLast(East.value({ value, key, path: path.concat(segs), parent: parentId }, Frame)));
|
|
999
|
+
};
|
|
1000
|
+
if (member === undefined)
|
|
1001
|
+
add($2);
|
|
1002
|
+
else
|
|
1003
|
+
$2.if(member(value, key), add);
|
|
1004
|
+
});
|
|
1005
|
+
$(frames.reverseInPlace());
|
|
1006
|
+
return frames;
|
|
1007
|
+
});
|
|
1008
|
+
return East.function([cc, PathType, PlanRowIdType], PlanRowsCollectionType, ($, coll, path0, parent0) => {
|
|
1009
|
+
// Every function the loop calls, bound ONCE (see the data walk).
|
|
1010
|
+
const frames = $.const(framesOf);
|
|
1011
|
+
const childrenOf = $.const(next);
|
|
1012
|
+
const rowsOf = parts.map((p) => $.const(p.row));
|
|
1013
|
+
const showsOf = parts.map((p) => (p.match === undefined ? undefined : $.const(p.match)));
|
|
1014
|
+
const collapse = collapsed === undefined ? undefined : $.const(collapsed);
|
|
1015
|
+
const out = $.let([], PlanRowsCollectionType);
|
|
1016
|
+
const stack = $.let(frames(coll, path0, parent0), Frames);
|
|
1017
|
+
$.while(stack.size().greater(0n), ($2) => {
|
|
1018
|
+
const f = $2.let(stack.popLast(), Frame);
|
|
1019
|
+
const ids = memberSpecs.map((m) => $2.let(East.value(variant("entry", { series: m.key, path: f.path }), PlanRowIdType), PlanRowIdType));
|
|
1020
|
+
const present = showsOf.map((shows) => (shows === undefined
|
|
1021
|
+
? undefined
|
|
1022
|
+
: $2.let(shows(f.value, f.key), BooleanType)));
|
|
1023
|
+
// The first member whose row shows — the entry's children nest under it.
|
|
1024
|
+
const first = $2.let(-1n, IntegerType);
|
|
1025
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
1026
|
+
const shows = present[i];
|
|
1027
|
+
if (shows === undefined)
|
|
1028
|
+
$2.assign(first, BigInt(i));
|
|
1029
|
+
else
|
|
1030
|
+
$2.if(shows, ($3) => { $3.assign(first, BigInt(i)); });
|
|
1031
|
+
}
|
|
1032
|
+
const firstId = $2.let(ids[0], PlanRowIdType);
|
|
1033
|
+
for (let i = 1; i < ids.length; i++) {
|
|
1034
|
+
$2.if(first.equal(BigInt(i)), ($3) => { $3.assign(firstId, ids[i]); });
|
|
1035
|
+
}
|
|
1036
|
+
const below = $2.let(first.greaterEqual(0n).ifElse(() => frames(childrenOf(f.value, f.key), f.path, firstId), () => East.value([], Frames)), Frames);
|
|
1037
|
+
const parent = $2.let(East.value(some(f.parent), IdOptType), IdOptType);
|
|
1038
|
+
const fold = $2.let(collapse === undefined
|
|
1039
|
+
? East.value(false, BooleanType)
|
|
1040
|
+
: collapse(f.value, f.key), BooleanType);
|
|
1041
|
+
const noFold = $2.let(East.value(false, BooleanType), BooleanType);
|
|
1042
|
+
parts.forEach((_p, i) => {
|
|
1043
|
+
const isFirst = $2.let(first.equal(BigInt(i)), BooleanType);
|
|
1044
|
+
const hasChildren = $2.let(isFirst.and(() => below.size().greater(0n)), BooleanType);
|
|
1045
|
+
const viewRow = rowsOf[i];
|
|
1046
|
+
const push = ($3) => {
|
|
1047
|
+
$3(out.pushLast(viewRow(f.value, f.key, ids[i], parent, hasChildren, isFirst.ifElse(() => fold, () => noFold))));
|
|
1048
|
+
};
|
|
1049
|
+
const shows = present[i];
|
|
1050
|
+
if (shows === undefined)
|
|
1051
|
+
push($2);
|
|
1052
|
+
else
|
|
1053
|
+
$2.if(shows, push);
|
|
1054
|
+
});
|
|
1055
|
+
$2(stack.append(below));
|
|
1056
|
+
});
|
|
1057
|
+
return out;
|
|
1058
|
+
});
|
|
1059
|
+
});
|
|
1060
|
+
/** The entry's children rows for entries keyed by `kt`: a step-down's through the host, a bare accessor's walked here. */
|
|
1061
|
+
const childrenFor = (kt) => {
|
|
1062
|
+
if (bare === undefined)
|
|
1063
|
+
return host.childrenFor(kt);
|
|
1064
|
+
const of = childOf(kt);
|
|
1065
|
+
const walk = walkFor(Expr.type(of).output);
|
|
1066
|
+
return East.function([rowType, kt, PlanRowIdType, PathType], PlanRowsCollectionType, (_$, value, key, id, path) => walk(of(value, key), path, id));
|
|
1067
|
+
};
|
|
1068
|
+
const walkWriters = new ByType();
|
|
1069
|
+
/**
|
|
1070
|
+
* The writer below an entry, through a bare accessor: down the path to the
|
|
1071
|
+
* entry the row is one of, through the field `children` reads, and the
|
|
1072
|
+
* gesture written by the member whose row it was made on.
|
|
1073
|
+
*/
|
|
1074
|
+
const walkWriterFor = (kt) => walkWriters.get(kt, () => {
|
|
1075
|
+
const at = `${where} › children`;
|
|
1076
|
+
const of = childOf(kt);
|
|
1077
|
+
const cc = Expr.type(of).output;
|
|
1078
|
+
const { entry, key: kc } = shapeOf(cc, at);
|
|
1079
|
+
assertEntry(entry, rowType, at);
|
|
1080
|
+
const owns = memberSpecs.flatMap((m) => {
|
|
1081
|
+
const own = m.ownFor?.(kc);
|
|
1082
|
+
return own === undefined ? [] : [{ key: m.key, own }];
|
|
1083
|
+
});
|
|
1084
|
+
if (owns.length === 0)
|
|
1085
|
+
return undefined;
|
|
1086
|
+
const ref = childRefOf(rowType, kt, bare);
|
|
1087
|
+
if (ref === undefined) {
|
|
1088
|
+
throw new Error(`${at}: a member takes gestures, and one on a child row is written back into its entry through ` +
|
|
1089
|
+
"`children` — which must read a field of the entry (`r => r.children`), not compute a collection");
|
|
1090
|
+
}
|
|
1091
|
+
const ops = collectionOps(cc);
|
|
1092
|
+
const collOf = (value) => (ref.kind === "field" ? entryAccess(value)[ref.field] : nodeOf(value));
|
|
1093
|
+
return East.function([rowType, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, _key, path, depth, series, gesture, carried) => {
|
|
1094
|
+
const result = $.let(none, OptionType(rowType));
|
|
1095
|
+
const writes = owns.map((o) => ({ key: o.key, own: $.const(o.own) }));
|
|
1096
|
+
const keyOf = $.const(segmentKeyOf(kc));
|
|
1097
|
+
const cur = $.let(value, rowType);
|
|
1098
|
+
const d = $.let(depth, IntegerType);
|
|
1099
|
+
const last = $.let(path.size().subtract(1n), IntegerType);
|
|
1100
|
+
// Down to the target's parent, then the target itself — a path
|
|
1101
|
+
// through an entry the tree lacks writes nothing.
|
|
1102
|
+
const found = $.let(true, BooleanType);
|
|
1103
|
+
$.while(d.less(last).and(() => found), ($2) => {
|
|
1104
|
+
const coll = $2.let(collOf(cur), cc);
|
|
1105
|
+
const step = $2.let(keyOf(path.get(d)), kc);
|
|
1106
|
+
$2.if(ops.has(coll, step), ($3) => {
|
|
1107
|
+
$3.assign(cur, ops.get(coll, step));
|
|
1108
|
+
$3.assign(d, d.add(1n));
|
|
1109
|
+
}).else(($3) => { $3.assign(found, false); });
|
|
1110
|
+
});
|
|
1111
|
+
$.if(found, ($2) => {
|
|
1112
|
+
const coll = $2.let(collOf(cur), cc);
|
|
1113
|
+
const k = $2.let(keyOf(path.get(last)), kc);
|
|
1114
|
+
$2.if(ops.has(coll, k), ($3) => {
|
|
1115
|
+
const target = $3.let(ops.get(coll, k), rowType);
|
|
1116
|
+
for (const w of writes) {
|
|
1117
|
+
$3.if(result.hasTag("none").and(() => series.equal(w.key)), ($4) => {
|
|
1118
|
+
const written = $4.let(w.own(target, k, path, gesture, carried), OptionType(rowType));
|
|
1119
|
+
$4.match(written, {
|
|
1120
|
+
some: ($5, next) => {
|
|
1121
|
+
$5(ops.put(coll, k, next));
|
|
1122
|
+
$5.assign(result, some(value));
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
});
|
|
1129
|
+
return result;
|
|
1130
|
+
});
|
|
1131
|
+
});
|
|
1132
|
+
const spec = {
|
|
1133
|
+
key: cfg.key,
|
|
1134
|
+
title: cfg.title,
|
|
1135
|
+
arm: "views",
|
|
1136
|
+
nested: [...memberSpecs, ...host.nested],
|
|
1137
|
+
nests: cfg.children !== undefined,
|
|
1138
|
+
emitter(collection, at) {
|
|
1139
|
+
return emitters.get(collection, () => {
|
|
1140
|
+
const { entry, key: kt } = shapeOf(collection, at);
|
|
1141
|
+
assertEntry(entry, rowType, where);
|
|
1142
|
+
const parts = memberSpecs.map((m) => m.entryParts(entry, kt, `${where} › ${nameOf(m)}`));
|
|
1143
|
+
const hostParts = host.entryParts(entry, kt, where);
|
|
1144
|
+
const hostKids = childrenFor(kt);
|
|
1145
|
+
const hostCollapse = host.collapsedFor(kt);
|
|
1146
|
+
const seg = segmentOf(kt);
|
|
1147
|
+
return East.function([collection, PathType, IdOptType], PlanRowsCollectionType, ($, coll, prefix, parent) => {
|
|
1148
|
+
// Every function the loop calls, bound ONCE (see the data walk).
|
|
1149
|
+
const rowsOf = parts.map((p) => $.const(p.row));
|
|
1150
|
+
const showsOf = parts.map((p) => (p.match === undefined ? undefined : $.const(p.match)));
|
|
1151
|
+
const member = hostParts.match === undefined ? undefined : $.const(hostParts.match);
|
|
1152
|
+
const childRows = hostKids === undefined ? undefined : $.const(hostKids);
|
|
1153
|
+
const collapse = hostCollapse === undefined ? undefined : $.const(hostCollapse);
|
|
1154
|
+
const out = $.let([], PlanRowsCollectionType);
|
|
1155
|
+
$.for(coll, ($2, value, key) => {
|
|
1156
|
+
const emit = ($3) => {
|
|
1157
|
+
const segs = $3.let(East.value([seg(key)], PathType), PathType);
|
|
1158
|
+
const path = $3.let(prefix.concat(segs), PathType);
|
|
1159
|
+
const ids = memberSpecs.map((m) => $3.let(East.value(variant("entry", { series: m.key, path }), PlanRowIdType), PlanRowIdType));
|
|
1160
|
+
const present = showsOf.map((shows) => (shows === undefined
|
|
1161
|
+
? undefined
|
|
1162
|
+
: $3.let(shows(value, key), BooleanType)));
|
|
1163
|
+
// The first member whose row shows — the entry's
|
|
1164
|
+
// children nest under it.
|
|
1165
|
+
const first = $3.let(-1n, IntegerType);
|
|
1166
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
1167
|
+
const shows = present[i];
|
|
1168
|
+
if (shows === undefined)
|
|
1169
|
+
$3.assign(first, BigInt(i));
|
|
1170
|
+
else
|
|
1171
|
+
$3.if(shows, ($4) => { $4.assign(first, BigInt(i)); });
|
|
1172
|
+
}
|
|
1173
|
+
let sub;
|
|
1174
|
+
if (childRows !== undefined) {
|
|
1175
|
+
const firstId = $3.let(ids[0], PlanRowIdType);
|
|
1176
|
+
for (let i = 1; i < ids.length; i++) {
|
|
1177
|
+
$3.if(first.equal(BigInt(i)), ($4) => { $4.assign(firstId, ids[i]); });
|
|
1178
|
+
}
|
|
1179
|
+
sub = $3.let(first.greaterEqual(0n).ifElse(() => childRows(value, key, firstId, path), () => East.value([], PlanRowsCollectionType)), PlanRowsCollectionType);
|
|
1180
|
+
}
|
|
1181
|
+
const fold = $3.let(collapse === undefined
|
|
1182
|
+
? East.value(false, BooleanType)
|
|
1183
|
+
: collapse(value, key), BooleanType);
|
|
1184
|
+
const noFold = $3.let(East.value(false, BooleanType), BooleanType);
|
|
1185
|
+
parts.forEach((_p, i) => {
|
|
1186
|
+
const isFirst = $3.let(first.equal(BigInt(i)), BooleanType);
|
|
1187
|
+
const kids = sub;
|
|
1188
|
+
const hasChildren = $3.let(kids === undefined
|
|
1189
|
+
? East.value(false, BooleanType)
|
|
1190
|
+
: isFirst.and(() => kids.size().greater(0n)), BooleanType);
|
|
1191
|
+
const viewRow = rowsOf[i];
|
|
1192
|
+
const push = ($4) => {
|
|
1193
|
+
$4(out.pushLast(viewRow(value, key, ids[i], parent, hasChildren, isFirst.ifElse(() => fold, () => noFold))));
|
|
1194
|
+
};
|
|
1195
|
+
const shows = present[i];
|
|
1196
|
+
if (shows === undefined)
|
|
1197
|
+
push($3);
|
|
1198
|
+
else
|
|
1199
|
+
$3.if(shows, push);
|
|
1200
|
+
});
|
|
1201
|
+
if (sub !== undefined)
|
|
1202
|
+
$3(out.append(sub));
|
|
1203
|
+
};
|
|
1204
|
+
if (member === undefined)
|
|
1205
|
+
emit($2);
|
|
1206
|
+
else
|
|
1207
|
+
$2.if(member(value, key), emit);
|
|
1208
|
+
});
|
|
1209
|
+
return out;
|
|
1210
|
+
});
|
|
1211
|
+
});
|
|
1212
|
+
},
|
|
1213
|
+
// One block of the entries' view rows — what a paged canvas pages (#823).
|
|
1214
|
+
blocks(collection, at) {
|
|
1215
|
+
return blockLists.get(collection, () => oneBlock(collection, spec.emitter(collection, at), false));
|
|
1216
|
+
},
|
|
1217
|
+
// An entry's view rows are its members' own (each at the entry's path,
|
|
1218
|
+
// under its member's key), and the rows below them the views' (#880).
|
|
1219
|
+
writer(collection, at) {
|
|
1220
|
+
return writerLists.get(collection, () => {
|
|
1221
|
+
const { entry, key: kt } = shapeOf(collection, at);
|
|
1222
|
+
assertEntry(entry, rowType, where);
|
|
1223
|
+
const owns = memberSpecs.flatMap((m) => {
|
|
1224
|
+
const own = m.ownFor?.(kt);
|
|
1225
|
+
return own === undefined ? [] : [{ key: m.key, own }];
|
|
1226
|
+
});
|
|
1227
|
+
// Below an entry: a step-down's rows are written by its series
|
|
1228
|
+
// through the host; a bare accessor's are the members' own (#1219).
|
|
1229
|
+
const below = bare === undefined ? host.writer(collection, at) : walkWriterFor(kt);
|
|
1230
|
+
if (owns.length === 0 && below === undefined)
|
|
1231
|
+
return undefined;
|
|
1232
|
+
return East.function([rowType, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, value, key, path, depth, series, gesture, carried) => {
|
|
1233
|
+
const result = $.let(none, OptionType(rowType));
|
|
1234
|
+
$.if(depth.equal(path.size()), ($2) => {
|
|
1235
|
+
for (const o of owns) {
|
|
1236
|
+
const write = $2.const(o.own);
|
|
1237
|
+
$2.if(series.equal(o.key), ($3) => { $3.assign(result, write(value, key, path, gesture, carried)); });
|
|
1238
|
+
}
|
|
1239
|
+
}).else(($2) => {
|
|
1240
|
+
if (below === undefined)
|
|
1241
|
+
return;
|
|
1242
|
+
const write = $2.const(below);
|
|
1243
|
+
$2.assign(result, write(value, key, path, depth, series, gesture, carried));
|
|
1244
|
+
});
|
|
1245
|
+
return result;
|
|
1246
|
+
});
|
|
1247
|
+
});
|
|
1248
|
+
},
|
|
1249
|
+
};
|
|
1250
|
+
return spec;
|
|
1251
|
+
}
|
|
1252
|
+
/** The build spec of a `section`. */
|
|
1253
|
+
function sectionSpec(cfg, members) {
|
|
1254
|
+
const where = `Plan.series.section "${cfg.key}"`;
|
|
1255
|
+
const memberSpecs = members.map((m, i) => nestedSpec(m, `${where}[${i}]`));
|
|
1256
|
+
const summary = groupSummary(cfg.summary, cfg.summaryAggregate, where);
|
|
1257
|
+
/** The header row — at `id`, nested under `parent`. */
|
|
1258
|
+
const header = (id, parent) => planRow({
|
|
1259
|
+
id, parent,
|
|
1260
|
+
gutter: planGutter({
|
|
1261
|
+
label: cfg.title,
|
|
1262
|
+
...(cfg.meta !== undefined ? { meta: some(cfg.meta) } : {}),
|
|
1263
|
+
...(cfg.value !== undefined ? { value: some(cfg.value) } : {}),
|
|
1264
|
+
}),
|
|
1265
|
+
kind: groupKind(summary),
|
|
1266
|
+
collapsed: cfg.collapsed ?? false,
|
|
1267
|
+
...(cfg.status !== undefined ? { status: some(resolveTag(cfg.status, StatusValueType)) } : {}),
|
|
1268
|
+
});
|
|
1269
|
+
const emitters = new ByType();
|
|
1270
|
+
const blockLists = new ByType();
|
|
1271
|
+
const writerLists = new ByType();
|
|
1272
|
+
return {
|
|
1273
|
+
key: cfg.key,
|
|
1274
|
+
title: cfg.title,
|
|
1275
|
+
arm: "section",
|
|
1276
|
+
nested: memberSpecs,
|
|
1277
|
+
nests: false,
|
|
1278
|
+
emitter(collection, at) {
|
|
1279
|
+
return emitters.get(collection, () => {
|
|
1280
|
+
const blocks = memberSpecs.map((m) => m.emitter(collection, `${at} › ${where}`));
|
|
1281
|
+
return East.function([collection, PathType, IdOptType], PlanRowsCollectionType, ($, coll, prefix, parent) => {
|
|
1282
|
+
const id = $.let(East.value(variant("section", { series: cfg.key, path: prefix }), PlanRowIdType), PlanRowIdType);
|
|
1283
|
+
const under = $.let(East.value(some(id), IdOptType), IdOptType);
|
|
1284
|
+
const out = $.let(East.value([header(id, parent)], PlanRowsCollectionType), PlanRowsCollectionType);
|
|
1285
|
+
for (const block of blocks)
|
|
1286
|
+
$(out.append(block(coll, prefix, under)));
|
|
1287
|
+
return out;
|
|
1288
|
+
});
|
|
1289
|
+
});
|
|
1290
|
+
},
|
|
1291
|
+
// The header is a FIXED block — no entry produces it, so every window
|
|
1292
|
+
// serves it alike and the canvas draws it once — and each member lays
|
|
1293
|
+
// out as its own blocks under it, so the series in a section page on
|
|
1294
|
+
// their own (#823).
|
|
1295
|
+
blocks(collection, at) {
|
|
1296
|
+
return blockLists.get(collection, () => {
|
|
1297
|
+
const members = memberSpecs.map((m) => m.blocks(collection, `${at} › ${where}`));
|
|
1298
|
+
return East.function([collection, PathType, IdOptType], PlanBlocksType, ($, coll, prefix, parent) => {
|
|
1299
|
+
const id = $.let(East.value(variant("section", { series: cfg.key, path: prefix }), PlanRowIdType), PlanRowIdType);
|
|
1300
|
+
const under = $.let(East.value(some(id), IdOptType), IdOptType);
|
|
1301
|
+
const head = $.let(East.value({ fixed: true, parent, rows: [header(id, parent)] }, PlanBlockType), PlanBlockType);
|
|
1302
|
+
const out = $.let(East.value([head], PlanBlocksType), PlanBlocksType);
|
|
1303
|
+
for (const member of members)
|
|
1304
|
+
$(out.append(member(coll, prefix, under)));
|
|
1305
|
+
return out;
|
|
1306
|
+
});
|
|
1307
|
+
});
|
|
1308
|
+
},
|
|
1309
|
+
// A section adds no path segment, so an entry's rows under it are its
|
|
1310
|
+
// members' own: the first member that takes the gesture writes it
|
|
1311
|
+
// (#880). The header is written by none.
|
|
1312
|
+
writer(collection, at) {
|
|
1313
|
+
return writerLists.get(collection, () => {
|
|
1314
|
+
const writes = memberSpecs
|
|
1315
|
+
.map((m) => m.writer(collection, `${at} › ${where}`))
|
|
1316
|
+
.filter((w) => w !== undefined);
|
|
1317
|
+
if (writes.length === 0)
|
|
1318
|
+
return undefined;
|
|
1319
|
+
const { entry, key: kt } = shapeOf(collection, at);
|
|
1320
|
+
return East.function([entry, kt, PathType, IntegerType, StringType, PlanGestureType, PlanCarriedType], OptionType(entry), ($, value, key, path, depth, series, gesture, carried) => {
|
|
1321
|
+
const result = $.let(none, OptionType(entry));
|
|
1322
|
+
for (const w of writes) {
|
|
1323
|
+
const write = $.const(w);
|
|
1324
|
+
$.if(result.hasTag("none"), ($2) => {
|
|
1325
|
+
$2.assign(result, write(value, key, path, depth, series, gesture, carried));
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
return result;
|
|
1329
|
+
});
|
|
1330
|
+
});
|
|
1331
|
+
},
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
/** The build spec of a `rows` series — hand-built rows, named and placed. */
|
|
1335
|
+
function rowsSpec(identity, rows) {
|
|
1336
|
+
const emitters = new ByType();
|
|
1337
|
+
const blockLists = new ByType();
|
|
1338
|
+
const emitter = (collection) => emitters.get(collection, () => East.function([collection, PathType, IdOptType], PlanRowsCollectionType, (_$, _coll, prefix, parent) => REBASE_ROWS(rows, identity.key, prefix, parent)));
|
|
1339
|
+
return {
|
|
1340
|
+
key: identity.key,
|
|
1341
|
+
title: identity.title,
|
|
1342
|
+
arm: "rows",
|
|
1343
|
+
nested: [],
|
|
1344
|
+
nests: false,
|
|
1345
|
+
emitter,
|
|
1346
|
+
// A FIXED block: no entry produces hand-built rows, so every window
|
|
1347
|
+
// serves them alike and the canvas draws them once (#823).
|
|
1348
|
+
blocks(collection) {
|
|
1349
|
+
return blockLists.get(collection, () => oneBlock(collection, emitter(collection), true));
|
|
1350
|
+
},
|
|
1351
|
+
// No entry produces them, so no gesture is written into one (#880).
|
|
1352
|
+
writer() {
|
|
1353
|
+
return undefined;
|
|
1354
|
+
},
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
// ============================================================================
|
|
1358
|
+
// Keys — unique across the whole series tree
|
|
1359
|
+
// ============================================================================
|
|
1360
|
+
/**
|
|
1361
|
+
* Refuse two series sharing a key anywhere in a series tree — a row's id is
|
|
1362
|
+
* its series key and its path, so a repeated key would give two rows one id.
|
|
1363
|
+
*
|
|
1364
|
+
* @param specs - The series, in order
|
|
1365
|
+
* @param where - Who is asking, for the message
|
|
1366
|
+
* @throws {Error} Naming both series and where each sits
|
|
1367
|
+
*/
|
|
1368
|
+
function assertUniqueKeys(specs, where) {
|
|
1369
|
+
const seen = new Map();
|
|
1370
|
+
const walk = (list, trail) => {
|
|
1371
|
+
list.forEach((spec, i) => {
|
|
1372
|
+
const site = `${trail}[${i}] ${nameOf(spec)}`;
|
|
1373
|
+
const prior = seen.get(spec.key);
|
|
1374
|
+
if (prior !== undefined) {
|
|
1375
|
+
throw new Error(`${where}: two series share the key "${spec.key}" — ${prior} and ${site}. A row's id is its ` +
|
|
1376
|
+
"series key and its path, so every series key must be unique across the whole series tree.");
|
|
1377
|
+
}
|
|
1378
|
+
seen.set(spec.key, site);
|
|
1379
|
+
walk(spec.nested, `${site} ›`);
|
|
1380
|
+
});
|
|
1381
|
+
};
|
|
1382
|
+
walk(specs, "series");
|
|
1383
|
+
}
|
|
1384
|
+
// ============================================================================
|
|
1385
|
+
// Series values — the East value, its spec beside it
|
|
1386
|
+
// ============================================================================
|
|
1387
|
+
/** Each spec's top-level writes, by collection type. */
|
|
1388
|
+
const topWrites = new WeakMap();
|
|
1389
|
+
/**
|
|
1390
|
+
* A series' write at the top of a canvas over `collection` (#880) — what the
|
|
1391
|
+
* series value carries as `write`: a gesture on a row of one entry, written
|
|
1392
|
+
* into a DETACHED copy of the entry (a nested entry in place in its
|
|
1393
|
+
* collection); `none` when the row is not the entry's, or no series in the
|
|
1394
|
+
* tree takes the gesture on it. A move to another row carries its item
|
|
1395
|
+
* between its two writes in `carried` (#825).
|
|
1396
|
+
*
|
|
1397
|
+
* @param spec - The series' build spec
|
|
1398
|
+
* @param collection - The source's collection type
|
|
1399
|
+
* @param where - Who is asking, for messages
|
|
1400
|
+
* @returns `(entry, key, row, gesture, carried) → the entry written`
|
|
1401
|
+
*/
|
|
1402
|
+
function topWrite(spec, collection, where) {
|
|
1403
|
+
let cache = topWrites.get(spec);
|
|
1404
|
+
if (cache === undefined) {
|
|
1405
|
+
cache = new ByType();
|
|
1406
|
+
topWrites.set(spec, cache);
|
|
1407
|
+
}
|
|
1408
|
+
return cache.get(collection, () => {
|
|
1409
|
+
const { entry: rowType, key: kt } = shapeOf(collection, where);
|
|
1410
|
+
const writer = spec.writer(collection, where);
|
|
1411
|
+
const seg = segmentOf(kt);
|
|
1412
|
+
return East.function([rowType, kt, PlanRowIdType, PlanGestureType, PlanCarriedType], OptionType(rowType), ($, entry, key, row, gesture, carried) => {
|
|
1413
|
+
const result = $.let(none, OptionType(rowType));
|
|
1414
|
+
if (writer === undefined)
|
|
1415
|
+
return result;
|
|
1416
|
+
const write = $.const(writer);
|
|
1417
|
+
$.match(row, {
|
|
1418
|
+
entry: ($2, id) => {
|
|
1419
|
+
// A row's path starts with its entry's key.
|
|
1420
|
+
const mine = $2.let(id.path.size().greater(0n).and(() => id.path.get(0n).equal(seg(key))), BooleanType);
|
|
1421
|
+
$2.if(mine, ($3) => {
|
|
1422
|
+
const detached = $3.let(East.Blob.encodeBeast(entry, "v2").decodeBeast(rowType, "v2"), rowType);
|
|
1423
|
+
$3.assign(result, write(detached, key, id.path, 1n, id.series, gesture, carried));
|
|
1424
|
+
});
|
|
1425
|
+
},
|
|
1426
|
+
});
|
|
1427
|
+
return result;
|
|
1428
|
+
});
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
/** Pin a series value against the instantiated series type, recording its spec. */
|
|
1432
|
+
function seriesValue(rowType, keyType, spec, identity) {
|
|
1433
|
+
assertUniqueKeys([spec], `Plan.series.${spec.arm} "${spec.key}"`);
|
|
1434
|
+
const kt = keyType ?? StringType;
|
|
1435
|
+
const source = DictType(kt, rowType);
|
|
1436
|
+
const where = `Plan.series.${spec.arm} "${spec.key}"`;
|
|
1437
|
+
const blocks = spec.blocks(source, where);
|
|
1438
|
+
const derive = East.function([source], PlanBlocksType, ($, data) => {
|
|
1439
|
+
const top = $.let(East.value([], PathType), PathType);
|
|
1440
|
+
return blocks(data, top, East.value(none, IdOptType));
|
|
1441
|
+
});
|
|
1442
|
+
const value = East.value(variant(spec.arm, {
|
|
1443
|
+
key: identity.key,
|
|
1444
|
+
title: identity.title,
|
|
1445
|
+
subtitle: identity.subtitle !== undefined ? some(identity.subtitle) : none,
|
|
1446
|
+
icon: identity.icon !== undefined ? some(resolveIcon(identity.icon)) : none,
|
|
1447
|
+
derive,
|
|
1448
|
+
write: topWrite(spec, source, where),
|
|
1449
|
+
}), PlanSeriesType(rowType, kt));
|
|
1450
|
+
SPECS.set(value, spec);
|
|
1451
|
+
return value;
|
|
1452
|
+
}
|
|
1453
|
+
/** Apply one series value to a source (the exhaustive-arm call) — its blocks. */
|
|
1454
|
+
export function applySeriesValue(s, data) {
|
|
1455
|
+
const d = data;
|
|
1456
|
+
return s.match({
|
|
1457
|
+
span: (_$, v) => v.derive(d),
|
|
1458
|
+
buckets: (_$, v) => v.derive(d),
|
|
1459
|
+
chart: (_$, v) => v.derive(d),
|
|
1460
|
+
heat: (_$, v) => v.derive(d),
|
|
1461
|
+
table: (_$, v) => v.derive(d),
|
|
1462
|
+
cards: (_$, v) => v.derive(d),
|
|
1463
|
+
events: (_$, v) => v.derive(d),
|
|
1464
|
+
group: (_$, v) => v.derive(d),
|
|
1465
|
+
section: (_$, v) => v.derive(d),
|
|
1466
|
+
views: (_$, v) => v.derive(d),
|
|
1467
|
+
rows: (_$, v) => v.derive(d),
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Check a TS series list before it is applied — its keys unique across the
|
|
1472
|
+
* whole tree. An East expression list is checked at render (a repeated id is
|
|
1473
|
+
* a row diagnostic), since its series are not known until it runs.
|
|
1474
|
+
*
|
|
1475
|
+
* @param series - The `series` input
|
|
1476
|
+
* @param where - Who is asking, for the message
|
|
1477
|
+
*/
|
|
1478
|
+
export function checkSeries(series, where) {
|
|
1479
|
+
if (!Array.isArray(series))
|
|
1480
|
+
return;
|
|
1481
|
+
const specs = series.map((s) => specOf(s)).filter((s) => s !== undefined);
|
|
1482
|
+
assertUniqueKeys(specs, where);
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Reads a series value's facts off the spec its builder recorded (#1190).
|
|
1486
|
+
*
|
|
1487
|
+
* @param series - The series value
|
|
1488
|
+
* @param collection - The collection type it is laid out over (`Dict<K, R>`)
|
|
1489
|
+
* @param where - Who is asking, for messages
|
|
1490
|
+
* @returns Its facts; `undefined` for a series value no `Plan.series.*` builder made in place (a bound, picked or stored one)
|
|
1491
|
+
* @throws {Error} When the series is built over entries of another type than the collection's
|
|
1492
|
+
* @internal
|
|
1493
|
+
*/
|
|
1494
|
+
export function planSeriesFacts(series, collection, where) {
|
|
1495
|
+
const spec = specOf(series);
|
|
1496
|
+
if (spec === undefined)
|
|
1497
|
+
return undefined;
|
|
1498
|
+
return { arm: spec.arm, key: spec.key, title: spec.title, nests: spec.nests, writes: spec.writer(collection, where) !== undefined };
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* The keys of a series list's whole tree, each series' and every series
|
|
1502
|
+
* nested in it, in order (#1192): what a Plan holds the keys it gives its own
|
|
1503
|
+
* rows apart from, since a row's id is its series' key and its path.
|
|
1504
|
+
*
|
|
1505
|
+
* @param series - The `series` input, or a list of series values
|
|
1506
|
+
* @returns The keys; `undefined` for an East expression list, or one holding a series no `Plan.series.*` builder
|
|
1507
|
+
* made in place, whose keys are not known until it runs
|
|
1508
|
+
* @internal
|
|
1509
|
+
*/
|
|
1510
|
+
export function planSeriesKeys(series) {
|
|
1511
|
+
if (!Array.isArray(series))
|
|
1512
|
+
return undefined;
|
|
1513
|
+
const keys = [];
|
|
1514
|
+
const walk = (spec) => {
|
|
1515
|
+
keys.push(spec.key);
|
|
1516
|
+
spec.nested.forEach(walk);
|
|
1517
|
+
};
|
|
1518
|
+
for (const one of series) {
|
|
1519
|
+
const spec = specOf(one);
|
|
1520
|
+
if (spec === undefined)
|
|
1521
|
+
return undefined;
|
|
1522
|
+
walk(spec);
|
|
1523
|
+
}
|
|
1524
|
+
return keys;
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Apply the `series` input to the source — the canvas's BLOCKS (#823): every
|
|
1528
|
+
* series' blocks, in declared order.
|
|
1529
|
+
*
|
|
1530
|
+
* @remarks
|
|
1531
|
+
* A TS array is built for the source's own collection type (so a source keyed
|
|
1532
|
+
* by any type works with series whose accessors ignore the key); an East
|
|
1533
|
+
* expression maps at evaluation, so a `$.const`-bound, picked or stored list
|
|
1534
|
+
* works identically, its series built for their declared key type.
|
|
1535
|
+
*
|
|
1536
|
+
* The blocks stay apart so a paged canvas can page each data series on its
|
|
1537
|
+
* own: applied to one window of a source, this is every block's share of that
|
|
1538
|
+
* window, from one read of it.
|
|
1539
|
+
*
|
|
1540
|
+
* @param series - The `series` input
|
|
1541
|
+
* @param data - The source collection (a whole `Dict`, or one paged window of it)
|
|
1542
|
+
* @returns The blocks
|
|
1543
|
+
*/
|
|
1544
|
+
export function applySeries(series, data) {
|
|
1545
|
+
const collection = Expr.type(data);
|
|
1546
|
+
if (Array.isArray(series)) {
|
|
1547
|
+
const lists = series.map((s) => {
|
|
1548
|
+
const spec = specOf(s);
|
|
1549
|
+
return spec !== undefined
|
|
1550
|
+
? spec.blocks(collection, `Plan.series.${spec.arm} "${spec.key}"`)(data, East.value([], PathType), East.value(none, IdOptType))
|
|
1551
|
+
: applySeriesValue(s, data);
|
|
1552
|
+
});
|
|
1553
|
+
return lists.reduce((acc, list) => acc.concat(list), East.value([], PlanBlocksType));
|
|
1554
|
+
}
|
|
1555
|
+
return series.map((_$, s) => applySeriesValue(s, data)).flatMap((_$, list) => list);
|
|
1556
|
+
}
|
|
1557
|
+
/** One series value's `write` (the exhaustive-arm read). */
|
|
1558
|
+
function seriesValueWrite(s) {
|
|
1559
|
+
return s.match({
|
|
1560
|
+
span: (_$, v) => v.write,
|
|
1561
|
+
buckets: (_$, v) => v.write,
|
|
1562
|
+
chart: (_$, v) => v.write,
|
|
1563
|
+
heat: (_$, v) => v.write,
|
|
1564
|
+
table: (_$, v) => v.write,
|
|
1565
|
+
cards: (_$, v) => v.write,
|
|
1566
|
+
events: (_$, v) => v.write,
|
|
1567
|
+
group: (_$, v) => v.write,
|
|
1568
|
+
section: (_$, v) => v.write,
|
|
1569
|
+
views: (_$, v) => v.write,
|
|
1570
|
+
rows: (_$, v) => v.write,
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* The canvas's write (#880) — a gesture on a row of one entry, written into
|
|
1575
|
+
* the entry by whichever series in the list made the row: `(entry, key, row,
|
|
1576
|
+
* gesture, carried) → the entry written`, `none` when none takes it. A move
|
|
1577
|
+
* to another row carries its item between its two writes in `carried`
|
|
1578
|
+
* (#825).
|
|
1579
|
+
*
|
|
1580
|
+
* @remarks
|
|
1581
|
+
* The `applySeries` rule: a TS array is built for the source's own collection
|
|
1582
|
+
* type, and an East list (a `$.const` list, a pick's) writes through each
|
|
1583
|
+
* series value's own `write`, built for its declared key type.
|
|
1584
|
+
*
|
|
1585
|
+
* @param series - The `series` input
|
|
1586
|
+
* @param collection - The source's collection type (`Dict<K, R>`)
|
|
1587
|
+
* @returns The write
|
|
1588
|
+
*/
|
|
1589
|
+
export function seriesWriteFn(series, collection) {
|
|
1590
|
+
const { entry: rowType, key: kt } = shapeOf(collection, "Plan");
|
|
1591
|
+
const inputs = [rowType, kt, PlanRowIdType, PlanGestureType, PlanCarriedType];
|
|
1592
|
+
if (Array.isArray(series)) {
|
|
1593
|
+
const built = series.map((s) => {
|
|
1594
|
+
const spec = specOf(s);
|
|
1595
|
+
return spec !== undefined ? topWrite(spec, collection, `Plan.series.${spec.arm} "${spec.key}"`) : undefined;
|
|
1596
|
+
});
|
|
1597
|
+
return East.function([...inputs], OptionType(rowType), ($, entry, key, row, gesture, carried) => {
|
|
1598
|
+
const result = $.let(none, OptionType(rowType));
|
|
1599
|
+
series.forEach((s, i) => {
|
|
1600
|
+
const own = built[i];
|
|
1601
|
+
const write = $.const(own !== undefined ? own : seriesValueWrite(s));
|
|
1602
|
+
$.if(result.hasTag("none"), ($2) => { $2.assign(result, write(entry, key, row, gesture, carried)); });
|
|
1603
|
+
});
|
|
1604
|
+
return result;
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
return East.function([...inputs], OptionType(rowType), ($, entry, key, row, gesture, carried) => {
|
|
1608
|
+
const result = $.let(none, OptionType(rowType));
|
|
1609
|
+
$.for(series, ($2, s) => {
|
|
1610
|
+
$2.if(result.hasTag("none"), ($3) => {
|
|
1611
|
+
const write = $3.let(seriesValueWrite(s));
|
|
1612
|
+
$3.assign(result, write(entry, key, row, gesture, carried));
|
|
1613
|
+
});
|
|
1614
|
+
});
|
|
1615
|
+
return result;
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
// ============================================================================
|
|
1619
|
+
// Builders — Plan.series.*
|
|
1620
|
+
// ============================================================================
|
|
1621
|
+
/**
|
|
1622
|
+
* A span series — one state-run row per entry; a row with children shows its
|
|
1623
|
+
* own runs plus bands over its subtree's runs (`rollup`) — each band sums its
|
|
1624
|
+
* runs' quantities unit by unit (#824).
|
|
1625
|
+
*
|
|
1626
|
+
* @typeParam R - The entry type
|
|
1627
|
+
* @typeParam K - The axis kind the runs imply (inferred from `runs`; `never` when erased)
|
|
1628
|
+
* @typeParam KT - The entries' key type (`keyType`; `String` by default)
|
|
1629
|
+
* @param rowType - The entry type (every builder takes it first — the `Slice.config` shape)
|
|
1630
|
+
* @param config - The accessors and nesting ({@link PlanSpanSeriesConfig})
|
|
1631
|
+
* @returns A series value, branded with its kind
|
|
1632
|
+
*/
|
|
1633
|
+
export function createSeriesSpan(rowType, config) {
|
|
1634
|
+
const cfg = config;
|
|
1635
|
+
// A row rolls up its children: its own (`children`), or — declared on a
|
|
1636
|
+
// views member — the ones the views nest under it (#1219).
|
|
1637
|
+
const rollup = cfg.children !== undefined || cfg.rollup !== undefined ? resolveTag(cfg.rollup ?? "union", PlanRollupType) : undefined;
|
|
1638
|
+
const spec = dataSpec(rowType, cfg, {
|
|
1639
|
+
arm: "span",
|
|
1640
|
+
kind: (value, key, hasChildren) => spanKind({
|
|
1641
|
+
runs: cfg.runs(value, key),
|
|
1642
|
+
...(cfg.decisions !== undefined ? { decisions: cfg.decisions(value, key) } : {}),
|
|
1643
|
+
...(cfg.ports !== undefined ? { ports: cfg.ports(value, key) } : {}),
|
|
1644
|
+
}, rollup === undefined
|
|
1645
|
+
? East.value(none, OptionType(PlanRollupType))
|
|
1646
|
+
// A row rolls up only when it HAS child rows — a leaf's bands
|
|
1647
|
+
// would repeat its own runs.
|
|
1648
|
+
: hasChildren.ifElse(() => East.value(some(rollup), OptionType(PlanRollupType)), () => East.value(none, OptionType(PlanRollupType)))),
|
|
1649
|
+
edit: cfg.edit,
|
|
1650
|
+
});
|
|
1651
|
+
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* A heat series — one per-bucket cell row per entry; a row with children and
|
|
1655
|
+
* no cells of its own shows its children's per-bucket `aggregate`.
|
|
1656
|
+
*
|
|
1657
|
+
* @typeParam R - The entry type
|
|
1658
|
+
* @typeParam K - The axis kind the cells imply (inferred from `cells`; `never` when erased)
|
|
1659
|
+
* @typeParam KT - The entries' key type
|
|
1660
|
+
* @param rowType - The entry type
|
|
1661
|
+
* @param config - The accessors and nesting ({@link PlanHeatSeriesConfig})
|
|
1662
|
+
* @returns A series value, branded with its kind
|
|
1663
|
+
*/
|
|
1664
|
+
export function createSeriesHeat(rowType, config) {
|
|
1665
|
+
const cfg = config;
|
|
1666
|
+
const aggregate = cfg.children !== undefined
|
|
1667
|
+
? East.value(some(resolveTag(cfg.aggregate ?? "mean", PlanAggregateType)), OptionType(PlanAggregateType))
|
|
1668
|
+
: East.value(none, OptionType(PlanAggregateType));
|
|
1669
|
+
const spec = dataSpec(rowType, cfg, {
|
|
1670
|
+
arm: "heat",
|
|
1671
|
+
kind: (value, key) => heatKind({
|
|
1672
|
+
cells: cfg.cells(value, key),
|
|
1673
|
+
...(cfg.scale !== undefined ? { scale: cfg.scale } : {}),
|
|
1674
|
+
}, aggregate),
|
|
1675
|
+
});
|
|
1676
|
+
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* A table series — one bucketed-numeral row per entry; a row with children and
|
|
1680
|
+
* no values of its own shows per-position subtotals (`aggregate`, `format`).
|
|
1681
|
+
*
|
|
1682
|
+
* @typeParam R - The entry type
|
|
1683
|
+
* @typeParam K - The axis kind the cells imply (inferred from `cells` / `series`; `never` when erased)
|
|
1684
|
+
* @typeParam KT - The entries' key type
|
|
1685
|
+
* @param rowType - The entry type
|
|
1686
|
+
* @param config - The accessors and nesting ({@link PlanTableSeriesOfConfig})
|
|
1687
|
+
* @returns A series value, branded with its kind
|
|
1688
|
+
*/
|
|
1689
|
+
export function createSeriesTable(rowType, config) {
|
|
1690
|
+
const cfg = config;
|
|
1691
|
+
if (cfg.cells !== undefined && cfg.series !== undefined) {
|
|
1692
|
+
throw new Error(`Plan.series.table "${cfg.key}": pass \`cells\` (one plain value series) OR \`series\` — not both`);
|
|
1693
|
+
}
|
|
1694
|
+
const aggregate = cfg.children !== undefined
|
|
1695
|
+
? East.value(some(resolveTag(cfg.aggregate ?? "sum", TableAggregateType)), OptionType(TableAggregateType))
|
|
1696
|
+
: East.value(none, OptionType(TableAggregateType));
|
|
1697
|
+
const spec = dataSpec(rowType, cfg, {
|
|
1698
|
+
arm: "table",
|
|
1699
|
+
kind: (value, key) => tableKind({
|
|
1700
|
+
...(cfg.cells !== undefined ? { cells: cfg.cells(value, key) } : {}),
|
|
1701
|
+
...(cfg.fold !== undefined ? { fold: cfg.fold } : {}),
|
|
1702
|
+
...(cfg.series !== undefined ? { series: cfg.series(value, key) } : {}),
|
|
1703
|
+
...(cfg.split !== undefined ? { split: cfg.split } : {}),
|
|
1704
|
+
...(cfg.format !== undefined ? { format: cfg.format } : {}),
|
|
1705
|
+
...(cfg.emphasis !== undefined ? { emphasis: cfg.emphasis } : {}),
|
|
1706
|
+
}, aggregate),
|
|
1707
|
+
});
|
|
1708
|
+
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* A bucket series — one bucket row (the Planner surface) per entry.
|
|
1712
|
+
*
|
|
1713
|
+
* @typeParam R - The entry type
|
|
1714
|
+
* @typeParam K - The axis kind the tiles imply (inferred from `events` / `markers`; `never` when erased)
|
|
1715
|
+
* @typeParam KT - The entries' key type
|
|
1716
|
+
* @param rowType - The entry type
|
|
1717
|
+
* @param config - The accessors ({@link PlanBucketsSeriesConfig})
|
|
1718
|
+
* @returns A series value, branded with its kind
|
|
1719
|
+
*/
|
|
1720
|
+
export function createSeriesBuckets(rowType, config) {
|
|
1721
|
+
const cfg = config;
|
|
1722
|
+
const spec = dataSpec(rowType, cfg, {
|
|
1723
|
+
arm: "buckets",
|
|
1724
|
+
kind: (value, key) => bucketsKind({
|
|
1725
|
+
...(cfg.lanes !== undefined ? { lanes: cfg.lanes(value, key) } : {}),
|
|
1726
|
+
events: cfg.events(value, key),
|
|
1727
|
+
...(cfg.markers !== undefined ? { markers: cfg.markers(value, key) } : {}),
|
|
1728
|
+
}),
|
|
1729
|
+
edit: cfg.edit,
|
|
1730
|
+
});
|
|
1731
|
+
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* A cards series — one cards row (Roster chips) per entry.
|
|
1735
|
+
*
|
|
1736
|
+
* @typeParam R - The entry type
|
|
1737
|
+
* @typeParam K - The axis kind the chips imply (inferred from `chips`; `never` when erased)
|
|
1738
|
+
* @typeParam KT - The entries' key type
|
|
1739
|
+
* @param rowType - The entry type
|
|
1740
|
+
* @param config - The accessors ({@link PlanCardsSeriesConfig})
|
|
1741
|
+
* @returns A series value, branded with its kind
|
|
1742
|
+
*/
|
|
1743
|
+
export function createSeriesCards(rowType, config) {
|
|
1744
|
+
const cfg = config;
|
|
1745
|
+
const spec = dataSpec(rowType, cfg, {
|
|
1746
|
+
arm: "cards",
|
|
1747
|
+
kind: (value, key) => cardsKind(cfg.chips(value, key)),
|
|
1748
|
+
edit: cfg.edit,
|
|
1749
|
+
});
|
|
1750
|
+
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* An events series — one instant-mark row per entry.
|
|
1754
|
+
*
|
|
1755
|
+
* @typeParam R - The entry type
|
|
1756
|
+
* @typeParam K - The axis kind the marks imply (inferred from `marks`; `never` when erased)
|
|
1757
|
+
* @typeParam KT - The entries' key type
|
|
1758
|
+
* @param rowType - The entry type
|
|
1759
|
+
* @param config - The accessors ({@link PlanEventsSeriesConfig})
|
|
1760
|
+
* @returns A series value, branded with its kind
|
|
1761
|
+
*/
|
|
1762
|
+
export function createSeriesEvents(rowType, config) {
|
|
1763
|
+
const cfg = config;
|
|
1764
|
+
const spec = dataSpec(rowType, cfg, {
|
|
1765
|
+
arm: "events",
|
|
1766
|
+
kind: (value, key) => eventsKind(cfg.marks(value, key)),
|
|
1767
|
+
edit: cfg.edit,
|
|
1768
|
+
});
|
|
1769
|
+
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* A chart series — one chart row per entry, layers built from the entry's own
|
|
1773
|
+
* data. Kind-ERASED: a Chart layer's TS face does not expose its x type, so a
|
|
1774
|
+
* chart series constrains no axis at compile time — the render-time
|
|
1775
|
+
* diagnostic holds it to the axis like any row.
|
|
1776
|
+
*
|
|
1777
|
+
* @typeParam R - The entry type
|
|
1778
|
+
* @typeParam KT - The entries' key type
|
|
1779
|
+
* @param rowType - The entry type
|
|
1780
|
+
* @param config - The accessors and shared axes ({@link PlanChartSeriesConfig})
|
|
1781
|
+
* @returns A series value
|
|
1782
|
+
*/
|
|
1783
|
+
export function createSeriesChart(rowType, config) {
|
|
1784
|
+
const cfg = config;
|
|
1785
|
+
const spec = dataSpec(rowType, cfg, {
|
|
1786
|
+
arm: "chart",
|
|
1787
|
+
kind: (value, key) => chartKind({
|
|
1788
|
+
layers: cfg.layers(value, key),
|
|
1789
|
+
...(cfg.left !== undefined ? { left: cfg.left } : {}),
|
|
1790
|
+
...(cfg.right !== undefined ? { right: cfg.right } : {}),
|
|
1791
|
+
...(cfg.height !== undefined ? { height: cfg.height } : {}),
|
|
1792
|
+
...(cfg.expandedHeight !== undefined ? { expandedHeight: cfg.expandedHeight } : {}),
|
|
1793
|
+
...(cfg.expandable !== undefined ? { expandable: cfg.expandable } : {}),
|
|
1794
|
+
}),
|
|
1795
|
+
...(cfg.pinned !== undefined
|
|
1796
|
+
? { pinned: (value, key) => East.value(cfg.pinned(value, key), BooleanType) }
|
|
1797
|
+
: {}),
|
|
1798
|
+
...(cfg.swatches !== undefined ? { swatches: cfg.swatches } : {}),
|
|
1799
|
+
});
|
|
1800
|
+
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* A group series — one group strip PER ENTRY, its members the entry's
|
|
1804
|
+
* children (#822). Collapsed, a strip rests as its summary heat strip —
|
|
1805
|
+
* explicit `summary` cells, or the renderer-derived `summaryAggregate` — and
|
|
1806
|
+
* its member count is derived.
|
|
1807
|
+
*
|
|
1808
|
+
* @remarks
|
|
1809
|
+
* Hierarchy comes from the data's nesting: to group a flat source, reshape it
|
|
1810
|
+
* first — inline, one East `groupToDicts` in the canvas function (the entries
|
|
1811
|
+
* are then `Dict<String, Row>` and a group's label is its key); a paged source
|
|
1812
|
+
* is grouped in the dataflow. The `by` form and the static
|
|
1813
|
+
* `group(R, chrome, children)` form are gone — see `Plan.series.section` for a
|
|
1814
|
+
* fixed titled block.
|
|
1815
|
+
*
|
|
1816
|
+
* @typeParam R - The entry type
|
|
1817
|
+
* @typeParam K - The axis kind the summary cells imply
|
|
1818
|
+
* @typeParam KT - The entries' key type
|
|
1819
|
+
* @param rowType - The entry type
|
|
1820
|
+
* @param config - The strip's accessors and its members ({@link PlanGroupSeriesConfig})
|
|
1821
|
+
* @returns A series value, branded with its kind
|
|
1822
|
+
* @throws {Error} When called with the removed `by` or static forms
|
|
1823
|
+
*/
|
|
1824
|
+
export function createSeriesGroup(rowType, config) {
|
|
1825
|
+
// The removed forms, named — a plain JS caller would otherwise meet an
|
|
1826
|
+
// unhelpful "label is not a function".
|
|
1827
|
+
if (arguments.length > 2 || "by" in config) {
|
|
1828
|
+
throw new Error("Plan.series.group: the `by` and static `group(R, chrome, children)` forms are removed (#822) — a group " +
|
|
1829
|
+
"strip is now one per ENTRY with its members nested in the entry (`children`); reshape a flat source " +
|
|
1830
|
+
"with `groupToDicts` first, and use `Plan.series.section(R, { key, title }, [series…])` for a fixed block");
|
|
1831
|
+
}
|
|
1832
|
+
const cfg = config;
|
|
1833
|
+
const where = `Plan.series.group "${cfg.key}"`;
|
|
1834
|
+
if (cfg.children === undefined) {
|
|
1835
|
+
throw new Error(`${where}: a group strip needs its members — declare \`children\``);
|
|
1836
|
+
}
|
|
1837
|
+
const summary = cfg.summary;
|
|
1838
|
+
// The strip's ONE summary (#824): the per-entry cells, the declared
|
|
1839
|
+
// aggregate, or a plain band — both at once is refused here.
|
|
1840
|
+
const declared = summary === undefined ? groupSummary(undefined, cfg.summaryAggregate, where) : undefined;
|
|
1841
|
+
if (summary !== undefined && cfg.summaryAggregate !== undefined) {
|
|
1842
|
+
throw new Error(`${where}: give \`summary\` (explicit strip cells) OR \`summaryAggregate\` (cells derived from the ` +
|
|
1843
|
+
"members) — a collapsed strip shows one or the other");
|
|
1844
|
+
}
|
|
1845
|
+
const spec = dataSpec(rowType, cfg, {
|
|
1846
|
+
arm: "group",
|
|
1847
|
+
kind: (value, key) => groupKind(declared ?? East.value(variant("cells", East.value(summary(value, key), PlanHeatCellsType)), PlanGroupSummaryType)),
|
|
1848
|
+
});
|
|
1849
|
+
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* A section — a fixed titled block over series: one header row, the member
|
|
1853
|
+
* series' rows beneath it (#822). A section adds no path segment: its header's
|
|
1854
|
+
* id is `section { series, path }` at its parent's path, and its members' rows
|
|
1855
|
+
* keep their own.
|
|
1856
|
+
*
|
|
1857
|
+
* @typeParam R - The entry type (the members' — they share the section's entries)
|
|
1858
|
+
* @typeParam K - The union of the members' axis kinds (inferred; `never` when all are erased)
|
|
1859
|
+
* @param rowType - The entry type
|
|
1860
|
+
* @param config - The header ({@link PlanSectionSeriesConfig})
|
|
1861
|
+
* @param members - The member series, in order
|
|
1862
|
+
* @returns A series value, branded with its members' kinds
|
|
1863
|
+
*/
|
|
1864
|
+
export function createSeriesSection(rowType, config, members) {
|
|
1865
|
+
const cfg = config;
|
|
1866
|
+
const spec = sectionSpec(cfg, members);
|
|
1867
|
+
return seriesValue(rowType, undefined, spec, cfg);
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* A views series — each entry gets one row per member series, adjacent and in
|
|
1871
|
+
* declared order; the entry's children follow its view rows, nested under the
|
|
1872
|
+
* first of them (#822). How one entity is shown several ways: a press's jobs,
|
|
1873
|
+
* utilisation and sheets as three rows side by side.
|
|
1874
|
+
*
|
|
1875
|
+
* @remarks
|
|
1876
|
+
* A member series' own `match` decides whether its row shows for an entry; a
|
|
1877
|
+
* member may not declare `children` (the views own the entry's children). A
|
|
1878
|
+
* view row's id is its member series' key and the entry's path, and a seek on
|
|
1879
|
+
* the entry lands on its first view row.
|
|
1880
|
+
*
|
|
1881
|
+
* The views' `children` are a step-down's series, or — a bare accessor — more
|
|
1882
|
+
* of the views' own entries, each drawn as its members' rows, to any depth,
|
|
1883
|
+
* the views' `match` and `collapsed` at every level (#1219). A span member that
|
|
1884
|
+
* declares `rollup` rolls up the children nested under its row.
|
|
1885
|
+
*
|
|
1886
|
+
* @typeParam R - The entry type
|
|
1887
|
+
* @typeParam K - The union of the members' axis kinds (inferred; `never` when all are erased)
|
|
1888
|
+
* @typeParam KT - The entries' key type
|
|
1889
|
+
* @param rowType - The entry type
|
|
1890
|
+
* @param config - Membership, collapse and children ({@link PlanViewsSeriesConfig})
|
|
1891
|
+
* @param members - The member series — one row each per entry, in order
|
|
1892
|
+
* @returns A series value, branded with its members' kinds
|
|
1893
|
+
*/
|
|
1894
|
+
export function createSeriesViews(rowType, config, members) {
|
|
1895
|
+
const cfg = config;
|
|
1896
|
+
const spec = viewsSpec(rowType, cfg, members);
|
|
1897
|
+
return seriesValue(rowType, cfg.keyType, spec, cfg);
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Hand-built rows riding beside the data-driven series (a pinned KPI chart, a
|
|
1901
|
+
* shutdown written out once) — the kind factories' streams, named by this
|
|
1902
|
+
* series and placed as its block.
|
|
1903
|
+
*
|
|
1904
|
+
* @remarks
|
|
1905
|
+
* A hand-built row's id is this series' key and the factory `key`s that lead to
|
|
1906
|
+
* it (`Plan.ref("works", "shutdown", "elec")`); keys must be unique among a
|
|
1907
|
+
* row's siblings, or two rows share an id and the renderer draws the second as
|
|
1908
|
+
* a diagnostic.
|
|
1909
|
+
*
|
|
1910
|
+
* @typeParam R - The entry type (pins the series against its siblings)
|
|
1911
|
+
* @typeParam K - The union of the rows' axis kinds (inferred from the kind-factory results; `never` when erased)
|
|
1912
|
+
* @param rowType - The entry type
|
|
1913
|
+
* @param identity - How the library lists this block ({@link PlanSeriesIdentity})
|
|
1914
|
+
* @param rows - The hand-built rows (kind-factory results)
|
|
1915
|
+
* @returns A series value, branded with the rows' kinds
|
|
1916
|
+
*/
|
|
1917
|
+
export function createSeriesRows(rowType, identity, rows) {
|
|
1918
|
+
const spec = rowsSpec(identity, normalizeRows(rows));
|
|
1919
|
+
return seriesValue(rowType, undefined, spec, identity);
|
|
1920
|
+
}
|
|
1921
|
+
//# sourceMappingURL=series.js.map
|