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.
@@ -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 = workspaceMode ? (sessionId ? workspace : scoped.root) : null;
186
- const history = workspaceMode ? (root === scoped.root ? scoped.history : firstPath(root)) : localHistory;
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, root]);
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
- function openPreview(absolute, name) {
360
- setPreview(current => current?.absolute === absolute ? current : {
361
- name, absolute, address: resourceAddress(absolute),
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
- function openSearchHit(match) {
366
- if (!underPath(match.path, root)) return;
367
- const folder = parentOf(match.path);
368
- navigate(folder);
369
- resultToReveal.current = { folder, name: match.name };
370
- setSelection(singleSelection(match.name));
371
- openPreview(match.path, match.name);
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) target.actions.openResource(resourceAddress(shownPreview.absolute, target.sessionId));
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
- h('div', { className: 'dwe-nav', role: 'group', 'aria-label': '视图与预览' },
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' },
@@ -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', null, ...folders.map(path => {
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', { key: path },
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),
@@ -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', { className: 'dwe-preview-cover', 'data-poster': Boolean(poster) || undefined },
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-action' },
56
- !videoType && fileIcon,
57
- h('button', {
58
- type: 'button', className: videoType ? 'dwe-cover-play' : 'dwe-cover-open',
59
- title: label, 'aria-label': label, onClick: start,
60
- }, videoType ? h('svg', { width: 24, height: 24, viewBox: '0 0 24 24', 'aria-hidden': true },
61
- h('path', { d: 'M9 5v14l11-7z', fill: 'currentColor' })) : label)),
62
- h('div', { className: 'dwe-cover-file', title: file.name }, file.name));
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('pre', { className: 'dwe-text' },
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 var(--dwe-line);
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="favorites"][aria-expanded="true"] {
70
- background: color-mix(in srgb, var(--dwe-blue) 20%, transparent);
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 var(--dwe-line);
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; justify-content: space-between; gap: 4px;
175
- padding: 2px 6px 2px 12px; border-top: 1px solid var(--dwe-line);
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: 100px; overflow: hidden; background: var(--dwe-tint);
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; border-radius: 50%;
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-file {
206
- position: absolute; inset-block-end: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
207
- max-width: calc(100% - 24px); padding: 3px 8px; border-radius: 5px;
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-poster] .dwe-cover-file { background: #15171abb; color: #fff; }
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; }