feat(hosted-files): finalize download button UI with fixed spinner, divider, and alternating status view

This commit is contained in:
Scott Idem
2026-02-03 14:35:31 -05:00
parent 76b9e97894
commit 671247e783

View File

@@ -1,6 +1,7 @@
<script lang="ts">
// *** Import Svelte specific
import * as Lucide from 'lucide-svelte';
import { fade } from 'svelte/transition';
// *** Import Aether specific variables and functions
import type { key_val } from '$lib/stores/ae_stores';
@@ -26,6 +27,7 @@
download_status_msg?: string;
variant?: 'tonal' | 'filled' | 'outline' | 'ghost';
color?: 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';
show_divider?: boolean;
classes?: string;
label?: import('svelte').Snippet;
}
@@ -44,6 +46,7 @@
download_status_msg = $bindable('Not started'),
variant = 'tonal',
color = 'primary',
show_divider = true,
classes = '',
label
}: Props = $props();
@@ -95,11 +98,14 @@
};
let variant_classes = $derived.by(() => {
const base = 'btn btn-sm lg:btn-md min-w-48 transition-all';
const base = 'btn btn-sm lg:btn-md min-w-48 transition-all overflow-hidden';
const style = color_map[color]?.[variant] || color_map.primary.tonal;
return `${base} ${style} ${classes}`.trim();
});
let show_filename_view = $state(true);
let status_interval: any;
$effect(() => {
if (log_lvl) {
console.log(
@@ -118,6 +124,33 @@
$ae_sess.api_download_kv[file_id].percent_completed;
}
});
// Reactive timer to alternate views during active download
$effect(() => {
const file_id = hosted_file_obj?.id || hosted_file_obj?.hosted_file_id || hosted_file_id;
const is_actively_downloading = ae_promises[file_id] && download_complete === undefined;
if (is_actively_downloading) {
if (!status_interval) {
status_interval = setInterval(() => {
show_filename_view = !show_filename_view;
}, 3000);
}
} else {
if (status_interval) {
clearInterval(status_interval);
status_interval = null;
}
show_filename_view = true; // Default view when not downloading
}
return () => {
if (status_interval) {
clearInterval(status_interval);
status_interval = null;
}
};
});
</script>
{#if hosted_file_id && hosted_file_obj}
@@ -159,39 +192,77 @@
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>
<div class="flex items-center w-full min-h-[1.5rem]">
<div
class="flex items-center pr-2 shrink-0 {show_divider ? 'border-r border-surface-500/30 mr-2' : ''}"
>
<Lucide.Loader2 class="animate-spin" size={18} />
</div>
<div class="grow relative text-left h-full">
{#if show_filename_view}
<div in:fade={{ duration: 250 }} out:fade={{ duration: 250 }} class="flex items-center h-full">
<span class="truncate">
{ae_util.shorten_filename({
filename: filename ?? hosted_file_obj?.filename,
max_length: max_length
})}
</span>
</div>
{:else}
<div in:fade={{ duration: 250 }} out:fade={{ duration: 250 }} class="absolute inset-0 flex items-center h-full">
<span class="font-bold whitespace-nowrap">
Downloading:
{#if $ae_sess.api_download_kv[file_id]}
{$ae_sess.api_download_kv[file_id].percent_completed}%
{:else}
...
{/if}
</span>
</div>
{/if}
</div>
</div>
{: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>
<div class="flex items-center w-full">
<div
class="flex items-center pr-2 shrink-0 {show_divider ? 'border-r border-surface-500/30 mr-2' : ''}"
>
<IconComp size={18} />
</div>
<span class="grow truncate text-left">
{ae_util.shorten_filename({
filename: filename ?? hosted_file_obj?.filename,
max_length: max_length
})}
</span>
{#if hosted_file_obj?.file_purpose || hosted_file_obj?.group}
<span class="badge preset-tonal-success ml-2 text-[10px] uppercase font-bold shrink-0">
{hosted_file_obj.file_purpose || hosted_file_obj.group}
</span>
{/if}
</div>
{/if}
{/await}
{#if download_complete === null}
<span class="text-red-800 dark:text-red-200 ml-2">File not found</span>
<span class="text-red-800 dark:text-red-200 ml-2 whitespace-nowrap">File not found</span>
{:else if download_complete === false}
<span class="text-red-800 dark:text-red-200 ml-2">Failed to download!</span>
<span class="text-red-800 dark:text-red-200 ml-2 whitespace-nowrap text-xs">Failed!</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 type="button" disabled class={variant_classes} title="No file selected">
<div class="flex items-center w-full">
<div
class="flex items-center pr-2 shrink-0 {show_divider ? 'border-r border-surface-500/30 mr-2' : ''}"
>
<Lucide.FileX size={18} />
</div>
<span class="grow text-left"> No file info </span>
</div>
</button>
{/if}