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
|
@@ -12,5 +12,6 @@ export { createCanvasEditor, CANVAS_ACTIONS } from './editor.js';
|
|
|
12
12
|
export { CANVAS_STRINGS } from './strings.js';
|
|
13
13
|
export {
|
|
14
14
|
CANVAS_NODE, layoutGraph as arrangeCanvasGraph, portAnchor as canvasPortAnchor, nodeBottom as canvasNodeBottom,
|
|
15
|
-
|
|
15
|
+
nodeWidth as canvasNodeWidth,
|
|
16
|
+
isReturnEdge as isCanvasReturnEdge, pathPoints as canvasPathPoints, pathMidpoint as canvasPathMidpoint,
|
|
16
17
|
} from './layout.js';
|
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
* ARITHMETIC over these, never by measuring, so the
|
|
7
7
|
* stylesheet must agree with them — `flow_canvas_css` reads
|
|
8
8
|
* the sheet and holds it to them.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* nodeWidth a node's width: its type's own `size.w`, else the box's
|
|
10
|
+
* portAnchor where a port's line starts or ends (a folded node's: its header)
|
|
11
|
+
* nodeBottom where a node's drawn box ends (a folded node's: its header)
|
|
11
12
|
* isReturnEdge is this line a loop's body going back round?
|
|
12
13
|
* layoutGraph ARRANGE: every step's place, left to right in run order
|
|
14
|
+
* pathPoints a drawn line's path, as the points along it
|
|
15
|
+
* pathMidpoint the point halfway along a drawn line, where its label sits
|
|
13
16
|
*/
|
|
14
17
|
|
|
15
18
|
import { arrange } from '../../canvas/edge_router.js';
|
|
@@ -22,26 +25,42 @@ const FOOT = 12;
|
|
|
22
25
|
const inputsOf = (catalogue, node) => (catalogue?.inputs ? catalogue.inputs(node?.type) : []);
|
|
23
26
|
const outputsOf = (catalogue, node) => (catalogue?.outputs ? catalogue.outputs(node?.type) : []);
|
|
24
27
|
const roleOf = (catalogue, node) => (catalogue?.role ? catalogue.role(node?.type) : 'step');
|
|
28
|
+
const sizeOf = (catalogue, node) => (catalogue?.size ? catalogue.size(node?.type) : null);
|
|
29
|
+
/** Is `node` drawn folded to its header? The box's own `folded(node)` decides
|
|
30
|
+
* when it has one (the editor's, which folds only with `collapsible`); else
|
|
31
|
+
* the node's `collapsed: true`. */
|
|
32
|
+
const foldedOf = (node, box) => (typeof box?.folded === 'function' ? Boolean(box.folded(node))
|
|
33
|
+
: node?.collapsed === true);
|
|
34
|
+
|
|
35
|
+
/** `node`'s width: its type's own `size.w` when it has one, else the box's. */
|
|
36
|
+
export function nodeWidth(catalogue, node, box = CANVAS_NODE) {
|
|
37
|
+
return sizeOf(catalogue, node)?.w || box.width;
|
|
38
|
+
}
|
|
25
39
|
|
|
26
40
|
/**
|
|
27
41
|
* The point a line meets `node`'s port `portName`: an input on the node's
|
|
28
42
|
* left edge, an output on its right, one row of ports down from the header
|
|
29
43
|
* per port before it — in the order the type DECLARES its ports, the order
|
|
30
44
|
* they are drawn in. A port the type does not have anchors at the first row.
|
|
45
|
+
* A node folded to its header has its ports folded away: every line meets
|
|
46
|
+
* the header's middle, on its side.
|
|
31
47
|
*/
|
|
32
48
|
export function portAnchor(catalogue, node, portName, direction, box = CANVAS_NODE) {
|
|
49
|
+
const x = node.position.x + (direction === 'input' ? 0 : nodeWidth(catalogue, node, box));
|
|
50
|
+
const side = direction === 'input' ? 'left' : 'right';
|
|
51
|
+
if (foldedOf(node, box)) return { x, y: node.position.y + box.header / 2, side };
|
|
33
52
|
const list = direction === 'input' ? inputsOf(catalogue, node) : outputsOf(catalogue, node);
|
|
34
53
|
const i = Math.max(0, list.findIndex((p) => p.name === portName));
|
|
35
|
-
return {
|
|
36
|
-
x: node.position.x + (direction === 'input' ? 0 : box.width),
|
|
37
|
-
y: node.position.y + box.header + box.row * i + box.row / 2,
|
|
38
|
-
side: direction === 'input' ? 'left' : 'right',
|
|
39
|
-
};
|
|
54
|
+
return { x, y: node.position.y + box.header + box.row * i + box.row / 2, side };
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
/** Where `node`'s drawn box ends — computed, because jsdom and a node not yet
|
|
43
|
-
* laid out both answer 0 for `offsetHeight`.
|
|
58
|
+
* laid out both answer 0 for `offsetHeight`. A type with its own `size.h` is
|
|
59
|
+
* drawn exactly that tall; a node folded to its header, as tall as that. */
|
|
44
60
|
export function nodeBottom(catalogue, node, box = CANVAS_NODE) {
|
|
61
|
+
if (foldedOf(node, box)) return node.position.y + box.header;
|
|
62
|
+
const h = sizeOf(catalogue, node)?.h;
|
|
63
|
+
if (h) return node.position.y + h;
|
|
45
64
|
const rows = Math.max(inputsOf(catalogue, node).length, outputsOf(catalogue, node).length, 1);
|
|
46
65
|
return node.position.y + box.header + box.row * rows + FOOT;
|
|
47
66
|
}
|
|
@@ -74,13 +93,17 @@ export function isReturnEdge(connection, target, catalogue, loopEntry = 'in') {
|
|
|
74
93
|
* start step(s) that follows each step's outputs in the order its type
|
|
75
94
|
* declares them — so a condition's first arm sits above its second
|
|
76
95
|
* whatever the two steps are called.
|
|
96
|
+
* - **each node's own box.** A type's `size` (or `heightOf(node)`, a height
|
|
97
|
+
* the editor measured) is the node's height, a folded node's (`folded`, as
|
|
98
|
+
* for `portAnchor`) is its header's, and the widest node sets the column
|
|
99
|
+
* spacing; with neither, every node is the box's.
|
|
77
100
|
* - **a loop's return lines left out.** The line from a loop's body back into
|
|
78
101
|
* the loop is the loop, not a dependency; followed, it would pull the body
|
|
79
102
|
* left of the loop that runs it. Any other cycle is broken by `arrange`.
|
|
80
103
|
*/
|
|
81
104
|
export function layoutGraph(graph, catalogue, { width = CANVAS_NODE.width, header = CANVAS_NODE.header,
|
|
82
105
|
row = CANVAS_NODE.row, gap = 96, rowGap = 40,
|
|
83
|
-
loopEntry = 'in' } = {}) {
|
|
106
|
+
loopEntry = 'in', heightOf = null, folded = undefined } = {}) {
|
|
84
107
|
const nodes = graph?.nodes || [];
|
|
85
108
|
const connections = graph?.connections || [];
|
|
86
109
|
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
@@ -109,10 +132,94 @@ export function layoutGraph(graph, catalogue, { width = CANVAS_NODE.width, heade
|
|
|
109
132
|
for (const n of nodes) if (!order.has(n.id)) order.set(n.id, order.size);
|
|
110
133
|
|
|
111
134
|
const rows = (n) => Math.max(inputsOf(catalogue, n).length, outputsOf(catalogue, n).length);
|
|
135
|
+
const box = { width, header, row, ...(folded !== undefined ? { folded } : {}) };
|
|
136
|
+
const height = (n) => {
|
|
137
|
+
if (foldedOf(n, box)) return header + 24;
|
|
138
|
+
const own = sizeOf(catalogue, n)?.h || (typeof heightOf === 'function' ? heightOf(n) : null);
|
|
139
|
+
return own ? own + 2 * FOOT : header + row * rows(n) + 24;
|
|
140
|
+
};
|
|
141
|
+
const widest = Math.max(width, ...nodes.map((n) => nodeWidth(catalogue, n, box)));
|
|
112
142
|
return arrange(
|
|
113
|
-
nodes.map((n) => ({ id: n.id, label: String(order.get(n.id)).padStart(6, '0'),
|
|
114
|
-
height: header + row * rows(n) + 24 })),
|
|
143
|
+
nodes.map((n) => ({ id: n.id, label: String(order.get(n.id)).padStart(6, '0'), height: height(n) })),
|
|
115
144
|
connections.filter((c) => !isReturn(c)).map((c) => ({ from: c.target, to: c.source })),
|
|
116
|
-
{ columnGap:
|
|
145
|
+
{ columnGap: widest + gap, rowGap },
|
|
117
146
|
);
|
|
118
147
|
}
|
|
148
|
+
|
|
149
|
+
/** Points per curve segment of a sampled path: enough that a label's place
|
|
150
|
+
* and a crossing test agree with what the browser draws to within a pixel. */
|
|
151
|
+
const CURVE_STEPS = 24;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The points along an SVG path `d`, as the kernel writes them: absolute `M`,
|
|
155
|
+
* `L`, `C`, `S` and `Z`, numbers separated by spaces or commas. Each curve is
|
|
156
|
+
* sampled at `steps` points; a straight run is its two ends. Computed rather
|
|
157
|
+
* than asked of the browser (`getPointAtLength`), because jsdom has no
|
|
158
|
+
* geometry and a label must sit in the same place in both.
|
|
159
|
+
*/
|
|
160
|
+
export function pathPoints(d, { steps = CURVE_STEPS } = {}) {
|
|
161
|
+
const tokens = String(d || '').match(/-?\d*\.?\d+(?:[eE][-+]?\d+)?|[A-Za-z]/g) || [];
|
|
162
|
+
const points = [];
|
|
163
|
+
let at = null;
|
|
164
|
+
let lastControl = null;
|
|
165
|
+
let opened = null;
|
|
166
|
+
let i = 0;
|
|
167
|
+
const num = () => Number(tokens[i++]);
|
|
168
|
+
const cubic = (c1, c2, end) => {
|
|
169
|
+
for (let k = 1; k <= steps; k += 1) {
|
|
170
|
+
const t = k / steps;
|
|
171
|
+
const u = 1 - t;
|
|
172
|
+
points.push({
|
|
173
|
+
x: u * u * u * at.x + 3 * u * u * t * c1.x + 3 * u * t * t * c2.x + t * t * t * end.x,
|
|
174
|
+
y: u * u * u * at.y + 3 * u * u * t * c1.y + 3 * u * t * t * c2.y + t * t * t * end.y,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
lastControl = c2;
|
|
178
|
+
at = end;
|
|
179
|
+
};
|
|
180
|
+
while (i < tokens.length) {
|
|
181
|
+
const command = tokens[i++];
|
|
182
|
+
if (command === 'M') {
|
|
183
|
+
at = { x: num(), y: num() };
|
|
184
|
+
opened = at;
|
|
185
|
+
points.push(at);
|
|
186
|
+
lastControl = null;
|
|
187
|
+
} else if (command === 'Z') {
|
|
188
|
+
at = opened;
|
|
189
|
+
points.push(at);
|
|
190
|
+
lastControl = null;
|
|
191
|
+
} else if (command === 'L') {
|
|
192
|
+
at = { x: num(), y: num() };
|
|
193
|
+
points.push(at);
|
|
194
|
+
lastControl = null;
|
|
195
|
+
} else if (command === 'C') {
|
|
196
|
+
const c1 = { x: num(), y: num() };
|
|
197
|
+
const c2 = { x: num(), y: num() };
|
|
198
|
+
cubic(c1, c2, { x: num(), y: num() });
|
|
199
|
+
} else if (command === 'S') {
|
|
200
|
+
const c1 = lastControl ? { x: 2 * at.x - lastControl.x, y: 2 * at.y - lastControl.y } : at;
|
|
201
|
+
const c2 = { x: num(), y: num() };
|
|
202
|
+
cubic(c1, c2, { x: num(), y: num() });
|
|
203
|
+
} else {
|
|
204
|
+
throw new Error(`pathPoints reads M, L, C, S and Z only, not "${command}".`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return points;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** The point halfway along `d`, by length. */
|
|
211
|
+
export function pathMidpoint(d) {
|
|
212
|
+
const points = pathPoints(d);
|
|
213
|
+
if (points.length < 2) return points[0] ? { ...points[0] } : { x: 0, y: 0 };
|
|
214
|
+
const lengths = [0];
|
|
215
|
+
for (let k = 1; k < points.length; k += 1) {
|
|
216
|
+
lengths.push(lengths[k - 1] + Math.hypot(points[k].x - points[k - 1].x, points[k].y - points[k - 1].y));
|
|
217
|
+
}
|
|
218
|
+
const half = lengths[lengths.length - 1] / 2;
|
|
219
|
+
let k = 1;
|
|
220
|
+
while (k < lengths.length - 1 && lengths[k] < half) k += 1;
|
|
221
|
+
const span = lengths[k] - lengths[k - 1];
|
|
222
|
+
const t = span > 0 ? (half - lengths[k - 1]) / span : 0;
|
|
223
|
+
return { x: points[k - 1].x + (points[k].x - points[k - 1].x) * t,
|
|
224
|
+
y: points[k - 1].y + (points[k].y - points[k - 1].y) * t };
|
|
225
|
+
}
|
|
@@ -7,14 +7,28 @@
|
|
|
7
7
|
* through `strings` and the `dataPortRead` option.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
/** `1 step`, `2 notes`, …; a count of none is left out of what is said. */
|
|
11
|
+
const counted = (steps, notes) => [[steps, 'step'], [notes, 'note']].filter(([n]) => n > 0)
|
|
12
|
+
.map(([n, w]) => `${n} ${w}${n === 1 ? '' : 's'}`).join(' and ');
|
|
13
|
+
|
|
10
14
|
export const CANVAS_STRINGS = Object.freeze({
|
|
11
15
|
palette: 'Steps',
|
|
12
16
|
canvasLabel: 'Flow canvas',
|
|
13
17
|
toolbarLabel: 'Flow',
|
|
14
18
|
arrange: 'Arrange',
|
|
15
19
|
arrangeTitle: 'Lay the steps out left to right, in the order a run takes them',
|
|
20
|
+
arrangeLayeredTitle: 'Lay the steps out left to right, following their connections',
|
|
16
21
|
fit: 'Fit',
|
|
17
22
|
fitTitle: 'Show the whole flow',
|
|
23
|
+
zoomOut: 'Zoom out',
|
|
24
|
+
zoomOutTitle: 'Make the flow smaller, one step',
|
|
25
|
+
zoomIn: 'Zoom in',
|
|
26
|
+
zoomInTitle: 'Make the flow larger, one step',
|
|
27
|
+
zoomReset: '100%',
|
|
28
|
+
zoomResetTitle: 'Show the flow at its natural size',
|
|
29
|
+
zoomAtMin: 'This is as small as the flow is drawn.',
|
|
30
|
+
zoomAtMax: 'This is as large as the flow is drawn.',
|
|
31
|
+
minimapTitle: 'Click to move the view there',
|
|
18
32
|
nothingToUndo: 'There is nothing to undo.',
|
|
19
33
|
nothingToRedo: 'There is nothing to redo.',
|
|
20
34
|
arrangeNeedsTwo: 'Arrange needs two steps or more.',
|
|
@@ -28,10 +42,76 @@ export const CANVAS_STRINGS = Object.freeze({
|
|
|
28
42
|
returnLine: 'Back to the loop for its next pass',
|
|
29
43
|
connectedReturn: 'Goes round the loop again.',
|
|
30
44
|
unknownType: (type) => `There is no step type "${type}".`,
|
|
45
|
+
renderFailed: (name, why) => `${name} could not draw its body${why ? `: ${why}` : '.'}`,
|
|
46
|
+
updateFailed: (name, why) => `${name} could not bring its body up to date${why ? `: ${why}` : '.'}`,
|
|
31
47
|
stepId: (id) => `Step id: ${id}`,
|
|
48
|
+
// `renameInPlace`
|
|
49
|
+
nameEmpty: 'A step needs a name: the one it had is kept.',
|
|
50
|
+
// `collapsible`
|
|
51
|
+
collapseNode: 'Fold this step to its header',
|
|
52
|
+
expandNode: 'Show this step\'s ports again',
|
|
32
53
|
connection: 'Connection',
|
|
33
54
|
connectionLine: (e) => `${e.source} · ${e.sourcePort} → ${e.target} · ${e.targetPort}`,
|
|
34
55
|
removeConnection: 'Remove connection',
|
|
35
56
|
portCount: (n) => `×${n}`,
|
|
36
57
|
portNotTaken: 'not taken',
|
|
58
|
+
|
|
59
|
+
// ── under the `connections` option ───────────────────────────────
|
|
60
|
+
connectTypes: ({ from, fromStep, to, toStep, accepts, provides }) => `${to} on ${toStep} takes `
|
|
61
|
+
+ `${accepts.join(' or ')}, and ${from} on ${fromStep} gives ${provides.join(' or ')}.`,
|
|
62
|
+
connectRefused: 'These two cannot be connected.',
|
|
63
|
+
connectDrag: 'Drop it on an input to connect, or on the canvas to add a step there. Esc cancels.',
|
|
64
|
+
dropOnInput: 'Drop the line on an input to connect it.',
|
|
65
|
+
dropPickerTitle: 'Add a step and connect it',
|
|
66
|
+
dropNothingFits: 'No step has an input this output can connect to.',
|
|
67
|
+
offerAccept: 'Accept',
|
|
68
|
+
offerDismiss: 'Not now',
|
|
69
|
+
|
|
70
|
+
// `multiSelect`
|
|
71
|
+
alignLeft: 'Align left edges',
|
|
72
|
+
alignCenter: 'Align centres',
|
|
73
|
+
alignRight: 'Align right edges',
|
|
74
|
+
alignTop: 'Align top edges',
|
|
75
|
+
alignMiddle: 'Align middles',
|
|
76
|
+
alignBottom: 'Align bottom edges',
|
|
77
|
+
distributeHorizontal: 'Distribute horizontally',
|
|
78
|
+
distributeVertical: 'Distribute vertically',
|
|
79
|
+
alignNeedsTwo: 'Pick two steps or more to align them.',
|
|
80
|
+
distributeNeedsThree: 'Pick three steps or more to distribute them.',
|
|
81
|
+
stepsPicked: (n) => `${n} steps picked`,
|
|
82
|
+
stepsPickedLine: 'Drag one to move them all. The arrow keys nudge them.',
|
|
83
|
+
removeSteps: (n) => (n === 1 ? 'Remove 1 step' : `Remove ${n} steps`),
|
|
84
|
+
// `groups`
|
|
85
|
+
group: 'Group',
|
|
86
|
+
groupTitle: 'Group the picked steps (Ctrl+G)',
|
|
87
|
+
ungroup: 'Ungroup',
|
|
88
|
+
ungroupTitle: 'Ungroup the groups the picked steps are in (Ctrl+Shift+G)',
|
|
89
|
+
groupNeedsOne: 'Pick the steps to group first.',
|
|
90
|
+
ungroupNeedsGroup: 'None of the picked steps is in a group.',
|
|
91
|
+
groupUntitled: 'Group',
|
|
92
|
+
groupName: 'Group name',
|
|
93
|
+
// `notes`
|
|
94
|
+
note: 'Note',
|
|
95
|
+
noteTitle: 'Add a note to the canvas',
|
|
96
|
+
noteKind: 'Note',
|
|
97
|
+
noteText: 'Note text',
|
|
98
|
+
noteEmpty: 'Empty note — double-click to write in it',
|
|
99
|
+
removeNote: 'Remove note',
|
|
100
|
+
itemsPicked: (steps, notes) => `${counted(steps, notes)} picked`,
|
|
101
|
+
removeItems: (n) => (n === 1 ? 'Remove 1 item' : `Remove ${n} items`),
|
|
102
|
+
// `clipboard`
|
|
103
|
+
copyLabel: (title, n) => (n > 1 ? `${title} (copy ${n})` : `${title} (copy)`),
|
|
104
|
+
copied: (n) => (n === 1 ? 'Copied 1 step.' : `Copied ${n} steps.`),
|
|
105
|
+
cutDone: (n) => (n === 1 ? 'Cut 1 step.' : `Cut ${n} steps.`),
|
|
106
|
+
// a copy or a cut that took notes too (`notes`)
|
|
107
|
+
copiedItems: (steps, notes) => `Copied ${counted(steps, notes)}.`,
|
|
108
|
+
cutDoneItems: (steps, notes) => `Cut ${counted(steps, notes)}.`,
|
|
109
|
+
nothingPicked: 'Pick a step first.',
|
|
110
|
+
cannotCopy: 'None of the picked steps can be copied.',
|
|
111
|
+
cannotCut: 'None of the picked steps can be removed, so none can be cut.',
|
|
112
|
+
nothingToPaste: 'There is nothing to paste.',
|
|
113
|
+
pasteNotSteps: 'What is on the clipboard is not steps of a flow.',
|
|
114
|
+
notAddable: (name) => `‘${name}’ cannot be added to this flow.`,
|
|
115
|
+
clipboardFailed: 'The clipboard could not be reached.',
|
|
116
|
+
routingFallback: (n) => `More than ${n} steps: the lines are drawn as plain curves, not routed round the steps.`,
|
|
37
117
|
});
|
|
File without changes
|
|
@@ -11,6 +11,29 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Any other key a type carries is kept and ignored.
|
|
13
13
|
*
|
|
14
|
+
* ══ A TYPE MAY DRAW ITS OWN BODY ═══════════════════════════════════════
|
|
15
|
+
*
|
|
16
|
+
* Four optional keys are read by the canvas editor, and only by it:
|
|
17
|
+
*
|
|
18
|
+
* render(body, node, ctx) fills the node's body, below its ports, once
|
|
19
|
+
* update(body, node, ctx) brings that body up to date on every repaint
|
|
20
|
+
* size: {w, h} the node's box in px (either may be left out)
|
|
21
|
+
* accent a tone (`dom.js`'s TONES) the node is marked in
|
|
22
|
+
*
|
|
23
|
+
* A type without them is drawn as every other step is. A `size` that is not
|
|
24
|
+
* positive numbers, an `accent` that is not a tone, a `render` or `update`
|
|
25
|
+
* that is not a function: each is a line in `problems`, and that key is not
|
|
26
|
+
* used.
|
|
27
|
+
*
|
|
28
|
+
* A fifth, `inline: ['key', …]`, names settings of the type's `config_schema`
|
|
29
|
+
* the canvas editor draws ON the node too, by the same widget registry the
|
|
30
|
+
* settings panel uses. A name the schema does not have, or an `inline` that is
|
|
31
|
+
* not a list of names, is a line in `problems`, and that name is not drawn.
|
|
32
|
+
*
|
|
33
|
+
* A sixth, `rank: 'source' | 'sink'`, is the hint the canvas editor's
|
|
34
|
+
* `layout: 'layered'` Arrange gives its steps: the first column or the last.
|
|
35
|
+
* Any other value is a line in `problems`, and it is not used.
|
|
36
|
+
*
|
|
14
37
|
* ══ DECLARED PORT ORDER IS MEANING ═════════════════════════════════════
|
|
15
38
|
*
|
|
16
39
|
* A lane reads an operation's first FLOW input as its own lane and the rest as
|
|
@@ -33,7 +56,7 @@
|
|
|
33
56
|
* consumer's catalogue with one bad entry still opens an editor.
|
|
34
57
|
*/
|
|
35
58
|
|
|
36
|
-
import { checkSettingsSchema } from './settings_schema.js';
|
|
59
|
+
import { checkSettingsSchema, fieldsFromSchema } from './settings_schema.js';
|
|
37
60
|
import { TONES } from './dom.js';
|
|
38
61
|
|
|
39
62
|
const isPlainObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
@@ -47,6 +70,41 @@ function cleanStem(stem) {
|
|
|
47
70
|
return String(stem ?? '').replace(/[^A-Za-z0-9_-]/g, '-').slice(0, 40) || 'step';
|
|
48
71
|
}
|
|
49
72
|
|
|
73
|
+
const positive = (v) => (typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : null);
|
|
74
|
+
|
|
75
|
+
/** A type's `size` as `{w, h}` (either null), or null — with the problem, if any. */
|
|
76
|
+
function readSize(raw) {
|
|
77
|
+
if (raw === undefined || raw === null) return { size: null, problem: null };
|
|
78
|
+
const refused = { size: null, problem: 'size must be {w, h} in positive pixels; it is not used.' };
|
|
79
|
+
if (!isPlainObject(raw)) return refused;
|
|
80
|
+
const w = raw.w === undefined ? null : positive(raw.w);
|
|
81
|
+
const h = raw.h === undefined ? null : positive(raw.h);
|
|
82
|
+
if ((raw.w !== undefined && w === null) || (raw.h !== undefined && h === null) || (w === null && h === null)) {
|
|
83
|
+
return refused;
|
|
84
|
+
}
|
|
85
|
+
return { size: Object.freeze({ w, h }), problem: null };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A type's `inline` names, those its schema has, in the order given — each
|
|
89
|
+
* one it does not have a line in `problems`. */
|
|
90
|
+
function readInline(raw, schema, id, problems) {
|
|
91
|
+
if (raw === undefined || raw === null) return Object.freeze([]);
|
|
92
|
+
if (!Array.isArray(raw)) {
|
|
93
|
+
problems.push({ type_id: id, message: `"${id}"'s inline must be a list of setting names; it is not used.` });
|
|
94
|
+
return Object.freeze([]);
|
|
95
|
+
}
|
|
96
|
+
const known = new Set(fieldsFromSchema(schema).map((f) => f.key));
|
|
97
|
+
const keys = [];
|
|
98
|
+
for (const key of raw) {
|
|
99
|
+
if (typeof key === 'string' && known.has(key)) {
|
|
100
|
+
if (!keys.includes(key)) keys.push(key);
|
|
101
|
+
} else {
|
|
102
|
+
problems.push({ type_id: id, message: `"${id}"'s inline names "${String(key)}", which its settings do not have; it is not drawn.` });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return Object.freeze(keys);
|
|
106
|
+
}
|
|
107
|
+
|
|
50
108
|
function normalisePort(p) {
|
|
51
109
|
return {
|
|
52
110
|
...p,
|
|
@@ -69,6 +127,10 @@ export function createStepCatalogue(types, { categories = [], idBase = defaultId
|
|
|
69
127
|
const problems = [];
|
|
70
128
|
const byId = new Map();
|
|
71
129
|
const order = [];
|
|
130
|
+
const sizes = new Map();
|
|
131
|
+
const accents = new Map();
|
|
132
|
+
const inlines = new Map();
|
|
133
|
+
const ranks = new Map();
|
|
72
134
|
|
|
73
135
|
for (const raw of Array.isArray(types) ? types : []) {
|
|
74
136
|
if (!isPlainObject(raw)) {
|
|
@@ -108,6 +170,24 @@ export function createStepCatalogue(types, { categories = [], idBase = defaultId
|
|
|
108
170
|
});
|
|
109
171
|
byId.set(id, type);
|
|
110
172
|
order.push(type);
|
|
173
|
+
const { size, problem } = readSize(raw.size);
|
|
174
|
+
if (problem) problems.push({ type_id: id, message: `"${id}"'s ${problem}` });
|
|
175
|
+
if (size) sizes.set(id, size);
|
|
176
|
+
if (raw.accent !== undefined && raw.accent !== null) {
|
|
177
|
+
if (TONES.includes(raw.accent)) accents.set(id, raw.accent);
|
|
178
|
+
else problems.push({ type_id: id, message: `"${id}"'s accent "${raw.accent}" is not a tone; it is not used.` });
|
|
179
|
+
}
|
|
180
|
+
const inline = readInline(raw.inline, schema, id, problems);
|
|
181
|
+
if (inline.length) inlines.set(id, inline);
|
|
182
|
+
if (raw.rank !== undefined && raw.rank !== null) {
|
|
183
|
+
if (raw.rank === 'source' || raw.rank === 'sink') ranks.set(id, raw.rank);
|
|
184
|
+
else problems.push({ type_id: id, message: `"${id}"'s rank "${String(raw.rank)}" is not 'source' or 'sink'; it is not used.` });
|
|
185
|
+
}
|
|
186
|
+
for (const hook of ['render', 'update']) {
|
|
187
|
+
if (raw[hook] !== undefined && typeof raw[hook] !== 'function') {
|
|
188
|
+
problems.push({ type_id: id, message: `"${id}"'s ${hook} is not a function; it is not used.` });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
111
191
|
}
|
|
112
192
|
|
|
113
193
|
const cats = (Array.isArray(categories) ? categories : []).filter(isPlainObject).map((c) => Object.freeze({
|
|
@@ -129,11 +209,11 @@ export function createStepCatalogue(types, { categories = [], idBase = defaultId
|
|
|
129
209
|
extra.push(Object.freeze({ id: '', label: '', tone: 'grey', layout: 'list' }));
|
|
130
210
|
}
|
|
131
211
|
const allCats = Object.freeze([...cats, ...extra]);
|
|
132
|
-
const
|
|
212
|
+
const categoryIndex = (t) => {
|
|
133
213
|
const id = t.category === null ? '' : t.category;
|
|
134
214
|
return allCats.findIndex((c) => c.id === id);
|
|
135
215
|
};
|
|
136
|
-
const sorted = [...order].sort((a, b) => (
|
|
216
|
+
const sorted = [...order].sort((a, b) => (categoryIndex(a) - categoryIndex(b))
|
|
137
217
|
|| String(a.label).localeCompare(String(b.label)));
|
|
138
218
|
|
|
139
219
|
const portsOf = (typeId, direction, flow) => {
|
|
@@ -160,6 +240,20 @@ export function createStepCatalogue(types, { categories = [], idBase = defaultId
|
|
|
160
240
|
const t = byId.get(typeId);
|
|
161
241
|
return (t && allCats.find((c) => c.id === (t.category ?? ''))?.tone) || 'grey';
|
|
162
242
|
},
|
|
243
|
+
/** The type's own box, `{w, h}` (either null), or null when it has none. */
|
|
244
|
+
size: (typeId) => sizes.get(typeId) ?? null,
|
|
245
|
+
/** The type's accent tone, or null when it has none. */
|
|
246
|
+
accent: (typeId) => accents.get(typeId) ?? null,
|
|
247
|
+
/** The setting names the type draws on its node (`inline`), in order; `[]` when none. */
|
|
248
|
+
inline: (typeId) => inlines.get(typeId) ?? Object.freeze([]),
|
|
249
|
+
/** The type's layered-layout hint, 'source' or 'sink', or null when it has none. */
|
|
250
|
+
rank: (typeId) => ranks.get(typeId) ?? null,
|
|
251
|
+
/** The type's `{render, update}` (`update` may be null), or null when it has no `render`. */
|
|
252
|
+
body: (typeId) => {
|
|
253
|
+
const t = byId.get(typeId);
|
|
254
|
+
const render = typeof t?.render === 'function' ? t.render : null;
|
|
255
|
+
return render ? { render, update: typeof t.update === 'function' ? t.update : null } : null;
|
|
256
|
+
},
|
|
163
257
|
inputs: (typeId, { flow = false } = {}) => portsOf(typeId, 'input', flow),
|
|
164
258
|
outputs: (typeId, { flow = false } = {}) => portsOf(typeId, 'output', flow),
|
|
165
259
|
port: (typeId, name, direction) => byId.get(typeId)?.ports
|
|
File without changes
|
package/src/flow/kit/dom.js
CHANGED
|
File without changes
|
package/src/flow/kit/findings.js
CHANGED
|
File without changes
|
package/src/flow/kit/history.js
CHANGED
|
File without changes
|
package/src/flow/kit/index.js
CHANGED
|
@@ -20,9 +20,9 @@ export {
|
|
|
20
20
|
} from './references.js';
|
|
21
21
|
export { createChipInput } from './chip_input.js';
|
|
22
22
|
export {
|
|
23
|
-
createWidgetRegistry, GENERIC_WIDGETS, DEFAULT_WIDGET_REFERENCES, renderField,
|
|
24
|
-
fitToText,
|
|
23
|
+
createWidgetRegistry, GENERIC_WIDGETS, DEFAULT_WIDGET_REFERENCES, renderField, typedValue, fitToText,
|
|
25
24
|
} from './widgets.js';
|
|
25
|
+
export { shownValue } from '../../ui/components/choice_controls.js';
|
|
26
26
|
export { createSettingsPanel } from './settings_panel.js';
|
|
27
27
|
export { openStepPicker } from './step_picker.js';
|
|
28
28
|
export { openValuePicker } from './value_picker.js';
|
|
@@ -34,5 +34,5 @@ export { FlowHistory } from './history.js';
|
|
|
34
34
|
export { bindFlowKeys, ownsUndo, isTextField, isOwnControl } from './keys.js';
|
|
35
35
|
export {
|
|
36
36
|
ID_OK, LOGIC_GRAPH_STRINGS, emptyGraph, normalise, serialise, nextId, addNode, removeNode, moveNode,
|
|
37
|
-
canConnect, connect, disconnect, sameEdge, inputNamesOf,
|
|
37
|
+
canConnect, connect, addConnection, disconnect, sameEdge, inputNamesOf,
|
|
38
38
|
} from './logic_graph.js';
|
package/src/flow/kit/keys.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Ctrl/⌘+Z · Ctrl/⌘+Y, Ctrl/⌘+Shift+Z undo · redo
|
|
5
5
|
* Delete remove the selection
|
|
6
6
|
* Ctrl/⌘+D · C · X · V duplicate · copy · cut · paste
|
|
7
|
+
* Ctrl/⌘+A pick everything
|
|
8
|
+
* Ctrl/⌘+G · Ctrl/⌘+Shift+G group · ungroup the selection
|
|
7
9
|
* F2 rename
|
|
8
10
|
* Alt+↑ · Alt+↓ · Alt+← move up · down · out of its block
|
|
9
11
|
* ↑ ↓ ← → move the selection
|
|
@@ -66,7 +68,8 @@ export function isTextField(target) {
|
|
|
66
68
|
*
|
|
67
69
|
* @param {HTMLElement} root
|
|
68
70
|
* @param {object} handlers undo, redo, remove, duplicate, copy, cut, paste,
|
|
69
|
-
* rename, moveUp, moveDown, moveOut, up, down, left, right,
|
|
71
|
+
* selectAll, group, ungroup, rename, moveUp, moveDown, moveOut, up, down, left, right,
|
|
72
|
+
* open, escape
|
|
70
73
|
* @returns {() => void} unbind
|
|
71
74
|
*/
|
|
72
75
|
export function bindFlowKeys(root, handlers = {}) {
|
|
@@ -100,8 +103,11 @@ export function bindFlowKeys(root, handlers = {}) {
|
|
|
100
103
|
if (lower === 'c') { run('copy', ev); return; }
|
|
101
104
|
if (lower === 'x') { run('cut', ev); return; }
|
|
102
105
|
if (lower === 'v') { run('paste', ev); return; }
|
|
106
|
+
if (lower === 'a') { run('selectAll', ev); return; }
|
|
107
|
+
if (lower === 'g') { run('group', ev); return; }
|
|
103
108
|
return;
|
|
104
109
|
}
|
|
110
|
+
if (mod && lower === 'g') { run('ungroup', ev); return; }
|
|
105
111
|
if (ev.altKey && !ev.ctrlKey && !ev.metaKey) {
|
|
106
112
|
if (k === 'ArrowUp') run('moveUp', ev);
|
|
107
113
|
else if (k === 'ArrowDown') run('moveDown', ev);
|