@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,276 @@
|
|
|
1
|
+
// Project: TinyCommand
|
|
2
|
+
// Vendored dependency
|
|
3
|
+
//
|
|
4
|
+
// Module: tcmd/vendor/bzip2
|
|
5
|
+
// bzip2.js — a small bzip2 decompression implementation.
|
|
6
|
+
//
|
|
7
|
+
// Source: https://github.com/antimatter15/bzip2.js (bzip2.js)
|
|
8
|
+
// Author: antimatter15
|
|
9
|
+
// License: MIT
|
|
10
|
+
//
|
|
11
|
+
// Vendored verbatim (only this header and the trailing export were added) so
|
|
12
|
+
// TinyCommand can preview `.bz2` files without adding a runtime dependency.
|
|
13
|
+
// `array` wraps the bytes in a bit reader; `header` returns the block size and
|
|
14
|
+
// `decompress` decodes one block, returning `-1` on the stream terminator.
|
|
15
|
+
// Decoded output is a binary string (one char code per byte), so callers must
|
|
16
|
+
// decode it as UTF-8 themselves.
|
|
17
|
+
//
|
|
18
|
+
// MIT License
|
|
19
|
+
//
|
|
20
|
+
// Copyright (c) 2011 by antimatter15 (antimatter15@gmail.com).
|
|
21
|
+
//
|
|
22
|
+
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
23
|
+
// copy of this software and associated documentation files (the "Software"),
|
|
24
|
+
// to deal in the Software without restriction, including without limitation
|
|
25
|
+
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
26
|
+
// and/or sell copies of the Software, and to permit persons to whom the
|
|
27
|
+
// Software is furnished to do so, subject to the following conditions:
|
|
28
|
+
//
|
|
29
|
+
// The above copyright notice and this permission notice shall be included in
|
|
30
|
+
// all copies or substantial portions of the Software.
|
|
31
|
+
//
|
|
32
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
33
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
34
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
35
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
36
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
37
|
+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
38
|
+
// IN THE SOFTWARE.
|
|
39
|
+
|
|
40
|
+
var bzip2 = {};
|
|
41
|
+
|
|
42
|
+
bzip2.array = function(bytes){
|
|
43
|
+
var bit = 0, byte = 0;
|
|
44
|
+
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ];
|
|
45
|
+
return function(n){
|
|
46
|
+
var result = 0;
|
|
47
|
+
while(n > 0){
|
|
48
|
+
var left = 8 - bit;
|
|
49
|
+
if(n >= left){
|
|
50
|
+
result <<= left;
|
|
51
|
+
result |= (BITMASK[left] & bytes[byte++]);
|
|
52
|
+
bit = 0;
|
|
53
|
+
n -= left;
|
|
54
|
+
}else{
|
|
55
|
+
result <<= n;
|
|
56
|
+
result |= ((bytes[byte] & (BITMASK[n] << (8 - n - bit))) >> (8 - n - bit));
|
|
57
|
+
bit += n;
|
|
58
|
+
n = 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
bzip2.simple = function(bits){
|
|
66
|
+
var size = bzip2.header(bits);
|
|
67
|
+
var all = '', chunk = '';
|
|
68
|
+
do{
|
|
69
|
+
all += chunk;
|
|
70
|
+
chunk = bzip2.decompress(bits, size);
|
|
71
|
+
}while(chunk != -1);
|
|
72
|
+
return all;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
bzip2.header = function(bits){
|
|
76
|
+
if(bits(8*3) != 4348520) throw "No magic number found";
|
|
77
|
+
var i = bits(8) - 48;
|
|
78
|
+
if(i < 1 || i > 9) throw "Not a BZIP archive";
|
|
79
|
+
return i;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
//takes a function for reading the block data (starting with 0x314159265359)
|
|
84
|
+
//a block size (0-9) (optional, defaults to 9)
|
|
85
|
+
//a length at which to stop decompressing and return the output
|
|
86
|
+
bzip2.decompress = function(bits, size, len){
|
|
87
|
+
var MAX_HUFCODE_BITS = 20;
|
|
88
|
+
var MAX_SYMBOLS = 258;
|
|
89
|
+
var SYMBOL_RUNA = 0;
|
|
90
|
+
var SYMBOL_RUNB = 1;
|
|
91
|
+
var GROUP_SIZE = 50;
|
|
92
|
+
|
|
93
|
+
var bufsize = 100000 * size;
|
|
94
|
+
for(var h = '', i = 0; i < 6; i++) h += bits(8).toString(16);
|
|
95
|
+
if(h == "177245385090") return -1; //last block
|
|
96
|
+
if(h != "314159265359") throw "eek not valid bzip data";
|
|
97
|
+
bits(32); //ignore CRC codes
|
|
98
|
+
if(bits(1)) throw "unsupported obsolete version";
|
|
99
|
+
var origPtr = bits(24);
|
|
100
|
+
if(origPtr > bufsize) throw "Initial position larger than buffer size";
|
|
101
|
+
var t = bits(16);
|
|
102
|
+
var symToByte = new Uint8Array(256),
|
|
103
|
+
symTotal = 0;
|
|
104
|
+
for (i = 0; i < 16; i++) {
|
|
105
|
+
if(t & (1 << (15 - i))) {
|
|
106
|
+
var k = bits(16);
|
|
107
|
+
for(j = 0; j < 16; j++){
|
|
108
|
+
if(k & (1 << (15 - j))){
|
|
109
|
+
symToByte[symTotal++] = (16 * i) + j;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
var groupCount = bits(3);
|
|
116
|
+
if(groupCount < 2 || groupCount > 6) throw "another error";
|
|
117
|
+
var nSelectors = bits(15);
|
|
118
|
+
if(nSelectors == 0) throw "meh";
|
|
119
|
+
var mtfSymbol = []; //TODO: possibly replace JS array with typed arrays
|
|
120
|
+
for(var i = 0; i < groupCount; i++) mtfSymbol[i] = i;
|
|
121
|
+
var selectors = new Uint8Array(32768);
|
|
122
|
+
|
|
123
|
+
for(var i = 0; i < nSelectors; i++){
|
|
124
|
+
for(var j = 0; bits(1); j++) if(j >= groupCount) throw "whoops another error";
|
|
125
|
+
var uc = mtfSymbol[j];
|
|
126
|
+
mtfSymbol.splice(j, 1); //this is a probably inefficient MTF transform
|
|
127
|
+
mtfSymbol.splice(0, 0, uc);
|
|
128
|
+
selectors[i] = uc;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
var symCount = symTotal + 2;
|
|
132
|
+
var groups = [];
|
|
133
|
+
for(var j = 0; j < groupCount; j++){
|
|
134
|
+
var length = new Uint8Array(MAX_SYMBOLS),
|
|
135
|
+
temp = new Uint8Array(MAX_HUFCODE_BITS+1);
|
|
136
|
+
t = bits(5); //lengths
|
|
137
|
+
for(var i = 0; i < symCount; i++){
|
|
138
|
+
while(true){
|
|
139
|
+
if (t < 1 || t > MAX_HUFCODE_BITS) throw "I gave up a while ago on writing error messages";
|
|
140
|
+
if(!bits(1)) break;
|
|
141
|
+
if(!bits(1)) t++;
|
|
142
|
+
else t--;
|
|
143
|
+
}
|
|
144
|
+
length[i] = t;
|
|
145
|
+
}
|
|
146
|
+
var minLen, maxLen;
|
|
147
|
+
minLen = maxLen = length[0];
|
|
148
|
+
for(var i = 1; i < symCount; i++){
|
|
149
|
+
if(length[i] > maxLen) maxLen = length[i];
|
|
150
|
+
else if(length[i] < minLen) minLen = length[i];
|
|
151
|
+
}
|
|
152
|
+
var hufGroup;
|
|
153
|
+
hufGroup = groups[j] = {};
|
|
154
|
+
hufGroup.permute = new Uint32Array(MAX_SYMBOLS);
|
|
155
|
+
hufGroup.limit = new Uint32Array(MAX_HUFCODE_BITS + 1);
|
|
156
|
+
hufGroup.base = new Uint32Array(MAX_HUFCODE_BITS + 1);
|
|
157
|
+
hufGroup.minLen = minLen;
|
|
158
|
+
hufGroup.maxLen = maxLen;
|
|
159
|
+
var base = hufGroup.base.subarray(1);
|
|
160
|
+
var limit = hufGroup.limit.subarray(1);
|
|
161
|
+
var pp = 0;
|
|
162
|
+
for(var i = minLen; i <= maxLen; i++)
|
|
163
|
+
for(var t = 0; t < symCount; t++)
|
|
164
|
+
if(length[t] == i) hufGroup.permute[pp++] = t;
|
|
165
|
+
for(i = minLen; i <= maxLen; i++) temp[i] = limit[i] = 0;
|
|
166
|
+
for(i = 0; i < symCount; i++) temp[length[i]]++;
|
|
167
|
+
pp = t = 0;
|
|
168
|
+
for(i = minLen; i < maxLen; i++) {
|
|
169
|
+
pp += temp[i];
|
|
170
|
+
limit[i] = pp - 1;
|
|
171
|
+
pp <<= 1;
|
|
172
|
+
base[i+1] = pp - (t += temp[i]);
|
|
173
|
+
}
|
|
174
|
+
limit[maxLen]=pp+temp[maxLen]-1;
|
|
175
|
+
base[minLen]=0;
|
|
176
|
+
}
|
|
177
|
+
var byteCount = new Uint32Array(256);
|
|
178
|
+
for(var i = 0; i < 256; i++) mtfSymbol[i] = i;
|
|
179
|
+
var runPos, count, symCount, selector;
|
|
180
|
+
runPos = count = symCount = selector = 0;
|
|
181
|
+
var buf = new Uint32Array(bufsize);
|
|
182
|
+
while(true){
|
|
183
|
+
if(!(symCount--)){
|
|
184
|
+
symCount = GROUP_SIZE - 1;
|
|
185
|
+
if(selector >= nSelectors) throw "meow i'm a kitty, that's an error";
|
|
186
|
+
hufGroup = groups[selectors[selector++]];
|
|
187
|
+
base = hufGroup.base.subarray(1);
|
|
188
|
+
limit = hufGroup.limit.subarray(1);
|
|
189
|
+
}
|
|
190
|
+
i = hufGroup.minLen;
|
|
191
|
+
j = bits(i);
|
|
192
|
+
while(true){
|
|
193
|
+
if(i > hufGroup.maxLen) throw "rawr i'm a dinosaur";
|
|
194
|
+
if(j <= limit[i]) break;
|
|
195
|
+
i++;
|
|
196
|
+
j = (j << 1) | bits(1);
|
|
197
|
+
}
|
|
198
|
+
j -= base[i];
|
|
199
|
+
if(j < 0 || j >= MAX_SYMBOLS) throw "moo i'm a cow";
|
|
200
|
+
var nextSym = hufGroup.permute[j];
|
|
201
|
+
if (nextSym == SYMBOL_RUNA || nextSym == SYMBOL_RUNB) {
|
|
202
|
+
if(!runPos){
|
|
203
|
+
runPos = 1;
|
|
204
|
+
t = 0;
|
|
205
|
+
}
|
|
206
|
+
if(nextSym == SYMBOL_RUNA) t += runPos;
|
|
207
|
+
else t += 2 * runPos;
|
|
208
|
+
runPos <<= 1;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if(runPos){
|
|
212
|
+
runPos = 0;
|
|
213
|
+
if(count + t >= bufsize) throw "Boom.";
|
|
214
|
+
uc = symToByte[mtfSymbol[0]];
|
|
215
|
+
byteCount[uc] += t;
|
|
216
|
+
while(t--) buf[count++] = uc;
|
|
217
|
+
}
|
|
218
|
+
if(nextSym > symTotal) break;
|
|
219
|
+
if(count >= bufsize) throw "I can't think of anything. Error";
|
|
220
|
+
i = nextSym -1;
|
|
221
|
+
uc = mtfSymbol[i];
|
|
222
|
+
mtfSymbol.splice(i, 1);
|
|
223
|
+
mtfSymbol.splice(0, 0, uc);
|
|
224
|
+
uc = symToByte[uc];
|
|
225
|
+
byteCount[uc]++;
|
|
226
|
+
buf[count++] = uc;
|
|
227
|
+
}
|
|
228
|
+
if(origPtr < 0 || origPtr >= count) throw "I'm a monkey and I'm throwing something at someone, namely you";
|
|
229
|
+
var j = 0;
|
|
230
|
+
for(var i = 0; i < 256; i++){
|
|
231
|
+
k = j + byteCount[i];
|
|
232
|
+
byteCount[i] = j;
|
|
233
|
+
j = k;
|
|
234
|
+
}
|
|
235
|
+
for(var i = 0; i < count; i++){
|
|
236
|
+
uc = buf[i] & 0xff;
|
|
237
|
+
buf[byteCount[uc]] |= (i << 8);
|
|
238
|
+
byteCount[uc]++;
|
|
239
|
+
}
|
|
240
|
+
var pos = 0, current = 0, run = 0;
|
|
241
|
+
if(count) {
|
|
242
|
+
pos = buf[origPtr];
|
|
243
|
+
current = (pos & 0xff);
|
|
244
|
+
pos >>= 8;
|
|
245
|
+
run = -1;
|
|
246
|
+
}
|
|
247
|
+
count = count;
|
|
248
|
+
var output = '';
|
|
249
|
+
var copies, previous, outbyte;
|
|
250
|
+
if(!len) len = Infinity;
|
|
251
|
+
while(count){
|
|
252
|
+
count--;
|
|
253
|
+
previous = current;
|
|
254
|
+
pos = buf[pos];
|
|
255
|
+
current = pos & 0xff;
|
|
256
|
+
pos >>= 8;
|
|
257
|
+
if(run++ == 3){
|
|
258
|
+
copies = current;
|
|
259
|
+
outbyte = previous;
|
|
260
|
+
current = -1;
|
|
261
|
+
}else{
|
|
262
|
+
copies = 1;
|
|
263
|
+
outbyte = current;
|
|
264
|
+
}
|
|
265
|
+
while(copies--){
|
|
266
|
+
output += (String.fromCharCode(outbyte));
|
|
267
|
+
if(!--len) return output;
|
|
268
|
+
}
|
|
269
|
+
if(current != previous) run = 0;
|
|
270
|
+
}
|
|
271
|
+
return output;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export { bzip2 }
|
|
275
|
+
|
|
276
|
+
// EOF
|
package/src/js/tcmd/web/app.js
CHANGED
|
@@ -14,8 +14,8 @@ import appHtml from "../components/App.html" with { type: "text" }
|
|
|
14
14
|
import { appBehaviors, appEvents, appInit } from "../components/index.js"
|
|
15
15
|
import { registerWebComponents } from "./catalogue.js"
|
|
16
16
|
import { elideCrumbs } from "../components/format.js"
|
|
17
|
-
import { createFrameBehaviors, INSPECTOR_INLINE_WIDTH,
|
|
18
|
-
import { connectEffects } from "./effects.js"
|
|
17
|
+
import { createFrameBehaviors, INSPECTOR_INLINE_WIDTH, paneColumnWidth } from "../components/Frame.js"
|
|
18
|
+
import { connectEffects, isToolbarTarget } from "./effects.js"
|
|
19
19
|
import { webTheme } from "./theme.js"
|
|
20
20
|
|
|
21
21
|
// Map DOM key names to the core keymap chord names.
|
|
@@ -59,23 +59,31 @@ function paneClass(data, paneId) {
|
|
|
59
59
|
|
|
60
60
|
function webViewBehaviors() {
|
|
61
61
|
return {
|
|
62
|
+
// The view mode as an app-level class (`is-explorer` / `is-commander`),
|
|
63
|
+
// so the CSS can tint the active pane per mode (blue/gold).
|
|
64
|
+
appModeClass: (_self, data) => [
|
|
65
|
+
`is-${unwrap(data.viewMode) === "explorer" ? "explorer" : "commander"}`,
|
|
66
|
+
unwrap(data.desktopMode) ? "is-desktop" : null,
|
|
67
|
+
].filter(Boolean).join(" "),
|
|
62
68
|
headerView: (_self, data) => ({ host: data.appHost, path: data.appPath, mode: data.viewMode }),
|
|
69
|
+
desktopViewLabel: (_self, data) => unwrap(data.viewMode) === "explorer" ? "Explorer" : "Commander",
|
|
70
|
+
desktopPreviewLabel: (_self, data) => unwrap(data.showPreview) ? "Hide preview" : "Show preview",
|
|
63
71
|
bookmarkView: (_self, data) => ({
|
|
64
72
|
items: data.bookmarkItems,
|
|
65
|
-
mode: data.bookmarkMode,
|
|
66
73
|
open: data.bookmarkOpen,
|
|
67
74
|
query: data.bookmarkQuery,
|
|
68
75
|
selected: data.bookmarkIndex,
|
|
69
|
-
target: data.bookmarkTarget,
|
|
70
76
|
}),
|
|
71
77
|
commandBarView: (_self, data) => ({
|
|
72
78
|
items: data.commandBarItems,
|
|
79
|
+
desktopMode: data.desktopMode,
|
|
73
80
|
leftError: data.leftError,
|
|
74
81
|
notice: data.notice,
|
|
75
82
|
rightError: data.rightError,
|
|
76
83
|
active: data.active,
|
|
77
84
|
leftMarked: data.leftMarked,
|
|
78
85
|
rightMarked: data.rightMarked,
|
|
86
|
+
uiSize: data.uiSize,
|
|
79
87
|
}),
|
|
80
88
|
confirmView: (_self, data) => ({ confirm: data.confirm }),
|
|
81
89
|
leftCrumbsView: (_self, data) => ({
|
|
@@ -99,13 +107,8 @@ function webViewBehaviors() {
|
|
|
99
107
|
loading: data.leftLoading,
|
|
100
108
|
}),
|
|
101
109
|
leftPaneClass: (_self, data) => paneClass(data, "left"),
|
|
102
|
-
leftSummaryView: (_self, data) => ({
|
|
103
|
-
entries: data.leftEntries,
|
|
104
|
-
disk: data.leftDisk,
|
|
105
|
-
width: data.paneWidth,
|
|
106
|
-
}),
|
|
107
110
|
// The explorer's first column: the parent of the current directory,
|
|
108
|
-
// a full pane of its own (header, filter, listing, detail
|
|
111
|
+
// a full pane of its own (header, filter, listing, detail).
|
|
109
112
|
parentCrumbsView: (_self, data) => ({
|
|
110
113
|
segments: data.parentSegments,
|
|
111
114
|
paneId: data.parentId,
|
|
@@ -131,11 +134,6 @@ function webViewBehaviors() {
|
|
|
131
134
|
loading: data.parentLoading,
|
|
132
135
|
}),
|
|
133
136
|
parentPaneClass: (_self, data) => paneClass(data, "parent"),
|
|
134
|
-
parentSummaryView: (_self, data) => ({
|
|
135
|
-
entries: data.parentEntries,
|
|
136
|
-
disk: data.parentDisk,
|
|
137
|
-
width: data.paneWidth,
|
|
138
|
-
}),
|
|
139
137
|
panelView: (_self, data) => ({
|
|
140
138
|
// Cells pass through (as `in:` slots do in the TUI); `query`/`value`
|
|
141
139
|
// are written back by the inputs' `inout` two-way bindings.
|
|
@@ -145,17 +143,22 @@ function webViewBehaviors() {
|
|
|
145
143
|
opCursor: data.panelOpIndex,
|
|
146
144
|
rows: data.panelRows,
|
|
147
145
|
argCursor: data.panelRowIndex,
|
|
148
|
-
labelWidth: data.panelLabelWidth,
|
|
149
146
|
focus: data.panelFocus,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
147
|
+
preview: data.panelPreview,
|
|
148
|
+
picking: data.pickerOpen,
|
|
149
|
+
pickerPathText: data.pickerPathText,
|
|
150
|
+
pickerFocus: data.pickerFocus,
|
|
151
|
+
pickerRows: data.pickerRows,
|
|
155
152
|
query: data.panelQuery,
|
|
156
153
|
value: data.panelValue,
|
|
157
154
|
}),
|
|
158
155
|
helpView: (_self, data) => ({ open: data.helpOpen }),
|
|
156
|
+
overlayView: (_self, data) => ({
|
|
157
|
+
open: data.overlay,
|
|
158
|
+
mode: data.overlay,
|
|
159
|
+
items: data.overlayItems,
|
|
160
|
+
cursor: data.overlayIndex,
|
|
161
|
+
}),
|
|
159
162
|
previewView: (_self, data) => ({
|
|
160
163
|
preview: data.preview,
|
|
161
164
|
show: true,
|
|
@@ -191,11 +194,6 @@ function webViewBehaviors() {
|
|
|
191
194
|
loading: data.rightLoading,
|
|
192
195
|
}),
|
|
193
196
|
rightPaneClass: (_self, data) => paneClass(data, "right"),
|
|
194
|
-
rightSummaryView: (_self, data) => ({
|
|
195
|
-
entries: data.rightEntries,
|
|
196
|
-
disk: data.rightDisk,
|
|
197
|
-
width: data.paneWidth,
|
|
198
|
-
}),
|
|
199
197
|
}
|
|
200
198
|
}
|
|
201
199
|
|
|
@@ -205,28 +203,46 @@ function createWebApp(session, options = {}) {
|
|
|
205
203
|
registerWebComponents()
|
|
206
204
|
const size = options.size ?? cell({ width: 0, height: 0 })
|
|
207
205
|
const help = cell(false)
|
|
208
|
-
|
|
206
|
+
// The fullscreen inspector overlay lives in the session (core), so a
|
|
207
|
+
// command — Enter on a file that cannot be edited — can open it; the shared
|
|
208
|
+
// controller routes its keys (`components/App#createBehaviors`).
|
|
209
|
+
const viewer = session.viewerOpen
|
|
209
210
|
const paneWidth = derived([size, session.showPreview], ([value, show]) => {
|
|
210
211
|
if (!value.width) return 0
|
|
211
212
|
const columns = show && value.width >= INSPECTOR_INLINE_WIDTH ? 3 : 2
|
|
212
213
|
return Math.max(1, Math.floor(value.width / columns) - 4)
|
|
213
214
|
})
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
// Breadcrumb budgets follow each pane's OWN share of the row (explorer
|
|
216
|
+
// grows the current column, so its mirrors are narrower than an even
|
|
217
|
+
// split), or the crumbs overflow the narrower columns.
|
|
218
|
+
const crumbWidth = (paneId) =>
|
|
219
|
+
derived([size, session.showPreview, session.viewMode, session.currentId], ([value, showPreview, viewMode, currentId]) => {
|
|
220
|
+
const columns = paneColumnWidth(value?.width, paneId, { viewMode, currentId, showPreview })
|
|
221
|
+
return columns > 0 ? Math.max(1, columns - 4) : 0
|
|
222
|
+
})
|
|
223
|
+
const crumbs = (paneId, pane) =>
|
|
224
|
+
derived([pane.segments, crumbWidth(paneId)], ([segments, width]) => elideCrumbs(segments, width))
|
|
225
|
+
const quit = options.onQuit ?? ((self) => self.unmount?.())
|
|
216
226
|
// DOM inputs have no native `enter` event; the root routes Enter for them.
|
|
217
227
|
const baseKeys = appBehaviors({ normalizeKey, quit }).keys
|
|
218
|
-
const events = appEvents(
|
|
228
|
+
const events = appEvents()
|
|
219
229
|
return ui(appHtml)
|
|
220
|
-
.init(() =>
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
.init((self) => {
|
|
231
|
+
// The panel's Quit command reaches the host through this hook.
|
|
232
|
+
session.quit = () => quit(self)
|
|
233
|
+
return {
|
|
234
|
+
...appInit(session)(),
|
|
235
|
+
desktopMode: Boolean(options.desktopMode),
|
|
236
|
+
uiSize: size,
|
|
237
|
+
helpOpen: help,
|
|
238
|
+
viewerOpen: viewer,
|
|
239
|
+
paneWidth,
|
|
240
|
+
leftSegments: crumbs("left", session.panes.left),
|
|
241
|
+
parentSegments: crumbs("parent", session.panes.parent),
|
|
242
|
+
rightSegments: crumbs("right", session.panes.right),
|
|
243
|
+
previewSegments: crumbs("preview", session.panes.preview),
|
|
244
|
+
}
|
|
245
|
+
})
|
|
230
246
|
.does({
|
|
231
247
|
...createFrameBehaviors({ theme: webTheme }),
|
|
232
248
|
...webViewBehaviors(),
|
|
@@ -240,8 +256,23 @@ function createWebApp(session, options = {}) {
|
|
|
240
256
|
},
|
|
241
257
|
keys: (self, data, event) => {
|
|
242
258
|
if (event.isComposing) return
|
|
259
|
+
const busy = unwrap(data.confirm) || unwrap(data.bookmarkOpen) || unwrap(data.panelOpen) || unwrap(data.filtering) || unwrap(data.overlay)
|
|
260
|
+
if (event.key === "F6" && unwrap(data.desktopMode) && !busy && !help.value && !viewer.value) {
|
|
261
|
+
event.preventDefault()
|
|
262
|
+
const target = isToolbarTarget(event.target) ? self.ref.root : self.ref.root.querySelector(".desktop-toolbar button")
|
|
263
|
+
target?.focus({ preventScroll: true })
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
// Native controls own their activation and traversal keys, not the
|
|
267
|
+
// pane underneath them. Escape hands focus back to the workspace.
|
|
268
|
+
if (isToolbarTarget(event.target)) {
|
|
269
|
+
if (event.key === "Escape") {
|
|
270
|
+
event.preventDefault()
|
|
271
|
+
self.ref.root.focus({ preventScroll: true })
|
|
272
|
+
}
|
|
273
|
+
return
|
|
274
|
+
}
|
|
243
275
|
const key = normalizeKey(event)
|
|
244
|
-
const busy = unwrap(data.confirm) || unwrap(data.bookmarkOpen) || unwrap(data.panelOpen) || unwrap(data.filtering)
|
|
245
276
|
if (help.value) {
|
|
246
277
|
if (["escape", "q", "?"].includes(key.name)) help.set(false)
|
|
247
278
|
return
|
|
@@ -250,27 +281,54 @@ function createWebApp(session, options = {}) {
|
|
|
250
281
|
help.set(true)
|
|
251
282
|
return
|
|
252
283
|
}
|
|
253
|
-
// The viewer
|
|
254
|
-
//
|
|
255
|
-
if (viewer.value && !isNarrow(data, self)) viewer.set(false)
|
|
256
|
-
if (viewer.value) {
|
|
257
|
-
if (key.name === "escape" || key.name === "v") viewer.set(false)
|
|
258
|
-
else if (key.shift && key.name === "o") session.dispatch("file.open")
|
|
259
|
-
else if (key.shift && key.name === "e") session.dispatch("file.edit")
|
|
260
|
-
return
|
|
261
|
-
}
|
|
262
|
-
if (!busy && key.name === "v" && !key.shift && isNarrow(data, self)) {
|
|
263
|
-
viewer.set(!viewer.value)
|
|
264
|
-
return
|
|
265
|
-
}
|
|
284
|
+
// The viewer overlay and the narrow-`v` shortcut are shared
|
|
285
|
+
// modal routing, handled by `components/App#createBehaviors`.
|
|
266
286
|
return baseKeys(self, data, event)
|
|
267
287
|
},
|
|
288
|
+
dialogStop: (_self, _data, event) => event.stopPropagation(),
|
|
268
289
|
})
|
|
269
290
|
.sub({
|
|
270
291
|
...events,
|
|
292
|
+
"dialog:panel-cancel": (_self, data) => {
|
|
293
|
+
if (!unwrap(data.desktopMode)) return
|
|
294
|
+
data.dispatch(unwrap(data.pickerOpen) ? "picker.cancel" : "panel.cancel")
|
|
295
|
+
},
|
|
296
|
+
"dialog:bookmark-cancel": (_self, data) => {
|
|
297
|
+
if (unwrap(data.desktopMode)) data.dispatch("bookmark.cancel")
|
|
298
|
+
},
|
|
299
|
+
"dialog:overlay-cancel": (_self, data) => {
|
|
300
|
+
if (unwrap(data.desktopMode)) data.dispatch("overlay.cancel")
|
|
301
|
+
},
|
|
302
|
+
"dialog:confirm-cancel": (_self, data) => {
|
|
303
|
+
if (unwrap(data.desktopMode)) data.dispatch("confirm.no")
|
|
304
|
+
},
|
|
305
|
+
"dialog:help-cancel": (_self, data) => {
|
|
306
|
+
if (unwrap(data.desktopMode)) help.set(false)
|
|
307
|
+
},
|
|
308
|
+
"dialog:viewer-cancel": (_self, data) => {
|
|
309
|
+
if (unwrap(data.desktopMode)) data.dispatch("view.toggleViewer")
|
|
310
|
+
},
|
|
311
|
+
"toolbar:open-folder": async () => {
|
|
312
|
+
try {
|
|
313
|
+
await options.onOpenFolder?.()
|
|
314
|
+
} catch (error) {
|
|
315
|
+
await options.onError?.(error)
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
"toolbar:back": (_self, data) => data.dispatch("pane.back"),
|
|
319
|
+
"toolbar:forward": (_self, data) => data.dispatch("pane.forward"),
|
|
320
|
+
"toolbar:up": (_self, data) => data.dispatch("pane.up"),
|
|
321
|
+
"toolbar:refresh": (_self, data) => data.dispatch("pane.reload"),
|
|
322
|
+
"toolbar:toggle-mode": (_self, data) => data.dispatch("view.toggleMode"),
|
|
323
|
+
"toolbar:toggle-preview": (_self, data) => data.dispatch("view.togglePreview"),
|
|
324
|
+
"toolbar:commands": (_self, data) => data.dispatch("panel.open"),
|
|
325
|
+
"toolbar:help": () => help.set(true),
|
|
271
326
|
"commandbar:run": (self, data, event) => {
|
|
272
|
-
if (event.data === "
|
|
273
|
-
|
|
327
|
+
if (event.data === "app.help") {
|
|
328
|
+
help.set(!help.value)
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
events["commandbar:run"](self, data, event)
|
|
274
332
|
},
|
|
275
333
|
})
|
|
276
334
|
}
|
|
@@ -24,11 +24,16 @@ import confirmHtml from "../components/Confirm.html" with { type: "text" }
|
|
|
24
24
|
import crumbHtml from "../components/Crumb.html" with { type: "text" }
|
|
25
25
|
import entryDetailHtml from "../components/EntryDetail.html" with { type: "text" }
|
|
26
26
|
import fileListHtml from "../components/FileList.html" with { type: "text" }
|
|
27
|
+
import filePickerHtml from "../components/FilePicker.html" with { type: "text" }
|
|
28
|
+
import filePickerRowHtml from "../components/FilePickerRow.html" with { type: "text" }
|
|
27
29
|
import filePreviewHtml from "../components/FilePreview.html" with { type: "text" }
|
|
28
30
|
import fileRowHtml from "../components/FileRow.html" with { type: "text" }
|
|
29
31
|
import filterBarHtml from "../components/FilterBar.html" with { type: "text" }
|
|
30
32
|
import helpHtml from "../components/Help.html" with { type: "text" }
|
|
33
|
+
import helpRowHtml from "../components/HelpRow.html" with { type: "text" }
|
|
31
34
|
import headerHtml from "./header.html" with { type: "text" }
|
|
35
|
+
import overlayListHtml from "../components/OverlayList.html" with { type: "text" }
|
|
36
|
+
import overlayRowHtml from "../components/OverlayRow.html" with { type: "text" }
|
|
32
37
|
import rowListHtml from "../components/RowList.html" with { type: "text" }
|
|
33
38
|
import summaryHtml from "../components/Summary.html" with { type: "text" }
|
|
34
39
|
|
|
@@ -39,6 +44,7 @@ import {
|
|
|
39
44
|
bookmarkRows,
|
|
40
45
|
breadcrumbsBehaviors,
|
|
41
46
|
commandBarBehaviors,
|
|
47
|
+
commandBarFit,
|
|
42
48
|
commandBarMarksItem,
|
|
43
49
|
commandArgRowBehaviors,
|
|
44
50
|
commandItemBehaviors,
|
|
@@ -49,13 +55,22 @@ import {
|
|
|
49
55
|
crumbRows,
|
|
50
56
|
entryDetailBehaviors,
|
|
51
57
|
fileListBehaviors,
|
|
58
|
+
filePickerBehaviors,
|
|
59
|
+
filePickerRowBehaviors,
|
|
60
|
+
filePickerRows,
|
|
52
61
|
filePreviewBehaviors,
|
|
53
62
|
fileRowBehaviors,
|
|
54
63
|
fileRows,
|
|
55
64
|
filterBarBehaviors,
|
|
56
65
|
helpBehaviors,
|
|
66
|
+
helpRowBehaviors,
|
|
67
|
+
helpRows,
|
|
68
|
+
overlayListBehaviors,
|
|
69
|
+
overlayRowBehaviors,
|
|
70
|
+
overlayRows,
|
|
57
71
|
panelArgRows,
|
|
58
72
|
panelOpRows,
|
|
73
|
+
panelRunRows,
|
|
59
74
|
summaryBehaviors,
|
|
60
75
|
} from "../components/index.js"
|
|
61
76
|
import { createBehaviors as headerBehaviors } from "./header.js"
|
|
@@ -79,16 +94,19 @@ function elide(text, width) {
|
|
|
79
94
|
// Binds every shared component to its `.html` presentation.
|
|
80
95
|
function createComponents() {
|
|
81
96
|
const commandItem = bind(ui, "CommandItem", commandItemHtml, commandItemBehaviors(webTheme))
|
|
82
|
-
//
|
|
83
|
-
//
|
|
97
|
+
// Keep command namespace markers textual; the bundled Nerd face is reserved
|
|
98
|
+
// for filesystem names and paths.
|
|
84
99
|
const commandArgRow = bind(ui, "CommandArgRow", commandArgRowHtml, commandArgRowBehaviors({
|
|
85
100
|
...webTheme,
|
|
86
101
|
icons: false,
|
|
87
102
|
}))
|
|
88
103
|
const crumb = bind(ui, "Crumb", crumbHtml, crumbBehaviors(webTheme))
|
|
89
104
|
const fileRow = bind(ui, "FileRow", fileRowHtml, fileRowBehaviors(webTheme))
|
|
105
|
+
const filePickerRow = bind(ui, "FilePickerRow", filePickerRowHtml, filePickerRowBehaviors(webTheme))
|
|
90
106
|
const filterBar = bind(ui, "FilterBar", filterBarHtml, filterBarBehaviors(webTheme))
|
|
91
107
|
const bookmarkRow = bind(ui, "BookmarkRow", bookmarkRowHtml, bookmarkRowBehaviors(webTheme))
|
|
108
|
+
const overlayRow = bind(ui, "OverlayRow", overlayRowHtml, overlayRowBehaviors(webTheme))
|
|
109
|
+
const helpRow = bind(ui, "HelpRow", helpRowHtml, helpRowBehaviors({ theme: webTheme }))
|
|
92
110
|
return {
|
|
93
111
|
BookmarkRow: bookmarkRow,
|
|
94
112
|
CommandItem: commandItem,
|
|
@@ -108,15 +126,24 @@ function createComponents() {
|
|
|
108
126
|
...webTheme,
|
|
109
127
|
listClass: "command-bar-keys",
|
|
110
128
|
rows: (self, data) => {
|
|
129
|
+
const viewport = unwrap(data.uiSize)
|
|
130
|
+
const viewportWidth = Math.floor(viewport?.width ?? 0)
|
|
131
|
+
// The measured box is exact but still shows the previous width on
|
|
132
|
+
// the frame a resize arrives; prefer the injected viewport then.
|
|
133
|
+
const width = viewportWidth > 0 ? viewportWidth - 1 : columnWidth(self.ref.box)
|
|
111
134
|
const marks = commandBarMarksItem(data)
|
|
112
135
|
const items = unwrap(data.items) ?? []
|
|
113
|
-
return commandItemRows(commandItem)(self, {
|
|
136
|
+
return commandItemRows(commandItem)(self, {
|
|
137
|
+
...data,
|
|
138
|
+
items: commandBarFit(marks ? [...items, marks] : items, width),
|
|
139
|
+
})
|
|
114
140
|
},
|
|
115
141
|
})),
|
|
116
142
|
CommandPanel: bind(ui, "CommandPanel", commandPanelHtml, commandPanelBehaviors({
|
|
117
143
|
...webTheme,
|
|
118
144
|
opRows: panelOpRows(commandArgRow),
|
|
119
145
|
argRows: panelArgRows(commandArgRow),
|
|
146
|
+
runRows: panelRunRows(commandArgRow),
|
|
120
147
|
})),
|
|
121
148
|
Confirm: bind(ui, "Confirm", confirmHtml, confirmBehaviors(webTheme)),
|
|
122
149
|
EntryDetail: bind(ui, "EntryDetail", entryDetailHtml, entryDetailBehaviors({ ...webTheme, width: paneWidth, elide })),
|
|
@@ -124,6 +151,10 @@ function createComponents() {
|
|
|
124
151
|
...webTheme,
|
|
125
152
|
rows: fileRows(fileRow),
|
|
126
153
|
})),
|
|
154
|
+
FilePicker: bind(ui, "FilePicker", filePickerHtml, filePickerBehaviors({
|
|
155
|
+
...webTheme,
|
|
156
|
+
rows: filePickerRows(filePickerRow),
|
|
157
|
+
})),
|
|
127
158
|
FilePreview: bind(ui, "FilePreview", filePreviewHtml, filePreviewBehaviors({
|
|
128
159
|
...webTheme,
|
|
129
160
|
// Open/edit/run are TUI-only: the browser says so instead of
|
|
@@ -131,8 +162,12 @@ function createComponents() {
|
|
|
131
162
|
actions: () => " Open / edit are unavailable in the browser",
|
|
132
163
|
})),
|
|
133
164
|
FilterBar: filterBar,
|
|
134
|
-
Help: bind(ui, "Help", helpHtml, helpBehaviors({ theme: webTheme })),
|
|
165
|
+
Help: bind(ui, "Help", helpHtml, helpBehaviors({ theme: webTheme, rows: helpRows(helpRow) })),
|
|
135
166
|
Header: bind(ui, "Header", headerHtml, headerBehaviors(webTheme)),
|
|
167
|
+
OverlayList: bind(ui, "OverlayList", overlayListHtml, overlayListBehaviors({
|
|
168
|
+
...webTheme,
|
|
169
|
+
rows: overlayRows(overlayRow),
|
|
170
|
+
})),
|
|
136
171
|
Summary: bind(ui, "Summary", summaryHtml, summaryBehaviors({ ...webTheme, width: paneWidth, elide })),
|
|
137
172
|
}
|
|
138
173
|
}
|
|
@@ -152,10 +187,12 @@ function registerWebComponents() {
|
|
|
152
187
|
ui.format("Confirm", components.Confirm)
|
|
153
188
|
ui.format("EntryDetail", components.EntryDetail)
|
|
154
189
|
ui.format("FileList", components.FileList)
|
|
190
|
+
ui.format("FilePicker", components.FilePicker)
|
|
155
191
|
ui.format("FilePreview", components.FilePreview)
|
|
156
192
|
ui.format("FilterBar", components.FilterBar)
|
|
157
193
|
ui.format("Header", components.Header)
|
|
158
194
|
ui.format("Help", components.Help)
|
|
195
|
+
ui.format("OverlayList", components.OverlayList)
|
|
159
196
|
ui.format("Summary", components.Summary)
|
|
160
197
|
return components
|
|
161
198
|
}
|