flexdesk 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +54 -3
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/css/base.css +0 -0
- package/css/flexdesk.css +34 -0
- package/css/overrides.css +0 -0
- package/css/reset.css +0 -0
- package/css/tokens.css +0 -0
- package/dist/canvas.js +0 -0
- package/dist/canvas.js.map +0 -0
- package/dist/charts.js +2 -2
- package/dist/charts.js.map +0 -0
- package/dist/chunk-3PHCPZHT.js +0 -0
- package/dist/chunk-3PHCPZHT.js.map +0 -0
- package/dist/chunk-6JSOVNID.js +0 -0
- package/dist/chunk-6JSOVNID.js.map +0 -0
- package/dist/chunk-CWPMLX67.js +0 -0
- package/dist/chunk-CWPMLX67.js.map +0 -0
- package/dist/{chunk-M2L4XW2U.js → chunk-FBGP5KN5.js} +33 -21
- package/dist/chunk-FBGP5KN5.js.map +7 -0
- package/dist/chunk-FL5KFNQH.js +0 -0
- package/dist/chunk-FL5KFNQH.js.map +0 -0
- package/dist/chunk-FOOS3T5L.js +0 -0
- package/dist/chunk-FOOS3T5L.js.map +0 -0
- package/dist/chunk-JYWURG5T.js +0 -0
- package/dist/chunk-JYWURG5T.js.map +0 -0
- package/dist/chunk-NLTOY57M.js +0 -0
- package/dist/chunk-NLTOY57M.js.map +0 -0
- package/dist/chunk-NNFTETK3.js +0 -0
- package/dist/chunk-NNFTETK3.js.map +0 -0
- package/dist/chunk-O5OHMWBB.js +0 -0
- package/dist/chunk-O5OHMWBB.js.map +0 -0
- package/dist/{chunk-IYMNDPWH.js → chunk-RQM5Z2MU.js} +2 -2
- package/dist/{chunk-IYMNDPWH.js.map → chunk-RQM5Z2MU.js.map} +0 -0
- package/dist/chunk-SN4LSOAR.js +0 -0
- package/dist/chunk-SN4LSOAR.js.map +0 -0
- package/dist/chunk-WVFGV5FT.js +0 -0
- package/dist/chunk-WVFGV5FT.js.map +0 -0
- package/dist/core.js +0 -0
- package/dist/core.js.map +0 -0
- package/dist/editor.js +0 -0
- package/dist/editor.js.map +0 -0
- package/dist/flexdesk.css +34 -0
- package/dist/flow.js +1 -1
- package/dist/flow.js.map +0 -0
- package/dist/host.js +28 -5
- package/dist/host.js.map +2 -2
- 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 +11 -3
- package/dist/tiles.js.map +2 -2
- package/dist/tokens.css +0 -0
- package/dist/widgets.js +101 -10
- package/dist/widgets.js.map +2 -2
- package/dist/wm.js +0 -0
- package/dist/wm.js.map +0 -0
- package/package.json +1 -1
- 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/charts/chart_types.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/plot_popout_window.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 +0 -0
- 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_tab_bar.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/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/host.js +2 -1
- package/src/host/photino_host.js +31 -3
- package/src/host/photino_host.md +56 -3
- package/src/host/pywebview_host.js +0 -0
- package/src/tiles/chart_tiles.js +14 -1
- package/src/tiles/config_schema.js +0 -0
- package/src/tiles/layout_persistence.js +0 -0
- package/src/tiles/tile_base.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 +0 -0
- package/src/tiling/entity_sources.js +0 -0
- package/src/tiling/keymap.js +0 -0
- 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_keys.js +0 -0
- package/src/tiling/shell.js +0 -0
- 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 +0 -0
- package/src/tiling/wm.js +0 -0
- package/src/tiling/zoom.js +0 -0
- package/src/ui/base/component_base.js +0 -0
- package/src/ui/base/controller_base.js +0 -0
- package/src/ui/base/page_base.js +0 -0
- package/src/ui/components/about_dialog.js +0 -0
- package/src/ui/components/action_dropdown.js +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/column_fit.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/context_menu.js +0 -0
- package/src/ui/components/data_table.js +32 -14
- package/src/ui/components/detail_header.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/gallery_picker.js +0 -0
- package/src/ui/components/inline_renamer.js +0 -0
- package/src/ui/components/managed_window.js +0 -0
- package/src/ui/components/modal.js +0 -0
- package/src/ui/components/notification_history.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/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/table_filter_chips.js +0 -0
- package/src/ui/components/table_state_store.js +0 -0
- package/src/ui/components/table_values.js +0 -0
- package/src/ui/components/toast.js +0 -0
- package/src/ui/components/tree_view.js +0 -0
- package/src/ui/components/window_taskbar.js +0 -0
- package/src/ui/controllers/panel_state_machine.js +0 -0
- package/src/ui/controllers/window_chrome_controller.js +106 -11
- package/src/ui/notification_center.js +0 -0
- package/src/ui/utils/auto_scrollbars.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/tooltip_service.js +0 -0
- package/dist/chunk-M2L4XW2U.js.map +0 -7
package/dist/wm.js
CHANGED
|
File without changes
|
package/dist/wm.js.map
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/canvas/index.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/charts/downsample.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/core/event_bus.js
CHANGED
|
File without changes
|
package/src/core/logging.js
CHANGED
|
File without changes
|
package/src/core/settings.js
CHANGED
|
File without changes
|
package/src/core/state_guard.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
|
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
|
package/src/host/host.js
CHANGED
|
@@ -19,7 +19,8 @@ export const HOST_CONTRACT = Object.freeze({
|
|
|
19
19
|
window: Object.freeze({
|
|
20
20
|
required: Object.freeze(['chrome', 'minimize', 'close', 'isMaximized', 'setMaximized',
|
|
21
21
|
'isFullscreen', 'toggleFullscreen', 'getBounds', 'setBounds']),
|
|
22
|
-
|
|
22
|
+
// `chrome()` answers 'native' or 'custom', or a promise of one.
|
|
23
|
+
optional: Object.freeze(['startNativeDrag', 'startNativeResize']),
|
|
23
24
|
}),
|
|
24
25
|
dialogs: Object.freeze({ required: Object.freeze(['saveFile']), optional: Object.freeze([]) }),
|
|
25
26
|
state: Object.freeze({ required: Object.freeze(['read', 'write']), optional: Object.freeze([]) }),
|
package/src/host/photino_host.js
CHANGED
|
@@ -99,6 +99,10 @@ export function createPhotinoTransport({ external, timeoutMs = 10000, onEvent =
|
|
|
99
99
|
* @param {number} [opts.timeoutMs=10000]
|
|
100
100
|
* @param {(name: string, value: any) => void} [opts.onEvent]
|
|
101
101
|
* @param {object} [opts.logger=console]
|
|
102
|
+
* @param {'auto'|'native'|'custom'} [opts.chrome='auto'] who draws the window's
|
|
103
|
+
* title bar and borders. `auto` asks the native side once (`host.describe`,
|
|
104
|
+
* its `frame` field), the first time somebody needs to know; `native`
|
|
105
|
+
* or `custom` fixes the answer without asking. See photino_host.md.
|
|
102
106
|
* @param {object|null} [opts.window] replace the window capability, or `null` to leave it out
|
|
103
107
|
* @param {object|null} [opts.dialogs] replace the dialogs capability, or `null` to leave it out
|
|
104
108
|
* @param {object|null} [opts.state] replace the state capability, or `null` to leave it out
|
|
@@ -110,6 +114,7 @@ export function createPhotinoHost(opts = {}) {
|
|
|
110
114
|
timeoutMs = 10000,
|
|
111
115
|
onEvent = null,
|
|
112
116
|
logger = console,
|
|
117
|
+
chrome = 'auto',
|
|
113
118
|
} = opts;
|
|
114
119
|
if (!isPhotinoWindow(external)) return NULL_HOST;
|
|
115
120
|
|
|
@@ -121,11 +126,32 @@ export function createPhotinoHost(opts = {}) {
|
|
|
121
126
|
};
|
|
122
127
|
const fire = (cap, op, ...args) => { void soft(null, cap, op, ...args); };
|
|
123
128
|
|
|
129
|
+
// Who draws the frame. Asked once and remembered: a string once known, the
|
|
130
|
+
// pending question before that. A native side that predates `frame`, or
|
|
131
|
+
// doesn't answer within 3 s, has native chrome.
|
|
132
|
+
let frame = chrome === 'custom' || chrome === 'native' ? chrome : null;
|
|
133
|
+
let asking = null;
|
|
134
|
+
const askFrame = () => {
|
|
135
|
+
if (frame) return frame;
|
|
136
|
+
asking ??= request('host', 'describe', [], { wait: 3000 })
|
|
137
|
+
.then((d) => (d && d.frame === 'custom' ? 'custom' : 'native'), () => 'native')
|
|
138
|
+
.then((f) => { frame = f; return f; });
|
|
139
|
+
return asking;
|
|
140
|
+
};
|
|
141
|
+
// A drag or resize handed to the OS: the reply comes when the mouse is
|
|
142
|
+
// released, however long that takes. Refused (a native side without the op,
|
|
143
|
+
// or a window with native chrome) means "not handled": the caller does it in JS.
|
|
144
|
+
const gesture = async (op, ...args) => {
|
|
145
|
+
try { await request('window', op, args, { wait: 0 }); return true; }
|
|
146
|
+
catch (e) { return false; }
|
|
147
|
+
};
|
|
148
|
+
|
|
124
149
|
const defaults = {
|
|
125
150
|
window: {
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
151
|
+
// 'native' (the OS draws the frame: FlexDesk's own buttons would be
|
|
152
|
+
// a second set) or 'custom' (the native side made the window
|
|
153
|
+
// frameless). A promise until the native side has said which.
|
|
154
|
+
chrome: () => askFrame(),
|
|
129
155
|
minimize: () => { fire('window', 'minimize'); },
|
|
130
156
|
close: () => { fire('window', 'close'); },
|
|
131
157
|
isMaximized: async () => (await soft(false, 'window', 'isMaximized')) === true,
|
|
@@ -137,6 +163,8 @@ export function createPhotinoHost(opts = {}) {
|
|
|
137
163
|
if (!b || typeof b !== 'object') return null;
|
|
138
164
|
return { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
139
165
|
},
|
|
166
|
+
startNativeDrag: () => gesture('startDrag'),
|
|
167
|
+
startNativeResize: (edge) => gesture('startResize', String(edge || '')),
|
|
140
168
|
// Not async, like pywebview's: a rAF hot path during a drag; nobody awaits.
|
|
141
169
|
setBounds({ x = null, y = null, width = null, height = null } = {}) {
|
|
142
170
|
fire('window', 'setBounds', { x, y, width, height });
|
package/src/host/photino_host.md
CHANGED
|
@@ -28,6 +28,7 @@ host → page {"event": "window.maximizedChanged", "value": true}
|
|
|
28
28
|
|
|
29
29
|
| `cap.op` | `args` | reply `value` | Photino |
|
|
30
30
|
|---|---|---|---|
|
|
31
|
+
| `host.describe` | `[]` | `{frame: "native" \| "custom", …}` | asked once, for `chrome()` (see below) |
|
|
31
32
|
| `window.minimize` | `[]` | anything | `Minimized = true` |
|
|
32
33
|
| `window.close` | `[]` | anything | `Close()` |
|
|
33
34
|
| `window.isMaximized` | `[]` | `bool` | `Maximized` |
|
|
@@ -36,13 +37,65 @@ host → page {"event": "window.maximizedChanged", "value": true}
|
|
|
36
37
|
| `window.toggleFullscreen` | `[]` | `bool`, the state now | `FullScreen = !FullScreen` |
|
|
37
38
|
| `window.getBounds` | `[]` | `{x, y, width, height}` | `Location`, `Size` |
|
|
38
39
|
| `window.setBounds` | `[{x, y, width, height}]`, any of them `null` = leave alone | anything | `Location`, `Size` |
|
|
40
|
+
| `window.startDrag` | `[]` | anything, **after mouse-up** | custom chrome only: the OS move loop (below) |
|
|
41
|
+
| `window.startResize` | `[edge]`: `n` `s` `e` `w` `ne` `nw` `se` `sw` | anything, **after mouse-up** | custom chrome only: the OS size loop (below) |
|
|
39
42
|
| `dialogs.saveFile` | `[{data, filename, kind}]` | see below | `ShowSaveFile(...)` + write |
|
|
40
43
|
| `state.read` | `[key]` | the stored value, or `null` | a file per key |
|
|
41
44
|
| `state.write` | `[key, value]` | `true` | a file per key |
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
## Native or custom chrome
|
|
47
|
+
|
|
48
|
+
A FlexDesk app runs in one of three ways, and the page needs no switch for it:
|
|
49
|
+
|
|
50
|
+
| | title bar and borders | `chrome()` | FlexDesk draws |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| a browser tab | the browser's | (no Photino host) | nothing |
|
|
53
|
+
| a Photino window, native frame | the OS's | `'native'` | nothing |
|
|
54
|
+
| a Photino window, custom frame | none: the page's top bar | `'custom'` | buttons, resize handles, drag |
|
|
55
|
+
|
|
56
|
+
`window.chrome()` asks the native side once, with `host.describe`, the first
|
|
57
|
+
time it's called (that is `WindowChromeController.initialize()`). It resolves
|
|
58
|
+
`'custom'` when the reply's `frame` is `"custom"`, and `'native'` for anything
|
|
59
|
+
else: no `frame` field, an `ok: false`, no answer within 3 s. An app that knows
|
|
60
|
+
already can say so: `createPhotinoHost({ chrome: 'custom' })` or `'native'`
|
|
61
|
+
answers without asking.
|
|
62
|
+
|
|
63
|
+
**What the native side does for a custom frame (Windows).** Don't use
|
|
64
|
+
Photino's `SetChromeless(true)`: that makes a bare popup window, with no
|
|
65
|
+
snapping, no Win+arrows, no shadow and square corners, and Photino then reports
|
|
66
|
+
it as fullscreen. Keep the normal window and stop drawing its frame:
|
|
67
|
+
|
|
68
|
+
1. After the window exists, add `WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX |
|
|
69
|
+
WS_MAXIMIZEBOX | WS_SYSMENU` to its style, and subclass it
|
|
70
|
+
(`SetWindowSubclass`). These styles are what make Windows treat it as a
|
|
71
|
+
normal window: Aero Snap, Win+arrows, the shadow, the rounded corners on
|
|
72
|
+
Windows 11.
|
|
73
|
+
2. In the subclass, answer `WM_NCCALCSIZE` (with `wParam` true) with `0`, so
|
|
74
|
+
the whole window is client area and nothing of the frame is drawn. While
|
|
75
|
+
maximised (`IsZoomed`), set the proposed rect to the monitor's work area
|
|
76
|
+
first: a maximised window is larger than the monitor by the frame's
|
|
77
|
+
thickness, and the taskbar must stay visible.
|
|
78
|
+
3. Answer `WM_NCACTIVATE` with `DefSubclassProc(h, msg, wParam, -1)`, so
|
|
79
|
+
Windows repaints no caption on activation.
|
|
80
|
+
4. `DwmExtendFrameIntoClientArea` with a top margin of 1 keeps DWM drawing
|
|
81
|
+
the shadow. `DWMWA_WINDOW_CORNER_PREFERENCE = DWMWCP_ROUND` asks for the
|
|
82
|
+
rounded corners.
|
|
83
|
+
5. Call `SetWindowPos(..., SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |
|
|
84
|
+
SWP_NOZORDER)` to apply it.
|
|
85
|
+
|
|
86
|
+
The WebView covers the whole window, so the window never sees the mouse. That
|
|
87
|
+
is what `startDrag` and `startResize` are for. The page asks when the mouse
|
|
88
|
+
goes down on its top bar or on a resize handle. On the window's thread, call
|
|
89
|
+
`ReleaseCapture()`, then `SendMessage(hwnd, WM_NCLBUTTONDOWN, hit, cursor)`
|
|
90
|
+
with `HTCAPTION` for a drag, or `HTTOP` / `HTBOTTOM` / `HTLEFT` / `HTRIGHT` /
|
|
91
|
+
`HTTOPLEFT` / … for the edge. Windows then runs its own move or size loop,
|
|
92
|
+
exactly as for a click on a real caption or border, and `SendMessage` returns
|
|
93
|
+
when the mouse is released. Reply then. The page waits without a timeout, and a
|
|
94
|
+
reply of `ok: false` makes it move or resize the window from JS instead.
|
|
95
|
+
|
|
96
|
+
`describe`'s reply says `frame: "custom"` only when all of this was done. A
|
|
97
|
+
Photino window on Linux or macOS keeps its native frame and says `"native"`, or
|
|
98
|
+
leaves `frame` out.
|
|
46
99
|
|
|
47
100
|
**`dialogs.saveFile`.** `kind` is the file type the caller is saving, `'csv'`
|
|
48
101
|
or `'png'` today. `data` is the file's text for `csv` and the file's bytes,
|
|
File without changes
|
package/src/tiles/chart_tiles.js
CHANGED
|
@@ -528,9 +528,22 @@ export class ChartTile extends TileBase {
|
|
|
528
528
|
try {
|
|
529
529
|
const P = await ensurePlotly();
|
|
530
530
|
const uri = await P.toImage(this._plotEl, { format: 'png', scale: 2 });
|
|
531
|
+
const filename = `${slug(this.config.title || this.constructor.TITLE)}.png`;
|
|
532
|
+
// The native Save dialog when the host has one (a desktop window).
|
|
533
|
+
// `saveFile` answers null when there is no dialog behind it, which
|
|
534
|
+
// means "download it yourself", not a failed save.
|
|
535
|
+
const dialogs = this.host?.dialogs;
|
|
536
|
+
const result = dialogs
|
|
537
|
+
? await dialogs.saveFile({ data: uri.split(',')[1], filename, kind: 'png' })
|
|
538
|
+
: null;
|
|
539
|
+
if (result) {
|
|
540
|
+
if (result.ok) this._notify('Download', `Chart saved to ${result.path}`, 'success');
|
|
541
|
+
else if (!result.cancelled) this._notify('Download failed', result.error || 'The image could not be saved', 'error');
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
531
544
|
const a = document.createElement('a');
|
|
532
545
|
a.href = uri;
|
|
533
|
-
a.download =
|
|
546
|
+
a.download = filename;
|
|
534
547
|
a.click();
|
|
535
548
|
} catch (err) {
|
|
536
549
|
this._notify('Download failed', `The image could not be made: ${err?.message || err}`, 'error');
|
|
File without changes
|
|
File without changes
|
package/src/tiles/tile_base.js
CHANGED
|
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
|
File without changes
|
|
File without changes
|
package/src/tiling/keymap.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/tiling/nav_panel.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/tiling/panel_keys.js
CHANGED
|
File without changes
|
package/src/tiling/shell.js
CHANGED
|
File without changes
|
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
|
File without changes
|
package/src/tiling/wm.js
CHANGED
|
File without changes
|
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
|
|
@@ -327,7 +327,8 @@ const AUTOSIZE_MAX_PX = 520;
|
|
|
327
327
|
* @property {Function} [onRowClick] - Row click handler: (rowIdx, row, ev) => void. Receives the original (unfiltered) row index.
|
|
328
328
|
* @property {Function} [onRowContextMenu] - Row right-click handler: (rowIdx, row, ev) => void. Fires before the default context menu; call ev.preventDefault() to suppress the default.
|
|
329
329
|
* @property {Function} [onCellContextMenu] - Cell right-click handler: (colIdx, rowIdx, value, td, ev) => void. Fires before onRowContextMenu; same suppression semantics.
|
|
330
|
-
* @property {boolean} [showExportButton=false] - Adds a "CSV" download button to the pagination strip that invokes `downloadCSV()`.
|
|
330
|
+
* @property {boolean} [showExportButton=false] - Adds a "CSV" download button to the pagination strip that invokes `downloadCSV()`. The strip is drawn for it even when the table fits on one page (0.14.1).
|
|
331
|
+
* @property {string|function(DataTable): string} [exportFilename='data.csv'] - The file the export button saves (0.14.1); in a desktop host, the name the Save dialog suggests.
|
|
331
332
|
* @property {Object} [host] - A Host (see ui/js/host/host.js). Its `dialogs` capability backs `downloadCSV()`'s native save dialog; without it CSV export falls back to a Blob download.
|
|
332
333
|
* @property {Object} [stateStore] - `{ ready(), get(key), set(key, state) }` — REQUIRED when `persistKey` is set. Built by `createTableStateStore()`.
|
|
333
334
|
* @property {'container'|'content'} [columnFit='container'] - How the automatic fit pass sizes a column the user has NOT dragged. 'container' squeezes every column into the wrap: a 360px cap, a more generous one for the first column, and water-fill-shrink toward the 40px floor when they do not fit. 'content' sizes each column to its own content through the same `_autoWidth` a grip double-click uses (520px cap) and lets the body scroll sideways rather than squeezing. A pinned column is honoured verbatim under both.
|
|
@@ -466,6 +467,7 @@ export class DataTable {
|
|
|
466
467
|
onRowContextMenu: null,
|
|
467
468
|
onCellContextMenu: null,
|
|
468
469
|
showExportButton: false,
|
|
470
|
+
exportFilename: null,
|
|
469
471
|
// The host port. TOP LEVEL, deliberately — NOT inside `services`,
|
|
470
472
|
// which most call sites never pass. Supplies the native save
|
|
471
473
|
// dialog for downloadCSV(); absent => Blob fallback.
|
|
@@ -2623,7 +2625,7 @@ export class DataTable {
|
|
|
2623
2625
|
// Don't render the strip when there's only one page — the row
|
|
2624
2626
|
// count is uninformative (the table itself shows every row) and
|
|
2625
2627
|
// the nav buttons would all be disabled.
|
|
2626
|
-
if (totalPages <= 1 && !isFiltered) return
|
|
2628
|
+
if (totalPages <= 1 && !isFiltered) return this._createExportOnlyStrip();
|
|
2627
2629
|
const currentPage = Math.floor(offset / pageSize) + 1;
|
|
2628
2630
|
const hasPrev = offset > 0;
|
|
2629
2631
|
const hasNext = offset + pageSize < effectiveTotal;
|
|
@@ -2719,16 +2721,35 @@ export class DataTable {
|
|
|
2719
2721
|
// CSV export button (opt-in). Re-uses the existing downloadCSV
|
|
2720
2722
|
// method which already handles the host save dialog + browser
|
|
2721
2723
|
// fallback.
|
|
2722
|
-
if (this.config.showExportButton)
|
|
2723
|
-
const exportBtn = this._createPaginationBtn(
|
|
2724
|
-
'download', 'Download visible rows as CSV', false,
|
|
2725
|
-
() => this.downloadCSV('data.csv'));
|
|
2726
|
-
el.appendChild(exportBtn);
|
|
2727
|
-
}
|
|
2724
|
+
if (this.config.showExportButton) el.appendChild(this._createExportBtn());
|
|
2728
2725
|
|
|
2729
2726
|
return el;
|
|
2730
2727
|
}
|
|
2731
2728
|
|
|
2729
|
+
/** The export button: downloadCSV() under the configured file name. */
|
|
2730
|
+
_createExportBtn() {
|
|
2731
|
+
const name = this.config.exportFilename;
|
|
2732
|
+
const filename = (typeof name === 'function' ? name(this) : name) || 'data.csv';
|
|
2733
|
+
return this._createPaginationBtn('download', 'Download visible rows as CSV', false,
|
|
2734
|
+
() => this.downloadCSV(filename));
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
/**
|
|
2738
|
+
* The strip on a table with one page: nothing, as always, unless the export
|
|
2739
|
+
* button needs somewhere to be. Then the button alone, with no page
|
|
2740
|
+
* buttons (0.14.1).
|
|
2741
|
+
*/
|
|
2742
|
+
_createExportOnlyStrip() {
|
|
2743
|
+
if (!this.config.showExportButton) return null;
|
|
2744
|
+
const el = document.createElement('div');
|
|
2745
|
+
el.className = 'twm-pagination-controls twm-pagination-controls--export-only';
|
|
2746
|
+
el.style.cssText = 'display:flex; align-items:center; gap:6px; padding:2px 8px; border-bottom:1px solid #2a2a2a; font-size:11px;';
|
|
2747
|
+
const spacer = document.createElement('div');
|
|
2748
|
+
spacer.style.flex = '1';
|
|
2749
|
+
el.append(spacer, this._createExportBtn());
|
|
2750
|
+
return el;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2732
2753
|
_createPaginationBtn(icon, tooltip, disabled, onClick, textLabel = null) {
|
|
2733
2754
|
const btn = document.createElement('button');
|
|
2734
2755
|
btn.type = 'button';
|
|
@@ -4738,8 +4759,8 @@ export class DataTable {
|
|
|
4738
4759
|
const pn = this._pageNav({ offset, held: drawn, total, hasMore: hasMoreKnown, pageSize });
|
|
4739
4760
|
const { hasPrev, hasNext, page } = pn;
|
|
4740
4761
|
const totalPages = pn.pages;
|
|
4741
|
-
if (known && totalPages <= 1 && !isFiltered && !hasPrev) return
|
|
4742
|
-
if (!known && !hasPrev && !hasNext) return
|
|
4762
|
+
if (known && totalPages <= 1 && !isFiltered && !hasPrev) return this._createExportOnlyStrip();
|
|
4763
|
+
if (!known && !hasPrev && !hasNext) return this._createExportOnlyStrip();
|
|
4743
4764
|
const maxJump = Number.isFinite(c.maxJumpOffset) ? c.maxJumpOffset : null;
|
|
4744
4765
|
|
|
4745
4766
|
const go = (newOffset) => {
|
|
@@ -4817,10 +4838,7 @@ export class DataTable {
|
|
|
4817
4838
|
jumpBtn.dataset.pager = 'go';
|
|
4818
4839
|
|
|
4819
4840
|
el.append(info, spacer, firstBtn, prevBtn, pageInfo, nextBtn, lastBtn, jumpInput, jumpBtn);
|
|
4820
|
-
if (c.showExportButton)
|
|
4821
|
-
el.appendChild(this._createPaginationBtn('download', 'Download visible rows as CSV', false,
|
|
4822
|
-
() => this.downloadCSV('data.csv')));
|
|
4823
|
-
}
|
|
4841
|
+
if (c.showExportButton) el.appendChild(this._createExportBtn());
|
|
4824
4842
|
return el;
|
|
4825
4843
|
}
|
|
4826
4844
|
|
|
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
|