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
|
@@ -41,12 +41,15 @@
|
|
|
41
41
|
* the panel itself is open is the embedder's (a tile it toggles), so the bus
|
|
42
42
|
* event is the embedder's to hear too.
|
|
43
43
|
*
|
|
44
|
-
* The strip is
|
|
45
|
-
*
|
|
44
|
+
* The strip is `createTabs` (`../ui/components/tabs.js`, STORY-0048) under the
|
|
45
|
+
* `twm-bp` block: this file adds only what a bottom panel has and a strip does
|
|
46
|
+
* not — the pane's own store, the remembered tab, `persist`, and its event.
|
|
46
47
|
*/
|
|
47
48
|
|
|
49
|
+
import { createTabs, TAB_LEVELS } from '../ui/components/tabs.js';
|
|
50
|
+
|
|
48
51
|
/** Badge levels, worst last. Any other value draws the neutral badge. */
|
|
49
|
-
export const BADGE_LEVELS =
|
|
52
|
+
export const BADGE_LEVELS = TAB_LEVELS;
|
|
50
53
|
|
|
51
54
|
/**
|
|
52
55
|
* @param {HTMLElement} container
|
|
@@ -61,108 +64,44 @@ export const BADGE_LEVELS = Object.freeze(['info', 'warning', 'alert']);
|
|
|
61
64
|
export function createBottomPanelTabs(container, {
|
|
62
65
|
panes = [], remember = null, store = null, eventBus = null, onSelect = null, initial = null,
|
|
63
66
|
} = {}) {
|
|
64
|
-
const doc = container.ownerDocument;
|
|
65
|
-
const root = doc.createElement('div');
|
|
66
|
-
root.className = 'twm-bp';
|
|
67
|
-
const strip = doc.createElement('div');
|
|
68
|
-
strip.className = 'twm-bp__tabs';
|
|
69
|
-
strip.setAttribute('role', 'tablist');
|
|
70
|
-
const body = doc.createElement('div');
|
|
71
|
-
body.className = 'twm-bp__body';
|
|
72
|
-
root.append(strip, body);
|
|
73
|
-
container.appendChild(root);
|
|
74
|
-
|
|
75
67
|
const byId = new Map(panes.map((pane) => [pane.id, pane]));
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
let
|
|
68
|
+
// A tab put in front by THIS FILE (the remembered one) is not a choice: it
|
|
69
|
+
// is neither written back nor allowed to beat a choice made meanwhile.
|
|
70
|
+
let restoring = false;
|
|
79
71
|
let chosen = false;
|
|
80
72
|
let destroyed = false;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
tab.className = 'twm-bp__tab';
|
|
86
|
-
tab.dataset.pane = pane.id;
|
|
87
|
-
tab.setAttribute('role', 'tab');
|
|
88
|
-
tab.setAttribute('aria-selected', 'false');
|
|
89
|
-
const label = doc.createElement('span');
|
|
90
|
-
label.className = 'twm-bp__label';
|
|
91
|
-
label.textContent = pane.label;
|
|
92
|
-
const badge = doc.createElement('span');
|
|
93
|
-
badge.className = 'twm-bp__badge';
|
|
94
|
-
badge.hidden = true;
|
|
95
|
-
tab.append(label, badge);
|
|
96
|
-
tab.addEventListener('click', () => api.select(pane.id));
|
|
97
|
-
strip.appendChild(tab);
|
|
98
|
-
tabs.set(pane.id, tab);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function mount(id) {
|
|
102
|
-
if (mounted.has(id)) return mounted.get(id);
|
|
103
|
-
const pane = byId.get(id);
|
|
104
|
-
const el = doc.createElement('div');
|
|
105
|
-
el.className = 'twm-bp-pane';
|
|
106
|
-
el.dataset.pane = id;
|
|
107
|
-
el.setAttribute('role', 'tabpanel');
|
|
108
|
-
el.hidden = id !== active;
|
|
109
|
-
body.appendChild(el);
|
|
110
|
-
const entry = { el, instance: null };
|
|
111
|
-
mounted.set(id, entry);
|
|
112
|
-
// ONE PANE THAT THROWS IS ONE EMPTY TAB, not a dead panel: the strip
|
|
113
|
-
// and every other pane go on working.
|
|
114
|
-
try {
|
|
115
|
-
entry.instance = pane.mount?.(el, store ? store(pane) : null, api) ?? null;
|
|
116
|
-
} catch (err) {
|
|
117
|
-
console.error(`[bottom-panel] pane "${id}" failed to mount`, err);
|
|
118
|
-
}
|
|
119
|
-
return entry;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function show(id) {
|
|
123
|
-
if (destroyed || !byId.has(id)) return false;
|
|
124
|
-
active = id;
|
|
125
|
-
for (const [key, tab] of tabs) {
|
|
126
|
-
const on = key === id;
|
|
127
|
-
tab.classList.toggle('twm-bp__tab--on', on);
|
|
128
|
-
tab.setAttribute('aria-selected', String(on));
|
|
129
|
-
}
|
|
130
|
-
for (const [key, entry] of mounted) entry.el.hidden = key !== id;
|
|
131
|
-
mount(id).instance?.onShow?.();
|
|
132
|
-
onSelect?.(id, byId.get(id));
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
73
|
+
// Eager panes and the first one shown mount inside `createTabs`, before it
|
|
74
|
+
// has answered; `mountPane` hands the strip (and `api.root`) over then, so
|
|
75
|
+
// a pane sees the whole api inside its `mount`.
|
|
76
|
+
let strip = null;
|
|
135
77
|
|
|
136
78
|
const api = {
|
|
137
|
-
root,
|
|
79
|
+
root: null,
|
|
138
80
|
/** Bring a tab to the front, and remember it. */
|
|
139
81
|
select(id) {
|
|
140
|
-
if (!
|
|
82
|
+
if (destroyed || !byId.has(id)) return;
|
|
141
83
|
chosen = true;
|
|
142
|
-
|
|
84
|
+
if (strip.active() !== id) { strip.select(id); return; }
|
|
85
|
+
// Already in front: asked for again (a click on it, a toast's
|
|
86
|
+
// "+N more"), so the pane is told again and the choice is
|
|
87
|
+
// remembered, as any select.
|
|
88
|
+
strip.instance(id)?.onShow?.();
|
|
89
|
+
remembered(id);
|
|
143
90
|
},
|
|
144
91
|
/** The tab in front, or null. */
|
|
145
|
-
active: () => active,
|
|
92
|
+
active: () => strip.active(),
|
|
146
93
|
/** `count` on a tab, coloured by `level`; nothing at zero. */
|
|
147
|
-
setBadge(id, count, level = null)
|
|
148
|
-
const badge = tabs.get(id)?.querySelector('.twm-bp__badge');
|
|
149
|
-
if (!badge) return;
|
|
150
|
-
const n = Number(count) || 0;
|
|
151
|
-
badge.hidden = n <= 0;
|
|
152
|
-
badge.textContent = n > 99 ? '99+' : String(Math.max(n, 0));
|
|
153
|
-
badge.className = 'twm-bp__badge'
|
|
154
|
-
+ (BADGE_LEVELS.includes(level) ? ` twm-bp__badge--${level}` : '');
|
|
155
|
-
},
|
|
94
|
+
setBadge: (id, count, level = null) => strip.setBadge(id, count, level),
|
|
156
95
|
/** A mounted pane's live instance, or null — how one tab hands work to
|
|
157
96
|
* another. `select` it first, which mounts it. */
|
|
158
|
-
instance: (id) =>
|
|
97
|
+
instance: (id) => strip.instance(id),
|
|
159
98
|
/** The panel is on screen again (its tile re-mounted): tell the pane in front. */
|
|
160
99
|
reshow() {
|
|
161
|
-
|
|
100
|
+
strip.instance(strip.active())?.onShow?.();
|
|
162
101
|
},
|
|
163
102
|
/** Before the panel is torn down: every mounted pane saves. */
|
|
164
103
|
async persist() {
|
|
165
|
-
for (const
|
|
104
|
+
for (const instance of strip.instances()) await instance.persist?.();
|
|
166
105
|
},
|
|
167
106
|
/** Gone for good: panes save, then are destroyed; the bus is let go. */
|
|
168
107
|
async destroy() {
|
|
@@ -170,29 +109,58 @@ export function createBottomPanelTabs(container, {
|
|
|
170
109
|
await api.persist();
|
|
171
110
|
destroyed = true;
|
|
172
111
|
try { subscription?.dispose?.(); } catch { /* bus gone */ }
|
|
173
|
-
|
|
174
|
-
try { entry.instance?.destroy?.(); } catch (err) {
|
|
175
|
-
console.warn('[bottom-panel] pane destroy failed', err);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
mounted.clear();
|
|
179
|
-
root.remove();
|
|
112
|
+
strip.destroy();
|
|
180
113
|
},
|
|
181
114
|
};
|
|
182
115
|
|
|
183
|
-
|
|
116
|
+
function remembered(id) {
|
|
117
|
+
onSelect?.(id, byId.get(id));
|
|
118
|
+
try { remember?.write?.(id); } catch { /* storage refused: not ours to fix */ }
|
|
119
|
+
}
|
|
184
120
|
|
|
121
|
+
strip = createTabs(container, {
|
|
122
|
+
block: 'twm-bp',
|
|
123
|
+
dataKey: 'pane',
|
|
124
|
+
items: panes.map(({ id, label, eager }) => ({ id, label, eager: !!eager })),
|
|
125
|
+
active: byId.has(initial) ? initial : panes[0]?.id,
|
|
126
|
+
mountPane: (item, el, tabs) => {
|
|
127
|
+
strip ??= tabs;
|
|
128
|
+
api.root ??= tabs.root;
|
|
129
|
+
const pane = byId.get(item.id);
|
|
130
|
+
return pane.mount?.(el, store ? store(pane) : null, api) ?? null;
|
|
131
|
+
},
|
|
132
|
+
onSelect: (id) => {
|
|
133
|
+
if (restoring) { onSelect?.(id, byId.get(id)); return; }
|
|
134
|
+
chosen = true;
|
|
135
|
+
remembered(id);
|
|
136
|
+
},
|
|
137
|
+
// A click on the tab already in front is a select here, as it always
|
|
138
|
+
// was: the pane hears onShow again and the choice is remembered.
|
|
139
|
+
onReselect: (id) => api.select(id),
|
|
140
|
+
});
|
|
141
|
+
api.root ??= strip.root;
|
|
142
|
+
if (strip.active() != null) onSelect?.(strip.active(), byId.get(strip.active()));
|
|
143
|
+
|
|
144
|
+
// The bus is the bottom panel's, not the strip's: a `bottompanel:show` for
|
|
145
|
+
// the tab already in front still tells its pane, which a strip does not.
|
|
185
146
|
const subscription = eventBus?.on?.('bottompanel:show',
|
|
186
147
|
(payload) => api.select(payload?.tab ?? panes[0]?.id)) ?? null;
|
|
187
148
|
|
|
188
|
-
show(byId.has(initial) ? initial : panes[0]?.id);
|
|
189
|
-
|
|
190
149
|
if (remember?.read) {
|
|
191
150
|
Promise.resolve()
|
|
192
151
|
.then(() => remember.read())
|
|
193
152
|
.then((saved) => {
|
|
194
|
-
if (chosen || destroyed) return;
|
|
195
|
-
if (
|
|
153
|
+
if (chosen || destroyed || !byId.has(saved)) return;
|
|
154
|
+
if (strip.active() === saved) {
|
|
155
|
+
// Already in front: shown again, as it always was — the
|
|
156
|
+
// pane hears onShow and the caller onSelect, nothing is
|
|
157
|
+
// written back.
|
|
158
|
+
strip.instance(saved)?.onShow?.();
|
|
159
|
+
onSelect?.(saved, byId.get(saved));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
restoring = true;
|
|
163
|
+
try { strip.select(saved); } finally { restoring = false; }
|
|
196
164
|
})
|
|
197
165
|
.catch(() => { /* nothing remembered */ });
|
|
198
166
|
}
|
|
@@ -65,7 +65,10 @@
|
|
|
65
65
|
* groupLabel the group's header, when the group is not a taxonomy kind
|
|
66
66
|
* kindLabel the word drawn on the right, when the taxonomy's is wrong
|
|
67
67
|
* boost added to the match score (an embedder's "recently opened")
|
|
68
|
-
* modes which of 'split' / 'window' the row offers (key hints)
|
|
68
|
+
* modes which of 'split' / 'window' the row offers (key hints). For
|
|
69
|
+
* a row whose kind is single (KindDef `single`) both are copy
|
|
70
|
+
* verbs: the hints are drawn greyed with the taxonomy's reason,
|
|
71
|
+
* and Ctrl/Shift+Enter on the row does nothing.
|
|
69
72
|
* drillLabel the Tab hint ("search rows"); Tab is offered only with it
|
|
70
73
|
* shortcut a key binding drawn on the right
|
|
71
74
|
* verb the Enter hint ('open' by default, 'run' for a command)
|
|
@@ -79,16 +82,33 @@
|
|
|
79
82
|
*/
|
|
80
83
|
|
|
81
84
|
import { HelpModal } from '../help/help_modal.js';
|
|
85
|
+
import { KEYBOARD_SHORTCUTS_TOPIC_ID, KEYMAP_SHORTCUTS, keyboardShortcutsTopic } from './keymap.js';
|
|
82
86
|
|
|
83
87
|
const ROOT_ID = 'twm-cmdpal';
|
|
84
88
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
/** The words a shortcuts table uses for the help action — the default `?` row's. */
|
|
90
|
+
const HELP_ACTION = KEYMAP_SHORTCUTS.find((r) => r.keys === '?').action;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Non-entity commands surfaced in the palette's search results. They carry an
|
|
94
|
+
* `action` so `commit()` runs them instead of opening a tile. `shortcuts` is
|
|
95
|
+
* the table the *Keyboard shortcuts* command shows — the shell passes the one
|
|
96
|
+
* its keymap's bindings produced (`keymapShortcuts`). The command's hint names
|
|
97
|
+
* the keys that open help in that table: `?` by default, `F9` when an embedder
|
|
98
|
+
* moved help there, none when no key opens it.
|
|
99
|
+
*/
|
|
100
|
+
function _staticCommands(shortcuts) {
|
|
101
|
+
const helpKeys = shortcuts.filter((r) => r.action === HELP_ACTION).map((r) => r.keys).join(' / ');
|
|
102
|
+
return [
|
|
103
|
+
{ kind: 'command', id: 'shortcuts', label: 'Keyboard shortcuts',
|
|
104
|
+
hint: helpKeys ? `Show all key bindings (${helpKeys})` : 'Show all key bindings',
|
|
105
|
+
icon: 'keyboard', ...(helpKeys ? { shortcut: helpKeys } : {}), verb: 'run',
|
|
106
|
+
// The application's help may define the topic; when it does not, the
|
|
107
|
+
// window manager's own bindings are shown rather than an error.
|
|
108
|
+
action: () => HelpModal.open(KEYBOARD_SHORTCUTS_TOPIC_ID,
|
|
109
|
+
{ fallback: keyboardShortcutsTopic(shortcuts) }) },
|
|
110
|
+
];
|
|
111
|
+
}
|
|
92
112
|
|
|
93
113
|
const DEFAULT_GROUP_CAP = 4;
|
|
94
114
|
const DEFAULT_COMMAND_MIN_CHARS = 2;
|
|
@@ -104,7 +124,9 @@ const MAX_ROWS = 200;
|
|
|
104
124
|
* gesture. See `commit` for what it is for; omitted, the
|
|
105
125
|
* palette behaves as it always has.
|
|
106
126
|
* @param chrome, layout, groupCap, empty, emptyLabel, commands,
|
|
107
|
-
* commandMinChars, drill, cache, tips, footerNote — C39, above.
|
|
127
|
+
* commandMinChars, drill, cache, tips, footerNote — C39, above.
|
|
128
|
+
* @param shortcuts the rows the built-in *Keyboard shortcuts* command lists
|
|
129
|
+
* (`{keys, action}`); `KEYMAP_SHORTCUTS` when omitted. */
|
|
108
130
|
export function createCommandPalette({ wm, api, taxonomy, catalog, placeholder = 'Search…',
|
|
109
131
|
onPick = null,
|
|
110
132
|
chrome = {}, layout = 'flat',
|
|
@@ -113,7 +135,8 @@ export function createCommandPalette({ wm, api, taxonomy, catalog, placeholder =
|
|
|
113
135
|
commands = null,
|
|
114
136
|
commandMinChars = DEFAULT_COMMAND_MIN_CHARS,
|
|
115
137
|
drill = null, cache = false,
|
|
116
|
-
tips = null, footerNote = ''
|
|
138
|
+
tips = null, footerNote = '',
|
|
139
|
+
shortcuts = KEYMAP_SHORTCUTS }) {
|
|
117
140
|
if (!taxonomy) throw new Error('createCommandPalette: a taxonomy is required');
|
|
118
141
|
if (!catalog) throw new Error('createCommandPalette: an entity catalog is required');
|
|
119
142
|
const grouped = layout === 'grouped';
|
|
@@ -122,6 +145,9 @@ export function createCommandPalette({ wm, api, taxonomy, catalog, placeholder =
|
|
|
122
145
|
let cached = null; // C39 `cache`: the last loaded catalogue
|
|
123
146
|
let onClose = null;
|
|
124
147
|
|
|
148
|
+
/** A row whose kind exists once (KindDef `single`): no split, no window. */
|
|
149
|
+
const isSingle = (row) => !!row && taxonomy.isSingle(row.kind);
|
|
150
|
+
|
|
125
151
|
const isOpen = () => !!overlay;
|
|
126
152
|
const close = () => {
|
|
127
153
|
if (!overlay) return;
|
|
@@ -132,6 +158,7 @@ export function createCommandPalette({ wm, api, taxonomy, catalog, placeholder =
|
|
|
132
158
|
};
|
|
133
159
|
const toggle = () => { isOpen() ? close() : open(); };
|
|
134
160
|
const invalidate = () => { cached = null; };
|
|
161
|
+
const staticCommands = _staticCommands(shortcuts);
|
|
135
162
|
|
|
136
163
|
const open = async () => {
|
|
137
164
|
if (overlay) return;
|
|
@@ -152,11 +179,22 @@ export function createCommandPalette({ wm, api, taxonomy, catalog, placeholder =
|
|
|
152
179
|
const chips = overlay.querySelectorAll('[data-shortcut]');
|
|
153
180
|
const toggles = overlay.querySelectorAll('[data-toggle]');
|
|
154
181
|
|
|
155
|
-
// Reflect current panel state in toggle buttons.
|
|
156
|
-
|
|
182
|
+
// Reflect current panel state in toggle buttons. A panel the primary
|
|
183
|
+
// content withholds is a chip that says why and does nothing.
|
|
184
|
+
const syncToggle = (btn) => {
|
|
157
185
|
const which = btn.dataset.toggle;
|
|
186
|
+
const { available, reason } = wm.getPanelAvailability(which);
|
|
158
187
|
btn.classList.toggle('twm-chip--on', wm.isPanelOpen(which));
|
|
159
|
-
|
|
188
|
+
if (available) {
|
|
189
|
+
btn.removeAttribute('aria-disabled');
|
|
190
|
+
btn.removeAttribute('title');
|
|
191
|
+
} else {
|
|
192
|
+
btn.setAttribute('aria-disabled', 'true');
|
|
193
|
+
if (reason) btn.setAttribute('title', reason);
|
|
194
|
+
else btn.removeAttribute('title');
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
toggles.forEach(syncToggle);
|
|
160
198
|
|
|
161
199
|
chips.forEach((btn) => btn.addEventListener('click', () => {
|
|
162
200
|
const kind = btn.dataset.shortcut;
|
|
@@ -164,9 +202,8 @@ export function createCommandPalette({ wm, api, taxonomy, catalog, placeholder =
|
|
|
164
202
|
close();
|
|
165
203
|
}));
|
|
166
204
|
toggles.forEach((btn) => btn.addEventListener('click', () => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
btn.classList.toggle('twm-chip--on', wm.isPanelOpen(which));
|
|
205
|
+
wm.togglePanel(btn.dataset.toggle);
|
|
206
|
+
syncToggle(btn);
|
|
170
207
|
}));
|
|
171
208
|
|
|
172
209
|
// Scoped-search state. Typing `<prefix>:` converts the prefix
|
|
@@ -296,8 +333,13 @@ export function createCommandPalette({ wm, api, taxonomy, catalog, placeholder =
|
|
|
296
333
|
const modes = Array.isArray(r.modes) ? r.modes : [];
|
|
297
334
|
const acts = [`<span><kbd>Enter</kbd> ${_esc(verb)}</span>`];
|
|
298
335
|
if (r.drillLabel && drill) acts.push(`<span><kbd>Tab</kbd> ${_esc(r.drillLabel)}</span>`);
|
|
299
|
-
|
|
300
|
-
|
|
336
|
+
// A single kind has no copies: its split and window hints are
|
|
337
|
+
// drawn refused, with the reason, rather than offered.
|
|
338
|
+
const refused = isSingle(r)
|
|
339
|
+
? ` class="twm-cmdpal__act--disabled" aria-disabled="true" title="${_esc(taxonomy.singleReason(r.kind))}"`
|
|
340
|
+
: '';
|
|
341
|
+
if (modes.includes('split')) acts.push(`<span${refused}><kbd>Ctrl Enter</kbd> split</span>`);
|
|
342
|
+
if (modes.includes('window')) acts.push(`<span${refused}><kbd>Shift Enter</kbd> window</span>`);
|
|
301
343
|
const side = r.shortcut
|
|
302
344
|
? `<kbd class="twm-cmdpal__shortcut">${_esc(r.shortcut)}</kbd>`
|
|
303
345
|
: `<span class="twm-cmdpal__kind">${_esc(kindLabel(r))}</span>`;
|
|
@@ -411,6 +453,9 @@ export function createCommandPalette({ wm, api, taxonomy, catalog, placeholder =
|
|
|
411
453
|
const picked = filtered[i];
|
|
412
454
|
if (!picked) return;
|
|
413
455
|
if (picked.more) { showAll(picked.more); return; }
|
|
456
|
+
// Split and window open a COPY; a single kind refuses both, and the
|
|
457
|
+
// palette stays open on the row whose hints say why.
|
|
458
|
+
if (mode !== 'open' && isSingle(picked)) return;
|
|
414
459
|
const pick = _public(picked);
|
|
415
460
|
const how = { mode, scope: scope?.id ?? null };
|
|
416
461
|
close();
|
|
@@ -668,7 +713,7 @@ export function createCommandPalette({ wm, api, taxonomy, catalog, placeholder =
|
|
|
668
713
|
try { extra = (commands() || []).map((c) => ({ kind: 'command', ...c })); }
|
|
669
714
|
catch (err) { console.warn('[cmdpal] commands() threw', err); }
|
|
670
715
|
}
|
|
671
|
-
return [...
|
|
716
|
+
return [...staticCommands, ...extra];
|
|
672
717
|
};
|
|
673
718
|
// Objects before commands in the grouped layout, so that a tie that
|
|
674
719
|
// survives every rule goes to the object (first seen wins).
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
* `ctx` is the shell's ctx plus, per leaf, `leafId` and `setTitle(text)` —
|
|
13
13
|
* rename the pane's chrome title from inside the content, at mount or any
|
|
14
14
|
* time after; it survives repaints, and `setTitle(null)` gives the title back
|
|
15
|
-
* to the tree (tile_renderer.js, C41)
|
|
15
|
+
* to the tree (tile_renderer.js, C41) — and `setChromeActions(specs)`, which
|
|
16
|
+
* replaces the pane's content buttons the same way: at mount (winning over a
|
|
17
|
+
* returned `chromeActions`) or any time after, surviving repaints, `null` or
|
|
18
|
+
* `[]` clearing them (tile_renderer.js, C42).
|
|
16
19
|
*
|
|
17
20
|
* ── ONE REGISTRY PER SHELL ────────────────────────────────────────────
|
|
18
21
|
* This module used to hold a module-level `Map` plus a `register(kind, fn)`
|
package/src/tiling/desktops.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Where that key actually lands is the embedder's business (see the host's
|
|
5
5
|
* `resolvePath`); this module only names it.
|
|
6
6
|
*
|
|
7
|
-
* Each desktop = { id, label, tree (TileTree), windows ([]), panels
|
|
7
|
+
* Each desktop = { id, label, tree (TileTree), windows ([]), panels,
|
|
8
|
+
* panelShares }.
|
|
8
9
|
* Switching desktops swaps the active tree under the renderer.
|
|
9
10
|
*/
|
|
10
11
|
|
|
@@ -47,6 +48,11 @@ function _makeDesktop(label, seed, panelDefaults = DEFAULT_PANEL_STATE) {
|
|
|
47
48
|
// when it asked for nothing. Names are assigned by wm._canonicalize via
|
|
48
49
|
// PANEL_TITLES.
|
|
49
50
|
panels: { ...panelDefaults },
|
|
51
|
+
// Each panel's last share of the WM area along its axis, recorded by
|
|
52
|
+
// the WM when a splitter drag ends (and, for a panel not yet
|
|
53
|
+
// measured, when it rebuilds the panels). A side with none opens at
|
|
54
|
+
// the default ratio.
|
|
55
|
+
panelShares: {},
|
|
50
56
|
// C40. Both unset is a desktop exactly as it always was.
|
|
51
57
|
group: null,
|
|
52
58
|
fixed: false,
|
|
@@ -159,6 +165,9 @@ export class DesktopManager {
|
|
|
159
165
|
id: d.id, label: d.label,
|
|
160
166
|
tree: d.tree.serialize(),
|
|
161
167
|
panels: { ...d.panels },
|
|
168
|
+
// Written only when a panel has been measured, like the C40
|
|
169
|
+
// keys below.
|
|
170
|
+
...(Object.keys(d.panelShares).length ? { panelShares: { ...d.panelShares } } : {}),
|
|
162
171
|
// C40. Written only when set, so a blob without them is
|
|
163
172
|
// byte-for-byte the blob it always was.
|
|
164
173
|
...(d.group ? { group: d.group } : {}),
|
|
@@ -182,6 +191,7 @@ export class DesktopManager {
|
|
|
182
191
|
// this merge order avoids. The default only fills a key the stored
|
|
183
192
|
// blob predates.
|
|
184
193
|
panels: { ...m.panelDefaults, ...(raw.panels || {}) },
|
|
194
|
+
panelShares: { ...(raw.panelShares || {}) },
|
|
185
195
|
group: raw.group || null,
|
|
186
196
|
fixed: !!raw.fixed,
|
|
187
197
|
}));
|
|
File without changes
|