Files
ds-galo/frontend/src/routes/(app)/albums/[id]/settings/+page.svelte
T
iu 713c1ca6d3 fix(ui): keep trash counter in sync with trash changes
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.
2026-10-12 12:58:18 +03:00

171 lines
4.8 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>