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
package/src/flow/lanes/editor.js
CHANGED
|
@@ -35,9 +35,18 @@
|
|
|
35
35
|
*
|
|
36
36
|
* The kit's (`bindFlowKeys`, on the editor's root): ← → ↑ ↓ move between cards
|
|
37
37
|
* (and between the step tabs), Enter opens the step's settings, Delete removes
|
|
38
|
-
* it, F2 renames it, Ctrl/⌘+Z and Ctrl/⌘+Y undo and
|
|
39
|
-
* from the settings to the card. The context-menu key
|
|
40
|
-
* card's menu. Backspace is never the editor's: a host
|
|
38
|
+
* it, Ctrl/⌘+D duplicates it, F2 renames it, Ctrl/⌘+Z and Ctrl/⌘+Y undo and
|
|
39
|
+
* redo, Escape goes back from the settings to the card. The context-menu key
|
|
40
|
+
* (or Shift+F10) opens a card's menu. Backspace is never the editor's: a host
|
|
41
|
+
* may climb a level on it.
|
|
42
|
+
*
|
|
43
|
+
* ══ THE CARD'S MENU ════════════════════════════════════════════════════
|
|
44
|
+
*
|
|
45
|
+
* *Open its settings*, *Add a step after it*, *Duplicate*, *Remove step*, then
|
|
46
|
+
* the host's own items (`menuItems(step)`). An item that cannot be chosen is
|
|
47
|
+
* greyed WITH ITS REASON in its own line, never only greyed. A host item edits
|
|
48
|
+
* a DRAFT of the flow, and what it changed is taken as one undoable edit
|
|
49
|
+
* (`flow:step:menu`): it goes through the history like every other edit.
|
|
41
50
|
*/
|
|
42
51
|
|
|
43
52
|
import { createStrings, say } from '../kit/strings.js';
|
|
@@ -53,8 +62,9 @@ import { bindFlowKeys } from '../kit/keys.js';
|
|
|
53
62
|
import { DataTable } from '../../ui/components/data_table.js';
|
|
54
63
|
import { layoutLanes, placeLanes, stepPorts } from './layout.js';
|
|
55
64
|
import {
|
|
56
|
-
LANE_ACTIONS, PARAMETER_NAME, addSource, addStepAfter, emptyPipeline, inputCandidates, inputOf,
|
|
57
|
-
normalisePipeline, portsOf, removeParameter, removeStep, serialisePipeline, setInput, setParameter,
|
|
65
|
+
LANE_ACTIONS, PARAMETER_NAME, addSource, addStepAfter, duplicateStep, emptyPipeline, inputCandidates, inputOf,
|
|
66
|
+
laneFeedOf, normalisePipeline, portsOf, removeParameter, removeStep, serialisePipeline, setInput, setParameter,
|
|
67
|
+
upstreamOf,
|
|
58
68
|
} from './pipeline.js';
|
|
59
69
|
import { createPreviewRunner } from './preview.js';
|
|
60
70
|
import { LANE_STRINGS } from './strings.js';
|
|
@@ -124,6 +134,7 @@ export function createLaneEditor(host, options = {}) {
|
|
|
124
134
|
let travelling = null; // 'undo' | 'redo' while a history step is applied
|
|
125
135
|
let shownColumns = ''; // the selected step's input columns the panel was drawn with
|
|
126
136
|
let panelReadOnly = false; // what the panel was last told
|
|
137
|
+
let panelPainting = false; // inside paintPanel: a field it removes losing the focus is not the reader leaving
|
|
127
138
|
|
|
128
139
|
const node = (id) => pipeline.nodes.find((n) => n.id === id) ?? null;
|
|
129
140
|
const typeOf = (n) => catalogue.get(n?.type) ?? null;
|
|
@@ -164,6 +175,7 @@ export function createLaneEditor(host, options = {}) {
|
|
|
164
175
|
let undoBtn = null;
|
|
165
176
|
let redoBtn = null;
|
|
166
177
|
let statusEl = null;
|
|
178
|
+
let noticeEl = null; // why a key did nothing (a refused Ctrl+D)
|
|
167
179
|
let showPreviewBtn = null;
|
|
168
180
|
let showRunBtn = null;
|
|
169
181
|
const actionButtons = new Map();
|
|
@@ -203,6 +215,10 @@ export function createLaneEditor(host, options = {}) {
|
|
|
203
215
|
statusEl = el('span', 'twm-flow-lanes__status');
|
|
204
216
|
statusEl.setAttribute('aria-live', 'polite');
|
|
205
217
|
toolbar.appendChild(statusEl);
|
|
218
|
+
noticeEl = el('span', 'twm-flow-lanes__notice');
|
|
219
|
+
noticeEl.setAttribute('role', 'status');
|
|
220
|
+
noticeEl.hidden = true;
|
|
221
|
+
toolbar.appendChild(noticeEl);
|
|
206
222
|
toolbar.appendChild(el('span', 'twm-flow-lanes__spacer'));
|
|
207
223
|
const showId = uid('lanes-show');
|
|
208
224
|
const showLabel = el('span', 'twm-flow-lanes__show-label', say(S, 'stepsShow'));
|
|
@@ -361,6 +377,7 @@ export function createLaneEditor(host, options = {}) {
|
|
|
361
377
|
function commit(action, key = null, { preview = true } = {}) {
|
|
362
378
|
const moved = history.commit(snapshot(), action, key);
|
|
363
379
|
if (!moved) return false;
|
|
380
|
+
notice('');
|
|
364
381
|
report(action, key);
|
|
365
382
|
if (preview) runner.schedule();
|
|
366
383
|
return true;
|
|
@@ -776,6 +793,16 @@ export function createLaneEditor(host, options = {}) {
|
|
|
776
793
|
}
|
|
777
794
|
const n = selected && selected !== 'flow' ? node(selected) : null;
|
|
778
795
|
settingsHint.hidden = true;
|
|
796
|
+
panelPainting = true;
|
|
797
|
+
try {
|
|
798
|
+
paintPanelBody(n);
|
|
799
|
+
} finally {
|
|
800
|
+
panelPainting = false;
|
|
801
|
+
}
|
|
802
|
+
panel.setFindings(findings);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function paintPanelBody(n) {
|
|
779
806
|
if (n) {
|
|
780
807
|
panel.show(stepPanelSpec(n));
|
|
781
808
|
shownColumns = JSON.stringify(inputColumns(n.id));
|
|
@@ -790,16 +817,15 @@ export function createLaneEditor(host, options = {}) {
|
|
|
790
817
|
settingsHint.hidden = false;
|
|
791
818
|
shownColumns = '';
|
|
792
819
|
}
|
|
793
|
-
panel.setFindings(findings);
|
|
794
820
|
}
|
|
795
821
|
|
|
796
822
|
/** A describe or preview answer changed the selected step's input columns: redraw its fields, unless
|
|
797
823
|
* the reader is in them — a repaint the reader did not ask for rebuilds no field they hold. */
|
|
798
|
-
function reshowForColumns() {
|
|
824
|
+
function reshowForColumns({ leaving = false } = {}) {
|
|
799
825
|
if (!panel || !selected || selected === 'flow' || !node(selected)) return;
|
|
800
826
|
const now = JSON.stringify(inputColumns(selected));
|
|
801
827
|
if (now === shownColumns) return;
|
|
802
|
-
if (panel.el.contains(root.ownerDocument.activeElement)) return;
|
|
828
|
+
if (!leaving && panel.el.contains(root.ownerDocument.activeElement)) return;
|
|
803
829
|
paintPanel();
|
|
804
830
|
}
|
|
805
831
|
|
|
@@ -1063,6 +1089,59 @@ export function createLaneEditor(host, options = {}) {
|
|
|
1063
1089
|
return true;
|
|
1064
1090
|
}
|
|
1065
1091
|
|
|
1092
|
+
/** Why a step cannot be copied now, or null. */
|
|
1093
|
+
function duplicateRefusal(id) {
|
|
1094
|
+
if (isReadOnly()) return readOnlyReason()[0] ?? say(S, 'readOnlyTitle');
|
|
1095
|
+
const r = duplicateStep(normalisePipeline(pipeline), catalogue, id, { strings: options.strings });
|
|
1096
|
+
return r.ok ? null : r.reason;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/** Copy a step (`duplicateStep`), choose the copy and put the focus on it. */
|
|
1100
|
+
function duplicate(id) {
|
|
1101
|
+
if (isReadOnly() || !node(id)) return false;
|
|
1102
|
+
const r = duplicateStep(pipeline, catalogue, id, { strings: options.strings });
|
|
1103
|
+
if (!r.ok) {
|
|
1104
|
+
notice(r.reason);
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
selected = r.node.id;
|
|
1108
|
+
commit('flow:step:duplicate');
|
|
1109
|
+
afterShapeChange(r.node.id);
|
|
1110
|
+
options.onSelect?.({ kind: 'step', id: r.node.id });
|
|
1111
|
+
return true;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/** Words for a refused key, beside the status; an edit clears them. */
|
|
1115
|
+
function notice(text) {
|
|
1116
|
+
if (!noticeEl) return;
|
|
1117
|
+
noticeEl.textContent = String(text ?? '');
|
|
1118
|
+
noticeEl.hidden = !noticeEl.textContent;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Run a host's menu item on a DRAFT of the flow and take what it changed as
|
|
1123
|
+
* ONE undoable edit. An item may answer later (a promise); a draft that
|
|
1124
|
+
* answers after another edit, or once the flow is read only, is dropped —
|
|
1125
|
+
* it was made from a flow that is no longer there.
|
|
1126
|
+
*/
|
|
1127
|
+
async function runHostItem(item, id) {
|
|
1128
|
+
if (isReadOnly() || !node(id)) return false;
|
|
1129
|
+
const before = serialisePipeline(pipeline);
|
|
1130
|
+
const draft = normalisePipeline(pipeline);
|
|
1131
|
+
const step = clone(node(id));
|
|
1132
|
+
const answer = await item.run?.({ step, pipeline: draft, editor: api });
|
|
1133
|
+
if (answer === false || destroyed || isReadOnly()) return false;
|
|
1134
|
+
if (serialisePipeline(pipeline) !== before) return false;
|
|
1135
|
+
if (serialisePipeline(draft) === before) return false;
|
|
1136
|
+
pipeline = normalisePipeline(draft);
|
|
1137
|
+
relayout();
|
|
1138
|
+
if (selected && selected !== 'flow' && !node(selected)) selected = firstStep();
|
|
1139
|
+
commit('flow:step:menu');
|
|
1140
|
+
paintAll();
|
|
1141
|
+
focusCard(selected);
|
|
1142
|
+
return true;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1066
1145
|
function changeSetting(stepId, key, value) {
|
|
1067
1146
|
if (isReadOnly()) return;
|
|
1068
1147
|
if (stepId === null) {
|
|
@@ -1217,28 +1296,52 @@ export function createLaneEditor(host, options = {}) {
|
|
|
1217
1296
|
const menu = el('div', 'twm-flow-lanes__menu');
|
|
1218
1297
|
menu.setAttribute('role', 'menu');
|
|
1219
1298
|
let handle = null;
|
|
1220
|
-
const item = (label, glyph, run, { danger = false,
|
|
1299
|
+
const item = (label, glyph, run, { danger = false, refusal = null, id: itemId = null } = {}) => {
|
|
1221
1300
|
const b = el('button', `twm-flow-lanes__menu-item${danger ? ' twm-flow-lanes__menu-item--danger' : ''}`);
|
|
1301
|
+
b.classList.toggle('twm-flow-lanes__menu-item--refused', Boolean(refusal));
|
|
1222
1302
|
b.type = 'button';
|
|
1223
1303
|
b.setAttribute('role', 'menuitem');
|
|
1224
|
-
b.
|
|
1225
|
-
|
|
1304
|
+
if (itemId) b.dataset.item = itemId;
|
|
1305
|
+
const words = el('span', 'twm-flow-lanes__menu-words');
|
|
1306
|
+
words.appendChild(el('span', 'twm-flow-lanes__menu-label', label));
|
|
1307
|
+
if (refusal) {
|
|
1308
|
+
// Greyed WITH its reason, in its own line: a greyed line nobody can read the reason of is dead.
|
|
1309
|
+
b.disabled = true;
|
|
1310
|
+
b.setAttribute('aria-disabled', 'true');
|
|
1311
|
+
words.appendChild(el('span', 'twm-flow-lanes__menu-reason', refusal));
|
|
1312
|
+
}
|
|
1313
|
+
b.append(icon(glyph || ''), words);
|
|
1226
1314
|
b.addEventListener('click', () => {
|
|
1315
|
+
if (b.disabled) return;
|
|
1227
1316
|
handle?.close('chosen');
|
|
1228
1317
|
run();
|
|
1229
1318
|
});
|
|
1230
1319
|
menu.appendChild(b);
|
|
1231
1320
|
return b;
|
|
1232
1321
|
};
|
|
1233
|
-
const first = item(say(S, 'openSettings'), 'tune', () => openSettingsOf(id));
|
|
1322
|
+
const first = item(say(S, 'openSettings'), 'tune', () => openSettingsOf(id), { id: 'settings' });
|
|
1234
1323
|
if (canAddAfter(id)) {
|
|
1235
1324
|
item(say(S, 'addStepAfter'), 'add', () => {
|
|
1236
1325
|
const add = [...surface.querySelectorAll('.twm-flow-lanes__node')].find((w) => w.dataset.step === id)
|
|
1237
1326
|
?.querySelector('.twm-flow-lanes__add');
|
|
1238
1327
|
if (add) pickAfter(id, add);
|
|
1239
|
-
});
|
|
1328
|
+
}, { id: 'add' });
|
|
1329
|
+
}
|
|
1330
|
+
item(say(S, 'duplicate'), 'content_copy', () => duplicate(id), { refusal: duplicateRefusal(id), id: 'duplicate' });
|
|
1331
|
+
if (!isReadOnly()) item(say(S, 'removeStep'), 'delete', () => remove(id), { danger: true, id: 'remove' });
|
|
1332
|
+
const extra = typeof options.menuItems === 'function' ? options.menuItems(clone(node(id))) : null;
|
|
1333
|
+
const hosts = (Array.isArray(extra) ? extra : []).filter((x) => isObject(x) && x.label !== undefined);
|
|
1334
|
+
if (hosts.length) {
|
|
1335
|
+
const sep = el('div', 'twm-flow-lanes__menu-sep');
|
|
1336
|
+
sep.setAttribute('role', 'separator');
|
|
1337
|
+
menu.appendChild(sep);
|
|
1338
|
+
}
|
|
1339
|
+
const roWhy = isReadOnly() ? (readOnlyReason()[0] ?? say(S, 'readOnlyTitle')) : null;
|
|
1340
|
+
for (const h of hosts) {
|
|
1341
|
+
const refusal = roWhy ?? (h.disabled ? String(h.reason || say(S, 'unavailable')) : null);
|
|
1342
|
+
item(String(h.label), h.icon || '', () => { runHostItem(h, id); },
|
|
1343
|
+
{ danger: Boolean(h.danger), refusal, id: h.id !== undefined ? String(h.id) : null });
|
|
1240
1344
|
}
|
|
1241
|
-
if (!isReadOnly()) item(say(S, 'removeStep'), 'delete', () => remove(id), { danger: true });
|
|
1242
1345
|
menu.addEventListener('keydown', (ev) => {
|
|
1243
1346
|
if (ev.key !== 'ArrowDown' && ev.key !== 'ArrowUp') return;
|
|
1244
1347
|
ev.preventDefault();
|
|
@@ -1277,6 +1380,14 @@ export function createLaneEditor(host, options = {}) {
|
|
|
1277
1380
|
remove(id);
|
|
1278
1381
|
return true;
|
|
1279
1382
|
},
|
|
1383
|
+
duplicate: (ev) => {
|
|
1384
|
+
if (!(inSurface(ev) || inTabs(ev))) return false;
|
|
1385
|
+
const id = stepAt(ev) ?? selected;
|
|
1386
|
+
if (!id || id === 'flow' || !node(id)) return false;
|
|
1387
|
+
if (isReadOnly()) notice(readOnlyReason()[0] ?? say(S, 'readOnlyTitle'));
|
|
1388
|
+
else duplicate(id);
|
|
1389
|
+
return true;
|
|
1390
|
+
},
|
|
1280
1391
|
rename: () => {
|
|
1281
1392
|
if (!selected || selected === 'flow' || !panel) return false;
|
|
1282
1393
|
const t = panel.el.querySelector('[data-twm-flow-title]');
|
|
@@ -1311,6 +1422,16 @@ export function createLaneEditor(host, options = {}) {
|
|
|
1311
1422
|
};
|
|
1312
1423
|
settingsCol?.addEventListener('keydown', onPanelKey);
|
|
1313
1424
|
|
|
1425
|
+
// Columns that arrived while the reader was in the settings are drawn when
|
|
1426
|
+
// the focus LEAVES them (for anything but a popover the panel opened): a
|
|
1427
|
+
// column typed by name before the columns were known becomes a select then.
|
|
1428
|
+
const onPanelLeave = (ev) => {
|
|
1429
|
+
const to = ev.relatedTarget;
|
|
1430
|
+
if (panelPainting || (to && (panel.el.contains(to) || to.closest?.('.twm-flow-popover')))) return;
|
|
1431
|
+
reshowForColumns({ leaving: true });
|
|
1432
|
+
};
|
|
1433
|
+
settingsCol?.addEventListener('focusout', onPanelLeave);
|
|
1434
|
+
|
|
1314
1435
|
function arrow(ev, dir) {
|
|
1315
1436
|
if (inTabs(ev)) {
|
|
1316
1437
|
if (dir !== 'left' && dir !== 'right') return false;
|
|
@@ -1449,6 +1570,7 @@ export function createLaneEditor(host, options = {}) {
|
|
|
1449
1570
|
lastRunAsk?.abort();
|
|
1450
1571
|
unbindKeys();
|
|
1451
1572
|
settingsCol?.removeEventListener('keydown', onPanelKey);
|
|
1573
|
+
settingsCol?.removeEventListener('focusout', onPanelLeave);
|
|
1452
1574
|
closeFlowPopovers();
|
|
1453
1575
|
table?.dispose?.();
|
|
1454
1576
|
table = null;
|
package/src/flow/lanes/index.js
CHANGED
|
@@ -20,7 +20,9 @@ export {
|
|
|
20
20
|
LANE_GEOMETRY, LANE_PAD, LANE_GAP, CW as LANE_CW, CH as LANE_CH, GAP as LANE_COLUMN_GAP,
|
|
21
21
|
LAYOUT_STRINGS as LANE_LAYOUT_STRINGS, layoutLanes, placeLanes, stepPorts as laneStepPorts,
|
|
22
22
|
} from './layout.js';
|
|
23
|
-
export {
|
|
23
|
+
export {
|
|
24
|
+
LANE_ACTIONS, PIPELINE_STRINGS as LANE_PIPELINE_STRINGS, emptyPipeline, normalisePipeline, serialisePipeline,
|
|
25
|
+
} from './pipeline.js';
|
|
24
26
|
export * as lanePipeline from './pipeline.js';
|
|
25
27
|
export { createPreviewRunner as createLanePreviewRunner } from './preview.js';
|
|
26
28
|
export { LANE_STRINGS } from './strings.js';
|
package/src/flow/lanes/layout.js
CHANGED
|
File without changes
|
|
@@ -29,10 +29,18 @@ import { stepPorts } from './layout.js';
|
|
|
29
29
|
|
|
30
30
|
/** The edits the lane editor records — anything else is refused by its history. */
|
|
31
31
|
export const LANE_ACTIONS = Object.freeze([
|
|
32
|
-
'flow:step:add', 'flow:step:remove', 'flow:step:label', 'flow:step:config', 'flow:
|
|
33
|
-
'flow:
|
|
32
|
+
'flow:step:add', 'flow:step:remove', 'flow:step:label', 'flow:step:config', 'flow:step:duplicate',
|
|
33
|
+
'flow:step:menu', 'flow:join:set', 'flow:source:add', 'flow:parameter:add', 'flow:parameter:change',
|
|
34
|
+
'flow:parameter:remove', 'flow:settings',
|
|
34
35
|
]);
|
|
35
36
|
|
|
37
|
+
/** The words the pure edits say: a copy's name, and why a step cannot be copied. */
|
|
38
|
+
export const PIPELINE_STRINGS = Object.freeze({
|
|
39
|
+
copyLabel: (title, n) => (n > 1 ? `${title} (copy ${n})` : `${title} (copy)`),
|
|
40
|
+
duplicateNoStep: (id) => `There is no step ‘${id}’ to copy.`,
|
|
41
|
+
duplicateUnavailable: (title, why) => `‘${title}’ cannot be copied: ${why}`,
|
|
42
|
+
});
|
|
43
|
+
|
|
36
44
|
/** A parameter's name: what a reference to it can spell. */
|
|
37
45
|
export const PARAMETER_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
38
46
|
|
|
@@ -250,6 +258,40 @@ export function removeStep(pipeline, catalogue, id) {
|
|
|
250
258
|
return true;
|
|
251
259
|
}
|
|
252
260
|
|
|
261
|
+
/**
|
|
262
|
+
* A copy of step `id`, put right after it in `nodes`: a fresh id (the
|
|
263
|
+
* catalogue's stem, as for a new step), a label derived from the step's title
|
|
264
|
+
* (`Title (copy)`, then `(copy 2)`, … — never one another step already has), a
|
|
265
|
+
* DEEP copy of its config and every other key it carries (bar `position`, which
|
|
266
|
+
* belongs to the original's place), and THE SAME LINES IN: each connection into
|
|
267
|
+
* the step is repeated into the copy at the same port, under the lowest free
|
|
268
|
+
* ids. Nothing leads out of the copy, so it is a fork of what fed the step (a
|
|
269
|
+
* copied source starts a lane of its own).
|
|
270
|
+
*
|
|
271
|
+
* Returns `{ok: true, node}`, or `{ok: false, reason}` — and changes nothing —
|
|
272
|
+
* when there is no such step, or its type is one the catalogue says is not
|
|
273
|
+
* available here (`unavailable`, which is the reason given).
|
|
274
|
+
*/
|
|
275
|
+
export function duplicateStep(pipeline, catalogue, id, { strings = null } = {}) {
|
|
276
|
+
const S = { ...PIPELINE_STRINGS, ...(isObject(strings) ? strings : {}) };
|
|
277
|
+
const original = findNode(pipeline, id);
|
|
278
|
+
if (!original) return { ok: false, reason: S.duplicateNoStep(String(id)) };
|
|
279
|
+
const type = catalogue?.get?.(original.type) ?? null;
|
|
280
|
+
const title = original.label || type?.label || String(original.type);
|
|
281
|
+
if (type?.unavailable) return { ok: false, reason: S.duplicateUnavailable(title, String(type.unavailable)) };
|
|
282
|
+
const titles = new Set(pipeline.nodes.map((n) => n.label || catalogue?.get?.(n.type)?.label || String(n.type)));
|
|
283
|
+
let label = S.copyLabel(title, 1);
|
|
284
|
+
for (let i = 2; titles.has(label); i += 1) label = S.copyLabel(title, i);
|
|
285
|
+
const copy = { id: nextStepId(pipeline, original.type, catalogue), type: original.type, label,
|
|
286
|
+
config: isObject(original.config) ? clone(original.config) : {} };
|
|
287
|
+
for (const [k, v] of Object.entries(original)) if (!NODE_KEYS.includes(k)) copy[k] = clone(v);
|
|
288
|
+
pipeline.nodes.splice(pipeline.nodes.indexOf(original) + 1, 0, copy);
|
|
289
|
+
for (const c of pipeline.connections.filter((x) => x.targetId === id)) {
|
|
290
|
+
pipeline.connections.push({ ...clone(c), id: nextConnectionId(pipeline), targetId: copy.id });
|
|
291
|
+
}
|
|
292
|
+
return { ok: true, node: copy };
|
|
293
|
+
}
|
|
294
|
+
|
|
253
295
|
/** May `sourceId` feed `stepId` without making a cycle? */
|
|
254
296
|
export function mayFeed(pipeline, stepId, sourceId) {
|
|
255
297
|
if (!sourceId || sourceId === stepId) return false;
|
|
File without changes
|
|
@@ -24,6 +24,7 @@ export const LANE_STRINGS = Object.freeze({
|
|
|
24
24
|
stepMenu: (title) => `${title}: what to do`,
|
|
25
25
|
openSettings: 'Open its settings',
|
|
26
26
|
addStepAfter: 'Add a step after it',
|
|
27
|
+
unavailable: 'Not available here.',
|
|
27
28
|
|
|
28
29
|
// ── the toolbar ────────────────────────────────────────────────────
|
|
29
30
|
toolbar: 'Flow',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/flow/outline/menu.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|