@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
|
@@ -25,15 +25,18 @@
|
|
|
25
25
|
Cmd oranges (see `modeGradient` in Frame.js). The trailing
|
|
26
26
|
slots of the shorter word are empty and take no column. -->
|
|
27
27
|
<box flexDirection="row" flexShrink="0" overflow="hidden">
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
<!-- Clicking the mode word flips explorer ⇄ commander. -->
|
|
29
|
+
<box flexDirection="row" overflow="hidden" on:click="!view:toggleMode">
|
|
30
|
+
<text when="modeHas0" out="modeL0" out:fg="modeFg0" wrapMode="none"></text>
|
|
31
|
+
<text when="modeHas1" out="modeL1" out:fg="modeFg1" wrapMode="none"></text>
|
|
32
|
+
<text when="modeHas2" out="modeL2" out:fg="modeFg2" wrapMode="none"></text>
|
|
33
|
+
<text when="modeHas3" out="modeL3" out:fg="modeFg3" wrapMode="none"></text>
|
|
34
|
+
<text when="modeHas4" out="modeL4" out:fg="modeFg4" wrapMode="none"></text>
|
|
35
|
+
<text when="modeHas5" out="modeL5" out:fg="modeFg5" wrapMode="none"></text>
|
|
36
|
+
<text when="modeHas6" out="modeL6" out:fg="modeFg6" wrapMode="none"></text>
|
|
37
|
+
<text when="modeHas7" out="modeL7" out:fg="modeFg7" wrapMode="none"></text>
|
|
38
|
+
<text when="modeHas8" out="modeL8" out:fg="modeFg8" wrapMode="none"></text>
|
|
39
|
+
</box>
|
|
37
40
|
<text out="topBarStatus" out:fg="topBarStatusFg" wrapMode="none"></text>
|
|
38
41
|
</box>
|
|
39
42
|
</box>
|
|
@@ -64,7 +67,6 @@
|
|
|
64
67
|
in:active="parentActive" in:paneId="parentId" in:loading="parentLoading"
|
|
65
68
|
in:viewport="listViewport"></filelist>
|
|
66
69
|
<entrydetail in:entry="parentEntry" in:marked="parentMarked" height="2"></entrydetail>
|
|
67
|
-
<summary in:entries="parentEntries" in:disk="parentDisk" height="1"></summary>
|
|
68
70
|
</box>
|
|
69
71
|
<box when="showLeftPane" flexDirection="column" flexBasis="0" out:flexGrow="leftGrow" border borderStyle="single"
|
|
70
72
|
out:border="leftBorder" out:borderColor="leftBorderFg"
|
|
@@ -80,7 +82,6 @@
|
|
|
80
82
|
in:active="leftActive" in:paneId="leftId" in:loading="leftLoading"
|
|
81
83
|
in:viewport="listViewport"></filelist>
|
|
82
84
|
<entrydetail in:entry="leftEntry" in:marked="leftMarked" height="2"></entrydetail>
|
|
83
|
-
<summary in:entries="leftEntries" in:disk="leftDisk" height="1"></summary>
|
|
84
85
|
</box>
|
|
85
86
|
<box when="showRightPane" flexDirection="column" flexBasis="0" out:flexGrow="rightGrow" border borderStyle="single"
|
|
86
87
|
out:border="rightBorder" out:borderColor="rightBorderFg"
|
|
@@ -96,10 +97,9 @@
|
|
|
96
97
|
in:active="rightActive" in:paneId="rightId" in:loading="rightLoading"
|
|
97
98
|
in:viewport="listViewport"></filelist>
|
|
98
99
|
<entrydetail in:entry="rightEntry" in:marked="rightMarked" height="2"></entrydetail>
|
|
99
|
-
<summary in:entries="rightEntries" in:disk="rightDisk" height="1"></summary>
|
|
100
100
|
</box>
|
|
101
101
|
<filepreview when="showInspectorInline" in:preview="preview" in:show="showInspectorInline"
|
|
102
|
-
in:entry="appEntry" in:paneId="previewId"
|
|
102
|
+
in:entry="appEntry" in:paneId="previewId" in:scroll="previewScroll"
|
|
103
103
|
in:dirSegments="previewSegments" in:dirEntries="previewEntries"
|
|
104
104
|
in:dirCursor="previewCursor" in:dirMarked="previewMarked"
|
|
105
105
|
in:dirActive="previewActive" in:dirLoading="previewLoading"
|
|
@@ -108,22 +108,24 @@
|
|
|
108
108
|
<!-- Maximized inspector (`⇧V`): the preview takes the whole pane
|
|
109
109
|
row, the identity and command bars staying in place. -->
|
|
110
110
|
<filepreview when="showPreviewFull" in:preview="preview" in:show="showPreviewFull"
|
|
111
|
-
in:entry="appEntry" in:paneId="previewId"
|
|
111
|
+
in:entry="appEntry" in:paneId="previewId" in:scroll="previewScroll"
|
|
112
112
|
in:dirSegments="previewSegments" in:dirEntries="previewEntries"
|
|
113
113
|
in:dirCursor="previewCursor" in:dirMarked="previewMarked"
|
|
114
114
|
in:dirActive="previewActive" in:dirLoading="previewLoading"
|
|
115
115
|
flexGrow="1"></filepreview>
|
|
116
116
|
<commandbar in:items="commandBarItems" in:notice="notice" in:leftError="leftError"
|
|
117
|
-
in:rightError="rightError" in:active="active"
|
|
117
|
+
in:rightError="rightError" in:active="active" in:uiSize="uiSize"
|
|
118
118
|
in:leftMarked="leftMarked" in:rightMarked="rightMarked" out:height="barHeight"></commandbar>
|
|
119
119
|
</box>
|
|
120
120
|
</box>
|
|
121
121
|
|
|
122
|
-
<!-- Fullscreen inspector
|
|
122
|
+
<!-- Fullscreen inspector overlay: `v` below the inline breakpoint, or
|
|
123
|
+
Enter on a file the host cannot edit (any width). -->
|
|
123
124
|
<box when="showViewer" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="10"
|
|
124
125
|
alignItems="center" justifyContent="center">
|
|
125
126
|
<box width="80%" height="90%">
|
|
126
127
|
<filepreview in:preview="preview" in:show="showViewer" in:entry="appEntry" in:paneId="previewId"
|
|
128
|
+
in:scroll="previewScroll"
|
|
127
129
|
in:dirSegments="previewSegments" in:dirEntries="previewEntries"
|
|
128
130
|
in:dirCursor="previewCursor" in:dirMarked="previewMarked"
|
|
129
131
|
in:dirActive="previewActive" in:dirLoading="previewLoading"></filepreview>
|
|
@@ -133,15 +135,18 @@
|
|
|
133
135
|
<commandpanel when="panelOpen" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="22"
|
|
134
136
|
in:open="panelOpen" in:entry="panelEntry" in:items="panelItems"
|
|
135
137
|
in:opCursor="panelOpIndex" in:rows="panelRows" in:argCursor="panelRowIndex"
|
|
136
|
-
in:
|
|
137
|
-
in:
|
|
138
|
-
in:picking="
|
|
138
|
+
in:permBit="panelPermBit" in:focus="panelFocus"
|
|
139
|
+
in:preview="panelPreview"
|
|
140
|
+
in:picking="pickerOpen"
|
|
141
|
+
in:pickerPathText="pickerPathText" in:pickerFocus="pickerFocus" in:pickerRows="pickerRows"
|
|
139
142
|
in:query="panelQuery" in:value="panelValue"></commandpanel>
|
|
140
143
|
<bookmarklist when="bookmarkOpen" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="25"
|
|
141
|
-
in:open="bookmarkOpen" in:
|
|
142
|
-
in:
|
|
144
|
+
in:open="bookmarkOpen" in:query="bookmarkQuery"
|
|
145
|
+
in:items="bookmarkItems" in:selected="bookmarkIndex"></bookmarklist>
|
|
146
|
+
<overlaylist when="overlayOpen" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="27"
|
|
147
|
+
in:open="overlay" in:mode="overlay" in:items="overlayItems" in:cursor="overlayIndex"></overlaylist>
|
|
143
148
|
<confirm when="confirm" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="30"
|
|
144
149
|
in:confirm="confirm"></confirm>
|
|
145
150
|
<help when="helpOpen" position="absolute" top="0" left="0" width="100%" height="100%" zIndex="40"
|
|
146
|
-
in:open="helpOpen"></help>
|
|
151
|
+
in:open="helpOpen" in:size="uiSize"></help>
|
|
147
152
|
</box>
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
<div class="bookmark-list" when="open" role="dialog" aria-modal="true" aria-label="Bookmarks">
|
|
2
|
-
<div class="bookmark-dialog">
|
|
1
|
+
<div class="bookmark-list" when="open" role="dialog" aria-modal="true" aria-label="Bookmarks" on:click="!dialog:bookmark-cancel">
|
|
2
|
+
<div class="bookmark-dialog" on:click="dialogStop">
|
|
3
3
|
<h2 out="title"></h2>
|
|
4
4
|
<header>
|
|
5
5
|
<span class="bookmark-sign" out="sign"></span>
|
|
6
|
-
<input class="bookmark-query" type="text" inout="query" aria-label="
|
|
6
|
+
<input class="bookmark-query" type="text" inout="query" aria-label="Filter bookmarks" on:enter="confirm">
|
|
7
7
|
</header>
|
|
8
|
-
<p class="bookmark-target" when="inputVisible" out="target"></p>
|
|
9
8
|
<p class="bookmark-empty" when="emptyList" out="empty"></p>
|
|
10
|
-
<ul class="bookmark-rows"
|
|
9
|
+
<ul class="bookmark-rows" out="rows"></ul>
|
|
11
10
|
<footer out="footer"></footer>
|
|
12
11
|
</div>
|
|
13
12
|
</div>
|
|
@@ -4,61 +4,35 @@
|
|
|
4
4
|
// Created: 2026-10-09
|
|
5
5
|
|
|
6
6
|
// Module: tcmd/components/BookmarkList
|
|
7
|
-
// BookmarkList: the
|
|
8
|
-
// the
|
|
9
|
-
//
|
|
7
|
+
// BookmarkList: the single bookmarks panel opened with `b`. The input filters
|
|
8
|
+
// the stored bookmarks (matched by name or path); the first row offers to add
|
|
9
|
+
// the current path when it is not bookmarked yet. The row logic lives in
|
|
10
10
|
// `BookmarkRow.js`; this parent maps its `items` to rows.
|
|
11
11
|
//
|
|
12
12
|
// Environment: `overlayBg`, `borderPrimaryActiveFg`, `textSecondaryFg`
|
|
13
|
-
// (footer
|
|
14
|
-
//
|
|
13
|
+
// (footer), `textHeadingFg` (title), `rows(data)` returning the keyed rows and
|
|
14
|
+
// `scrollTop(self, data)` (TUI viewport tracking).
|
|
15
15
|
|
|
16
16
|
import { unwrap } from "select/state/cells"
|
|
17
17
|
|
|
18
|
-
// Function: title
|
|
19
|
-
// The overlay title: "bookmark" while naming, "bookmarks" while picking.
|
|
20
|
-
function bookmarkTitle(mode) {
|
|
21
|
-
return unwrap(mode) === "name" ? "bookmark" : "bookmarks"
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Function: footer
|
|
25
|
-
function bookmarkFooter(mode) {
|
|
26
|
-
return unwrap(mode) === "name"
|
|
27
|
-
? "name the bookmark ↵ save Esc cancel"
|
|
28
|
-
: "↑↓ move ↵ jump Esc close"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Function: targetLine
|
|
32
|
-
// The path being recorded (name mode). `target` is `{ path, isDir }` or null.
|
|
33
|
-
function targetLine(target) {
|
|
34
|
-
const value = unwrap(target)
|
|
35
|
-
if (!value) return ""
|
|
36
|
-
if (typeof value === "string") return value
|
|
37
|
-
return `${value.path ?? ""}${value.isDir === true ? "/" : ""}`
|
|
38
|
-
}
|
|
39
|
-
|
|
40
18
|
// Function: createBehaviors
|
|
41
19
|
// Builds the BookmarkList behavior map for the given renderer environment.
|
|
42
20
|
function createBehaviors(env) {
|
|
43
21
|
const behaviors = {
|
|
44
22
|
bg: () => env.overlayBg,
|
|
45
23
|
borderFg: () => env.borderPrimaryActiveFg,
|
|
46
|
-
confirm: (self
|
|
47
|
-
footer: (
|
|
24
|
+
confirm: (self) => self.pub("bookmark:confirm"),
|
|
25
|
+
footer: () => "↑↓ move ⌫/Del remove ↵ open Esc close",
|
|
48
26
|
footerFg: () => env.textSecondaryFg,
|
|
49
|
-
// The input
|
|
27
|
+
// The filter input owns typing; the list navigates with the arrows.
|
|
50
28
|
focused: (_self, data) => Boolean(unwrap(data.open)),
|
|
51
|
-
inputVisible: (_self, data) => unwrap(data.mode) === "name",
|
|
52
|
-
listVisible: (_self, data) => unwrap(data.mode) === "list",
|
|
53
29
|
open: (_self, data) => Boolean(unwrap(data.open)),
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
target: (_self, data) => targetLine(data.target),
|
|
57
|
-
title: (_self, data) => bookmarkTitle(data.mode),
|
|
30
|
+
sign: () => "›",
|
|
31
|
+
title: () => "bookmarks",
|
|
58
32
|
titleColor: () => env.textHeadingFg,
|
|
59
|
-
emptyList: (_self, data) =>
|
|
60
|
-
|
|
61
|
-
|
|
33
|
+
emptyList: (_self, data) => (unwrap(data.items)?.length ?? 0) === 0,
|
|
34
|
+
empty: (_self, data) =>
|
|
35
|
+
`${unwrap(data.query) ?? ""}`.trim() ? " no matching bookmarks" : " no bookmarks",
|
|
62
36
|
emptyFg: () => env.textSecondaryFg,
|
|
63
37
|
}
|
|
64
38
|
if (env?.rows) {
|
|
@@ -70,4 +44,4 @@ function createBehaviors(env) {
|
|
|
70
44
|
return behaviors
|
|
71
45
|
}
|
|
72
46
|
|
|
73
|
-
export {
|
|
47
|
+
export { createBehaviors }
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
<text out="sign" out:fg="titleColor"></text>
|
|
7
7
|
<input ref="query" inout="query" flexGrow="1" height="1" out:focused="focused" on:enter="confirm"></input>
|
|
8
8
|
</box>
|
|
9
|
-
<text when="inputVisible" out="target" out:fg="pathFg" paddingLeft="1" wrapMode="none" truncate="true"></text>
|
|
10
9
|
<text when="emptyList" out="empty" out:fg="emptyFg" paddingLeft="1" wrapMode="none"></text>
|
|
11
|
-
<scrollbox
|
|
10
|
+
<scrollbox ref="list" flexGrow="1">
|
|
12
11
|
<box out="rows" flexDirection="column"></box>
|
|
13
12
|
</scrollbox>
|
|
14
13
|
<box flexDirection="row" height="1" paddingLeft="1">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
<li class="bookmark-row" out:class="rowClass" on:click="item.
|
|
1
|
+
<li class="bookmark-row" out:class="rowClass" on:click="item.key!bookmark:select">
|
|
2
2
|
<span class="bookmark-marker" out="rowMarker"></span>
|
|
3
|
+
<svg class="bookmark-icon ui-icon" when="hasIcon" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use out:href="rowIconHref"></use></svg>
|
|
3
4
|
<span class="bookmark-name" out="rowName"></span>
|
|
4
5
|
<span class="bookmark-path" out="rowPath"></span>
|
|
5
6
|
</li>
|
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
// Created: 2026-10-09
|
|
5
5
|
|
|
6
6
|
// Module: tcmd/components/BookmarkRow
|
|
7
|
-
// One row of the
|
|
8
|
-
// selected }`, the same bag shape in both engines.
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
7
|
+
// One row of the bookmarks panel. Reads `{ item: { name, path, add? }, index,
|
|
8
|
+
// selected }`, the same bag shape in both engines. The synthetic add row reads
|
|
9
|
+
// as an action; stored rows read as `name/` for directories. Selection is
|
|
10
|
+
// snapshotted by the row builder into a plain `selectedRow` boolean and read
|
|
11
|
+
// through the shared `isSelected` predicate, with the `selected` cell as
|
|
12
|
+
// fallback for hand-built bags.
|
|
12
13
|
//
|
|
13
14
|
// Environment: `accentFg`, `textFg`, `textSecondaryFg`, `selectionBg` (TUI row
|
|
14
15
|
// background), `selectedClass` (web), `icons` (show Nerd Font icons).
|
|
15
16
|
|
|
16
17
|
import { isSelected } from "./rows.js"
|
|
17
|
-
import { iconFor } from "./icons.js"
|
|
18
|
+
import { iconFor, lucideIconFor } from "./icons.js"
|
|
18
19
|
|
|
19
20
|
// Function: createBehaviors
|
|
20
21
|
// Builds the BookmarkRow behavior map for the given renderer environment.
|
|
@@ -25,13 +26,17 @@ function createBehaviors(env) {
|
|
|
25
26
|
rowFg: (_self, data) => (isSelected(data) ? env.accentFg : env.textFg),
|
|
26
27
|
// The icon is its own slot, dimmer than the name; it collapses when
|
|
27
28
|
// the environment keeps rows icon-free.
|
|
28
|
-
hasIcon: () => Boolean(env.icons),
|
|
29
|
+
hasIcon: () => Boolean(env.icons || env.lucideIcons),
|
|
29
30
|
rowIcon: (_self, data) => (env.icons ? iconFor(data.item) : ""),
|
|
31
|
+
rowIconHref: (_self, data) => (env.lucideIcons ? `#lucide-${lucideIconFor(data.item)}` : ""),
|
|
30
32
|
rowIconFg: () => env.textSecondaryFg,
|
|
31
33
|
rowMarker: (_self, data) => (isSelected(data) ? ">" : " "),
|
|
32
|
-
//
|
|
34
|
+
// The add row names its action; a directory bookmark reads as
|
|
35
|
+
// `name/`, mirroring the listing.
|
|
33
36
|
rowName: (_self, data) =>
|
|
34
|
-
|
|
37
|
+
data.item?.add
|
|
38
|
+
? data.item.name ?? ""
|
|
39
|
+
: `${data.item?.name ?? ""}${data.item?.isDir === true ? "/" : ""}`,
|
|
35
40
|
rowPath: (_self, data) => `${data.item?.path ?? ""}`,
|
|
36
41
|
rowPathFg: () => env.textSecondaryFg,
|
|
37
42
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<box flexDirection="row" height="1" columnGap="1" out:backgroundColor="rowBg" on:click="item.
|
|
1
|
+
<box flexDirection="row" height="1" columnGap="1" out:backgroundColor="rowBg" on:click="item.key!bookmark:select">
|
|
2
2
|
<text out="rowMarker" width="2" out:fg="rowFg"></text>
|
|
3
3
|
<text out="rowIcon" out:fg="rowIconFg" wrapMode="none" when="hasIcon"></text>
|
|
4
4
|
<box minWidth="12" flexShrink="0">
|
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
<li class="panel-row" out:class="rowClass" out:data-risk="row.risk" out:data-type="row.type" out:data-selected="rowSelected" on:click="row.key!panel:select">
|
|
1
|
+
<li class="panel-row" out:class="rowClass" out:data-risk="row.risk" out:data-group="row.group" out:data-type="row.type" out:data-selected="rowSelected" on:click="row.key!panel:select">
|
|
2
2
|
<span class="panel-list-indent" when="isList" out="listSpacer"></span>
|
|
3
3
|
<span class="panel-list-box" when="isList">
|
|
4
|
-
<span class="panel-marker" out="marker"></span>
|
|
5
|
-
<span class="panel-name" out="name"></span>
|
|
6
|
-
<span class="panel-summary" out="summary"></span>
|
|
4
|
+
<span class="panel-marker" when="isItem" out="marker"></span>
|
|
5
|
+
<span class="panel-name" when="isItem" out="name"></span>
|
|
6
|
+
<span class="panel-summary" when="isItem" out="summary"></span>
|
|
7
7
|
</span>
|
|
8
|
-
<span class="panel-
|
|
9
|
-
<span class="panel-
|
|
10
|
-
<span class="panel-
|
|
8
|
+
<span class="panel-name panel-group-name" when="isGroup" out="name"></span>
|
|
9
|
+
<span class="panel-marker" when="showPlain" out="marker"></span>
|
|
10
|
+
<span class="panel-name" when="showPlain" out="name"></span>
|
|
11
|
+
<span class="panel-marker" when="isArg" out="marker"></span>
|
|
12
|
+
<span class="panel-name panel-arg-name" when="isArg" out="name"></span>
|
|
13
|
+
<span class="panel-perm" when="isPerm">
|
|
14
|
+
<span class="panel-perm-class" out="permClass"></span>
|
|
15
|
+
<button class="panel-perm-bit" type="button" tabindex="-1" out:class="permRClass" out:aria-pressed="permROn" on:click="row.keyR!panel:perm." out="permR"></button>
|
|
16
|
+
<button class="panel-perm-bit" type="button" tabindex="-1" out:class="permWClass" out:aria-pressed="permWOn" on:click="row.keyW!panel:perm." out="permW"></button>
|
|
17
|
+
<button class="panel-perm-bit" type="button" tabindex="-1" out:class="permXClass" out:aria-pressed="permXOn" on:click="row.keyX!panel:perm." out="permX"></button>
|
|
18
|
+
</span>
|
|
19
|
+
<button class="panel-run-button" when="isRun" type="button" tabindex="-1" out:aria-label="runAriaLabel" out="runLabel"></button>
|
|
20
|
+
<span class="panel-field" when="isArg" out:data-editable="editable">
|
|
11
21
|
<span class="panel-field-bracket" out="fieldOpen"></span>
|
|
12
22
|
<input class="panel-value" when="editing" type="text" inout="value" aria-label="Argument value" />
|
|
13
23
|
<span class="panel-field-value" when="!editing" out="summary"></span>
|
|
14
24
|
<span class="panel-field-bracket" out="fieldClose"></span>
|
|
15
25
|
</span>
|
|
16
26
|
<span class="panel-summary" when="isPlain" out="summary"></span>
|
|
27
|
+
<svg class="panel-op-chevron ui-icon" when="isOp" aria-hidden="true" focusable="false" viewBox="0 0 24 24"><use href="#lucide-chevron-right"></use></svg>
|
|
17
28
|
</li>
|
|
@@ -5,53 +5,66 @@
|
|
|
5
5
|
|
|
6
6
|
// Module: tcmd/components/CommandArgRow
|
|
7
7
|
// CommandArgRow: one row shared by the command panel's two panes. The left pane
|
|
8
|
-
// passes `{ type: "op" }` rows (
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// `frame`
|
|
16
|
-
//
|
|
8
|
+
// passes `{ type: "op" }` name-only rows (`risk` tints caution/destructive
|
|
9
|
+
// categories) and `{ type: "group" }` headers (Command / File / Create / Edit / Delete). The
|
|
10
|
+
// right pane passes `{ type: "arg" | "item" | "perm" | "frame" | "entry" |
|
|
11
|
+
// "parent" | "run" }` rows: an argument header stacks its label over its value
|
|
12
|
+
// — a bracketed field with a distinct background for textual arguments, a
|
|
13
|
+
// summary otherwise; an item shows a `[x]`/`[ ]` (or radio) marker with its
|
|
14
|
+
// basename and parent; `perm` is a `u/g/o` permission class with three r/w/x
|
|
15
|
+
// cells; a `frame` draws a selection list's border; and `parent` is the
|
|
16
|
+
// picker's `..` navigation row. Reads the row bag
|
|
17
|
+
// `{ item, index, row, cursor, focus, permBit, value }`
|
|
17
18
|
// built by `rows.js`.
|
|
18
19
|
//
|
|
19
20
|
// Environment: `accentFg` (focused row), `textFg`, `textSecondaryFg` (summary),
|
|
20
|
-
// `textHeadingFg` (
|
|
21
|
-
// `errorFg` (destructive op), `warningFg` (caution op), `selectionBg`
|
|
22
|
-
// background), `fieldBg`/`fieldFg` (
|
|
23
|
-
//
|
|
21
|
+
// `textHeadingFg` (general headings), `accentSecondaryFg` (a selected item),
|
|
22
|
+
// `errorFg` (destructive op/error), `warningFg` (caution op), `selectionBg`
|
|
23
|
+
// (row background), `fieldBg`/`fieldFg` (editable values), `readonlyFg`
|
|
24
|
+
// (read-only values), `borderSecondaryFg` (the list border), `focusedClass`
|
|
25
|
+
// (web), `icons` (namespace marker).
|
|
24
26
|
|
|
25
27
|
import { unwrap } from "select/state/cells"
|
|
28
|
+
import { PERMISSION_BITS } from "../core/permissions.js"
|
|
26
29
|
|
|
27
30
|
// Function: riskFg
|
|
28
|
-
// The colour a row's
|
|
29
|
-
//
|
|
31
|
+
// The colour a row's category maps to: red for Delete, amber for Edit, null
|
|
32
|
+
// when the row is neither.
|
|
30
33
|
function riskFg(row, env) {
|
|
31
|
-
if (row?.type === "op" && row.risk === "destructive") return env.errorFg
|
|
32
|
-
if (row?.type === "op" && row.risk === "caution") return env.warningFg
|
|
34
|
+
if ((row?.type === "op" || row?.type === "run") && row.risk === "destructive") return env.errorFg
|
|
35
|
+
if ((row?.type === "op" || row?.type === "run") && row.risk === "caution") return env.warningFg
|
|
33
36
|
return null
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
// Function: groupFg
|
|
40
|
+
// Color the purpose-group headings: Edit is amber and Delete is red.
|
|
41
|
+
function groupFg(group, env) {
|
|
42
|
+
if (group === "Edit") return env.warningFg
|
|
43
|
+
if (group === "Delete") return env.errorFg
|
|
44
|
+
return env.textFg
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
// Function: rowName
|
|
37
|
-
// The row's
|
|
38
|
-
//
|
|
39
|
-
// carry a trailing slash).
|
|
48
|
+
// The row's label: a group heading, operation name, argument label, `[Run]`
|
|
49
|
+
// action, `[x] basename`, or picker entry (directories carry a trailing slash).
|
|
40
50
|
function rowName(row) {
|
|
41
51
|
if (!row) return ""
|
|
42
52
|
if (row.type === "group") return row.name ?? ""
|
|
53
|
+
if (row.type === "run") return row.error ? "[Retry]" : row.armed ? "[Confirm]" : "[Run]"
|
|
43
54
|
if (row.type === "item") return `${row.marker ?? "[ ]"} ${row.name ?? ""}`
|
|
55
|
+
if (row.type === "perm") return row.label ?? ""
|
|
56
|
+
if (row.type === "parent") return ".."
|
|
44
57
|
if (row.type === "entry") return `${row.name ?? ""}${row.isDir ? "/" : ""}`
|
|
45
58
|
return row.name ?? ""
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
// Function: rowSummary
|
|
49
|
-
// The row's right cell: an
|
|
50
|
-
//
|
|
51
|
-
// entry's kind.
|
|
62
|
+
// The row's right cell: an argument's binding summary (or its value), an
|
|
63
|
+
// item's parent directory, or a picker entry's kind.
|
|
52
64
|
function rowSummary(row) {
|
|
53
65
|
if (!row) return ""
|
|
54
66
|
if (row.type === "item") return row.parent ?? ""
|
|
67
|
+
if (row.type === "parent") return "parent directory"
|
|
55
68
|
if (row.type === "entry") {
|
|
56
69
|
if (row.isSymlink && row.target) return `${row.target}`
|
|
57
70
|
return row.isDir ? "dir" : ""
|
|
@@ -81,6 +94,11 @@ function createBehaviors(env) {
|
|
|
81
94
|
const focused = (data) => isFocused(data.row, data.index, unwrap(data.cursor), unwrap(data.focus))
|
|
82
95
|
const type = (data) => data.row?.type
|
|
83
96
|
const isList = (data) => type(data) === "item" || type(data) === "frame"
|
|
97
|
+
// A permission row has three bit cells; the bit cursor marks the one Space
|
|
98
|
+
// toggles, and a click toggles the cell it lands on.
|
|
99
|
+
const bits = (data) => data.row?.bits ?? []
|
|
100
|
+
const bitAt = (data, index) => bits(data)[index]
|
|
101
|
+
const bitFocused = (data, index) => focused(data) && (unwrap(data.permBit) ?? 0) === index
|
|
84
102
|
// The web classes that draw a selection's list box: the item rows carry the
|
|
85
103
|
// sides, the frame rows the top/bottom.
|
|
86
104
|
const listClass = (data) => {
|
|
@@ -91,23 +109,22 @@ function createBehaviors(env) {
|
|
|
91
109
|
// A textual argument header edits in place while it owns the cursor: its
|
|
92
110
|
// summary yields to a value input bound to the panel's edit cell.
|
|
93
111
|
const editing = (_self, data) => focused(data) && type(data) === "arg" && data.row?.textual === true
|
|
94
|
-
|
|
112
|
+
const behaviors = {
|
|
95
113
|
editing,
|
|
96
114
|
// --- shared row content ---
|
|
97
115
|
marker: (_self, data) => (focused(data) ? ">" : " "),
|
|
98
116
|
markerFg: () => env.accentFg,
|
|
99
|
-
// An argument label
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
},
|
|
117
|
+
// An argument's label sits on its own line, above the value.
|
|
118
|
+
name: (_self, data) => rowName(data.row),
|
|
119
|
+
// A permission row: its class label and three bit cells.
|
|
120
|
+
permClass: (_self, data) => data.row?.label ?? data.row?.classId ?? "",
|
|
121
|
+
isPerm: (_self, data) => type(data) === "perm",
|
|
105
122
|
rowBg: (_self, data) => (focused(data) ? env.selectionBg : undefined),
|
|
106
123
|
rowClass: (_self, data) =>
|
|
107
124
|
[focused(data) ? env.focusedClass : null, listClass(data)].filter(Boolean).join(" ") || null,
|
|
108
125
|
rowSelected: (_self, data) => `${data.row?.selected === true}`,
|
|
109
126
|
rowFg: (_self, data) => {
|
|
110
|
-
if (type(data) === "group") return env
|
|
127
|
+
if (type(data) === "group") return groupFg(data.row?.group ?? data.row?.name, env)
|
|
111
128
|
const risk = riskFg(data.row, env)
|
|
112
129
|
if (risk) return risk
|
|
113
130
|
if (focused(data)) return env.accentFg
|
|
@@ -124,27 +141,66 @@ function createBehaviors(env) {
|
|
|
124
141
|
// --- layout branches ---
|
|
125
142
|
isArg: (_self, data) => type(data) === "arg",
|
|
126
143
|
isItem: (_self, data) => type(data) === "item",
|
|
144
|
+
isOp: (_self, data) => type(data) === "op",
|
|
145
|
+
isGroup: (_self, data) => type(data) === "group",
|
|
146
|
+
isEntry: (_self, data) => type(data) === "entry" || type(data) === "parent",
|
|
147
|
+
isRun: (_self, data) => type(data) === "run",
|
|
148
|
+
hasRunError: (_self, data) => type(data) === "run" && Boolean(data.row?.error),
|
|
127
149
|
isFrame: (_self, data) => type(data) === "frame",
|
|
128
150
|
isList: (_self, data) => isList(data),
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
151
|
+
showPlain: (_self, data) => type(data) === "op" || type(data) === "entry" || type(data) === "parent",
|
|
152
|
+
isPlain: (_self, data) => type(data) === "entry" || type(data) === "parent",
|
|
153
|
+
// An argument stacks its label over its value; every other row is one
|
|
154
|
+
// line tall.
|
|
155
|
+
rowHeight: (_self, data) => (type(data) === "arg" ? 2 : 1),
|
|
156
|
+
runMarginTop: (_self, data) => (type(data) === "run" ? 1 : 0),
|
|
157
|
+
runLabel: (_self, data) => (data?.row?.error ? "[Retry]" : data?.row?.armed ? "[Confirm]" : "[Run]"),
|
|
158
|
+
runAriaLabel: (_self, data) =>
|
|
159
|
+
data?.row?.error ? "Retry command" : data?.row?.armed ? "Confirm command" : "Run command",
|
|
160
|
+
runError: (_self, data) => data.row?.error ?? "",
|
|
161
|
+
runErrorFg: () => env.errorFg,
|
|
162
|
+
runFg: (_self, data) => riskFg(data.row, env) ?? env.textFg,
|
|
163
|
+
groupAttributes: () => env.groupAttributes ?? 0,
|
|
164
|
+
editable: (_self, data) => `${data.row?.editable === true}`,
|
|
165
|
+
// Editable values have a filled bracketed field; read-only summaries
|
|
166
|
+
// stay plain and muted.
|
|
167
|
+
fieldBg: (_self, data) => (data.row?.textual ? env.fieldBg : undefined),
|
|
168
|
+
fieldFg: (_self, data) =>
|
|
169
|
+
data.row?.textual ? (env.fieldFg ?? env.textFg) : (env.readonlyFg ?? env.textSecondaryFg),
|
|
170
|
+
textual: (_self, data) => data.row?.textual === true,
|
|
171
|
+
fieldOpen: (_self, data) => (data.row?.textual ? "[" : ""),
|
|
172
|
+
fieldClose: (_self, data) => (data.row?.textual ? "]" : ""),
|
|
173
|
+
// Selection lists share the argument pane's one-cell inset; the spacer
|
|
174
|
+
// is NBSP so the renderers do not collapse the leading run.
|
|
175
|
+
rowIndent: (_self, data) => (isList(data) ? 1 : 0),
|
|
176
|
+
listIndent: (_self, data) => 1,
|
|
177
|
+
listSpacer: (_self, data) => "\u00a0",
|
|
142
178
|
listBorder: (_self, data) => {
|
|
143
179
|
if (type(data) !== "frame") return ["left", "right"]
|
|
144
180
|
return data.row?.variant === "top" ? ["top", "left", "right"] : ["bottom", "left", "right"]
|
|
145
181
|
},
|
|
146
182
|
borderFg: () => env.borderSecondaryFg,
|
|
147
183
|
}
|
|
184
|
+
// r/w/x cells of a permission row, in the shared permission order so the
|
|
185
|
+
// templates can bind each cell without looping.
|
|
186
|
+
for (let i = 0; i < PERMISSION_BITS.length; i++) {
|
|
187
|
+
const bit = PERMISSION_BITS[i]
|
|
188
|
+
const name = bit.id.toUpperCase()
|
|
189
|
+
behaviors[`perm${name}`] = (_self, data) => (bitAt(data, i)?.selected ? bit.id : "-")
|
|
190
|
+
behaviors[`perm${name}On`] = (_self, data) => `${bitAt(data, i)?.selected === true}`
|
|
191
|
+
behaviors[`perm${name}Fg`] = (_self, data) =>
|
|
192
|
+
bitAt(data, i)?.selected ? env.accentSecondaryFg : env.textSecondaryFg
|
|
193
|
+
behaviors[`perm${name}Bg`] = (_self, data) =>
|
|
194
|
+
bitFocused(data, i) ? env.accentSecondaryBg : undefined
|
|
195
|
+
behaviors[`perm${name}Class`] = (_self, data) =>
|
|
196
|
+
[
|
|
197
|
+
bitAt(data, i)?.selected ? "is-on" : "is-off",
|
|
198
|
+
bitFocused(data, i) ? "is-bit-focused" : null,
|
|
199
|
+
]
|
|
200
|
+
.filter(Boolean)
|
|
201
|
+
.join(" ")
|
|
202
|
+
}
|
|
203
|
+
return behaviors
|
|
148
204
|
}
|
|
149
205
|
|
|
150
206
|
export { createBehaviors, isFocused, riskFg, rowName, rowSummary, wantsFocus }
|