feat(hosted-files): finalize download button UI with fixed spinner, divider, and alternating status view
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// *** Import Svelte specific
|
// *** Import Svelte specific
|
||||||
import * as Lucide from 'lucide-svelte';
|
import * as Lucide from 'lucide-svelte';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
// *** Import Aether specific variables and functions
|
// *** Import Aether specific variables and functions
|
||||||
import type { key_val } from '$lib/stores/ae_stores';
|
import type { key_val } from '$lib/stores/ae_stores';
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
download_status_msg?: string;
|
download_status_msg?: string;
|
||||||
variant?: 'tonal' | 'filled' | 'outline' | 'ghost';
|
variant?: 'tonal' | 'filled' | 'outline' | 'ghost';
|
||||||
color?: 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';
|
color?: 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'error' | 'surface';
|
||||||
|
show_divider?: boolean;
|
||||||
classes?: string;
|
classes?: string;
|
||||||
label?: import('svelte').Snippet;
|
label?: import('svelte').Snippet;
|
||||||
}
|
}
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
download_status_msg = $bindable('Not started'),
|
download_status_msg = $bindable('Not started'),
|
||||||
variant = 'tonal',
|
variant = 'tonal',
|
||||||
color = 'primary',
|
color = 'primary',
|
||||||
|
show_divider = true,
|
||||||
classes = '',
|
classes = '',
|
||||||
label
|
label
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
@@ -95,11 +98,14 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
let variant_classes = $derived.by(() => {
|
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;
|
const style = color_map[color]?.[variant] || color_map.primary.tonal;
|
||||||
return `${base} ${style} ${classes}`.trim();
|
return `${base} ${style} ${classes}`.trim();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let show_filename_view = $state(true);
|
||||||
|
let status_interval: any;
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (log_lvl) {
|
if (log_lvl) {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -118,6 +124,33 @@
|
|||||||
$ae_sess.api_download_kv[file_id].percent_completed;
|
$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>
|
</script>
|
||||||
|
|
||||||
{#if hosted_file_id && hosted_file_obj}
|
{#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}`}
|
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]}
|
{#await ae_promises[file_id]}
|
||||||
<Lucide.Loader2 class="animate-spin mr-2" size={18} />
|
<div class="flex items-center w-full min-h-[1.5rem]">
|
||||||
<span class="">
|
<div
|
||||||
Downloading
|
class="flex items-center pr-2 shrink-0 {show_divider ? 'border-r border-surface-500/30 mr-2' : ''}"
|
||||||
{#if $ae_sess.api_download_kv[file_id]}
|
>
|
||||||
{$ae_sess.api_download_kv[file_id]
|
<Lucide.Loader2 class="animate-spin" size={18} />
|
||||||
.percent_completed}%
|
</div>
|
||||||
{/if}
|
<div class="grow relative text-left h-full">
|
||||||
:
|
{#if show_filename_view}
|
||||||
</span>
|
<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}
|
{:then}
|
||||||
{#if label}
|
{#if label}
|
||||||
{@render label()}
|
{@render label()}
|
||||||
{:else}
|
{:else}
|
||||||
{@const IconComp = ae_util.file_extension_icon_lucide(hosted_file_obj?.extension)}
|
{@const IconComp = ae_util.file_extension_icon_lucide(hosted_file_obj?.extension)}
|
||||||
<IconComp size={18} class="mr-2" />
|
<div class="flex items-center w-full">
|
||||||
<span class="grow">
|
<div
|
||||||
{ae_util.shorten_filename({
|
class="flex items-center pr-2 shrink-0 {show_divider ? 'border-r border-surface-500/30 mr-2' : ''}"
|
||||||
filename: filename ?? hosted_file_obj?.filename,
|
>
|
||||||
max_length: max_length
|
<IconComp size={18} />
|
||||||
})}
|
</div>
|
||||||
</span>
|
<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}
|
{/if}
|
||||||
{/await}
|
{/await}
|
||||||
|
|
||||||
{#if download_complete === null}
|
{#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}
|
{: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}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button type="button" disabled class={classes ?? 'btn'} title="No file selected">
|
<button type="button" disabled class={variant_classes} title="No file selected">
|
||||||
<Lucide.FileX size={18} class="mr-2" />
|
<div class="flex items-center w-full">
|
||||||
<span class="grow"> No file info </span>
|
<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>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user