enh: ot move
This commit is contained in:
@@ -193,3 +193,29 @@ export const setCwd = async (
|
|||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const moveEntry = async (
|
||||||
|
baseUrl: string,
|
||||||
|
apiKey: string,
|
||||||
|
source: string,
|
||||||
|
destination: string
|
||||||
|
): Promise<{ source: string; destination: string } | null> => {
|
||||||
|
const url = `${baseUrl.replace(/\/$/, '')}/files/move`;
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ source, destination })
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('open-terminal moveEntry error:', err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
uploadToTerminal,
|
uploadToTerminal,
|
||||||
createDirectory,
|
createDirectory,
|
||||||
deleteEntry,
|
deleteEntry,
|
||||||
|
moveEntry,
|
||||||
setCwd,
|
setCwd,
|
||||||
type FileEntry
|
type FileEntry
|
||||||
} from '$lib/apis/terminal';
|
} from '$lib/apis/terminal';
|
||||||
@@ -323,6 +324,23 @@
|
|||||||
showDeleteConfirm = true;
|
showDeleteConfirm = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Move (drag-and-drop) ────────────────────────────────────────────
|
||||||
|
const handleMove = async (source: string, destFolder: string) => {
|
||||||
|
const terminal = selectedTerminal;
|
||||||
|
if (!terminal) return;
|
||||||
|
|
||||||
|
const fileName = source.split('/').pop() ?? '';
|
||||||
|
const destination = `${destFolder}${fileName}`;
|
||||||
|
|
||||||
|
if (source === destination) return;
|
||||||
|
|
||||||
|
const result = await moveEntry(terminal.url, terminal.key, source, destination);
|
||||||
|
toast[result ? 'success' : 'error'](
|
||||||
|
$i18n.t(result ? 'Moved {{name}}' : 'Failed to move {{name}}', { name: fileName })
|
||||||
|
);
|
||||||
|
await loadDir(currentPath);
|
||||||
|
};
|
||||||
|
|
||||||
// ── Lifecycle ────────────────────────────────────────────────────────
|
// ── Lifecycle ────────────────────────────────────────────────────────
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const terminal = getTerminal();
|
const terminal = getTerminal();
|
||||||
@@ -717,6 +735,7 @@
|
|||||||
onOpen={openEntry}
|
onOpen={openEntry}
|
||||||
onDownload={downloadFile}
|
onDownload={downloadFile}
|
||||||
onDelete={requestDelete}
|
onDelete={requestDelete}
|
||||||
|
onMove={handleMove}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -18,24 +18,66 @@
|
|||||||
export let onOpen: (entry: FileEntry) => void = () => {};
|
export let onOpen: (entry: FileEntry) => void = () => {};
|
||||||
export let onDownload: (path: string) => void = () => {};
|
export let onDownload: (path: string) => void = () => {};
|
||||||
export let onDelete: (path: string, name: string) => void = () => {};
|
export let onDelete: (path: string, name: string) => void = () => {};
|
||||||
|
export let onMove: (source: string, destFolder: string) => void = () => {};
|
||||||
|
|
||||||
|
let dragOverFolder = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li class="group">
|
<li class="group">
|
||||||
<div class="w-full flex items-center hover:bg-gray-50 dark:hover:bg-gray-800 transition">
|
<div
|
||||||
|
class="w-full flex items-center hover:bg-gray-50 dark:hover:bg-gray-800 transition
|
||||||
|
{dragOverFolder ? 'bg-blue-50 dark:bg-blue-900/30 ring-1 ring-blue-400 dark:ring-blue-500 ring-inset' : ''}"
|
||||||
|
role={entry.type === 'directory' ? 'button' : undefined}
|
||||||
|
on:dragover={(e) => {
|
||||||
|
if (entry.type !== 'directory') return;
|
||||||
|
if (!e.dataTransfer?.types.includes('application/x-terminal-file-move')) return;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
dragOverFolder = true;
|
||||||
|
}}
|
||||||
|
on:dragleave={(e) => {
|
||||||
|
if (entry.type !== 'directory') return;
|
||||||
|
e.stopPropagation();
|
||||||
|
dragOverFolder = false;
|
||||||
|
}}
|
||||||
|
on:drop={(e) => {
|
||||||
|
if (entry.type !== 'directory') return;
|
||||||
|
const raw = e.dataTransfer?.getData('application/x-terminal-file-move');
|
||||||
|
if (!raw) return;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
dragOverFolder = false;
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(raw);
|
||||||
|
if (data.path) {
|
||||||
|
const destFolder = `${currentPath}${entry.name}/`;
|
||||||
|
onMove(data.path, destFolder);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
class="flex-1 flex items-center gap-2 px-3 py-1.5 text-left min-w-0"
|
class="flex-1 flex items-center gap-2 px-3 py-1.5 text-left min-w-0"
|
||||||
draggable={entry.type === 'file'}
|
draggable={true}
|
||||||
on:dragstart={(e) => {
|
on:dragstart={(e) => {
|
||||||
if (entry.type !== 'file') return;
|
const filePath = `${currentPath}${entry.name}`;
|
||||||
|
// Internal move data
|
||||||
e.dataTransfer?.setData(
|
e.dataTransfer?.setData(
|
||||||
'application/x-terminal-file',
|
'application/x-terminal-file-move',
|
||||||
JSON.stringify({
|
JSON.stringify({ path: filePath, name: entry.name })
|
||||||
path: `${currentPath}${entry.name}`,
|
|
||||||
name: entry.name,
|
|
||||||
url: terminalUrl,
|
|
||||||
key: terminalKey
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
// Keep existing chat-attachment drag for files
|
||||||
|
if (entry.type === 'file') {
|
||||||
|
e.dataTransfer?.setData(
|
||||||
|
'application/x-terminal-file',
|
||||||
|
JSON.stringify({
|
||||||
|
path: filePath,
|
||||||
|
name: entry.name,
|
||||||
|
url: terminalUrl,
|
||||||
|
key: terminalKey
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
on:click={() => onOpen(entry)}
|
on:click={() => onOpen(entry)}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user