flexdesk 0.16.0 → 0.17.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.
- package/CHANGELOG.md +93 -0
- package/LICENSE +0 -0
- package/README.md +2261 -42
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/css/base.css +1834 -75
- package/css/flexdesk.css +3373 -238
- 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 +709 -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-RQM5Z2MU.js → chunk-MCIXFNVF.js} +633 -311
- package/dist/chunk-MCIXFNVF.js.map +7 -0
- package/dist/chunk-N7HC3ADV.js +30 -0
- package/dist/chunk-N7HC3ADV.js.map +7 -0
- package/dist/{chunk-FBGP5KN5.js → chunk-Q36EPT2R.js} +3268 -203
- package/dist/chunk-Q36EPT2R.js.map +7 -0
- package/dist/chunk-QWQJUDPD.js +610 -0
- package/dist/chunk-QWQJUDPD.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-UWO43BZU.js +111 -0
- 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 +3373 -238
- 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 +1131 -253
- package/dist/tiles.js.map +4 -4
- package/dist/tokens.css +0 -0
- package/dist/widgets.js +3254 -2112
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +1227 -345
- package/dist/wm.js.map +4 -4
- package/package.json +3 -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 +422 -0
- package/src/tiles/scope_picker.js +96 -0
- package/src/tiles/tile_base.js +12 -1
- package/src/tiles/tile_config_form.js +442 -0
- package/src/tiles/tile_grid.js +38 -1
- 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 +211 -0
- package/src/ui/components/cell_grid.js +648 -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 +601 -62
- 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 +26 -4
- package/src/ui/components/staged_edits.js +343 -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/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/dist/chunk-6JSOVNID.js.map +0 -7
- package/dist/chunk-CWPMLX67.js +0 -475
- package/dist/chunk-CWPMLX67.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/src/ui/controllers/panel_state_machine.js +0 -634
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fill_handle.js — STORY-0089. DataTable's FILL HANDLE (`fillHandle: true`).
|
|
3
|
+
* INTERNAL to the table, like `cell_grid.js`: built by the cell grid, and
|
|
4
|
+
* reading the table through it.
|
|
5
|
+
*
|
|
6
|
+
* A small square on the bottom-right corner of the cell range. Dragging it
|
|
7
|
+
* up, down, left or right over the cells beyond the range previews the cells
|
|
8
|
+
* it covers (`twm-dt-cell--fill-preview`) and, on release, STAGES their new
|
|
9
|
+
* values (`staged_edits.js`) as ONE undo entry. What the values are is
|
|
10
|
+
* `series.js`'s: each line of the range — a column for a fill up or down, a
|
|
11
|
+
* row for a fill left or right — is a seed, continued as a series or repeated
|
|
12
|
+
* in a cycle; Ctrl (Cmd) held at release repeats it.
|
|
13
|
+
*
|
|
14
|
+
* A target cell that `editable` refuses is SKIPPED: nothing is staged in it,
|
|
15
|
+
* and it is drawn `twm-dt-cell--fill-skipped` until the selection moves, the
|
|
16
|
+
* next fill, or new rows.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { projectSeries } from './series.js';
|
|
20
|
+
|
|
21
|
+
const key = (rowIdx, colIdx) => `${rowIdx}:${colIdx}`;
|
|
22
|
+
|
|
23
|
+
export class FillHandle {
|
|
24
|
+
/** @param {import('./cell_grid.js').CellGrid} grid */
|
|
25
|
+
constructor(grid) {
|
|
26
|
+
this.grid = grid;
|
|
27
|
+
/** The handle element; made on first use, moved into the corner cell. */
|
|
28
|
+
this.el = null;
|
|
29
|
+
/** The `<td>` holding the handle, or null. */
|
|
30
|
+
this.corner = null;
|
|
31
|
+
/** `rowIdx:colIdx` of the cells the last fill skipped. */
|
|
32
|
+
this.skipped = new Set();
|
|
33
|
+
/** `pos:colIdx` of the cells a drag covers now. */
|
|
34
|
+
this.preview = new Set();
|
|
35
|
+
/** The drag in progress: `{range, plan, unbind}`, or null. */
|
|
36
|
+
this.drag = null;
|
|
37
|
+
/** Takes the click guard of the last press on the handle off, or null. */
|
|
38
|
+
this._unguard = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── painting (called by the grid for every cell it paints) ───────────
|
|
42
|
+
|
|
43
|
+
/** One cell: its preview and skipped marks, and the handle when it is
|
|
44
|
+
* the range's corner. */
|
|
45
|
+
paintCell(td, pos, col) {
|
|
46
|
+
const rowIdx = td.parentNode?.__rowIndex;
|
|
47
|
+
td.classList.toggle('twm-dt-cell--fill-preview', this.preview.has(`${pos}:${col}`));
|
|
48
|
+
td.classList.toggle('twm-dt-cell--fill-skipped', this.skipped.has(key(rowIdx, col)));
|
|
49
|
+
const r = this.grid.sel.range();
|
|
50
|
+
if (r && r.bottom === pos && r.right === col) this._place(td);
|
|
51
|
+
else if (td === this.corner) this._unplace();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_handle() {
|
|
55
|
+
if (this.el) return this.el;
|
|
56
|
+
const el = document.createElement('div');
|
|
57
|
+
el.className = 'twm-dt-fill-handle';
|
|
58
|
+
el.setAttribute('aria-hidden', 'true');
|
|
59
|
+
el.title = 'Drag to fill; hold Ctrl to repeat instead of continuing a series';
|
|
60
|
+
el.addEventListener('pointerdown', (ev) => this._begin(ev));
|
|
61
|
+
this.el = el;
|
|
62
|
+
return el;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_place(td) {
|
|
66
|
+
if (this.grid.isEditing() || this.grid.staged.committing) { this._unplace(); return; }
|
|
67
|
+
if (this.corner && this.corner !== td) this.corner.classList.remove('twm-dt-cell--fill-corner');
|
|
68
|
+
this.corner = td;
|
|
69
|
+
td.classList.add('twm-dt-cell--fill-corner');
|
|
70
|
+
const el = this._handle();
|
|
71
|
+
if (el.parentNode !== td) td.appendChild(el);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_unplace() {
|
|
75
|
+
this.corner?.classList.remove('twm-dt-cell--fill-corner');
|
|
76
|
+
this.corner = null;
|
|
77
|
+
this.el?.remove();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── what a fill covers ───────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The fill that reaching cell `to` (`{row, col}`, position and data
|
|
84
|
+
* column) from `range` makes: `{direction, count}`, or null when `to` is
|
|
85
|
+
* inside the range's rows and columns. The direction is the one the cell
|
|
86
|
+
* lies furthest out in; up and down win a tie. Clamped to the page drawn.
|
|
87
|
+
*/
|
|
88
|
+
plan(range, to) {
|
|
89
|
+
if (!range || !to) return null;
|
|
90
|
+
const cols = this.grid.columns();
|
|
91
|
+
const [first, last] = this.grid.span();
|
|
92
|
+
const row = Math.max(first, Math.min(last, Number(to.row)));
|
|
93
|
+
const ordL = cols.indexOf(range.left);
|
|
94
|
+
const ordR = cols.indexOf(range.right);
|
|
95
|
+
const ordTo = cols.indexOf(to.col);
|
|
96
|
+
const out = [
|
|
97
|
+
['down', row - range.bottom],
|
|
98
|
+
['up', range.top - row],
|
|
99
|
+
['right', ordTo < 0 ? 0 : ordTo - ordR],
|
|
100
|
+
['left', ordTo < 0 ? 0 : ordL - ordTo],
|
|
101
|
+
];
|
|
102
|
+
let best = null;
|
|
103
|
+
for (const [direction, count] of out) {
|
|
104
|
+
if (count > 0 && (!best || count > best.count)) best = { direction, count };
|
|
105
|
+
}
|
|
106
|
+
return best;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The cells a plan covers, as `{pos, col}`, line by line in fill order:
|
|
110
|
+
* `[{seed: [{pos, col}], targets: [{pos, col}]}]`. */
|
|
111
|
+
_lines(range, { direction, count }) {
|
|
112
|
+
const cols = this.grid.columns();
|
|
113
|
+
const ordL = cols.indexOf(range.left);
|
|
114
|
+
const ordR = cols.indexOf(range.right);
|
|
115
|
+
const lines = [];
|
|
116
|
+
if (direction === 'down' || direction === 'up') {
|
|
117
|
+
const down = direction === 'down';
|
|
118
|
+
for (let o = ordL; o <= ordR; o++) {
|
|
119
|
+
const col = cols[o];
|
|
120
|
+
const seed = [];
|
|
121
|
+
for (let p = range.top; p <= range.bottom; p++) seed.push({ pos: p, col });
|
|
122
|
+
if (!down) seed.reverse();
|
|
123
|
+
const targets = [];
|
|
124
|
+
for (let i = 1; i <= count; i++) {
|
|
125
|
+
targets.push({ pos: down ? range.bottom + i : range.top - i, col });
|
|
126
|
+
}
|
|
127
|
+
lines.push({ seed, targets });
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
const right = direction === 'right';
|
|
131
|
+
for (let p = range.top; p <= range.bottom; p++) {
|
|
132
|
+
const seed = [];
|
|
133
|
+
for (let o = ordL; o <= ordR; o++) seed.push({ pos: p, col: cols[o] });
|
|
134
|
+
if (!right) seed.reverse();
|
|
135
|
+
const targets = [];
|
|
136
|
+
for (let i = 1; i <= count; i++) {
|
|
137
|
+
const o = right ? ordR + i : ordL - i;
|
|
138
|
+
if (o >= 0 && o < cols.length) targets.push({ pos: p, col: cols[o] });
|
|
139
|
+
}
|
|
140
|
+
lines.push({ seed, targets });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return lines;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── the fill ─────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Fill from the range to cell `to`, staging the values as ONE undo
|
|
150
|
+
* entry. Returns `{direction, count, staged, skipped}` (`skipped` the
|
|
151
|
+
* cells `editable` refused, `{row, col, rowIdx}`), or null when nothing
|
|
152
|
+
* was filled: no range, `to` inside it, an editor open or a commit out.
|
|
153
|
+
*/
|
|
154
|
+
fill(to, { forceCopy = false } = {}) {
|
|
155
|
+
const g = this.grid;
|
|
156
|
+
g._sync();
|
|
157
|
+
const range = g.sel.range();
|
|
158
|
+
const plan = this.plan(range, to);
|
|
159
|
+
if (!plan || g.staged.committing || g.isEditing()) return null;
|
|
160
|
+
const cells = [];
|
|
161
|
+
const skipped = [];
|
|
162
|
+
for (const { seed, targets } of this._lines(range, plan)) {
|
|
163
|
+
const values = projectSeries(
|
|
164
|
+
seed.map(({ pos, col }) => g.shownValue(g.rowIndexAt(pos), col)),
|
|
165
|
+
targets.length, { forceCopy });
|
|
166
|
+
targets.forEach(({ pos, col }, i) => {
|
|
167
|
+
const rowIdx = g.rowIndexAt(pos);
|
|
168
|
+
if (g.canEdit(pos, col)) cells.push({ rowIdx, colIdx: col, value: values[i] });
|
|
169
|
+
else skipped.push({ row: pos, col, rowIdx });
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const staged = g.staged.stageMany(cells, `fill ${cells.length} cell(s)`);
|
|
173
|
+
// The range grows over what was filled, the cursor at its far end.
|
|
174
|
+
const cols = g.columns();
|
|
175
|
+
const ordL = cols.indexOf(range.left);
|
|
176
|
+
const ordR = cols.indexOf(range.right);
|
|
177
|
+
const { direction, count } = plan;
|
|
178
|
+
if (direction === 'down') g.set({ row: range.bottom + count, col: range.right }, { row: range.top, col: range.left });
|
|
179
|
+
else if (direction === 'up') g.set({ row: range.top - count, col: range.left }, { row: range.bottom, col: range.right });
|
|
180
|
+
else if (direction === 'right') g.set({ row: range.bottom, col: cols[Math.min(cols.length - 1, ordR + count)] }, { row: range.top, col: range.left });
|
|
181
|
+
else g.set({ row: range.top, col: cols[Math.max(0, ordL - count)] }, { row: range.bottom, col: range.right });
|
|
182
|
+
// Marked after the selection moved, which clears the last fill's marks.
|
|
183
|
+
for (const s of skipped) this.skipped.add(key(s.rowIdx, s.col));
|
|
184
|
+
if (skipped.length) g.paint();
|
|
185
|
+
return { direction, count, staged, skipped };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The selection moved, or new rows came: the skipped marks are over. */
|
|
189
|
+
clearMarks() {
|
|
190
|
+
this.skipped.clear();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── the drag ─────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
_begin(ev) {
|
|
196
|
+
if (ev.button != null && ev.button !== 0) return;
|
|
197
|
+
ev.preventDefault();
|
|
198
|
+
ev.stopPropagation();
|
|
199
|
+
const g = this.grid;
|
|
200
|
+
g._sync();
|
|
201
|
+
const range = g.sel.range();
|
|
202
|
+
if (!range || this.drag || g.isEditing() || g.staged.committing) return;
|
|
203
|
+
const doc = this.el.ownerDocument;
|
|
204
|
+
this._guardClick(doc);
|
|
205
|
+
const onMove = (move) => this._move(move);
|
|
206
|
+
const onUp = (up) => {
|
|
207
|
+
const plan = this.drag?.plan;
|
|
208
|
+
this._end();
|
|
209
|
+
if (plan) this.fill(this._reach(range, plan), { forceCopy: !!(up.ctrlKey || up.metaKey) });
|
|
210
|
+
try { g.table._tableEl?.focus({ preventScroll: true }); } catch (_) { /* detached */ }
|
|
211
|
+
};
|
|
212
|
+
const onCancel = () => this._end();
|
|
213
|
+
const onKey = (k) => { if (k.key === 'Escape') { k.preventDefault(); k.stopPropagation(); this._end(); } };
|
|
214
|
+
doc.addEventListener('pointermove', onMove);
|
|
215
|
+
doc.addEventListener('pointerup', onUp);
|
|
216
|
+
doc.addEventListener('pointercancel', onCancel);
|
|
217
|
+
doc.addEventListener('keydown', onKey, true);
|
|
218
|
+
this.drag = {
|
|
219
|
+
range,
|
|
220
|
+
plan: null,
|
|
221
|
+
unbind: () => {
|
|
222
|
+
doc.removeEventListener('pointermove', onMove);
|
|
223
|
+
doc.removeEventListener('pointerup', onUp);
|
|
224
|
+
doc.removeEventListener('pointercancel', onCancel);
|
|
225
|
+
doc.removeEventListener('keydown', onKey, true);
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* A press on the handle and its release make, in a browser, a click (and
|
|
232
|
+
* on a second press a dblclick) of their own, on the element under both:
|
|
233
|
+
* the corner cell, the row, the body. Filled, cancelled by Escape or
|
|
234
|
+
* released where it began, a drag is no click on the table: from the
|
|
235
|
+
* press, the next click in the document is swallowed in the capture
|
|
236
|
+
* phase, and so is every dblclick until the task the release is handled
|
|
237
|
+
* in is over. A cancelled pointer makes no click; the guard ends with it.
|
|
238
|
+
*/
|
|
239
|
+
_guardClick(doc) {
|
|
240
|
+
this._unguard?.();
|
|
241
|
+
const view = doc.defaultView;
|
|
242
|
+
let timer = null;
|
|
243
|
+
const swallow = (c) => { c.stopPropagation(); c.preventDefault(); };
|
|
244
|
+
const swallowOnce = (c) => { swallow(c); doc.removeEventListener('click', swallowOnce, true); };
|
|
245
|
+
const released = () => {
|
|
246
|
+
doc.removeEventListener('pointerup', released, true);
|
|
247
|
+
doc.removeEventListener('pointercancel', unguard, true);
|
|
248
|
+
timer = view.setTimeout(unguard, 0);
|
|
249
|
+
};
|
|
250
|
+
const unguard = () => {
|
|
251
|
+
if (timer !== null) view.clearTimeout(timer);
|
|
252
|
+
doc.removeEventListener('click', swallowOnce, true);
|
|
253
|
+
doc.removeEventListener('dblclick', swallow, true);
|
|
254
|
+
doc.removeEventListener('pointerup', released, true);
|
|
255
|
+
doc.removeEventListener('pointercancel', unguard, true);
|
|
256
|
+
if (this._unguard === unguard) this._unguard = null;
|
|
257
|
+
};
|
|
258
|
+
doc.addEventListener('click', swallowOnce, true);
|
|
259
|
+
doc.addEventListener('dblclick', swallow, true);
|
|
260
|
+
doc.addEventListener('pointerup', released, true);
|
|
261
|
+
doc.addEventListener('pointercancel', unguard, true);
|
|
262
|
+
this._unguard = unguard;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** The cell a plan reaches, as `fill` takes it. */
|
|
266
|
+
_reach(range, { direction, count }) {
|
|
267
|
+
const cols = this.grid.columns();
|
|
268
|
+
if (direction === 'down') return { row: range.bottom + count, col: range.right };
|
|
269
|
+
if (direction === 'up') return { row: range.top - count, col: range.left };
|
|
270
|
+
if (direction === 'right') return { row: range.bottom, col: cols[cols.indexOf(range.right) + count] };
|
|
271
|
+
return { row: range.top, col: cols[cols.indexOf(range.left) - count] };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** The data cell under the pointer: the event's target, or (a touch,
|
|
275
|
+
* whose events stay on the handle) the element at its point. */
|
|
276
|
+
_cellUnder(ev) {
|
|
277
|
+
const tbody = this.grid.table._tbodyEl;
|
|
278
|
+
const hit = (el) => {
|
|
279
|
+
const td = el?.closest?.('td');
|
|
280
|
+
if (!td || td.__colIdx === undefined || !tbody?.contains(td)) return null;
|
|
281
|
+
const pos = td.parentNode?.__cellPos;
|
|
282
|
+
return pos === undefined ? null : { row: pos, col: td.__colIdx };
|
|
283
|
+
};
|
|
284
|
+
let cell = ev.target === this.el ? null : hit(ev.target);
|
|
285
|
+
const doc = this.el.ownerDocument;
|
|
286
|
+
if (!cell && typeof doc.elementFromPoint === 'function') {
|
|
287
|
+
cell = hit(doc.elementFromPoint(ev.clientX, ev.clientY));
|
|
288
|
+
}
|
|
289
|
+
return cell;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
_move(ev) {
|
|
293
|
+
const d = this.drag;
|
|
294
|
+
if (!d) return;
|
|
295
|
+
const cell = this._cellUnder(ev);
|
|
296
|
+
if (!cell) return;
|
|
297
|
+
d.plan = this.plan(d.range, cell);
|
|
298
|
+
this.preview.clear();
|
|
299
|
+
if (d.plan) {
|
|
300
|
+
for (const { targets } of this._lines(d.range, d.plan)) {
|
|
301
|
+
for (const t of targets) this.preview.add(`${t.pos}:${t.col}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
this.grid.paint();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
_end() {
|
|
308
|
+
const d = this.drag;
|
|
309
|
+
if (!d) return;
|
|
310
|
+
this.drag = null;
|
|
311
|
+
d.unbind();
|
|
312
|
+
this.preview.clear();
|
|
313
|
+
this.grid.paint();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
dispose() {
|
|
317
|
+
this._end();
|
|
318
|
+
this._unguard?.();
|
|
319
|
+
this._unplace();
|
|
320
|
+
this.skipped.clear();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* called a "template"). The picker is mounted into a host element
|
|
7
7
|
* and fires `onChange(entry)` whenever the selection changes.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* The entries come from `opts.entries`, or else from the host's optional
|
|
10
|
+
* `gallery` capability (`host.gallery.list(kind)`, see host.js). Each entry is
|
|
10
11
|
*
|
|
11
12
|
* { id, kind, label, description, fields }
|
|
12
13
|
*
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
* Usage:
|
|
18
19
|
*
|
|
19
20
|
* const picker = await mountGalleryPicker(hostEl, {
|
|
21
|
+
* host, // or entries: [...]
|
|
20
22
|
* kind: 'agent',
|
|
21
23
|
* selectedId: 'blank',
|
|
22
24
|
* onChange: (entry) => { … },
|
|
@@ -45,8 +47,11 @@ function esc(s) {
|
|
|
45
47
|
* first entry wins.
|
|
46
48
|
* @param {function} [opts.onChange] — `(entry) => void` fired on selection
|
|
47
49
|
* change AND on initial mount.
|
|
48
|
-
* @param {object[]} [opts.entries] — explicit entries (
|
|
49
|
-
*
|
|
50
|
+
* @param {object[]} [opts.entries] — explicit entries (the host is not asked).
|
|
51
|
+
* @param {object} [opts.host] — a Host; its `gallery.list(kind)` supplies
|
|
52
|
+
* the entries when `entries` is absent.
|
|
53
|
+
* With neither, the picker shows
|
|
54
|
+
* `emptyLabel` and warns once.
|
|
50
55
|
* @param {string} [opts.emptyLabel] — copy shown when the catalog is empty
|
|
51
56
|
* for that kind.
|
|
52
57
|
* @returns {Promise<{getSelected, setSelectedById, dispose}>}
|
|
@@ -57,18 +62,24 @@ export async function mountGalleryPicker(hostEl, opts = {}) {
|
|
|
57
62
|
selectedId = null,
|
|
58
63
|
onChange = () => {},
|
|
59
64
|
entries = null,
|
|
65
|
+
host = null,
|
|
60
66
|
emptyLabel = 'No starters available for this kind.',
|
|
61
67
|
} = opts;
|
|
62
68
|
if (!hostEl) throw new Error('mountGalleryPicker: hostEl is required');
|
|
63
69
|
|
|
64
70
|
let catalog = entries;
|
|
65
71
|
if (!catalog) {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} catch (err) {
|
|
70
|
-
console.warn('[gallery-picker] gallery_list failed', err);
|
|
72
|
+
const list = host?.gallery?.list;
|
|
73
|
+
if (typeof list !== 'function') {
|
|
74
|
+
console.warn('[gallery-picker] no entries and no host gallery capability; showing the empty label');
|
|
71
75
|
catalog = [];
|
|
76
|
+
} else {
|
|
77
|
+
try {
|
|
78
|
+
catalog = (await list(kind)) || [];
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.warn('[gallery-picker] host.gallery.list failed', err);
|
|
81
|
+
catalog = [];
|
|
82
|
+
}
|
|
72
83
|
}
|
|
73
84
|
}
|
|
74
85
|
catalog = Array.isArray(catalog) ? catalog : [];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* header_actions.js — STORY-0066. The pieces DataTable's column groups
|
|
3
|
+
* (column_groups.js) and structural edits (table_structure.js) share: the
|
|
4
|
+
* consumer callback called behind a guard, and the action button drawn in a
|
|
5
|
+
* header cell. INTERNAL to the table, and not part of the widgets barrel.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Call the table option `name` (a function on `config`) with `args`. An
|
|
9
|
+
* option that is not a function answers undefined; one that throws is
|
|
10
|
+
* logged and answers undefined too, so a consumer's bug never breaks a
|
|
11
|
+
* render or a click. */
|
|
12
|
+
export function callOption(config, name, ...args) {
|
|
13
|
+
const fn = config?.[name];
|
|
14
|
+
if (typeof fn !== 'function') return undefined;
|
|
15
|
+
try {
|
|
16
|
+
return fn.apply(config, args);
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error(`[DataTable] ${name} threw`, err);
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A button in a header cell for the item `{icon?, label?, title?, disabled?}`:
|
|
25
|
+
* the material-symbols icon, or the label when there is no icon; the title
|
|
26
|
+
* and aria-label from `title ?? label`; disabled when `disabled`. Its press
|
|
27
|
+
* is its own: neither the mousedown nor the click reaches the header (no
|
|
28
|
+
* sort, no drag under it).
|
|
29
|
+
*/
|
|
30
|
+
export function headerActionButton(className, { icon, label, title, disabled } = {}, onClick) {
|
|
31
|
+
const b = document.createElement('button');
|
|
32
|
+
b.type = 'button';
|
|
33
|
+
b.className = className;
|
|
34
|
+
if (icon) {
|
|
35
|
+
const span = document.createElement('span');
|
|
36
|
+
span.className = 'material-symbols-outlined';
|
|
37
|
+
span.textContent = String(icon);
|
|
38
|
+
b.appendChild(span);
|
|
39
|
+
} else if (label != null) {
|
|
40
|
+
b.appendChild(document.createTextNode(String(label)));
|
|
41
|
+
}
|
|
42
|
+
const name = title ?? label;
|
|
43
|
+
if (name != null) {
|
|
44
|
+
b.title = String(name);
|
|
45
|
+
b.setAttribute('aria-label', String(name));
|
|
46
|
+
}
|
|
47
|
+
if (disabled) b.disabled = true;
|
|
48
|
+
b.addEventListener('mousedown', (ev) => ev.stopPropagation());
|
|
49
|
+
b.addEventListener('click', (ev) => {
|
|
50
|
+
ev.preventDefault();
|
|
51
|
+
ev.stopPropagation();
|
|
52
|
+
onClick(ev);
|
|
53
|
+
});
|
|
54
|
+
return b;
|
|
55
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The progress block both job dialogs share — `showRunProgressDialog` and
|
|
3
|
+
* `showComputingWindow` — so a bar and a row of phases behave the same in
|
|
4
|
+
* either. Internal: not in the `widgets` barrel.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ProgressBar, StepProgress } from './progress.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Mount the optional phases and bar into `parent`, phases first.
|
|
11
|
+
*
|
|
12
|
+
* @param {HTMLElement} parent
|
|
13
|
+
* @param {{ progress?: boolean|Object, steps?: Array, spinner?: HTMLElement|null }} options
|
|
14
|
+
* `progress`: `true`, or ProgressBar options (its `onCancel` is ignored: the
|
|
15
|
+
* dialog owns cancelling). `steps`: StepProgress steps. `spinner`: hidden
|
|
16
|
+
* while a bar is shown. Both are fixed at mount: `setProgress` throws
|
|
17
|
+
* without `progress` and `setStep` without `steps`, so the window is sized
|
|
18
|
+
* for what it holds from the start.
|
|
19
|
+
*/
|
|
20
|
+
export function mountJobProgress(parent, { progress = null, steps = null, spinner = null } = {}) {
|
|
21
|
+
const stepper = Array.isArray(steps) && steps.length
|
|
22
|
+
? new StepProgress(parent, { steps, className: 'twm-job-progress__steps' })
|
|
23
|
+
: null;
|
|
24
|
+
let bar = null;
|
|
25
|
+
if (progress) {
|
|
26
|
+
const { onCancel: _ignored, ...rest } = typeof progress === 'object' ? progress : {};
|
|
27
|
+
bar = new ProgressBar(parent, { ...rest, className: 'twm-job-progress__bar' });
|
|
28
|
+
if (spinner) spinner.hidden = true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
get bar() { return bar; },
|
|
33
|
+
get steps() { return stepper; },
|
|
34
|
+
/** `bar.update(next)`. */
|
|
35
|
+
setProgress(next) {
|
|
36
|
+
if (!bar) throw new Error('setProgress: this dialog was opened without `progress`');
|
|
37
|
+
bar.update(next);
|
|
38
|
+
},
|
|
39
|
+
/** `steps.setStep(which, opts)`. */
|
|
40
|
+
setStep(which, opts) {
|
|
41
|
+
if (!stepper) throw new Error('setStep: this dialog was opened without `steps`');
|
|
42
|
+
stepper.setStep(which, opts);
|
|
43
|
+
},
|
|
44
|
+
/** Put the bar and the phases in their final state. */
|
|
45
|
+
finish(status) {
|
|
46
|
+
if (status === 'done') { bar?.setState('done'); stepper?.complete(); }
|
|
47
|
+
else if (status === 'error') { bar?.setState('error'); stepper?.fail(); }
|
|
48
|
+
else bar?.setState('cancelled');
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -19,7 +19,11 @@
|
|
|
19
19
|
|
|
20
20
|
import { getSetting } from '../../core/settings.js';
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
/** Where window geometry lives when the embedder names no key. An embedder
|
|
23
|
+
* that saved geometry under another key keeps it by passing that key to
|
|
24
|
+
* `ManagedWindow.configure({ storageKey })` before the first window opens. */
|
|
25
|
+
export const DEFAULT_WINDOW_STORAGE_KEY = 'flexdesk.managedWindows.v1';
|
|
26
|
+
let _storageKey = DEFAULT_WINDOW_STORAGE_KEY;
|
|
23
27
|
const BASE_Z_INDEX = 6000;
|
|
24
28
|
const MAX_Z_INDEX = 6999;
|
|
25
29
|
|
|
@@ -70,7 +74,7 @@ let _stateCache = null;
|
|
|
70
74
|
function _loadState() {
|
|
71
75
|
if (_stateCache) return _stateCache;
|
|
72
76
|
try {
|
|
73
|
-
const raw = localStorage.getItem(
|
|
77
|
+
const raw = localStorage.getItem(_storageKey);
|
|
74
78
|
_stateCache = raw ? JSON.parse(raw) : {};
|
|
75
79
|
} catch {
|
|
76
80
|
_stateCache = {};
|
|
@@ -81,7 +85,7 @@ function _loadState() {
|
|
|
81
85
|
function _saveState(state) {
|
|
82
86
|
_stateCache = state;
|
|
83
87
|
try {
|
|
84
|
-
localStorage.setItem(
|
|
88
|
+
localStorage.setItem(_storageKey, JSON.stringify(state));
|
|
85
89
|
} catch (err) {
|
|
86
90
|
console.warn('[ManagedWindow] Failed to save state:', err);
|
|
87
91
|
}
|
|
@@ -958,7 +962,7 @@ export class ManagedWindow {
|
|
|
958
962
|
* x = max(minX, min(x, maxX)) = max(0, min(x, maxX)) ✓
|
|
959
963
|
* y = max(minY, min(y, maxY)) = max(TOP, min(y, maxY)) ✓
|
|
960
964
|
*
|
|
961
|
-
*
|
|
965
|
+
* Embedders' modals depend on that arithmetic; prove the
|
|
962
966
|
* equivalence in review by substitution rather than by testing.
|
|
963
967
|
*/
|
|
964
968
|
_bounds() {
|
|
@@ -1661,6 +1665,25 @@ export class ManagedWindow {
|
|
|
1661
1665
|
|
|
1662
1666
|
// ========== Static Methods ==========
|
|
1663
1667
|
|
|
1668
|
+
/**
|
|
1669
|
+
* Choose where window geometry is saved and restored. Call once at boot,
|
|
1670
|
+
* before the first window opens; from then on windows read and write only
|
|
1671
|
+
* `storageKey`. The geometry already read from another key is dropped, so
|
|
1672
|
+
* a window shown afterwards restores what `storageKey` holds.
|
|
1673
|
+
*
|
|
1674
|
+
* @param {object} [opts]
|
|
1675
|
+
* @param {string} [opts.storageKey=DEFAULT_WINDOW_STORAGE_KEY]
|
|
1676
|
+
*/
|
|
1677
|
+
static configure({ storageKey = DEFAULT_WINDOW_STORAGE_KEY } = {}) {
|
|
1678
|
+
_storageKey = storageKey;
|
|
1679
|
+
_stateCache = null;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
/** @returns {string} The localStorage key window geometry uses. */
|
|
1683
|
+
static get storageKey() {
|
|
1684
|
+
return _storageKey;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1664
1687
|
/**
|
|
1665
1688
|
* Get a window by ID.
|
|
1666
1689
|
*/
|