flexdesk 0.16.1 → 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 +2217 -42
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/css/base.css +1834 -75
- package/css/flexdesk.css +3146 -164
- 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-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 +3146 -164
- 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 +3252 -2124
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +1227 -347
- 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 +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 +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 +0 -0
- 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-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,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* column_groups.js — STORY-0066. DataTable's COLUMN GROUPS (`columnGroups`).
|
|
3
|
+
* INTERNAL to the table: imported by `data_table.js` (and its span helpers by
|
|
4
|
+
* `crosstab.js`) only, and not part of the widgets barrel — it reads the
|
|
5
|
+
* table's private fields.
|
|
6
|
+
*
|
|
7
|
+
* A group is `{key, label?, columns: number[]}` (data column indices). The
|
|
8
|
+
* groups draw a second header row above the column headers, one cell a run of
|
|
9
|
+
* shown columns of the same group spanning them, a blank cell over the row
|
|
10
|
+
* number / select column and over any run of columns in no group.
|
|
11
|
+
*
|
|
12
|
+
* ── WHY A TABLE OF ITS OWN ────────────────────────────────────────────────
|
|
13
|
+
*
|
|
14
|
+
* The group row is a separate `<table class="twm-dt-groups">` in the header
|
|
15
|
+
* wrap, above the header table, rather than a first row of the header's
|
|
16
|
+
* `<thead>`. Every width pass of the table (`_measureNaturalWidths`,
|
|
17
|
+
* `_syncHeaderWidths`, the resize drag, `_applyTableWidth`) reads and writes
|
|
18
|
+
* the header rows CELL BY CELL at the column's index, and under
|
|
19
|
+
* `table-layout: fixed` the first row decides the track — a row of spanning
|
|
20
|
+
* cells in either place would misalign every column. So the group table has a
|
|
21
|
+
* `<colgroup>` of its own, one `<col>` a drawn column, and `align()` copies the
|
|
22
|
+
* header's widths, table width, layout and sideways scroll onto it after every
|
|
23
|
+
* pass that changes them (DataTable `_syncHeaderScroll`). The header wrap
|
|
24
|
+
* scrolls both tables together.
|
|
25
|
+
*
|
|
26
|
+
* ── AN EMPTY GROUP KEEPS A PLACEHOLDER COLUMN ─────────────────────────────
|
|
27
|
+
*
|
|
28
|
+
* A group with no columns would have no cell to draw, so it gets one drawn
|
|
29
|
+
* column of its own: a header cell `th.twm-dt-col--placeholder` (holding the
|
|
30
|
+
* group's add control under `onColumnAdd`), an empty filter cell, and an
|
|
31
|
+
* empty `td.twm-dt-cell--placeholder` in every body row. It is NO DATA
|
|
32
|
+
* column: `_dataIdxOfDom` answers null for it, it is not in a copy, the CSV,
|
|
33
|
+
* `ctx.colIdx` or the cell mode. It stands after the shown columns of the
|
|
34
|
+
* groups listed before it.
|
|
35
|
+
*
|
|
36
|
+
* A table without `columnGroups` never builds this.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { callOption, headerActionButton } from './header_actions.js';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A row of spanning cells, cleaned (shared with STORY-0091's crosstab header
|
|
43
|
+
* rows): the data columns that exist, each column in the first cell that lists
|
|
44
|
+
* it, `key`, a label, and `index`, the cell's place in `spec` (the consumer's
|
|
45
|
+
* object is looked up there when drawing, never kept). Under `requireKey` a
|
|
46
|
+
* cell without a key is dropped; a cell without a label is labelled
|
|
47
|
+
* `defaultLabel(cell)`.
|
|
48
|
+
*/
|
|
49
|
+
export function normalizeSpans(spec, n, { requireKey = false, defaultLabel = () => '' } = {}) {
|
|
50
|
+
const taken = new Set();
|
|
51
|
+
const out = [];
|
|
52
|
+
(Array.isArray(spec) ? spec : []).forEach((g, index) => {
|
|
53
|
+
if (!g || (requireKey && g.key == null)) return;
|
|
54
|
+
const columns = [];
|
|
55
|
+
for (const c of Array.isArray(g.columns) ? g.columns : []) {
|
|
56
|
+
const i = Number(c);
|
|
57
|
+
if (!Number.isInteger(i) || i < 0 || i >= n || taken.has(i)) continue;
|
|
58
|
+
taken.add(i);
|
|
59
|
+
columns.push(i);
|
|
60
|
+
}
|
|
61
|
+
out.push({ key: g.key, label: g.label == null ? defaultLabel(g) : String(g.label), columns, index });
|
|
62
|
+
});
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The groups the table was given, cleaned: a key each, the key as the label
|
|
67
|
+
* when none is given. */
|
|
68
|
+
function normalize(spec, n) {
|
|
69
|
+
return normalizeSpans(spec, n, { requireKey: true, defaultLabel: (g) => String(g.key) });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A spanning header cell over a run of columns in no group (or no cell). */
|
|
73
|
+
export function blankSpanCell(span) {
|
|
74
|
+
const th = document.createElement('th');
|
|
75
|
+
th.className = 'twm-dt-group twm-dt-group--none';
|
|
76
|
+
th.colSpan = span;
|
|
77
|
+
return th;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A spanning header cell holding a label, its tooltip `title`. */
|
|
81
|
+
export function labelSpanCell(span, label, title = label) {
|
|
82
|
+
const th = document.createElement('th');
|
|
83
|
+
th.className = 'twm-dt-group';
|
|
84
|
+
th.colSpan = span;
|
|
85
|
+
th.title = title;
|
|
86
|
+
const el = document.createElement('span');
|
|
87
|
+
el.className = 'twm-dt-group__label';
|
|
88
|
+
el.textContent = label;
|
|
89
|
+
th.appendChild(el);
|
|
90
|
+
return th;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class ColumnGroups {
|
|
94
|
+
/** @param {import('./data_table.js').DataTable} table */
|
|
95
|
+
constructor(table) {
|
|
96
|
+
this.table = table;
|
|
97
|
+
this._memoKey = null;
|
|
98
|
+
this._memo = null;
|
|
99
|
+
this._el = null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_cfg() {
|
|
103
|
+
return this.table.config;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The drawn columns after the lead, in order: `{col, group}` for a shown
|
|
108
|
+
* data column (`group` the index in `groups`, or null) and
|
|
109
|
+
* `{placeholder: true, group}` for an empty group's column. The data
|
|
110
|
+
* columns come in the order the table draws them (STORY-0086's
|
|
111
|
+
* `columnOrder`, hidden ones left out). Memoised on the groups' content,
|
|
112
|
+
* the column count and the drawn columns.
|
|
113
|
+
*/
|
|
114
|
+
layout() {
|
|
115
|
+
const cfg = this._cfg();
|
|
116
|
+
const n = cfg.headers?.length || 0;
|
|
117
|
+
const shown = this.table._shownColumns();
|
|
118
|
+
const spec = Array.isArray(cfg.columnGroups) ? cfg.columnGroups : [];
|
|
119
|
+
const key = `${n}|${shown.join(',')}|${spec.map((g) => (g ? `${g.key}\x01${g.label}\x01${(g.columns || []).join(',')}` : '')).join('\x02')}`;
|
|
120
|
+
if (key === this._memoKey) return this._memo;
|
|
121
|
+
|
|
122
|
+
const groups = normalize(spec, n);
|
|
123
|
+
const groupOf = new Array(n).fill(null);
|
|
124
|
+
groups.forEach((g, gi) => { for (const c of g.columns) groupOf[c] = gi; });
|
|
125
|
+
const empty = groups.map((g) => g.columns.length === 0);
|
|
126
|
+
const placed = new Set();
|
|
127
|
+
const slots = [];
|
|
128
|
+
const placeEmptyBefore = (gi) => {
|
|
129
|
+
for (let e = 0; e < gi; e++) {
|
|
130
|
+
if (empty[e] && !placed.has(e)) { placed.add(e); slots.push({ placeholder: true, group: e }); }
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
for (const c of shown) {
|
|
134
|
+
const gi = groupOf[c];
|
|
135
|
+
if (gi !== null) placeEmptyBefore(gi);
|
|
136
|
+
slots.push({ col: c, group: gi });
|
|
137
|
+
}
|
|
138
|
+
placeEmptyBefore(groups.length);
|
|
139
|
+
|
|
140
|
+
const slotOfCol = new Map();
|
|
141
|
+
slots.forEach((s, i) => { if (!s.placeholder) slotOfCol.set(s.col, i); });
|
|
142
|
+
const placeholders = slots.filter((s) => s.placeholder).length;
|
|
143
|
+
this._memoKey = key;
|
|
144
|
+
this._memo = { groups, slots, slotOfCol, placeholders };
|
|
145
|
+
return this._memo;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** How many placeholder columns are drawn. */
|
|
149
|
+
placeholderCount() {
|
|
150
|
+
return this.layout().placeholders;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** A shown data column's place after the lead, or -1 when the layout
|
|
154
|
+
* has no placeholder (the table's own arithmetic then holds). */
|
|
155
|
+
slotOf(colIdx) {
|
|
156
|
+
const l = this.layout();
|
|
157
|
+
if (!l.placeholders) return -1;
|
|
158
|
+
return l.slotOfCol.has(colIdx) ? l.slotOfCol.get(colIdx) : -1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** What the drawn column `i` after the lead is: the data column, null for
|
|
162
|
+
* a placeholder or past the end, `undefined` when the layout has no
|
|
163
|
+
* placeholder (the table's own arithmetic then holds). */
|
|
164
|
+
slotAt(i) {
|
|
165
|
+
const l = this.layout();
|
|
166
|
+
if (!l.placeholders) return undefined;
|
|
167
|
+
const s = l.slots[i];
|
|
168
|
+
return s && !s.placeholder ? s.col : null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Part of the table's column signature: where the placeholders stand. */
|
|
172
|
+
sig() {
|
|
173
|
+
const l = this.layout();
|
|
174
|
+
if (!l.placeholders) return '';
|
|
175
|
+
return `|g${l.slots.map((s, i) => (s.placeholder ? i : '')).filter((x) => x !== '').join(',')}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The drawn positions (after the lead) of the placeholders, ascending,
|
|
179
|
+
* with their groups. */
|
|
180
|
+
_placeholderSlots() {
|
|
181
|
+
const out = [];
|
|
182
|
+
this.layout().slots.forEach((s, i) => { if (s.placeholder) out.push({ at: i, group: s.group }); });
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Put the placeholders' cells into a drawn row whose cells are the lead
|
|
187
|
+
* and the shown data columns, in order. */
|
|
188
|
+
_insert(tr, make) {
|
|
189
|
+
const lead = this.table._lead().all;
|
|
190
|
+
for (const { at, group } of this._placeholderSlots()) {
|
|
191
|
+
const ref = tr.children[lead + at] || null;
|
|
192
|
+
tr.insertBefore(make(this.layout().groups[group]), ref);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** STORY-0066. A body row gets an empty cell under each placeholder. */
|
|
197
|
+
fillRow(tr) {
|
|
198
|
+
if (!this.placeholderCount()) return;
|
|
199
|
+
this._insert(tr, () => {
|
|
200
|
+
const td = document.createElement('td');
|
|
201
|
+
td.className = 'twm-dt-cell--placeholder';
|
|
202
|
+
return td;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* After the header is built and in the header table: the placeholders'
|
|
208
|
+
* header and filter cells, and the group table above the header table.
|
|
209
|
+
*/
|
|
210
|
+
decorate(headerWrap, headerTable) {
|
|
211
|
+
this._el = null;
|
|
212
|
+
if (!headerWrap || !headerTable) return;
|
|
213
|
+
const thead = headerTable.querySelector('thead');
|
|
214
|
+
const labelRow = thead?.querySelector('tr');
|
|
215
|
+
if (!labelRow) return;
|
|
216
|
+
if (this.placeholderCount()) {
|
|
217
|
+
this._insert(labelRow, (g) => this._placeholderHead(g));
|
|
218
|
+
for (const tr of thead.querySelectorAll('tr.twm-data-table__filter-row')) {
|
|
219
|
+
this._insert(tr, () => {
|
|
220
|
+
const th = document.createElement('th');
|
|
221
|
+
th.className = 'data-table__filter-cell data-table__filter-cell--empty twm-dt-col--placeholder';
|
|
222
|
+
return th;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const groupTable = this._buildGroupTable(headerTable);
|
|
227
|
+
headerWrap.insertBefore(groupTable, headerTable);
|
|
228
|
+
this._el = groupTable;
|
|
229
|
+
this.align();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
_placeholderHead(group) {
|
|
233
|
+
const th = document.createElement('th');
|
|
234
|
+
th.className = 'text twm-dt-col--placeholder';
|
|
235
|
+
if (typeof this._cfg().onColumnAdd === 'function') {
|
|
236
|
+
th.appendChild(this._addButton(group));
|
|
237
|
+
}
|
|
238
|
+
return th;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
_addButton(group) {
|
|
242
|
+
return headerActionButton('twm-dt-group__add', { icon: 'add', title: `Add column to ${group.label}` },
|
|
243
|
+
() => callOption(this._cfg(), 'onColumnAdd', group.key));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
_buildGroupTable(headerTable) {
|
|
247
|
+
const { groups, slots } = this.layout();
|
|
248
|
+
const lead = this.table._lead().all;
|
|
249
|
+
const table = document.createElement('table');
|
|
250
|
+
table.className = `${headerTable.className} twm-dt-groups`;
|
|
251
|
+
const colgroup = document.createElement('colgroup');
|
|
252
|
+
for (let i = 0; i < lead + slots.length; i++) colgroup.appendChild(document.createElement('col'));
|
|
253
|
+
table.appendChild(colgroup);
|
|
254
|
+
const thead = document.createElement('thead');
|
|
255
|
+
const tr = document.createElement('tr');
|
|
256
|
+
tr.className = 'twm-dt-group-row';
|
|
257
|
+
if (lead) {
|
|
258
|
+
const th = document.createElement('th');
|
|
259
|
+
th.className = 'twm-dt-group twm-dt-group--lead';
|
|
260
|
+
th.colSpan = lead;
|
|
261
|
+
tr.appendChild(th);
|
|
262
|
+
}
|
|
263
|
+
// Runs of the same group (or of no group) are one cell.
|
|
264
|
+
let i = 0;
|
|
265
|
+
while (i < slots.length) {
|
|
266
|
+
const gi = slots[i].group;
|
|
267
|
+
let j = i + 1;
|
|
268
|
+
while (j < slots.length && slots[j].group === gi) j++;
|
|
269
|
+
tr.appendChild(gi === null ? blankSpanCell(j - i) : this._groupCell(groups[gi], j - i, slots[i].placeholder));
|
|
270
|
+
i = j;
|
|
271
|
+
}
|
|
272
|
+
thead.appendChild(tr);
|
|
273
|
+
table.appendChild(thead);
|
|
274
|
+
return table;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
_groupCell(group, span, empty) {
|
|
278
|
+
const th = labelSpanCell(span, group.label);
|
|
279
|
+
if (empty) th.classList.add('twm-dt-group--empty');
|
|
280
|
+
|
|
281
|
+
const actions = document.createElement('span');
|
|
282
|
+
actions.className = 'twm-dt-group__actions';
|
|
283
|
+
const cfg = this._cfg();
|
|
284
|
+
// The consumer's group object from the live config: the layout is
|
|
285
|
+
// memoised on key, label and columns, so a group whose other fields
|
|
286
|
+
// changed is still asked about with its current object.
|
|
287
|
+
const items = callOption(cfg, 'groupActions', cfg.columnGroups[group.index], { key: group.key, columns: group.columns.slice(), table: this.table });
|
|
288
|
+
for (const item of Array.isArray(items) ? items.filter(Boolean) : []) {
|
|
289
|
+
actions.appendChild(headerActionButton('twm-dt-group__action', item,
|
|
290
|
+
() => callOption(cfg, 'onGroupAction', item.action, group.key)));
|
|
291
|
+
}
|
|
292
|
+
if (typeof cfg.onColumnAdd === 'function') actions.appendChild(this._addButton(group));
|
|
293
|
+
if (actions.childNodes.length) th.appendChild(actions);
|
|
294
|
+
return th;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** The group table follows the header table: the same column widths,
|
|
298
|
+
* table width, layout and sideways offset. */
|
|
299
|
+
align() {
|
|
300
|
+
const groupTable = this._el;
|
|
301
|
+
const headerTable = this.table._headerTableEl;
|
|
302
|
+
if (!groupTable || !headerTable) return;
|
|
303
|
+
const labelRow = headerTable.querySelector('thead > tr');
|
|
304
|
+
const cols = groupTable.querySelector('colgroup')?.children || [];
|
|
305
|
+
for (let i = 0; i < cols.length; i++) {
|
|
306
|
+
const th = labelRow?.children[i];
|
|
307
|
+
const w = th ? parseFloat(th.style.width) : NaN;
|
|
308
|
+
cols[i].style.width = Number.isFinite(w) ? `${w}px` : '';
|
|
309
|
+
}
|
|
310
|
+
groupTable.style.width = headerTable.style.width;
|
|
311
|
+
groupTable.style.tableLayout = headerTable.style.tableLayout;
|
|
312
|
+
groupTable.style.transform = headerTable.style.transform;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* column_order.js — STORY-0086. The order DataTable draws its data columns in
|
|
3
|
+
* (`reorderable`, `columnOrder`, `moveColumn`, `getColumnOrder`), and the drag
|
|
4
|
+
* on the header that changes it. Internal to the table, like `cell_grid.js`.
|
|
5
|
+
*
|
|
6
|
+
* ══ AN ORDER IS A LIST OF DATA INDICES ══════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* The order is every data column's index (hidden ones included), in the order
|
|
9
|
+
* shown: `[0, 3, 1, 2]` draws column 3 second. Everything else the table keeps
|
|
10
|
+
* by column — `hiddenColumns`, the sort, the filters, `renderCell`'s `colIdx`,
|
|
11
|
+
* the cell mode's `col` — stays a data index and so stays on its column. Only
|
|
12
|
+
* what is BY DRAWN POSITION (the cells of a row, the widths) follows the order.
|
|
13
|
+
*
|
|
14
|
+
* A table with no order asked for (`null`) draws 0, 1, 2, … as in 0.16. An
|
|
15
|
+
* order that is not a permutation of today's columns — a saved one from when
|
|
16
|
+
* the table had a different number of columns — is no order: the natural one
|
|
17
|
+
* is drawn.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Whether `order` is a permutation of `0..n-1`. */
|
|
21
|
+
export function isColumnOrder(order, n) {
|
|
22
|
+
if (!Array.isArray(order) || order.length !== n) return false;
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
for (const c of order) {
|
|
25
|
+
if (!Number.isInteger(c) || c < 0 || c >= n || seen.has(c)) return false;
|
|
26
|
+
seen.add(c);
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** `0..n-1`. */
|
|
32
|
+
export function naturalOrder(n) {
|
|
33
|
+
return Array.from({ length: Math.max(0, n) }, (_, i) => i);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The data indices of a row of `count` cells in the order drawn: `order`
|
|
38
|
+
* where it holds, and any cell past the headers (a row longer than them) after
|
|
39
|
+
* it, as 0.16 drew them.
|
|
40
|
+
*/
|
|
41
|
+
export function cellOrder(order, count) {
|
|
42
|
+
if (!order) return naturalOrder(count);
|
|
43
|
+
const out = order.filter((c) => c < count);
|
|
44
|
+
for (let c = order.length; c < count; c++) out.push(c);
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** `order` with the column at position `from` taken out and put back at
|
|
49
|
+
* position `to` (positions in `order`). A copy; `order` is not changed. */
|
|
50
|
+
export function moveInOrder(order, from, to) {
|
|
51
|
+
const next = order.slice();
|
|
52
|
+
const [moved] = next.splice(from, 1);
|
|
53
|
+
next.splice(to, 0, moved);
|
|
54
|
+
return next;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The header drag. Every data column's header cell is draggable; dropping one
|
|
59
|
+
* on another data column's header moves it to that column's place. A press
|
|
60
|
+
* that starts on a resize grip is the resize's, never a drag. The row-number
|
|
61
|
+
* and select columns are no data columns: they do not drag and take no drop,
|
|
62
|
+
* so nothing lands in front of them.
|
|
63
|
+
*/
|
|
64
|
+
export class ColumnReorder {
|
|
65
|
+
/** @param {import('./data_table.js').DataTable} table */
|
|
66
|
+
constructor(table) {
|
|
67
|
+
this.table = table;
|
|
68
|
+
/** The data column being dragged, or null. */
|
|
69
|
+
this.dragging = null;
|
|
70
|
+
/** Whether the last press on a header cell was on its resize grip. */
|
|
71
|
+
this.onGrip = false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Wire the header row of a fresh render. Its cells are new each render,
|
|
75
|
+
* so nothing needs taking off. */
|
|
76
|
+
install(headRow) {
|
|
77
|
+
if (!headRow) return;
|
|
78
|
+
[...headRow.children].forEach((th, domIdx) => {
|
|
79
|
+
const col = this.table._dataIdxOfDom(domIdx);
|
|
80
|
+
if (col === null) return;
|
|
81
|
+
th.draggable = true;
|
|
82
|
+
th.classList.add('twm-dt-col--movable');
|
|
83
|
+
// Capture: the grip stops its own mousedown, so a listener on the
|
|
84
|
+
// way up would never hear it.
|
|
85
|
+
th.addEventListener('mousedown', (ev) => {
|
|
86
|
+
this.onGrip = !!ev.target?.closest?.('.twm-dt-col-resizer');
|
|
87
|
+
}, true);
|
|
88
|
+
th.addEventListener('dragstart', (ev) => {
|
|
89
|
+
if (this.onGrip || ev.target?.closest?.('.twm-dt-col-resizer')) {
|
|
90
|
+
ev.preventDefault();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.dragging = col;
|
|
94
|
+
try {
|
|
95
|
+
ev.dataTransfer?.setData('text/plain', String(col));
|
|
96
|
+
if (ev.dataTransfer) ev.dataTransfer.effectAllowed = 'move';
|
|
97
|
+
} catch (_) { /* a drag with no data transfer */ }
|
|
98
|
+
th.classList.add('twm-dt-col--dragging');
|
|
99
|
+
});
|
|
100
|
+
th.addEventListener('dragover', (ev) => {
|
|
101
|
+
if (this.dragging === null || this.dragging === col) return;
|
|
102
|
+
ev.preventDefault(); // a drop is welcome here
|
|
103
|
+
if (ev.dataTransfer) ev.dataTransfer.dropEffect = 'move';
|
|
104
|
+
// Moved right it lands AFTER the column it is dropped on.
|
|
105
|
+
const order = this.table.getColumnOrder();
|
|
106
|
+
th.classList.add('twm-dt-col--drop');
|
|
107
|
+
th.classList.toggle('twm-dt-col--drop-after', order.indexOf(col) > order.indexOf(this.dragging));
|
|
108
|
+
});
|
|
109
|
+
th.addEventListener('dragleave', () => th.classList.remove('twm-dt-col--drop', 'twm-dt-col--drop-after'));
|
|
110
|
+
th.addEventListener('drop', (ev) => {
|
|
111
|
+
th.classList.remove('twm-dt-col--drop', 'twm-dt-col--drop-after');
|
|
112
|
+
const from = this.dragging;
|
|
113
|
+
this.dragging = null;
|
|
114
|
+
if (from === null || from === col) return;
|
|
115
|
+
ev.preventDefault();
|
|
116
|
+
const order = this.table.getColumnOrder();
|
|
117
|
+
this.table.moveColumn(order.indexOf(from), order.indexOf(col));
|
|
118
|
+
});
|
|
119
|
+
th.addEventListener('dragend', () => {
|
|
120
|
+
th.classList.remove('twm-dt-col--dragging');
|
|
121
|
+
this.dragging = null;
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** "Move left" / "Move right" for the header menu of data column `col`:
|
|
127
|
+
* past the shown column next to it. */
|
|
128
|
+
menuItems(col) {
|
|
129
|
+
const shown = this.table._shownColumns();
|
|
130
|
+
const at = shown.indexOf(col);
|
|
131
|
+
if (at < 0) return [];
|
|
132
|
+
const order = this.table.getColumnOrder();
|
|
133
|
+
const step = (to) => () => this.table.moveColumn(order.indexOf(col), order.indexOf(to));
|
|
134
|
+
return [
|
|
135
|
+
{ label: 'Move left', icon: 'arrow_back', action: 'move-left', disabled: at === 0,
|
|
136
|
+
onChoose: at === 0 ? undefined : step(shown[at - 1]) },
|
|
137
|
+
{ label: 'Move right', icon: 'arrow_forward', action: 'move-right',
|
|
138
|
+
disabled: at === shown.length - 1,
|
|
139
|
+
onChoose: at === shown.length - 1 ? undefined : step(shown[at + 1]) },
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -1,53 +1,102 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Computing Status Window
|
|
3
3
|
*
|
|
4
|
-
* Lightweight ManagedWindow that shows a spinner and
|
|
5
|
-
* while a long-running computation is in progress. Returns a
|
|
6
|
-
* with update()
|
|
4
|
+
* Lightweight ManagedWindow that shows a spinner (or a progress bar) and a
|
|
5
|
+
* status message while a long-running computation is in progress. Returns a
|
|
6
|
+
* handle with update(), setProgress(), setStep(), complete(), cancelled(),
|
|
7
|
+
* close() and a `result` promise.
|
|
7
8
|
*
|
|
8
9
|
* Optionally shows a Cancel button when an onCancel callback is provided.
|
|
10
|
+
* Only one is open at a time: opening another closes the first, which then
|
|
11
|
+
* settles as cancelled (its onCancel is not called).
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
import { ManagedWindow } from './managed_window.js';
|
|
15
|
+
import { mountJobProgress } from './job_progress.js';
|
|
12
16
|
|
|
13
17
|
let _activeWindow = null;
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* Show a computing status window.
|
|
21
|
+
*
|
|
22
|
+
* `result` settles once: `complete({ok, message})` gives `done` or `error`
|
|
23
|
+
* (the window stays, with a Close button, until closed), `cancelled()` gives
|
|
24
|
+
* `cancelled`, and so does closing the window while it runs (by its ×,
|
|
25
|
+
* Escape, `close()` or opening another). Closing never calls `onCancel`: only
|
|
26
|
+
* the Cancel button does. `progress` and `steps` are fixed when the window
|
|
27
|
+
* opens: `setProgress` throws if it was opened without `progress`, and
|
|
28
|
+
* `setStep` if it was opened without `steps`.
|
|
29
|
+
*
|
|
17
30
|
* @param {Object} options
|
|
18
31
|
* @param {string} [options.title='Computing...'] - Window title
|
|
19
|
-
* @param {string} [options.message=''] - Status message below spinner
|
|
32
|
+
* @param {string} [options.message=''] - Status message below spinner (markup; `update` takes text)
|
|
20
33
|
* @param {string} [options.icon='hourglass_top'] - Material icon name
|
|
21
34
|
* @param {Function} [options.onCancel] - If provided, shows a Cancel button. Called once when clicked.
|
|
22
|
-
* @
|
|
35
|
+
* @param {boolean|Object} [options.progress] - A ProgressBar in place of the spinner: `true` or its options.
|
|
36
|
+
* @param {Array} [options.steps] - StepProgress steps, shown above the message.
|
|
37
|
+
* @returns {{ update(msg: string): void, setProgress(next: number|Object): void,
|
|
38
|
+
* setStep(which: string|number, opts?: {pass?: number, passes?: number}): void,
|
|
39
|
+
* complete(result: {ok: boolean, message?: string}): void, cancelled(msg?: string): void,
|
|
40
|
+
* close(): void, result: Promise<{status: 'done'|'error'|'cancelled', message: string}> }}
|
|
23
41
|
*/
|
|
24
|
-
export function showComputingWindow({
|
|
42
|
+
export function showComputingWindow({
|
|
43
|
+
title = 'Computing...', message = '', icon = 'hourglass_top', onCancel,
|
|
44
|
+
progress = null, steps = null,
|
|
45
|
+
} = {}) {
|
|
25
46
|
if (_activeWindow) {
|
|
26
47
|
_activeWindow.close();
|
|
27
48
|
_activeWindow = null;
|
|
28
49
|
}
|
|
29
50
|
|
|
51
|
+
let finished = false;
|
|
52
|
+
let settle;
|
|
53
|
+
const result = new Promise((resolve) => { settle = resolve; });
|
|
54
|
+
|
|
30
55
|
const content = document.createElement('div');
|
|
31
56
|
content.className = 'twm-computing-status';
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
57
|
+
const spinnerEl = document.createElement('div');
|
|
58
|
+
spinnerEl.className = 'twm-computing-status__spinner';
|
|
59
|
+
const msgEl = document.createElement('div');
|
|
60
|
+
msgEl.className = 'twm-computing-status__message';
|
|
61
|
+
// The opening message is markup, as it always was; every later one is text.
|
|
62
|
+
msgEl.innerHTML = message;
|
|
63
|
+
content.appendChild(spinnerEl);
|
|
64
|
+
const job = mountJobProgress(content, { progress, steps, spinner: spinnerEl });
|
|
65
|
+
content.appendChild(msgEl);
|
|
36
66
|
|
|
67
|
+
let cancelBtn = null;
|
|
37
68
|
if (typeof onCancel === 'function') {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (msgEl) msgEl.textContent = 'Cancelling...';
|
|
69
|
+
cancelBtn = document.createElement('button');
|
|
70
|
+
cancelBtn.className = 'twm-computing-status__cancel-btn';
|
|
71
|
+
cancelBtn.textContent = 'Cancel';
|
|
72
|
+
cancelBtn.addEventListener('click', () => {
|
|
73
|
+
cancelBtn.disabled = true;
|
|
74
|
+
msgEl.textContent = 'Cancelling...';
|
|
45
75
|
onCancel();
|
|
46
76
|
}, { once: true });
|
|
47
|
-
content.appendChild(
|
|
77
|
+
content.appendChild(cancelBtn);
|
|
48
78
|
}
|
|
49
79
|
|
|
50
|
-
|
|
80
|
+
const closeBtn = document.createElement('button');
|
|
81
|
+
closeBtn.type = 'button';
|
|
82
|
+
closeBtn.className = 'twm-computing-status__cancel-btn twm-computing-status__close-btn';
|
|
83
|
+
closeBtn.textContent = 'Close';
|
|
84
|
+
closeBtn.hidden = true;
|
|
85
|
+
content.appendChild(closeBtn);
|
|
86
|
+
|
|
87
|
+
const finish = (status, text) => {
|
|
88
|
+
finished = true;
|
|
89
|
+
spinnerEl.hidden = true;
|
|
90
|
+
if (cancelBtn) cancelBtn.hidden = true;
|
|
91
|
+
msgEl.textContent = text;
|
|
92
|
+
msgEl.classList.toggle('is-ok', status === 'done');
|
|
93
|
+
msgEl.classList.toggle('is-err', status === 'error');
|
|
94
|
+
msgEl.classList.toggle('is-cancelled', status === 'cancelled');
|
|
95
|
+
job.finish(status);
|
|
96
|
+
settle({ status, message: text });
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const win = new ManagedWindow({
|
|
51
100
|
id: 'computing-status-window',
|
|
52
101
|
title,
|
|
53
102
|
icon,
|
|
@@ -55,21 +104,45 @@ export function showComputingWindow({ title = 'Computing...', message = '', icon
|
|
|
55
104
|
minWidth: 320,
|
|
56
105
|
minHeight: 140,
|
|
57
106
|
defaultWidth: 380,
|
|
58
|
-
defaultHeight: onCancel ? 190 : 160,
|
|
107
|
+
defaultHeight: (onCancel ? 190 : 160) + (job.bar ? 30 : 0) + (job.steps ? 40 : 0),
|
|
59
108
|
canMinimize: true,
|
|
60
109
|
canMaximize: false,
|
|
61
110
|
canResize: false,
|
|
62
111
|
canDrag: true,
|
|
63
112
|
modal: false,
|
|
64
|
-
onClose: () => {
|
|
113
|
+
onClose: () => {
|
|
114
|
+
if (_activeWindow === win) _activeWindow = null;
|
|
115
|
+
if (finished) return;
|
|
116
|
+
finish('cancelled', 'Cancelled.');
|
|
117
|
+
},
|
|
65
118
|
});
|
|
66
|
-
|
|
67
|
-
_activeWindow
|
|
119
|
+
closeBtn.addEventListener('click', () => win.close());
|
|
120
|
+
_activeWindow = win;
|
|
121
|
+
win.show();
|
|
68
122
|
|
|
69
123
|
return {
|
|
124
|
+
result,
|
|
70
125
|
update(msg) {
|
|
71
|
-
|
|
72
|
-
|
|
126
|
+
if (finished) return;
|
|
127
|
+
msgEl.textContent = msg;
|
|
128
|
+
},
|
|
129
|
+
setProgress(next) {
|
|
130
|
+
if (finished) return;
|
|
131
|
+
job.setProgress(next);
|
|
132
|
+
},
|
|
133
|
+
setStep(which, opts) {
|
|
134
|
+
if (finished) return;
|
|
135
|
+
job.setStep(which, opts);
|
|
136
|
+
},
|
|
137
|
+
complete(res) {
|
|
138
|
+
if (finished) return;
|
|
139
|
+
finish(res?.ok ? 'done' : 'error', res?.message || '');
|
|
140
|
+
closeBtn.hidden = false;
|
|
141
|
+
},
|
|
142
|
+
cancelled(msg) {
|
|
143
|
+
if (finished) return;
|
|
144
|
+
finish('cancelled', msg || 'Cancelled.');
|
|
145
|
+
closeBtn.hidden = false;
|
|
73
146
|
},
|
|
74
147
|
close() {
|
|
75
148
|
if (_activeWindow) {
|