@lemoncat7/dsh-ssh 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/lib/activity-panel.js +2 -2
- package/lib/activity-panel.js.map +1 -1
- package/lib/adaptive-workspace.js +1 -1
- package/lib/adaptive-workspace.js.map +1 -1
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js +38 -10
- package/lib/api.js.map +1 -1
- package/lib/border-glow.d.ts +14 -0
- package/lib/border-glow.d.ts.map +1 -0
- package/lib/border-glow.js +47 -0
- package/lib/border-glow.js.map +1 -0
- package/lib/client-api.d.ts +9 -0
- package/lib/client-api.d.ts.map +1 -1
- package/lib/client-api.js +6 -0
- package/lib/client-api.js.map +1 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +1628 -324
- package/lib/client.js.map +4 -4
- package/lib/file-entry-delete-dialog.d.ts +9 -0
- package/lib/file-entry-delete-dialog.d.ts.map +1 -0
- package/lib/file-entry-delete-dialog.js +25 -0
- package/lib/file-entry-delete-dialog.js.map +1 -0
- package/lib/file-transfer-intent.d.ts +15 -0
- package/lib/file-transfer-intent.d.ts.map +1 -0
- package/lib/file-transfer-intent.js +35 -0
- package/lib/file-transfer-intent.js.map +1 -0
- package/lib/file-transfer-workspace.d.ts.map +1 -1
- package/lib/file-transfer-workspace.js +96 -43
- package/lib/file-transfer-workspace.js.map +1 -1
- package/lib/ftp-adapter.js +29 -1
- package/lib/ftp-adapter.js.map +1 -1
- package/lib/ftp-profile-editor.js +2 -2
- package/lib/ftp-profile-editor.js.map +1 -1
- package/lib/glare-hover.d.ts +13 -0
- package/lib/glare-hover.d.ts.map +1 -0
- package/lib/glare-hover.js +22 -0
- package/lib/glare-hover.js.map +1 -0
- package/lib/local-workspace.d.ts +1 -0
- package/lib/local-workspace.d.ts.map +1 -1
- package/lib/local-workspace.js +22 -1
- package/lib/local-workspace.js.map +1 -1
- package/lib/motion.d.ts +5 -0
- package/lib/motion.d.ts.map +1 -0
- package/lib/motion.js +115 -0
- package/lib/motion.js.map +1 -0
- package/lib/profile-editor.js +2 -2
- package/lib/profile-editor.js.map +1 -1
- package/lib/remote-entry-operations.d.ts +11 -0
- package/lib/remote-entry-operations.d.ts.map +1 -0
- package/lib/remote-entry-operations.js +38 -0
- package/lib/remote-entry-operations.js.map +1 -0
- package/lib/remote-file-drop.d.ts +14 -0
- package/lib/remote-file-drop.d.ts.map +1 -0
- package/lib/remote-file-drop.js +29 -0
- package/lib/remote-file-drop.js.map +1 -0
- package/lib/remote-files.d.ts +2 -0
- package/lib/remote-files.d.ts.map +1 -1
- package/lib/remote-files.js.map +1 -1
- package/lib/remote-workspace-tree.d.ts.map +1 -1
- package/lib/remote-workspace-tree.js +7 -5
- package/lib/remote-workspace-tree.js.map +1 -1
- package/lib/sftp-client.d.ts.map +1 -1
- package/lib/sftp-client.js +95 -16
- package/lib/sftp-client.js.map +1 -1
- package/lib/sftp.d.ts.map +1 -1
- package/lib/sftp.js +12 -0
- package/lib/sftp.js.map +1 -1
- package/lib/terminal-view.js +21 -21
- package/lib/ui-components.d.ts.map +1 -1
- package/lib/ui-components.js +20 -4
- package/lib/ui-components.js.map +1 -1
- 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,15 @@
|
|
|
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 isSameRemoteTransferLocation(source: RemoteTransferLocation, destination: RemoteTransferLocation): boolean;
|
|
12
|
+
export declare function canTransferIntoRemoteDirectory(source: RemoteTransferLocation, sourcePaths: string[], destination: RemoteTransferLocation): boolean;
|
|
13
|
+
export declare function remoteDropOperation(source: RemoteFilesDragPayload, destination: RemoteTransferLocation): RemoteDropOperation;
|
|
14
|
+
export declare function parseRemoteFilesDragPayload(raw: string): RemoteFilesDragPayload | undefined;
|
|
15
|
+
//# 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,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,35 @@
|
|
|
1
|
+
export const REMOTE_FILES_DRAG_TYPE = 'application/x-dsh-remote-files';
|
|
2
|
+
export function isSameRemoteTransferLocation(source, destination) {
|
|
3
|
+
return source.endpointId === destination.endpointId && normalizeRemoteDirectory(source.directory) === normalizeRemoteDirectory(destination.directory);
|
|
4
|
+
}
|
|
5
|
+
export function canTransferIntoRemoteDirectory(source, sourcePaths, destination) {
|
|
6
|
+
if (source.endpointId !== destination.endpointId)
|
|
7
|
+
return true;
|
|
8
|
+
const target = normalizeRemoteDirectory(destination.directory);
|
|
9
|
+
return !sourcePaths.some(path => {
|
|
10
|
+
const candidate = normalizeRemoteDirectory(path);
|
|
11
|
+
return target === candidate || target.startsWith(`${candidate}/`);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function remoteDropOperation(source, destination) {
|
|
15
|
+
if (isSameRemoteTransferLocation(source, destination))
|
|
16
|
+
return 'none';
|
|
17
|
+
if (!canTransferIntoRemoteDirectory(source, source.paths, destination))
|
|
18
|
+
return 'invalid';
|
|
19
|
+
return source.endpointId === destination.endpointId ? 'move' : 'copy';
|
|
20
|
+
}
|
|
21
|
+
export function parseRemoteFilesDragPayload(raw) {
|
|
22
|
+
try {
|
|
23
|
+
const value = JSON.parse(raw);
|
|
24
|
+
return typeof value.paneId === 'string' && typeof value.endpointId === 'string' && typeof value.directory === 'string' && Array.isArray(value.paths) && value.paths.every(path => typeof path === 'string')
|
|
25
|
+
? { paneId: value.paneId, endpointId: value.endpointId, directory: value.directory, paths: value.paths }
|
|
26
|
+
: undefined;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function normalizeRemoteDirectory(value) {
|
|
33
|
+
return value.replaceAll('\\', '/').replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
|
|
34
|
+
}
|
|
35
|
+
//# 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,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;
|
|
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, 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
|
-
|
|
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: (
|
|
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,63 @@ function FileTransferPane({ pane, endpoints, destination, refreshRevision, onCha
|
|
|
178
210
|
const drop = (event) => {
|
|
179
211
|
event.preventDefault();
|
|
180
212
|
setDragOver(false);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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(
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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: () => { if (entry.kind === 'directory')
|
|
234
|
+
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)
|
|
235
|
+
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
236
|
}
|
|
220
|
-
function FileEntryRow({ entry, endpointId, selected, selectedPaths, style, onSelect, onOpen }) {
|
|
221
|
-
|
|
237
|
+
function FileEntryRow({ entry, paneId, endpointId, sourceDirectory, dragSource, dropTarget, selected, selectedPaths, style, onSelect, onOpen, onDelete, onDirectoryTarget, onDropIntoDirectory, onDragSourceChange }) {
|
|
238
|
+
const directory = entry.kind === 'directory' || entry.navigable === true;
|
|
239
|
+
const acceptsDrop = directory && (dragSource === undefined || canTransferIntoRemoteDirectory(dragSource, dragSource.paths, { endpointId, directory: entry.path }));
|
|
240
|
+
const dropIntoDirectory = (event) => {
|
|
241
|
+
if (!directory)
|
|
242
|
+
return;
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
event.stopPropagation();
|
|
245
|
+
onDirectoryTarget(undefined);
|
|
246
|
+
const payload = parseRemoteFilesDragPayload(event.dataTransfer.getData(REMOTE_FILES_DRAG_TYPE));
|
|
247
|
+
if (payload !== undefined && canTransferIntoRemoteDirectory(payload, payload.paths, { endpointId, directory: entry.path }))
|
|
248
|
+
onDropIntoDirectory(payload, entry.path);
|
|
249
|
+
};
|
|
250
|
+
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' || entry.kind === '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)
|
|
251
|
+
onOpen();
|
|
252
|
+
else
|
|
253
|
+
onSelect(event.ctrlKey || event.metaKey); }, onKeyDown: event => { if (event.target !== event.currentTarget)
|
|
254
|
+
return; if (event.key === 'Enter' && directory)
|
|
222
255
|
onOpen(); if (event.key === ' ') {
|
|
223
256
|
event.preventDefault();
|
|
224
257
|
onSelect(event.ctrlKey || event.metaKey);
|
|
225
|
-
} }, onDragStart: event => { const paths = selected ? selectedPaths : [entry.path]; event.dataTransfer.effectAllowed = '
|
|
258
|
+
} }, 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) {
|
|
259
|
+
event.preventDefault();
|
|
260
|
+
event.stopPropagation();
|
|
261
|
+
event.dataTransfer.dropEffect = dragSource?.endpointId === endpointId ? 'move' : 'copy';
|
|
262
|
+
onDirectoryTarget(entry.path);
|
|
263
|
+
} }, onDragLeave: event => { if (dropTarget && !event.currentTarget.contains(event.relatedTarget))
|
|
264
|
+
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
265
|
}
|
|
227
266
|
function TransferQueue({ jobs, endpoints, onCancel, onConflict }) {
|
|
228
267
|
const [open, setOpen] = useState(true);
|
|
229
268
|
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 => {
|
|
269
|
+
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
270
|
const progress = job.totalBytes > 0 ? Math.min(100, job.transferredBytes / job.totalBytes * 100) : job.state === 'completed' ? 100 : 0;
|
|
232
271
|
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
272
|
}) })] });
|
|
@@ -235,12 +274,25 @@ function TransferQueue({ jobs, endpoints, onCancel, onConflict }) {
|
|
|
235
274
|
function ConflictDialog({ job, onClose, onRetry }) {
|
|
236
275
|
const [policy, setPolicy] = useState('rename');
|
|
237
276
|
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 ? '正在重新加入队列…' : '继续传输' })] })] });
|
|
277
|
+
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
278
|
}
|
|
240
279
|
function FileAccessDialog({ endpoints, access, onClose }) {
|
|
241
280
|
const value = access.value;
|
|
281
|
+
const mounted = endpoints.filter(endpoint => value?.fileEndpointIds.includes(endpoint.id)).length;
|
|
242
282
|
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: "\
|
|
283
|
+
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 ? '保存中…' : '完成' })] })] });
|
|
284
|
+
}
|
|
285
|
+
function FileAccessEndpoint({ endpoint, enabled, onToggle }) {
|
|
286
|
+
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 ? '已授权' : '未授权' })] })] });
|
|
287
|
+
}
|
|
288
|
+
function FileAccessSkeleton() {
|
|
289
|
+
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)) });
|
|
290
|
+
}
|
|
291
|
+
function FilePermissionPicker({ value, onChange }) {
|
|
292
|
+
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)) });
|
|
293
|
+
}
|
|
294
|
+
function FileApprovalSwitch({ checked, onChange }) {
|
|
295
|
+
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
296
|
}
|
|
245
297
|
function restoreTabs() {
|
|
246
298
|
try {
|
|
@@ -276,6 +328,7 @@ function endpointName(endpoints, id) { return endpoints.find(endpoint => endpoin
|
|
|
276
328
|
function remoteLabel(paths) { const name = paths[0]?.replaceAll('\\', '/').split('/').filter(Boolean).at(-1) ?? '文件'; return paths.length > 1 ? `${name} 等 ${paths.length} 项` : name; }
|
|
277
329
|
function protocolShort(protocol) { return protocol === 'sftp' ? 'SFTP' : protocol === 'ftp' ? 'FTP' : protocol === 'ftps-explicit' ? 'FTPS' : 'FTPS-I'; }
|
|
278
330
|
function directoryKey(endpointId, value) { return JSON.stringify([endpointId, value.replaceAll('\\', '/').replace(/\/+$/, '') || '/']); }
|
|
331
|
+
function canDropIntoPane(source, pane) { return source === undefined || (source.paneId !== pane.id && !isSameRemoteTransferLocation(source, { endpointId: pane.endpointId, directory: pane.path })); }
|
|
279
332
|
function isFinished(state) { return state === 'completed' || state === 'failed' || state === 'cancelled'; }
|
|
280
333
|
function formatBytes(value) { if (value < 1024)
|
|
281
334
|
return `${value} B`; if (value < 1024 ** 2)
|