Reload the rail trash count after every operation that changes the trash contents, not just navigation and empty-trash: moving photos to trash (grid, panel, viewer), restoring, permanent delete and deleting an album. Previously the counter stayed stale until the next route change.
171 lines
4.8 KiB
Svelte
171 lines
4.8 KiB
Svelte
<script lang="ts">
|
||
import { page } from '$app/stores';
|
||
import { goto } from '$app/navigation';
|
||
import { api } from '$lib/api';
|
||
import Icon from '$lib/components/Icon.svelte';
|
||
import { trash } from '$lib/stores/trash.svelte';
|
||
import type { Album, ShareLink } from '$lib/types';
|
||
|
||
const albumId = $derived($page.params.id);
|
||
|
||
let album = $state<Album | null>(null);
|
||
let shareLinks = $state<ShareLink[]>([]);
|
||
let title = $state('');
|
||
let description = $state('');
|
||
let expiresIn = $state('168h');
|
||
let error = $state('');
|
||
let saved = $state(false);
|
||
|
||
async function load() {
|
||
try {
|
||
const [albumData, linksData] = await Promise.all([
|
||
api<{ album: Album }>(`/albums/${albumId}`),
|
||
api<{ share_links: ShareLink[] }>(`/albums/${albumId}/share`)
|
||
]);
|
||
album = albumData.album;
|
||
title = album.title;
|
||
description = album.description;
|
||
shareLinks = linksData.share_links;
|
||
} catch (e) {
|
||
error = (e as Error).message;
|
||
}
|
||
}
|
||
|
||
$effect(() => {
|
||
load();
|
||
});
|
||
|
||
async function save(ev: Event) {
|
||
ev.preventDefault();
|
||
error = '';
|
||
saved = false;
|
||
try {
|
||
const data = await api<{ album: Album }>(`/albums/${albumId}`, {
|
||
method: 'PATCH',
|
||
body: { title, description }
|
||
});
|
||
album = data.album;
|
||
saved = true;
|
||
} catch (e) {
|
||
error = (e as Error).message;
|
||
}
|
||
}
|
||
|
||
async function createLink(ev: Event) {
|
||
ev.preventDefault();
|
||
try {
|
||
await api(`/albums/${albumId}/share`, { method: 'POST', body: { expires_in: expiresIn } });
|
||
await load();
|
||
} catch (e) {
|
||
error = (e as Error).message;
|
||
}
|
||
}
|
||
|
||
async function revoke(id: string) {
|
||
try {
|
||
await api(`/albums/${albumId}/share/${id}`, { method: 'DELETE' });
|
||
shareLinks = shareLinks.filter((l) => l.id !== id);
|
||
} catch (e) {
|
||
error = (e as Error).message;
|
||
}
|
||
}
|
||
|
||
async function removeAlbum() {
|
||
if (!confirm('Удалить альбом вместе с фото?')) return;
|
||
try {
|
||
await api(`/albums/${albumId}`, { method: 'DELETE' });
|
||
await trash.load();
|
||
goto('/');
|
||
} catch (e) {
|
||
error = (e as Error).message;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<div class="mb-6 flex items-center justify-between">
|
||
<h1 class="text-2xl font-bold">Настройки</h1>
|
||
<a href="/albums/{albumId}" class="text-primary inline-flex items-center gap-1 text-sm">
|
||
<Icon name="arrow_back" class="size-4" />
|
||
К альбому
|
||
</a>
|
||
</div>
|
||
|
||
{#if error}<p class="text-danger mb-4 text-sm">{error}</p>{/if}
|
||
|
||
<form class="border-border bg-bg-elevated mb-6 space-y-3 rounded-xl border p-4" onsubmit={save}>
|
||
<h2 class="font-semibold">Альбом</h2>
|
||
<label class="block">
|
||
<span class="text-text-muted mb-1 block text-sm">Название</span>
|
||
<input
|
||
bind:value={title}
|
||
class="border-border bg-bg text-text w-full rounded-lg border px-3 py-2"
|
||
/>
|
||
</label>
|
||
<label class="block">
|
||
<span class="text-text-muted mb-1 block text-sm">Описание</span>
|
||
<textarea
|
||
bind:value={description}
|
||
rows="3"
|
||
class="border-border bg-bg text-text w-full rounded-lg border px-3 py-2"
|
||
></textarea>
|
||
</label>
|
||
<button
|
||
class="bg-primary text-primary-fg inline-flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm"
|
||
type="submit"
|
||
>
|
||
<Icon name="save" class="size-4" />
|
||
Сохранить
|
||
</button>
|
||
{#if saved}<span class="text-success ml-3 text-sm">Сохранено</span>{/if}
|
||
</form>
|
||
|
||
<div class="border-border bg-bg-elevated mb-6 space-y-3 rounded-xl border p-4">
|
||
<h2 class="font-semibold">Ссылки для просмотра</h2>
|
||
<form class="flex items-end gap-2" onsubmit={createLink}>
|
||
<label class="text-sm">
|
||
<span class="text-text-muted mb-1 block">Срок действия</span>
|
||
<input
|
||
bind:value={expiresIn}
|
||
class="border-border bg-bg text-text w-32 rounded-lg border px-2 py-1.5"
|
||
/>
|
||
</label>
|
||
<button
|
||
class="bg-primary text-primary-fg inline-flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm"
|
||
type="submit"
|
||
>
|
||
<Icon name="add_link" class="size-4" />
|
||
Создать ссылку
|
||
</button>
|
||
</form>
|
||
<ul class="divide-border divide-y">
|
||
{#each shareLinks as link (link.id)}
|
||
<li class="flex items-center justify-between gap-2 py-2 text-sm">
|
||
<span class="text-text-muted truncate">
|
||
{link.revoked_at ? 'отозвана' : 'до ' + new Date(link.expires_at).toLocaleString()}
|
||
· <a class="text-primary" href="/share/{link.token}">/share/{link.token}</a>
|
||
</span>
|
||
{#if !link.revoked_at}
|
||
<button
|
||
type="button"
|
||
class="text-danger inline-flex shrink-0 items-center gap-1"
|
||
onclick={() => revoke(link.id)}
|
||
>
|
||
<Icon name="link_off" class="size-4" />
|
||
Отозвать
|
||
</button>
|
||
{/if}
|
||
</li>
|
||
{/each}
|
||
</ul>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
class="border-danger text-danger inline-flex items-center gap-1.5 rounded-lg border px-4 py-2 text-sm"
|
||
onclick={removeAlbum}
|
||
>
|
||
<Icon name="delete_forever" class="size-4" />
|
||
Удалить альбом
|
||
</button>
|
||
|