@lemoncat7/dsh-ssh 1.3.0 → 1.3.2

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.
Files changed (73) hide show
  1. package/README.md +3 -3
  2. package/lib/activity-panel.js +2 -2
  3. package/lib/activity-panel.js.map +1 -1
  4. package/lib/adaptive-workspace.js +1 -1
  5. package/lib/adaptive-workspace.js.map +1 -1
  6. package/lib/api.d.ts.map +1 -1
  7. package/lib/api.js +38 -10
  8. package/lib/api.js.map +1 -1
  9. package/lib/border-glow.d.ts +14 -0
  10. package/lib/border-glow.d.ts.map +1 -0
  11. package/lib/border-glow.js +47 -0
  12. package/lib/border-glow.js.map +1 -0
  13. package/lib/client-api.d.ts +9 -0
  14. package/lib/client-api.d.ts.map +1 -1
  15. package/lib/client-api.js +6 -0
  16. package/lib/client-api.js.map +1 -1
  17. package/lib/client.d.ts.map +1 -1
  18. package/lib/client.js +1628 -324
  19. package/lib/client.js.map +4 -4
  20. package/lib/file-entry-delete-dialog.d.ts +9 -0
  21. package/lib/file-entry-delete-dialog.d.ts.map +1 -0
  22. package/lib/file-entry-delete-dialog.js +25 -0
  23. package/lib/file-entry-delete-dialog.js.map +1 -0
  24. package/lib/file-transfer-intent.d.ts +19 -0
  25. package/lib/file-transfer-intent.d.ts.map +1 -0
  26. package/lib/file-transfer-intent.js +38 -0
  27. package/lib/file-transfer-intent.js.map +1 -0
  28. package/lib/file-transfer-workspace.d.ts.map +1 -1
  29. package/lib/file-transfer-workspace.js +95 -43
  30. package/lib/file-transfer-workspace.js.map +1 -1
  31. package/lib/ftp-adapter.js +29 -1
  32. package/lib/ftp-adapter.js.map +1 -1
  33. package/lib/ftp-profile-editor.js +2 -2
  34. package/lib/ftp-profile-editor.js.map +1 -1
  35. package/lib/glare-hover.d.ts +13 -0
  36. package/lib/glare-hover.d.ts.map +1 -0
  37. package/lib/glare-hover.js +22 -0
  38. package/lib/glare-hover.js.map +1 -0
  39. package/lib/local-workspace.d.ts +1 -0
  40. package/lib/local-workspace.d.ts.map +1 -1
  41. package/lib/local-workspace.js +22 -1
  42. package/lib/local-workspace.js.map +1 -1
  43. package/lib/motion.d.ts +5 -0
  44. package/lib/motion.d.ts.map +1 -0
  45. package/lib/motion.js +115 -0
  46. package/lib/motion.js.map +1 -0
  47. package/lib/profile-editor.js +2 -2
  48. package/lib/profile-editor.js.map +1 -1
  49. package/lib/remote-entry-operations.d.ts +11 -0
  50. package/lib/remote-entry-operations.d.ts.map +1 -0
  51. package/lib/remote-entry-operations.js +38 -0
  52. package/lib/remote-entry-operations.js.map +1 -0
  53. package/lib/remote-file-drop.d.ts +14 -0
  54. package/lib/remote-file-drop.d.ts.map +1 -0
  55. package/lib/remote-file-drop.js +29 -0
  56. package/lib/remote-file-drop.js.map +1 -0
  57. package/lib/remote-files.d.ts +2 -0
  58. package/lib/remote-files.d.ts.map +1 -1
  59. package/lib/remote-files.js.map +1 -1
  60. package/lib/remote-workspace-tree.d.ts.map +1 -1
  61. package/lib/remote-workspace-tree.js +7 -5
  62. package/lib/remote-workspace-tree.js.map +1 -1
  63. package/lib/sftp-client.d.ts.map +1 -1
  64. package/lib/sftp-client.js +95 -16
  65. package/lib/sftp-client.js.map +1 -1
  66. package/lib/sftp.d.ts.map +1 -1
  67. package/lib/sftp.js +12 -0
  68. package/lib/sftp.js.map +1 -1
  69. package/lib/terminal-view.js +21 -21
  70. package/lib/ui-components.d.ts.map +1 -1
  71. package/lib/ui-components.js +20 -4
  72. package/lib/ui-components.js.map +1 -1
  73. package/package.json +3 -1
@@ -0,0 +1,9 @@
1
+ import type { SftpEntryView } from './client-api.js';
2
+ export declare function FileEntryDeleteDialog({ locationName, locationKind, entries, onClose, onDelete }: {
3
+ locationName: string;
4
+ locationKind: 'local' | 'remote';
5
+ entries: SftpEntryView[];
6
+ onClose(): void;
7
+ onDelete(): Promise<void>;
8
+ }): JSX.Element;
9
+ //# sourceMappingURL=file-entry-delete-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-entry-delete-dialog.d.ts","sourceRoot":"","sources":["../src/file-entry-delete-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAGpD,wBAAgB,qBAAqB,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,GAAG,QAAQ,CAAC;IAAC,OAAO,EAAE,aAAa,EAAE,CAAC;IAAC,OAAO,IAAI,IAAI,CAAC;IAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GAAG,GAAG,CAAC,OAAO,CAe/O"}
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { IconDataOutline16, IconFolderClose16, IconTrashOutline16 } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { Dialog, errorMessage } from './ui-components.js';
5
+ export function FileEntryDeleteDialog({ locationName, locationKind, entries, onClose, onDelete }) {
6
+ const [deleting, setDeleting] = useState(false);
7
+ const [error, setError] = useState();
8
+ const directories = entries.filter(entry => entry.kind === 'directory').length;
9
+ const submit = async () => {
10
+ setDeleting(true);
11
+ setError(undefined);
12
+ try {
13
+ await onDelete();
14
+ setDeleting(false);
15
+ onClose();
16
+ }
17
+ catch (reason) {
18
+ setError(errorMessage(reason));
19
+ setDeleting(false);
20
+ }
21
+ };
22
+ return _jsxs(Dialog, { className: "dsh-ssh-file-delete-dialog", title: `删除 ${entries.length} 项?`, subtitle: `${locationName} · 此操作无法撤销`, onClose: () => { if (!deleting)
23
+ onClose(); }, children: [_jsxs("div", { className: "dsh-ssh-file-delete-copy", children: [_jsx("span", { children: _jsx(IconTrashOutline16, { size: 18 }) }), _jsxs("p", { children: ["\u5C06\u76F4\u63A5\u4ECE", locationKind === 'local' ? '当前会话目录' : '远端', "\u5220\u9664\u6240\u9009\u5185\u5BB9\u3002", directories > 0 ? `其中 ${directories} 个目录及其全部内容会被递归删除。` : ''] })] }), _jsxs("div", { className: "dsh-ssh-file-delete-list", children: [entries.slice(0, 6).map(entry => _jsxs("div", { children: [entry.kind === 'directory' ? _jsx(IconFolderClose16, { size: 17 }) : _jsx(IconDataOutline16, { size: 17 }), _jsxs("span", { children: [_jsx("strong", { children: entry.name }), _jsx("small", { title: entry.path, children: entry.path })] })] }, entry.path)), entries.length > 6 && _jsxs("p", { children: ["\u4EE5\u53CA\u5176\u4ED6 ", entries.length - 6, " \u9879"] })] }), error && _jsx("p", { className: "dsh-ssh-inline-error", role: "alert", children: error }), _jsxs("div", { className: "dsh-ssh-dialog-actions", children: [_jsx("span", {}), _jsx("button", { type: "button", className: "dsh-ssh-secondary-button", "data-ssh-dialog-close": true, disabled: deleting, onClick: onClose, children: "\u53D6\u6D88" }), _jsx("button", { type: "button", className: "dsh-ssh-danger-button", disabled: deleting, onClick: () => { void submit(); }, children: deleting ? '正在删除…' : '确认删除' })] })] });
24
+ }
25
+ //# sourceMappingURL=file-entry-delete-dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-entry-delete-dialog.js","sourceRoot":"","sources":["../src/file-entry-delete-dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAA;AAEhH,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEzD,MAAM,UAAU,qBAAqB,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAoI;IAChO,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC/C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,EAAU,CAAA;IAC5C,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,MAAM,CAAA;IAC9E,MAAM,MAAM,GAAG,KAAK,IAAmB,EAAE;QACvC,WAAW,CAAC,IAAI,CAAC,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;QACtC,IAAI,CAAC;YAAC,MAAM,QAAQ,EAAE,CAAC;YAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAAC,OAAO,EAAE,CAAA;QAAC,CAAC;QACvD,OAAO,MAAM,EAAE,CAAC;YAAC,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;YAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QAAC,CAAC;IACvE,CAAC,CAAA;IACD,OAAO,MAAC,MAAM,IAAC,SAAS,EAAC,4BAA4B,EAAC,KAAK,EAAE,MAAM,OAAO,CAAC,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,YAAY,YAAY,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,CAAA,CAAC,CAAC,aACxK,eAAK,SAAS,EAAC,0BAA0B,aAAC,yBAAM,KAAC,kBAAkB,IAAC,IAAI,EAAE,EAAE,GAAI,GAAO,EAAA,oDAAQ,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,gDAAS,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,WAAW,mBAAmB,CAAC,CAAC,CAAC,EAAE,IAAK,IAAM,EACxN,eAAK,SAAS,EAAC,0BAA0B,aAAE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,0BAAuB,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,KAAC,iBAAiB,IAAC,IAAI,EAAE,EAAE,GAAI,CAAC,CAAC,CAAC,KAAC,iBAAiB,IAAC,IAAI,EAAE,EAAE,GAAI,EAAC,2BAAM,2BAAS,KAAK,CAAC,IAAI,GAAU,EAAA,gBAAO,KAAK,EAAE,KAAK,CAAC,IAAI,YAAG,KAAK,CAAC,IAAI,GAAS,IAAO,KAApM,KAAK,CAAC,IAAI,CAAgM,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,qDAAS,OAAO,CAAC,MAAM,GAAG,CAAC,eAAO,IAAO,EACjW,KAAK,IAAI,YAAG,SAAS,EAAC,sBAAsB,EAAC,IAAI,EAAC,OAAO,YAAE,KAAK,GAAK,EACtE,eAAK,SAAS,EAAC,wBAAwB,aAAC,gBAAQ,EAAA,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,0BAA0B,iCAAuB,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,6BAAa,EAAA,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,uBAAuB,EAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,KAAK,MAAM,EAAE,CAAA,CAAC,CAAC,YAAG,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,GAAU,IAAM,IACrU,CAAA;AACX,CAAC"}
@@ -0,0 +1,19 @@
1
+ export interface RemoteTransferLocation {
2
+ endpointId: string;
3
+ directory: string;
4
+ }
5
+ export interface RemoteFilesDragPayload extends RemoteTransferLocation {
6
+ paneId: string;
7
+ paths: string[];
8
+ }
9
+ export declare const REMOTE_FILES_DRAG_TYPE = "application/x-dsh-remote-files";
10
+ export type RemoteDropOperation = 'none' | 'move' | 'copy' | 'invalid';
11
+ export declare function isNavigableRemoteEntry(entry: {
12
+ kind: string;
13
+ navigable?: boolean;
14
+ }): boolean;
15
+ export declare function isSameRemoteTransferLocation(source: RemoteTransferLocation, destination: RemoteTransferLocation): boolean;
16
+ export declare function canTransferIntoRemoteDirectory(source: RemoteTransferLocation, sourcePaths: string[], destination: RemoteTransferLocation): boolean;
17
+ export declare function remoteDropOperation(source: RemoteFilesDragPayload, destination: RemoteTransferLocation): RemoteDropOperation;
18
+ export declare function parseRemoteFilesDragPayload(raw: string): RemoteFilesDragPayload | undefined;
19
+ //# sourceMappingURL=file-transfer-intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-transfer-intent.d.ts","sourceRoot":"","sources":["../src/file-transfer-intent.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,sBAAuB,SAAQ,sBAAsB;IACpE,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,eAAO,MAAM,sBAAsB,mCAAmC,CAAA;AACtE,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;AAEtE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAE5F;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,sBAAsB,EAAE,WAAW,EAAE,sBAAsB,GAAG,OAAO,CAEzH;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,sBAAsB,GAAG,OAAO,CAOlJ;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,sBAAsB,EAAE,WAAW,EAAE,sBAAsB,GAAG,mBAAmB,CAI5H;AAED,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CAO3F"}
@@ -0,0 +1,38 @@
1
+ export const REMOTE_FILES_DRAG_TYPE = 'application/x-dsh-remote-files';
2
+ export function isNavigableRemoteEntry(entry) {
3
+ return entry.kind === 'directory' || entry.navigable === true;
4
+ }
5
+ export function isSameRemoteTransferLocation(source, destination) {
6
+ return source.endpointId === destination.endpointId && normalizeRemoteDirectory(source.directory) === normalizeRemoteDirectory(destination.directory);
7
+ }
8
+ export function canTransferIntoRemoteDirectory(source, sourcePaths, destination) {
9
+ if (source.endpointId !== destination.endpointId)
10
+ return true;
11
+ const target = normalizeRemoteDirectory(destination.directory);
12
+ return !sourcePaths.some(path => {
13
+ const candidate = normalizeRemoteDirectory(path);
14
+ return target === candidate || target.startsWith(`${candidate}/`);
15
+ });
16
+ }
17
+ export function remoteDropOperation(source, destination) {
18
+ if (isSameRemoteTransferLocation(source, destination))
19
+ return 'none';
20
+ if (!canTransferIntoRemoteDirectory(source, source.paths, destination))
21
+ return 'invalid';
22
+ return source.endpointId === destination.endpointId ? 'move' : 'copy';
23
+ }
24
+ export function parseRemoteFilesDragPayload(raw) {
25
+ try {
26
+ const value = JSON.parse(raw);
27
+ return typeof value.paneId === 'string' && typeof value.endpointId === 'string' && typeof value.directory === 'string' && Array.isArray(value.paths) && value.paths.every(path => typeof path === 'string')
28
+ ? { paneId: value.paneId, endpointId: value.endpointId, directory: value.directory, paths: value.paths }
29
+ : undefined;
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ }
35
+ function normalizeRemoteDirectory(value) {
36
+ return value.replaceAll('\\', '/').replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
37
+ }
38
+ //# sourceMappingURL=file-transfer-intent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-transfer-intent.js","sourceRoot":"","sources":["../src/file-transfer-intent.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,sBAAsB,GAAG,gCAAgC,CAAA;AAGtE,MAAM,UAAU,sBAAsB,CAAC,KAA4C;IACjF,OAAO,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI,CAAA;AAC/D,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,MAA8B,EAAE,WAAmC;IAC9G,OAAO,MAAM,CAAC,UAAU,KAAK,WAAW,CAAC,UAAU,IAAI,wBAAwB,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,wBAAwB,CAAC,WAAW,CAAC,SAAS,CAAC,CAAA;AACvJ,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,MAA8B,EAAE,WAAqB,EAAE,WAAmC;IACvI,IAAI,MAAM,CAAC,UAAU,KAAK,WAAW,CAAC,UAAU;QAAE,OAAO,IAAI,CAAA;IAC7D,MAAM,MAAM,GAAG,wBAAwB,CAAC,WAAW,CAAC,SAAS,CAAC,CAAA;IAC9D,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC9B,MAAM,SAAS,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAA;QAChD,OAAO,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,CAAC,CAAA;IACnE,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAA8B,EAAE,WAAmC;IACrG,IAAI,4BAA4B,CAAC,MAAM,EAAE,WAAW,CAAC;QAAE,OAAO,MAAM,CAAA;IACpE,IAAI,CAAC,8BAA8B,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC;QAAE,OAAO,SAAS,CAAA;IACxF,OAAO,MAAM,CAAC,UAAU,KAAK,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;AACvE,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,GAAW;IACrD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoC,CAAA;QAChE,OAAO,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC;YACzM,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE;YACxG,CAAC,CAAC,SAAS,CAAA;IACf,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,SAAS,CAAA;IAAC,CAAC;AAC9B,CAAC;AAED,SAAS,wBAAwB,CAAC,KAAa;IAC7C,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAA;AACtF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"file-transfer-workspace.d.ts","sourceRoot":"","sources":["../src/file-transfer-workspace.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEkB,KAAK,cAAc,EAAE,KAAK,cAAc,EAAoE,KAAK,cAAc,EACvJ,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAS7D,wBAAgB,qBAAqB,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,iBAAiB,EAAE,EAAE;IAAE,WAAW,EAAE,cAAc,EAAE,CAAC;IAAC,YAAY,EAAE,cAAc,EAAE,CAAC;IAAC,YAAY,EAAE,cAAc,EAAE,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAC;IAAC,iBAAiB,IAAI,IAAI,CAAA;CAAE,GAAG,GAAG,CAAC,OAAO,CAiFnR"}
1
+ {"version":3,"file":"file-transfer-workspace.d.ts","sourceRoot":"","sources":["../src/file-transfer-workspace.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEkB,KAAK,cAAc,EAAE,KAAK,cAAc,EAAoE,KAAK,cAAc,EACvJ,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAgB7D,wBAAgB,qBAAqB,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,iBAAiB,EAAE,EAAE;IAAE,WAAW,EAAE,cAAc,EAAE,CAAC;IAAC,YAAY,EAAE,cAAc,EAAE,CAAC;IAAC,YAAY,EAAE,cAAc,EAAE,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAC;IAAC,iBAAiB,IAAI,IAAI,CAAA;CAAE,GAAG,GAAG,CAAC,OAAO,CAmGnR"}
@@ -4,6 +4,9 @@ import { IconChevronLeftOutline14, IconCloseOutline16, IconDataOutline16, IconPl
4
4
  import { cancelFileTransfer, deleteFileEndpointEntries, loadFileEndpointDirectory, loadFileEndpoints, loadTransferJobs, startFileTransfer, } from './client-api.js';
5
5
  import { Dialog, errorMessage } from './ui-components.js';
6
6
  import { FtpConnectionsDialog } from './ftp-profile-editor.js';
7
+ import { REMOTE_FILES_DRAG_TYPE, canTransferIntoRemoteDirectory, isNavigableRemoteEntry, isSameRemoteTransferLocation, parseRemoteFilesDragPayload, } from './file-transfer-intent.js';
8
+ import { executeRemoteFileDrop } from './remote-file-drop.js';
9
+ import { FileEntryDeleteDialog } from './file-entry-delete-dialog.js';
7
10
  const STORAGE_KEY = 'dsh-ssh:file-transfer:tabs:v2';
8
11
  const FILE_ROW_HEIGHT = 38;
9
12
  export function FileTransferWorkspace({ ftpProfiles, vaultEntries, proxyEntries, access, onProfilesChanged }) {
@@ -14,6 +17,7 @@ export function FileTransferWorkspace({ ftpProfiles, vaultEntries, proxyEntries,
14
17
  const [connectionsOpen, setConnectionsOpen] = useState(false);
15
18
  const [accessOpen, setAccessOpen] = useState(false);
16
19
  const [conflictJob, setConflictJob] = useState();
20
+ const [dragSource, setDragSource] = useState();
17
21
  const [error, setError] = useState();
18
22
  const [directoryRevisions, setDirectoryRevisions] = useState({});
19
23
  const previousJobStatesRef = useRef(new Map());
@@ -86,7 +90,11 @@ export function FileTransferWorkspace({ ftpProfiles, vaultEntries, proxyEntries,
86
90
  setActiveTabId(next[Math.max(0, index - 1)].id);
87
91
  };
88
92
  const setPaneCount = (count) => updateActive(tab => ({ ...tab, panes: Array.from({ length: count }, (_, index) => tab.panes[index] ?? createPane(`${tab.id}-pane-${index}`)) }));
89
- const transfer = async (sourceEndpointId, sourcePaths, destinationEndpointId, destinationDirectory) => {
93
+ const transfer = async (sourceEndpointId, sourceDirectory, sourcePaths, destinationEndpointId, destinationDirectory) => {
94
+ const source = { endpointId: sourceEndpointId, directory: sourceDirectory };
95
+ const destination = { endpointId: destinationEndpointId, directory: destinationDirectory };
96
+ if (isSameRemoteTransferLocation(source, destination) || !canTransferIntoRemoteDirectory(source, sourcePaths, destination))
97
+ return;
90
98
  try {
91
99
  setError(undefined);
92
100
  const job = await startFileTransfer({ sourceEndpointId, sourcePaths, destinationEndpointId, destinationDirectory, conflictPolicy: 'fail' });
@@ -96,10 +104,29 @@ export function FileTransferWorkspace({ ftpProfiles, vaultEntries, proxyEntries,
96
104
  setError(errorMessage(reason));
97
105
  }
98
106
  };
99
- return _jsxs("div", { className: "dsh-ssh-transfer-workspace", children: [_jsxs("header", { className: "dsh-ssh-transfer-header", children: [_jsxs("div", { children: [_jsx("h1", { children: "\u6587\u4EF6\u4F20\u8F93" }), _jsx("p", { children: "FTP\u3001FTPS \u4E0E SFTP \u4E4B\u95F4\u76F4\u63A5\u6D41\u5F0F\u4E92\u4F20" })] }), _jsxs("div", { className: "dsh-ssh-transfer-actions", children: [_jsxs("button", { type: "button", className: "dsh-ssh-secondary-button", onClick: () => setAccessOpen(true), children: ["\u4F1A\u8BDD\u8BBF\u95EE \u00B7 ", access.value?.fileEndpointIds.length ?? 0] }), _jsx("button", { type: "button", className: "dsh-ssh-secondary-button", onClick: () => setConnectionsOpen(true), children: "FTP \u7BA1\u7406" })] })] }), error && _jsxs("div", { className: "dsh-ssh-banner is-error", role: "alert", children: [_jsx("span", { children: error }), _jsx("button", { type: "button", onClick: () => setError(undefined), "aria-label": "\u5173\u95ED", children: _jsx(IconCloseOutline16, { size: 16 }) })] }), _jsxs("div", { className: "dsh-ssh-transfer-tabbar", role: "tablist", "aria-label": "\u6587\u4EF6\u4F20\u8F93\u4EFB\u52A1\u9875", children: [_jsxs("div", { className: "dsh-ssh-transfer-tabs", children: [tabs.map(tab => _jsxs("span", { className: `dsh-ssh-transfer-tab${tab.id === active?.id ? ' is-active' : ''}`, children: [_jsx("button", { type: "button", role: "tab", "aria-selected": tab.id === active?.id, onClick: () => setActiveTabId(tab.id), children: tab.name }), tabs.length > 1 && _jsx("button", { type: "button", className: "is-close", "aria-label": `关闭 ${tab.name}`, onClick: () => closeTab(tab.id), children: _jsx(IconCloseOutline16, { size: 13 }) })] }, tab.id)), _jsx("button", { type: "button", className: "is-add", "aria-label": "\u65B0\u5EFA\u4F20\u8F93\u4EFB\u52A1\u9875", onClick: addTab, children: _jsx(IconPlusOutline16, { size: 15 }) })] }), _jsx("div", { className: "dsh-ssh-pane-layout", "aria-label": "\u7A97\u683C\u6570\u91CF", children: [2, 3, 4].map(count => _jsxs("button", { type: "button", className: active?.panes.length === count ? 'is-active' : '', onClick: () => setPaneCount(count), children: [count, " \u680F"] }, count)) })] }), endpoints.length === 0 ? _jsxs("div", { className: "dsh-ssh-transfer-empty", children: [_jsx(IconDataOutline16, { size: 24 }), _jsx("strong", { children: "\u8FD8\u6CA1\u6709\u53EF\u7528\u6587\u4EF6\u8FDE\u63A5" }), _jsx("p", { children: "\u65B0\u5EFA FTP/FTPS \u8FDE\u63A5\uFF0C\u6216\u5148\u6DFB\u52A0\u4E00\u53F0 SSH \u4E3B\u673A\u4F7F\u7528 SFTP\u3002" }), _jsx("button", { type: "button", className: "dsh-ssh-primary-button", onClick: () => setConnectionsOpen(true), children: "\u65B0\u5EFA FTP \u8FDE\u63A5" })] })
107
+ const dropFiles = async (source, destination, destinationDirectory) => {
108
+ try {
109
+ setError(undefined);
110
+ const result = await executeRemoteFileDrop(source, { paneId: destination.id, endpointId: destination.endpointId, directory: destinationDirectory });
111
+ if (result.job !== undefined)
112
+ setJobs(current => [result.job, ...current.filter(item => item.id !== result.job.id)]);
113
+ if (result.operation === 'move') {
114
+ setDirectoryRevisions(current => {
115
+ const next = { ...current };
116
+ for (const key of [directoryKey(source.endpointId, source.directory), directoryKey(destination.endpointId, destinationDirectory)])
117
+ next[key] = (next[key] ?? 0) + 1;
118
+ return next;
119
+ });
120
+ }
121
+ }
122
+ catch (reason) {
123
+ setError(errorMessage(reason));
124
+ }
125
+ };
126
+ return _jsxs("div", { className: "dsh-ssh-transfer-workspace", children: [_jsxs("header", { className: "dsh-ssh-transfer-header", children: [_jsxs("div", { children: [_jsx("h1", { children: "\u6587\u4EF6\u4F20\u8F93" }), _jsx("p", { children: "FTP\u3001FTPS \u4E0E SFTP \u4E4B\u95F4\u76F4\u63A5\u6D41\u5F0F\u4E92\u4F20" })] }), _jsxs("div", { className: "dsh-ssh-transfer-actions", children: [_jsxs("button", { type: "button", "data-ssh-interactive": "control", className: "dsh-ssh-secondary-button", onClick: () => setAccessOpen(true), children: ["\u4F1A\u8BDD\u8BBF\u95EE \u00B7 ", access.value?.fileEndpointIds.length ?? 0] }), _jsx("button", { type: "button", "data-ssh-interactive": "control", className: "dsh-ssh-secondary-button", onClick: () => setConnectionsOpen(true), children: "FTP \u7BA1\u7406" })] })] }), error && _jsxs("div", { className: "dsh-ssh-banner is-error", role: "alert", children: [_jsx("span", { children: error }), _jsx("button", { type: "button", onClick: () => setError(undefined), "aria-label": "\u5173\u95ED", children: _jsx(IconCloseOutline16, { size: 16 }) })] }), _jsxs("div", { className: "dsh-ssh-transfer-tabbar", role: "tablist", "aria-label": "\u6587\u4EF6\u4F20\u8F93\u4EFB\u52A1\u9875", children: [_jsxs("div", { className: "dsh-ssh-transfer-tabs", children: [tabs.map(tab => _jsxs("span", { "data-ssh-interactive": "choice", className: `dsh-ssh-transfer-tab${tab.id === active?.id ? ' is-active' : ''}`, children: [_jsx("button", { type: "button", role: "tab", "aria-selected": tab.id === active?.id, onClick: () => setActiveTabId(tab.id), children: tab.name }), tabs.length > 1 && _jsx("button", { type: "button", className: "is-close", "aria-label": `关闭 ${tab.name}`, onClick: () => closeTab(tab.id), children: _jsx(IconCloseOutline16, { size: 13 }) })] }, tab.id)), _jsx("button", { type: "button", "data-ssh-interactive": "control", className: "is-add", "aria-label": "\u65B0\u5EFA\u4F20\u8F93\u4EFB\u52A1\u9875", onClick: addTab, children: _jsx(IconPlusOutline16, { size: 15 }) })] }), _jsx("div", { className: "dsh-ssh-pane-layout", "aria-label": "\u7A97\u683C\u6570\u91CF", children: [2, 3, 4].map(count => _jsxs("button", { type: "button", "data-ssh-interactive": "choice", "aria-pressed": active?.panes.length === count, className: active?.panes.length === count ? 'is-active' : '', onClick: () => setPaneCount(count), children: [count, " \u680F"] }, count)) })] }), endpoints.length === 0 ? _jsxs("div", { className: "dsh-ssh-transfer-empty", children: [_jsx(IconDataOutline16, { size: 24 }), _jsx("strong", { children: "\u8FD8\u6CA1\u6709\u53EF\u7528\u6587\u4EF6\u8FDE\u63A5" }), _jsx("p", { children: "\u65B0\u5EFA FTP/FTPS \u8FDE\u63A5\uFF0C\u6216\u5148\u6DFB\u52A0\u4E00\u53F0 SSH \u4E3B\u673A\u4F7F\u7528 SFTP\u3002" }), _jsx("button", { type: "button", className: "dsh-ssh-primary-button", onClick: () => setConnectionsOpen(true), children: "\u65B0\u5EFA FTP \u8FDE\u63A5" })] })
100
127
  : active && _jsx("div", { className: `dsh-ssh-transfer-panes has-${active.panes.length}`, children: active.panes.map((pane, index) => {
101
128
  const destination = active.panes[(index + 1) % active.panes.length];
102
- return _jsx(FileTransferPane, { pane: pane, endpoints: endpoints, refreshRevision: directoryRevisions[directoryKey(pane.endpointId, pane.path)] ?? 0, ...destination === undefined ? {} : { destination }, onManageConnections: () => setConnectionsOpen(true), onChange: patch => updateActive(tab => ({ ...tab, panes: tab.panes.map(item => item.id === pane.id ? { ...item, ...patch } : item) })), onTransfer: (paths, target) => { void transfer(pane.endpointId, paths, target.endpointId, target.path); }, onExternalDrop: (sourceEndpointId, paths) => { void transfer(sourceEndpointId, paths, pane.endpointId, pane.path); } }, pane.id);
129
+ return _jsx(FileTransferPane, { pane: pane, endpoints: endpoints, dragSource: dragSource, refreshRevision: directoryRevisions[directoryKey(pane.endpointId, pane.path)] ?? 0, ...destination === undefined ? {} : { destination }, onManageConnections: () => setConnectionsOpen(true), onDragSourceChange: setDragSource, onChange: patch => updateActive(tab => ({ ...tab, panes: tab.panes.map(item => item.id === pane.id ? { ...item, ...patch } : item) })), onTransfer: (paths, target) => { void transfer(pane.endpointId, pane.path, paths, target.endpointId, target.path); }, onExternalDrop: (source, destinationDirectory) => { void dropFiles(source, pane, destinationDirectory); } }, pane.id);
103
130
  }) }), _jsx(TransferQueue, { jobs: jobs, endpoints: endpoints, onCancel: async (id) => { try {
104
131
  await cancelFileTransfer(id);
105
132
  setJobs(current => current.map(job => job.id === id ? { ...job, state: 'cancelled' } : job));
@@ -115,13 +142,14 @@ export function FileTransferWorkspace({ ftpProfiles, vaultEntries, proxyEntries,
115
142
  setError(errorMessage(reason));
116
143
  } } })] });
117
144
  }
118
- function FileTransferPane({ pane, endpoints, destination, refreshRevision, onChange, onTransfer, onExternalDrop, onManageConnections }) {
145
+ function FileTransferPane({ pane, endpoints, destination, dragSource, refreshRevision, onChange, onTransfer, onExternalDrop, onDragSourceChange, onManageConnections }) {
119
146
  const [view, setView] = useState();
120
147
  const [selected, setSelected] = useState([]);
121
148
  const [draftPath, setDraftPath] = useState(pane.path);
122
149
  const [loading, setLoading] = useState(false);
123
150
  const [scrollTop, setScrollTop] = useState(0);
124
151
  const [dragOver, setDragOver] = useState(false);
152
+ const [directoryDropTarget, setDirectoryDropTarget] = useState();
125
153
  const [deleteTarget, setDeleteTarget] = useState();
126
154
  const [error, setError] = useState();
127
155
  const bodyRef = useRef(null);
@@ -160,6 +188,10 @@ function FileTransferPane({ pane, endpoints, destination, refreshRevision, onCha
160
188
  }, [endpoint?.id, pane.path]);
161
189
  useEffect(() => { if (endpoint !== undefined)
162
190
  void load(pane.path); }, [endpoint?.id, refreshRevision]);
191
+ useEffect(() => { if (dragSource === undefined) {
192
+ setDragOver(false);
193
+ setDirectoryDropTarget(undefined);
194
+ } }, [dragSource]);
163
195
  const submitPath = (event) => { event.preventDefault(); void load(draftPath); };
164
196
  const chooseEndpoint = (next) => { loadGenerationRef.current += 1; onChange({ endpointId: next.id, path: next.initialPath }); setDraftPath(next.initialPath); setView(undefined); setError(undefined); };
165
197
  const showConnections = () => { loadGenerationRef.current += 1; onChange({ endpointId: '', path: '/' }); setView(undefined); setSelected([]); setError(undefined); setLoading(false); };
@@ -178,56 +210,62 @@ function FileTransferPane({ pane, endpoints, destination, refreshRevision, onCha
178
210
  const drop = (event) => {
179
211
  event.preventDefault();
180
212
  setDragOver(false);
181
- try {
182
- const payload = JSON.parse(event.dataTransfer.getData('application/x-dsh-remote-files'));
183
- if (payload.endpointId && Array.isArray(payload.paths))
184
- onExternalDrop(payload.endpointId, payload.paths);
185
- }
186
- catch { }
213
+ const payload = parseRemoteFilesDragPayload(event.dataTransfer.getData(REMOTE_FILES_DRAG_TYPE));
214
+ onDragSourceChange(undefined);
215
+ if (payload === undefined || payload.paneId === pane.id || isSameRemoteTransferLocation(payload, { endpointId: pane.endpointId, directory: pane.path }))
216
+ return;
217
+ onExternalDrop(payload, pane.path);
187
218
  };
188
219
  if (endpoint === undefined)
189
- return _jsxs("section", { className: "dsh-ssh-file-pane is-connections", children: [_jsxs("header", { children: [_jsxs("span", { className: "dsh-ssh-pane-heading", children: [_jsx("strong", { children: "\u8FDE\u63A5\u5217\u8868" }), _jsx("small", { children: "\u5355\u51FB\u8FDE\u63A5\u540E\u6D4F\u89C8\u8FDC\u7AEF\u6587\u4EF6" })] }), _jsx("button", { type: "button", className: "dsh-ssh-pane-manage-button", onClick: onManageConnections, children: "FTP \u7BA1\u7406" })] }), _jsx("div", { className: "dsh-ssh-endpoint-list", children: endpoints.map(item => _jsxs("button", { type: "button", className: "dsh-ssh-endpoint-row", onClick: () => chooseEndpoint(item), children: [_jsx("span", { className: `dsh-ssh-protocol-badge is-${item.protocol}`, children: protocolShort(item.protocol) }), _jsxs("span", { children: [_jsx("strong", { title: item.name, children: item.name }), _jsx("small", { title: item.address, children: item.address })] }), _jsxs("span", { children: [_jsx("i", { className: `dsh-ssh-protocol-dot is-${item.protocol}`, "aria-hidden": "true" }), item.group ?? (item.kind === 'sftp' ? 'SSH 主机' : '文件连接')] })] }, item.id)) }), _jsxs("footer", { children: [_jsxs("span", { children: [endpoints.length, " \u4E2A\u53EF\u7528\u8FDE\u63A5"] }), _jsx("small", { children: "SFTP \u590D\u7528 SSH \u4E3B\u673A\u914D\u7F6E" })] })] });
190
- return _jsxs("section", { className: `dsh-ssh-file-pane${dragOver ? ' is-drop-target' : ''}`, onDragOver: event => { if (event.dataTransfer.types.includes('application/x-dsh-remote-files')) {
220
+ return _jsxs("section", { className: "dsh-ssh-file-pane is-connections", children: [_jsxs("header", { children: [_jsxs("span", { className: "dsh-ssh-pane-heading", children: [_jsx("strong", { children: "\u8FDE\u63A5\u5217\u8868" }), _jsx("small", { children: "\u5355\u51FB\u8FDE\u63A5\u540E\u6D4F\u89C8\u8FDC\u7AEF\u6587\u4EF6" })] }), _jsx("button", { type: "button", "data-ssh-interactive": "control", className: "dsh-ssh-pane-manage-button", onClick: onManageConnections, children: "FTP \u7BA1\u7406" })] }), _jsx("div", { className: "dsh-ssh-endpoint-list", children: endpoints.map(item => _jsxs("button", { type: "button", "data-ssh-interactive": "row", className: "dsh-ssh-endpoint-row", onClick: () => chooseEndpoint(item), children: [_jsx("span", { className: `dsh-ssh-protocol-badge is-${item.protocol}`, children: protocolShort(item.protocol) }), _jsxs("span", { children: [_jsx("strong", { title: item.name, children: item.name }), _jsx("small", { title: item.address, children: item.address })] }), _jsxs("span", { children: [_jsx("i", { className: `dsh-ssh-protocol-dot is-${item.protocol}`, "aria-hidden": "true" }), item.group ?? (item.kind === 'sftp' ? 'SSH 主机' : '文件连接')] })] }, item.id)) }), _jsxs("footer", { children: [_jsxs("span", { children: [endpoints.length, " \u4E2A\u53EF\u7528\u8FDE\u63A5"] }), _jsx("small", { children: "SFTP \u590D\u7528 SSH \u4E3B\u673A\u914D\u7F6E" })] })] });
221
+ return _jsxs("section", { className: `dsh-ssh-file-pane${dragOver ? ' is-drop-target' : ''}`, onDragOver: event => { if (event.dataTransfer.types.includes(REMOTE_FILES_DRAG_TYPE) && canDropIntoPane(dragSource, pane)) {
191
222
  event.preventDefault();
223
+ event.dataTransfer.dropEffect = dragSource?.endpointId === pane.endpointId ? 'move' : 'copy';
224
+ setDirectoryDropTarget(undefined);
192
225
  setDragOver(true);
193
- } }, onDragLeave: event => { if (!event.currentTarget.contains(event.relatedTarget))
194
- setDragOver(false); }, onDrop: drop, children: [_jsxs("header", { children: [_jsx("button", { type: "button", className: "dsh-ssh-pane-back", "aria-label": "\u8FD4\u56DE\u8FDE\u63A5\u5217\u8868", onClick: showConnections, children: _jsx(IconChevronLeftOutline14, { size: 14 }) }), _jsxs("span", { className: "dsh-ssh-pane-endpoint", children: [_jsx("strong", { children: endpoint.name }), _jsx("small", { title: endpoint.address, children: endpoint.address })] }), _jsx("span", { className: `dsh-ssh-protocol-badge is-${endpoint.protocol}`, children: protocolShort(endpoint.protocol) })] }), _jsxs("form", { className: "dsh-ssh-file-pathbar", onSubmit: submitPath, children: [_jsx("button", { type: "button", "aria-label": "\u4E0A\u4E00\u7EA7\u76EE\u5F55", disabled: view?.parent === null || loading, onClick: () => { if (view?.parent)
195
- void load(view.parent); }, children: _jsx(UpGlyph, {}) }), _jsx("button", { type: "button", "aria-label": "\u5237\u65B0\u76EE\u5F55", disabled: loading, onClick: () => { void load(); }, children: _jsx(RefreshGlyph, {}) }), _jsx("input", { "aria-label": "\u8FDC\u7AEF\u8DEF\u5F84", value: draftPath, onChange: event => setDraftPath(event.target.value) }), _jsx("button", { type: "submit", disabled: loading, children: "\u524D\u5F80" })] }), _jsxs("div", { className: "dsh-ssh-file-table", role: "grid", "aria-busy": loading, children: [_jsxs("div", { className: "dsh-ssh-file-table-head", role: "row", children: [_jsx("span", { children: "\u540D\u79F0" }), _jsx("span", { children: "\u5927\u5C0F" }), _jsx("span", { children: "\u4FEE\u6539\u65F6\u95F4" })] }), _jsx("div", { ref: bodyRef, className: "dsh-ssh-file-table-body", onScroll: event => { if (virtualized)
196
- setScrollTop(event.currentTarget.scrollTop); }, children: loading && !view ? _jsx("div", { className: "dsh-ssh-file-loading", children: "\u6B63\u5728\u8BFB\u53D6\u76EE\u5F55\u2026" }) : error ? _jsxs("div", { className: "dsh-ssh-file-error", children: [_jsx("span", { children: error }), _jsx("button", { type: "button", onClick: () => { void load(); }, children: "\u91CD\u8BD5" })] }) : entries.length === 0 ? _jsx("div", { className: "dsh-ssh-table-empty", children: "\u8FD9\u4E2A\u76EE\u5F55\u662F\u7A7A\u7684\u3002" }) : _jsx("div", { className: virtualized ? 'dsh-ssh-file-virtual-list' : undefined, style: virtualized ? { height: `${entries.length * FILE_ROW_HEIGHT}px` } : undefined, children: visibleEntries.map((entry, offset) => _jsx(FileEntryRow, { entry: entry, endpointId: endpoint.id, selected: selected.includes(entry.path), selectedPaths: selected, ...virtualized ? { style: { position: 'absolute', insetInline: 0, transform: `translateY(${(virtualStart + offset) * FILE_ROW_HEIGHT}px)` } } : {}, onSelect: additive => select(entry, additive), onOpen: () => { if (entry.kind === 'directory')
197
- void load(entry.path); } }, entry.path)) }) })] }), _jsxs("footer", { children: [_jsx("span", { children: selected.length > 0 ? `已选择 ${selected.length} 项` : `${view?.entries.length ?? 0} 项` }), _jsxs("span", { className: "dsh-ssh-file-pane-actions", children: [_jsxs("button", { type: "button", className: "dsh-ssh-file-delete-button", disabled: selected.length === 0 || loading, onClick: () => setDeleteTarget(entries.filter(entry => selected.includes(entry.path))), children: [_jsx(IconTrashOutline16, { size: 14 }), "\u5220\u9664"] }), _jsxs("button", { type: "button", className: "dsh-ssh-transfer-to-button", disabled: selected.length === 0 || destination === undefined || !destination.endpointId || loading, onClick: () => { if (destination?.endpointId)
198
- onTransfer(selected, destination); }, children: ["\u4F20\u9001\u5230\u4E0B\u4E00\u680F ", _jsx("span", { "aria-hidden": "true", children: "\u2192" })] })] })] }), dragOver && _jsxs("div", { className: "dsh-ssh-file-drop-overlay", children: [_jsx("strong", { children: "\u4F20\u9001\u5230\u6B64\u76EE\u5F55" }), _jsx("span", { children: view?.path ?? pane.path })] }), deleteTarget !== undefined && _jsx(DeleteRemoteEntriesDialog, { endpoint: endpoint, entries: deleteTarget, onClose: () => setDeleteTarget(undefined), onDelete: removeSelected })] });
199
- }
200
- function DeleteRemoteEntriesDialog({ endpoint, entries, onClose, onDelete }) {
201
- const [deleting, setDeleting] = useState(false);
202
- const [error, setError] = useState();
203
- const directories = entries.filter(entry => entry.kind === 'directory').length;
204
- const submit = async () => {
205
- setDeleting(true);
206
- setError(undefined);
207
- try {
208
- await onDelete();
209
- setDeleting(false);
210
- onClose();
211
226
  }
212
- catch (reason) {
213
- setError(errorMessage(reason));
214
- setDeleting(false);
215
- }
216
- };
217
- return _jsxs(Dialog, { className: "dsh-ssh-file-delete-dialog", title: `删除 ${entries.length} 项?`, subtitle: `${endpoint.name} · 此操作无法撤销`, onClose: () => { if (!deleting)
218
- onClose(); }, children: [_jsxs("div", { className: "dsh-ssh-file-delete-copy", children: [_jsx("span", { children: _jsx(IconTrashOutline16, { size: 18 }) }), _jsxs("p", { children: ["\u5C06\u76F4\u63A5\u4ECE\u8FDC\u7AEF\u5220\u9664\u6240\u9009\u5185\u5BB9\u3002", directories > 0 ? `其中 ${directories} 个目录及其全部内容会被递归删除。` : ''] })] }), _jsxs("div", { className: "dsh-ssh-file-delete-list", children: [entries.slice(0, 6).map(entry => _jsxs("div", { children: [_jsx(FileGlyph, { directory: entry.kind === 'directory' }), _jsxs("span", { children: [_jsx("strong", { children: entry.name }), _jsx("small", { title: entry.path, children: entry.path })] })] }, entry.path)), entries.length > 6 && _jsxs("p", { children: ["\u4EE5\u53CA\u5176\u4ED6 ", entries.length - 6, " \u9879"] })] }), error && _jsx("p", { className: "dsh-ssh-inline-error", role: "alert", children: error }), _jsxs("div", { className: "dsh-ssh-dialog-actions", children: [_jsx("span", {}), _jsx("button", { type: "button", className: "dsh-ssh-secondary-button", disabled: deleting, onClick: onClose, children: "\u53D6\u6D88" }), _jsx("button", { type: "button", className: "dsh-ssh-danger-button", disabled: deleting, onClick: () => { void submit(); }, children: deleting ? '正在删除…' : '确认删除' })] })] });
227
+ else if (dragOver)
228
+ setDragOver(false); }, onDragLeave: event => { if (!event.currentTarget.contains(event.relatedTarget)) {
229
+ setDragOver(false);
230
+ setDirectoryDropTarget(undefined);
231
+ } }, onDrop: drop, children: [_jsxs("header", { children: [_jsx("button", { type: "button", className: "dsh-ssh-pane-back", "aria-label": "\u8FD4\u56DE\u8FDE\u63A5\u5217\u8868", onClick: showConnections, children: _jsx(IconChevronLeftOutline14, { size: 14 }) }), _jsxs("span", { className: "dsh-ssh-pane-endpoint", children: [_jsx("strong", { children: endpoint.name }), _jsx("small", { title: endpoint.address, children: endpoint.address })] }), _jsx("span", { className: `dsh-ssh-protocol-badge is-${endpoint.protocol}`, children: protocolShort(endpoint.protocol) })] }), _jsxs("form", { className: "dsh-ssh-file-pathbar", onSubmit: submitPath, children: [_jsx("button", { type: "button", "aria-label": "\u4E0A\u4E00\u7EA7\u76EE\u5F55", disabled: view?.parent === null || loading, onClick: () => { if (view?.parent)
232
+ void load(view.parent); }, children: _jsx(UpGlyph, {}) }), _jsx("button", { type: "button", "aria-label": "\u5237\u65B0\u76EE\u5F55", disabled: loading, onClick: () => { void load(); }, children: _jsx(RefreshGlyph, {}) }), _jsx("input", { "aria-label": "\u8FDC\u7AEF\u8DEF\u5F84", value: draftPath, onChange: event => setDraftPath(event.target.value) }), _jsx("button", { type: "submit", disabled: loading, children: "\u524D\u5F80" })] }), _jsxs("div", { className: "dsh-ssh-file-table", role: "grid", "aria-busy": loading, children: [_jsxs("div", { className: "dsh-ssh-file-table-head", role: "row", children: [_jsx("span", { children: "\u540D\u79F0" }), _jsx("span", { children: "\u5927\u5C0F" }), _jsx("span", { children: "\u4FEE\u6539\u65F6\u95F4" }), _jsx("span", { "aria-hidden": "true" })] }), _jsx("div", { ref: bodyRef, className: "dsh-ssh-file-table-body", onScroll: event => { if (virtualized)
233
+ setScrollTop(event.currentTarget.scrollTop); }, children: loading && !view ? _jsx("div", { className: "dsh-ssh-file-loading", children: "\u6B63\u5728\u8BFB\u53D6\u76EE\u5F55\u2026" }) : error ? _jsxs("div", { className: "dsh-ssh-file-error", children: [_jsx("span", { children: error }), _jsx("button", { type: "button", onClick: () => { void load(); }, children: "\u91CD\u8BD5" })] }) : entries.length === 0 ? _jsx("div", { className: "dsh-ssh-table-empty", children: "\u8FD9\u4E2A\u76EE\u5F55\u662F\u7A7A\u7684\u3002" }) : _jsx("div", { className: virtualized ? 'dsh-ssh-file-virtual-list' : undefined, style: virtualized ? { height: `${entries.length * FILE_ROW_HEIGHT}px` } : undefined, children: visibleEntries.map((entry, offset) => _jsx(FileEntryRow, { entry: entry, paneId: pane.id, endpointId: endpoint.id, sourceDirectory: view?.path ?? pane.path, dragSource: dragSource, dropTarget: directoryDropTarget === entry.path, selected: selected.includes(entry.path), selectedPaths: selected, ...virtualized ? { style: { position: 'absolute', insetInline: 0, transform: `translateY(${(virtualStart + offset) * FILE_ROW_HEIGHT}px)` } } : {}, onDelete: () => setDeleteTarget([entry]), onDirectoryTarget: target => { setDragOver(false); setDirectoryDropTarget(target); }, onDropIntoDirectory: (payload, target) => { setDirectoryDropTarget(undefined); onDragSourceChange(undefined); onExternalDrop(payload, target); }, onDragSourceChange: onDragSourceChange, onSelect: additive => select(entry, additive), onOpen: () => { void load(entry.path); } }, entry.path)) }) })] }), _jsxs("footer", { children: [_jsx("span", { children: selected.length > 0 ? `已选择 ${selected.length} 项` : `${view?.entries.length ?? 0} 项` }), _jsx("span", { className: "dsh-ssh-file-pane-actions", children: _jsxs("button", { type: "button", "data-ssh-interactive": "control", className: "dsh-ssh-transfer-to-button", disabled: selected.length === 0 || destination === undefined || !destination.endpointId || loading, onClick: () => { if (destination?.endpointId)
234
+ onTransfer(selected, destination); }, children: ["\u4F20\u9001\u5230\u4E0B\u4E00\u680F ", _jsx("span", { "aria-hidden": "true", children: "\u2192" })] }) })] }), dragOver && _jsxs("div", { className: "dsh-ssh-file-drop-overlay", children: [_jsx("strong", { children: dragSource?.endpointId === pane.endpointId ? '移动到此目录' : '复制到此目录' }), _jsx("span", { children: view?.path ?? pane.path })] }), deleteTarget !== undefined && _jsx(FileEntryDeleteDialog, { locationName: endpoint.name, locationKind: "remote", entries: deleteTarget, onClose: () => setDeleteTarget(undefined), onDelete: removeSelected })] });
219
235
  }
220
- function FileEntryRow({ entry, endpointId, selected, selectedPaths, style, onSelect, onOpen }) {
221
- return _jsxs("div", { role: "row", tabIndex: 0, "aria-selected": selected, draggable: entry.kind === 'file' || entry.kind === 'directory', className: `dsh-ssh-file-row is-${entry.kind}${selected ? ' is-selected' : ''}`, style: style, onClick: event => onSelect(event.ctrlKey || event.metaKey), onDoubleClick: onOpen, onKeyDown: event => { if (event.key === 'Enter')
236
+ function FileEntryRow({ entry, paneId, endpointId, sourceDirectory, dragSource, dropTarget, selected, selectedPaths, style, onSelect, onOpen, onDelete, onDirectoryTarget, onDropIntoDirectory, onDragSourceChange }) {
237
+ const directory = isNavigableRemoteEntry(entry);
238
+ const acceptsDrop = directory && (dragSource === undefined || canTransferIntoRemoteDirectory(dragSource, dragSource.paths, { endpointId, directory: entry.path }));
239
+ const dropIntoDirectory = (event) => {
240
+ if (!directory)
241
+ return;
242
+ event.preventDefault();
243
+ event.stopPropagation();
244
+ onDirectoryTarget(undefined);
245
+ const payload = parseRemoteFilesDragPayload(event.dataTransfer.getData(REMOTE_FILES_DRAG_TYPE));
246
+ if (payload !== undefined && canTransferIntoRemoteDirectory(payload, payload.paths, { endpointId, directory: entry.path }))
247
+ onDropIntoDirectory(payload, entry.path);
248
+ };
249
+ return _jsxs("div", { role: "row", "data-ssh-interactive": "row", "data-ssh-context-row": true, tabIndex: 0, "aria-selected": selected, "aria-label": `${entry.name}${directory ? ',目录,单击进入;可接收拖放' : ''}`, draggable: entry.kind === 'file' || directory, className: `dsh-ssh-file-row is-${directory ? 'directory' : entry.kind}${selected ? ' is-selected' : ''}${dropTarget ? ' is-drop-target' : ''}`, style: style, onClick: event => { if (directory && !event.ctrlKey && !event.metaKey && !event.shiftKey)
250
+ onOpen();
251
+ else
252
+ onSelect(event.ctrlKey || event.metaKey); }, onKeyDown: event => { if (event.target !== event.currentTarget)
253
+ return; if (event.key === 'Enter' && directory)
222
254
  onOpen(); if (event.key === ' ') {
223
255
  event.preventDefault();
224
256
  onSelect(event.ctrlKey || event.metaKey);
225
- } }, onDragStart: event => { const paths = selected ? selectedPaths : [entry.path]; event.dataTransfer.effectAllowed = 'copy'; event.dataTransfer.setData('application/x-dsh-remote-files', JSON.stringify({ endpointId, paths })); }, children: [_jsxs("span", { children: [_jsx(FileGlyph, { directory: entry.kind === 'directory' }), _jsx("i", { title: entry.name, children: entry.name })] }), _jsx("span", { children: entry.kind === 'directory' ? '—' : formatBytes(entry.size) }), _jsx("span", { children: entry.modifiedAt > 0 ? new Date(entry.modifiedAt).toLocaleString() : '—' })] });
257
+ } }, onDragStart: event => { const paths = selected ? selectedPaths : [entry.path]; const source = { paneId, endpointId, directory: sourceDirectory, paths }; onDragSourceChange(source); event.dataTransfer.effectAllowed = 'copyMove'; event.dataTransfer.setData(REMOTE_FILES_DRAG_TYPE, JSON.stringify(source)); }, onDragEnd: () => onDragSourceChange(undefined), onDragOver: event => { if (event.dataTransfer.types.includes(REMOTE_FILES_DRAG_TYPE) && acceptsDrop) {
258
+ event.preventDefault();
259
+ event.stopPropagation();
260
+ event.dataTransfer.dropEffect = dragSource?.endpointId === endpointId ? 'move' : 'copy';
261
+ onDirectoryTarget(entry.path);
262
+ } }, onDragLeave: event => { if (dropTarget && !event.currentTarget.contains(event.relatedTarget))
263
+ onDirectoryTarget(undefined); }, onDrop: dropIntoDirectory, children: [_jsxs("span", { children: [_jsx(FileGlyph, { directory: directory }), _jsx("i", { title: entry.name, children: entry.name })] }), _jsx("span", { children: directory ? '—' : formatBytes(entry.size) }), _jsx("span", { children: entry.modifiedAt > 0 ? new Date(entry.modifiedAt).toLocaleString() : '—' }), _jsx("button", { type: "button", className: "dsh-ssh-file-row-delete dsh-ssh-context-action", draggable: false, "aria-label": `删除 ${entry.name}`, title: `删除 ${entry.name}`, onClick: event => { event.stopPropagation(); onDelete(); }, children: _jsx(IconTrashOutline16, { size: 14 }) })] });
226
264
  }
227
265
  function TransferQueue({ jobs, endpoints, onCancel, onConflict }) {
228
266
  const [open, setOpen] = useState(true);
229
267
  const active = jobs.filter(job => job.state === 'queued' || job.state === 'scanning' || job.state === 'transferring');
230
- return _jsxs("section", { className: `dsh-ssh-transfer-queue${open ? ' is-open' : ''}`, children: [_jsxs("button", { type: "button", className: "dsh-ssh-transfer-queue-heading", "aria-expanded": open, onClick: () => setOpen(value => !value), children: [_jsxs("span", { children: [_jsx("strong", { children: "\u4F20\u8F93\u4EFB\u52A1" }), _jsx("small", { children: active.length > 0 ? `${active.length} 个进行中` : jobs.length > 0 ? '最近任务' : '暂无任务' })] }), _jsx("span", { children: open ? '收起' : '展开' })] }), open && _jsx("div", { className: "dsh-ssh-transfer-job-list", children: jobs.length === 0 ? _jsx("p", { children: "\u4ECE\u4E00\u4E2A\u7A97\u683C\u62D6\u5230\u53E6\u4E00\u4E2A\u7A97\u683C\uFF0C\u6216\u9009\u4E2D\u6587\u4EF6\u540E\u70B9\u51FB\u201C\u4F20\u9001\u5230\u4E0B\u4E00\u680F\u201D\u3002" }) : jobs.slice(0, 12).map(job => {
268
+ return _jsxs("section", { className: `dsh-ssh-transfer-queue${open ? ' is-open' : ''}`, children: [_jsxs("button", { type: "button", "data-ssh-interactive": "control", className: "dsh-ssh-transfer-queue-heading", "aria-expanded": open, onClick: () => setOpen(value => !value), children: [_jsxs("span", { children: [_jsx("strong", { children: "\u4F20\u8F93\u4EFB\u52A1" }), _jsx("small", { children: active.length > 0 ? `${active.length} 个进行中` : jobs.length > 0 ? '最近任务' : '暂无任务' })] }), _jsx("span", { children: open ? '收起' : '展开' })] }), open && _jsx("div", { className: "dsh-ssh-transfer-job-list", children: jobs.length === 0 ? _jsx("p", { children: "\u4ECE\u4E00\u4E2A\u7A97\u683C\u62D6\u5230\u53E6\u4E00\u4E2A\u7A97\u683C\uFF0C\u6216\u9009\u4E2D\u6587\u4EF6\u540E\u70B9\u51FB\u201C\u4F20\u9001\u5230\u4E0B\u4E00\u680F\u201D\u3002" }) : jobs.slice(0, 12).map(job => {
231
269
  const progress = job.totalBytes > 0 ? Math.min(100, job.transferredBytes / job.totalBytes * 100) : job.state === 'completed' ? 100 : 0;
232
270
  return _jsxs("article", { children: [_jsx("span", { className: `dsh-ssh-transfer-state is-${job.state}`, "aria-hidden": "true" }), _jsxs("span", { className: "dsh-ssh-transfer-job-copy", children: [_jsxs("strong", { children: [remoteLabel(job.request.sourcePaths), " ", _jsx("i", { children: "\u2192" }), " ", endpointName(endpoints, job.request.destinationEndpointId)] }), _jsx("small", { children: job.error ?? jobLabel(job, progress) }), _jsx("span", { className: "dsh-ssh-transfer-progress", children: _jsx("i", { style: { transform: `scaleX(${progress / 100})` } }) })] }), (job.state === 'queued' || job.state === 'scanning' || job.state === 'transferring') ? _jsx("button", { type: "button", className: "dsh-ssh-icon-button", "aria-label": "\u53D6\u6D88\u4F20\u8F93", onClick: () => { void onCancel(job.id); }, children: _jsx(IconCloseOutline16, { size: 15 }) }) : job.state === 'failed' && job.error?.includes('destination already contains') ? _jsx("button", { type: "button", className: "dsh-ssh-job-resolve", onClick: () => onConflict(job), children: "\u5904\u7406" }) : null] }, job.id);
233
271
  }) })] });
@@ -235,12 +273,25 @@ function TransferQueue({ jobs, endpoints, onCancel, onConflict }) {
235
273
  function ConflictDialog({ job, onClose, onRetry }) {
236
274
  const [policy, setPolicy] = useState('rename');
237
275
  const [saving, setSaving] = useState(false);
238
- return _jsxs(Dialog, { className: "dsh-ssh-transfer-conflict-dialog", title: "\u76EE\u6807\u4E2D\u5B58\u5728\u540C\u540D\u6587\u4EF6", subtitle: "\u4E3A\u672C\u6279\u4F20\u8F93\u9009\u62E9\u5904\u7406\u65B9\u5F0F", onClose: onClose, children: [_jsxs("div", { className: "dsh-ssh-transfer-conflict-copy", children: [_jsx("strong", { children: remoteLabel(job.request.sourcePaths) }), _jsx("span", { children: job.request.destinationDirectory })] }), _jsx("div", { className: "dsh-ssh-conflict-options", children: [['rename', '自动重命名', '保留两份文件,在名称后添加序号'], ['skip', '跳过同名文件', '继续传输其他内容'], ['overwrite', '覆盖目标文件', '使用来源内容替换目标内容']].map(option => _jsxs("button", { type: "button", className: policy === option[0] ? 'is-active' : '', onClick: () => setPolicy(option[0]), children: [_jsx("i", { "aria-hidden": "true" }), _jsxs("span", { children: [_jsx("strong", { children: option[1] }), _jsx("small", { children: option[2] })] })] }, option[0])) }), _jsxs("div", { className: "dsh-ssh-dialog-actions", children: [_jsx("button", { type: "button", className: "dsh-ssh-secondary-button", onClick: onClose, children: "\u53D6\u6D88" }), _jsx("button", { type: "button", className: "dsh-ssh-primary-button", disabled: saving, onClick: () => { setSaving(true); void onRetry(policy).finally(() => setSaving(false)); }, children: saving ? '正在重新加入队列…' : '继续传输' })] })] });
276
+ return _jsxs(Dialog, { className: "dsh-ssh-transfer-conflict-dialog", title: "\u76EE\u6807\u4E2D\u5B58\u5728\u540C\u540D\u6587\u4EF6", subtitle: "\u4E3A\u672C\u6279\u4F20\u8F93\u9009\u62E9\u5904\u7406\u65B9\u5F0F", onClose: onClose, children: [_jsxs("div", { className: "dsh-ssh-transfer-conflict-copy", children: [_jsx("strong", { children: remoteLabel(job.request.sourcePaths) }), _jsx("span", { children: job.request.destinationDirectory })] }), _jsx("div", { className: "dsh-ssh-conflict-options", children: [['rename', '自动重命名', '保留两份文件,在名称后添加序号'], ['skip', '跳过同名文件', '继续传输其他内容'], ['overwrite', '覆盖目标文件', '使用来源内容替换目标内容']].map(option => _jsxs("button", { type: "button", "data-ssh-interactive": "choice", "aria-pressed": policy === option[0], className: policy === option[0] ? 'is-active' : '', onClick: () => setPolicy(option[0]), children: [_jsx("i", { "aria-hidden": "true" }), _jsxs("span", { children: [_jsx("strong", { children: option[1] }), _jsx("small", { children: option[2] })] })] }, option[0])) }), _jsxs("div", { className: "dsh-ssh-dialog-actions", children: [_jsx("button", { type: "button", className: "dsh-ssh-secondary-button", "data-ssh-dialog-close": true, onClick: onClose, children: "\u53D6\u6D88" }), _jsx("button", { type: "button", className: "dsh-ssh-primary-button", disabled: saving, onClick: () => { setSaving(true); void onRetry(policy).finally(() => setSaving(false)); }, children: saving ? '正在重新加入队列…' : '继续传输' })] })] });
239
277
  }
240
278
  function FileAccessDialog({ endpoints, access, onClose }) {
241
279
  const value = access.value;
280
+ const mounted = endpoints.filter(endpoint => value?.fileEndpointIds.includes(endpoint.id)).length;
242
281
  const toggle = (id) => access.setFileEndpoints(value?.fileEndpointIds.includes(id) ? value.fileEndpointIds.filter(item => item !== id) : [...(value?.fileEndpointIds ?? []), id]);
243
- return _jsxs(Dialog, { className: "dsh-ssh-file-access-dialog", title: "\u5F53\u524D\u4F1A\u8BDD\u7684\u6587\u4EF6\u6743\u9650", subtitle: "\u6587\u4EF6\u6743\u9650\u4E0E SSH \u547D\u4EE4\u3001\u7EC8\u7AEF\u6743\u9650\u76F8\u4E92\u72EC\u7ACB", onClose: onClose, children: [access.error && _jsx("p", { className: "dsh-ssh-inline-error", role: "alert", children: access.error }), _jsx("div", { className: "dsh-ssh-file-access-list", children: endpoints.length === 0 ? _jsx("div", { className: "dsh-ssh-table-empty", children: "\u8FD8\u6CA1\u6709\u53EF\u6388\u6743\u7684\u6587\u4EF6\u8FDE\u63A5\u3002" }) : endpoints.map(endpoint => _jsxs("button", { type: "button", className: value?.fileEndpointIds.includes(endpoint.id) ? 'is-mounted' : '', "aria-pressed": value?.fileEndpointIds.includes(endpoint.id), onClick: () => toggle(endpoint.id), children: [_jsx("span", { className: `dsh-ssh-protocol-badge is-${endpoint.protocol}`, children: protocolShort(endpoint.protocol) }), _jsxs("span", { children: [_jsx("strong", { children: endpoint.name }), _jsx("small", { children: endpoint.address })] }), _jsx("i", { children: value?.fileEndpointIds.includes(endpoint.id) ? '已授权' : '未授权' })] }, endpoint.id)) }), _jsxs("div", { className: "dsh-ssh-file-access-options", children: [_jsxs("label", { children: [_jsxs("span", { children: [_jsx("strong", { children: "\u6587\u4EF6\u64CD\u4F5C\u6743\u9650" }), _jsx("small", { children: "\u6D4F\u89C8\u6A21\u5F0F\u4E0D\u4F1A\u5411 AI \u66B4\u9732\u4F20\u8F93\u5DE5\u5177" })] }), _jsxs("select", { value: value?.filePermission ?? 'browse', onChange: event => access.setFilePermission(event.target.value), children: [_jsx("option", { value: "browse", children: "\u4EC5\u6D4F\u89C8\u76EE\u5F55" }), _jsx("option", { value: "transfer", children: "\u5141\u8BB8\u8DE8\u7AEF\u4F20\u8F93" })] })] }), _jsxs("label", { className: "dsh-ssh-switch-row", children: [_jsxs("span", { children: [_jsx("strong", { children: "\u4F20\u8F93\u524D\u786E\u8BA4" }), _jsx("small", { children: "AI \u53D1\u8D77\u4F20\u8F93\u65F6\u8BF7\u6C42 DSH \u6388\u6743" })] }), _jsx("input", { type: "checkbox", checked: value?.requireFileApproval ?? true, onChange: event => access.setRequireFileApproval(event.target.checked) })] })] }), _jsxs("div", { className: "dsh-ssh-dialog-actions", children: [_jsx("span", {}), _jsx("button", { type: "button", className: "dsh-ssh-primary-button", onClick: onClose, children: "\u5B8C\u6210" })] })] });
282
+ return _jsxs(Dialog, { className: "dsh-ssh-file-access-dialog", title: "\u4F1A\u8BDD\u8BBF\u95EE", subtitle: "\u5355\u72EC\u7BA1\u7406\u5F53\u524D\u4F1A\u8BDD\u53EF\u4F7F\u7528\u7684\u8FDC\u7AEF\u6587\u4EF6\u8FDE\u63A5", onClose: onClose, children: [_jsxs("div", { className: "dsh-ssh-file-access-body dsh-ssh-scroll-surface", children: [access.error && _jsx("p", { className: "dsh-ssh-inline-error", role: "alert", children: access.error }), _jsxs("section", { className: "dsh-ssh-file-access-section", "aria-labelledby": "dsh-ssh-file-access-connections", children: [_jsxs("header", { children: [_jsxs("span", { children: [_jsx("strong", { id: "dsh-ssh-file-access-connections", children: "\u6587\u4EF6\u8FDE\u63A5" }), _jsx("small", { children: "\u5141\u8BB8\u5F53\u524D\u4F1A\u8BDD\u6D4F\u89C8\u6307\u5B9A\u7684 FTP\u3001FTPS \u6216 SFTP" })] }), _jsxs("i", { children: [mounted.toString().padStart(2, '0'), " / ", endpoints.length.toString().padStart(2, '0')] })] }), _jsx("div", { className: "dsh-ssh-file-access-list", children: access.loading ? _jsx(FileAccessSkeleton, {}) : endpoints.length === 0 ? _jsxs("div", { className: "dsh-ssh-file-access-empty", children: [_jsx("strong", { children: "\u6CA1\u6709\u53EF\u7528\u8FDE\u63A5" }), _jsx("small", { children: "\u8BF7\u5148\u5728\u6587\u4EF6\u4F20\u8F93\u4E2D\u6DFB\u52A0 FTP\uFF0C\u6216\u5728\u4E3B\u673A\u9762\u677F\u4E2D\u6DFB\u52A0 SSH \u4E3B\u673A\u3002" })] }) : endpoints.map(endpoint => _jsx(FileAccessEndpoint, { endpoint: endpoint, enabled: value?.fileEndpointIds.includes(endpoint.id) ?? false, onToggle: () => toggle(endpoint.id) }, endpoint.id)) })] }), _jsxs("section", { className: "dsh-ssh-file-access-section is-options", "aria-labelledby": "dsh-ssh-file-access-policy", children: [_jsx("header", { children: _jsxs("span", { children: [_jsx("strong", { id: "dsh-ssh-file-access-policy", children: "\u8BBF\u95EE\u7B56\u7565" }), _jsx("small", { children: "\u6587\u4EF6\u6743\u9650\u4E0E SSH \u547D\u4EE4\u3001\u7EC8\u7AEF\u6743\u9650\u76F8\u4E92\u72EC\u7ACB" })] }) }), _jsxs("div", { className: "dsh-ssh-file-access-options", children: [_jsxs("div", { className: "dsh-ssh-file-access-policy-row", children: [_jsxs("span", { children: [_jsx("strong", { children: "\u6587\u4EF6\u64CD\u4F5C\u6743\u9650" }), _jsx("small", { children: "\u6D4F\u89C8\u6A21\u5F0F\u4E0D\u4F1A\u5411 AI \u66B4\u9732\u8DE8\u7AEF\u4F20\u8F93\u5DE5\u5177" })] }), _jsx(FilePermissionPicker, { value: value?.filePermission ?? 'browse', onChange: access.setFilePermission })] }), _jsxs("div", { className: "dsh-ssh-file-access-policy-row", children: [_jsxs("span", { children: [_jsx("strong", { children: "\u4F20\u8F93\u524D\u786E\u8BA4" }), _jsx("small", { children: "AI \u53D1\u8D77\u4F20\u8F93\u65F6\u8BF7\u6C42 DSH \u6388\u6743" })] }), _jsx(FileApprovalSwitch, { checked: value?.requireFileApproval ?? true, onChange: access.setRequireFileApproval })] })] })] })] }), _jsxs("footer", { className: "dsh-ssh-file-access-footer", children: [_jsxs("span", { className: access.saving ? 'is-saving' : 'is-saved', children: [_jsx("i", { "aria-hidden": "true" }), access.saving ? '正在保存更改' : `${mounted > 0 ? `已授权 ${mounted} 个连接` : '未授权连接'} · 自动保存`] }), _jsx("button", { type: "button", className: "dsh-ssh-primary-button", "data-ssh-dialog-close": true, disabled: access.saving, onClick: onClose, children: access.saving ? '保存中…' : '完成' })] })] });
283
+ }
284
+ function FileAccessEndpoint({ endpoint, enabled, onToggle }) {
285
+ return _jsxs("button", { type: "button", "data-ssh-interactive": "choice", className: `dsh-ssh-file-access-endpoint${enabled ? ' is-mounted' : ''}`, "aria-pressed": enabled, onClick: onToggle, children: [_jsx("span", { className: `dsh-ssh-protocol-badge is-${endpoint.protocol}`, children: protocolShort(endpoint.protocol) }), _jsxs("span", { className: "dsh-ssh-file-access-endpoint-copy", children: [_jsx("strong", { title: endpoint.name, children: endpoint.name }), _jsx("small", { title: endpoint.address, children: endpoint.address })] }), _jsxs("span", { className: "dsh-ssh-file-access-state", children: [_jsx("i", { "aria-hidden": "true", children: _jsx("b", {}) }), _jsx("em", { children: enabled ? '已授权' : '未授权' })] })] });
286
+ }
287
+ function FileAccessSkeleton() {
288
+ return _jsx("div", { className: "dsh-ssh-file-access-skeleton", "aria-label": "\u6B63\u5728\u8BFB\u53D6\u4F1A\u8BDD\u6743\u9650", children: [0, 1, 2].map(index => _jsxs("span", { style: { '--ssh-skeleton-index': index }, children: [_jsx("i", {}), _jsxs("b", { children: [_jsx("i", {}), _jsx("i", {})] }), _jsx("em", {})] }, index)) });
289
+ }
290
+ function FilePermissionPicker({ value, onChange }) {
291
+ return _jsx("div", { className: "dsh-ssh-file-permission-picker", role: "radiogroup", "aria-label": "\u6587\u4EF6\u64CD\u4F5C\u6743\u9650", children: [['browse', '仅浏览'], ['transfer', '允许传输']].map(([id, label]) => _jsx("button", { type: "button", "data-ssh-interactive": "choice", role: "radio", "aria-checked": value === id, className: value === id ? 'is-active' : '', onClick: () => onChange(id), children: label }, id)) });
292
+ }
293
+ function FileApprovalSwitch({ checked, onChange }) {
294
+ return _jsxs("button", { type: "button", "data-ssh-interactive": "choice", role: "switch", "aria-checked": checked, className: `dsh-ssh-file-approval-switch${checked ? ' is-active' : ''}`, onClick: () => onChange(!checked), children: [_jsx("i", { "aria-hidden": "true", children: _jsx("b", {}) }), _jsx("span", { children: checked ? '需要确认' : '直接执行' })] });
244
295
  }
245
296
  function restoreTabs() {
246
297
  try {
@@ -276,6 +327,7 @@ function endpointName(endpoints, id) { return endpoints.find(endpoint => endpoin
276
327
  function remoteLabel(paths) { const name = paths[0]?.replaceAll('\\', '/').split('/').filter(Boolean).at(-1) ?? '文件'; return paths.length > 1 ? `${name} 等 ${paths.length} 项` : name; }
277
328
  function protocolShort(protocol) { return protocol === 'sftp' ? 'SFTP' : protocol === 'ftp' ? 'FTP' : protocol === 'ftps-explicit' ? 'FTPS' : 'FTPS-I'; }
278
329
  function directoryKey(endpointId, value) { return JSON.stringify([endpointId, value.replaceAll('\\', '/').replace(/\/+$/, '') || '/']); }
330
+ function canDropIntoPane(source, pane) { return source === undefined || (source.paneId !== pane.id && !isSameRemoteTransferLocation(source, { endpointId: pane.endpointId, directory: pane.path })); }
279
331
  function isFinished(state) { return state === 'completed' || state === 'failed' || state === 'cancelled'; }
280
332
  function formatBytes(value) { if (value < 1024)
281
333
  return `${value} B`; if (value < 1024 ** 2)