@gaia-ai/addon-gaia-ui 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Component/badge/index.d.ts +11 -0
- package/dist/src/Component/badge/index.js +10 -0
- package/dist/src/Component/bar-chart/index.d.ts +16 -0
- package/dist/src/Component/bar-chart/index.js +63 -0
- package/dist/src/Component/card/index.d.ts +16 -0
- package/dist/src/Component/card/index.js +39 -0
- package/dist/src/Component/divider/index.d.ts +7 -0
- package/dist/src/Component/divider/index.js +13 -0
- package/dist/src/Component/heat-map/index.d.ts +21 -0
- package/dist/src/Component/heat-map/index.js +86 -0
- package/dist/src/Component/key-value/index.d.ts +18 -0
- package/dist/src/Component/key-value/index.js +30 -0
- package/dist/src/Component/markdown/index.d.ts +35 -5
- package/dist/src/Component/markdown/index.js +72 -16
- package/dist/src/Entity/team/Data/workload.d.ts +3 -1
- package/dist/src/Entity/team/Data/workload.js +6 -4
- package/dist/src/Entity/team/Screen/team-work.d.ts +12 -1
- package/dist/src/Entity/team/Screen/team-work.js +47 -2
- package/dist/src/Entity/ticket/Component/demo-statistics.d.ts +14 -0
- package/dist/src/Entity/ticket/Component/demo-statistics.js +88 -0
- package/dist/src/Entity/ticket/Component/react-statistics.d.ts +14 -22
- package/dist/src/Entity/ticket/Component/react-statistics.js +21 -42
- package/dist/src/Entity/ticket/Component/ticket-content/index.d.ts +28 -3
- package/dist/src/Entity/ticket/Component/ticket-content/index.js +331 -157
- package/dist/src/Entity/ticket/Data/parents.js +1 -1
- package/dist/src/Entity/ticket/Data/query.d.ts +2 -2
- package/dist/src/Entity/ticket/Data/query.js +13 -9
- package/dist/src/Entity/ticket/Data/store.js +1 -1
- package/dist/src/Entity/ticket/Data/tickets.d.ts +1 -1
- package/dist/src/Entity/ticket/Data/tickets.js +1 -1
- package/dist/src/Entity/ticket/Form/edit.d.ts +13 -0
- package/dist/src/Entity/ticket/Form/edit.js +17 -7
- package/dist/src/Entity/ticket/Screen/conductor-tickets.d.ts +7 -1
- package/dist/src/Entity/ticket/Screen/conductor-tickets.js +30 -6
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.d.ts +3 -1
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.js +21 -6
- package/dist/src/Entity/ticket/Screen/my-work.d.ts +8 -1
- package/dist/src/Entity/ticket/Screen/my-work.js +37 -4
- package/dist/src/Entity/ticket/Screen/ticket-comments.d.ts +6 -12
- package/dist/src/Entity/ticket/Screen/ticket-comments.js +11 -16
- package/dist/src/Entity/ticket/Screen/ticket-content-screen.d.ts +25 -44
- package/dist/src/Entity/ticket/Screen/ticket-content-screen.js +79 -87
- package/dist/src/Entity/ticket/Screen/ticket-detail.d.ts +6 -11
- package/dist/src/Entity/ticket/Screen/ticket-detail.js +14 -19
- package/dist/src/Entity/ticket/Screen/ticket-filter-bar.js +2 -9
- package/dist/src/Entity/ticket/Screen/ticket-planning.d.ts +6 -12
- package/dist/src/Entity/ticket/Screen/ticket-planning.js +11 -16
- package/dist/src/Entity/ticket/Screen/ticket-react-markdown.d.ts +9 -7
- package/dist/src/Entity/ticket/Screen/ticket-react-markdown.js +36 -13
- package/dist/src/Entity/ticket/Screen/ticket-react.d.ts +21 -8
- package/dist/src/Entity/ticket/Screen/ticket-react.js +109 -34
- package/dist/src/Entity/ticket/Screen/ticket-search.d.ts +44 -0
- package/dist/src/Entity/ticket/Screen/ticket-search.js +97 -0
- package/dist/src/Entity/ticket/Screen/tickets.d.ts +9 -7
- package/dist/src/Entity/ticket/Screen/tickets.js +25 -85
- package/dist/src/Entity/ticket/Screen/user-tickets.d.ts +7 -0
- package/dist/src/Entity/ticket/Screen/user-tickets.js +30 -2
- package/dist/src/Kernel/renderer.d.ts +1 -3
- package/dist/src/Kernel/renderer.js +2 -5
- package/dist/src/Kernel/tui-kernel.js +7 -16
- package/dist/src/Screen/async-screen.d.ts +13 -0
- package/dist/src/Screen/async-screen.js +34 -5
- package/dist/src/Screen/list-screen.d.ts +11 -2
- package/dist/src/Screen/list-screen.js +20 -2
- package/dist/src/Screen/screen.d.ts +6 -2
- package/dist/src/Statistics/comparison-chart.d.ts +4 -2
- package/dist/src/Statistics/comparison-chart.js +14 -6
- package/dist/src/Statistics/figures.d.ts +17 -3
- package/dist/src/Statistics/figures.js +104 -41
- package/dist/src/Statistics/presentation.d.ts +3 -5
- package/dist/src/Statistics/presentation.js +3 -5
- package/dist/src/Statistics/screen.d.ts +16 -5
- package/dist/src/Statistics/screen.js +59 -26
- package/dist/src/lib/theme.d.ts +0 -56
- package/dist/src/lib/theme.js +0 -42
- package/dist/src/lib/vnode.d.ts +29 -0
- package/dist/src/lib/vnode.js +69 -0
- package/dist/src/route.js +37 -0
- package/dist/src/types.d.ts +5 -1
- package/dist/src/ui.d.ts +0 -1
- package/dist/src/ui.js +0 -1
- package/package.json +2 -2
- package/dist/src/Agent/Component/agent-list/index.d.ts +0 -6
- package/dist/src/Agent/Component/agent-list/index.js +0 -14
- package/dist/src/Component/agent-list/index.d.ts +0 -6
- package/dist/src/Component/agent-list/index.js +0 -14
- package/dist/src/Component/comment-item/index.d.ts +0 -21
- package/dist/src/Component/comment-item/index.js +0 -39
- package/dist/src/Component/create-form/index.d.ts +0 -3
- package/dist/src/Component/create-form/index.js +0 -26
- package/dist/src/Component/dashboard-list/index.d.ts +0 -8
- package/dist/src/Component/dashboard-list/index.js +0 -7
- package/dist/src/Component/project-picker/index.d.ts +0 -15
- package/dist/src/Component/project-picker/index.js +0 -42
- package/dist/src/Component/run-table/index.d.ts +0 -10
- package/dist/src/Component/run-table/index.js +0 -20
- package/dist/src/Component/tab-bar/index.d.ts +0 -20
- package/dist/src/Component/tab-bar/index.js +0 -42
- package/dist/src/Component/ticket-detail/index.d.ts +0 -38
- package/dist/src/Component/ticket-detail/index.js +0 -193
- package/dist/src/Component/ticket-table/index.d.ts +0 -5
- package/dist/src/Component/ticket-table/index.js +0 -14
- package/dist/src/Component/ticket-teaser/index.d.ts +0 -7
- package/dist/src/Component/ticket-teaser/index.js +0 -76
- package/dist/src/Deriver/liveness.d.ts +0 -12
- package/dist/src/Deriver/liveness.js +0 -34
- package/dist/src/Deriver/run-status.d.ts +0 -12
- package/dist/src/Deriver/run-status.js +0 -36
- package/dist/src/Entity/conductor/Component/conductor-list/index.d.ts +0 -25
- package/dist/src/Entity/conductor/Component/conductor-list/index.js +0 -60
- package/dist/src/Entity/project/Component/dashboard-list/index.d.ts +0 -8
- package/dist/src/Entity/project/Component/dashboard-list/index.js +0 -7
- package/dist/src/Entity/project/Component/project-list/index.d.ts +0 -8
- package/dist/src/Entity/project/Component/project-list/index.js +0 -7
- package/dist/src/Entity/project/Component/project-picker/index.d.ts +0 -15
- package/dist/src/Entity/project/Component/project-picker/index.js +0 -42
- package/dist/src/Entity/project/Screen/project-work.d.ts +0 -33
- package/dist/src/Entity/project/Screen/project-work.js +0 -114
- package/dist/src/Entity/run/Component/run-table/index.d.ts +0 -8
- package/dist/src/Entity/run/Component/run-table/index.js +0 -11
- package/dist/src/Entity/statistics/Data/statistics.d.ts +0 -147
- package/dist/src/Entity/statistics/Data/statistics.js +0 -235
- package/dist/src/Entity/statistics/Screen/dashboard.d.ts +0 -70
- package/dist/src/Entity/statistics/Screen/dashboard.js +0 -216
- package/dist/src/Entity/statistics/render.d.ts +0 -6
- package/dist/src/Entity/statistics/render.js +0 -71
- package/dist/src/Entity/ticket/Component/tab-bar/index.d.ts +0 -20
- package/dist/src/Entity/ticket/Component/tab-bar/index.js +0 -71
- package/dist/src/Entity/ticket/Component/ticket-table/index.d.ts +0 -7
- package/dist/src/Entity/ticket/Component/ticket-table/index.js +0 -10
- package/dist/src/Entity/user/Screen/user-overview.d.ts +0 -33
- package/dist/src/Entity/user/Screen/user-overview.js +0 -80
- package/dist/src/Extension/contract.d.ts +0 -204
- package/dist/src/Extension/contract.js +0 -1
- package/dist/src/Kernel/react-body.d.ts +0 -9
- package/dist/src/Kernel/react-body.js +0 -42
- package/dist/src/Screen/async-react-screen.d.ts +0 -45
- package/dist/src/Screen/async-react-screen.js +0 -120
- package/dist/src/Screen/dashboard.d.ts +0 -1
- package/dist/src/Screen/dashboard.js +0 -1
- package/dist/src/Screen/project.d.ts +0 -1
- package/dist/src/Screen/project.js +0 -1
- package/dist/src/Screen/projects.d.ts +0 -1
- package/dist/src/Screen/projects.js +0 -1
- package/dist/src/Screen/react-screen.d.ts +0 -14
- package/dist/src/Screen/react-screen.js +0 -34
- package/dist/src/Screen/ticket.d.ts +0 -1
- package/dist/src/Screen/ticket.js +0 -1
- package/dist/src/Service/agent.d.ts +0 -17
- package/dist/src/Service/agent.js +0 -28
- package/dist/src/Service/create-form-data.d.ts +0 -10
- package/dist/src/Service/create-form-data.js +0 -52
- package/dist/src/Service/create-form.d.ts +0 -52
- package/dist/src/Service/create-form.js +0 -98
- package/dist/src/Service/project-data.d.ts +0 -48
- package/dist/src/Service/project-data.js +0 -92
- package/dist/src/Service/projects.d.ts +0 -21
- package/dist/src/Service/projects.js +0 -80
- package/dist/src/Service/route.d.ts +0 -11
- package/dist/src/Service/route.js +0 -215
- package/dist/src/Service/run-data.d.ts +0 -35
- package/dist/src/Service/run-data.js +0 -62
- package/dist/src/Service/ticket-data.d.ts +0 -6
- package/dist/src/Service/ticket-data.js +0 -7
- package/dist/src/Tui/controller.d.ts +0 -13
- package/dist/src/Tui/controller.js +0 -1794
- package/dist/src/Vendor/termcn/hooks/use-motion.d.ts +0 -5
- package/dist/src/Vendor/termcn/hooks/use-motion.js +0 -7
- package/dist/src/Vendor/termcn/hooks/use-theme.d.ts +0 -5
- package/dist/src/Vendor/termcn/hooks/use-theme.js +0 -10
- package/dist/src/Vendor/termcn/hooks/use-unicode.d.ts +0 -5
- package/dist/src/Vendor/termcn/hooks/use-unicode.js +0 -34
- package/dist/src/Vendor/termcn/lib/dither-chart-utils.d.ts +0 -130
- package/dist/src/Vendor/termcn/lib/dither-chart-utils.js +0 -357
- package/dist/src/Vendor/termcn/lib/dither-line-chart-utils.d.ts +0 -2
- package/dist/src/Vendor/termcn/lib/dither-line-chart-utils.js +0 -72
- package/dist/src/Vendor/termcn/lib/dither-radar-chart-utils.d.ts +0 -2
- package/dist/src/Vendor/termcn/lib/dither-radar-chart-utils.js +0 -113
- package/dist/src/Vendor/termcn/themes/default.d.ts +0 -1
- package/dist/src/Vendor/termcn/themes/default.js +0 -3
- package/dist/src/Vendor/termcn/ui/badge.d.ts +0 -13
- package/dist/src/Vendor/termcn/ui/badge.js +0 -33
- package/dist/src/Vendor/termcn/ui/bar-chart.d.ts +0 -16
- package/dist/src/Vendor/termcn/ui/bar-chart.js +0 -61
- package/dist/src/Vendor/termcn/ui/card.d.ts +0 -18
- package/dist/src/Vendor/termcn/ui/card.js +0 -7
- package/dist/src/Vendor/termcn/ui/dither-chart.d.ts +0 -118
- package/dist/src/Vendor/termcn/ui/dither-chart.js +0 -418
- package/dist/src/Vendor/termcn/ui/dither-line-chart.d.ts +0 -10
- package/dist/src/Vendor/termcn/ui/dither-line-chart.js +0 -11
- package/dist/src/Vendor/termcn/ui/dither-radar-chart.d.ts +0 -21
- package/dist/src/Vendor/termcn/ui/dither-radar-chart.js +0 -11
- package/dist/src/Vendor/termcn/ui/divider.d.ts +0 -14
- package/dist/src/Vendor/termcn/ui/divider.js +0 -25
- package/dist/src/Vendor/termcn/ui/gauge.d.ts +0 -12
- package/dist/src/Vendor/termcn/ui/gauge.js +0 -48
- package/dist/src/Vendor/termcn/ui/heat-map.d.ts +0 -13
- package/dist/src/Vendor/termcn/ui/heat-map.js +0 -59
- package/dist/src/Vendor/termcn/ui/key-value.d.ts +0 -16
- package/dist/src/Vendor/termcn/ui/key-value.js +0 -24
- package/dist/src/Vendor/termcn/ui/line-chart.d.ts +0 -15
- package/dist/src/Vendor/termcn/ui/line-chart.js +0 -102
- package/dist/src/Vendor/termcn/ui/markdown.d.ts +0 -24
- package/dist/src/Vendor/termcn/ui/markdown.js +0 -39
- package/dist/src/Vendor/termcn/ui/progress-bar.d.ts +0 -13
- package/dist/src/Vendor/termcn/ui/progress-bar.js +0 -13
- package/dist/src/Vendor/termcn/ui/sparkline.d.ts +0 -8
- package/dist/src/Vendor/termcn/ui/sparkline.js +0 -30
- package/dist/src/Vendor/termcn/ui/types.d.ts +0 -82
- package/dist/src/Vendor/termcn/ui/types.js +0 -1
- package/dist/src/lib/ansi.d.ts +0 -34
- package/dist/src/lib/ansi.js +0 -97
- package/dist/src/lib/hit-test.d.ts +0 -138
- package/dist/src/lib/hit-test.js +0 -138
- package/dist/src/lib/mouse.d.ts +0 -35
- package/dist/src/lib/mouse.js +0 -89
- package/dist/src/lib/table.d.ts +0 -37
- package/dist/src/lib/table.js +0 -20
- package/dist/src/lib/ticket-query.d.ts +0 -21
- package/dist/src/lib/ticket-query.js +0 -72
|
@@ -8,7 +8,6 @@ export interface TerminalShellInput {
|
|
|
8
8
|
readonly output: typeof process.stdout;
|
|
9
9
|
readonly trace: InputTrace | undefined;
|
|
10
10
|
readonly interactive: boolean;
|
|
11
|
-
readonly reactEnabled?: boolean;
|
|
12
11
|
}
|
|
13
12
|
/**
|
|
14
13
|
* The terminal, as the kernel holds it.
|
|
@@ -22,6 +21,5 @@ export interface TerminalShell {
|
|
|
22
21
|
readonly renderer: CliRenderer | undefined;
|
|
23
22
|
readonly shell: BoxRenderable | undefined;
|
|
24
23
|
readonly ownsRenderer: boolean;
|
|
25
|
-
readonly reactBody: import('./react-body.js').ReactBody | undefined;
|
|
26
24
|
}
|
|
27
|
-
export declare function createTerminalShell({ options, input, output, trace, interactive,
|
|
25
|
+
export declare function createTerminalShell({ options, input, output, trace, interactive, }: TerminalShellInput): Promise<TerminalShell>;
|
|
@@ -47,7 +47,7 @@ import { DEFAULT_BACKGROUND } from '../lib/theme.js';
|
|
|
47
47
|
* the alternate one changes what the operator's terminal shows after `q`.
|
|
48
48
|
*/
|
|
49
49
|
const HOME_AND_CLEAR = '\x1b[H\x1b[2J';
|
|
50
|
-
export async function createTerminalShell({ options, input, output, trace, interactive,
|
|
50
|
+
export async function createTerminalShell({ options, input, output, trace, interactive, }) {
|
|
51
51
|
/**
|
|
52
52
|
* GAIA-326 — the ONE renderer that owns the terminal.
|
|
53
53
|
*
|
|
@@ -123,9 +123,6 @@ export async function createTerminalShell({ options, input, output, trace, inter
|
|
|
123
123
|
* kernel computed every row's `top` itself, which is how a framework bought
|
|
124
124
|
* partly for its layout engine ended up using none of it.
|
|
125
125
|
*/
|
|
126
|
-
const reactBody = renderer && reactEnabled
|
|
127
|
-
? (await import('./react-body.js')).createReactBody(renderer)
|
|
128
|
-
: undefined;
|
|
129
126
|
const shell = renderer
|
|
130
127
|
? new BoxRenderable(renderer, {
|
|
131
128
|
id: 'shell',
|
|
@@ -141,5 +138,5 @@ export async function createTerminalShell({ options, input, output, trace, inter
|
|
|
141
138
|
: undefined;
|
|
142
139
|
if (renderer && shell)
|
|
143
140
|
renderer.root.add(shell);
|
|
144
|
-
return { renderer, shell, ownsRenderer
|
|
141
|
+
return { renderer, shell, ownsRenderer };
|
|
145
142
|
}
|
|
@@ -7,7 +7,6 @@ import { createRefreshSignal, } from '../lib/refresh-signal.js';
|
|
|
7
7
|
import { scrollTo } from '../lib/scroll.js';
|
|
8
8
|
import { Router } from '../Routing/router.js';
|
|
9
9
|
import { bootLocationFor, buildRoutes, WORKSPACE_PICKER_ROUTE_ID, } from '../Routing/routes.js';
|
|
10
|
-
import { ReactScreen } from '../Screen/react-screen.js';
|
|
11
10
|
import { timerDebounce } from '../Widget/option-source.js';
|
|
12
11
|
import { bodyRowsFor, chromeInputFor, FOOTER_SPINNER_MS, frameTrees, modalBoxWidth, NOTICE_ANIM_MS, } from './chrome.js';
|
|
13
12
|
import { clipboardWriter } from './clipboard.js';
|
|
@@ -88,13 +87,12 @@ export async function runTicketTui(_document, _context, services, options = {})
|
|
|
88
87
|
// over both and the router paints on its first `navigate`. GAIA-382 moved the
|
|
89
88
|
// bring-up itself to `Kernel/renderer.ts`, rationale included: it reads five
|
|
90
89
|
// values and returns a terminal, so it never belonged to this closure.
|
|
91
|
-
const { renderer, shell, ownsRenderer
|
|
90
|
+
const { renderer, shell, ownsRenderer } = await createTerminalShell({
|
|
92
91
|
options,
|
|
93
92
|
input,
|
|
94
93
|
output,
|
|
95
94
|
trace,
|
|
96
95
|
interactive,
|
|
97
|
-
reactEnabled: [...session.screens().values()].some((screen) => screen instanceof ReactScreen),
|
|
98
96
|
});
|
|
99
97
|
/**
|
|
100
98
|
* The selection this kernel last scrolled into view.
|
|
@@ -122,6 +120,11 @@ export async function runTicketTui(_document, _context, services, options = {})
|
|
|
122
120
|
store: createSessionStore(),
|
|
123
121
|
options,
|
|
124
122
|
copyToClipboard: clipboardWriter(renderer),
|
|
123
|
+
// Read when a context is built — once per present — so a screen sees the
|
|
124
|
+
// width of the frame it is drawing. The pipe has no terminal to measure.
|
|
125
|
+
get terminalColumns() {
|
|
126
|
+
return interactive ? terminalColumns() : undefined;
|
|
127
|
+
},
|
|
125
128
|
};
|
|
126
129
|
let router;
|
|
127
130
|
const context = () => router.context();
|
|
@@ -209,15 +212,10 @@ export async function runTicketTui(_document, _context, services, options = {})
|
|
|
209
212
|
},
|
|
210
213
|
});
|
|
211
214
|
};
|
|
212
|
-
let reactVisit = -1;
|
|
213
215
|
const draw = () => {
|
|
214
216
|
const bodyBox = regions.body;
|
|
215
217
|
if (closing || !renderer || !shell || !bodyBox || !active())
|
|
216
218
|
return;
|
|
217
|
-
if (reactVisit !== router.visitCount) {
|
|
218
|
-
reactBody?.clear();
|
|
219
|
-
reactVisit = router.visitCount;
|
|
220
|
-
}
|
|
221
219
|
regions.retire(router.visitCount);
|
|
222
220
|
const shown = active()?.present(context());
|
|
223
221
|
if (!shown)
|
|
@@ -280,11 +278,6 @@ export async function runTicketTui(_document, _context, services, options = {})
|
|
|
280
278
|
slot.width = id === 'body' ? '100%' : rowWidth;
|
|
281
279
|
regions.patch(id, next);
|
|
282
280
|
}
|
|
283
|
-
const reactHost = regions.treeOf('body')?.byId.get('react-screen-host');
|
|
284
|
-
if (reactHost && shown.reactBody !== undefined && !modal)
|
|
285
|
-
reactBody?.show(reactHost, shown.reactBody, router.visitCount);
|
|
286
|
-
else
|
|
287
|
-
reactBody?.clear();
|
|
288
281
|
// Keep the cursor in view — when it MOVES. See `scrolledSelection`.
|
|
289
282
|
//
|
|
290
283
|
// GAIA-340 Task 12 — AFTER the patch, and asking the LAYOUT rather than the
|
|
@@ -300,8 +293,7 @@ export async function runTicketTui(_document, _context, services, options = {})
|
|
|
300
293
|
if (!modal &&
|
|
301
294
|
selectedRowId !== undefined &&
|
|
302
295
|
selection !== scrolledSelection) {
|
|
303
|
-
const row = regions.treeOf('body')?.byId.get(selectedRowId)
|
|
304
|
-
reactHost?.findDescendantById(selectedRowId);
|
|
296
|
+
const row = regions.treeOf('body')?.byId.get(selectedRowId);
|
|
305
297
|
if (row) {
|
|
306
298
|
renderer.root.calculateLayout();
|
|
307
299
|
// `lib/scroll.ts` — the clamp the autocomplete already scrolls by, fed
|
|
@@ -405,7 +397,6 @@ export async function runTicketTui(_document, _context, services, options = {})
|
|
|
405
397
|
// them, so `regions` takes them down. The shell is the kernel's, and it
|
|
406
398
|
// goes last: destroying a parent before its children leaves their Yoga
|
|
407
399
|
// nodes unfreed.
|
|
408
|
-
reactBody?.destroy();
|
|
409
400
|
regions.destroy();
|
|
410
401
|
renderer.root.remove(shell);
|
|
411
402
|
shell.destroy();
|
|
@@ -54,6 +54,12 @@ export declare abstract class AsyncScreen<T> extends BaseScreen {
|
|
|
54
54
|
protected abstract renderReady(data: T, context: ScreenContext): VNode;
|
|
55
55
|
protected renderError(error: Error): string[];
|
|
56
56
|
protected didLoad(_data: T, _context: ScreenContext): void;
|
|
57
|
+
/**
|
|
58
|
+
* Whether the body sits flush against the frame. A document screen that
|
|
59
|
+
* reads as content inside the frame returns `false` for the body-window
|
|
60
|
+
* inset every framed row surface used to have.
|
|
61
|
+
*/
|
|
62
|
+
protected flushBody(): boolean;
|
|
57
63
|
/** Optional filter chips above the FRAME. */
|
|
58
64
|
protected badges(_context: ScreenContext): readonly ListFilterBadge[];
|
|
59
65
|
/** title / meta / header / selection — not footer. */
|
|
@@ -74,6 +80,13 @@ export declare abstract class AsyncScreen<T> extends BaseScreen {
|
|
|
74
80
|
*/
|
|
75
81
|
protected actionStillCurrent(guard: LifecycleGuard, context: ScreenContext): boolean;
|
|
76
82
|
protected replaceReadyIfCurrent(guard: LifecycleGuard, data: T, context: ScreenContext): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Re-read over the ready surface, superseding any read still in flight.
|
|
85
|
+
* Resolves `false` when a newer read or a local mutation overtook this one;
|
|
86
|
+
* rejects only while it is still the current read, so a stale failure
|
|
87
|
+
* cannot overwrite a newer answer.
|
|
88
|
+
*/
|
|
89
|
+
protected reloadInPlace(context: ScreenContext): Promise<boolean>;
|
|
77
90
|
protected requestActiveDraw(context: ScreenContext): boolean;
|
|
78
91
|
enter(context: ScreenContext): void;
|
|
79
92
|
retry(context: ScreenContext): Promise<void>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { filterBadgeStrip, } from '../Component/list-chrome/index.js';
|
|
2
|
+
import { serverMessage } from '../lib/jsonapi-error.js';
|
|
2
3
|
import { columnNode, messageTree } from '../lib/vnode.js';
|
|
3
4
|
import { BaseScreen, presentation, } from './screen.js';
|
|
4
5
|
/**
|
|
@@ -46,6 +47,14 @@ export class AsyncScreen extends BaseScreen {
|
|
|
46
47
|
return [error.message, '', 'Press Enter to retry.'];
|
|
47
48
|
}
|
|
48
49
|
didLoad(_data, _context) { }
|
|
50
|
+
/**
|
|
51
|
+
* Whether the body sits flush against the frame. A document screen that
|
|
52
|
+
* reads as content inside the frame returns `false` for the body-window
|
|
53
|
+
* inset every framed row surface used to have.
|
|
54
|
+
*/
|
|
55
|
+
flushBody() {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
49
58
|
/** Optional filter chips above the FRAME. */
|
|
50
59
|
badges(_context) {
|
|
51
60
|
return [];
|
|
@@ -95,6 +104,28 @@ export class AsyncScreen extends BaseScreen {
|
|
|
95
104
|
this.state = 'ready';
|
|
96
105
|
return true;
|
|
97
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Re-read over the ready surface, superseding any read still in flight.
|
|
109
|
+
* Resolves `false` when a newer read or a local mutation overtook this one;
|
|
110
|
+
* rejects only while it is still the current read, so a stale failure
|
|
111
|
+
* cannot overwrite a newer answer.
|
|
112
|
+
*/
|
|
113
|
+
async reloadInPlace(context) {
|
|
114
|
+
const guard = this.captureLifecycle(context, true);
|
|
115
|
+
let data;
|
|
116
|
+
try {
|
|
117
|
+
data = await this.load(context);
|
|
118
|
+
}
|
|
119
|
+
catch (caught) {
|
|
120
|
+
if (!this.lifecycleIsCurrent(guard, context))
|
|
121
|
+
return false;
|
|
122
|
+
throw caught;
|
|
123
|
+
}
|
|
124
|
+
if (!this.replaceReadyIfCurrent(guard, data, context))
|
|
125
|
+
return false;
|
|
126
|
+
this.requestActiveDraw(context);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
98
129
|
requestActiveDraw(context) {
|
|
99
130
|
if (!context.isActive()) {
|
|
100
131
|
this.staleRedrawCount++;
|
|
@@ -159,13 +190,11 @@ export class AsyncScreen extends BaseScreen {
|
|
|
159
190
|
return;
|
|
160
191
|
this.refreshing = false;
|
|
161
192
|
if (keepSurface) {
|
|
162
|
-
this.toast =
|
|
163
|
-
caught instanceof Error ? caught.message : String(caught);
|
|
193
|
+
this.toast = serverMessage(caught);
|
|
164
194
|
this.requestActiveDraw(context);
|
|
165
195
|
return;
|
|
166
196
|
}
|
|
167
|
-
this.failure =
|
|
168
|
-
caught instanceof Error ? caught : new Error(String(caught));
|
|
197
|
+
this.failure = new Error(serverMessage(caught));
|
|
169
198
|
this.state = 'error';
|
|
170
199
|
this.requestActiveDraw(context);
|
|
171
200
|
});
|
|
@@ -204,7 +233,7 @@ export class AsyncScreen extends BaseScreen {
|
|
|
204
233
|
}
|
|
205
234
|
children.push(this.surface(context));
|
|
206
235
|
return presentation(columnNode(`${id}-body`, ...children), {
|
|
207
|
-
flush:
|
|
236
|
+
flush: this.flushBody(),
|
|
208
237
|
...this.chrome(context),
|
|
209
238
|
...(actions.length > 0 ? { actions } : {}),
|
|
210
239
|
...(notice !== undefined && notice !== '' ? { status: notice } : {}),
|
|
@@ -14,7 +14,7 @@ import type { ScreenContext } from './screen.js';
|
|
|
14
14
|
* enough hooks to be a framework.
|
|
15
15
|
*
|
|
16
16
|
* It does not claim keys from `handleInput` either. The current dispatch ORDER is
|
|
17
|
-
* load-bearing: `←`/`→` sit after the ticket list's
|
|
17
|
+
* load-bearing: `←`/`→` sit after the ticket list's `/` search branch, where the
|
|
18
18
|
* arrows are inert while the `/` field is open. A base that claimed them first
|
|
19
19
|
* would make them page there — a behaviour change, and GAIA-294 is
|
|
20
20
|
* behaviour-neutral. So the dispatch TABLE lives here once and each subclass calls
|
|
@@ -93,7 +93,7 @@ export declare abstract class ListScreen<Row extends {
|
|
|
93
93
|
* `←` forwards too (GAIA-292 R1).
|
|
94
94
|
*
|
|
95
95
|
* WHERE it is called stays the subclass's decision (GAIA-294 D3) — on the ticket
|
|
96
|
-
* list it must sit after the
|
|
96
|
+
* list it must sit after the `/` search branch, where the arrows are inert
|
|
97
97
|
* while the `/` field is open.
|
|
98
98
|
*/
|
|
99
99
|
protected handlePagingInput(input: string, context: ScreenContext): boolean;
|
|
@@ -103,6 +103,15 @@ export declare abstract class ListScreen<Row extends {
|
|
|
103
103
|
* chain continues.
|
|
104
104
|
*/
|
|
105
105
|
protected handleCursorInput(input: string, context: ScreenContext): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Show `rows` in place of the loaded page until the next read answers — the
|
|
108
|
+
* optimistic narrowing of a search. It keeps the LOADED page number so a
|
|
109
|
+
* keystroke does not flip `page N`; the total becomes the explicit unknown
|
|
110
|
+
* (`N of ?`); a narrowing that dropped rows stops the window positioning
|
|
111
|
+
* the survivors (GAIA-312 D4). An emptied list (a refusal) also drops
|
|
112
|
+
* `hasNext`, so `→` cannot re-send the refused query.
|
|
113
|
+
*/
|
|
114
|
+
showRows(rows: readonly Row[]): void;
|
|
106
115
|
/** `page N` for the strip above the table (count sits beside it). */
|
|
107
116
|
protected pagingSegments(): string[];
|
|
108
117
|
/**
|
|
@@ -24,7 +24,7 @@ import { AsyncScreen } from './async-screen.js';
|
|
|
24
24
|
* enough hooks to be a framework.
|
|
25
25
|
*
|
|
26
26
|
* It does not claim keys from `handleInput` either. The current dispatch ORDER is
|
|
27
|
-
* load-bearing: `←`/`→` sit after the ticket list's
|
|
27
|
+
* load-bearing: `←`/`→` sit after the ticket list's `/` search branch, where the
|
|
28
28
|
* arrows are inert while the `/` field is open. A base that claimed them first
|
|
29
29
|
* would make them page there — a behaviour change, and GAIA-294 is
|
|
30
30
|
* behaviour-neutral. So the dispatch TABLE lives here once and each subclass calls
|
|
@@ -92,7 +92,7 @@ export class ListScreen extends AsyncScreen {
|
|
|
92
92
|
* `←` forwards too (GAIA-292 R1).
|
|
93
93
|
*
|
|
94
94
|
* WHERE it is called stays the subclass's decision (GAIA-294 D3) — on the ticket
|
|
95
|
-
* list it must sit after the
|
|
95
|
+
* list it must sit after the `/` search branch, where the arrows are inert
|
|
96
96
|
* while the `/` field is open.
|
|
97
97
|
*/
|
|
98
98
|
handlePagingInput(input, context) {
|
|
@@ -116,6 +116,24 @@ export class ListScreen extends AsyncScreen {
|
|
|
116
116
|
context.requestDraw();
|
|
117
117
|
return true;
|
|
118
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Show `rows` in place of the loaded page until the next read answers — the
|
|
121
|
+
* optimistic narrowing of a search. It keeps the LOADED page number so a
|
|
122
|
+
* keystroke does not flip `page N`; the total becomes the explicit unknown
|
|
123
|
+
* (`N of ?`); a narrowing that dropped rows stops the window positioning
|
|
124
|
+
* the survivors (GAIA-312 D4). An emptied list (a refusal) also drops
|
|
125
|
+
* `hasNext`, so `→` cannot re-send the refused query.
|
|
126
|
+
*/
|
|
127
|
+
showRows(rows) {
|
|
128
|
+
const current = this.readyValue;
|
|
129
|
+
this.replaceReady({
|
|
130
|
+
hasNext: rows.length === 0 ? false : (current?.hasNext ?? false),
|
|
131
|
+
page: this.loadedPage,
|
|
132
|
+
total: null,
|
|
133
|
+
window: current && rows.length === current.rows.length ? current.window : null,
|
|
134
|
+
rows: [...rows],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
119
137
|
/** `page N` for the strip above the table (count sits beside it). */
|
|
120
138
|
pagingSegments() {
|
|
121
139
|
return [`page ${this.loadedPage + 1}`];
|
|
@@ -25,8 +25,6 @@ export interface ScreenPresentation {
|
|
|
25
25
|
* the id the kernel mounts the body slot's tree under.
|
|
26
26
|
*/
|
|
27
27
|
readonly body: VNode;
|
|
28
|
-
/** Optional React-owned body; native patching never traverses its children. */
|
|
29
|
-
readonly reactBody?: import('react').ReactNode;
|
|
30
28
|
readonly selectable: number;
|
|
31
29
|
readonly selectedIndex: number;
|
|
32
30
|
/**
|
|
@@ -189,6 +187,12 @@ export interface ScreenContext {
|
|
|
189
187
|
* Absent on the non-interactive path, which has no terminal at all.
|
|
190
188
|
*/
|
|
191
189
|
readonly copyToClipboard?: import('../types.js').ClipboardWriter;
|
|
190
|
+
/**
|
|
191
|
+
* The terminal's width in columns, as the kernel last measured it — the one
|
|
192
|
+
* geometry fact a layout breakpoint needs before Yoga has run. Absent on the
|
|
193
|
+
* non-interactive path, which has no terminal; a reader takes a default.
|
|
194
|
+
*/
|
|
195
|
+
readonly terminalColumns?: number | undefined;
|
|
192
196
|
}
|
|
193
197
|
export interface Screen {
|
|
194
198
|
readonly id: ScreenId;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { type VNode } from '@opentui/core';
|
|
1
2
|
import type { StatisticFigure } from './client.js';
|
|
2
|
-
export declare function ComparisonChart({ figure, width, }: {
|
|
3
|
+
export declare function ComparisonChart({ id, figure, width, }: {
|
|
4
|
+
id: string;
|
|
3
5
|
figure: StatisticFigure;
|
|
4
6
|
width: number;
|
|
5
|
-
}):
|
|
7
|
+
}): VNode;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BoxRenderable, h } from '@opentui/core';
|
|
2
2
|
import { FOREGROUND, SERIES_A, SERIES_B, SERIES_C, SERIES_D, SERIES_E, SERIES_F, } from '../lib/theme.js';
|
|
3
|
-
import { BOLD } from '../lib/vnode.js';
|
|
3
|
+
import { BOLD, paragraphNode, plainRun } from '../lib/vnode.js';
|
|
4
4
|
import { displayWidth, truncateToWidth, wrapToWidth } from '../lib/width.js';
|
|
5
5
|
import { formatAxisMagnitude } from './presentation.js';
|
|
6
6
|
const MIN_PLOT_ROWS = 8;
|
|
@@ -20,11 +20,13 @@ const CROSS = '┼';
|
|
|
20
20
|
const INVALID_MESSAGE = 'Comparison data is invalid.';
|
|
21
21
|
const EMPTY_MESSAGE = 'No comparison values for this period.';
|
|
22
22
|
const NARROW_MESSAGE = 'Plot area is too narrow to draw this comparison.';
|
|
23
|
-
export function ComparisonChart({ figure, width, }) {
|
|
23
|
+
export function ComparisonChart({ id, figure, width, }) {
|
|
24
24
|
const rows = layoutChart(figure, Math.max(1, width));
|
|
25
|
-
|
|
25
|
+
// A chart is a fixed grid of rows: the row index is its identity.
|
|
26
|
+
return h(BoxRenderable, { id, flexDirection: 'column', flexShrink: 0 }, ...rows.map((row, index) => chartRow(`${id}-${index}`, row)));
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
+
/** One plot row as one text, its cells merged into runs of equal style. */
|
|
29
|
+
function chartRow(id, cells) {
|
|
28
30
|
const runs = [];
|
|
29
31
|
for (const cell of cells) {
|
|
30
32
|
const last = runs.at(-1);
|
|
@@ -33,7 +35,13 @@ function ChartRow({ cells }) {
|
|
|
33
35
|
else
|
|
34
36
|
runs.push({ text: cell.ch, fg: cell.fg, bold: cell.bold });
|
|
35
37
|
}
|
|
36
|
-
return (
|
|
38
|
+
return paragraphNode({
|
|
39
|
+
id,
|
|
40
|
+
runs: runs.map((run) => plainRun(run.text, run.bold ? BOLD : 0, run.fg)),
|
|
41
|
+
fg: FOREGROUND,
|
|
42
|
+
wrapMode: 'none',
|
|
43
|
+
flexShrink: 0,
|
|
44
|
+
});
|
|
37
45
|
}
|
|
38
46
|
function layoutChart(figure, width) {
|
|
39
47
|
const styles = seriesStyles(figure.measurements);
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
import { type VNode } from '@opentui/core';
|
|
1
2
|
import type { StatisticFigureGroup } from './client.js';
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
export interface StatisticsFiguresProps {
|
|
4
|
+
/** The grid's own id; every group and figure inside derives from it. */
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly groups: readonly StatisticFigureGroup[];
|
|
7
|
+
/** The grid's laid-out width, as last reported through {@link onResize}. */
|
|
8
|
+
readonly width: number;
|
|
9
|
+
/** Called with the grid's new width whenever the layout resizes it. */
|
|
10
|
+
readonly onResize: (width: number) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The figure groups as cards. The charts are laid out for `width` — the
|
|
14
|
+
* screen's last measurement of this grid — and the grid reports its real
|
|
15
|
+
* width after every layout, so a first estimate is corrected on the next draw
|
|
16
|
+
* instead of the charts waiting for a measurement to appear at all.
|
|
17
|
+
*/
|
|
18
|
+
export declare function StatisticsFigures({ id, groups, width, onResize, }: StatisticsFiguresProps): VNode;
|
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { BoxRenderable, h } from '@opentui/core';
|
|
2
|
+
import { BarChart } from '../Component/bar-chart/index.js';
|
|
3
|
+
import { Card } from '../Component/card/index.js';
|
|
4
|
+
import { HeatMap } from '../Component/heat-map/index.js';
|
|
5
|
+
import { FOREGROUND, SERIES_A, SERIES_B, SERIES_C, SERIES_D, } from '../lib/theme.js';
|
|
6
|
+
import { BOLD, paragraphNode, plainRun } from '../lib/vnode.js';
|
|
7
7
|
import { ComparisonChart } from './comparison-chart.js';
|
|
8
8
|
import { formatAxisMagnitude } from './presentation.js';
|
|
9
|
-
|
|
9
|
+
// Per-measurement bar colours: the comparison-series slots, in the order the
|
|
10
|
+
// line chart uses them, so a measurement keeps its colour across widgets.
|
|
11
|
+
const COLORS = [SERIES_A, SERIES_B, SERIES_C, SERIES_D];
|
|
10
12
|
const valueAt = (figure, measurement, key) => figure.measurements[measurement]?.values[key] ?? null;
|
|
11
13
|
const numeric = (value) => value ?? 0;
|
|
12
14
|
const shown = (value) => value === null ? '—' : String(value);
|
|
13
|
-
|
|
15
|
+
const text = (id, content, attributes = 0) => paragraphNode({
|
|
16
|
+
id,
|
|
17
|
+
runs: [plainRun(content, attributes)],
|
|
18
|
+
fg: FOREGROUND,
|
|
19
|
+
});
|
|
20
|
+
const column = (id, gap, ...children) => h(BoxRenderable, { id, flexDirection: 'column', gap }, ...children);
|
|
21
|
+
function matrixDisplay(id, figure, width) {
|
|
14
22
|
// Categories are rows, measurements are columns — matching the web UI's
|
|
15
23
|
// matrix orientation (docs/notes/statistics.md).
|
|
16
24
|
const rawValues = figure.categories.map((category) => figure.measurements.map((_, measurement) => numeric(valueAt(figure, measurement, category.key))));
|
|
@@ -19,7 +27,7 @@ function MatrixDisplay({ figure, width, }) {
|
|
|
19
27
|
// whole matrix's maximum, so columns with a smaller value range still show
|
|
20
28
|
// visible contrast. HeatMap itself only supports one global min/max, so the
|
|
21
29
|
// per-column scaling happens here: values are rescaled to 0-100 percent of
|
|
22
|
-
// their column's own max before being handed to the
|
|
30
|
+
// their column's own max before being handed to the HeatMap module.
|
|
23
31
|
const columnMaximums = figure.measurements.map((_, measurement) => Math.max(0, ...rawValues.map((row) => row[measurement] ?? 0)));
|
|
24
32
|
const shadeValues = rawValues.map((row) => row.map((value, column) => {
|
|
25
33
|
const columnMax = columnMaximums[column] ?? 0;
|
|
@@ -28,7 +36,18 @@ function MatrixDisplay({ figure, width, }) {
|
|
|
28
36
|
const labelWidth = Math.max(1, ...figure.categories.map((category) => category.label.length));
|
|
29
37
|
const cellWidth = Math.max(7, Math.floor((Math.max(width, 20) - labelWidth - 2) /
|
|
30
38
|
Math.max(1, figure.measurements.length)));
|
|
31
|
-
return (
|
|
39
|
+
return HeatMap({
|
|
40
|
+
id,
|
|
41
|
+
data: shadeValues,
|
|
42
|
+
rowLabels: figure.categories.map((category) => category.label),
|
|
43
|
+
colLabels: figure.measurements.map((measurement) => measurement.label),
|
|
44
|
+
cellLabels: labels,
|
|
45
|
+
cellWidth,
|
|
46
|
+
showValues: true,
|
|
47
|
+
showLegend: false,
|
|
48
|
+
minValue: 0,
|
|
49
|
+
maxValue: 100,
|
|
50
|
+
});
|
|
32
51
|
}
|
|
33
52
|
function partialPeriodUnit(figure) {
|
|
34
53
|
return figure.filters.period?.value === '365' ? 'month' : 'day';
|
|
@@ -39,13 +58,19 @@ function partialPeriodUnit(figure) {
|
|
|
39
58
|
// figure's own words. Rendered once here rather than per-widget so every
|
|
40
59
|
// shipped widget (matrix, grouped_horizontal_bars) gets both without having
|
|
41
60
|
// to know about them.
|
|
42
|
-
function
|
|
61
|
+
function figureCaption(id, figure) {
|
|
43
62
|
const partial = figure.categories.some((category) => category.partial);
|
|
44
63
|
if (!partial && !figure.description)
|
|
45
64
|
return null;
|
|
46
|
-
return (
|
|
65
|
+
return column(id, 0, ...(figure.description
|
|
66
|
+
? [text(`${id}-description`, figure.description)]
|
|
67
|
+
: []), ...(partial
|
|
68
|
+
? [
|
|
69
|
+
text(`${id}-partial`, `Current ${partialPeriodUnit(figure)} partial (UTC)`),
|
|
70
|
+
]
|
|
71
|
+
: []));
|
|
47
72
|
}
|
|
48
|
-
function
|
|
73
|
+
function groupedHorizontalBars(id, figure, width) {
|
|
49
74
|
const maximum = Math.max(0, ...figure.categories.flatMap((category) => figure.measurements.map((measurement) => numeric(measurement.values[category.key] ?? null))));
|
|
50
75
|
// Share = this category's slice of the measurement's total across every
|
|
51
76
|
// category (e.g. one model's percent of total token spend) — every
|
|
@@ -63,37 +88,75 @@ function GroupedHorizontalBars({ figure, width, }) {
|
|
|
63
88
|
// into the label-width budget the bar itself needs, which is what let long
|
|
64
89
|
// category (model) labels crowd out or collapse against the bar. Elide it
|
|
65
90
|
// — the category heading above already names the row.
|
|
91
|
+
const single = figure.measurements[0];
|
|
66
92
|
const singleMeasurement = figure.measurements.length === 1;
|
|
67
|
-
return (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
return column(id, 1, ...figure.categories.map((category) => {
|
|
94
|
+
const row = `${id}-${category.key}`;
|
|
95
|
+
return column(row, 0, text(`${row}-label`, category.label, BOLD), BarChart({
|
|
96
|
+
id: `${row}-bars`,
|
|
97
|
+
data: figure.measurements.map((measurement, index) => {
|
|
98
|
+
const value = numeric(measurement.values[category.key] ?? null);
|
|
99
|
+
return {
|
|
100
|
+
label: singleMeasurement
|
|
101
|
+
? ''
|
|
102
|
+
: `${measurement.label}${shareOf(measurement.id, value)}`,
|
|
103
|
+
value,
|
|
104
|
+
color: COLORS[index % COLORS.length],
|
|
105
|
+
};
|
|
106
|
+
}),
|
|
107
|
+
maxValue: maximum,
|
|
108
|
+
// The bar chart's own showValues renders the raw number,
|
|
109
|
+
// which is unreadable at token-spend (billion) scale — the
|
|
110
|
+
// formatted line below (or inline per-row text, for multiple
|
|
111
|
+
// measurements) replaces it.
|
|
112
|
+
showValues: false,
|
|
113
|
+
width,
|
|
114
|
+
}), singleMeasurement && single
|
|
115
|
+
? text(`${row}-value`, `${formatAxisMagnitude(numeric(valueAt(figure, 0, category.key)))}${shareOf(single.id, numeric(valueAt(figure, 0, category.key)))}`)
|
|
116
|
+
: h(BoxRenderable, { id: `${row}-values`, flexDirection: 'row', gap: 2 }, ...figure.measurements.map((measurement, index) => text(`${row}-value-${measurement.id}`, `${measurement.label}: ${formatAxisMagnitude(numeric(valueAt(figure, index, category.key)))}`))));
|
|
117
|
+
}));
|
|
82
118
|
}
|
|
83
|
-
function
|
|
119
|
+
function figureDisplay(id, figure, width) {
|
|
84
120
|
if (figure.empty.is_empty)
|
|
85
|
-
return
|
|
86
|
-
|
|
121
|
+
return text(`${id}-empty`, figure.empty.message ?? 'No data for this selection.');
|
|
122
|
+
const caption = figureCaption(`${id}-caption`, figure);
|
|
123
|
+
const widget = figure.widget === 'matrix'
|
|
124
|
+
? matrixDisplay(`${id}-matrix`, figure, width)
|
|
125
|
+
: figure.widget === 'line'
|
|
126
|
+
? ComparisonChart({ id: `${id}-line`, figure, width })
|
|
127
|
+
: groupedHorizontalBars(`${id}-bars`, figure, width);
|
|
128
|
+
return column(id, 1, ...(caption === null ? [] : [caption]), widget);
|
|
87
129
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
130
|
+
/**
|
|
131
|
+
* The figure groups as cards. The charts are laid out for `width` — the
|
|
132
|
+
* screen's last measurement of this grid — and the grid reports its real
|
|
133
|
+
* width after every layout, so a first estimate is corrected on the next draw
|
|
134
|
+
* instead of the charts waiting for a measurement to appear at all.
|
|
135
|
+
*/
|
|
136
|
+
export function StatisticsFigures({ id, groups, width, onResize, }) {
|
|
93
137
|
const contentWidth = Math.max(1, width - 2);
|
|
94
|
-
return (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
138
|
+
return h(BoxRenderable, {
|
|
139
|
+
id,
|
|
140
|
+
width: '100%',
|
|
141
|
+
paddingLeft: 1,
|
|
142
|
+
paddingRight: 1,
|
|
143
|
+
flexDirection: 'column',
|
|
144
|
+
gap: 0,
|
|
145
|
+
onSizeChange() {
|
|
146
|
+
onResize(this.width);
|
|
147
|
+
},
|
|
148
|
+
}, ...groups.map((group) => column(`${id}-${group.id}`, 0, h(BoxRenderable, { id: `${id}-${group.id}-spacer`, height: 1 }), text(`${id}-${group.id}-label`, group.label, BOLD), ...group.figures.map((figure) => h(BoxRenderable, {
|
|
149
|
+
id: `figure-${figure.id}`,
|
|
150
|
+
width: contentWidth,
|
|
151
|
+
flexShrink: 0,
|
|
152
|
+
flexDirection: 'column',
|
|
153
|
+
}, Card({
|
|
154
|
+
id: `figure-${figure.id}-card`,
|
|
155
|
+
title: figure.label,
|
|
156
|
+
width: contentWidth,
|
|
157
|
+
paddingX: 1,
|
|
158
|
+
children: [
|
|
159
|
+
column(`figure-${figure.id}-display`, 1, figureDisplay(`figure-${figure.id}-widget`, figure, Math.max(15, contentWidth - 4))),
|
|
160
|
+
],
|
|
161
|
+
}))))));
|
|
99
162
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type { StatisticFigureGroup } from './client.js';
|
|
2
2
|
/**
|
|
3
|
-
* Formats a
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* "844.0M". This formatter is passed in as a `tickFormatter` call-site
|
|
7
|
-
* override; the vendored chart lib itself is untouched.
|
|
3
|
+
* Formats a magnitude with the correct thousands/millions/billions suffix,
|
|
4
|
+
* dividing as often as the size needs: 844,000,000 is "844.0M", never the
|
|
5
|
+
* "844.0k" a single division by 1000 gives.
|
|
8
6
|
*/
|
|
9
7
|
export declare function formatAxisMagnitude(value: number): string;
|
|
10
8
|
export declare function groupedFigureLines(groups: readonly StatisticFigureGroup[]): string[];
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
const displayValue = (value) => value === null || value === undefined ? '—' : String(value);
|
|
2
2
|
/**
|
|
3
|
-
* Formats a
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* "844.0M". This formatter is passed in as a `tickFormatter` call-site
|
|
7
|
-
* override; the vendored chart lib itself is untouched.
|
|
3
|
+
* Formats a magnitude with the correct thousands/millions/billions suffix,
|
|
4
|
+
* dividing as often as the size needs: 844,000,000 is "844.0M", never the
|
|
5
|
+
* "844.0k" a single division by 1000 gives.
|
|
8
6
|
*/
|
|
9
7
|
export function formatAxisMagnitude(value) {
|
|
10
8
|
const sign = value < 0 ? '-' : '';
|