@ssebastien/tcmd 0.1.4 → 0.1.7
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/README.md +169 -58
- package/desktop/dist/index.html +13 -0
- package/desktop/dist/main.js +20298 -0
- package/desktop/dist/style.css +1515 -0
- package/desktop/frontend/index.html +13 -0
- package/package.json +17 -5
- package/public/fonts/OFL.txt +93 -0
- package/public/licenses/LUCIDE.txt +43 -0
- package/public/main.js +5066 -1888
- package/public/style.css +660 -46
- package/scripts/build-desktop.js +31 -0
- package/src/js/tcmd/components/App.html +56 -6
- package/src/js/tcmd/components/App.js +233 -58
- package/src/js/tcmd/components/App.xml +27 -22
- package/src/js/tcmd/components/BookmarkList.html +4 -5
- package/src/js/tcmd/components/BookmarkList.js +14 -40
- package/src/js/tcmd/components/BookmarkList.xml +1 -2
- package/src/js/tcmd/components/BookmarkRow.html +2 -1
- package/src/js/tcmd/components/BookmarkRow.js +14 -9
- package/src/js/tcmd/components/BookmarkRow.xml +1 -1
- package/src/js/tcmd/components/CommandArgRow.html +18 -7
- package/src/js/tcmd/components/CommandArgRow.js +100 -44
- package/src/js/tcmd/components/CommandArgRow.xml +39 -15
- package/src/js/tcmd/components/CommandBar.html +1 -1
- package/src/js/tcmd/components/CommandBar.js +31 -25
- package/src/js/tcmd/components/CommandBar.xml +2 -2
- package/src/js/tcmd/components/CommandItem.js +24 -25
- package/src/js/tcmd/components/CommandItem.xml +2 -2
- package/src/js/tcmd/components/CommandPanel.html +13 -13
- package/src/js/tcmd/components/CommandPanel.js +57 -33
- package/src/js/tcmd/components/CommandPanel.xml +30 -20
- package/src/js/tcmd/components/Confirm.html +2 -2
- package/src/js/tcmd/components/EntryDetail.js +21 -7
- package/src/js/tcmd/components/FilePicker.html +10 -0
- package/src/js/tcmd/components/FilePicker.js +43 -0
- package/src/js/tcmd/components/FilePicker.xml +13 -0
- package/src/js/tcmd/components/FilePickerRow.html +6 -0
- package/src/js/tcmd/components/FilePickerRow.js +27 -0
- package/src/js/tcmd/components/FilePickerRow.xml +10 -0
- package/src/js/tcmd/components/FilePreview.html +1 -2
- package/src/js/tcmd/components/FilePreview.js +40 -4
- package/src/js/tcmd/components/FilePreview.xml +17 -10
- package/src/js/tcmd/components/FileRow.html +1 -0
- package/src/js/tcmd/components/FileRow.js +60 -9
- package/src/js/tcmd/components/Frame.js +57 -31
- package/src/js/tcmd/components/Help.html +12 -4
- package/src/js/tcmd/components/Help.js +198 -47
- package/src/js/tcmd/components/Help.xml +13 -4
- package/src/js/tcmd/components/HelpRow.html +8 -0
- package/src/js/tcmd/components/HelpRow.xml +7 -0
- package/src/js/tcmd/components/OverlayList.html +8 -0
- package/src/js/tcmd/components/OverlayList.js +69 -0
- package/src/js/tcmd/components/OverlayList.xml +13 -0
- package/src/js/tcmd/components/OverlayRow.html +5 -0
- package/src/js/tcmd/components/OverlayRow.js +47 -0
- package/src/js/tcmd/components/OverlayRow.xml +9 -0
- package/src/js/tcmd/components/format.js +4 -23
- package/src/js/tcmd/components/icons.js +137 -0
- package/src/js/tcmd/components/index.js +8 -3
- package/src/js/tcmd/components/rows.js +149 -25
- package/src/js/tcmd/core/base64.js +64 -0
- package/src/js/tcmd/core/bookmarks.js +45 -8
- package/src/js/tcmd/core/commands.js +612 -136
- package/src/js/tcmd/core/concurrency.js +59 -0
- package/src/js/tcmd/core/decompress.js +117 -0
- package/src/js/tcmd/core/directory.js +81 -5
- package/src/js/tcmd/core/format.js +36 -0
- package/src/js/tcmd/core/fs.js +410 -53
- package/src/js/tcmd/core/journal.js +93 -0
- package/src/js/tcmd/core/keymap.js +39 -3
- package/src/js/tcmd/core/operations.js +735 -63
- package/src/js/tcmd/core/os.js +29 -3
- package/src/js/tcmd/core/pane.js +4 -2
- package/src/js/tcmd/core/panel.js +179 -93
- package/src/js/tcmd/core/path.js +30 -3
- package/src/js/tcmd/core/permissions.js +107 -0
- package/src/js/tcmd/core/picker.js +156 -0
- package/src/js/tcmd/core/preview.js +102 -0
- package/src/js/tcmd/core/session.js +286 -116
- package/src/js/tcmd/core/watch.js +153 -0
- package/src/js/tcmd/desktop/backend/api.js +165 -0
- package/src/js/tcmd/desktop/backend/fs.js +626 -0
- package/src/js/tcmd/desktop/backend/main.ts +9 -0
- package/src/js/tcmd/desktop/bookmarks.js +23 -0
- package/src/js/tcmd/desktop/fs.js +91 -0
- package/src/js/tcmd/desktop/main.js +194 -0
- package/src/js/tcmd/desktop/os.js +33 -0
- package/src/js/tcmd/tui/app.js +111 -74
- package/src/js/tcmd/tui/catalogue.js +232 -23
- package/src/js/tcmd/tui/index.js +18 -4
- package/src/js/tcmd/tui/opentui.js +33 -0
- package/src/js/tcmd/tui/syntax.js +91 -0
- package/src/js/tcmd/tui/theme.js +27 -2
- package/src/js/tcmd/vendor/bzip2.js +276 -0
- package/src/js/tcmd/web/app.js +114 -56
- package/src/js/tcmd/web/catalogue.js +41 -4
- package/src/js/tcmd/web/effects.js +94 -14
- package/src/js/tcmd/web/header.html +1 -1
- package/src/js/tcmd/web/http.js +13 -0
- package/src/js/tcmd/web/index.js +2 -2
- package/src/js/tcmd/web/opfs.js +297 -30
- package/src/js/tcmd/web/seed.js +1 -1
- package/src/js/tcmd/web/server.js +118 -17
- package/src/js/tcmd/web/theme.js +3 -0
- package/tinyjs.json +16 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Project: TinyCommand
|
|
2
|
+
// License: BSD
|
|
3
|
+
// Build the self-contained webview bundle. tinyjs invokes this hook on
|
|
4
|
+
// `tinyjs build`; `desktop:dev` also runs it before opening the native window.
|
|
5
|
+
|
|
6
|
+
import { copyFile, mkdir } from "node:fs/promises"
|
|
7
|
+
import { fileURLToPath } from "node:url"
|
|
8
|
+
|
|
9
|
+
const root = new URL("../", import.meta.url)
|
|
10
|
+
const output = new URL("../desktop/dist/", import.meta.url)
|
|
11
|
+
await mkdir(output, { recursive: true })
|
|
12
|
+
|
|
13
|
+
const result = await Bun.build({
|
|
14
|
+
entrypoints: [fileURLToPath(new URL("../src/js/tcmd/desktop/main.js", import.meta.url))],
|
|
15
|
+
outdir: fileURLToPath(output),
|
|
16
|
+
target: "browser",
|
|
17
|
+
naming: "main.js",
|
|
18
|
+
})
|
|
19
|
+
if (!result.success) throw new AggregateError(result.logs, "Could not build the TinyCommand desktop frontend")
|
|
20
|
+
|
|
21
|
+
await copyFile(new URL("../desktop/frontend/index.html", import.meta.url), new URL("index.html", output))
|
|
22
|
+
await copyFile(new URL("../public/style.css", import.meta.url), new URL("style.css", output))
|
|
23
|
+
await mkdir(new URL("../desktop/dist/fonts/", import.meta.url), { recursive: true })
|
|
24
|
+
await copyFile(
|
|
25
|
+
new URL("../public/fonts/M+1CodeNerdFontMono-Regular.ttf", import.meta.url),
|
|
26
|
+
new URL("../desktop/dist/fonts/M+1CodeNerdFontMono-Regular.ttf", import.meta.url),
|
|
27
|
+
)
|
|
28
|
+
await copyFile(new URL("../public/fonts/OFL.txt", import.meta.url), new URL("../desktop/dist/fonts/OFL.txt", import.meta.url))
|
|
29
|
+
await mkdir(new URL("../desktop/dist/licenses/", import.meta.url), { recursive: true })
|
|
30
|
+
await copyFile(new URL("../public/licenses/LUCIDE.txt", import.meta.url), new URL("../desktop/dist/licenses/LUCIDE.txt", import.meta.url))
|
|
31
|
+
console.log(`TinyCommand desktop frontend built in ${fileURLToPath(output)}`)
|
|
@@ -1,5 +1,57 @@
|
|
|
1
|
-
<div class="app" ref="root" tabindex="0" on:keydown="keys">
|
|
2
|
-
<
|
|
1
|
+
<div class="app" ref="root" tabindex="0" out:class="appModeClass" on:keydown="keys">
|
|
2
|
+
<svg class="lucide-defs" aria-hidden="true" focusable="false">
|
|
3
|
+
<defs>
|
|
4
|
+
<symbol id="lucide-folder-plus" viewBox="0 0 24 24"><path d="M12 10v6"/><path d="M9 13h6"/><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></symbol>
|
|
5
|
+
<symbol id="lucide-arrow-left" viewBox="0 0 24 24"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></symbol>
|
|
6
|
+
<symbol id="lucide-arrow-right" viewBox="0 0 24 24"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></symbol>
|
|
7
|
+
<symbol id="lucide-arrow-up" viewBox="0 0 24 24"><path d="m5 12 7-7 7 7"/><path d="M12 19V5"/></symbol>
|
|
8
|
+
<symbol id="lucide-refresh-cw" viewBox="0 0 24 24"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/></symbol>
|
|
9
|
+
<symbol id="lucide-columns-2" viewBox="0 0 24 24"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M12 3v18"/></symbol>
|
|
10
|
+
<symbol id="lucide-panel-right" viewBox="0 0 24 24"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/></symbol>
|
|
11
|
+
<symbol id="lucide-terminal" viewBox="0 0 24 24"><path d="M12 19h8"/><path d="m4 17 6-6-6-6"/></symbol>
|
|
12
|
+
<symbol id="lucide-circle-question-mark" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></symbol>
|
|
13
|
+
<symbol id="lucide-chevron-right" viewBox="0 0 24 24"><path d="m9 18 6-6-6-6"/></symbol>
|
|
14
|
+
<symbol id="lucide-arrow-down" viewBox="0 0 24 24"><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></symbol>
|
|
15
|
+
<symbol id="lucide-check" viewBox="0 0 24 24"><path d="M20 6 9 17l-5-5"/></symbol>
|
|
16
|
+
<symbol id="lucide-bookmark" viewBox="0 0 24 24"><path d="M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z"/></symbol>
|
|
17
|
+
<symbol id="lucide-maximize" viewBox="0 0 24 24"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></symbol>
|
|
18
|
+
<symbol id="lucide-trash" viewBox="0 0 24 24"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></symbol>
|
|
19
|
+
<symbol id="lucide-power" viewBox="0 0 24 24"><path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.77.04"/></symbol>
|
|
20
|
+
<symbol id="lucide-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></symbol>
|
|
21
|
+
<symbol id="lucide-folder" viewBox="0 0 24 24"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></symbol>
|
|
22
|
+
<symbol id="lucide-link-2" viewBox="0 0 24 24"><path d="M9 17H7A5 5 0 0 1 7 7h2"/><path d="M15 7h2a5 5 0 1 1 0 10h-2"/><line x1="8" x2="16" y1="12" y2="12"/></symbol>
|
|
23
|
+
<symbol id="lucide-cpu" viewBox="0 0 24 24"><path d="M12 20v2"/><path d="M12 2v2"/><path d="M17 20v2"/><path d="M17 2v2"/><path d="M2 12h2"/><path d="M2 17h2"/><path d="M2 7h2"/><path d="M20 12h2"/><path d="M20 17h2"/><path d="M20 7h2"/><path d="M7 20v2"/><path d="M7 2v2"/><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="8" y="8" width="8" height="8" rx="1"/></symbol>
|
|
24
|
+
<symbol id="lucide-file" viewBox="0 0 24 24"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/></symbol>
|
|
25
|
+
<symbol id="lucide-file-code" viewBox="0 0 24 24"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="m10 13-2 2 2 2"/><path d="m14 13 2 2-2 2"/></symbol>
|
|
26
|
+
<symbol id="lucide-file-text" viewBox="0 0 24 24"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></symbol>
|
|
27
|
+
<symbol id="lucide-file-image" viewBox="0 0 24 24"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><circle cx="10" cy="12" r="2"/><path d="m20 17-1.296-1.296a2.41 2.41 0 0 0-3.408 0L9 22"/></symbol>
|
|
28
|
+
<symbol id="lucide-audio-lines" viewBox="0 0 24 24"><path d="M2 10v3"/><path d="M6 6v11"/><path d="M10 3v18"/><path d="M14 8v7"/><path d="M18 5v13"/><path d="M22 10v3"/></symbol>
|
|
29
|
+
<symbol id="lucide-video" viewBox="0 0 24 24"><path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5"/><rect x="2" y="6" width="14" height="12" rx="2"/></symbol>
|
|
30
|
+
<symbol id="lucide-file-archive" viewBox="0 0 24 24"><path d="M13.659 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v11.5"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M8 12v-1"/><path d="M8 18v-2"/><path d="M8 7V6"/><circle cx="8" cy="20" r="2"/></symbol>
|
|
31
|
+
<symbol id="lucide-database" viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5V19A9 3 0 0 0 21 19V5"/><path d="M3 12A9 3 0 0 0 21 12"/></symbol>
|
|
32
|
+
<symbol id="lucide-settings" viewBox="0 0 24 24"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/><circle cx="12" cy="12" r="3"/></symbol>
|
|
33
|
+
</defs>
|
|
34
|
+
</svg>
|
|
35
|
+
<div when="!desktopMode" out="headerView|Header"></div>
|
|
36
|
+
<div class="desktop-toolbar" when="desktopMode" role="toolbar" aria-label="File browser">
|
|
37
|
+
<div class="desktop-toolbar-group">
|
|
38
|
+
<button class="desktop-toolbar-button is-primary" type="button" title="Open a folder" aria-label="Open a folder" on:click="!toolbar:open-folder"><svg class="toolbar-icon ui-icon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-folder-plus"></use></svg><span>Open folder</span></button>
|
|
39
|
+
<button class="desktop-toolbar-button" type="button" title="Go back" aria-label="Go back" on:click="!toolbar:back"><svg class="toolbar-icon ui-icon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-arrow-left"></use></svg><span>Back</span></button>
|
|
40
|
+
<button class="desktop-toolbar-button" type="button" title="Go forward" aria-label="Go forward" on:click="!toolbar:forward"><svg class="toolbar-icon ui-icon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-arrow-right"></use></svg><span>Forward</span></button>
|
|
41
|
+
<button class="desktop-toolbar-button" type="button" title="Go to the parent folder" aria-label="Go to the parent folder" on:click="!toolbar:up"><svg class="toolbar-icon ui-icon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-arrow-up"></use></svg><span>Up</span></button>
|
|
42
|
+
<button class="desktop-toolbar-button" type="button" title="Refresh the active folder" aria-label="Refresh the active folder" on:click="!toolbar:refresh"><svg class="toolbar-icon ui-icon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-refresh-cw"></use></svg><span>Refresh</span></button>
|
|
43
|
+
</div>
|
|
44
|
+
<span class="desktop-toolbar-separator" aria-hidden="true"></span>
|
|
45
|
+
<div class="desktop-toolbar-group">
|
|
46
|
+
<button class="desktop-toolbar-button" type="button" title="Switch between Explorer and Commander" aria-label="Switch view mode" on:click="!toolbar:toggle-mode"><svg class="toolbar-icon ui-icon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-columns-2"></use></svg><span out="desktopViewLabel"></span></button>
|
|
47
|
+
<button class="desktop-toolbar-button" type="button" title="Toggle the preview pane" aria-label="Toggle the preview pane" on:click="!toolbar:toggle-preview"><svg class="toolbar-icon ui-icon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-panel-right"></use></svg><span out="desktopPreviewLabel"></span></button>
|
|
48
|
+
</div>
|
|
49
|
+
<span class="desktop-toolbar-spacer"></span>
|
|
50
|
+
<div class="desktop-toolbar-group">
|
|
51
|
+
<button class="desktop-toolbar-button" type="button" title="Open the command panel" aria-label="Open the command panel" on:click="!toolbar:commands"><svg class="toolbar-icon ui-icon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-terminal"></use></svg><span>Commands</span><svg class="toolbar-chevron ui-icon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-chevron-right"></use></svg></button>
|
|
52
|
+
<button class="desktop-toolbar-button" type="button" title="Show keyboard shortcuts" aria-label="Show keyboard shortcuts" on:click="!toolbar:help"><svg class="toolbar-icon ui-icon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-circle-question-mark"></use></svg><span>Help</span></button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
3
55
|
<section class="resize-hint" when="tooSmall">
|
|
4
56
|
<h2 out="minSizeTitle"></h2>
|
|
5
57
|
<p out="minSizeHint"></p>
|
|
@@ -12,7 +64,6 @@
|
|
|
12
64
|
</div>
|
|
13
65
|
<div data-slot="list" out="parentListView|FileList"></div>
|
|
14
66
|
<div out="parentDetailView|EntryDetail"></div>
|
|
15
|
-
<div out="parentSummaryView|Summary"></div>
|
|
16
67
|
</section>
|
|
17
68
|
<section class="pane pane-left" when="showLeftPane" out:class="leftPaneClass" on:click="leftId!pane:activate">
|
|
18
69
|
<div class="pane-header">
|
|
@@ -21,7 +72,6 @@
|
|
|
21
72
|
</div>
|
|
22
73
|
<div data-slot="list" out="leftListView|FileList"></div>
|
|
23
74
|
<div out="leftDetailView|EntryDetail"></div>
|
|
24
|
-
<div out="leftSummaryView|Summary"></div>
|
|
25
75
|
</section>
|
|
26
76
|
<section class="pane pane-right" when="showRightPane" out:class="rightPaneClass" on:click="rightId!pane:activate">
|
|
27
77
|
<div class="pane-header">
|
|
@@ -30,15 +80,15 @@
|
|
|
30
80
|
</div>
|
|
31
81
|
<div data-slot="list" out="rightListView|FileList"></div>
|
|
32
82
|
<div out="rightDetailView|EntryDetail"></div>
|
|
33
|
-
<div out="rightSummaryView|Summary"></div>
|
|
34
83
|
</section>
|
|
35
84
|
<div data-slot="inspector" when="showInspectorInline" out="previewView|FilePreview"></div>
|
|
36
85
|
<div class="preview-max" when="showPreviewFull" out="previewView|FilePreview"></div>
|
|
37
86
|
</main>
|
|
38
87
|
<div out="commandBarView|CommandBar"></div>
|
|
39
|
-
<div class="viewer" when="showViewer" role="dialog" aria-modal="true" aria-label="File inspector" tabindex="-1" out="previewView|FilePreview"></div>
|
|
88
|
+
<div class="viewer" when="showViewer" role="dialog" aria-modal="true" aria-label="File inspector" tabindex="-1" on:click="!dialog:viewer-cancel" out="previewView|FilePreview"></div>
|
|
40
89
|
<div when="panelOpen" out="panelView|CommandPanel"></div>
|
|
41
90
|
<div when="bookmarkOpen" out="bookmarkView|BookmarkList"></div>
|
|
91
|
+
<div when="overlayOpen" out="overlayView|OverlayList"></div>
|
|
42
92
|
<div out="confirmView|Confirm"></div>
|
|
43
93
|
<div when="helpOpen" out="helpView|Help"></div>
|
|
44
94
|
</div>
|
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
// Created: 2026-09-26
|
|
5
5
|
|
|
6
6
|
// Module: tcmd/components/App
|
|
7
|
-
// Root component: a header, two panes (breadcrumbs + FileList +
|
|
7
|
+
// Root component: a header, two panes (breadcrumbs + FileList + detail each),
|
|
8
8
|
// an optional preview column, the command bar and the command panel overlay.
|
|
9
9
|
// App is the controller seam: component events (published bubbles) and key
|
|
10
10
|
// chords are translated into `session.dispatch(commandId, payload)` calls; it
|
|
11
|
-
// never mutates model state directly.
|
|
11
|
+
// never mutates model state directly. It also owns the shared modal key
|
|
12
|
+
// routing — the fullscreen viewer overlay included — so both renderers behave
|
|
13
|
+
// alike.
|
|
12
14
|
//
|
|
13
15
|
// Environment: `normalizeKey(event)` (optional; identity when omitted) and
|
|
14
16
|
// `quit(self)`. Renderer-specific bindings (pane borders, status color) live in
|
|
@@ -17,6 +19,12 @@
|
|
|
17
19
|
import { derived, unwrap } from "select/state/cells"
|
|
18
20
|
import { DEFAULT_KEYMAP, matchKeymap } from "../core/keymap.js"
|
|
19
21
|
import { PANEL_FOCUS } from "../core/panel.js"
|
|
22
|
+
import { isNarrow } from "./Frame.js"
|
|
23
|
+
|
|
24
|
+
// Constant: DOUBLE_CLICK_MS
|
|
25
|
+
// Two clicks on the same listing row within this window count as a double
|
|
26
|
+
// click, which opens the row like Enter (see `createEvents#list:select`).
|
|
27
|
+
const DOUBLE_CLICK_MS = 400
|
|
20
28
|
|
|
21
29
|
// Function: createInit
|
|
22
30
|
// State initializer binding the root component to a `session`.
|
|
@@ -40,10 +48,8 @@ function createInit(session) {
|
|
|
40
48
|
appPath: session.activePath,
|
|
41
49
|
bookmarkIndex: session.bookmarkIndex,
|
|
42
50
|
bookmarkItems: session.bookmarkItems,
|
|
43
|
-
bookmarkMode: session.bookmarkMode,
|
|
44
51
|
bookmarkOpen: session.bookmarkOpen,
|
|
45
52
|
bookmarkQuery: session.bookmarkQuery,
|
|
46
|
-
bookmarkTarget: session.bookmarkTarget,
|
|
47
53
|
commandBarItems: session.commandBarItems,
|
|
48
54
|
confirm: session.confirm,
|
|
49
55
|
currentId: session.currentId,
|
|
@@ -62,7 +68,10 @@ function createInit(session) {
|
|
|
62
68
|
leftSegments: left.segments,
|
|
63
69
|
leftSetFilter: (value) => left.setFilter(value),
|
|
64
70
|
notice: session.notice,
|
|
65
|
-
|
|
71
|
+
// The shared list overlay (`u` history / `⇧T` trash).
|
|
72
|
+
overlay: session.overlay,
|
|
73
|
+
overlayIndex: session.overlayIndex,
|
|
74
|
+
overlayItems: session.overlayItems,
|
|
66
75
|
panelDraft: session.panelDraft,
|
|
67
76
|
panelCursorRow: session.panelCursorRow,
|
|
68
77
|
panelEntry: session.panelEntry,
|
|
@@ -70,13 +79,12 @@ function createInit(session) {
|
|
|
70
79
|
panelFocus: session.panelFocus,
|
|
71
80
|
panelOpIndex: session.panelOpIndex,
|
|
72
81
|
panelItems: session.panelItems,
|
|
73
|
-
panelLabelWidth: session.panelLabelWidth,
|
|
74
82
|
panelOpen: session.panelOpen,
|
|
75
|
-
|
|
83
|
+
panelPermBit: session.panelPermBit,
|
|
84
|
+
panelPreview: session.panelPreview,
|
|
76
85
|
panelQuery: session.panelQuery,
|
|
77
86
|
panelRowIndex: session.panelRowIndex,
|
|
78
87
|
panelRows: session.panelRows,
|
|
79
|
-
panelRunnable: session.panelRunnable,
|
|
80
88
|
panelTextual: session.panelTextual,
|
|
81
89
|
panelValue: session.panelValue,
|
|
82
90
|
// The explorer's first column: the parent of the current directory.
|
|
@@ -94,6 +102,8 @@ function createInit(session) {
|
|
|
94
102
|
parentSetFilter: (value) => parent.setFilter(value),
|
|
95
103
|
preview: session.preview,
|
|
96
104
|
previewFull: session.previewFull,
|
|
105
|
+
// The full previews apply body scroll requests through this cell.
|
|
106
|
+
previewScroll: session.previewScroll,
|
|
97
107
|
// The third column: the directory the inspector previews, so it reads
|
|
98
108
|
// as a pane. Over a file the inspector stays a card and these idle.
|
|
99
109
|
previewActive,
|
|
@@ -117,9 +127,12 @@ function createInit(session) {
|
|
|
117
127
|
rightSetFilter: (value) => right.setFilter(value),
|
|
118
128
|
showPreview: session.showPreview,
|
|
119
129
|
viewMode: session.viewMode,
|
|
120
|
-
// The panel's file picker: its
|
|
121
|
-
//
|
|
122
|
-
|
|
130
|
+
// The panel's file picker: its open mode, editable path, focus region
|
|
131
|
+
// and rows, so the panel can render the picker view.
|
|
132
|
+
pickerOpen: session.pickerOpen,
|
|
133
|
+
pickerPathText: session.pickerPathText,
|
|
134
|
+
pickerFocus: session.pickerFocus,
|
|
135
|
+
pickerRows: session.pickerRows,
|
|
123
136
|
pickerQuery: session.pickerQuery,
|
|
124
137
|
})
|
|
125
138
|
}
|
|
@@ -134,15 +147,58 @@ function fieldOwnsKeys(data, focus) {
|
|
|
134
147
|
return focus === PANEL_FOCUS.OPS || Boolean(unwrap(data.panelTextual))
|
|
135
148
|
}
|
|
136
149
|
|
|
150
|
+
// Function: isFilePreview
|
|
151
|
+
// True when the inspector shows a file — a text body or a metadata card — as
|
|
152
|
+
// opposed to a directory column, which keeps its listing cursor navigation.
|
|
153
|
+
function isFilePreview(data) {
|
|
154
|
+
const entry = unwrap(data.appEntry)
|
|
155
|
+
return Boolean(entry) && !entry.isDir
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Function: routePreviewKeys
|
|
159
|
+
// The arrows of a full preview: Up/Down scroll the body, Left/Right step to
|
|
160
|
+
// the previous/next entry (the cursor movement the preview already follows).
|
|
161
|
+
// Returns true when the key was handled, so the caller can stop routing.
|
|
162
|
+
function routePreviewKeys(data, key) {
|
|
163
|
+
if (!isFilePreview(data)) return false
|
|
164
|
+
const name = key?.name
|
|
165
|
+
if (name === "up" || name === "down") {
|
|
166
|
+
data.dispatch("preview.scroll", { delta: name === "up" ? -1 : 1 })
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
if (name === "left" || name === "right") {
|
|
170
|
+
data.dispatch(name === "left" ? "nav.up" : "nav.down")
|
|
171
|
+
return true
|
|
172
|
+
}
|
|
173
|
+
return false
|
|
174
|
+
}
|
|
175
|
+
|
|
137
176
|
// Function: createBehaviors
|
|
138
177
|
// Builds the App data/key behavior map for the given renderer environment.
|
|
139
178
|
// Published events are registered separately via `createEvents()` (`.sub`).
|
|
140
179
|
function createBehaviors(env) {
|
|
141
180
|
return {
|
|
142
|
-
// Global key handling:
|
|
143
|
-
// the keymap.
|
|
181
|
+
// Global key handling: the fullscreen viewer modal first, then confirm
|
|
182
|
+
// dialogs, the command panel, and finally the keymap.
|
|
144
183
|
keys: (self, data, event) => {
|
|
145
184
|
const key = env.normalizeKey ? env.normalizeKey(event) : event
|
|
185
|
+
// The fullscreen inspector overlay is modal at any width: it opens
|
|
186
|
+
// from Enter on a file the host cannot edit (see `core/commands`)
|
|
187
|
+
// and from `v` below the inline breakpoint (below). While open it
|
|
188
|
+
// owns the keyboard — Esc or `v` closes it, ⇧O/⇧E act on the file,
|
|
189
|
+
// everything else is swallowed.
|
|
190
|
+
if (unwrap(data.viewerOpen)) {
|
|
191
|
+
if (key?.name === "escape" || (key?.name === "v" && !key.shift)) {
|
|
192
|
+
data.dispatch("view.toggleViewer")
|
|
193
|
+
} else if (key?.shift && key?.name === "o") {
|
|
194
|
+
data.dispatch("file.open", { paneId: unwrap(data.active) })
|
|
195
|
+
} else if (key?.shift && key?.name === "e") {
|
|
196
|
+
data.dispatch("file.edit", { paneId: unwrap(data.active) })
|
|
197
|
+
} else {
|
|
198
|
+
routePreviewKeys(data, key)
|
|
199
|
+
}
|
|
200
|
+
return
|
|
201
|
+
}
|
|
146
202
|
if (unwrap(data.confirm)) {
|
|
147
203
|
const name = key?.name
|
|
148
204
|
if (name === "y" || name === "return" || name === "enter") data.dispatch("confirm.yes")
|
|
@@ -150,41 +206,63 @@ function createBehaviors(env) {
|
|
|
150
206
|
return
|
|
151
207
|
}
|
|
152
208
|
if (unwrap(data.panelOpen)) {
|
|
153
|
-
// The picker is a sub-mode of the panel:
|
|
154
|
-
//
|
|
155
|
-
if (unwrap(data.
|
|
209
|
+
// The picker is a sub-mode of the panel: its editable path owns
|
|
210
|
+
// typing, and the entry list owns navigation once focused.
|
|
211
|
+
if (unwrap(data.pickerOpen)) {
|
|
212
|
+
const field = unwrap(data.pickerFocus) === "rows" ? "rows" : "path"
|
|
156
213
|
if (key?.name === "escape") {
|
|
157
|
-
data.dispatch("
|
|
214
|
+
data.dispatch("picker.cancel")
|
|
158
215
|
return
|
|
159
216
|
}
|
|
160
217
|
if (key?.name === "tab") {
|
|
161
|
-
data.dispatch("
|
|
218
|
+
data.dispatch(field === "path" ? "picker.focusRows" : "picker.focusPath")
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
if (field === "path") {
|
|
222
|
+
// Typing, Backspace and caret movement belong to the path
|
|
223
|
+
// input. Up/Down hand over to the entries, Enter commits
|
|
224
|
+
// the typed path.
|
|
225
|
+
if (key?.name === "up" || key?.name === "down") {
|
|
226
|
+
data.dispatch("picker.focusRows")
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
if (key?.name === "return" || key?.name === "enter") {
|
|
230
|
+
data.dispatch("picker.path")
|
|
231
|
+
return
|
|
232
|
+
}
|
|
162
233
|
return
|
|
163
234
|
}
|
|
164
235
|
if (key?.name === "up") {
|
|
165
|
-
data.dispatch("
|
|
236
|
+
data.dispatch("picker.up")
|
|
166
237
|
return
|
|
167
238
|
}
|
|
168
239
|
if (key?.name === "down") {
|
|
169
|
-
data.dispatch("
|
|
240
|
+
data.dispatch("picker.down")
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
if (key?.name === "left") {
|
|
244
|
+
data.dispatch("picker.focusPath")
|
|
170
245
|
return
|
|
171
246
|
}
|
|
172
247
|
if (key?.name === "right") {
|
|
173
|
-
data.dispatch("
|
|
248
|
+
data.dispatch("picker.descend")
|
|
174
249
|
return
|
|
175
250
|
}
|
|
176
251
|
if (key?.name === "backspace") {
|
|
177
|
-
// An empty
|
|
178
|
-
//
|
|
179
|
-
if (`${unwrap(data.pickerQuery) ?? ""}` === "") data.dispatch("
|
|
252
|
+
// An empty path (the current directory) steps up; a
|
|
253
|
+
// typed fragment is edited in the path input instead.
|
|
254
|
+
if (`${unwrap(data.pickerQuery) ?? ""}` === "") data.dispatch("picker.parent")
|
|
180
255
|
return
|
|
181
256
|
}
|
|
182
257
|
if (key?.name === "return" || key?.name === "enter") {
|
|
183
|
-
data.dispatch(key.ctrl ? "
|
|
258
|
+
data.dispatch(key.ctrl ? "picker.chooseDir" : "picker.enter")
|
|
184
259
|
return
|
|
185
260
|
}
|
|
186
|
-
//
|
|
187
|
-
|
|
261
|
+
// Typing anywhere in the picker narrows the path.
|
|
262
|
+
const char = key?.name === "space" ? " " : key?.name
|
|
263
|
+
if (char && char.length === 1 && !key.ctrl && !key.meta) {
|
|
264
|
+
data.dispatch("picker.type", { char })
|
|
265
|
+
}
|
|
188
266
|
return
|
|
189
267
|
}
|
|
190
268
|
const focus = unwrap(data.panelFocus)
|
|
@@ -225,6 +303,11 @@ function createBehaviors(env) {
|
|
|
225
303
|
// it. Intercepting Backspace here used to bounce the cursor
|
|
226
304
|
// back to the command filter mid-edit.
|
|
227
305
|
if (fieldOwns) return
|
|
306
|
+
// A focused permission row: Left/Right walk its r/w/x cells.
|
|
307
|
+
if ((key?.name === "left" || key?.name === "right") && focus === PANEL_FOCUS.ARGS) {
|
|
308
|
+
data.dispatch("panel.moveBit", { delta: key.name === "left" ? -1 : 1 })
|
|
309
|
+
return
|
|
310
|
+
}
|
|
228
311
|
// A focused non-textual argument row: Space toggles its
|
|
229
312
|
// selection, any other printable key jumps back to the command
|
|
230
313
|
// filter and appends the character; Backspace edits the filter.
|
|
@@ -243,20 +326,14 @@ function createBehaviors(env) {
|
|
|
243
326
|
return
|
|
244
327
|
}
|
|
245
328
|
if (unwrap(data.bookmarkOpen)) {
|
|
246
|
-
|
|
329
|
+
// One panel: the filter input owns typing, arrows move the
|
|
330
|
+
// list, Enter activates the row. Backspace edits the query while
|
|
331
|
+
// it has text and removes the selected bookmark once it is
|
|
332
|
+
// empty; Delete always removes.
|
|
247
333
|
if (key?.name === "escape") {
|
|
248
334
|
data.dispatch("bookmark.cancel")
|
|
249
335
|
return
|
|
250
336
|
}
|
|
251
|
-
if (mode === "name") {
|
|
252
|
-
// The input owns typing; Enter saves.
|
|
253
|
-
if (key?.name === "return" || key?.name === "enter") {
|
|
254
|
-
self.pub("bookmark:confirm")
|
|
255
|
-
return
|
|
256
|
-
}
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
// List mode: arrows move, Enter jumps.
|
|
260
337
|
if (key?.name === "up") {
|
|
261
338
|
data.dispatch("bookmark.up")
|
|
262
339
|
return
|
|
@@ -265,11 +342,42 @@ function createBehaviors(env) {
|
|
|
265
342
|
data.dispatch("bookmark.down")
|
|
266
343
|
return
|
|
267
344
|
}
|
|
345
|
+
if (key?.name === "delete") {
|
|
346
|
+
data.dispatch("bookmark.remove")
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
if (key?.name === "backspace" && !`${unwrap(data.bookmarkQuery) ?? ""}`.trim()) {
|
|
350
|
+
data.dispatch("bookmark.remove")
|
|
351
|
+
return
|
|
352
|
+
}
|
|
268
353
|
if (key?.name === "return" || key?.name === "enter") {
|
|
269
354
|
self.pub("bookmark:confirm")
|
|
270
355
|
return
|
|
271
356
|
}
|
|
272
|
-
// Let the focused input handle typing the
|
|
357
|
+
// Let the focused input handle typing the filter.
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
// The shared list overlay (history / trash) is modal: arrows move,
|
|
361
|
+
// Enter activates the row (jump in history, restore from trash), Esc
|
|
362
|
+
// closes. The key that opened it also closes it.
|
|
363
|
+
if (unwrap(data.overlay)) {
|
|
364
|
+
const mode = unwrap(data.overlay)
|
|
365
|
+
if (key?.name === "escape" || (key?.name === "u" && mode === "history") || (key?.name === "t" && key?.shift && mode === "trash")) {
|
|
366
|
+
data.dispatch("overlay.cancel")
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
if (key?.name === "up") {
|
|
370
|
+
data.dispatch("overlay.up")
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
if (key?.name === "down") {
|
|
374
|
+
data.dispatch("overlay.down")
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
if (key?.name === "return" || key?.name === "enter") {
|
|
378
|
+
data.dispatch("overlay.activate")
|
|
379
|
+
return
|
|
380
|
+
}
|
|
273
381
|
return
|
|
274
382
|
}
|
|
275
383
|
const active = unwrap(data.active)
|
|
@@ -316,6 +424,17 @@ function createBehaviors(env) {
|
|
|
316
424
|
return
|
|
317
425
|
}
|
|
318
426
|
}
|
|
427
|
+
// The maximized inspector (`⇧V`) previews a file full-width: the
|
|
428
|
+
// arrows scroll and step through files instead of driving the pane
|
|
429
|
+
// underneath. A directory column keeps its listing navigation.
|
|
430
|
+
if (unwrap(data.previewFull) && routePreviewKeys(data, key)) return
|
|
431
|
+
// Below the inline breakpoint the viewer overlay replaces the side
|
|
432
|
+
// panel, so `v` opens it instead of toggling `showPreview` (⇧V
|
|
433
|
+
// still maximizes over the columns, via the keymap).
|
|
434
|
+
if (key?.name === "v" && !key?.shift && isNarrow(data, self)) {
|
|
435
|
+
data.dispatch("view.toggleViewer")
|
|
436
|
+
return
|
|
437
|
+
}
|
|
319
438
|
const id = matchKeymap(DEFAULT_KEYMAP, key)
|
|
320
439
|
if (!id) return
|
|
321
440
|
if (id === "app.quit") {
|
|
@@ -329,14 +448,17 @@ function createBehaviors(env) {
|
|
|
329
448
|
|
|
330
449
|
// Function: createEvents
|
|
331
450
|
// App event subscriptions, registered with `.sub(...)`. Handlers receive
|
|
332
|
-
// `(self, data, event)` with the published payload in `event.data`.
|
|
333
|
-
|
|
334
|
-
//
|
|
335
|
-
|
|
451
|
+
// `(self, data, event)` with the published payload in `event.data`.
|
|
452
|
+
function createEvents() {
|
|
453
|
+
// Last listing row clicked, for the double-click-to-open gesture: a second
|
|
454
|
+
// click on the same row within `DOUBLE_CLICK_MS` opens it like Enter.
|
|
455
|
+
let lastRowClick = { paneId: null, index: -1, at: 0 }
|
|
336
456
|
return {
|
|
337
|
-
"commandbar:run": (
|
|
338
|
-
|
|
339
|
-
|
|
457
|
+
"commandbar:run": (self, data, event) => {
|
|
458
|
+
// Below the inline breakpoint the bar's `[Preview v]` opens the
|
|
459
|
+
// viewer overlay, the same as the key (see `createBehaviors#keys`).
|
|
460
|
+
if (event.data === "view.togglePreview" && isNarrow(data, self)) {
|
|
461
|
+
data.dispatch("view.toggleViewer")
|
|
340
462
|
return
|
|
341
463
|
}
|
|
342
464
|
if (event.data) data.dispatch(event.data)
|
|
@@ -344,9 +466,9 @@ function createEvents(env = {}) {
|
|
|
344
466
|
"panel:select": (_self, data, event) => {
|
|
345
467
|
// Clicking a command row moves the cursor to it (a group header is
|
|
346
468
|
// inert); clicking an argument/item row focuses the right pane
|
|
347
|
-
// (toggling an item); clicking a picker row chooses it.
|
|
469
|
+
// (toggling an item); clicking a picker row chooses or navigates it.
|
|
348
470
|
const items = unwrap(data.panelItems) ?? []
|
|
349
|
-
const item = items.find((row) => row.
|
|
471
|
+
const item = items.find((row) => row.key === event.data)
|
|
350
472
|
if (item?.type === "op") {
|
|
351
473
|
data.dispatch("panel.setOp", { index: item.entryIndex })
|
|
352
474
|
return
|
|
@@ -356,50 +478,103 @@ function createEvents(env = {}) {
|
|
|
356
478
|
const rowIndex = rows.findIndex((row) => row.key === event.data)
|
|
357
479
|
if (rowIndex < 0) return
|
|
358
480
|
const row = rows[rowIndex]
|
|
359
|
-
if (row?.type === "entry") {
|
|
481
|
+
if (row?.type === "entry" || row?.type === "parent") {
|
|
360
482
|
data.dispatch("panel.focusArg")
|
|
361
|
-
data.dispatch("
|
|
362
|
-
data.dispatch("
|
|
483
|
+
data.dispatch("picker.setCursor", { index: rowIndex })
|
|
484
|
+
data.dispatch(row.type === "parent" ? "picker.parent" : "picker.enter")
|
|
363
485
|
return
|
|
364
486
|
}
|
|
487
|
+
if (row?.type === "run") {
|
|
488
|
+
data.dispatch("panel.focusArg")
|
|
489
|
+
data.dispatch("panel.setRow", { index: rowIndex })
|
|
490
|
+
data.dispatch("panel.run")
|
|
491
|
+
return
|
|
492
|
+
}
|
|
493
|
+
// A read-only value (a single-path selection, resolved from the
|
|
494
|
+
// pane) is a path worth copying: clicking it sends the value to the
|
|
495
|
+
// host clipboard. Copy runs last so its confirmation is not cleared
|
|
496
|
+
// by the focus dispatches (each dispatch resets the notice).
|
|
365
497
|
data.dispatch("panel.focusArg")
|
|
366
498
|
data.dispatch("panel.setRow", { index: rowIndex })
|
|
367
499
|
if (row?.type === "item") data.dispatch("panel.toggleItem")
|
|
500
|
+
if (row?.type === "arg" && row.readonly === true && row.summary) {
|
|
501
|
+
data.dispatch("panel.copy", { text: row.summary })
|
|
502
|
+
}
|
|
368
503
|
},
|
|
369
504
|
"panel:confirm": (_self, data) => {
|
|
370
505
|
// Published by a focused panel input's `on:enter`; the key handler
|
|
371
506
|
// leaves Enter to it (see `fieldOwnsKeys`).
|
|
372
507
|
if (!unwrap(data.panelOpen)) return
|
|
373
|
-
if (unwrap(data.
|
|
374
|
-
data.dispatch("
|
|
508
|
+
if (unwrap(data.pickerOpen)) {
|
|
509
|
+
data.dispatch("picker.enter")
|
|
375
510
|
return
|
|
376
511
|
}
|
|
377
512
|
data.dispatch("panel.run")
|
|
378
513
|
},
|
|
514
|
+
"panel:perm": (_self, data, event) => {
|
|
515
|
+
// A click on one r/w/x cell: focus its class row, then toggle the
|
|
516
|
+
// bit. The payload is `argIndex:itemIndex:rowIndex` (see
|
|
517
|
+
// `core/panel#panelRowList`).
|
|
518
|
+
const [argIndex, itemIndex, rowIndex] = `${event.data ?? ""}`.split(":").map(Number)
|
|
519
|
+
if (![argIndex, itemIndex, rowIndex].every(Number.isInteger)) return
|
|
520
|
+
data.dispatch("panel.focusArg")
|
|
521
|
+
data.dispatch("panel.setRow", { index: rowIndex })
|
|
522
|
+
data.dispatch("panel.togglePerm", { argIndex, itemIndex })
|
|
523
|
+
},
|
|
524
|
+
"panel:close": (_self, data) => {
|
|
525
|
+
// The dialog's upper-right close shorthand: back out of the picker
|
|
526
|
+
// sub-mode first, otherwise close the panel.
|
|
527
|
+
if (!unwrap(data.panelOpen)) return
|
|
528
|
+
data.dispatch(unwrap(data.pickerOpen) ? "picker.cancel" : "panel.cancel")
|
|
529
|
+
},
|
|
379
530
|
"crumbs:go": (_self, data, event) => {
|
|
380
531
|
// The "…" truncation marker carries no path.
|
|
381
532
|
if (!event.data?.path) return
|
|
382
533
|
data.dispatch("pane.setPath", event.data)
|
|
383
534
|
},
|
|
384
|
-
"list:select": (_self, data, event) =>
|
|
535
|
+
"list:select": (_self, data, event) => {
|
|
536
|
+
const paneId = event.data?.paneId
|
|
537
|
+
const index = event.data?.index
|
|
538
|
+
const now = Date.now()
|
|
539
|
+
// A click selects (focus + cursor); a second click on the same row
|
|
540
|
+
// within the window opens it like Enter. `list.cursor` activates the
|
|
541
|
+
// pane synchronously, so `pane.enter` then acts on the clicked pane.
|
|
542
|
+
const isDouble =
|
|
543
|
+
paneId === lastRowClick.paneId &&
|
|
544
|
+
index === lastRowClick.index &&
|
|
545
|
+
now - lastRowClick.at <= DOUBLE_CLICK_MS
|
|
546
|
+
lastRowClick = { paneId, index, at: isDouble ? 0 : now }
|
|
547
|
+
data.dispatch("list.cursor", event.data)
|
|
548
|
+
if (isDouble) data.dispatch("pane.enter", { paneId })
|
|
549
|
+
},
|
|
385
550
|
"pane:activate": (_self, data, event) => data.dispatch("pane.activate", { paneId: event.data }),
|
|
551
|
+
// Clicking the identity bar's view-mode word flips explorer/commander.
|
|
552
|
+
"view:toggleMode": (_self, data) => data.dispatch("view.toggleMode"),
|
|
386
553
|
"bookmark:select": (_self, data, event) => {
|
|
387
|
-
// Clicking a bookmark row selects and
|
|
554
|
+
// Clicking a bookmark (or the add) row selects and activates it.
|
|
388
555
|
if (!unwrap(data.bookmarkOpen)) return
|
|
389
556
|
const items = unwrap(data.bookmarkItems) ?? []
|
|
390
|
-
const index = items.findIndex((row) => row.
|
|
557
|
+
const index = items.findIndex((row) => row.key === event.data)
|
|
391
558
|
if (index < 0) return
|
|
392
559
|
data.dispatch("bookmark.setIndex", { index })
|
|
393
560
|
data.dispatch("bookmark.select")
|
|
394
561
|
},
|
|
395
562
|
"bookmark:confirm": (_self, data) => {
|
|
396
563
|
if (!unwrap(data.bookmarkOpen)) return
|
|
397
|
-
|
|
398
|
-
|
|
564
|
+
data.dispatch("bookmark.select")
|
|
565
|
+
},
|
|
566
|
+
"overlay:select": (_self, data, event) => {
|
|
567
|
+
// Clicking an overlay row moves the cursor to it and activates it.
|
|
568
|
+
if (!unwrap(data.overlay)) return
|
|
569
|
+
const rows = unwrap(data.overlayItems) ?? []
|
|
570
|
+
const index = rows.findIndex((row) => row.key === event.data)
|
|
571
|
+
if (index < 0) return
|
|
572
|
+
data.dispatch("overlay.setIndex", { index })
|
|
573
|
+
data.dispatch("overlay.activate")
|
|
399
574
|
},
|
|
400
575
|
}
|
|
401
576
|
}
|
|
402
577
|
|
|
403
|
-
export { createBehaviors, createEvents, createInit }
|
|
578
|
+
export { createBehaviors, createEvents, createInit, DOUBLE_CLICK_MS }
|
|
404
579
|
|
|
405
580
|
// EOF
|