flexdesk 0.16.1 → 0.17.1
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/CHANGELOG.md +112 -0
- package/LICENSE +0 -0
- package/README.md +2435 -42
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/canvas.js +18 -0
- package/charts.js +21 -0
- package/core.js +14 -0
- package/css/base.css +1834 -75
- package/css/flexdesk.css +3165 -159
- package/css/overrides.css +16 -0
- package/css/reset.css +0 -0
- package/css/tokens.css +0 -0
- package/dist/canvas.js +6 -239
- package/dist/canvas.js.map +3 -3
- package/dist/charts.js +707 -18
- package/dist/charts.js.map +4 -4
- package/dist/chunk-3PHCPZHT.js +0 -0
- package/dist/chunk-3PHCPZHT.js.map +0 -0
- package/dist/chunk-4A7SJC3T.js +503 -0
- package/dist/chunk-4A7SJC3T.js.map +7 -0
- package/dist/{chunk-6JSOVNID.js → chunk-CVMCZZX2.js} +635 -10
- package/dist/chunk-CVMCZZX2.js.map +7 -0
- package/dist/chunk-D3COLZZ2.js +1311 -0
- package/dist/chunk-D3COLZZ2.js.map +7 -0
- package/dist/chunk-FOOS3T5L.js +0 -0
- package/dist/chunk-FOOS3T5L.js.map +0 -0
- package/dist/{chunk-SN4LSOAR.js → chunk-GOQLR5WR.js} +452 -2
- package/dist/chunk-GOQLR5WR.js.map +7 -0
- package/dist/{chunk-FL5KFNQH.js → chunk-HNU6MM2B.js} +52 -17
- package/dist/chunk-HNU6MM2B.js.map +7 -0
- package/dist/chunk-HYJYYEPK.js +375 -0
- package/dist/chunk-HYJYYEPK.js.map +7 -0
- package/dist/chunk-JKZLNKG6.js +2041 -0
- package/dist/chunk-JKZLNKG6.js.map +7 -0
- package/dist/chunk-JYWURG5T.js +0 -0
- package/dist/chunk-JYWURG5T.js.map +0 -0
- package/dist/{chunk-FBGP5KN5.js → chunk-LWV23YVE.js} +5073 -532
- package/dist/chunk-LWV23YVE.js.map +7 -0
- package/dist/chunk-N7HC3ADV.js +30 -0
- package/dist/chunk-N7HC3ADV.js.map +7 -0
- package/dist/{chunk-RQM5Z2MU.js → chunk-RMTRWWEO.js} +633 -311
- package/dist/chunk-RMTRWWEO.js.map +7 -0
- package/dist/{chunk-NLTOY57M.js → chunk-UURZ37SD.js} +28 -48
- package/dist/chunk-UURZ37SD.js.map +7 -0
- package/dist/{chunk-B5Q53LAJ.js → chunk-UWO43BZU.js} +2 -2
- package/dist/chunk-UWO43BZU.js.map +7 -0
- package/dist/chunk-WVFGV5FT.js +0 -0
- package/dist/chunk-WVFGV5FT.js.map +0 -0
- package/dist/core.js +170 -118
- package/dist/core.js.map +2 -2
- package/dist/editor.js +156 -70
- package/dist/editor.js.map +2 -2
- package/dist/flexdesk.css +3165 -159
- package/dist/flow.js +2789 -306
- package/dist/flow.js.map +4 -4
- package/dist/host.js +109 -3
- package/dist/host.js.map +3 -3
- package/dist/reset.css +0 -0
- package/dist/tile_registry-6WZPTUZV.js +0 -0
- package/dist/tile_registry-6WZPTUZV.js.map +0 -0
- package/dist/tiles.js +657 -234
- package/dist/tiles.js.map +4 -4
- package/dist/tokens.css +0 -0
- package/dist/widgets.js +3290 -2124
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +1227 -347
- package/dist/wm.js.map +4 -4
- package/editor.js +20 -0
- package/flow.js +21 -0
- package/host.js +14 -0
- package/package.json +12 -1
- package/src/canvas/canvas_adapter.js +5 -0
- package/src/canvas/edge_router.js +0 -0
- package/src/canvas/graph/connector_router.js +0 -0
- package/src/canvas/graph/history_service.js +0 -0
- package/src/canvas/graph/node_platform.js +0 -0
- package/src/canvas/index.js +1 -0
- package/src/canvas/layered_layout.js +244 -0
- package/src/charts/chart_types.js +0 -0
- package/src/charts/data_series_plot_window.js +0 -0
- package/src/charts/downsample.js +2 -2
- package/src/charts/plot_config.js +53 -29
- package/src/charts/plot_config_editor.js +627 -0
- package/src/charts/plot_grid.js +449 -0
- package/src/charts/plot_popout_window.js +103 -317
- package/src/charts/plotly_wrapper.js +22 -2
- package/src/charts/series_transforms.js +206 -0
- package/src/core/event_bus.js +0 -0
- package/src/core/logging.js +1 -1
- package/src/core/settings.js +105 -19
- package/src/core/state_guard.js +93 -51
- package/src/core/state_guard_config.js +85 -74
- package/src/core/state_machine.js +0 -0
- package/src/editor/editor_pane.js +3 -3
- package/src/editor/monaco_editor_factory.js +124 -60
- package/src/editor/monaco_loader.js +103 -28
- package/src/editor/notebook_search.js +0 -0
- package/src/editor/notebook_tab_bar.js +38 -52
- package/src/editor/notebook_undo_manager.js +0 -0
- package/src/editor/split_pane_container.js +6 -3
- package/src/flow/canvas_editor/connections.js +90 -0
- package/src/flow/canvas_editor/editor.js +2351 -153
- package/src/flow/canvas_editor/index.js +2 -1
- package/src/flow/canvas_editor/layout.js +119 -12
- package/src/flow/canvas_editor/strings.js +80 -0
- package/src/flow/kit/always_before.js +0 -0
- package/src/flow/kit/catalogue.js +97 -3
- package/src/flow/kit/chip_input.js +0 -0
- package/src/flow/kit/dom.js +0 -0
- package/src/flow/kit/findings.js +0 -0
- package/src/flow/kit/history.js +0 -0
- package/src/flow/kit/index.js +3 -3
- package/src/flow/kit/keys.js +7 -1
- package/src/flow/kit/logic_graph.js +97 -15
- package/src/flow/kit/references.js +0 -0
- package/src/flow/kit/settings_panel.js +0 -0
- package/src/flow/kit/settings_schema.js +2 -1
- package/src/flow/kit/step_picker.js +0 -0
- package/src/flow/kit/strings.js +3 -1
- package/src/flow/kit/value_picker.js +0 -0
- package/src/flow/kit/widgets.js +86 -109
- package/src/flow/lanes/editor.js +136 -14
- package/src/flow/lanes/index.js +3 -1
- package/src/flow/lanes/layout.js +0 -0
- package/src/flow/lanes/pipeline.js +44 -2
- package/src/flow/lanes/preview.js +0 -0
- package/src/flow/lanes/strings.js +1 -0
- package/src/flow/outline/editor.js +0 -0
- package/src/flow/outline/index.js +0 -0
- package/src/flow/outline/mapping.js +0 -0
- package/src/flow/outline/menu.js +0 -0
- package/src/flow/outline/operations.js +0 -0
- package/src/flow/outline/recognise.js +0 -0
- package/src/flow/outline/strings.js +0 -0
- package/src/help/help_modal.js +168 -42
- package/src/help/help_registry.js +12 -1
- package/src/host/external_links.js +0 -0
- package/src/host/file_picker.js +117 -0
- package/src/host/host.js +10 -2
- package/src/host/photino_host.js +9 -0
- package/src/host/photino_host.md +21 -3
- package/src/host/pywebview_host.js +10 -0
- package/src/tiles/chart_tiles.js +53 -245
- package/src/tiles/config_schema.js +8 -0
- package/src/tiles/figure_tile.js +322 -0
- package/src/tiles/layout_persistence.js +3 -3
- package/src/tiles/list_tiles.js +7 -7
- package/src/tiles/scope_picker.js +0 -0
- package/src/tiles/tile_base.js +1 -1
- package/src/tiles/tile_config_form.js +442 -0
- package/src/tiles/tile_grid.js +2 -0
- package/src/tiles/tile_registry.js +0 -0
- package/src/tiling/bottom_panel_tabs.js +67 -99
- package/src/tiling/command_palette.js +64 -19
- package/src/tiling/content_registry.js +4 -1
- package/src/tiling/desktops.js +11 -1
- package/src/tiling/entity_sources.js +0 -0
- package/src/tiling/keymap.js +403 -128
- package/src/tiling/kind_taxonomy.js +78 -0
- package/src/tiling/landing_table.js +0 -0
- package/src/tiling/loading_overlay.js +0 -0
- package/src/tiling/nav_panel.js +59 -10
- package/src/tiling/page_factory.js +5 -5
- package/src/tiling/panel_availability.js +49 -0
- package/src/tiling/panel_keys.js +0 -0
- package/src/tiling/shell.js +114 -12
- package/src/tiling/tab_strip.js +0 -0
- package/src/tiling/tile_breadcrumb.js +0 -0
- package/src/tiling/tile_renderer.js +105 -9
- package/src/tiling/tile_tab_menu.js +0 -0
- package/src/tiling/tile_tree.js +0 -0
- package/src/tiling/wm.js +562 -30
- package/src/tiling/zoom.js +0 -0
- package/src/ui/base/component_base.js +0 -0
- package/src/ui/base/controller_base.js +0 -0
- package/src/ui/base/page_base.js +0 -0
- package/src/ui/components/about_dialog.js +0 -0
- package/src/ui/components/action_dropdown.js +30 -2
- package/src/ui/components/alert_log.js +0 -0
- package/src/ui/components/alerts_pane.js +0 -0
- package/src/ui/components/attribute_list_editor.js +0 -0
- package/src/ui/components/autocomplete_field.js +0 -0
- package/src/ui/components/cell_editor_host.js +293 -0
- package/src/ui/components/cell_editors.js +480 -0
- package/src/ui/components/cell_grid.js +761 -0
- package/src/ui/components/cell_paste.js +243 -0
- package/src/ui/components/cell_selection.js +220 -0
- package/src/ui/components/choice_controls.js +351 -0
- package/src/ui/components/color_field.js +211 -0
- package/src/ui/components/color_list_field.js +210 -0
- package/src/ui/components/column_fit.js +0 -0
- package/src/ui/components/column_groups.js +314 -0
- package/src/ui/components/column_order.js +142 -0
- package/src/ui/components/computing_status_window.js +98 -25
- package/src/ui/components/confirm_dialog.js +81 -38
- package/src/ui/components/context_menu.js +2 -2
- package/src/ui/components/conversation_view.js +440 -0
- package/src/ui/components/crosstab.js +289 -0
- package/src/ui/components/data_table.js +696 -71
- package/src/ui/components/detail_header.js +22 -11
- package/src/ui/components/document_outline.js +397 -0
- package/src/ui/components/drag_reorder.js +0 -0
- package/src/ui/components/fact_table.js +0 -0
- package/src/ui/components/fill_handle.js +322 -0
- package/src/ui/components/gallery_picker.js +19 -8
- package/src/ui/components/header_actions.js +55 -0
- package/src/ui/components/inline_renamer.js +0 -0
- package/src/ui/components/job_progress.js +51 -0
- package/src/ui/components/managed_window.js +27 -4
- package/src/ui/components/menu_bar.js +745 -0
- package/src/ui/components/modal.js +449 -75
- package/src/ui/components/notification_history.js +3 -3
- package/src/ui/components/progress.js +483 -0
- package/src/ui/components/record_list.js +0 -0
- package/src/ui/components/row_activation.js +0 -0
- package/src/ui/components/section_stack.js +298 -0
- package/src/ui/components/series.js +173 -0
- package/src/ui/components/settings_view.js +634 -0
- package/src/ui/components/slide_out_panel.js +43 -6
- package/src/ui/components/slider_field.js +275 -18
- package/src/ui/components/sortable_list.js +0 -0
- package/src/ui/components/staged_edits.js +361 -0
- package/src/ui/components/staged_history.js +172 -0
- package/src/ui/components/table_filter_chips.js +0 -0
- package/src/ui/components/table_state_store.js +0 -0
- package/src/ui/components/table_structure.js +182 -0
- package/src/ui/components/table_values.js +0 -0
- package/src/ui/components/tabs.js +497 -0
- package/src/ui/components/tag_input.js +215 -0
- package/src/ui/components/toast.js +25 -8
- package/src/ui/components/toggle_field.js +175 -0
- package/src/ui/components/tree_view.js +55 -34
- package/src/ui/components/tsv.js +74 -0
- package/src/ui/components/window_taskbar.js +0 -0
- package/src/ui/components/wizard.js +563 -0
- package/src/ui/controllers/window_chrome_controller.js +2 -2
- package/src/ui/notification_center.js +375 -119
- package/src/ui/utils/auto_scrollbars.js +0 -0
- package/src/ui/utils/markdown.js +709 -0
- package/src/ui/utils/native_context_menu.js +0 -0
- package/src/ui/utils/overlay_scrollbar.js +16 -12
- package/src/ui/utils/raf_resize_observer.js +0 -0
- package/src/ui/utils/scroll_lock.js +0 -0
- package/src/ui/utils/strip_scroll.js +55 -0
- package/src/ui/utils/tooltip_service.js +0 -8
- package/tiles.js +27 -0
- package/widgets.js +69 -0
- package/wm.js +28 -0
- package/dist/chunk-6JSOVNID.js.map +0 -7
- package/dist/chunk-B5Q53LAJ.js.map +0 -7
- package/dist/chunk-FBGP5KN5.js.map +0 -7
- package/dist/chunk-FL5KFNQH.js.map +0 -7
- package/dist/chunk-NLTOY57M.js.map +0 -7
- package/dist/chunk-NNFTETK3.js +0 -633
- package/dist/chunk-NNFTETK3.js.map +0 -7
- package/dist/chunk-O5OHMWBB.js +0 -284
- package/dist/chunk-O5OHMWBB.js.map +0 -7
- package/dist/chunk-RQM5Z2MU.js.map +0 -7
- package/dist/chunk-SN4LSOAR.js.map +0 -7
- package/dist/chunk-W6FBJLPW.js +0 -367
- package/dist/chunk-W6FBJLPW.js.map +0 -7
- package/src/ui/controllers/panel_state_machine.js +0 -634
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* series_transforms.js — derived series an embedder supplies: a trend, a moving
|
|
3
|
+
* average, a decomposition. @flexdesk/charts ships none of its own.
|
|
4
|
+
*
|
|
5
|
+
* A transform is a description plus an async function:
|
|
6
|
+
*
|
|
7
|
+
* const seriesTransforms = {
|
|
8
|
+
* movingAverage: {
|
|
9
|
+
* label: 'Moving average',
|
|
10
|
+
* params: { window: { label: 'Window', default: 5, min: 1, step: 1 } },
|
|
11
|
+
* outputs: {
|
|
12
|
+
* mean: { label: 'Mean', suffix: ' (MA)', lineStyle: 'dashed' },
|
|
13
|
+
* },
|
|
14
|
+
* run: async (y, params) => ({ mean: rollingMean(y, params.window) }),
|
|
15
|
+
* },
|
|
16
|
+
* };
|
|
17
|
+
*
|
|
18
|
+
* `run(y, params)` resolves `{ [output]: number[] }`. It is called ONCE per
|
|
19
|
+
* eligible trace (a 2-D line/scatter trace) for each transform with at least one
|
|
20
|
+
* output switched on, and every switched-on output it returns is drawn as an
|
|
21
|
+
* extra trace in the source trace's colour, named `<source name><suffix>`, with
|
|
22
|
+
* the output's `lineStyle` (solid | dashed | dotted) and `lineWidth`.
|
|
23
|
+
*
|
|
24
|
+
* What is switched on lives in the subplot, under `subplot.transforms`:
|
|
25
|
+
*
|
|
26
|
+
* subplot.transforms = { movingAverage: { outputs: { mean: true }, params: { window: 7 } } }
|
|
27
|
+
*
|
|
28
|
+
* A transform that throws, rejects or resolves something that is not an object
|
|
29
|
+
* is skipped for that trace; the figure is drawn without it.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { buildTrace } from './chart_types.js';
|
|
33
|
+
|
|
34
|
+
/** A transform's parameter values: its declared defaults under the subplot's own. */
|
|
35
|
+
function resolveParams(transform, state) {
|
|
36
|
+
const params = {};
|
|
37
|
+
for (const [name, spec] of Object.entries(transform.params ?? {})) {
|
|
38
|
+
params[name] = spec?.default;
|
|
39
|
+
}
|
|
40
|
+
return { ...params, ...(state?.params ?? {}) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The output names of `transform` that `state` switches on, in declared order. */
|
|
44
|
+
function enabledOutputs(transform, state) {
|
|
45
|
+
return Object.keys(transform.outputs ?? {}).filter((name) => state?.outputs?.[name] === true);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A trace a transform may run on: 2-D, line or scatter, with a y array. */
|
|
49
|
+
function isEligible(trace) {
|
|
50
|
+
const type = trace.type ?? 'scatter';
|
|
51
|
+
return (type === 'scatter' || type === 'scattergl') && Array.isArray(trace.y);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The derived traces for one subplot's figure.
|
|
56
|
+
*
|
|
57
|
+
* @param {Object[]} traces - The figure's Plotly traces so far (not modified).
|
|
58
|
+
* @param {Object} subplot - The subplot config; reads `subplot.transforms`.
|
|
59
|
+
* @param {Object|null} seriesTransforms - `{ [id]: transform }`, or null for none.
|
|
60
|
+
* @param {Object} [options]
|
|
61
|
+
* @param {Function} [options.onError] - `({ transformId, traceName, error })`,
|
|
62
|
+
* called for each skipped run.
|
|
63
|
+
* @returns {Promise<Object[]>} The extra traces, in transform then trace order.
|
|
64
|
+
*/
|
|
65
|
+
async function deriveSeriesTraces(traces, subplot, seriesTransforms, { onError } = {}) {
|
|
66
|
+
const derived = [];
|
|
67
|
+
if (!seriesTransforms) return derived;
|
|
68
|
+
const sources = traces.filter(isEligible);
|
|
69
|
+
|
|
70
|
+
for (const [transformId, transform] of Object.entries(seriesTransforms)) {
|
|
71
|
+
const state = subplot?.transforms?.[transformId];
|
|
72
|
+
const outputs = enabledOutputs(transform, state);
|
|
73
|
+
if (!outputs.length || typeof transform?.run !== 'function') continue;
|
|
74
|
+
const params = resolveParams(transform, state);
|
|
75
|
+
|
|
76
|
+
for (const source of sources) {
|
|
77
|
+
let result;
|
|
78
|
+
try {
|
|
79
|
+
result = await transform.run(source.y, params);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
onError?.({ transformId, traceName: source.name, error });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!result || typeof result !== 'object') {
|
|
85
|
+
onError?.({ transformId, traceName: source.name,
|
|
86
|
+
error: new Error(`transform "${transformId}" resolved no outputs`) });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const color = source.line?.color || source.marker?.color;
|
|
90
|
+
for (const name of outputs) {
|
|
91
|
+
const y = result[name];
|
|
92
|
+
if (!Array.isArray(y)) continue;
|
|
93
|
+
const spec = transform.outputs[name];
|
|
94
|
+
derived.push({
|
|
95
|
+
...buildTrace({
|
|
96
|
+
chartType: 'line',
|
|
97
|
+
x: source.x,
|
|
98
|
+
y,
|
|
99
|
+
name: `${source.name}${spec.suffix ?? ` (${spec.label ?? name})`}`,
|
|
100
|
+
color,
|
|
101
|
+
lineWidth: spec.lineWidth ?? 2,
|
|
102
|
+
lineStyle: spec.lineStyle ?? 'solid',
|
|
103
|
+
yAxisId: source.yaxis || 'y',
|
|
104
|
+
}),
|
|
105
|
+
showlegend: true,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return derived;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The controls for `seriesTransforms` in a subplot's config: per transform, a
|
|
115
|
+
* toggle for each output and a number field for each parameter. They edit
|
|
116
|
+
* `subplot.transforms` in place and call `onChange()` after every edit. With no
|
|
117
|
+
* transforms (null or `{}`) nothing is rendered.
|
|
118
|
+
*
|
|
119
|
+
* @param {HTMLElement} container
|
|
120
|
+
* @param {Object} subplot
|
|
121
|
+
* @param {Object|null} seriesTransforms
|
|
122
|
+
* @param {Object} [options]
|
|
123
|
+
* @param {Function} [options.onChange]
|
|
124
|
+
* @returns {{ dispose: Function }}
|
|
125
|
+
*/
|
|
126
|
+
function renderSeriesTransformControls(container, subplot, seriesTransforms, { onChange } = {}) {
|
|
127
|
+
const entries = Object.entries(seriesTransforms ?? {});
|
|
128
|
+
if (!entries.length) return { dispose() {} };
|
|
129
|
+
|
|
130
|
+
const root = document.createElement('div');
|
|
131
|
+
root.className = 'twm-series-transforms';
|
|
132
|
+
|
|
133
|
+
const stateOf = (transformId) => {
|
|
134
|
+
subplot.transforms ??= {};
|
|
135
|
+
subplot.transforms[transformId] ??= {};
|
|
136
|
+
return subplot.transforms[transformId];
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
for (const [transformId, transform] of entries) {
|
|
140
|
+
const group = document.createElement('fieldset');
|
|
141
|
+
group.className = 'twm-series-transforms__group';
|
|
142
|
+
const legend = document.createElement('legend');
|
|
143
|
+
legend.className = 'twm-series-transforms__label';
|
|
144
|
+
legend.textContent = transform.label ?? transformId;
|
|
145
|
+
group.appendChild(legend);
|
|
146
|
+
|
|
147
|
+
const paramInputs = [];
|
|
148
|
+
const syncDisabled = () => {
|
|
149
|
+
const on = enabledOutputs(transform, subplot.transforms?.[transformId]).length > 0;
|
|
150
|
+
for (const input of paramInputs) input.disabled = !on;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
for (const [name, spec] of Object.entries(transform.outputs ?? {})) {
|
|
154
|
+
const row = document.createElement('label');
|
|
155
|
+
row.className = 'twm-series-transforms__row';
|
|
156
|
+
const box = document.createElement('input');
|
|
157
|
+
box.type = 'checkbox';
|
|
158
|
+
box.checked = subplot.transforms?.[transformId]?.outputs?.[name] === true;
|
|
159
|
+
box.addEventListener('change', () => {
|
|
160
|
+
const state = stateOf(transformId);
|
|
161
|
+
state.outputs = { ...(state.outputs ?? {}), [name]: box.checked };
|
|
162
|
+
syncDisabled();
|
|
163
|
+
onChange?.();
|
|
164
|
+
});
|
|
165
|
+
const text = document.createElement('span');
|
|
166
|
+
text.textContent = spec?.label ?? name;
|
|
167
|
+
row.append(box, text);
|
|
168
|
+
group.appendChild(row);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const [name, spec] of Object.entries(transform.params ?? {})) {
|
|
172
|
+
const row = document.createElement('label');
|
|
173
|
+
row.className = 'twm-series-transforms__row';
|
|
174
|
+
const text = document.createElement('span');
|
|
175
|
+
text.textContent = spec?.label ?? name;
|
|
176
|
+
const input = document.createElement('input');
|
|
177
|
+
input.type = 'number';
|
|
178
|
+
input.className = 'twm-series-transforms__param';
|
|
179
|
+
for (const attr of ['min', 'max', 'step']) {
|
|
180
|
+
if (spec?.[attr] != null) input[attr] = String(spec[attr]);
|
|
181
|
+
}
|
|
182
|
+
const current = subplot.transforms?.[transformId]?.params?.[name] ?? spec?.default;
|
|
183
|
+
input.value = current == null ? '' : String(current);
|
|
184
|
+
input.addEventListener('change', () => {
|
|
185
|
+
const value = parseFloat(input.value);
|
|
186
|
+
const state = stateOf(transformId);
|
|
187
|
+
const params = { ...(state.params ?? {}) };
|
|
188
|
+
if (Number.isFinite(value)) params[name] = value;
|
|
189
|
+
else delete params[name];
|
|
190
|
+
state.params = params;
|
|
191
|
+
onChange?.();
|
|
192
|
+
});
|
|
193
|
+
paramInputs.push(input);
|
|
194
|
+
row.append(text, input);
|
|
195
|
+
group.appendChild(row);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
syncDisabled();
|
|
199
|
+
root.appendChild(group);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
container.appendChild(root);
|
|
203
|
+
return { dispose() { root.remove(); } };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export { deriveSeriesTraces, renderSeriesTransformControls };
|
package/src/core/event_bus.js
CHANGED
|
File without changes
|
package/src/core/logging.js
CHANGED
package/src/core/settings.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* SCHEMA. The store knows nothing about any particular application's settings:
|
|
8
8
|
* it ships the *shell's* own namespaces (workspace, window, logging, data,
|
|
9
9
|
* notebook, ai, …) and an embedder PUSHES its own namespace in at boot with
|
|
10
|
-
* `registerSettings()`. The
|
|
10
|
+
* `registerSettings()`. The settings page — `SettingsView` in
|
|
11
|
+
* `src/ui/components/settings_view.js` — renders whatever is in the schema.
|
|
11
12
|
*
|
|
12
13
|
* Two doors, on purpose
|
|
13
14
|
* ---------------------
|
|
@@ -18,8 +19,9 @@
|
|
|
18
19
|
* getSetting() / setSetting() / registerSettings() / …
|
|
19
20
|
* A thin binding over ONE instance of that primitive for THIS document.
|
|
20
21
|
* The cardinality is correct, not merely convenient: the store has a
|
|
21
|
-
* single physical backing key (`localStorage['
|
|
22
|
-
*
|
|
22
|
+
* single physical backing key (`localStorage['flexdesk.settings.v1']`
|
|
23
|
+
* unless `configureSettings({ storageKey })` names another), so two
|
|
24
|
+
* live stores in one document would be two writers to one key.
|
|
23
25
|
*
|
|
24
26
|
* Usage
|
|
25
27
|
* -----
|
|
@@ -300,7 +302,7 @@ const SCHEMA = Object.freeze({
|
|
|
300
302
|
label: 'Projects directory',
|
|
301
303
|
description: 'Default directory for new projects and bundled demos. Leave empty for OS default.',
|
|
302
304
|
defaultValue: null,
|
|
303
|
-
placeholder: 'OS default
|
|
305
|
+
placeholder: 'OS default',
|
|
304
306
|
},
|
|
305
307
|
|
|
306
308
|
// ── AI Assistant ────────────────────────────────────────────────────────
|
|
@@ -387,7 +389,10 @@ const CATEGORIES = Object.freeze([
|
|
|
387
389
|
{ id: 'advanced', label: 'Advanced', icon: 'code', description: 'Debug and developer settings', order: 80 },
|
|
388
390
|
]);
|
|
389
391
|
|
|
390
|
-
|
|
392
|
+
/** The binding's localStorage key when the embedder names none. An embedder
|
|
393
|
+
* that stored settings under another key keeps them by passing that key to
|
|
394
|
+
* `configureSettings({ storageKey })` before anything reads a setting. */
|
|
395
|
+
export const DEFAULT_SETTINGS_STORAGE_KEY = 'flexdesk.settings.v1';
|
|
391
396
|
|
|
392
397
|
// ─── Path / value helpers ────────────────────────────────────────────────────
|
|
393
398
|
|
|
@@ -494,8 +499,9 @@ export function createSettingsStore({
|
|
|
494
499
|
const _defaults = _clone(defaults) ?? {};
|
|
495
500
|
const _schema = { ...schema };
|
|
496
501
|
let _categories = categories.map(c => ({ ...c }));
|
|
497
|
-
|
|
502
|
+
let _values = _clone(defaults) ?? {};
|
|
498
503
|
let _bus = eventBus;
|
|
504
|
+
let _storageKey = storageKey;
|
|
499
505
|
|
|
500
506
|
const _sortCategories = () => {
|
|
501
507
|
// Array#sort is stable, so equal `order` keeps registration order.
|
|
@@ -513,8 +519,8 @@ export function createSettingsStore({
|
|
|
513
519
|
*/
|
|
514
520
|
const _load = () => {
|
|
515
521
|
try {
|
|
516
|
-
if (!
|
|
517
|
-
const raw = localStorage.getItem(
|
|
522
|
+
if (!_storageKey || typeof localStorage === 'undefined') return;
|
|
523
|
+
const raw = localStorage.getItem(_storageKey);
|
|
518
524
|
if (!raw) return;
|
|
519
525
|
const saved = JSON.parse(raw);
|
|
520
526
|
for (const path of Object.keys(_schema)) {
|
|
@@ -532,7 +538,7 @@ export function createSettingsStore({
|
|
|
532
538
|
/** Persist values that differ from their defaults (sparse storage). */
|
|
533
539
|
const _save = () => {
|
|
534
540
|
try {
|
|
535
|
-
if (!
|
|
541
|
+
if (!_storageKey || typeof localStorage === 'undefined') return;
|
|
536
542
|
const sparse = {};
|
|
537
543
|
for (const path of Object.keys(_schema)) {
|
|
538
544
|
const parts = path.split('.');
|
|
@@ -543,9 +549,9 @@ export function createSettingsStore({
|
|
|
543
549
|
}
|
|
544
550
|
}
|
|
545
551
|
if (Object.keys(sparse).length === 0) {
|
|
546
|
-
localStorage.removeItem(
|
|
552
|
+
localStorage.removeItem(_storageKey);
|
|
547
553
|
} else {
|
|
548
|
-
localStorage.setItem(
|
|
554
|
+
localStorage.setItem(_storageKey, JSON.stringify(sparse));
|
|
549
555
|
}
|
|
550
556
|
} catch (err) {
|
|
551
557
|
console.warn('[Settings] Failed to persist settings', err);
|
|
@@ -569,19 +575,43 @@ export function createSettingsStore({
|
|
|
569
575
|
* namespace is registered would garbage-collect that namespace's saved
|
|
570
576
|
* values. Registering at the top of bootstrap satisfies this trivially.
|
|
571
577
|
*
|
|
578
|
+
* Listeners hear `settings:registered` with `{ paths, categories }` — the
|
|
579
|
+
* slice's schema paths and the ids of the categories it added — so a view
|
|
580
|
+
* that is already open (the settings view) can draw the new rows.
|
|
581
|
+
*
|
|
572
582
|
* @param {SettingsSlice} slice
|
|
573
583
|
*/
|
|
574
584
|
const extend = ({ defaults: d = {}, schema: s = {}, categories: c = [] } = {}) => {
|
|
575
585
|
_deepMerge(_defaults, d);
|
|
576
586
|
_deepMerge(_values, d);
|
|
577
587
|
Object.assign(_schema, s);
|
|
588
|
+
const added = [];
|
|
578
589
|
for (const cat of c) {
|
|
579
590
|
if (!cat?.id) continue;
|
|
580
591
|
if (_categories.some(x => x.id === cat.id)) continue;
|
|
581
592
|
_categories.push({ ...cat });
|
|
593
|
+
added.push(cat.id);
|
|
582
594
|
}
|
|
583
595
|
_sortCategories();
|
|
584
596
|
_load(); // idempotent: overlays only paths it finds in storage
|
|
597
|
+
if (_bus) _bus.emit('settings:registered', { paths: Object.keys(s), categories: added });
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Move the store onto another localStorage key: every value goes back to
|
|
602
|
+
* its default and is then overlaid from the NEW key only. Nothing is
|
|
603
|
+
* written, so the key the store leaves keeps whatever it held. Listeners
|
|
604
|
+
* hear one `settings:changed` with a null path, as after a reset.
|
|
605
|
+
*
|
|
606
|
+
* @param {string|null} key localStorage key; null -> no persistence.
|
|
607
|
+
* @param {object} [opts]
|
|
608
|
+
* @param {boolean} [opts.notify=true] false -> emit nothing.
|
|
609
|
+
*/
|
|
610
|
+
const setStorageKey = (key, { notify = true } = {}) => {
|
|
611
|
+
_storageKey = key || null;
|
|
612
|
+
_values = _clone(_defaults) ?? {};
|
|
613
|
+
_load();
|
|
614
|
+
if (notify && _bus) _bus.emit('settings:changed', { path: null });
|
|
585
615
|
};
|
|
586
616
|
|
|
587
617
|
const get = (path, defaultValue = undefined) => {
|
|
@@ -683,6 +713,8 @@ export function createSettingsStore({
|
|
|
683
713
|
getCategories: () => Object.freeze(_categories.slice()),
|
|
684
714
|
getByCategory,
|
|
685
715
|
setEventBus: (bus) => { _bus = bus; },
|
|
716
|
+
setStorageKey,
|
|
717
|
+
getStorageKey: () => _storageKey,
|
|
686
718
|
});
|
|
687
719
|
}
|
|
688
720
|
|
|
@@ -691,7 +723,8 @@ export function createSettingsStore({
|
|
|
691
723
|
// A capability *port* (the host bridge) may have many implementations at once,
|
|
692
724
|
// so a singleton there is a category error. A *value store* with a single
|
|
693
725
|
// physical backing key has cardinality one by construction: two live stores in
|
|
694
|
-
// one document would be two writers to `
|
|
726
|
+
// one document would be two writers to the one key `configureSettings()`
|
|
727
|
+
// names (default `DEFAULT_SETTINGS_STORAGE_KEY`). The
|
|
695
728
|
// singleton is the correct cardinality, not a shortcut — and the factory above
|
|
696
729
|
// is the primitive, so tests and a standalone demo never touch this binding.
|
|
697
730
|
|
|
@@ -699,9 +732,62 @@ const _store = createSettingsStore({
|
|
|
699
732
|
defaults: DEFAULTS,
|
|
700
733
|
schema: SCHEMA,
|
|
701
734
|
categories: CATEGORIES,
|
|
702
|
-
storageKey:
|
|
735
|
+
storageKey: null,
|
|
703
736
|
});
|
|
704
737
|
|
|
738
|
+
// The binding reads its key LAZILY — on the first call that reads, writes or
|
|
739
|
+
// resets a VALUE — so an embedder that calls `configureSettings({ storageKey })`
|
|
740
|
+
// at boot never has the default key read at all, and importing this module
|
|
741
|
+
// touches no storage. registerSettings(), registerSettingsEventBus() and the
|
|
742
|
+
// schema/category/default getters do not attach it: they may come before or
|
|
743
|
+
// after configureSettings().
|
|
744
|
+
let _bindingKey = DEFAULT_SETTINGS_STORAGE_KEY;
|
|
745
|
+
let _bound = false;
|
|
746
|
+
|
|
747
|
+
/** The binding's store, attached to its key on first use. */
|
|
748
|
+
function _binding() {
|
|
749
|
+
if (!_bound) {
|
|
750
|
+
_bound = true;
|
|
751
|
+
_store.setStorageKey(_bindingKey, { notify: false });
|
|
752
|
+
}
|
|
753
|
+
return _store;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* The app-global binding's store itself — the handle `createSettingsStore()`
|
|
758
|
+
* returns — for code that takes a store, such as the settings view
|
|
759
|
+
* (`new SettingsView({ store: getSettingsStore(), eventBus })`). Its `set` is
|
|
760
|
+
* the one `setSetting()` calls. Like `getSetting()`, it attaches the binding
|
|
761
|
+
* to its storage key, so call `configureSettings()` first.
|
|
762
|
+
*
|
|
763
|
+
* @returns {object}
|
|
764
|
+
*/
|
|
765
|
+
export function getSettingsStore() {
|
|
766
|
+
return _binding();
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Choose the localStorage key the app-global binding reads and writes.
|
|
771
|
+
* Call it at boot, before the first getSetting/setSetting/reset*: from then on
|
|
772
|
+
* the binding touches only `storageKey`.
|
|
773
|
+
*
|
|
774
|
+
* Called later, the binding moves: values return to their defaults, are
|
|
775
|
+
* reloaded from the new key, and a `settings:changed` (path null) is emitted.
|
|
776
|
+
* The old key is left as it was.
|
|
777
|
+
*
|
|
778
|
+
* @param {object} [opts]
|
|
779
|
+
* @param {string} [opts.storageKey=DEFAULT_SETTINGS_STORAGE_KEY]
|
|
780
|
+
*/
|
|
781
|
+
export function configureSettings({ storageKey = DEFAULT_SETTINGS_STORAGE_KEY } = {}) {
|
|
782
|
+
_bindingKey = storageKey;
|
|
783
|
+
if (_bound) _store.setStorageKey(storageKey);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/** @returns {string} The localStorage key the app-global binding uses. */
|
|
787
|
+
export function getSettingsStorageKey() {
|
|
788
|
+
return _bindingKey;
|
|
789
|
+
}
|
|
790
|
+
|
|
705
791
|
/**
|
|
706
792
|
* Push an embedder's settings namespace into the app-global store.
|
|
707
793
|
*
|
|
@@ -733,7 +819,7 @@ export function registerSettingsEventBus(eventBus) {
|
|
|
733
819
|
* @returns {*} The setting value or defaultValue
|
|
734
820
|
*/
|
|
735
821
|
export function getSetting(path, defaultValue = undefined) {
|
|
736
|
-
return
|
|
822
|
+
return _binding().get(path, defaultValue);
|
|
737
823
|
}
|
|
738
824
|
|
|
739
825
|
/**
|
|
@@ -743,7 +829,7 @@ export function getSetting(path, defaultValue = undefined) {
|
|
|
743
829
|
* @returns {boolean} True if set successfully
|
|
744
830
|
*/
|
|
745
831
|
export function setSetting(path, value) {
|
|
746
|
-
return
|
|
832
|
+
return _binding().set(path, value);
|
|
747
833
|
}
|
|
748
834
|
|
|
749
835
|
/**
|
|
@@ -761,7 +847,7 @@ export function getDefaultValue(path) {
|
|
|
761
847
|
* @returns {boolean} True if reset successfully
|
|
762
848
|
*/
|
|
763
849
|
export function resetSetting(path) {
|
|
764
|
-
return
|
|
850
|
+
return _binding().resetOne(path);
|
|
765
851
|
}
|
|
766
852
|
|
|
767
853
|
/**
|
|
@@ -769,12 +855,12 @@ export function resetSetting(path) {
|
|
|
769
855
|
* @param {string} categoryId
|
|
770
856
|
*/
|
|
771
857
|
export function resetCategory(categoryId) {
|
|
772
|
-
|
|
858
|
+
_binding().resetCategory(categoryId);
|
|
773
859
|
}
|
|
774
860
|
|
|
775
861
|
/** Reset all settings to their defaults. */
|
|
776
862
|
export function resetAllSettings() {
|
|
777
|
-
|
|
863
|
+
_binding().resetAll();
|
|
778
864
|
}
|
|
779
865
|
|
|
780
866
|
/**
|
|
@@ -782,7 +868,7 @@ export function resetAllSettings() {
|
|
|
782
868
|
* @returns {object} Deep copy of current settings
|
|
783
869
|
*/
|
|
784
870
|
export function getAllSettings() {
|
|
785
|
-
return
|
|
871
|
+
return _binding().getAll();
|
|
786
872
|
}
|
|
787
873
|
|
|
788
874
|
// ─── Schema / Category API (for Settings page UI) ───────────────────────────
|