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
package/src/tiles/chart_tiles.js
CHANGED
|
@@ -34,33 +34,26 @@
|
|
|
34
34
|
* each. `line` on a series is merged over the built `trace.line`, so
|
|
35
35
|
* `line: {shape: 'hv', dash: 'dash'}` is as good as `interpolation`/`lineStyle`.
|
|
36
36
|
*
|
|
37
|
-
* ══
|
|
37
|
+
* ══ THE DRAW IS FigureTile's ═══════════════════════════════════════════
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
39
|
+
* `ChartTile` is a `FigureTile` (figure_tile.js, 0.17.0): it answers
|
|
40
|
+
* `figure(data)` with the traces and layout `_plan` builds, and the base awaits
|
|
41
|
+
* Plotly, draws, discards a stale draw, purges on every state change, relayouts
|
|
42
|
+
* after a resize, downloads the PNG and opens Expand. What stays here is what a
|
|
43
|
+
* CHART is: the data shorthands, the chart types, the note under the figure.
|
|
43
44
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* without telling Plotly. So each of them here first purges the figure, drops the
|
|
47
|
-
* reference, marks it not drawn, and bumps `_drawSeq` — and a draw still in
|
|
48
|
-
* flight when `setData(empty)` arrives finds itself stale and purges its own
|
|
49
|
-
* figure instead of marking it drawn.
|
|
50
|
-
*
|
|
51
|
-
* `scrollZoom` is off per call: a tile sits on a scrolling board, and the wheel
|
|
52
|
-
* over a chart must scroll past it rather than zoom it.
|
|
45
|
+
* The menu is Download PNG alone, as 0.13.0 shipped it: `TileBase`'s two
|
|
46
|
+
* documentation entries belong to an application with documentation.
|
|
53
47
|
*/
|
|
54
48
|
|
|
55
49
|
import { TileBase } from './tile_base.js';
|
|
50
|
+
import { FigureTile } from './figure_tile.js';
|
|
56
51
|
import { registerWidget, getWidget } from './tile_registry.js';
|
|
57
52
|
import {
|
|
58
53
|
buildTrace, buildXAxisLayout, buildYAxisLayout, buildLegendLayout,
|
|
59
54
|
buildHistogramTrace, buildOverlayShapes,
|
|
60
55
|
} from '../charts/chart_types.js';
|
|
61
|
-
import {
|
|
62
|
-
createChart, resize as resizePlot, purge, ensurePlotly, getSeriesColor,
|
|
63
|
-
} from '../charts/plotly_wrapper.js';
|
|
56
|
+
import { getSeriesColor } from '../charts/plotly_wrapper.js';
|
|
64
57
|
import { strideDownsampleShared } from '../charts/downsample.js';
|
|
65
58
|
|
|
66
59
|
/** Part-of-whole types: one series, no axes. */
|
|
@@ -136,7 +129,7 @@ function pointCount(s) {
|
|
|
136
129
|
return 0;
|
|
137
130
|
}
|
|
138
131
|
|
|
139
|
-
export class ChartTile extends
|
|
132
|
+
export class ChartTile extends FigureTile {
|
|
140
133
|
static TYPE = 'flexdesk.chart';
|
|
141
134
|
static TITLE = 'Chart';
|
|
142
135
|
static ICON = 'insert_chart';
|
|
@@ -144,19 +137,6 @@ export class ChartTile extends TileBase {
|
|
|
144
137
|
static DEFAULT_SIZE = { w: 6, h: 4 };
|
|
145
138
|
static SIZE_CONSTRAINTS = { minW: 3, minH: 3, maxW: 12, maxH: 10 };
|
|
146
139
|
|
|
147
|
-
/**
|
|
148
|
-
* @param {Object} options - TileBase's, plus:
|
|
149
|
-
* @param {Function} [options.onPointClick] - `({customdata, point, tile})`
|
|
150
|
-
* on a click on a mark. Also assignable later as `tile.onPointClick`.
|
|
151
|
-
*/
|
|
152
|
-
constructor(options = {}) {
|
|
153
|
-
super(options);
|
|
154
|
-
this.onPointClick = typeof options.onPointClick === 'function' ? options.onPointClick : null;
|
|
155
|
-
this._plotEl = null;
|
|
156
|
-
this._drawn = false;
|
|
157
|
-
this._drawSeq = 0;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
140
|
/** Pure and static-only — it runs inside `TileBase`'s constructor. */
|
|
161
141
|
getDefaultConfig() {
|
|
162
142
|
return {
|
|
@@ -177,12 +157,12 @@ export class ChartTile extends TileBase {
|
|
|
177
157
|
getConfigSchema() {
|
|
178
158
|
return {
|
|
179
159
|
fields: [
|
|
180
|
-
{
|
|
181
|
-
{
|
|
160
|
+
{ key: 'title', type: 'text', label: 'Tile title' },
|
|
161
|
+
{ key: 'chart_type', type: 'select', label: 'Chart type',
|
|
182
162
|
options: CHART_TILE_TYPES.map((o) => ({ ...o })) },
|
|
183
|
-
{
|
|
184
|
-
|
|
185
|
-
{
|
|
163
|
+
{ key: 'unit', type: 'text', label: 'Unit',
|
|
164
|
+
description: 'Shown with the value axis title, e.g. "pts" or "h".' },
|
|
165
|
+
{ key: 'legend', type: 'select', label: 'Legend',
|
|
186
166
|
visibleWhen: { field: 'chart_type', in: [...READS.legend] },
|
|
187
167
|
options: [
|
|
188
168
|
{ value: 'bottom', label: 'Below' },
|
|
@@ -191,18 +171,18 @@ export class ChartTile extends TileBase {
|
|
|
191
171
|
{ value: 'left', label: 'Left' },
|
|
192
172
|
{ value: 'hidden', label: 'None' },
|
|
193
173
|
] },
|
|
194
|
-
{
|
|
174
|
+
{ key: 'x_title', type: 'text', label: 'X axis title',
|
|
195
175
|
visibleWhen: { field: 'chart_type', in: [...READS.axis_titles] } },
|
|
196
|
-
{
|
|
176
|
+
{ key: 'y_title', type: 'text', label: 'Y axis title',
|
|
197
177
|
visibleWhen: { field: 'chart_type', in: [...READS.axis_titles] } },
|
|
198
|
-
{
|
|
178
|
+
{ key: 'y_scale', type: 'select', label: 'Y scale',
|
|
199
179
|
visibleWhen: { field: 'chart_type', in: [...READS.y_scale] },
|
|
200
180
|
options: [{ value: 'linear', label: 'Linear' }, { value: 'log', label: 'Logarithmic' }] },
|
|
201
|
-
{
|
|
181
|
+
{ key: 'y_min', type: 'number', label: 'Y minimum',
|
|
202
182
|
visibleWhen: { field: 'chart_type', in: [...READS.y_range] } },
|
|
203
|
-
{
|
|
183
|
+
{ key: 'y_max', type: 'number', label: 'Y maximum',
|
|
204
184
|
visibleWhen: { field: 'chart_type', in: [...READS.y_range] } },
|
|
205
|
-
{
|
|
185
|
+
{ key: 'show_markers', type: 'checkbox', label: 'Show point markers',
|
|
206
186
|
visibleWhen: { field: 'chart_type', in: [...READS.show_markers] } },
|
|
207
187
|
],
|
|
208
188
|
};
|
|
@@ -225,163 +205,31 @@ export class ChartTile extends TileBase {
|
|
|
225
205
|
if (config) applyTitle(this);
|
|
226
206
|
}
|
|
227
207
|
|
|
228
|
-
/**
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (this._disposed) return;
|
|
234
|
-
this.data = data ?? null;
|
|
235
|
-
if (this.contentElement) this.render(this.data);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Kick off the draw and return. Deliberately not `async`: nothing awaits
|
|
240
|
-
* `render`, so a returned promise would be a rejection nobody catches.
|
|
241
|
-
*/
|
|
242
|
-
render(data) {
|
|
243
|
-
this._draw(data);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/** One relayout when a resize settles; nothing while a draw is in flight. */
|
|
247
|
-
onResize() {
|
|
248
|
-
if (!this._plotEl || !this._drawn) return;
|
|
249
|
-
resizePlot(this._plotEl).catch(() => {});
|
|
208
|
+
/** `null` (the empty state) when there is nothing to draw. */
|
|
209
|
+
figure(data) {
|
|
210
|
+
const norm = normalizeChartData(data);
|
|
211
|
+
if (!norm) return null;
|
|
212
|
+
return { ...this._plan(norm), note: norm.note };
|
|
250
213
|
}
|
|
251
214
|
|
|
215
|
+
/** Download PNG alone — see the header. */
|
|
252
216
|
menuItems() {
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
_onMenuAction(action) {
|
|
257
|
-
if (action === 'download-png') this._downloadPng();
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// ── the three states: each one invalidates the figure ───────────────
|
|
261
|
-
|
|
262
|
-
showLoading() {
|
|
263
|
-
this._invalidate();
|
|
264
|
-
super.showLoading();
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
showEmpty(message) {
|
|
268
|
-
this._invalidate();
|
|
269
|
-
super.showEmpty(message);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
showError(message) {
|
|
273
|
-
this._invalidate();
|
|
274
|
-
super.showError(message);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/** Release the figure BEFORE the element goes — `TileBase.dispose` nulls it. */
|
|
278
|
-
dispose() {
|
|
279
|
-
this._invalidate();
|
|
280
|
-
super.dispose();
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/** Any draw in flight is now stale, and the figure on screen is released. */
|
|
284
|
-
_invalidate() {
|
|
285
|
-
this._drawSeq += 1;
|
|
286
|
-
this._releasePlot();
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/** Hand the current figure back to Plotly and forget it. Idempotent, and
|
|
290
|
-
* not awaited: a rejected purge (Plotly absent, already purged) must not
|
|
291
|
-
* surface as an unhandled rejection. */
|
|
292
|
-
_releasePlot() {
|
|
293
|
-
const el = this._plotEl;
|
|
294
|
-
this._plotEl = null;
|
|
295
|
-
this._drawn = false;
|
|
296
|
-
if (el) purge(el).catch(() => {});
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// ── the draw ────────────────────────────────────────────────────────
|
|
300
|
-
|
|
301
|
-
async _draw(data) {
|
|
302
|
-
this._drawSeq += 1;
|
|
303
|
-
const seq = this._drawSeq;
|
|
304
|
-
const content = this.contentElement;
|
|
305
|
-
if (!content) return;
|
|
306
|
-
|
|
307
|
-
// Every path below replaces the content, so the figure there now is
|
|
308
|
-
// about to be orphaned whichever way this goes.
|
|
309
|
-
this._releasePlot();
|
|
310
|
-
|
|
311
|
-
const norm = normalizeChartData(data);
|
|
312
|
-
if (!norm) { this.showEmpty('No data to show.'); return; }
|
|
313
|
-
|
|
314
|
-
let plan;
|
|
315
|
-
try {
|
|
316
|
-
plan = this._plan(norm);
|
|
317
|
-
} catch (err) {
|
|
318
|
-
this._failed(err);
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
if (!plan) return; // `_plan` has already said why
|
|
322
|
-
|
|
323
|
-
const host = document.createElement('div');
|
|
324
|
-
host.className = 'fd-chart-tile';
|
|
325
|
-
const parts = [host];
|
|
326
|
-
if (norm.note) {
|
|
327
|
-
const note = document.createElement('div');
|
|
328
|
-
note.className = 'fd-chart-tile__note';
|
|
329
|
-
note.setAttribute('role', 'note');
|
|
330
|
-
note.textContent = norm.note;
|
|
331
|
-
note.title = norm.note;
|
|
332
|
-
parts.push(note);
|
|
333
|
-
}
|
|
334
|
-
content.replaceChildren(...parts);
|
|
335
|
-
this._plotEl = host;
|
|
336
|
-
this._drawn = false;
|
|
337
|
-
|
|
338
|
-
try {
|
|
339
|
-
await createChart(host, plan.traces, plan.layout, {
|
|
340
|
-
scrollZoom: false,
|
|
341
|
-
displayModeBar: false,
|
|
342
|
-
responsive: true,
|
|
343
|
-
});
|
|
344
|
-
} catch (err) {
|
|
345
|
-
if (this._disposed || seq !== this._drawSeq) return;
|
|
346
|
-
this._failed(err);
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
if (this._disposed || seq !== this._drawSeq) {
|
|
350
|
-
// A newer draw or a state change has already replaced the content;
|
|
351
|
-
// this figure is detached, and purging it releases its listeners.
|
|
352
|
-
purge(host).catch(() => {});
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
this._drawn = true;
|
|
356
|
-
this._wireClick(host);
|
|
217
|
+
return this._figureMenuItems();
|
|
357
218
|
}
|
|
358
219
|
|
|
359
|
-
/**
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (trace.type === 'pie' || customdata === undefined) {
|
|
370
|
-
const i = point.pointNumber ?? point.pointIndex;
|
|
371
|
-
if (Array.isArray(trace.customdata) && i != null) customdata = trace.customdata[i];
|
|
372
|
-
}
|
|
373
|
-
try {
|
|
374
|
-
this.onPointClick({ customdata, point, tile: this });
|
|
375
|
-
} catch (err) {
|
|
376
|
-
console.error('[ChartTile] onPointClick threw:', err);
|
|
377
|
-
}
|
|
378
|
-
});
|
|
220
|
+
/** The figure, and the note on its own line under it. */
|
|
221
|
+
_figureContent(plotEl, fig) {
|
|
222
|
+
plotEl.classList.add('fd-chart-tile');
|
|
223
|
+
if (!fig.note) return [plotEl];
|
|
224
|
+
const note = document.createElement('div');
|
|
225
|
+
note.className = 'fd-chart-tile__note';
|
|
226
|
+
note.setAttribute('role', 'note');
|
|
227
|
+
note.textContent = fig.note;
|
|
228
|
+
note.title = fig.note;
|
|
229
|
+
return [plotEl, note];
|
|
379
230
|
}
|
|
380
231
|
|
|
381
|
-
/**
|
|
382
|
-
* Turn normalised data into `{traces, layout}`, or put the tile in the
|
|
383
|
-
* empty state and answer `null`.
|
|
384
|
-
*/
|
|
232
|
+
/** Turn normalised data into `{traces, layout}`. */
|
|
385
233
|
_plan(norm) {
|
|
386
234
|
const type = this.config.chart_type || 'line';
|
|
387
235
|
const { series } = norm;
|
|
@@ -522,42 +370,6 @@ export class ChartTile extends TileBase {
|
|
|
522
370
|
}
|
|
523
371
|
return out;
|
|
524
372
|
}
|
|
525
|
-
|
|
526
|
-
async _downloadPng() {
|
|
527
|
-
if (!this._plotEl || !this._drawn) return;
|
|
528
|
-
try {
|
|
529
|
-
const P = await ensurePlotly();
|
|
530
|
-
const uri = await P.toImage(this._plotEl, { format: 'png', scale: 2 });
|
|
531
|
-
const filename = `${slug(this.config.title || this.constructor.TITLE)}.png`;
|
|
532
|
-
// The native Save dialog when the host has one (a desktop window).
|
|
533
|
-
// `saveFile` answers null when there is no dialog behind it, which
|
|
534
|
-
// means "download it yourself", not a failed save.
|
|
535
|
-
const dialogs = this.host?.dialogs;
|
|
536
|
-
const result = dialogs
|
|
537
|
-
? await dialogs.saveFile({ data: uri.split(',')[1], filename, kind: 'png' })
|
|
538
|
-
: null;
|
|
539
|
-
if (result) {
|
|
540
|
-
if (result.ok) this._notify('Download', `Chart saved to ${result.path}`, 'success');
|
|
541
|
-
else if (!result.cancelled) this._notify('Download failed', result.error || 'The image could not be saved', 'error');
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
const a = document.createElement('a');
|
|
545
|
-
a.href = uri;
|
|
546
|
-
a.download = filename;
|
|
547
|
-
a.click();
|
|
548
|
-
} catch (err) {
|
|
549
|
-
this._notify('Download failed', `The image could not be made: ${err?.message || err}`, 'error');
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
_failed(err) {
|
|
554
|
-
if (this._disposed) return;
|
|
555
|
-
this.showError(`This chart could not be drawn: ${err?.message || err}`);
|
|
556
|
-
this.eventBus?.emit?.('tile:render-failed', {
|
|
557
|
-
tileId: this.id,
|
|
558
|
-
message: err?.message || String(err),
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
373
|
}
|
|
562
374
|
|
|
563
375
|
/**
|
|
@@ -599,10 +411,10 @@ export class KpiTile extends TileBase {
|
|
|
599
411
|
getConfigSchema() {
|
|
600
412
|
return {
|
|
601
413
|
fields: [
|
|
602
|
-
{
|
|
603
|
-
{
|
|
604
|
-
{
|
|
605
|
-
{
|
|
414
|
+
{ key: 'title', type: 'text', label: 'Tile title' },
|
|
415
|
+
{ key: 'label', type: 'text', label: 'Label' },
|
|
416
|
+
{ key: 'unit', type: 'text', label: 'Unit', description: 'Shown after the number, e.g. "pts".' },
|
|
417
|
+
{ key: 'format', type: 'select', label: 'Format',
|
|
606
418
|
options: [
|
|
607
419
|
{ value: 'number', label: 'Number' },
|
|
608
420
|
{ value: 'percent', label: 'Percentage (of a ratio)' },
|
|
@@ -610,15 +422,15 @@ export class KpiTile extends TileBase {
|
|
|
610
422
|
{ value: 'duration', label: 'Duration (seconds)' },
|
|
611
423
|
{ value: 'text', label: 'Text' },
|
|
612
424
|
] },
|
|
613
|
-
{
|
|
614
|
-
{
|
|
615
|
-
|
|
616
|
-
{
|
|
617
|
-
{
|
|
618
|
-
{
|
|
619
|
-
{
|
|
620
|
-
|
|
621
|
-
{
|
|
425
|
+
{ key: 'decimals', type: 'number', label: 'Decimals', min: 0, max: 6 },
|
|
426
|
+
{ key: 'currency', type: 'text', label: 'Currency code',
|
|
427
|
+
description: 'ISO 4217, e.g. EUR. Left empty, the number is shown without a symbol.' },
|
|
428
|
+
{ key: 'prefix', type: 'text', label: 'Prefix' },
|
|
429
|
+
{ key: 'suffix', type: 'text', label: 'Suffix' },
|
|
430
|
+
{ key: 'delta_relative', type: 'checkbox', label: 'Show the change as a percentage' },
|
|
431
|
+
{ key: 'invert_delta', type: 'checkbox', label: 'A fall is good',
|
|
432
|
+
description: 'For a cost, a queue length or a latency: a smaller number is the better one.' },
|
|
433
|
+
{ key: 'foot', type: 'text', label: 'Caption' },
|
|
622
434
|
],
|
|
623
435
|
};
|
|
624
436
|
}
|
|
@@ -834,7 +646,3 @@ function formatValue(value, { format = 'number', decimals = null, prefix = '', s
|
|
|
834
646
|
})();
|
|
835
647
|
return `${prefix || ''}${body}${suffix || ''}`;
|
|
836
648
|
}
|
|
837
|
-
|
|
838
|
-
function slug(text) {
|
|
839
|
-
return String(text).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'chart';
|
|
840
|
-
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Defines field types, validation rules, and default values.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { isHexColor } from '../ui/components/color_field.js';
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Field type definitions for widget configuration.
|
|
10
12
|
*/
|
|
@@ -54,6 +56,12 @@ export function validateField(value, field) {
|
|
|
54
56
|
}
|
|
55
57
|
break;
|
|
56
58
|
|
|
59
|
+
case FieldTypes.COLOR:
|
|
60
|
+
if (value != null && value !== '' && !isHexColor(value)) {
|
|
61
|
+
return { valid: false, error: `${field.label} must be a hex colour (#rgb or #rrggbb)` };
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
|
|
57
65
|
case FieldTypes.MULTISELECT:
|
|
58
66
|
if (field.required && (!Array.isArray(value) || value.length === 0)) {
|
|
59
67
|
return { valid: false, error: `${field.label} must have at least one selection` };
|