dsh-workspace-explorer 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +7 -5
- package/lib/client.js +783 -162
- package/package.json +53 -52
- package/scripts/build-client.mjs +1 -0
- package/src/client/conversation-drop.js +142 -0
- package/src/client/main.js +318 -91
- package/src/client/sidebar-motion.js +69 -0
- package/src/host/files.js +1 -1
- package/src/ui/explorer.js +42 -42
- package/src/ui/favorites.js +54 -2
- package/src/ui/navigation.js +9 -0
- package/src/ui/preview.js +58 -12
- package/src/ui/styles.js +74 -15
package/src/ui/explorer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { joinPath, move, parentOf, renamePath, resourceAddress, underPath, visit, windowsPath } from './navigation.js';
|
|
2
|
-
import { Preview, PreviewBoundary } from './preview.js';
|
|
2
|
+
import { formatSize, Preview, PreviewBoundary } from './preview.js';
|
|
3
3
|
import { disks } from './workspace.js';
|
|
4
4
|
import { useComposerDrop } from './composer-drop.js';
|
|
5
5
|
import { Thumbnail } from './thumbnail.js';
|
|
@@ -80,14 +80,6 @@ function selectNames(current, name, entries, shift, ctrl) {
|
|
|
80
80
|
return { names, anchor: name };
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function formatSize(bytes) {
|
|
84
|
-
if (typeof bytes !== 'number') return '—';
|
|
85
|
-
if (bytes < 1024) return bytes + ' B';
|
|
86
|
-
if (bytes < 1024 ** 2) return (bytes / 1024).toFixed(1) + ' KB';
|
|
87
|
-
if (bytes < 1024 ** 3) return (bytes / 1024 ** 2).toFixed(1) + ' MB';
|
|
88
|
-
return (bytes / 1024 ** 3).toFixed(1) + ' GB';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
83
|
function entryType(entry) {
|
|
92
84
|
if (entry.type === 'drive') return '磁盘';
|
|
93
85
|
if (entry.type === 'directory') return '文件夹';
|
|
@@ -175,21 +167,19 @@ function PreviewDivider({ root, ratio, setRatio }) {
|
|
|
175
167
|
}
|
|
176
168
|
|
|
177
169
|
export function Explorer({
|
|
178
|
-
cwd, sessionId, initialPath, bindFileActions, getTarget, onPath, openLocal, openFolderTab,
|
|
170
|
+
cwd, sessionId, initialPath, bindFileActions, getTarget, onPath, openLocal, openDocument, openFolderTab,
|
|
179
171
|
refreshDirectories, updateRenamedPath,
|
|
180
172
|
}) {
|
|
181
173
|
const workspace = windowsPath(cwd);
|
|
182
174
|
const [localHistory, setLocalHistory] = useState(() => firstPath(initialPath ?? workspace));
|
|
183
175
|
const [scoped, setScoped] = useState(null);
|
|
184
176
|
const workspaceMode = scoped !== null;
|
|
185
|
-
const root =
|
|
186
|
-
const history =
|
|
177
|
+
const root = scoped?.root ?? null;
|
|
178
|
+
const history = scoped?.history ?? localHistory;
|
|
187
179
|
const changeHistory = useCallback(change => {
|
|
188
|
-
if (workspaceMode) setScoped(current => current && {
|
|
189
|
-
root, history: change(current.root === root ? current.history : firstPath(root)),
|
|
190
|
-
});
|
|
180
|
+
if (workspaceMode) setScoped(current => current && { ...current, history: change(current.history) });
|
|
191
181
|
else setLocalHistory(change);
|
|
192
|
-
}, [workspaceMode
|
|
182
|
+
}, [workspaceMode]);
|
|
193
183
|
const [revision, setRevision] = useState(0);
|
|
194
184
|
const [listing, setListing] = useState(null);
|
|
195
185
|
const [selection, setSelection] = useState(emptySelection);
|
|
@@ -244,7 +234,7 @@ export function Explorer({
|
|
|
244
234
|
if (!current) return current;
|
|
245
235
|
const absolute = renamePath(current.absolute, from, to);
|
|
246
236
|
return absolute === current.absolute ? current
|
|
247
|
-
: { absolute, name: absolute.split('\\').at(-1), address: resourceAddress(absolute) };
|
|
237
|
+
: { ...current, absolute, name: absolute.split('\\').at(-1), address: resourceAddress(absolute) };
|
|
248
238
|
});
|
|
249
239
|
}, []);
|
|
250
240
|
const closeMenu = useCallback((restore = false) => {
|
|
@@ -295,12 +285,6 @@ export function Explorer({
|
|
|
295
285
|
setScoped(workspaceMode ? null : { root: workspace, history: firstPath(workspace) });
|
|
296
286
|
}
|
|
297
287
|
|
|
298
|
-
useLayoutEffect(() => {
|
|
299
|
-
if (!workspaceMode || !root || root === scoped.root) return;
|
|
300
|
-
resetNavigation();
|
|
301
|
-
setScoped({ root, history: firstPath(root) });
|
|
302
|
-
}, [workspaceMode, root, scoped?.root, resetNavigation]);
|
|
303
|
-
|
|
304
288
|
useLayoutEffect(() => { onPath(path); }, [onPath, path]);
|
|
305
289
|
|
|
306
290
|
useEffect(() => {
|
|
@@ -331,7 +315,6 @@ export function Explorer({
|
|
|
331
315
|
}, [root, term, revision, sessionId]);
|
|
332
316
|
|
|
333
317
|
useEffect(() => () => pathRequest.current?.abort(), []);
|
|
334
|
-
useLayoutEffect(() => bindFileActions({ refresh, rename }), [bindFileActions, refresh, rename]);
|
|
335
318
|
useLayoutEffect(() => { scroll.current.scrollTop = 0; }, [path, term]);
|
|
336
319
|
useLayoutEffect(() => {
|
|
337
320
|
const result = resultToReveal.current;
|
|
@@ -356,20 +339,33 @@ export function Explorer({
|
|
|
356
339
|
changeHistory(value => move(value, delta));
|
|
357
340
|
}
|
|
358
341
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
342
|
+
const openPreview = useCallback((absolute, name, size, line) => {
|
|
343
|
+
const targetLine = Number.isSafeInteger(line) && line > 0 ? line : undefined;
|
|
344
|
+
setPreview(current => current?.absolute === absolute && !targetLine ? current : {
|
|
345
|
+
name, absolute, size, line: targetLine, address: resourceAddress(absolute),
|
|
362
346
|
});
|
|
363
|
-
}
|
|
347
|
+
}, []);
|
|
364
348
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
349
|
+
const revealFile = useCallback((absolute, name, size, line) => {
|
|
350
|
+
const folder = parentOf(absolute);
|
|
351
|
+
if (workspaceMode && !underPath(folder, root)) {
|
|
352
|
+
resetNavigation();
|
|
353
|
+
setScoped(null);
|
|
354
|
+
setLocalHistory(history => visit(history, folder));
|
|
355
|
+
} else {
|
|
356
|
+
navigate(folder);
|
|
357
|
+
if (folder === path && ready && !ready.entries.some(entry => entry.name === name)) refresh();
|
|
358
|
+
}
|
|
359
|
+
resultToReveal.current = { folder, name };
|
|
360
|
+
setSelection(singleSelection(name));
|
|
361
|
+
openPreview(absolute, name, size, line);
|
|
372
362
|
setPreviewOpen(true);
|
|
363
|
+
}, [workspaceMode, root, path, ready, resetNavigation, navigate, refresh, openPreview]);
|
|
364
|
+
|
|
365
|
+
useLayoutEffect(() => bindFileActions({ refresh, rename, reveal: revealFile }), [bindFileActions, refresh, rename, revealFile]);
|
|
366
|
+
|
|
367
|
+
function openSearchHit(match) {
|
|
368
|
+
if (underPath(match.path, root)) revealFile(match.path, match.name);
|
|
373
369
|
}
|
|
374
370
|
|
|
375
371
|
function select(event, entry) {
|
|
@@ -378,7 +374,7 @@ export function Explorer({
|
|
|
378
374
|
setSelection(current => selectNames(current, entry.name, entries, shiftKey, ctrlKey));
|
|
379
375
|
setActionError('');
|
|
380
376
|
setActionNotice('');
|
|
381
|
-
if (!shiftKey && !ctrlKey && entry.type === 'file') openPreview(joinPath(path, entry.name), entry.name);
|
|
377
|
+
if (!shiftKey && !ctrlKey && entry.type === 'file') openPreview(joinPath(path, entry.name), entry.name, entry.size);
|
|
382
378
|
}
|
|
383
379
|
|
|
384
380
|
async function openWithWindows(path) {
|
|
@@ -511,7 +507,7 @@ export function Explorer({
|
|
|
511
507
|
if (target.type === 'directory') navigate(windowsPath(target.path));
|
|
512
508
|
else {
|
|
513
509
|
closeEditor();
|
|
514
|
-
openPreview(target.path, target.name);
|
|
510
|
+
openPreview(target.path, target.name, target.size);
|
|
515
511
|
}
|
|
516
512
|
scroll.current.focus();
|
|
517
513
|
} catch (error) {
|
|
@@ -529,7 +525,9 @@ export function Explorer({
|
|
|
529
525
|
if (!target || !shownPreview) return;
|
|
530
526
|
try {
|
|
531
527
|
if (root) await disks.locate(shownPreview.absolute, undefined, root);
|
|
532
|
-
if (getTarget() === target)
|
|
528
|
+
if (getTarget() === target) openDocument(resourceAddress(shownPreview.absolute, target.sessionId), {
|
|
529
|
+
paneId: target.paneId, params: shownPreview.line ? { line: shownPreview.line } : undefined,
|
|
530
|
+
});
|
|
533
531
|
} catch (error) {
|
|
534
532
|
setActionError(error?.message || String(error));
|
|
535
533
|
}
|
|
@@ -586,6 +584,7 @@ export function Explorer({
|
|
|
586
584
|
tool('home', workspaceMode ? '返回工作区根目录' : '返回此电脑', () => navigate(root), path === root),
|
|
587
585
|
tool('workspace', '跳转至工作区', () => navigate(workspace), !workspace || path === workspace || workspaceMode)),
|
|
588
586
|
h('div', { className: 'dwe-nav' },
|
|
587
|
+
h('span', { className: 'dwe-v-divider', 'aria-hidden': true }),
|
|
589
588
|
tool('copy', '复制', () => runAction('copy', selectedEntries), !modifySelected),
|
|
590
589
|
tool('cut', '剪切', () => runAction('cut', selectedEntries), !modifySelected),
|
|
591
590
|
tool('paste', '粘贴', () => runAction('paste'), !path || working),
|
|
@@ -651,14 +650,15 @@ export function Explorer({
|
|
|
651
650
|
!grid && h('span', { className: 'dwe-meta' }, entryType(entry)),
|
|
652
651
|
!grid && h('span', { className: 'dwe-meta' }, formatSize(entry.size))))))),
|
|
653
652
|
h('footer', { className: 'dwe-status-bar' },
|
|
653
|
+
h('div', { className: 'dwe-nav', role: 'group', 'aria-label': '文件视图' },
|
|
654
|
+
tool('list', '列表', () => changeView('list'), false, !grid),
|
|
655
|
+
tool('grid', '大图标', () => changeView('grid'), false, grid)),
|
|
656
|
+
h('span', { className: 'dwe-v-divider', 'aria-hidden': true }),
|
|
654
657
|
h('span', { className: 'dwe-status', role: 'status', title: actionNotice },
|
|
655
658
|
term ? found?.matches ? found.matches.length + ' 个结果' + (found.truncated ? ' · 请缩小关键词' : '') : ' '
|
|
656
659
|
: actionNotice || (selectedEntries.length > 1 ? '已选择 ' + selectedEntries.length + ' 项'
|
|
657
660
|
: ready?.entries ? ready.entries.length + ' 个项目' + (ready.truncated ? ' · 列表已截断' : '') : ' ')),
|
|
658
|
-
|
|
659
|
-
tool('list', '列表', () => changeView('list'), false, !grid),
|
|
660
|
-
tool('grid', '大图标', () => changeView('grid'), false, grid),
|
|
661
|
-
tool('preview', '预览窗格', () => setPreviewOpen(open => !open), false, previewOpen)))),
|
|
661
|
+
tool('preview', '预览窗格', () => setPreviewOpen(open => !open), false, previewOpen))),
|
|
662
662
|
previewOpen && h(PreviewDivider, { root: layout, ratio: splitRatio, setRatio: setSplitRatio }),
|
|
663
663
|
previewOpen && h('section', { className: 'dwe-preview', 'aria-label': '文件预览' },
|
|
664
664
|
h('header', { className: 'dwe-preview-bar' },
|
package/src/ui/favorites.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { renamePath, underPath, windowsPath } from './navigation.js';
|
|
2
2
|
|
|
3
3
|
const key = 'dsh-workspace-explorer.favorites';
|
|
4
|
+
const dragType = 'application/x-dwe-favorite';
|
|
4
5
|
const { createElement: h, useLayoutEffect, useRef, useSyncExternalStore } = react;
|
|
5
6
|
const listeners = new Set();
|
|
6
7
|
|
|
@@ -66,8 +67,33 @@ export function renameFavorites(from, to) {
|
|
|
66
67
|
update(unique(folders.map(path => renamePath(path, from, to))));
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
function moveFavorite(from, to, after) {
|
|
71
|
+
if (from === to || !folders.includes(from) || !folders.includes(to)) return;
|
|
72
|
+
const next = folders.filter(path => path !== from);
|
|
73
|
+
next.splice(next.indexOf(to) + Number(after), 0, from);
|
|
74
|
+
update(next);
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
export function Favorites({ anchor, close, folders, navigate, currentPath, removeIcon, workspaceMode, root }) {
|
|
70
78
|
const panel = useRef(null);
|
|
79
|
+
const dragged = useRef(null);
|
|
80
|
+
const dropRow = useRef(null);
|
|
81
|
+
|
|
82
|
+
function clearDropRow() {
|
|
83
|
+
if (dropRow.current) delete dropRow.current.dataset.dweFavoriteDrop;
|
|
84
|
+
dropRow.current = null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resetDrag() {
|
|
88
|
+
dragged.current = null;
|
|
89
|
+
clearDropRow();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isAfter(event) {
|
|
93
|
+
const bounds = event.currentTarget.getBoundingClientRect();
|
|
94
|
+
return event.clientY >= bounds.top + bounds.height / 2;
|
|
95
|
+
}
|
|
96
|
+
|
|
71
97
|
useLayoutEffect(() => {
|
|
72
98
|
const popup = panel.current;
|
|
73
99
|
const button = anchor.current;
|
|
@@ -108,9 +134,35 @@ export function Favorites({ anchor, close, folders, navigate, currentPath, remov
|
|
|
108
134
|
return h('section', { ref: panel, className: 'dwe-favorites', 'aria-label': '收藏夹' },
|
|
109
135
|
h('header', null, h('strong', null, '收藏夹'),
|
|
110
136
|
h('button', { type: 'button', className: 'dwe-clear-favorites', disabled: !folders.length, onClick: clearFavorites }, '清空收藏')),
|
|
111
|
-
folders.length ? h('ul',
|
|
137
|
+
folders.length ? h('ul', { onDragLeave: event => {
|
|
138
|
+
if (!event.currentTarget.contains(event.relatedTarget)) clearDropRow();
|
|
139
|
+
} }, ...folders.map(path => {
|
|
112
140
|
const name = path.length === 3 ? path.slice(0, 2) : path.split('\\').at(-1);
|
|
113
|
-
return h('li', {
|
|
141
|
+
return h('li', {
|
|
142
|
+
key: path, draggable: true,
|
|
143
|
+
onDragStart: event => {
|
|
144
|
+
dragged.current = path;
|
|
145
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
146
|
+
event.dataTransfer.setData(dragType, path);
|
|
147
|
+
},
|
|
148
|
+
onDragOver: event => {
|
|
149
|
+
if (!dragged.current || !event.dataTransfer.types.includes(dragType)) return;
|
|
150
|
+
if (dropRow.current !== event.currentTarget) clearDropRow();
|
|
151
|
+
if (dragged.current === path) return;
|
|
152
|
+
event.preventDefault();
|
|
153
|
+
event.dataTransfer.dropEffect = 'move';
|
|
154
|
+
dropRow.current = event.currentTarget;
|
|
155
|
+
dropRow.current.dataset.dweFavoriteDrop = isAfter(event) ? 'after' : 'before';
|
|
156
|
+
},
|
|
157
|
+
onDrop: event => {
|
|
158
|
+
const from = dragged.current;
|
|
159
|
+
if (!from || event.dataTransfer.getData(dragType) !== from) return;
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
resetDrag();
|
|
162
|
+
moveFavorite(from, path, isAfter(event));
|
|
163
|
+
},
|
|
164
|
+
onDragEnd: resetDrag,
|
|
165
|
+
},
|
|
114
166
|
h('button', {
|
|
115
167
|
type: 'button', className: 'dwe-favorite-location', title: path, onClick: () => navigate(path),
|
|
116
168
|
disabled: workspaceMode && !underPath(path, root),
|
package/src/ui/navigation.js
CHANGED
|
@@ -39,3 +39,12 @@ export function resourceAddress(path, sessionId) {
|
|
|
39
39
|
const encoded = path.split('\\').map(segment => encodeURIComponent(segment).replace(/%3A/gi, ':')).join('/');
|
|
40
40
|
return 'dsh-resource://file/' + (sessionId ? 'session/' + encodeURIComponent(sessionId) : 'absolute') + '/' + encoded;
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
export function sessionFilePath(address, sessionId) {
|
|
44
|
+
const match = /^dsh-resource:\/\/file\/session\/([^/?#]+)\/([^?#]+)(?:[?#].*)?$/.exec(address);
|
|
45
|
+
if (!match || !sessionId) return null;
|
|
46
|
+
try {
|
|
47
|
+
if (decodeURIComponent(match[1]) !== sessionId) return null;
|
|
48
|
+
return match[2].split('/').map(decodeURIComponent).join('/');
|
|
49
|
+
} catch { return null; }
|
|
50
|
+
}
|
package/src/ui/preview.js
CHANGED
|
@@ -7,7 +7,7 @@ import { PreviewUnavailable } from './preview-unavailable.js';
|
|
|
7
7
|
import { PdfPreview } from './pdf-preview.js';
|
|
8
8
|
import { usePreviewZoom } from './preview-zoom.js';
|
|
9
9
|
|
|
10
|
-
const { createElement: h, useEffect, useRef, useState } = react;
|
|
10
|
+
const { createElement: h, useEffect, useLayoutEffect, useRef, useState } = react;
|
|
11
11
|
const coverActions = {
|
|
12
12
|
[PREVIEW_IDS.archive]: '解压预览',
|
|
13
13
|
[PREVIEW_IDS.model]: '查看模型',
|
|
@@ -21,6 +21,14 @@ const coverActions = {
|
|
|
21
21
|
[PREVIEW_IDS.dfu]: '查看固件',
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
export function formatSize(bytes) {
|
|
25
|
+
if (typeof bytes !== 'number') return '—';
|
|
26
|
+
if (bytes < 1024) return bytes + ' B';
|
|
27
|
+
if (bytes < 1024 ** 2) return (bytes / 1024).toFixed(1) + ' KB';
|
|
28
|
+
if (bytes < 1024 ** 3) return (bytes / 1024 ** 2).toFixed(1) + ' MB';
|
|
29
|
+
return (bytes / 1024 ** 3).toFixed(1) + ' GB';
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
export class PreviewBoundary extends react.Component {
|
|
25
33
|
state = { failed: false };
|
|
26
34
|
|
|
@@ -35,6 +43,18 @@ export class PreviewBoundary extends react.Component {
|
|
|
35
43
|
|
|
36
44
|
function PreviewCover({ file, root, videoType, label, archive, start }) {
|
|
37
45
|
const [poster, setPoster] = useState(null);
|
|
46
|
+
const [resolvedSize, setResolvedSize] = useState(undefined);
|
|
47
|
+
const { absolute, name, size } = file;
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (size !== undefined) return;
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
void disks.locate(absolute, controller.signal, root).then(result => {
|
|
53
|
+
if (!controller.signal.aborted) setResolvedSize(result.size);
|
|
54
|
+
}, () => {});
|
|
55
|
+
return () => controller.abort();
|
|
56
|
+
}, [absolute, size, root]);
|
|
57
|
+
|
|
38
58
|
useEffect(() => {
|
|
39
59
|
if (!videoType) return;
|
|
40
60
|
const controller = new AbortController();
|
|
@@ -50,16 +70,22 @@ function PreviewCover({ file, root, videoType, label, archive, start }) {
|
|
|
50
70
|
h('rect', { x: 5, y: 3, width: 26, height: 30, rx: 5, fill: '#7462c4' }),
|
|
51
71
|
h('path', { d: 'M18 5h3v4h-3v4h3v4h-3v4m-2 0h6v6h-6z', fill: 'none', stroke: '#fff', strokeWidth: 1.6, strokeLinejoin: 'round' }))
|
|
52
72
|
: h(primitives.FileTypeIcon, { path: file.name, size: 36 });
|
|
53
|
-
return h('div', {
|
|
73
|
+
return h('div', {
|
|
74
|
+
className: 'dwe-preview-cover', 'data-video': Boolean(videoType) || undefined,
|
|
75
|
+
'data-poster': Boolean(poster) || undefined,
|
|
76
|
+
},
|
|
54
77
|
poster && h('img', { src: poster, alt: '', draggable: false }),
|
|
55
|
-
h('div', { className: 'dwe-cover-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
h('
|
|
62
|
-
|
|
78
|
+
h('div', { className: 'dwe-cover-content' },
|
|
79
|
+
h('div', { className: 'dwe-cover-action' },
|
|
80
|
+
!videoType && fileIcon,
|
|
81
|
+
h('button', {
|
|
82
|
+
type: 'button', className: videoType ? 'dwe-cover-play' : 'dwe-cover-open',
|
|
83
|
+
title: label, 'aria-label': label, onClick: start,
|
|
84
|
+
}, videoType ? h('svg', { width: 24, height: 24, viewBox: '0 0 24 24', 'aria-hidden': true },
|
|
85
|
+
h('path', { d: 'M9 5v14l11-7z', fill: 'currentColor' })) : label)),
|
|
86
|
+
h('div', { className: 'dwe-cover-details' },
|
|
87
|
+
h('div', { className: 'dwe-cover-file', title: name }, name),
|
|
88
|
+
h('div', { className: 'dwe-cover-size' }, formatSize(size ?? resolvedSize)))));
|
|
63
89
|
}
|
|
64
90
|
|
|
65
91
|
function ImagePreview({ url, name, onError }) {
|
|
@@ -87,6 +113,27 @@ function ImagePreview({ url, name, onError }) {
|
|
|
87
113
|
}));
|
|
88
114
|
}
|
|
89
115
|
|
|
116
|
+
function TextPreview({ text, eof, file }) {
|
|
117
|
+
const content = useRef(null);
|
|
118
|
+
useLayoutEffect(() => {
|
|
119
|
+
if (!file.line || !text) return;
|
|
120
|
+
let offset = 0;
|
|
121
|
+
for (let line = 1; line < file.line; line++) {
|
|
122
|
+
const end = text.indexOf('\n', offset);
|
|
123
|
+
if (end < 0) return;
|
|
124
|
+
offset = end + 1;
|
|
125
|
+
}
|
|
126
|
+
if (offset === text.length) return;
|
|
127
|
+
const range = document.createRange();
|
|
128
|
+
range.setStart(content.current.firstChild, offset);
|
|
129
|
+
range.setEnd(content.current.firstChild, offset + 1);
|
|
130
|
+
const scroll = content.current.parentElement;
|
|
131
|
+
scroll.scrollTop += range.getBoundingClientRect().top - scroll.getBoundingClientRect().top - 12;
|
|
132
|
+
}, [file, text]);
|
|
133
|
+
return h('pre', { ref: content, className: 'dwe-text' },
|
|
134
|
+
text || '(空文件)', eof ? '' : '\n\n… 仅显示首页,可在原生预览中继续查看');
|
|
135
|
+
}
|
|
136
|
+
|
|
90
137
|
export function Preview({ file, root }) {
|
|
91
138
|
const [result, setResult] = useState({ kind: 'loading' });
|
|
92
139
|
const [started, setStarted] = useState(false);
|
|
@@ -155,8 +202,7 @@ export function Preview({ file, root }) {
|
|
|
155
202
|
onError: () => setResult({ kind: 'unavailable' }),
|
|
156
203
|
});
|
|
157
204
|
case 'text':
|
|
158
|
-
return h(
|
|
159
|
-
result.text || '(空文件)', result.eof ? '' : '\n\n… 仅显示首页,可在原生预览中继续查看');
|
|
205
|
+
return h(TextPreview, { text: result.text, eof: result.eof, file });
|
|
160
206
|
case 'unavailable':
|
|
161
207
|
return h(PreviewUnavailable);
|
|
162
208
|
default:
|
package/src/ui/styles.js
CHANGED
|
@@ -1,4 +1,36 @@
|
|
|
1
1
|
export const explorerCss = `
|
|
2
|
+
[data-dwe-sidebar-handoff] {
|
|
3
|
+
grid-template-columns: var(--dwe-handoff-columns) !important;
|
|
4
|
+
transition: none !important;
|
|
5
|
+
}
|
|
6
|
+
[data-dwe-sidebar-track-instant],
|
|
7
|
+
[data-sidebar-right-panel] :is([data-dockkit-host='dock'], [data-dockkit-empty], [data-dockkit-divider]):not([data-dwe-sidebar-toggle-motion]),
|
|
8
|
+
[data-dwe-sidebar-handoff] [data-sidebar-right-panel] :is([data-dockkit-host='dock'], [data-dockkit-empty], [data-dockkit-divider]) {
|
|
9
|
+
transition: none !important;
|
|
10
|
+
}
|
|
11
|
+
.dwe-conversation-drop {
|
|
12
|
+
position: fixed; inset: 0; z-index: 2147483647;
|
|
13
|
+
display: grid; box-sizing: border-box; padding: 0 6px 6px 3px;
|
|
14
|
+
--dwe-drop-blue: #4176E6;
|
|
15
|
+
pointer-events: none; user-select: none;
|
|
16
|
+
}
|
|
17
|
+
.dwe-conversation-drop-area {
|
|
18
|
+
position: relative; display: grid; place-items: center; padding: 24px;
|
|
19
|
+
border-radius: 10px; overflow: hidden;
|
|
20
|
+
background: color-mix(in srgb, var(--dsw-alias-bg-base, Canvas) 38%, transparent);
|
|
21
|
+
backdrop-filter: blur(7.5px); color: var(--dwe-drop-blue);
|
|
22
|
+
text-align: center; font-size: 24px;
|
|
23
|
+
}
|
|
24
|
+
.dwe-conversation-drop-area::before {
|
|
25
|
+
content: ''; position: absolute; inset: 0; border: 4px solid var(--dwe-drop-blue); border-radius: inherit;
|
|
26
|
+
}
|
|
27
|
+
.dwe-conversation-drop[data-dwe-drop-active] .dwe-conversation-drop-area::before { border-width: 8px; }
|
|
28
|
+
.dwe-conversation-drag-icon {
|
|
29
|
+
position: fixed; top: 0; left: 0; width: 40px; height: 40px;
|
|
30
|
+
display: grid; place-items: center; border-radius: 8px;
|
|
31
|
+
background: var(--dsw-alias-bg-base, Canvas); box-shadow: 0 2px 8px #0004;
|
|
32
|
+
transform: translate3d(clamp(0px, var(--dwe-drag-x), 100vw - 44px), clamp(0px, var(--dwe-drag-y), 100vh - 44px), 0);
|
|
33
|
+
}
|
|
2
34
|
.dwe-host { height: 100%; min-height: 0; min-width: 0; }
|
|
3
35
|
[data-dockkit-tab]:has([data-dwe-tab-title]) {
|
|
4
36
|
flex-shrink: 0; min-inline-size: calc(5em + 59px); max-inline-size: max(170px, calc(5em + 59px));
|
|
@@ -12,6 +44,7 @@ export const explorerCss = `
|
|
|
12
44
|
[data-dwe-tab-title] > span { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
13
45
|
.dwe {
|
|
14
46
|
--dwe-line: var(--dsw-alias-border-l3, color-mix(in srgb, currentColor 14%, transparent));
|
|
47
|
+
--dwe-rule-inset: 8px;
|
|
15
48
|
--dwe-tint: color-mix(in srgb, currentColor 5%, transparent);
|
|
16
49
|
--dwe-hover: color-mix(in srgb, currentColor 8%, transparent);
|
|
17
50
|
--dwe-blue: #458bf5;
|
|
@@ -48,11 +81,17 @@ export const explorerCss = `
|
|
|
48
81
|
.dwe-divider:focus-visible { outline: 2px solid var(--dwe-blue); outline-offset: -2px; }
|
|
49
82
|
.dwe-toolbar, .dwe-preview-bar {
|
|
50
83
|
display: flex; flex: none; align-items: center; gap: 6px;
|
|
51
|
-
padding: 3px 8px; min-width: 0; border-bottom: 1px solid
|
|
84
|
+
padding: 3px 8px; min-width: 0; border-bottom: 1px solid transparent;
|
|
85
|
+
}
|
|
86
|
+
.dwe-toolbar, .dwe-head, .dwe-status-bar, .dwe-preview-bar {
|
|
87
|
+
background-image: linear-gradient(#F5F5F5, #F5F5F5);
|
|
88
|
+
background-repeat: no-repeat; background-origin: border-box;
|
|
89
|
+
background-size: calc(100% - 2 * var(--dwe-rule-inset)) 1px; background-position: center bottom;
|
|
52
90
|
}
|
|
53
91
|
.dwe-address-bar { display: flex; min-width: 0; flex: none; align-items: center; gap: 3px; padding: 5px 8px 0; }
|
|
54
92
|
.dwe-toolbar { justify-content: space-between; gap: 4px; overflow-x: auto; scrollbar-width: none; }
|
|
55
93
|
.dwe-nav { display: flex; align-items: center; gap: 2px; flex: none; }
|
|
94
|
+
.dwe-v-divider { flex: 0 0 1px; height: 15px; margin-inline: 3px; background: var(--dwe-line); }
|
|
56
95
|
.dwe button {
|
|
57
96
|
font: inherit; color: inherit; border: 0; background: transparent; cursor: default;
|
|
58
97
|
}
|
|
@@ -66,8 +105,9 @@ export const explorerCss = `
|
|
|
66
105
|
background: color-mix(in srgb, var(--dwe-blue) 20%, transparent);
|
|
67
106
|
}
|
|
68
107
|
.dwe-tool[data-dwe-tool="star"][aria-pressed="true"] svg { color: #a36a00; fill: #ffca58; }
|
|
69
|
-
.dwe-tool[data-dwe-tool="
|
|
70
|
-
|
|
108
|
+
.dwe-tool:is(.dwe-scope-tool, .dwe-favorites-tool, [data-dwe-tool="preview"], [data-dwe-tool="list"], [data-dwe-tool="grid"]) {
|
|
109
|
+
&:is([aria-pressed="true"], [aria-expanded="true"]) { background: var(--dwe-hover); }
|
|
110
|
+
&:hover:not(:disabled) { background: color-mix(in srgb, var(--dwe-blue) 20%, transparent); }
|
|
71
111
|
}
|
|
72
112
|
.dwe-favorites {
|
|
73
113
|
position: absolute; z-index: 4; display: flex; flex-direction: column; box-sizing: border-box;
|
|
@@ -80,7 +120,12 @@ export const explorerCss = `
|
|
|
80
120
|
padding-inline-start: 6px; min-height: 25px;
|
|
81
121
|
}
|
|
82
122
|
.dwe-favorites ul { list-style: none; margin: 0; padding: 0; overflow: auto; }
|
|
83
|
-
.dwe-favorites li { display: flex; align-items: center; gap: 4px; }
|
|
123
|
+
.dwe-favorites li { position: relative; display: flex; align-items: center; gap: 4px; }
|
|
124
|
+
.dwe-favorites li[data-dwe-favorite-drop]::after {
|
|
125
|
+
content: ''; position: absolute; inset-inline: 4px; height: 2px; background: var(--dwe-blue); pointer-events: none;
|
|
126
|
+
}
|
|
127
|
+
.dwe-favorites li[data-dwe-favorite-drop='before']::after { top: 0; }
|
|
128
|
+
.dwe-favorites li[data-dwe-favorite-drop='after']::after { bottom: 0; }
|
|
84
129
|
.dwe-favorite-location {
|
|
85
130
|
display: flex; flex: 1; min-width: 0; align-items: center; gap: 7px; text-align: start;
|
|
86
131
|
min-height: 28px; padding: 2px 4px; border-radius: 5px;
|
|
@@ -132,7 +177,7 @@ export const explorerCss = `
|
|
|
132
177
|
.dwe-columns > span { padding-inline: 8px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
133
178
|
.dwe-head {
|
|
134
179
|
position: sticky; top: 0; z-index: 1; padding: 5px 6px; font-size: 12px;
|
|
135
|
-
background: var(--dsw-alias-bg-base, Canvas); border-bottom: 1px solid
|
|
180
|
+
background-color: var(--dsw-alias-bg-base, Canvas); border-bottom: 1px solid transparent;
|
|
136
181
|
}
|
|
137
182
|
.dwe-head span + span { border-inline-start: 1px solid var(--dwe-line); }
|
|
138
183
|
.dwe-columns > span:last-child { text-align: end; }
|
|
@@ -146,7 +191,7 @@ export const explorerCss = `
|
|
|
146
191
|
.dwe-tile .dwe-name { flex-direction: column; gap: 5px; }
|
|
147
192
|
.dwe-thumbnail {
|
|
148
193
|
position: relative; display: grid; place-items: center; inline-size: 100%; max-inline-size: 110px;
|
|
149
|
-
block-size: 60px; pointer-events: none;
|
|
194
|
+
grid-template: minmax(0, 1fr) / minmax(0, 1fr); block-size: 60px; pointer-events: none;
|
|
150
195
|
}
|
|
151
196
|
.dwe-thumbnail img {
|
|
152
197
|
display: block; max-inline-size: 100%; max-block-size: 100%; object-fit: contain; border-radius: 3px;
|
|
@@ -171,13 +216,14 @@ export const explorerCss = `
|
|
|
171
216
|
.dwe-meta, .dwe-status { opacity: .72; font-size: 12px; }
|
|
172
217
|
.dwe-status { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
173
218
|
.dwe-status-bar {
|
|
174
|
-
flex: none; display: flex; align-items: center;
|
|
175
|
-
padding: 2px
|
|
219
|
+
flex: none; display: flex; align-items: center; gap: 4px;
|
|
220
|
+
padding: 2px 8px; border-top: 1px solid transparent; background-position: center top;
|
|
176
221
|
}
|
|
222
|
+
.dwe-status-bar > .dwe-tool { margin-inline-start: auto; }
|
|
177
223
|
.dwe-preview {
|
|
178
224
|
min-height: 0; display: flex; flex-direction: column;
|
|
179
225
|
}
|
|
180
|
-
.dwe-preview-bar { background: var(--dsw-alias-bg-base, Canvas); }
|
|
226
|
+
.dwe-preview-bar { background-color: var(--dsw-alias-bg-base, Canvas); }
|
|
181
227
|
.dwe-preview-label { flex: none; margin-inline: 4px; line-height: 28px; }
|
|
182
228
|
.dwe-preview-name {
|
|
183
229
|
min-width: 0; height: 28px; box-sizing: border-box; padding-inline: 7px 3px;
|
|
@@ -188,13 +234,17 @@ export const explorerCss = `
|
|
|
188
234
|
.dwe-preview-body { flex: 1 1 auto; min-height: 0; overflow: auto; }
|
|
189
235
|
.dwe-preview-cover {
|
|
190
236
|
position: relative; display: grid; place-items: center; isolation: isolate;
|
|
191
|
-
height: 100%; min-height:
|
|
237
|
+
height: 100%; min-height: 112px; box-sizing: border-box; padding: 6px 12px;
|
|
238
|
+
overflow: hidden; background: var(--dwe-tint);
|
|
192
239
|
}
|
|
240
|
+
.dwe-preview-cover[data-video] { min-height: 128px; }
|
|
193
241
|
.dwe-preview-cover[data-poster] { background: #15171a; }
|
|
194
242
|
.dwe-preview-cover > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; z-index: -1; }
|
|
243
|
+
.dwe-cover-content { display: grid; grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 8px; width: 100%; }
|
|
195
244
|
.dwe-cover-action { display: flex; align-items: center; gap: 10px; }
|
|
196
245
|
.dwe .dwe-cover-play {
|
|
197
|
-
display: grid; place-items: center; width: 52px; height: 52px;
|
|
246
|
+
display: grid; place-items: center; width: 52px; height: 52px; padding: 0; box-sizing: border-box;
|
|
247
|
+
border-radius: 50%; corner-shape: round;
|
|
198
248
|
background: #243953; color: #fff; box-shadow: 0 2px 8px #0003; cursor: pointer;
|
|
199
249
|
}
|
|
200
250
|
.dwe .dwe-cover-play:hover:not(:disabled) { background: #34577f; }
|
|
@@ -202,11 +252,17 @@ export const explorerCss = `
|
|
|
202
252
|
padding: 7px 12px; border: 1px solid var(--dwe-line); border-radius: 6px;
|
|
203
253
|
background: var(--dsw-alias-bg-base, Canvas); white-space: nowrap; cursor: pointer;
|
|
204
254
|
}
|
|
205
|
-
.dwe-cover-
|
|
206
|
-
|
|
207
|
-
max-width:
|
|
255
|
+
.dwe-cover-details {
|
|
256
|
+
display: grid; grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 3px;
|
|
257
|
+
max-width: 100%; min-width: 0; box-sizing: border-box; padding: 2px 8px; border-radius: 5px;
|
|
258
|
+
}
|
|
259
|
+
.dwe-cover-file { max-width: 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
260
|
+
.dwe-cover-size { font-size: 12px; opacity: .72; }
|
|
261
|
+
.dwe-preview-cover[data-video] .dwe-cover-details {
|
|
262
|
+
position: absolute; inset: 0 0 auto; display: flex; align-items: center; gap: 6px;
|
|
263
|
+
padding: 8px; border-radius: 0; background: #0009; color: #fff;
|
|
208
264
|
}
|
|
209
|
-
.dwe-preview-cover[data-
|
|
265
|
+
.dwe-preview-cover[data-video] .dwe-cover-size { flex: none; white-space: nowrap; }
|
|
210
266
|
.dwe-pdf-scroll {
|
|
211
267
|
height: 100%; overflow: auto; scrollbar-gutter: stable both-edges; background: var(--dwe-tint);
|
|
212
268
|
}
|
|
@@ -274,6 +330,9 @@ export const explorerCss = `
|
|
|
274
330
|
.dwe-dialog-actions button[type="submit"] { background: color-mix(in srgb, var(--dwe-blue) 23%, transparent); }
|
|
275
331
|
.dwe-dialog-actions button[data-dwe-action="delete"] { background: #ad303d; color: #fff; }
|
|
276
332
|
.dwe-dialog-actions button[data-dwe-action="delete"]:hover:not(:disabled) { background: #942634; }
|
|
333
|
+
@container (max-width: 440px) {
|
|
334
|
+
.dwe-toolbar .dwe-tool:not(.dwe-scope-tool) { width: 26px; height: 26px; flex-basis: 26px; }
|
|
335
|
+
}
|
|
277
336
|
@container (max-width: 380px) {
|
|
278
337
|
.dwe-columns { grid-template-columns: minmax(0, 1fr) 62px; }
|
|
279
338
|
.dwe-columns > span:nth-child(2) { display: none; }
|