Files
beavergram/frontend/src/lib/components/media/AlbumTile.svelte
T

114 lines
2.7 KiB
Svelte

<script lang="ts">
import { visible } from "$lib/actions/visible";
import { fetchMedia } from "$lib/api/endpoints";
import { type InlineMedia, loadMediaItem, visualKind } from "$lib/api/media";
import type { MediaRef } from "$lib/api/types";
import Icon from "$lib/components/ui/Icon.svelte";
import Spinner from "$lib/components/ui/Spinner.svelte";
import { poster } from "$lib/media/poster";
import { toasts } from "$lib/stores/toasts.svelte";
interface Props {
chatId: number;
media: MediaRef;
onopen: () => void;
}
let { media, chatId, onopen }: Props = $props();
let loaded = $state(false);
let inline = $state<InlineMedia | null>(null);
let queuing = $state(false);
const ready = $derived(inline?.state === "ready" ? inline : null);
const isVideo = $derived(ready ? visualKind(ready.kind) === "video" : false);
async function start() {
inline = await loadMediaItem(media);
loaded = true;
}
async function queue() {
if (queuing) {
return;
}
queuing = true;
try {
await fetchMedia(chatId, media.message_id);
toasts.success("Download queued");
} catch {
toasts.error("Failed to queue download");
} finally {
queuing = false;
}
}
</script>
<div class="AlbumTile" use:visible={start}>
{#if ready && isVideo}
<button class="tile" onclick={onopen} type="button">
<video
muted
playsinline
preload="metadata"
src={ready.url}
use:poster
></video>
<span class="play"><Icon name="large-play" size="2rem" /></span>
</button>
{:else if ready}
<button class="tile" onclick={onopen} type="button">
<img alt="attachment" src={ready.url}>
</button>
{:else if inline?.state === "not-downloaded"}
<button class="tile placeholder" onclick={queue} type="button">
<Icon name={queuing ? "timer" : "download"} size="1.5rem" />
</button>
{:else if loaded}
<div class="tile placeholder"><Icon name="photo" size="1.5rem" /></div>
{:else}
<div class="tile placeholder"><Spinner /></div>
{/if}
</div>
<style lang="scss">
.AlbumTile {
aspect-ratio: 1;
min-width: 0;
}
.tile {
cursor: pointer;
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 0;
border: 0;
background-color: var(--color-default-shadow);
img,
video {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.placeholder {
color: var(--color-primary);
background-color: var(--color-primary-tint);
}
.play {
position: absolute;
color: var(--color-white);
filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.5));
}
</style>