feat(hosted-files): introduce standardized download component and V3 action support
- Created AE_Comp_Hosted_Files_Download_Button using Svelte 5 and Lucide icons. - Added file_extension_icon_lucide utility for direct Lucide icon mapping. - Refactored download logic to core__hosted_files.ts using V3 Action endpoint (/v3/action/hosted_file/.../download). - Integrated new component into Event File Object Table. - Cleaned up legacy window.postMessage calls in several file management views. NOTE: The new download component is currently in development and may not be fully functional.
This commit is contained in:
@@ -224,7 +224,6 @@
|
||||
log_lvl: 0
|
||||
});
|
||||
|
||||
// window.postMessage({ type: 'download_event_file', hosted_file_id: idaa_archive_content_obj.hosted_file_id, filename: idaa_archive_content_obj.filename, auto_download: true }, '*');
|
||||
}}
|
||||
class:hidden={!$ae_loc.edit_mode}
|
||||
class="novi_btn btn btn-sm lg:btn-md preset-tonal-primary hover:preset-filled-primary-500 min-w-72 lg:min-w-96"
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
log_lvl: log_lvl
|
||||
});
|
||||
|
||||
// window.postMessage({ type: 'download_event_file', hosted_file_id: idaa_archive_content_obj.hosted_file_id, filename: idaa_archive_content_obj.filename, auto_download: true }, '*');
|
||||
}}
|
||||
class="novi_btn btn btn-sm lg:btn-md preset-tonal-primary hover:preset-filled-primary-500 min-w-72 lg:min-w-96"
|
||||
title={`Download this file:\n${hosted_file_obj.filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}... Hosted ID: ${hosted_file_obj.hosted_file_id}`}
|
||||
|
||||
141
src/lib/ae_core/ae_comp__hosted_files_download_button.svelte
Normal file
141
src/lib/ae_core/ae_comp__hosted_files_download_button.svelte
Normal file
@@ -0,0 +1,141 @@
|
||||
<script lang="ts">
|
||||
// *** Import Svelte specific
|
||||
import * as Lucide from 'lucide-svelte';
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||
import { download_ae_obj_id__hosted_file } from '$lib/ae_core/core__hosted_files';
|
||||
import {
|
||||
ae_loc,
|
||||
ae_sess,
|
||||
ae_api
|
||||
} from '$lib/stores/ae_stores';
|
||||
|
||||
interface Props {
|
||||
log_lvl?: number;
|
||||
hosted_file_id: null | string;
|
||||
hosted_file_obj: null | key_val;
|
||||
filename?: null | string;
|
||||
max_length?: number;
|
||||
auto_download?: boolean;
|
||||
linked_to_type?: null | string;
|
||||
linked_to_id?: null | string;
|
||||
download_complete?: null | boolean;
|
||||
download_percent?: number;
|
||||
download_status_msg?: string;
|
||||
classes?: string;
|
||||
label?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
log_lvl = 0,
|
||||
hosted_file_id,
|
||||
hosted_file_obj,
|
||||
filename = $bindable(null),
|
||||
max_length = $bindable(30),
|
||||
auto_download = true,
|
||||
linked_to_type = $bindable(null),
|
||||
linked_to_id = $bindable(null),
|
||||
download_complete = $bindable(),
|
||||
download_percent = $bindable(),
|
||||
download_status_msg = $bindable('Not started'),
|
||||
classes = 'btn btn-sm lg:btn-md preset-tonal-primary border border-primary-500 hover:preset-filled-primary-500 min-w-48',
|
||||
label
|
||||
}: Props = $props();
|
||||
|
||||
$effect(() => {
|
||||
if (log_lvl) {
|
||||
console.log(
|
||||
`ae_comp__hosted_files_download_button.svelte hosted_file_id=${hosted_file_id}`,
|
||||
hosted_file_obj
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let ae_promises: key_val = $state({});
|
||||
|
||||
$effect(() => {
|
||||
const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id;
|
||||
if (file_id && $ae_sess?.api_download_kv[file_id]?.percent_completed) {
|
||||
download_percent =
|
||||
$ae_sess.api_download_kv[file_id].percent_completed;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if hosted_file_id && hosted_file_obj}
|
||||
{@const file_id = hosted_file_obj.id || hosted_file_obj.hosted_file_id || hosted_file_id}
|
||||
<button
|
||||
type="button"
|
||||
disabled={!$ae_loc.trusted_access}
|
||||
class={classes ?? 'btn'}
|
||||
onclick={() => {
|
||||
download_complete = false;
|
||||
download_status_msg = 'Downloading...';
|
||||
ae_promises[file_id] = download_ae_obj_id__hosted_file({
|
||||
api_cfg: $ae_api,
|
||||
hosted_file_id: file_id,
|
||||
return_file: true,
|
||||
filename: filename ?? hosted_file_obj.filename,
|
||||
auto_download: auto_download,
|
||||
log_lvl: log_lvl
|
||||
})
|
||||
.then((result) => {
|
||||
if (result === null) {
|
||||
console.log('File not found (404)');
|
||||
download_complete = null;
|
||||
download_status_msg = 'File not found';
|
||||
} else if (result === false) {
|
||||
console.log(
|
||||
'Possible error with API server (check network and server status)'
|
||||
);
|
||||
download_complete = false;
|
||||
download_status_msg = 'Failed to download';
|
||||
} else {
|
||||
// console.log('File found and downloaded');
|
||||
download_complete = true;
|
||||
download_status_msg = 'File downloaded';
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}}
|
||||
title={`Download this file:\n${filename ?? hosted_file_obj?.filename}\n[API] SHA256: ${hosted_file_obj?.hash_sha256?.slice(0, 10)}...\nHosted ID: ${file_id}\n Linked to: ${linked_to_type} ID: ${linked_to_id}`}
|
||||
>
|
||||
{#await ae_promises[file_id]}
|
||||
<Lucide.Loader2 class="animate-spin mr-2" size={18} />
|
||||
<span class="">
|
||||
Downloading
|
||||
{#if $ae_sess.api_download_kv[file_id]}
|
||||
{$ae_sess.api_download_kv[file_id]
|
||||
.percent_completed}%
|
||||
{/if}
|
||||
:
|
||||
</span>
|
||||
{:then}
|
||||
{#if label}
|
||||
{@render label()}
|
||||
{:else}
|
||||
{@const IconComp = ae_util.file_extension_icon_lucide(hosted_file_obj?.extension)}
|
||||
<IconComp size={18} class="mr-2" />
|
||||
<span class="grow">
|
||||
{ae_util.shorten_filename({
|
||||
filename: filename ?? hosted_file_obj?.filename,
|
||||
max_length: max_length
|
||||
})}
|
||||
</span>
|
||||
{/if}
|
||||
{/await}
|
||||
|
||||
{#if download_complete === null}
|
||||
<span class="text-red-800 dark:text-red-200 ml-2">File not found</span>
|
||||
{:else if download_complete === false}
|
||||
<span class="text-red-800 dark:text-red-200 ml-2">Failed to download!</span>
|
||||
{/if}
|
||||
</button>
|
||||
{:else}
|
||||
<button type="button" disabled class={classes ?? 'btn'} title="No file selected">
|
||||
<Lucide.FileX size={18} class="mr-2" />
|
||||
<span class="grow"> No file info </span>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -180,6 +180,53 @@ export async function delete_ae_obj_id__hosted_file({
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a hosted file (V3 Action)
|
||||
* Uses the new /v3/action/... standard.
|
||||
* Updated 2026-02-03
|
||||
*/
|
||||
export async function download_ae_obj_id__hosted_file({
|
||||
api_cfg,
|
||||
hosted_file_id,
|
||||
return_file = true,
|
||||
filename,
|
||||
auto_download = false,
|
||||
params = {},
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
hosted_file_id: string;
|
||||
return_file?: boolean;
|
||||
filename?: string;
|
||||
auto_download?: boolean;
|
||||
params?: key_val;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
if (log_lvl) {
|
||||
console.log(`*** download_ae_obj_id__hosted_file() *** id=${hosted_file_id}`);
|
||||
}
|
||||
|
||||
const task_id = hosted_file_id;
|
||||
const endpoint = `/v3/action/hosted_file/${hosted_file_id}/download`;
|
||||
|
||||
const query_params: key_val = { ...params };
|
||||
if (filename) {
|
||||
query_params['filename'] = filename;
|
||||
}
|
||||
query_params['return_file'] = 'true'; // V3 prefers string 'true' for bool flags in query
|
||||
|
||||
return await api.get_object({
|
||||
api_cfg,
|
||||
endpoint,
|
||||
params: query_params,
|
||||
return_blob: return_file,
|
||||
filename,
|
||||
auto_download,
|
||||
task_id,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
|
||||
export const properties_to_save = [
|
||||
'id',
|
||||
'hosted_file_id',
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from './ae_utils__files';
|
||||
import { get_obj_li_w_match_prop } from './ae_utils__get_obj_li_w_match_prop';
|
||||
import { file_extension_icon } from './ae_utils__file_extension_icon';
|
||||
import { file_extension_icon_lucide } from './ae_utils__file_extension_icon_lucide';
|
||||
import { process_permission_checks } from './ae_utils__perm_checks';
|
||||
import { iso_datetime_formatter } from './ae_utils__datetime_format';
|
||||
import { is_datetime_recent } from './ae_utils__is_datetime_recent';
|
||||
@@ -337,6 +338,7 @@ export const ae_util = {
|
||||
shorten_string: shorten_string,
|
||||
shorten_filename: shorten_filename,
|
||||
file_extension_icon: file_extension_icon,
|
||||
file_extension_icon_lucide: file_extension_icon_lucide,
|
||||
format_html: format_html,
|
||||
set_obj_prop_display_name: set_obj_prop_display_name,
|
||||
return_obj_type_path: return_obj_type_path,
|
||||
|
||||
66
src/lib/ae_utils/ae_utils__file_extension_icon_lucide.ts
Normal file
66
src/lib/ae_utils/ae_utils__file_extension_icon_lucide.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import * as Lucide from 'lucide-svelte';
|
||||
|
||||
/**
|
||||
* Returns a Lucide icon component based on the provided file extension.
|
||||
* @param extension The file extension (e.g., 'pdf', 'jpg').
|
||||
* @returns The Lucide icon component.
|
||||
*/
|
||||
export function file_extension_icon_lucide(extension: string | undefined | null): any {
|
||||
const ext = extension?.toLowerCase() || '';
|
||||
|
||||
const icon_map: Record<string, any> = {
|
||||
'pdf': Lucide.FileText,
|
||||
'doc': Lucide.FileText,
|
||||
'docx': Lucide.FileText,
|
||||
'txt': Lucide.FileText,
|
||||
'rtf': Lucide.FileText,
|
||||
|
||||
'xls': Lucide.FileSpreadsheet,
|
||||
'xlsx': Lucide.FileSpreadsheet,
|
||||
'csv': Lucide.FileSpreadsheet,
|
||||
|
||||
'png': Lucide.FileImage,
|
||||
'jpg': Lucide.FileImage,
|
||||
'jpeg': Lucide.FileImage,
|
||||
'gif': Lucide.FileImage,
|
||||
'webp': Lucide.FileImage,
|
||||
'bmp': Lucide.FileImage,
|
||||
'svg': Lucide.FileImage,
|
||||
|
||||
'mp3': Lucide.FileAudio,
|
||||
'wav': Lucide.FileAudio,
|
||||
'm4a': Lucide.FileAudio,
|
||||
'flac': Lucide.FileAudio,
|
||||
'aac': Lucide.FileAudio,
|
||||
'aif': Lucide.FileAudio,
|
||||
'aiff': Lucide.FileAudio,
|
||||
|
||||
'mp4': Lucide.FileVideo,
|
||||
'mkv': Lucide.FileVideo,
|
||||
'mov': Lucide.FileVideo,
|
||||
'avi': Lucide.FileVideo,
|
||||
'3gp': Lucide.FileVideo,
|
||||
|
||||
'ppt': Lucide.Presentation,
|
||||
'pptx': Lucide.Presentation,
|
||||
'key': Lucide.Presentation,
|
||||
'odp': Lucide.Presentation,
|
||||
|
||||
'zip': Lucide.FileArchive,
|
||||
'7z': Lucide.FileArchive,
|
||||
'rar': Lucide.FileArchive,
|
||||
'tar': Lucide.FileArchive,
|
||||
'gz': Lucide.FileArchive,
|
||||
|
||||
'json': Lucide.FileJson,
|
||||
|
||||
'html': Lucide.FileCode,
|
||||
'htm': Lucide.FileCode,
|
||||
'js': Lucide.FileCode,
|
||||
'ts': Lucide.FileCode,
|
||||
'css': Lucide.FileCode,
|
||||
'php': Lucide.FileCode
|
||||
};
|
||||
|
||||
return icon_map[ext] || Lucide.File;
|
||||
}
|
||||
@@ -183,7 +183,6 @@
|
||||
log_lvl: 0
|
||||
});
|
||||
|
||||
// window.postMessage({ type: 'download_event_file', event_file_id: event_file_obj.event_file_id, filename: event_file_obj.filename, auto_download: true }, '*');
|
||||
}}
|
||||
class="btn btn-sm lg:btn-md preset-tonal-primary hover:preset-filled-primary-500 min-w-72 lg:min-w-96"
|
||||
title={`Download this file:\n${event_file_obj.filename}\n[API] SHA256: ${event_file_obj.hash_sha256.slice(0, 10)}... Hosted ID: ${event_file_obj.hosted_file_id} Event File ID: ${event_file_obj.event_file_id}`}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
// import { events_loc, events_sess, events_slct, events_trigger, events_trig_kv } from '$lib/stores/ae_events_stores';
|
||||
// import { events_func } from '$lib/ae_events_functions';
|
||||
import MyClipboard from '$lib/app_components/e_app_clipboard.svelte';
|
||||
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
|
||||
|
||||
// export let display_mode: string = 'default'; // 'default', 'compact', 'minimal', 'launcher'
|
||||
|
||||
@@ -371,64 +372,27 @@
|
||||
{#each $lq__event_file_obj_li as event_file_obj}
|
||||
<tr class:dim={event_file_obj?.hide}>
|
||||
<td class="px-4 py-2">
|
||||
<button
|
||||
disabled={!allow_basic &&
|
||||
!allow_moderator &&
|
||||
!$ae_loc.trusted_access}
|
||||
onclick={() => {
|
||||
// ae_promises[event_file_obj?.event_file_id]
|
||||
ae_promises[event_file_obj?.event_file_id] =
|
||||
api.download_hosted_file({
|
||||
api_cfg: $ae_api,
|
||||
hosted_file_id: event_file_obj?.hosted_file_id,
|
||||
return_file: true,
|
||||
filename: event_file_obj?.filename,
|
||||
auto_download: true,
|
||||
log_lvl: 0
|
||||
});
|
||||
|
||||
// window.postMessage({ type: 'download_event_file', event_file_id: event_file_obj?.event_file_id, filename: event_file_obj?.filename, auto_download: true }, '*');
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500 min-w-72"
|
||||
title={`Download this file: ${event_file_obj?.filename} [API] -- SHA256 hash: ${event_file_obj?.hash_sha256.slice(0, 10)}...`}
|
||||
<AE_Comp_Hosted_Files_Download_Button
|
||||
hosted_file_id={event_file_obj?.hosted_file_id}
|
||||
hosted_file_obj={event_file_obj}
|
||||
classes="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500 min-w-72"
|
||||
>
|
||||
{#await ae_promises[event_file_obj?.event_file_id]}
|
||||
<span class="fas fa-spinner fa-spin mx-1"></span>
|
||||
<span class="">
|
||||
Downloading
|
||||
{#if $ae_sess.api_download_kv[event_file_obj?.hosted_file_id]}
|
||||
{$ae_sess.api_download_kv[
|
||||
event_file_obj?.hosted_file_id
|
||||
].percent_completed}%
|
||||
{/if}
|
||||
:
|
||||
{#snippet label()}
|
||||
<span class="grow">
|
||||
{ae_util.shorten_filename({
|
||||
filename: event_file_obj?.filename,
|
||||
max_length: 30
|
||||
})}
|
||||
</span>
|
||||
{:then}
|
||||
<!-- <span class="fas fa-download mx-1"></span> -->
|
||||
|
||||
<span
|
||||
class="fas fa-{ae_util.file_extension_icon(
|
||||
event_file_obj?.extension
|
||||
)}"
|
||||
></span>
|
||||
<!-- <span class="text-sm">
|
||||
Download:
|
||||
</span> -->
|
||||
{/await}
|
||||
|
||||
<span class="grow">
|
||||
{ae_util.shorten_filename({
|
||||
filename: event_file_obj?.filename,
|
||||
max_length: 30
|
||||
})}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="badge preset-filled-success-600-400 hover:preset-filled-success-700-300 text-xs"
|
||||
class:hidden={!event_file_obj?.file_purpose}
|
||||
>
|
||||
{event_file_obj?.file_purpose}
|
||||
</span>
|
||||
</button>
|
||||
class="badge preset-filled-success-600-400 hover:preset-filled-success-700-300 text-xs"
|
||||
class:hidden={!event_file_obj?.file_purpose}
|
||||
>
|
||||
{event_file_obj?.file_purpose}
|
||||
</span>
|
||||
{/snippet}
|
||||
</AE_Comp_Hosted_Files_Download_Button>
|
||||
<!-- {event_file_obj?.filename} -->
|
||||
</td>
|
||||
<td
|
||||
|
||||
@@ -204,7 +204,6 @@
|
||||
auto_download: true
|
||||
});
|
||||
|
||||
// window.postMessage({ type: 'download_event_file', hosted_file_id: idaa_archive_content_obj.hosted_file_id, filename: idaa_archive_content_obj.filename, auto_download: true }, '*');
|
||||
}}
|
||||
class="novi_btn btn btn-sm lg:btn-md preset-tonal-primary hover:preset-filled-primary-500 min-w-72 lg:min-w-96"
|
||||
title={`Download this file:\n${idaa_archive_content_obj.filename}\n[API] SHA256: ${idaa_archive_content_obj?.hash_sha256.slice(0, 10)}... Hosted ID: ${idaa_archive_content_obj.hosted_file_id} Archive Content ID: ${idaa_archive_content_obj.archive_content_id}`}
|
||||
|
||||
@@ -581,7 +581,6 @@ Copy and paste link: <a href="${link_base_url}?post_id=${$idaa_slct.post_id}">${
|
||||
log_lvl: 0
|
||||
});
|
||||
|
||||
// window.postMessage({ type: 'download_event_file', hosted_file_id: linked_obj.hosted_file_id_random, filename: linked_obj.filename, auto_download: true }, '*');
|
||||
}}
|
||||
class="novi_btn btn btn-sm lg:btn-md preset-tonal-tertiary border border-tertiary-500 hover:preset-filled-tertiary-500 min-w-48"
|
||||
title={`Download this file:\n${linked_obj.filename}\n[API] SHA256: ${linked_obj?.hash_sha256.slice(0, 10)}... Hosted ID: ${linked_obj.hosted_file_id_random} Archive Content ID: ${linked_obj.archive_content_id}`}
|
||||
|
||||
@@ -179,7 +179,6 @@
|
||||
log_lvl: 0
|
||||
});
|
||||
|
||||
// window.postMessage({ type: 'download_event_file', hosted_file_id: linked_obj.hosted_file_id_random, filename: linked_obj.filename, auto_download: true }, '*');
|
||||
}}
|
||||
class="novi_btn btn btn-sm lg:btn-md preset-tonal-tertiary border border-tertiary-500 hover:preset-filled-tertiary-500 min-w-48"
|
||||
title={`Download this file:\n${linked_obj.filename}\n[API] SHA256: ${linked_obj?.hash_sha256.slice(0, 10)}... Hosted ID: ${linked_obj.hosted_file_id_random} Archive Content ID: ${linked_obj.archive_content_id}`}
|
||||
|
||||
Reference in New Issue
Block a user