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
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
|
|
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
|
|
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
|
|
@@ -56,8 +56,12 @@
|
|
|
56
56
|
* Rows — an entry of the form `{ row: [field, field, ...] }` puts its fields
|
|
57
57
|
* side by side (a min and a max, say). Each is an ordinary field: its own
|
|
58
58
|
* label, hint, error, `setVisible`, value and submitted key — the row is
|
|
59
|
-
* layout only and has no name.
|
|
60
|
-
*
|
|
59
|
+
* layout only and has no name. Its inputs are equally wide (up to each
|
|
60
|
+
* control's own padding): the space left after the labels is shared equally.
|
|
61
|
+
* A form whose widest row has three or more fields opens wider (about 230px
|
|
62
|
+
* per field; at least 640px, otherwise at most 90% of the window).
|
|
63
|
+
* The row stacks its fields when the form is narrower than 360px (a
|
|
64
|
+
* container query on `.twm-modal__form`).
|
|
61
65
|
*
|
|
62
66
|
* Picker — a value the user LOOKS UP rather than types (an id chosen in a
|
|
63
67
|
* search dialog, say). The row shows a read-only box and a search button;
|
|
@@ -913,6 +917,11 @@ function _estimateWidth(fields) {
|
|
|
913
917
|
// Forms with a textarea, a colour list, a section divider or a row of
|
|
914
918
|
// fields get the wider layout so multi-line content + grouped fields
|
|
915
919
|
// breathe; otherwise 480 px is enough for label + single-line input pairs.
|
|
920
|
+
// A row of three or more fields needs more than 640 px for inputs of a
|
|
921
|
+
// usable width (BUG-0050): ~230 px per field (its label and input) past the
|
|
922
|
+
// label column, clamped to 90% of the window.
|
|
923
|
+
const widest = Math.max(0, ...fields.filter((f) => Array.isArray(f?.row)).map((f) => _formFields(f.row).length));
|
|
924
|
+
if (widest >= 3) return Math.max(640, Math.min(170 + widest * 230, Math.floor(window.innerWidth * 0.9)));
|
|
916
925
|
const wide = fields.some((f) => f && (f.type === 'textarea' || f.type === 'colorList' || f.section || Array.isArray(f.row)));
|
|
917
926
|
return wide ? 640 : 480;
|
|
918
927
|
}
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -128,28 +128,39 @@ export function createTreeNode(options) {
|
|
|
128
128
|
`;
|
|
129
129
|
setText(node, '.twm-tree-node__label', label);
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
if (actions) {
|
|
133
|
-
const actionsEl = node.querySelector('.twm-tree-node__actions');
|
|
134
|
-
Object.entries(actions).forEach(([actionId, actionConfig]) => {
|
|
135
|
-
const btn = document.createElement('button');
|
|
136
|
-
btn.className = 'twm-tree-node__action-btn';
|
|
137
|
-
btn.dataset.action = actionId;
|
|
138
|
-
btn.title = actionConfig.title || actionId;
|
|
139
|
-
btn.innerHTML = `<span class="material-symbols-outlined">${actionConfig.icon}</span>`;
|
|
140
|
-
if (actionConfig.onClick) {
|
|
141
|
-
btn.addEventListener('click', (e) => {
|
|
142
|
-
e.stopPropagation();
|
|
143
|
-
actionConfig.onClick(id, e);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
actionsEl.appendChild(btn);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
131
|
+
if (actions) appendActionButtons(node.querySelector('.twm-tree-node__actions'), id, actions);
|
|
149
132
|
|
|
150
133
|
return node;
|
|
151
134
|
}
|
|
152
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Fill a node's or an item's actions slot: one `.twm-tree-node__action-btn`
|
|
138
|
+
* per entry. A click on an entry with `onClick` runs `onClick(id, event)` and
|
|
139
|
+
* goes no further, so it neither selects the item nor toggles the node; nor
|
|
140
|
+
* does a double-click, so it never reaches TreeView's onItemDoubleClick.
|
|
141
|
+
* @param {HTMLElement} actionsEl
|
|
142
|
+
* @param {string} id - The node's or item's id
|
|
143
|
+
* @param {Object<string, { icon: string, title?: string, onClick?: Function }>} actions
|
|
144
|
+
*/
|
|
145
|
+
function appendActionButtons(actionsEl, id, actions) {
|
|
146
|
+
Object.entries(actions).forEach(([actionId, actionConfig]) => {
|
|
147
|
+
const btn = document.createElement('button');
|
|
148
|
+
btn.type = 'button';
|
|
149
|
+
btn.className = 'twm-tree-node__action-btn';
|
|
150
|
+
btn.dataset.action = actionId;
|
|
151
|
+
btn.title = actionConfig.title || actionId;
|
|
152
|
+
btn.innerHTML = `<span class="material-symbols-outlined">${actionConfig.icon}</span>`;
|
|
153
|
+
if (actionConfig.onClick) {
|
|
154
|
+
btn.addEventListener('click', (e) => {
|
|
155
|
+
e.stopPropagation();
|
|
156
|
+
actionConfig.onClick(id, e);
|
|
157
|
+
});
|
|
158
|
+
btn.addEventListener('dblclick', (e) => e.stopPropagation());
|
|
159
|
+
}
|
|
160
|
+
actionsEl.appendChild(btn);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
153
164
|
/**
|
|
154
165
|
* Create a tree item (leaf node)
|
|
155
166
|
* @param {Object} options - Item options
|
|
@@ -164,6 +175,10 @@ export function createTreeNode(options) {
|
|
|
164
175
|
* @param {Object} [options.badge] - Optional badge config
|
|
165
176
|
* @param {string} [options.badge.text] - Badge text (if empty, renders as status dot)
|
|
166
177
|
* @param {string} [options.badge.variant] - 'muted'|'pass'|'fail'|'error'
|
|
178
|
+
* @param {Object} [options.actions] - Row actions, as createTreeNode takes them
|
|
179
|
+
* ({ id: { icon, title, onClick(id, event) } }): buttons in one line at the
|
|
180
|
+
* row's end (`.twm-tree-item__actions`), shown on hover, on focus within and
|
|
181
|
+
* on the selected item
|
|
167
182
|
* @returns {HTMLElement} - Item element
|
|
168
183
|
*/
|
|
169
184
|
export function createTreeItem(options) {
|
|
@@ -177,6 +192,7 @@ export function createTreeItem(options) {
|
|
|
177
192
|
subtitle = null,
|
|
178
193
|
data = null,
|
|
179
194
|
badge = null,
|
|
195
|
+
actions = null,
|
|
180
196
|
} = options;
|
|
181
197
|
|
|
182
198
|
const item = document.createElement('div');
|
|
@@ -220,6 +236,13 @@ export function createTreeItem(options) {
|
|
|
220
236
|
if (subtitle) setText(item, '.twm-tree-item__subtitle', subtitle);
|
|
221
237
|
if (badge?.text) setText(item, '.twm-tree-item__badge', badge.text);
|
|
222
238
|
|
|
239
|
+
if (actions) {
|
|
240
|
+
const actionsEl = document.createElement('span');
|
|
241
|
+
actionsEl.className = 'twm-tree-item__actions';
|
|
242
|
+
appendActionButtons(actionsEl, id, actions);
|
|
243
|
+
item.appendChild(actionsEl);
|
|
244
|
+
}
|
|
245
|
+
|
|
223
246
|
return item;
|
|
224
247
|
}
|
|
225
248
|
|
package/src/ui/components/tsv.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/ui/utils/markdown.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/tiles.js
CHANGED
|
File without changes
|
package/widgets.js
CHANGED
|
File without changes
|
package/wm.js
CHANGED
|
File without changes
|