flexdesk 0.17.12 → 0.17.14
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 +44 -0
- package/LICENSE +0 -0
- package/README.md +0 -0
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/canvas.js +0 -0
- package/charts.js +0 -0
- package/core.js +0 -0
- package/css/base.css +61 -0
- package/css/flexdesk.css +85 -2
- package/css/overrides.css +24 -2
- package/css/reset.css +0 -0
- package/dist/canvas.js +0 -0
- package/dist/canvas.js.map +0 -0
- package/dist/charts.js +3 -3
- package/dist/{chunk-NIJEJAZ7.js → chunk-2PM6ZMNS.js} +2 -2
- package/dist/chunk-3PHCPZHT.js +0 -0
- package/dist/chunk-3PHCPZHT.js.map +0 -0
- package/dist/chunk-FOOS3T5L.js +0 -0
- package/dist/chunk-FOOS3T5L.js.map +0 -0
- package/dist/chunk-GOQLR5WR.js +0 -0
- package/dist/chunk-GOQLR5WR.js.map +0 -0
- package/dist/chunk-HNU6MM2B.js +0 -0
- package/dist/chunk-HNU6MM2B.js.map +0 -0
- package/dist/{chunk-F35XSQWA.js → chunk-IG74S3ZH.js} +2 -2
- package/dist/chunk-JKZLNKG6.js +0 -0
- package/dist/chunk-JKZLNKG6.js.map +0 -0
- package/dist/chunk-JYWURG5T.js +0 -0
- package/dist/chunk-JYWURG5T.js.map +0 -0
- package/dist/{chunk-CVMCZZX2.js → chunk-MADMGRX2.js} +18 -7
- package/dist/chunk-MADMGRX2.js.map +7 -0
- package/dist/chunk-N7HC3ADV.js +0 -0
- package/dist/chunk-N7HC3ADV.js.map +0 -0
- package/dist/{chunk-75FLVAUT.js → chunk-TLIV6QSU.js} +3 -3
- package/dist/{chunk-BE2PU3LL.js → chunk-ZMH3OC7H.js} +2 -2
- package/dist/core.js +3 -18
- package/dist/core.js.map +2 -2
- package/dist/editor.js.map +0 -0
- package/dist/flexdesk.css +85 -2
- package/dist/flow.js +3 -3
- package/dist/flow.js.map +0 -0
- package/dist/host.js +0 -0
- package/dist/host.js.map +0 -0
- 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 +5 -5
- package/dist/widgets.js +31 -22
- package/dist/widgets.js.map +2 -2
- package/dist/wm.js +198 -31
- package/dist/wm.js.map +3 -3
- package/docs/README.md +1 -1
- package/docs/best-practices.md +1 -1
- package/docs/components/command-palette.md +0 -0
- package/docs/components/conversation.md +0 -0
- package/docs/components/data-table-grid.md +0 -0
- package/docs/components/data-table.md +0 -0
- package/docs/components/editor.md +0 -0
- package/docs/components/flow-canvas-editor.md +0 -0
- package/docs/components/flow-editors.md +0 -0
- package/docs/components/flow-lane-editor.md +0 -0
- package/docs/components/flow-outline-editor.md +0 -0
- package/docs/components/forms-and-dialogs.md +0 -0
- package/docs/components/input-fields.md +0 -0
- package/docs/components/keymap.md +66 -3
- package/docs/components/layout-widgets.md +32 -0
- package/docs/components/markdown-and-outline.md +0 -0
- package/docs/components/menus.md +63 -0
- package/docs/components/notifications.md +0 -0
- package/docs/components/panels-and-desktops.md +56 -0
- package/docs/components/record-list-and-fact-table.md +0 -0
- package/docs/components/settings-view.md +0 -0
- package/docs/components/shell.md +0 -0
- package/docs/components/slide-out-panel.md +0 -0
- package/docs/components/wizard.md +0 -0
- package/docs/concepts/building-and-packaging.md +0 -0
- package/docs/concepts/domain-neutrality.md +0 -0
- package/docs/concepts/host-port.md +0 -0
- package/docs/concepts/packages-and-entry-points.md +0 -0
- package/docs/concepts/peer-dependencies.md +0 -0
- package/docs/concepts/state-guard.md +65 -2
- package/docs/concepts/styling.md +24 -6
- package/docs/quickstart.md +0 -0
- package/editor.js +0 -0
- package/flow.js +0 -0
- package/host.js +0 -0
- package/package.json +2 -2
- package/src/canvas/canvas_adapter.js +0 -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 +0 -0
- package/src/canvas/layered_layout.js +0 -0
- package/src/charts/data_series_plot_window.js +0 -0
- package/src/charts/downsample.js +0 -0
- package/src/charts/plot_config.js +0 -0
- package/src/charts/plotly_wrapper.js +0 -0
- package/src/core/event_bus.js +0 -0
- package/src/core/logging.js +0 -0
- package/src/core/settings.js +0 -0
- package/src/core/state_guard.js +0 -0
- package/src/core/state_guard_config.js +19 -18
- package/src/core/state_machine.js +0 -0
- package/src/editor/editor_pane.js +0 -0
- package/src/editor/monaco_editor_factory.js +0 -0
- package/src/editor/monaco_loader.js +0 -0
- package/src/editor/notebook_search.js +0 -0
- package/src/editor/notebook_undo_manager.js +0 -0
- package/src/editor/split_pane_container.js +0 -0
- package/src/flow/canvas_editor/connections.js +0 -0
- package/src/flow/canvas_editor/editor.js +0 -0
- package/src/flow/canvas_editor/index.js +0 -0
- package/src/flow/canvas_editor/layout.js +0 -0
- package/src/flow/canvas_editor/strings.js +0 -0
- package/src/flow/kit/always_before.js +0 -0
- package/src/flow/kit/catalogue.js +0 -0
- 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 +0 -0
- package/src/flow/kit/keys.js +0 -0
- package/src/flow/kit/logic_graph.js +0 -0
- package/src/flow/kit/references.js +0 -0
- package/src/flow/kit/settings_panel.js +0 -0
- package/src/flow/kit/settings_schema.js +0 -0
- package/src/flow/kit/step_picker.js +0 -0
- package/src/flow/kit/strings.js +0 -0
- package/src/flow/kit/value_picker.js +0 -0
- package/src/flow/kit/widgets.js +0 -0
- package/src/flow/lanes/editor.js +0 -0
- package/src/flow/lanes/index.js +0 -0
- package/src/flow/lanes/layout.js +0 -0
- package/src/flow/lanes/pipeline.js +0 -0
- package/src/flow/lanes/preview.js +0 -0
- package/src/flow/lanes/strings.js +0 -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 +0 -0
- package/src/help/help_registry.js +0 -0
- package/src/host/external_links.js +0 -0
- package/src/host/file_picker.js +0 -0
- package/src/host/host.js +0 -0
- package/src/host/photino_host.js +0 -0
- package/src/host/photino_host.md +0 -0
- package/src/host/pywebview_host.js +0 -0
- package/src/tiles/chart_tiles.js +0 -0
- package/src/tiles/config_schema.js +0 -0
- package/src/tiles/figure_tile.js +0 -0
- package/src/tiles/layout_persistence.js +0 -0
- package/src/tiles/list_tiles.js +0 -0
- package/src/tiles/scope_picker.js +0 -0
- package/src/tiles/tile_config_form.js +0 -0
- package/src/tiles/tile_grid.js +0 -0
- package/src/tiles/tile_registry.js +0 -0
- package/src/tiling/bottom_panel_tabs.js +0 -0
- package/src/tiling/command_palette.js +0 -0
- package/src/tiling/content_registry.js +0 -0
- package/src/tiling/desktops.js +79 -16
- package/src/tiling/entity_sources.js +0 -0
- package/src/tiling/keymap.js +70 -2
- package/src/tiling/kind_taxonomy.js +0 -0
- package/src/tiling/landing_table.js +0 -0
- package/src/tiling/loading_overlay.js +0 -0
- package/src/tiling/nav_panel.js +0 -0
- package/src/tiling/page_factory.js +0 -0
- package/src/tiling/panel_availability.js +0 -0
- package/src/tiling/panel_keys.js +0 -0
- package/src/tiling/shell.js +9 -2
- package/src/tiling/tab_strip.js +0 -0
- package/src/tiling/tile_breadcrumb.js +0 -0
- package/src/tiling/tile_renderer.js +0 -0
- package/src/tiling/tile_tab_menu.js +0 -0
- package/src/tiling/tile_tree.js +63 -5
- package/src/tiling/wm.js +59 -6
- 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/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 +0 -0
- package/src/ui/components/cell_editors.js +0 -0
- package/src/ui/components/cell_grid.js +0 -0
- package/src/ui/components/cell_paste.js +0 -0
- package/src/ui/components/cell_selection.js +0 -0
- package/src/ui/components/choice_controls.js +0 -0
- package/src/ui/components/color_field.js +0 -0
- package/src/ui/components/color_list_field.js +0 -0
- package/src/ui/components/column_fit.js +0 -0
- package/src/ui/components/column_groups.js +0 -0
- package/src/ui/components/column_order.js +0 -0
- package/src/ui/components/computing_status_window.js +0 -0
- package/src/ui/components/confirm_dialog.js +0 -0
- package/src/ui/components/conversation_view.js +0 -0
- package/src/ui/components/crosstab.js +0 -0
- package/src/ui/components/detail_header.js +0 -0
- package/src/ui/components/document_outline.js +0 -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 +0 -0
- package/src/ui/components/gallery_picker.js +0 -0
- package/src/ui/components/header_actions.js +0 -0
- package/src/ui/components/inline_renamer.js +0 -0
- package/src/ui/components/job_progress.js +0 -0
- package/src/ui/components/managed_window.js +24 -6
- package/src/ui/components/menu_bar.js +0 -0
- package/src/ui/components/modal.js +0 -0
- package/src/ui/components/notification_history.js +0 -0
- package/src/ui/components/progress.js +0 -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 +0 -0
- package/src/ui/components/series.js +0 -0
- package/src/ui/components/settings_view.js +0 -0
- package/src/ui/components/slide_out_panel.js +0 -0
- package/src/ui/components/slider_field.js +0 -0
- package/src/ui/components/sortable_list.js +0 -0
- package/src/ui/components/staged_edits.js +0 -0
- package/src/ui/components/staged_history.js +0 -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 +0 -0
- package/src/ui/components/table_values.js +0 -0
- package/src/ui/components/tabs.js +0 -0
- package/src/ui/components/tag_input.js +0 -0
- package/src/ui/components/toast.js +0 -0
- package/src/ui/components/toggle_field.js +0 -0
- package/src/ui/components/tree_view.js +41 -18
- package/src/ui/components/tsv.js +0 -0
- package/src/ui/components/window_taskbar.js +0 -0
- package/src/ui/components/wizard.js +0 -0
- package/src/ui/controllers/window_chrome_controller.js +0 -0
- package/src/ui/notification_center.js +0 -0
- package/src/ui/utils/auto_scrollbars.js +0 -0
- package/src/ui/utils/markdown.js +0 -0
- package/src/ui/utils/native_context_menu.js +0 -0
- package/src/ui/utils/overlay_scrollbar.js +0 -0
- 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 +0 -0
- package/src/ui/utils/tooltip_service.js +0 -0
- package/tiles.js +0 -0
- package/widgets.js +0 -0
- package/wm.js +0 -0
- package/dist/chunk-CVMCZZX2.js.map +0 -7
- /package/dist/{chunk-NIJEJAZ7.js.map → chunk-2PM6ZMNS.js.map} +0 -0
- /package/dist/{chunk-F35XSQWA.js.map → chunk-IG74S3ZH.js.map} +0 -0
- /package/dist/{chunk-75FLVAUT.js.map → chunk-TLIV6QSU.js.map} +0 -0
- /package/dist/{chunk-BE2PU3LL.js.map → chunk-ZMH3OC7H.js.map} +0 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|
File without changes
|
package/src/flow/kit/keys.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/flow/kit/strings.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/flow/kit/widgets.js
CHANGED
|
File without changes
|
package/src/flow/lanes/editor.js
CHANGED
|
File without changes
|
package/src/flow/lanes/index.js
CHANGED
|
File without changes
|
package/src/flow/lanes/layout.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|
package/src/help/help_modal.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/host/file_picker.js
CHANGED
|
File without changes
|
package/src/host/host.js
CHANGED
|
File without changes
|
package/src/host/photino_host.js
CHANGED
|
File without changes
|
package/src/host/photino_host.md
CHANGED
|
File without changes
|
|
File without changes
|
package/src/tiles/chart_tiles.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/tiles/figure_tile.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/tiles/list_tiles.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/tiles/tile_grid.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/tiling/desktops.js
CHANGED
|
@@ -27,6 +27,25 @@ const DEFAULT_PANEL_STATE = {
|
|
|
27
27
|
bottom: true,
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
/** An object that is not an array: what a saved desktop or map is. */
|
|
31
|
+
function _isRecord(v) {
|
|
32
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Throw a `TypeError` saying what the saved layout was expected to hold at
|
|
36
|
+
* `path` and what it held instead (see the same helper in tile_tree.js, which
|
|
37
|
+
* reads the trees these desktops hold). */
|
|
38
|
+
function _expect(ok, path, want, got) {
|
|
39
|
+
if (ok) return;
|
|
40
|
+
let shown;
|
|
41
|
+
if (got === undefined) shown = 'nothing';
|
|
42
|
+
else {
|
|
43
|
+
try { shown = JSON.stringify(got); } catch { shown = String(got); }
|
|
44
|
+
if (shown.length > 40) shown = `${shown.slice(0, 37)}...`;
|
|
45
|
+
}
|
|
46
|
+
throw new TypeError(`${path}: expected ${want}, found ${shown}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
/**
|
|
31
50
|
* `seed()` yields the leaf a fresh (or emptied) desktop starts with. It comes
|
|
32
51
|
* from the taxonomy's root kind — see WindowManager, which supplies it. The
|
|
@@ -178,23 +197,65 @@ export class DesktopManager {
|
|
|
178
197
|
};
|
|
179
198
|
}
|
|
180
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Rebuild the manager from what `serialize()` wrote. `null`/`undefined` is
|
|
202
|
+
* nothing saved and gives the fresh manager.
|
|
203
|
+
*
|
|
204
|
+
* The blob is whatever the host's `state` held under `DESKTOPS_KEY`: a file
|
|
205
|
+
* anyone can truncate or edit, which is valid JSON of any shape. So it is
|
|
206
|
+
* read STRICTLY, and a blob that `serialize()` could not have written throws
|
|
207
|
+
* a `TypeError` naming the path and what was expected there. It builds only
|
|
208
|
+
* objects of its own and touches nothing its caller holds, which is what lets
|
|
209
|
+
* `WindowManager.load` treat a throw as "nothing saved" with nothing to undo.
|
|
210
|
+
*
|
|
211
|
+
* Strict about SHAPE, not about every value. What the loader, the tree
|
|
212
|
+
* operations and the renderer index, iterate or branch on has to be what they
|
|
213
|
+
* need (containers, node and tab kinds, `dir`, `children`, `sizes`,
|
|
214
|
+
* `activeTabIdx`). What is only drawn or coerced is taken as it is: a desktop's
|
|
215
|
+
* `id`, `label`, `group` and `fixed`, a panel's flag, a tab's title and props,
|
|
216
|
+
* `activeIdx`. A layout that fails on one of those would be thrown away for
|
|
217
|
+
* nothing, and an app can put almost anything in a title.
|
|
218
|
+
*
|
|
219
|
+
* And strict is not hostile to an old blob. A key the loader has always given
|
|
220
|
+
* a default may be absent: a desktop's `id`, `label` and `panels`, the keys
|
|
221
|
+
* later releases added (`panelShares`, `group`, `fixed`), and a leaf's `tabs`
|
|
222
|
+
* and `pageTabs`. What `serialize()` has always written and the loader cannot
|
|
223
|
+
* do without (`desktops`, a desktop's `tree`, a split's `dir`, `children` and
|
|
224
|
+
* `sizes`) may not.
|
|
225
|
+
*/
|
|
181
226
|
static deserialize(blob, { seed, panelDefaults = null } = {}) {
|
|
182
227
|
const m = new DesktopManager({ seed, panelDefaults });
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
panels
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
228
|
+
if (blob == null) return m;
|
|
229
|
+
_expect(Array.isArray(blob.desktops) && blob.desktops.length > 0,
|
|
230
|
+
'desktops', 'a non-empty array of desktops', blob.desktops);
|
|
231
|
+
m.desktops = blob.desktops.map((raw, i) => {
|
|
232
|
+
const at = `desktops[${i}]`;
|
|
233
|
+
_expect(_isRecord(raw), at, 'a desktop, an object', raw);
|
|
234
|
+
// `null` is a desktop with nothing on it (serialize() writes that);
|
|
235
|
+
// a desktop with no `tree` key at all is not one serialize() writes.
|
|
236
|
+
_expect(raw.tree === null || (_isRecord(raw.tree) && _isRecord(raw.tree.root)),
|
|
237
|
+
`${at}.tree`, '{ root }, or null for an empty desktop', raw.tree);
|
|
238
|
+
_expect(raw.panels == null || _isRecord(raw.panels), `${at}.panels`, 'an object', raw.panels);
|
|
239
|
+
// The VALUES of `panels` and `panelShares` are not checked: the loader
|
|
240
|
+
// already coerces a flag (`!!`) and `_validShare` drops a share that
|
|
241
|
+
// is not one, so the panel opens at its default.
|
|
242
|
+
_expect(raw.panelShares == null || _isRecord(raw.panelShares),
|
|
243
|
+
`${at}.panelShares`, 'an object', raw.panelShares);
|
|
244
|
+
return {
|
|
245
|
+
id: raw.id || `desk-${Math.random().toString(36).slice(2,8)}`,
|
|
246
|
+
label: raw.label || '?',
|
|
247
|
+
tree: raw.tree ? TileTree.deserialize(raw.tree, `${at}.tree`) : new TileTree(),
|
|
248
|
+
windows: [],
|
|
249
|
+
// A RESTORED desktop's own answer wins over the default: the user
|
|
250
|
+
// closed that panel, and re-opening it on every reload is the bug
|
|
251
|
+
// this merge order avoids. The default only fills a key the stored
|
|
252
|
+
// blob predates.
|
|
253
|
+
panels: { ...m.panelDefaults, ...(raw.panels || {}) },
|
|
254
|
+
panelShares: { ...(raw.panelShares || {}) },
|
|
255
|
+
group: raw.group || null,
|
|
256
|
+
fixed: !!raw.fixed,
|
|
257
|
+
};
|
|
258
|
+
});
|
|
198
259
|
// Empty tree → seed with the taxonomy root so the desktop is usable.
|
|
199
260
|
for (const d of m.desktops) {
|
|
200
261
|
if (!d.tree.rootId) d.tree.setRoot(makeLeaf(seed()));
|
|
@@ -212,7 +273,9 @@ export class DesktopManager {
|
|
|
212
273
|
* against a host that cannot persist (e.g. the standalone demo).
|
|
213
274
|
*/
|
|
214
275
|
|
|
215
|
-
|
|
276
|
+
/** The logical state key the layout is saved under. Exported so a warning about
|
|
277
|
+
* a layout that could not be read can name it. */
|
|
278
|
+
export const DESKTOPS_KEY = 'desktops';
|
|
216
279
|
|
|
217
280
|
export async function loadDesktops(state) {
|
|
218
281
|
if (!state) return null;
|
|
File without changes
|
package/src/tiling/keymap.js
CHANGED
|
@@ -287,6 +287,70 @@ function _compose(mods, key) {
|
|
|
287
287
|
return [...used, k].join('+');
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
/** What counts as "focus is in an editable field": a key press aimed at one of
|
|
291
|
+
* these may be typing. */
|
|
292
|
+
const TEXT_FIELDS = 'input, textarea, select, [contenteditable="true"]';
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Whether Alt on this machine is a key that TYPES: Option on a Mac (and on an
|
|
296
|
+
* iPad with a keyboard). Option changes the character on every Mac layout, so
|
|
297
|
+
* Option+digit is how a programmer types `{ } [ ] | \ @ #` on a German,
|
|
298
|
+
* Spanish, Swedish or French keyboard, where Windows and Linux keep Alt for
|
|
299
|
+
* shortcuts and type those with AltGr. Nothing in the event tells the two
|
|
300
|
+
* apart (a US Windows Alt+Shift+2 and a German Mac Option+8 both arrive as
|
|
301
|
+
* `altKey` with a printable `key`), so this is read from the platform, at the
|
|
302
|
+
* key press so that it is never stale.
|
|
303
|
+
*/
|
|
304
|
+
function _altTypes() {
|
|
305
|
+
const nav = globalThis.navigator;
|
|
306
|
+
return /^(mac|iphone|ipad|ipod)/i.test(nav?.userAgentData?.platform || nav?.platform || '');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* The digit ('1'..'9') a keydown on the digit row stands for, or null.
|
|
311
|
+
*
|
|
312
|
+
* `e.key` is what the layout and the modifiers TYPE, and `Alt+Shift+2` names a
|
|
313
|
+
* key, not a character. With Shift held a US keyboard's 2 types "@" and a
|
|
314
|
+
* German one's '"'; Option on a Mac types "™" or "€"; and an AZERTY keyboard's
|
|
315
|
+
* digit row types "&", "é", '"' and the like with no modifier at all and the
|
|
316
|
+
* digits only with Shift. Read `e.key` and Alt+Shift+2 is never seen from a
|
|
317
|
+
* real key press (only from a synthetic event that sets `key: '2'`, which is
|
|
318
|
+
* how the tests hid it), and on AZERTY it is the unshifted Alt+1..9 that never
|
|
319
|
+
* fire. `e.code` is the key's position (`Digit2`), which neither Shift nor the
|
|
320
|
+
* layout changes.
|
|
321
|
+
*
|
|
322
|
+
* `e.key` is still read in three cases:
|
|
323
|
+
*
|
|
324
|
+
* - An event with no `Digit` code: a synthetic one that carries only `key`,
|
|
325
|
+
* and the numpad (`Numpad2`), whose `key` is the digit itself with NumLock
|
|
326
|
+
* on and a navigation key ("ArrowDown") with it off. The numpad is left to
|
|
327
|
+
* say which it is, so a numpad used as arrows still moves focus.
|
|
328
|
+
* - AltGr. Windows reports it as Ctrl+Alt, and a German, French or Spanish
|
|
329
|
+
* layout types `{ [ ] @ # |` on its digit row with it. Taking those events
|
|
330
|
+
* for Alt+digit would switch desktops, and swallow the character in a
|
|
331
|
+
* field (the Alt chords work in fields), instead of typing it.
|
|
332
|
+
* - Option on a Mac, with focus in an editable field. It types the same
|
|
333
|
+
* characters AltGr does (German Option+8 is "{"), but arrives as a bare
|
|
334
|
+
* `altKey` with no Ctrl and no AltGraph state, so the AltGr test above does
|
|
335
|
+
* not see it. Out of a field nothing is typed, so there the key's position
|
|
336
|
+
* is read as everywhere else and Option+2 switches desktop; in a field the
|
|
337
|
+
* character is typed, as it was before the position was read, and the
|
|
338
|
+
* chord waits for focus to leave the field. The test is the platform, not
|
|
339
|
+
* "does `key` look like a character": US Windows Alt+Shift+2 types "@" in
|
|
340
|
+
* the same way and is the very press this function exists to read.
|
|
341
|
+
*
|
|
342
|
+
* This is for the digit row only. Letter keys move with the layout (AZERTY and
|
|
343
|
+
* QWERTZ swap some, Dvorak most), so `KeyW` is not "the W key" there: a letter
|
|
344
|
+
* chord stays named by the character its key types.
|
|
345
|
+
*/
|
|
346
|
+
function _digitOfEvent(e) {
|
|
347
|
+
const typesCharacter = e.getModifierState?.('AltGraph') || (e.ctrlKey && e.altKey)
|
|
348
|
+
|| (_altTypes() && !!e.target?.closest?.(TEXT_FIELDS));
|
|
349
|
+
const byPosition = typesCharacter ? null : /^Digit([1-9])$/.exec(e.code ?? '');
|
|
350
|
+
if (byPosition) return byPosition[1];
|
|
351
|
+
return /^[1-9]$/.test(e.key) ? e.key : null;
|
|
352
|
+
}
|
|
353
|
+
|
|
290
354
|
/**
|
|
291
355
|
* The chord id a keydown event is read as, in `DEFAULT_CHORDS`' spelling, or
|
|
292
356
|
* null. This is NOT an exact match on the modifier set: it reads an event the
|
|
@@ -303,6 +367,9 @@ function _compose(mods, key) {
|
|
|
303
367
|
* F1..F12, Shift+Left/Right, Ctrl+Left/Right exactly those modifiers
|
|
304
368
|
* with Alt held, Ctrl and Meta are ignored:
|
|
305
369
|
* Alt(+Shift)+1..9, +H, +V, +Arrow Shift picks the Alt+Shift+… row
|
|
370
|
+
* (1..9 by the key's position,
|
|
371
|
+
* not the character it types:
|
|
372
|
+
* see `_digitOfEvent`)
|
|
306
373
|
* Alt+T / N / W / F Shift ignored
|
|
307
374
|
* any other Alt+letter Alt+Letter
|
|
308
375
|
*
|
|
@@ -330,7 +397,8 @@ function _chordOfEvent(e) {
|
|
|
330
397
|
return null;
|
|
331
398
|
}
|
|
332
399
|
const sh = shift ? 'Shift+' : '';
|
|
333
|
-
|
|
400
|
+
const digit = _digitOfEvent(e);
|
|
401
|
+
if (digit) return `Alt+${sh}${digit}`;
|
|
334
402
|
if (lower === 'h' || lower === 'v') return `Alt+${sh}${lower.toUpperCase()}`;
|
|
335
403
|
if (lower === 't' || lower === 'n' || lower === 'w' || lower === 'f') return `Alt+${lower.toUpperCase()}`;
|
|
336
404
|
if (ARROWS.includes(key)) return `Alt+${sh}${key}`;
|
|
@@ -442,7 +510,7 @@ export function installKeymap({ wm, palette, navSelector = null, bindings = null
|
|
|
442
510
|
if (!chord) return;
|
|
443
511
|
const b = table.get(chord);
|
|
444
512
|
if (!b || b.disabled) return;
|
|
445
|
-
if (!b.inField && e.target?.closest?.(
|
|
513
|
+
if (!b.inField && e.target?.closest?.(TEXT_FIELDS)) return;
|
|
446
514
|
if (b.run(ctx, e) !== false) e.preventDefault();
|
|
447
515
|
};
|
|
448
516
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/tiling/nav_panel.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/tiling/panel_keys.js
CHANGED
|
File without changes
|
package/src/tiling/shell.js
CHANGED
|
@@ -62,7 +62,11 @@ import { openForm } from '../ui/components/modal.js';
|
|
|
62
62
|
* it through (the palette + tile tab menu
|
|
63
63
|
* hand it to the entity catalog's fetchers).
|
|
64
64
|
* @param {object} [cfg.eventBus]
|
|
65
|
-
* @param {object} [cfg.logger]
|
|
65
|
+
* @param {object} [cfg.logger] `{ warn? }`. Told of problems the shell
|
|
66
|
+
* carries on from: a keymap binding it
|
|
67
|
+
* does not know, a saved layout it cannot
|
|
68
|
+
* read (the default one opens instead).
|
|
69
|
+
* Else `console`.
|
|
66
70
|
* @param {object} [cfg.tableStore] threaded into ctx for table persistence
|
|
67
71
|
* @param {object} [cfg.events] bus-event NAMES the WM listens for
|
|
68
72
|
* @param {object} [cfg.rootCrumb] the breadcrumb's leading segment
|
|
@@ -237,6 +241,7 @@ export async function createShell({
|
|
|
237
241
|
host,
|
|
238
242
|
taxonomy,
|
|
239
243
|
events,
|
|
244
|
+
logger,
|
|
240
245
|
panelDefaults: panels,
|
|
241
246
|
snapPromotion,
|
|
242
247
|
promoteInPlace,
|
|
@@ -703,7 +708,9 @@ function _tileContextMenu(wm, leafId, x, y) {
|
|
|
703
708
|
];
|
|
704
709
|
if (wm.desktops.desktops.length > 1 && !isPanel) {
|
|
705
710
|
for (const [i, d] of wm.desktops.desktops.entries()) {
|
|
706
|
-
// C40. Never onto a fixed desktop
|
|
711
|
+
// C40. Never onto a fixed desktop, nor the one you are on, and (see
|
|
712
|
+
// `isPanel` above) never a panel: `moveFocusedToDesktop` refuses all
|
|
713
|
+
// three too, because the keyboard reaches it without this menu.
|
|
707
714
|
if (i === wm.desktops.activeIdx || d.fixed) continue;
|
|
708
715
|
items.push({ label: `Move to desktop ${d.label}`, icon: 'sweep',
|
|
709
716
|
action: `move:${i}` });
|
package/src/tiling/tab_strip.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/tiling/tile_tree.js
CHANGED
|
@@ -21,6 +21,25 @@
|
|
|
21
21
|
let _idCounter = 1;
|
|
22
22
|
function _newId(prefix) { return `${prefix}-${_idCounter++}`; }
|
|
23
23
|
|
|
24
|
+
/** An object that is not an array: what a saved node, tab or map is. */
|
|
25
|
+
function _isRecord(v) {
|
|
26
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Throw a `TypeError` saying what the saved layout was expected to hold at
|
|
30
|
+
* `path` and what it held instead. The value is shown short: a layout can be
|
|
31
|
+
* large, and the line is read in a console. */
|
|
32
|
+
function _expect(ok, path, want, got) {
|
|
33
|
+
if (ok) return;
|
|
34
|
+
let shown;
|
|
35
|
+
if (got === undefined) shown = 'nothing';
|
|
36
|
+
else {
|
|
37
|
+
try { shown = JSON.stringify(got); } catch { shown = String(got); }
|
|
38
|
+
if (shown.length > 40) shown = `${shown.slice(0, 37)}...`;
|
|
39
|
+
}
|
|
40
|
+
throw new TypeError(`${path}: expected ${want}, found ${shown}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
24
43
|
function _isPanel(leaf) {
|
|
25
44
|
return String(leaf?.content?.kind || '').startsWith('panel:');
|
|
26
45
|
}
|
|
@@ -939,11 +958,42 @@ export class TileTree {
|
|
|
939
958
|
};
|
|
940
959
|
}
|
|
941
960
|
|
|
942
|
-
|
|
961
|
+
/** Rebuild a tree from what `serialize()` wrote. A blob with no root is an
|
|
962
|
+
* empty tree, as it always was. Anything else is read STRICTLY: a node that
|
|
963
|
+
* `serialize()` could not have written throws a `TypeError` naming where
|
|
964
|
+
* (`at` is the path of the blob, for the message only) and what it wanted,
|
|
965
|
+
* rather than building half a tree or failing later, inside the renderer,
|
|
966
|
+
* with `node.sizes.reduce is not a function`. The caller decides what a
|
|
967
|
+
* tree that cannot be read means; see `WindowManager.load`. */
|
|
968
|
+
static deserialize(blob, at = 'tree') {
|
|
943
969
|
const t = new TileTree();
|
|
944
970
|
if (!blob || !blob.root) return t;
|
|
945
|
-
|
|
971
|
+
// A tab, and the legacy `content` a leaf mirrors its active tab into, are
|
|
972
|
+
// the same shape: what `makeLeaf` and `_syncActiveTab` read. Its `title` and
|
|
973
|
+
// `props` are NOT checked. A title is only drawn, and is whatever a content
|
|
974
|
+
// factory returned (a number, say); props are the app's, and a factory that
|
|
975
|
+
// cannot use them is told so by the registry's mount-error tile, not by a
|
|
976
|
+
// layout thrown away.
|
|
977
|
+
const checkTab = (tab, path) => {
|
|
978
|
+
_expect(_isRecord(tab) && typeof tab.kind === 'string', path, 'a tab, { kind, props, title }', tab);
|
|
979
|
+
};
|
|
980
|
+
const build = (raw, path) => {
|
|
981
|
+
_expect(_isRecord(raw), path, 'a node, an object', raw);
|
|
982
|
+
_expect(raw.kind === 'leaf' || raw.kind === 'split', `${path}.kind`, '"leaf" or "split"', raw.kind);
|
|
946
983
|
if (raw.kind === 'leaf') {
|
|
984
|
+
if (raw.content != null) checkTab(raw.content, `${path}.content`);
|
|
985
|
+
_expect(raw.tabs == null || Array.isArray(raw.tabs), `${path}.tabs`, 'an array of tabs', raw.tabs);
|
|
986
|
+
(raw.tabs || []).forEach((tab, i) => checkTab(tab, `${path}.tabs[${i}]`));
|
|
987
|
+
_expect(raw.activeTabIdx == null || Number.isFinite(raw.activeTabIdx),
|
|
988
|
+
`${path}.activeTabIdx`, 'a number', raw.activeTabIdx);
|
|
989
|
+
_expect(raw.pageTabs == null || _isRecord(raw.pageTabs), `${path}.pageTabs`, 'an object', raw.pageTabs);
|
|
990
|
+
for (const [page, arc] of Object.entries(raw.pageTabs || {})) {
|
|
991
|
+
const where = `${path}.pageTabs[${JSON.stringify(page)}]`;
|
|
992
|
+
_expect(_isRecord(arc) && Array.isArray(arc.tabs), where, '{ tabs, activeTabIdx }', arc);
|
|
993
|
+
arc.tabs.forEach((tab, i) => checkTab(tab, `${where}.tabs[${i}]`));
|
|
994
|
+
_expect(arc.activeTabIdx == null || Number.isFinite(arc.activeTabIdx),
|
|
995
|
+
`${where}.activeTabIdx`, 'a number', arc.activeTabIdx);
|
|
996
|
+
}
|
|
947
997
|
// Forward-compat with the pre-tabs layout: an old
|
|
948
998
|
// serialized leaf carries `content` but no `tabs`.
|
|
949
999
|
// makeLeaf will derive a single-tab list from `content`.
|
|
@@ -961,7 +1011,6 @@ export class TileTree {
|
|
|
961
1011
|
if (raw.pageTabs && typeof raw.pageTabs === 'object') {
|
|
962
1012
|
leaf.pageTabs = {};
|
|
963
1013
|
for (const [k, v] of Object.entries(raw.pageTabs)) {
|
|
964
|
-
if (!v || !Array.isArray(v.tabs)) continue;
|
|
965
1014
|
leaf.pageTabs[k] = {
|
|
966
1015
|
tabs: v.tabs.map((t) => ({
|
|
967
1016
|
kind: t.kind,
|
|
@@ -974,7 +1023,16 @@ export class TileTree {
|
|
|
974
1023
|
}
|
|
975
1024
|
return leaf;
|
|
976
1025
|
}
|
|
977
|
-
|
|
1026
|
+
// `sizes` run parallel to `children`: the renderer, `setSplitSizes`,
|
|
1027
|
+
// `swapSiblings` and the panel canonicalizer all index one by the
|
|
1028
|
+
// other, and none of them looks at a length first.
|
|
1029
|
+
_expect(raw.dir === 'h' || raw.dir === 'v', `${path}.dir`, '"h" or "v"', raw.dir);
|
|
1030
|
+
_expect(Array.isArray(raw.children) && raw.children.length > 0,
|
|
1031
|
+
`${path}.children`, 'a non-empty array of nodes', raw.children);
|
|
1032
|
+
_expect(Array.isArray(raw.sizes) && raw.sizes.length === raw.children.length
|
|
1033
|
+
&& raw.sizes.every((n) => Number.isFinite(n) && n >= 0),
|
|
1034
|
+
`${path}.sizes`, `${raw.children.length} non-negative numbers, one per child`, raw.sizes);
|
|
1035
|
+
const children = raw.children.map((c, i) => build(c, `${path}.children[${i}]`));
|
|
978
1036
|
const split = makeSplit({
|
|
979
1037
|
dir: raw.dir, children: children.map((c) => c.id),
|
|
980
1038
|
sizes: raw.sizes,
|
|
@@ -982,7 +1040,7 @@ export class TileTree {
|
|
|
982
1040
|
children.forEach((c) => t._register(c));
|
|
983
1041
|
return split;
|
|
984
1042
|
};
|
|
985
|
-
const root = build(blob.root);
|
|
1043
|
+
const root = build(blob.root, `${at}.root`);
|
|
986
1044
|
t.setRoot(root);
|
|
987
1045
|
return t;
|
|
988
1046
|
}
|
package/src/tiling/wm.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* wm.switchDesktop(idx) / wm.addDesktop() / wm.moveFocusedToDesktop(idx)
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { DesktopManager, saveDesktops, loadDesktops } from './desktops.js';
|
|
21
|
+
import { DesktopManager, saveDesktops, loadDesktops, DESKTOPS_KEY } from './desktops.js';
|
|
22
22
|
import { TileRenderer } from './tile_renderer.js';
|
|
23
23
|
import { makeLeaf } from './tile_tree.js';
|
|
24
24
|
import { PLACEHOLDER_KIND } from './content_registry.js';
|
|
@@ -38,7 +38,7 @@ const PANEL_TITLES = {
|
|
|
38
38
|
|
|
39
39
|
export class WindowManager {
|
|
40
40
|
constructor({ rootEl, api, ctx, onChange, eventBus, host, taxonomy, events, content,
|
|
41
|
-
panelDefaults = null, snapPromotion = false,
|
|
41
|
+
logger = null, panelDefaults = null, snapPromotion = false,
|
|
42
42
|
promoteInPlace = false, tabLayout = null, backToOpenList = false,
|
|
43
43
|
floatActiveTab = false, newTabButton = true }) {
|
|
44
44
|
if (!taxonomy) throw new Error('WindowManager: a taxonomy is required');
|
|
@@ -62,6 +62,9 @@ export class WindowManager {
|
|
|
62
62
|
this.events = events || {};
|
|
63
63
|
this.ctx = ctx || {};
|
|
64
64
|
this.eventBus = eventBus || null;
|
|
65
|
+
// Where load() reports a saved layout it could not read; else `console`,
|
|
66
|
+
// as the keymap does.
|
|
67
|
+
this.logger = logger || null;
|
|
65
68
|
this.onChange = onChange || (() => {});
|
|
66
69
|
// Panel availability. Runtime overrides (`setPanelAvailability`) win
|
|
67
70
|
// over the primary kind's declaration (`KindDef.panels`); the last
|
|
@@ -250,10 +253,41 @@ export class WindowManager {
|
|
|
250
253
|
// ── Persistence ─────────────────────────────────────────────────
|
|
251
254
|
async load() {
|
|
252
255
|
const blob = await loadDesktops(this.host?.state);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
// A SAVED LAYOUT THAT CANNOT BE READ IS NO LAYOUT. The state is a file that
|
|
257
|
+
// can be truncated or edited into valid JSON of the wrong shape, and a boot
|
|
258
|
+
// that dies on it dies on every start until someone deletes the file by
|
|
259
|
+
// hand. `deserialize` reads the blob strictly and throws before anything the
|
|
260
|
+
// WM holds has been touched, so recovering is doing nothing: `this.desktops`
|
|
261
|
+
// and the renderer are as they were, and the rest of this method runs as it
|
|
262
|
+
// does for a host with nothing saved. The catch stops there on purpose. The
|
|
263
|
+
// canonicalize and the render below only ever see a layout that WAS read, so
|
|
264
|
+
// a throw from them is a bug that has to reach the embedder, not a bad file
|
|
265
|
+
// to be answered by discarding what the user saved.
|
|
266
|
+
//
|
|
267
|
+
// The bad blob is LEFT in the store. Deleting it here would destroy the only
|
|
268
|
+
// copy of whatever the user was trying to restore; the next `_persist` (any
|
|
269
|
+
// layout change) overwrites it, and until then each boot warns again, which
|
|
270
|
+
// is the reminder.
|
|
271
|
+
if (blob != null) {
|
|
272
|
+
let restored = null;
|
|
273
|
+
try {
|
|
274
|
+
restored = DesktopManager.deserialize(blob, {
|
|
275
|
+
seed: this._rootLeaf, panelDefaults: this.panelDefaults });
|
|
276
|
+
} catch (err) {
|
|
277
|
+
// ONE string, as the keymap's warning is. The embedder's logger is
|
|
278
|
+
// usually a LoggingService, whose `scoped()` loggers read two leading
|
|
279
|
+
// strings as (subcategory, message) and keep only the second, and
|
|
280
|
+
// whose own warn(message, metadata) keeps the first string and files
|
|
281
|
+
// the rest as metadata: spread over several arguments, the key would
|
|
282
|
+
// be the whole recorded message and the cause would be gone. The
|
|
283
|
+
// console prints one string the same as several.
|
|
284
|
+
(this.logger ?? console).warn?.(`[wm] bad layout for state key ${DESKTOPS_KEY} `
|
|
285
|
+
+ `(opening the default layout): ${err?.message ?? err}`);
|
|
286
|
+
}
|
|
287
|
+
if (restored) {
|
|
288
|
+
this.desktops = restored;
|
|
289
|
+
this.renderer.tree = this.desktops.active().tree;
|
|
290
|
+
}
|
|
257
291
|
}
|
|
258
292
|
// Normalize: managed windows don't survive a reload, so any
|
|
259
293
|
// window-placeholder leaves restore their original content.
|
|
@@ -3941,10 +3975,26 @@ export class WindowManager {
|
|
|
3941
3975
|
return true;
|
|
3942
3976
|
}
|
|
3943
3977
|
|
|
3978
|
+
/**
|
|
3979
|
+
* Move the focused tile, with every tab it holds, to desktop `idx` (from 0).
|
|
3980
|
+
* A refused move returns `undefined` and changes nothing; the callers (the
|
|
3981
|
+
* keymap's `toDesktop:N`, the tile menu's *Move to desktop*) read no result.
|
|
3982
|
+
*
|
|
3983
|
+
* The tile menu (`shell.js`, `_tileContextMenu`) never offers a panel, a
|
|
3984
|
+
* fixed desktop or the desktop you are on, and this method refuses all
|
|
3985
|
+
* three itself, because the menu is one door and Alt+Shift+N is another.
|
|
3986
|
+
* A fixed desktop it always refused; a panel and the desktop you are on it
|
|
3987
|
+
* did not, and the move to the desktop you are on destroys the tile: source
|
|
3988
|
+
* and target are one tree, so the tabs are appended to its primary tile and
|
|
3989
|
+
* then the focused tile is closed. A stray Shift on Alt+N reaches that.
|
|
3990
|
+
*/
|
|
3944
3991
|
moveFocusedToDesktop(idx) {
|
|
3945
3992
|
const tree = this._tree();
|
|
3946
3993
|
const focused = tree.focused();
|
|
3947
3994
|
if (!focused || !focused.content) return;
|
|
3995
|
+
// A panel is chrome, not content (the menu's `isPanel`). Moved, it would
|
|
3996
|
+
// replace the target desktop's home ground.
|
|
3997
|
+
if (String(focused.content.kind || '').startsWith('panel:')) return;
|
|
3948
3998
|
// C20. IT DESTROYS A LEAF, SO IT ANSWERS TO THE SAME VETO `closeFocused`
|
|
3949
3999
|
// does. A move is not a close from the pane's point of view, but it is
|
|
3950
4000
|
// exactly a close from the PAGE's: this was the fourth door out of a
|
|
@@ -3954,6 +4004,9 @@ export class WindowManager {
|
|
|
3954
4004
|
if (closeChrome === false || closeChrome?.disabled === true) return;
|
|
3955
4005
|
// C40. A fixed desktop is the embedder's, and nothing is moved onto it.
|
|
3956
4006
|
if (this.desktops.desktops[idx]?.fixed) return;
|
|
4007
|
+
// Already there. Without this, `tree.close` below removes the tile from
|
|
4008
|
+
// the same tree its tabs were just appended to, and it is gone.
|
|
4009
|
+
if (idx === this.desktops.activeIdx) return;
|
|
3957
4010
|
this.desktops.ensureCount(idx + 1);
|
|
3958
4011
|
const target = this.desktops.desktops[idx];
|
|
3959
4012
|
// C40. THE PANE'S TABS JOIN THE TARGET'S PRIMARY LEAF, ALL OF THEM. It
|
package/src/tiling/zoom.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/ui/base/page_base.js
CHANGED
|
File without changes
|