flexdesk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/THIRD_PARTY_LICENSES.md +53 -0
  4. package/css/base.css +3439 -0
  5. package/css/flexdesk.css +5797 -0
  6. package/css/overrides.css +2349 -0
  7. package/css/reset.css +46 -0
  8. package/css/tokens.css +137 -0
  9. package/dist/charts.js +780 -0
  10. package/dist/charts.js.map +7 -0
  11. package/dist/chunk-3PHCPZHT.js +222 -0
  12. package/dist/chunk-3PHCPZHT.js.map +7 -0
  13. package/dist/chunk-CT4YXXLP.js +1770 -0
  14. package/dist/chunk-CT4YXXLP.js.map +7 -0
  15. package/dist/chunk-DRYCDMEG.js +2217 -0
  16. package/dist/chunk-DRYCDMEG.js.map +7 -0
  17. package/dist/chunk-DVU44T77.js +855 -0
  18. package/dist/chunk-DVU44T77.js.map +7 -0
  19. package/dist/chunk-FL5KFNQH.js +640 -0
  20. package/dist/chunk-FL5KFNQH.js.map +7 -0
  21. package/dist/chunk-FOOS3T5L.js +117 -0
  22. package/dist/chunk-FOOS3T5L.js.map +7 -0
  23. package/dist/chunk-JYWURG5T.js +32 -0
  24. package/dist/chunk-JYWURG5T.js.map +7 -0
  25. package/dist/chunk-TLZUUFOE.js +276 -0
  26. package/dist/chunk-TLZUUFOE.js.map +7 -0
  27. package/dist/chunk-UCJ2WD4D.js +625 -0
  28. package/dist/chunk-UCJ2WD4D.js.map +7 -0
  29. package/dist/chunk-WVFGV5FT.js +205 -0
  30. package/dist/chunk-WVFGV5FT.js.map +7 -0
  31. package/dist/core.js +1098 -0
  32. package/dist/core.js.map +7 -0
  33. package/dist/editor.js +1661 -0
  34. package/dist/editor.js.map +7 -0
  35. package/dist/flexdesk.css +5797 -0
  36. package/dist/host.js +132 -0
  37. package/dist/host.js.map +7 -0
  38. package/dist/reset.css +46 -0
  39. package/dist/tile_registry-6WZPTUZV.js +28 -0
  40. package/dist/tile_registry-6WZPTUZV.js.map +7 -0
  41. package/dist/tiles.js +2068 -0
  42. package/dist/tiles.js.map +7 -0
  43. package/dist/tokens.css +137 -0
  44. package/dist/widgets.js +5438 -0
  45. package/dist/widgets.js.map +7 -0
  46. package/dist/wm.js +4830 -0
  47. package/dist/wm.js.map +7 -0
  48. package/package.json +80 -0
  49. package/src/charts/chart_types.js +699 -0
  50. package/src/charts/data_series_plot_window.js +761 -0
  51. package/src/charts/downsample.js +119 -0
  52. package/src/charts/plot_config.js +176 -0
  53. package/src/charts/plot_popout_window.js +1560 -0
  54. package/src/charts/plotly_wrapper.js +432 -0
  55. package/src/core/event_bus.js +230 -0
  56. package/src/core/logging.js +300 -0
  57. package/src/core/settings.js +813 -0
  58. package/src/core/state_guard.js +382 -0
  59. package/src/core/state_guard_config.js +309 -0
  60. package/src/core/state_machine.js +258 -0
  61. package/src/editor/editor_pane.js +264 -0
  62. package/src/editor/monaco_editor_factory.js +449 -0
  63. package/src/editor/monaco_loader.js +74 -0
  64. package/src/editor/notebook_search.js +460 -0
  65. package/src/editor/notebook_tab_bar.js +456 -0
  66. package/src/editor/notebook_undo_manager.js +108 -0
  67. package/src/editor/split_pane_container.js +197 -0
  68. package/src/help/help_modal.js +409 -0
  69. package/src/help/help_registry.js +65 -0
  70. package/src/host/host.js +69 -0
  71. package/src/host/pywebview_host.js +133 -0
  72. package/src/tiles/config_schema.js +256 -0
  73. package/src/tiles/layout_persistence.js +662 -0
  74. package/src/tiles/tile_base.js +785 -0
  75. package/src/tiles/tile_grid.js +905 -0
  76. package/src/tiles/tile_registry.js +136 -0
  77. package/src/tiling/command_palette.js +328 -0
  78. package/src/tiling/content_registry.js +110 -0
  79. package/src/tiling/desktops.js +133 -0
  80. package/src/tiling/entity_sources.js +179 -0
  81. package/src/tiling/keymap.js +156 -0
  82. package/src/tiling/kind_taxonomy.js +172 -0
  83. package/src/tiling/landing_table.js +717 -0
  84. package/src/tiling/loading_overlay.js +33 -0
  85. package/src/tiling/nav_panel.js +142 -0
  86. package/src/tiling/page_factory.js +215 -0
  87. package/src/tiling/panel_keys.js +145 -0
  88. package/src/tiling/shell.js +437 -0
  89. package/src/tiling/tile_breadcrumb.js +222 -0
  90. package/src/tiling/tile_renderer.js +459 -0
  91. package/src/tiling/tile_tab_menu.js +340 -0
  92. package/src/tiling/tile_tree.js +880 -0
  93. package/src/tiling/wm.js +1352 -0
  94. package/src/ui/base/component_base.js +30 -0
  95. package/src/ui/base/controller_base.js +48 -0
  96. package/src/ui/base/page_base.js +102 -0
  97. package/src/ui/components/about_dialog.js +224 -0
  98. package/src/ui/components/action_dropdown.js +389 -0
  99. package/src/ui/components/attribute_list_editor.js +652 -0
  100. package/src/ui/components/autocomplete_field.js +732 -0
  101. package/src/ui/components/computing_status_window.js +81 -0
  102. package/src/ui/components/confirm_dialog.js +307 -0
  103. package/src/ui/components/context_menu.js +87 -0
  104. package/src/ui/components/data_table.js +2228 -0
  105. package/src/ui/components/detail_header.js +177 -0
  106. package/src/ui/components/drag_reorder.js +0 -0
  107. package/src/ui/components/gallery_picker.js +147 -0
  108. package/src/ui/components/inline_renamer.js +129 -0
  109. package/src/ui/components/managed_window.js +804 -0
  110. package/src/ui/components/modal.js +512 -0
  111. package/src/ui/components/notification_history.js +264 -0
  112. package/src/ui/components/slide_out_panel.js +185 -0
  113. package/src/ui/components/slider_field.js +122 -0
  114. package/src/ui/components/sortable_list.js +309 -0
  115. package/src/ui/components/table_state_store.js +81 -0
  116. package/src/ui/components/toast.js +59 -0
  117. package/src/ui/components/tree_view.js +460 -0
  118. package/src/ui/components/window_taskbar.js +143 -0
  119. package/src/ui/controllers/panel_state_machine.js +634 -0
  120. package/src/ui/controllers/window_chrome_controller.js +659 -0
  121. package/src/ui/notification_center.js +576 -0
  122. package/src/ui/utils/overlay_scrollbar.js +492 -0
  123. package/src/ui/utils/raf_resize_observer.js +31 -0
  124. package/src/ui/utils/tooltip_service.js +406 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * downsample.js
3
+ *
4
+ * Client-side time-series decimation for the render path. A long live run
5
+ * accumulates thousands of points per series; feeding all of them to
6
+ * Plotly.react() every refresh is what makes a running dashboard crawl.
7
+ * These helpers cap the points actually drawn while preserving the visual
8
+ * shape.
9
+ *
10
+ * Two strategies, mirroring EcoSim (App/downsampler.py = LTTB,
11
+ * streaming/server.py = stride decimation):
12
+ *
13
+ * • strideDownsampleShared — one set of indices applied to the shared x
14
+ * axis and EVERY series. Cheap, gap-safe (nulls keep their slot), and
15
+ * alignment-safe (all series sample the same x), so it works for
16
+ * stacked/area charts and multi-series subplots. This is the default
17
+ * used by the dashboard tiles.
18
+ *
19
+ * • lttb — Largest-Triangle-Three-Buckets for a single (x, y) line.
20
+ * Higher visual fidelity (keeps peaks/troughs) but each series picks
21
+ * its own x, so it's only safe for independent line/scatter traces
22
+ * with finite y. Offered for callers that want it.
23
+ */
24
+
25
+ /**
26
+ * Pick ~targetPoints evenly-spaced indices out of [0, n), always including
27
+ * the first and last. Returns an ascending Int array; returns null when no
28
+ * decimation is needed (n <= targetPoints) so callers can skip the copy.
29
+ *
30
+ * @param {number} n
31
+ * @param {number} targetPoints
32
+ * @returns {number[]|null}
33
+ */
34
+ export function strideIndices(n, targetPoints) {
35
+ if (!Number.isFinite(n) || n <= 0) return null;
36
+ const target = Math.max(2, Math.floor(targetPoints) || 0);
37
+ if (n <= target) return null;
38
+ const step = (n - 1) / (target - 1);
39
+ const idx = new Array(target);
40
+ for (let i = 0; i < target; i++) idx[i] = Math.round(i * step);
41
+ idx[target - 1] = n - 1; // pin the last point exactly
42
+ // Dedupe (rounding can collide on dense targets) while staying ascending.
43
+ let w = 0;
44
+ for (let r = 0; r < idx.length; r++) {
45
+ if (w === 0 || idx[r] > idx[w - 1]) idx[w++] = idx[r];
46
+ }
47
+ idx.length = w;
48
+ return idx;
49
+ }
50
+
51
+ /**
52
+ * Decimate a shared x axis plus a set of aligned series using one common
53
+ * set of indices, so every series stays sampled at the same x (alignment-
54
+ * and gap-safe). No-op (returns the inputs untouched) when under threshold.
55
+ *
56
+ * @param {Array<number>} time — shared x values
57
+ * @param {Object<string, Array>} series — name → aligned value array
58
+ * @param {number} targetPoints
59
+ * @returns {{ time: Array<number>, series: Object<string, Array> }}
60
+ */
61
+ export function strideDownsampleShared(time, series, targetPoints) {
62
+ const idx = strideIndices(time?.length ?? 0, targetPoints);
63
+ if (!idx) return { time, series };
64
+ const dsTime = idx.map((i) => time[i]);
65
+ const dsSeries = {};
66
+ for (const [name, arr] of Object.entries(series || {})) {
67
+ if (!Array.isArray(arr)) { dsSeries[name] = arr; continue; }
68
+ dsSeries[name] = idx.map((i) => (i < arr.length ? arr[i] : null));
69
+ }
70
+ return { time: dsTime, series: dsSeries };
71
+ }
72
+
73
+ /**
74
+ * Largest-Triangle-Three-Buckets for one line. Preserves the first and
75
+ * last points and the most "feature-bearing" point per bucket. Assumes
76
+ * ascending, finite x; y entries that are null/NaN are treated as 0 for
77
+ * the area math (callers with gappy series should prefer the stride path).
78
+ *
79
+ * @param {Array<number>} x
80
+ * @param {Array<number>} y
81
+ * @param {number} threshold — target point count
82
+ * @returns {{ x: Array<number>, y: Array<number> }}
83
+ */
84
+ export function lttb(x, y, threshold) {
85
+ const n = Math.min(x?.length ?? 0, y?.length ?? 0);
86
+ const target = Math.floor(threshold) || 0;
87
+ if (target <= 0 || target >= n) return { x: x.slice(0, n), y: y.slice(0, n) };
88
+ if (target < 3) return { x: [x[0], x[n - 1]], y: [y[0], y[n - 1]] };
89
+
90
+ const num = (v) => (Number.isFinite(v) ? v : 0);
91
+ const outX = [x[0]];
92
+ const outY = [y[0]];
93
+ const bucketSize = (n - 2) / (target - 2);
94
+ let a = 0;
95
+
96
+ for (let i = 0; i < target - 2; i++) {
97
+ let nextStart = Math.floor((i + 1) * bucketSize) + 1;
98
+ let nextEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, n);
99
+ if (nextEnd <= nextStart) nextEnd = Math.min(nextStart + 1, n);
100
+ let avgX = 0, avgY = 0;
101
+ const count = nextEnd - nextStart;
102
+ for (let j = nextStart; j < nextEnd; j++) { avgX += x[j]; avgY += num(y[j]); }
103
+ avgX /= count; avgY /= count;
104
+
105
+ const curStart = Math.floor(i * bucketSize) + 1;
106
+ const curEnd = Math.min(Math.floor((i + 1) * bucketSize) + 1, n);
107
+ const ax = x[a], ay = num(y[a]);
108
+ let maxArea = -1, chosen = curStart;
109
+ for (let j = curStart; j < curEnd; j++) {
110
+ const area = Math.abs((ax - avgX) * (num(y[j]) - ay) - (ax - x[j]) * (avgY - ay));
111
+ if (area > maxArea) { maxArea = area; chosen = j; }
112
+ }
113
+ outX.push(x[chosen]); outY.push(y[chosen]);
114
+ a = chosen;
115
+ }
116
+
117
+ outX.push(x[n - 1]); outY.push(y[n - 1]);
118
+ return { x: outX, y: outY };
119
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * plot_config.js — the shape of a plot, independent of what is being plotted.
3
+ *
4
+ * Subplot grid layouts, the default subplot, the legacy-format migration, and the
5
+ * NaN strategies (gap / zero / hold / interpolate). Not one line of it knows what
6
+ * an economy is.
7
+ *
8
+ * It lived in ui/js/notebook/cells/plot_cell.js — an EcoAgent notebook cell with
9
+ * 60 domain words in it — and that single import was the only thing keeping the
10
+ * plot windows out of @flexdesk/charts. Five modules imported these helpers THROUGH the
11
+ * cell, which is how you end up with a chart library that depends on a notebook.
12
+ *
13
+ * plot_cell.js re-exports everything here, so its existing importers are unchanged.
14
+ */
15
+
16
+ import { getSeriesColor } from './plotly_wrapper.js';
17
+
18
+
19
+ let _uid = 0;
20
+ function uid(prefix = 'p') { return `${prefix}-${++_uid}-${Date.now().toString(36)}`; }
21
+
22
+ /**
23
+ * Apply NaN handling strategy to a data array.
24
+ * @param {number[]} data
25
+ * @param {'gap'|'zero'|'hold'|'interpolate'} strategy
26
+ * @param {number[]} xData — for interpolation
27
+ * @returns {Array}
28
+ */
29
+ function applyNanHandling(data, strategy, xData) {
30
+ const isMissing = (v) => v === null || v === undefined || (typeof v === 'number' && !Number.isFinite(v));
31
+
32
+ if (strategy === 'zero') return data.map(v => isMissing(v) ? 0 : v);
33
+
34
+ if (strategy === 'hold') {
35
+ const result = [];
36
+ let last = null;
37
+ for (const v of data) {
38
+ if (isMissing(v)) result.push(last);
39
+ else { last = v; result.push(v); }
40
+ }
41
+ return result;
42
+ }
43
+
44
+ if (strategy === 'interpolate') {
45
+ const result = [...data];
46
+ for (let i = 0; i < result.length; i++) {
47
+ if (!isMissing(result[i])) continue;
48
+ let left = -1, right = -1;
49
+ for (let j = i - 1; j >= 0; j--) { if (!isMissing(result[j])) { left = j; break; } }
50
+ for (let j = i + 1; j < result.length; j++) { if (!isMissing(result[j])) { right = j; break; } }
51
+ if (left >= 0 && right >= 0 && xData) {
52
+ const frac = (xData[i] - xData[left]) / (xData[right] - xData[left]);
53
+ result[i] = result[left] + frac * (result[right] - result[left]);
54
+ } else if (left >= 0) {
55
+ result[i] = result[left];
56
+ } else if (right >= 0) {
57
+ result[i] = result[right];
58
+ } else {
59
+ result[i] = null;
60
+ }
61
+ }
62
+ return result;
63
+ }
64
+
65
+ // 'gap' (default) — replace with null for Plotly gap rendering
66
+ return data.map(v => isMissing(v) ? null : v);
67
+ }
68
+
69
+ // ─── Layout templates (matching figure_cell.js) ─────────────────────────────
70
+
71
+ const PLOT_LAYOUTS = Object.freeze({
72
+ '1x1': { label: 'Single', slots: 1, grid: { columns: '1fr', rows: '1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }] },
73
+ '1x2': { label: 'Side by side', slots: 2, grid: { columns: '1fr 1fr', rows: '1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }, { slot: 1, gridArea: '1/2/2/3' }] },
74
+ '2x1': { label: 'Stacked', slots: 2, grid: { columns: '1fr', rows: '1fr 1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }, { slot: 1, gridArea: '2/1/3/2' }] },
75
+ '2x2': { label: '2\u00d72 Grid', slots: 4, grid: { columns: '1fr 1fr', rows: '1fr 1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }, { slot: 1, gridArea: '1/2/2/3' }, { slot: 2, gridArea: '2/1/3/2' }, { slot: 3, gridArea: '2/2/3/3' }] },
76
+ '1x3': { label: 'Three columns', slots: 3, grid: { columns: '1fr 1fr 1fr', rows: '1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }, { slot: 1, gridArea: '1/2/2/3' }, { slot: 2, gridArea: '1/3/2/4' }] },
77
+ 'wide-top': { label: '1 + 2', slots: 3, grid: { columns: '1fr 1fr', rows: '1fr 1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/3' }, { slot: 1, gridArea: '2/1/3/2' }, { slot: 2, gridArea: '2/2/3/3' }] },
78
+ 'wide-bottom': { label: '2 + 1', slots: 3, grid: { columns: '1fr 1fr', rows: '1fr 1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }, { slot: 1, gridArea: '1/2/2/3' }, { slot: 2, gridArea: '2/1/3/3' }] },
79
+ });
80
+
81
+ function makeDefaultSubplot(index) {
82
+ return {
83
+ id: uid('sp'),
84
+ displayName: '',
85
+ chartType: 'line',
86
+ legendPosition: 'hidden',
87
+ interpolation: 'linear',
88
+ nanHandling: 'gap',
89
+ showDataPoints: false,
90
+ showHpTrend: false,
91
+ showHpCycle: false,
92
+ hpLambda: 1600,
93
+ xAxis: { useTime: true, variable: '' },
94
+ zAxis: null,
95
+ yAxes: [{
96
+ id: uid('y'),
97
+ label: '',
98
+ scale: 'linear',
99
+ position: 'left',
100
+ min: null,
101
+ max: null,
102
+ step: null,
103
+ series: [],
104
+ }],
105
+ };
106
+ }
107
+
108
+ function migrateData(data) {
109
+ // Already in multi-layout format
110
+ if (data?.layout && Array.isArray(data?.subplots)) {
111
+ // Migrate caption → description (old field name)
112
+ if ('caption' in data && !('description' in data)) {
113
+ data.description = data.caption;
114
+ data.caption = '';
115
+ }
116
+ return data;
117
+ }
118
+
119
+ // Migrate legacy single-chart format → multi-layout with one subplot
120
+ const legacySingle = migrateSingleChart(data);
121
+ return {
122
+ layout: '1x1',
123
+ subplots: [{ id: uid('sp'), ...legacySingle }],
124
+ caption: '',
125
+ description: data?.caption ?? data?.description ?? '',
126
+ doc: data?.doc ?? '',
127
+ };
128
+ }
129
+
130
+ /** Migrate legacy flat series[]/variables[] to yAxes[] single-chart format. */
131
+ function migrateSingleChart(data) {
132
+ if (data?.yAxes) {
133
+ return {
134
+ displayName: data.displayName ?? '',
135
+ chartType: data.chartType ?? 'line',
136
+ legendPosition: data.legendPosition ?? 'hidden',
137
+ interpolation: data.interpolation ?? 'linear',
138
+ nanHandling: data.nanHandling ?? 'gap',
139
+ showDataPoints: data.showDataPoints ?? false,
140
+ yAxes: data.yAxes,
141
+ };
142
+ }
143
+
144
+ const series = data?.series ?? data?.variables?.map((v, i) => ({
145
+ variable: v, label: '', color: getSeriesColor(i),
146
+ })) ?? [];
147
+
148
+ return {
149
+ displayName: data?.title ?? data?.displayName ?? '',
150
+ chartType: data?.chartType ?? data?.widgetType ?? 'line',
151
+ legendPosition: data?.legendPosition ?? 'hidden',
152
+ interpolation: data?.interpolation ?? 'linear',
153
+ nanHandling: data?.nanHandling ?? 'gap',
154
+ showDataPoints: data?.showDataPoints ?? false,
155
+ yAxes: [{
156
+ id: uid('y'),
157
+ label: data?.yLabel ?? '',
158
+ scale: 'linear',
159
+ position: 'left',
160
+ min: null,
161
+ max: null,
162
+ series: series.map((s, i) => ({
163
+ id: s.id ?? uid('s'),
164
+ variable: s.variable ?? '',
165
+ label: s.label ?? '',
166
+ color: s.color ?? getSeriesColor(i),
167
+ lineWidth: s.lineWidth ?? 2,
168
+ lineStyle: s.lineStyle ?? 'solid',
169
+ stackGroup: s.stackGroup ?? null,
170
+ ...(s.overlay ? { overlay: s.overlay } : {}),
171
+ })),
172
+ }],
173
+ };
174
+ }
175
+
176
+ export { uid, applyNanHandling, PLOT_LAYOUTS, makeDefaultSubplot, migrateData, migrateSingleChart };