flexdesk 0.17.13 → 0.17.15
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 +43 -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 +58 -7
- package/css/flexdesk.css +90 -8
- package/css/overrides.css +21 -0
- package/css/reset.css +0 -0
- package/dist/canvas.js +0 -0
- package/dist/canvas.js.map +0 -0
- package/dist/chunk-3PHCPZHT.js +0 -0
- package/dist/chunk-3PHCPZHT.js.map +0 -0
- package/dist/{chunk-IG74S3ZH.js → chunk-AD7FIA3Z.js} +2 -2
- 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-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-N7HC3ADV.js +0 -0
- package/dist/chunk-N7HC3ADV.js.map +0 -0
- package/dist/{chunk-ZMH3OC7H.js → chunk-OEG6NPAN.js} +3 -1
- package/dist/chunk-OEG6NPAN.js.map +7 -0
- package/dist/core.js +3 -18
- package/dist/core.js.map +2 -2
- package/dist/editor.js.map +0 -0
- package/dist/flexdesk.css +90 -8
- package/dist/flow.js +1 -1
- 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 +2 -2
- package/dist/widgets.js +29 -20
- package/dist/widgets.js.map +2 -2
- package/dist/wm.js +205 -35
- 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 +19 -2
- 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 +9 -5
- 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 +15 -0
- package/docs/components/slide-out-panel.md +15 -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 +12 -4
- 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/menu_bar.js +0 -0
- package/src/ui/components/modal.js +11 -2
- 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-ZMH3OC7H.js.map +0 -7
- /package/dist/{chunk-IG74S3ZH.js.map → chunk-AD7FIA3Z.js.map} +0 -0
|
@@ -23,6 +23,22 @@ const DEFAULT_ALLOWED_ATTRIBUTES = Object.freeze([
|
|
|
23
23
|
const DEFAULT_ALLOWED_ATTRIBUTE_PREFIXES = Object.freeze([
|
|
24
24
|
]);
|
|
25
25
|
|
|
26
|
+
// What belongs here: the keys FlexDesk's own widgets write (the last group,
|
|
27
|
+
// which tests/state_guard_defaults.test.mjs keeps complete by scanning src/),
|
|
28
|
+
// the third-party attributes Plotly sets, and generic UI words. What does not:
|
|
29
|
+
// one application's own keys. Whether `data-order-id` is a legitimate write is
|
|
30
|
+
// that application's call, and it says so by adding the key (see
|
|
31
|
+
// docs/concepts/state-guard.md). A key listed here for one embedder's sake that
|
|
32
|
+
// no widget writes is a key every OTHER embedder's strict guard lets through
|
|
33
|
+
// without a word, which is the one thing a strict guard is there to stop.
|
|
34
|
+
//
|
|
35
|
+
// The list does not yet meet that, and it has not been audited key by key:
|
|
36
|
+
// many keys no widget writes are still here, some of them carried over from one
|
|
37
|
+
// application's markup (series-id, dataset-id, func-name and connector-id are
|
|
38
|
+
// examples). Thirteen that name one application's concepts were taken out. The
|
|
39
|
+
// test pins nine of the rest as still allowed, so dropping one is a deliberate
|
|
40
|
+
// edit, and the docs tell an embedder that a strict guard built from the
|
|
41
|
+
// defaults lets a write of such a key through. Do not add another like them.
|
|
26
42
|
const DEFAULT_ALLOWED_DATASET_KEYS = Object.freeze([
|
|
27
43
|
'testid',
|
|
28
44
|
'phase',
|
|
@@ -70,11 +86,8 @@ const DEFAULT_ALLOWED_DATASET_KEYS = Object.freeze([
|
|
|
70
86
|
'node-name',
|
|
71
87
|
'lang',
|
|
72
88
|
'playback-control',
|
|
73
|
-
//
|
|
89
|
+
// Generic state words
|
|
74
90
|
'status',
|
|
75
|
-
'scenario-id',
|
|
76
|
-
'run-id',
|
|
77
|
-
'run-state',
|
|
78
91
|
'selected',
|
|
79
92
|
'active',
|
|
80
93
|
'disabled',
|
|
@@ -90,13 +103,10 @@ const DEFAULT_ALLOWED_DATASET_KEYS = Object.freeze([
|
|
|
90
103
|
'input-index',
|
|
91
104
|
'output-index',
|
|
92
105
|
'port-index',
|
|
93
|
-
//
|
|
106
|
+
// Identifier and binding keys
|
|
94
107
|
'source',
|
|
95
108
|
'series-id',
|
|
96
109
|
'dataset-id',
|
|
97
|
-
'mirror-input-bound',
|
|
98
|
-
'mirror-change-bound',
|
|
99
|
-
'mirror-input',
|
|
100
110
|
'bound',
|
|
101
111
|
'field',
|
|
102
112
|
'key',
|
|
@@ -105,14 +115,11 @@ const DEFAULT_ALLOWED_DATASET_KEYS = Object.freeze([
|
|
|
105
115
|
'tab-id',
|
|
106
116
|
'param-id',
|
|
107
117
|
'target-id',
|
|
108
|
-
'override-type',
|
|
109
118
|
// Tree view component keys
|
|
110
119
|
'item-id',
|
|
111
120
|
'category-id',
|
|
112
|
-
// Calibration page keys
|
|
113
|
-
'info-for',
|
|
114
|
-
'column',
|
|
115
121
|
// Data page keys
|
|
122
|
+
'column',
|
|
116
123
|
'readonly',
|
|
117
124
|
'id',
|
|
118
125
|
'action',
|
|
@@ -131,12 +138,6 @@ const DEFAULT_ALLOWED_DATASET_KEYS = Object.freeze([
|
|
|
131
138
|
'label',
|
|
132
139
|
'title',
|
|
133
140
|
'description',
|
|
134
|
-
// DSL generator keys
|
|
135
|
-
'dsl-state',
|
|
136
|
-
'dsl-section-registered',
|
|
137
|
-
'dsl-scrollbars-installed',
|
|
138
|
-
'dsl-tabs-bound',
|
|
139
|
-
'dsl-controls-bound',
|
|
140
141
|
// Inline highlighter keys
|
|
141
142
|
'inline-hl',
|
|
142
143
|
// Overlay scrollbar keys (UI-only, not state storage)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|
|
@@ -24,14 +24,19 @@
|
|
|
24
24
|
/** Build a breadcrumb strip element + render initial state. Returns
|
|
25
25
|
* `{ el, destroy }`. The strip is wired to the WM (clicks open content
|
|
26
26
|
* in the primary tile) and, when the embedder supplies a `rootCrumb`
|
|
27
|
-
* with a `subscribe` hook, to whatever keeps the root label fresh.
|
|
27
|
+
* with a `subscribe` hook, to whatever keeps the root label fresh.
|
|
28
|
+
* While the path is a single segment (the tile itself) the strip is
|
|
29
|
+
* `hidden` and takes no room; `refresh()` shows it once there are more. */
|
|
28
30
|
export function mountTileBreadcrumb(kind, props, ctx) {
|
|
29
31
|
const eventBus = ctx?.eventBus;
|
|
30
32
|
const taxonomy = ctx?.taxonomy;
|
|
31
33
|
const rootCrumb = ctx?.rootCrumb || null;
|
|
32
34
|
if (!taxonomy) {
|
|
33
35
|
console.error('[breadcrumb] no taxonomy in ctx — cannot render');
|
|
34
|
-
|
|
36
|
+
const el = document.createElement('nav');
|
|
37
|
+
el.className = 'twm-topbar-breadcrumb twm-tile-breadcrumb';
|
|
38
|
+
el.hidden = true; // nothing to draw: no strip, as for a one-segment path
|
|
39
|
+
return { el, refresh: () => {}, destroy: () => {} };
|
|
35
40
|
}
|
|
36
41
|
const root = document.createElement('nav');
|
|
37
42
|
root.className = 'twm-topbar-breadcrumb twm-tile-breadcrumb';
|
|
@@ -82,9 +87,12 @@ export function mountTileBreadcrumb(kind, props, ctx) {
|
|
|
82
87
|
try { return ctx.trailSegments() || []; }
|
|
83
88
|
catch (err) { console.warn('[breadcrumb] trailSegments threw', err); return []; }
|
|
84
89
|
};
|
|
90
|
+
// One segment is the tile itself, which its title already names: the strip
|
|
91
|
+
// is hidden (and its page-shell slot with it) until there is a level above.
|
|
85
92
|
const render = () => {
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
const segments = _segments(kind, props, taxonomy, rootCrumb, rootLabel, navigate, trailOf());
|
|
94
|
+
_renderInto(root, segments);
|
|
95
|
+
root.hidden = segments.length < 2;
|
|
88
96
|
};
|
|
89
97
|
render();
|
|
90
98
|
|
|
File without changes
|
|
File without changes
|