139 lines
3.6 KiB
Svelte
139 lines
3.6 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { get } from 'svelte/store';
|
|
import ndk from '$lib/stores/ndk';
|
|
import { parseDriveEvent, getBlobUrl, fileName, type BlossomFile } from '$lib/blossom';
|
|
import type { NDKFilter } from '@nostr-dev-kit/ndk';
|
|
import PdfCover from './PdfCover.svelte';
|
|
|
|
export let pubkey: string;
|
|
export let driveId: string;
|
|
|
|
let driveName = '';
|
|
let description = '';
|
|
let files: BlossomFile[] = [];
|
|
let servers: string[] = [];
|
|
let loading = true;
|
|
let error = '';
|
|
let activePdf: string | null = null;
|
|
|
|
onMount(() => {
|
|
const $ndk = get(ndk);
|
|
|
|
const filter: NDKFilter = {
|
|
kinds: [30563 as number],
|
|
authors: [pubkey],
|
|
'#d': [driveId]
|
|
};
|
|
|
|
$ndk
|
|
.fetchEvent(filter)
|
|
.then((event) => {
|
|
if (!event) {
|
|
error = 'Drive not found.';
|
|
return;
|
|
}
|
|
|
|
const drive = parseDriveEvent(event);
|
|
driveName = drive.name;
|
|
description = drive.description;
|
|
servers = drive.servers;
|
|
files = drive.files.filter(
|
|
(f) => f.mimeType === 'application/pdf' || f.path.endsWith('.pdf')
|
|
);
|
|
})
|
|
.catch((e: unknown) => {
|
|
error = e instanceof Error ? e.message : String(e);
|
|
})
|
|
.finally(() => {
|
|
loading = false;
|
|
});
|
|
});
|
|
|
|
function openPdf(file: BlossomFile) {
|
|
activePdf = getBlobUrl(file.sha256, servers, '.pdf');
|
|
}
|
|
|
|
function closePdf() {
|
|
activePdf = null;
|
|
}
|
|
</script>
|
|
|
|
{#if loading}
|
|
<p class="animate-pulse text-sm text-gray-500">Loading drive…</p>
|
|
{:else if error}
|
|
<p class="text-sm font-medium text-red-600">Error: {error}</p>
|
|
{:else}
|
|
<div class="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4">
|
|
{#each files as file (file.sha256)}
|
|
{@const blobUrl = getBlobUrl(file.sha256, servers, '.pdf')}
|
|
{@const name = fileName(file.path)}
|
|
|
|
<div class="group flex flex-col gap-2">
|
|
<!-- Cover — clicking opens the viewer -->
|
|
<button
|
|
on:click={() => openPdf(file)}
|
|
class="block w-full overflow-hidden rounded-lg shadow-md ring-2 ring-transparent transition-all duration-200 group-hover:-translate-y-1 group-hover:shadow-xl group-hover:ring-indigo-400"
|
|
aria-label="Read {name}"
|
|
>
|
|
<PdfCover url={blobUrl} alt={name} />
|
|
</button>
|
|
|
|
<!-- Title -->
|
|
<p
|
|
class="truncate px-1 text-center text-xs leading-tight font-medium text-gray-700 transition-colors group-hover:text-indigo-600"
|
|
title={name}
|
|
>
|
|
{name.replace(/\.pdf$/i, '')}
|
|
</p>
|
|
|
|
<!-- Download link -->
|
|
<a
|
|
href={blobUrl}
|
|
download={name}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="text-center text-xs text-gray-400 transition-colors hover:text-indigo-500"
|
|
>
|
|
⬇ Download
|
|
</a>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- PDF Viewer Modal -->
|
|
{#if activePdf}
|
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
<div
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
|
on:click={closePdf}
|
|
>
|
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
<div
|
|
class="relative flex w-[90vw] max-w-4xl flex-col rounded-xl bg-white p-4 shadow-2xl"
|
|
on:click|stopPropagation
|
|
>
|
|
<!-- Modal header -->
|
|
<div class="mb-3 flex items-center justify-between">
|
|
<span class="text-sm font-medium text-gray-700">Fanzine Viewer</span>
|
|
<button
|
|
on:click={closePdf}
|
|
class="text-xl leading-none text-gray-400 transition-colors hover:text-gray-700"
|
|
aria-label="Close"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
|
|
<!-- PDF iframe -->
|
|
<iframe
|
|
src={activePdf}
|
|
title="Fanzine PDF"
|
|
class="w-full rounded border border-gray-200"
|
|
style="height: 80vh;"
|
|
></iframe>
|
|
</div>
|
|
</div>
|
|
{/if}
|