refac
This commit is contained in:
@@ -120,6 +120,49 @@ export const downloadFileBlob = async (
|
|||||||
return { blob, filename };
|
return { blob, filename };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const downloadDirectoryArchive = async (
|
||||||
|
baseUrl: string,
|
||||||
|
apiKey: string,
|
||||||
|
path: string
|
||||||
|
): Promise<{ blob: Blob; filename: string } | null> => {
|
||||||
|
const url = `${baseUrl.replace(/\/$/, '')}/files/download?path=${encodeURIComponent(path)}`;
|
||||||
|
const res = await fetch(url, {
|
||||||
|
headers: { Authorization: `Bearer ${apiKey}` }
|
||||||
|
}).catch(() => null);
|
||||||
|
|
||||||
|
if (!res || !res.ok) return null;
|
||||||
|
|
||||||
|
const disposition = res.headers.get('content-disposition') ?? '';
|
||||||
|
const match = disposition.match(/filename="?([^"]+)"?/);
|
||||||
|
const filename = match?.[1] ?? `${path.split('/').filter(Boolean).pop() ?? 'download'}.zip`;
|
||||||
|
const blob = await res.blob();
|
||||||
|
return { blob, filename };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const downloadMultipleAsZip = async (
|
||||||
|
baseUrl: string,
|
||||||
|
apiKey: string,
|
||||||
|
paths: string[]
|
||||||
|
): Promise<{ blob: Blob; filename: string } | null> => {
|
||||||
|
const url = `${baseUrl.replace(/\/$/, '')}/files/download`;
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ paths })
|
||||||
|
}).catch(() => null);
|
||||||
|
|
||||||
|
if (!res || !res.ok) return null;
|
||||||
|
|
||||||
|
const disposition = res.headers.get('content-disposition') ?? '';
|
||||||
|
const match = disposition.match(/filename="?([^"]+)"?/);
|
||||||
|
const filename = match?.[1] ?? 'download.zip';
|
||||||
|
const blob = await res.blob();
|
||||||
|
return { blob, filename };
|
||||||
|
};
|
||||||
|
|
||||||
export const uploadToTerminal = async (
|
export const uploadToTerminal = async (
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
listFiles,
|
listFiles,
|
||||||
readFile,
|
readFile,
|
||||||
downloadFileBlob,
|
downloadFileBlob,
|
||||||
|
downloadDirectoryArchive,
|
||||||
|
downloadMultipleAsZip,
|
||||||
uploadToTerminal,
|
uploadToTerminal,
|
||||||
createDirectory,
|
createDirectory,
|
||||||
deleteEntry,
|
deleteEntry,
|
||||||
@@ -403,7 +405,11 @@
|
|||||||
const terminal = selectedTerminal;
|
const terminal = selectedTerminal;
|
||||||
if (!terminal) return;
|
if (!terminal) return;
|
||||||
|
|
||||||
const result = await downloadFileBlob(terminal.url, terminal.key, path);
|
// Directories end with '/' — download as ZIP archive
|
||||||
|
const isDir = path.endsWith('/');
|
||||||
|
const result = isDir
|
||||||
|
? await downloadDirectoryArchive(terminal.url, terminal.key, path.replace(/\/$/, ''))
|
||||||
|
: await downloadFileBlob(terminal.url, terminal.key, path);
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
const url = URL.createObjectURL(result.blob);
|
const url = URL.createObjectURL(result.blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
@@ -644,10 +650,24 @@
|
|||||||
const terminal = selectedTerminal;
|
const terminal = selectedTerminal;
|
||||||
if (!terminal) return;
|
if (!terminal) return;
|
||||||
|
|
||||||
const filePaths = [...selectedEntries].filter((p) => !p.endsWith('/'));
|
const paths = [...selectedEntries].map((p) => p.replace(/\/$/, ''));
|
||||||
for (const p of filePaths) {
|
if (paths.length === 0) return;
|
||||||
await downloadFile(p);
|
|
||||||
|
// Single item — use the regular downloadFile path
|
||||||
|
if (paths.length === 1) {
|
||||||
|
await downloadFile([...selectedEntries][0]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Multiple items — bundle into a single ZIP
|
||||||
|
const result = await downloadMultipleAsZip(terminal.url, terminal.key, paths);
|
||||||
|
if (!result) return;
|
||||||
|
const url = URL.createObjectURL(result.blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = result.filename;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Escape to clear selection
|
// Escape to clear selection
|
||||||
|
|||||||
@@ -40,29 +40,27 @@
|
|||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{#if hasFiles}
|
<Tooltip content={$i18n.t('Download')}>
|
||||||
<Tooltip content={$i18n.t('Download')}>
|
<button
|
||||||
<button
|
class="p-1 rounded transition text-gray-400 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-600 dark:hover:text-gray-400"
|
||||||
class="p-1 rounded transition text-gray-400 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-600 dark:hover:text-gray-400"
|
on:click={onDownload}
|
||||||
on:click={onDownload}
|
aria-label={$i18n.t('Download')}
|
||||||
aria-label={$i18n.t('Download')}
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
class="size-3.5"
|
||||||
>
|
>
|
||||||
<svg
|
<path
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
d="M10.75 2.75a.75.75 0 0 0-1.5 0v8.614L6.295 8.235a.75.75 0 1 0-1.09 1.03l4.25 4.5a.75.75 0 0 0 1.09 0l4.25-4.5a.75.75 0 0 0-1.09-1.03l-2.955 3.129V2.75Z"
|
||||||
viewBox="0 0 20 20"
|
/>
|
||||||
fill="currentColor"
|
<path
|
||||||
class="size-3.5"
|
d="M3.5 12.75a.75.75 0 0 0-1.5 0v2.5A2.75 2.75 0 0 0 4.75 18h10.5A2.75 2.75 0 0 0 18 15.25v-2.5a.75.75 0 0 0-1.5 0v2.5c0 .69-.56 1.25-1.25 1.25H4.75c-.69 0-1.25-.56-1.25-1.25v-2.5Z"
|
||||||
>
|
/>
|
||||||
<path
|
</svg>
|
||||||
d="M10.75 2.75a.75.75 0 0 0-1.5 0v8.614L6.295 8.235a.75.75 0 1 0-1.09 1.03l4.25 4.5a.75.75 0 0 0 1.09 0l4.25-4.5a.75.75 0 0 0-1.09-1.03l-2.955 3.129V2.75Z"
|
</button>
|
||||||
/>
|
</Tooltip>
|
||||||
<path
|
|
||||||
d="M3.5 12.75a.75.75 0 0 0-1.5 0v2.5A2.75 2.75 0 0 0 4.75 18h10.5A2.75 2.75 0 0 0 18 15.25v-2.5a.75.75 0 0 0-1.5 0v2.5c0 .69-.56 1.25-1.25 1.25H4.75c-.69 0-1.25-.56-1.25-1.25v-2.5Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Tooltip content={$i18n.t('Delete')}>
|
<Tooltip content={$i18n.t('Delete')}>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -287,31 +287,32 @@
|
|||||||
<div
|
<div
|
||||||
class="min-w-[150px] rounded-2xl p-1 z-[9999999] bg-white dark:bg-gray-850 dark:text-white shadow-lg border border-gray-100 dark:border-gray-800"
|
class="min-w-[150px] rounded-2xl p-1 z-[9999999] bg-white dark:bg-gray-850 dark:text-white shadow-lg border border-gray-100 dark:border-gray-800"
|
||||||
>
|
>
|
||||||
{#if entry.type !== 'directory'}
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="select-none flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2 text-sm"
|
||||||
class="select-none flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2 text-sm"
|
on:click={(e) => {
|
||||||
on:click={(e) => {
|
e.stopPropagation();
|
||||||
e.stopPropagation();
|
const path = entry.type === 'directory'
|
||||||
onDownload(`${currentPath}${entry.name}`);
|
? `${currentPath}${entry.name}/`
|
||||||
}}
|
: `${currentPath}${entry.name}`;
|
||||||
|
onDownload(path);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
class="size-4"
|
||||||
>
|
>
|
||||||
<svg
|
<path
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
d="M10.75 2.75a.75.75 0 0 0-1.5 0v8.614L6.295 8.235a.75.75 0 1 0-1.09 1.03l4.25 4.5a.75.75 0 0 0 1.09 0l4.25-4.5a.75.75 0 0 0-1.09-1.03l-2.955 3.129V2.75Z"
|
||||||
viewBox="0 0 20 20"
|
/>
|
||||||
fill="currentColor"
|
<path
|
||||||
class="size-4"
|
d="M3.5 12.75a.75.75 0 0 0-1.5 0v2.5A2.75 2.75 0 0 0 4.75 18h10.5A2.75 2.75 0 0 0 18 15.25v-2.5a.75.75 0 0 0-1.5 0v2.5c0 .69-.56 1.25-1.25 1.25H4.75c-.69 0-1.25-.56-1.25-1.25v-2.5Z"
|
||||||
>
|
/>
|
||||||
<path
|
</svg>
|
||||||
d="M10.75 2.75a.75.75 0 0 0-1.5 0v8.614L6.295 8.235a.75.75 0 1 0-1.09 1.03l4.25 4.5a.75.75 0 0 0 1.09 0l4.25-4.5a.75.75 0 0 0-1.09-1.03l-2.955 3.129V2.75Z"
|
<div class="flex items-center">{$i18n.t('Download')}</div>
|
||||||
/>
|
</button>
|
||||||
<path
|
|
||||||
d="M3.5 12.75a.75.75 0 0 0-1.5 0v2.5A2.75 2.75 0 0 0 4.75 18h10.5A2.75 2.75 0 0 0 18 15.25v-2.5a.75.75 0 0 0-1.5 0v2.5c0 .69-.56 1.25-1.25 1.25H4.75c-.69 0-1.25-.56-1.25-1.25v-2.5Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div class="flex items-center">{$i18n.t('Download')}</div>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
Reference in New Issue
Block a user