enh: ot move

This commit is contained in:
Timothy Jaeryang Baek
2026-03-02 12:03:23 -06:00
parent c701ebe07b
commit 0c42cd2c01
3 changed files with 97 additions and 10 deletions
+26
View File
@@ -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;
};
+19
View File
@@ -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)}
> >