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;
};
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,
createDirectory,
deleteEntry,
moveEntry,
setCwd,
type FileEntry
} from '$lib/apis/terminal';
@@ -323,6 +324,23 @@
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 ────────────────────────────────────────────────────────
onMount(async () => {
const terminal = getTerminal();
@@ -717,6 +735,7 @@
onOpen={openEntry}
onDownload={downloadFile}
onDelete={requestDelete}
onMove={handleMove}
/>
{/each}
</ul>
@@ -18,24 +18,66 @@
export let onOpen: (entry: FileEntry) => void = () => {};
export let onDownload: (path: string) => void = () => {};
export let onDelete: (path: string, name: string) => void = () => {};
export let onMove: (source: string, destFolder: string) => void = () => {};
let dragOverFolder = false;
</script>
<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
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) => {
if (entry.type !== 'file') return;
const filePath = `${currentPath}${entry.name}`;
// Internal move data
e.dataTransfer?.setData(
'application/x-terminal-file',
JSON.stringify({
path: `${currentPath}${entry.name}`,
name: entry.name,
url: terminalUrl,
key: terminalKey
})
'application/x-terminal-file-move',
JSON.stringify({ path: filePath, name: entry.name })
);
// 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)}
>