починили скачивание
This commit is contained in:
@@ -13,8 +13,9 @@
|
||||
<script lang="ts">
|
||||
// [SECTION: IMPORTS]
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { downloadFileUrl } from '../../services/storageService';
|
||||
import { downloadFile } from '../../services/storageService';
|
||||
import { t } from '../../lib/i18n';
|
||||
import { addToast } from '../../lib/toasts';
|
||||
// [/SECTION: IMPORTS]
|
||||
|
||||
let {
|
||||
@@ -68,6 +69,21 @@
|
||||
return new Date(dateStr).toLocaleString();
|
||||
}
|
||||
// [/DEF:formatDate:Function]
|
||||
|
||||
// [DEF:handleDownload:Function]
|
||||
/**
|
||||
* @purpose Downloads selected file through authenticated API request.
|
||||
* @pre file is a non-directory storage entry with category/path.
|
||||
* @post Browser download starts or user sees toast on failure.
|
||||
*/
|
||||
async function handleDownload(file) {
|
||||
try {
|
||||
await downloadFile(file.category, file.path, file.name);
|
||||
} catch (error) {
|
||||
addToast(error?.message || 'Download failed', 'error');
|
||||
}
|
||||
}
|
||||
// [/DEF:handleDownload:Function]
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
@@ -112,13 +128,13 @@
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{formatDate(file.created_at)}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
{#if !isDirectory(file)}
|
||||
<a
|
||||
href={downloadFileUrl(file.category, file.path)}
|
||||
download={file.name}
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => handleDownload(file)}
|
||||
class="text-indigo-600 hover:text-indigo-900 mr-4"
|
||||
>
|
||||
{$t.storage.table.download}
|
||||
</a>
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
on:click={() => dispatch('delete', { category: file.category, path: file.path, name: file.name })}
|
||||
@@ -141,4 +157,4 @@
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
|
||||
<!-- [/DEF:FileList:Component] -->
|
||||
<!-- [/DEF:FileList:Component] -->
|
||||
|
||||
@@ -28,6 +28,21 @@ function getStorageAuthHeaders() {
|
||||
}
|
||||
// [/DEF:getStorageAuthHeaders:Function]
|
||||
|
||||
// [DEF:encodeStoragePath:Function]
|
||||
/**
|
||||
* @purpose Encodes a storage-relative path preserving slash separators.
|
||||
* @param {string} path - Relative storage path.
|
||||
* @returns {string} Encoded path safe for URL segments.
|
||||
*/
|
||||
function encodeStoragePath(path) {
|
||||
return String(path || '')
|
||||
.split('/')
|
||||
.filter((part) => part.length > 0)
|
||||
.map((part) => encodeURIComponent(part))
|
||||
.join('/');
|
||||
}
|
||||
// [/DEF:encodeStoragePath:Function]
|
||||
|
||||
// [DEF:listFiles:Function]
|
||||
/**
|
||||
* @purpose Fetches the list of files for a given category and subpath.
|
||||
@@ -121,15 +136,50 @@ export async function deleteFile(category, path) {
|
||||
* or the backend must allow unauthenticated downloads for valid paths.
|
||||
*/
|
||||
export function downloadFileUrl(category, path) {
|
||||
return `${API_BASE}/download/${category}/${path}`;
|
||||
const safeCategory = encodeURIComponent(String(category || ''));
|
||||
const safePath = encodeStoragePath(path);
|
||||
return `${API_BASE}/download/${safeCategory}/${safePath}`;
|
||||
}
|
||||
// [/DEF:downloadFileUrl:Function]
|
||||
|
||||
// [DEF:downloadFile:Function]
|
||||
/**
|
||||
* @purpose Downloads a file using authenticated fetch and saves it in browser.
|
||||
* @param {string} category - File category.
|
||||
* @param {string} path - Relative path of the file.
|
||||
* @param {string} [filename] - Optional preferred filename.
|
||||
* @returns {Promise<void>}
|
||||
* @PRE category/path identify an existing file and user has READ permission.
|
||||
* @POST Browser download is triggered or an Error is thrown.
|
||||
*/
|
||||
export async function downloadFile(category, path, filename) {
|
||||
const response = await fetch(downloadFileUrl(category, path), {
|
||||
headers: getStorageAuthHeaders(),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.detail || `Failed to download file: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const objectUrl = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = objectUrl;
|
||||
link.download = filename || String(path || '').split('/').pop() || 'download';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(objectUrl);
|
||||
}
|
||||
// [/DEF:downloadFile:Function]
|
||||
|
||||
export default {
|
||||
listFiles,
|
||||
uploadFile,
|
||||
deleteFile,
|
||||
downloadFileUrl
|
||||
downloadFileUrl,
|
||||
downloadFile
|
||||
};
|
||||
|
||||
// [/DEF:storageService:Module]
|
||||
// [/DEF:storageService:Module]
|
||||
|
||||
Reference in New Issue
Block a user