feat: photo rename and viewer delete with confirmation
Backend: - add PATCH /api/photos/:id to rename original_filename (uploader/owner/admin) - emit photo.updated SSE event Frontend: - inline filename editor in the right panel - viewer: back button (arrow_back) on the left, delete on the right - delete confirmed via a modal dialog; filmstrip updates after deletion
This commit is contained in:
@@ -90,6 +90,11 @@
|
||||
photos = photos.filter((p) => p.id !== data.id);
|
||||
break;
|
||||
}
|
||||
case 'photo.updated': {
|
||||
const photo = data as unknown as Photo;
|
||||
photos = photos.map((p) => (p.id === photo.id ? photo : p));
|
||||
break;
|
||||
}
|
||||
case 'like.added': {
|
||||
photos = photos.map((p) =>
|
||||
p.id === data.photo_id
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { api } from '$lib/api';
|
||||
import Viewer from '$lib/components/Viewer.svelte';
|
||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||
import { albums } from '$lib/stores/albums.svelte';
|
||||
import { auth } from '$lib/stores/auth.svelte';
|
||||
import type { Album, Photo } from '$lib/types';
|
||||
|
||||
const albumId = $derived($page.params.id ?? '');
|
||||
@@ -14,9 +16,15 @@
|
||||
let loading = $state(true);
|
||||
let error = $state('');
|
||||
let saved = $state(false);
|
||||
let pendingDelete = $state<Photo | null>(null);
|
||||
let deleting = $state(false);
|
||||
|
||||
const canEdit = $derived(role === 'owner' || role === 'admin');
|
||||
const index = $derived(photos.findIndex((p) => p.id === photoId));
|
||||
const currentPhoto = $derived(index >= 0 ? photos[index] : null);
|
||||
const canDelete = $derived(
|
||||
canEdit || (!!currentPhoto && currentPhoto.uploader_id === auth.user?.id)
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
const id = albumId;
|
||||
@@ -53,6 +61,36 @@
|
||||
error = (e as Error).message;
|
||||
}
|
||||
}
|
||||
|
||||
async function removePhoto(photo: Photo) {
|
||||
pendingDelete = photo;
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
const photo = pendingDelete;
|
||||
if (!photo) return;
|
||||
deleting = true;
|
||||
error = '';
|
||||
try {
|
||||
await api(`/photos/${photo.id}`, { method: 'DELETE' });
|
||||
pendingDelete = null;
|
||||
const remaining = photos.filter((p) => p.id !== photo.id);
|
||||
if (remaining.length > 0) {
|
||||
const next = remaining[Math.min(Math.max(index, 0), remaining.length - 1)];
|
||||
await goto(`/albums/${albumId}/photos/${next.id}`, {
|
||||
replaceState: true,
|
||||
noScroll: true
|
||||
});
|
||||
} else {
|
||||
await goto(`/albums/${albumId}`);
|
||||
}
|
||||
photos = remaining;
|
||||
} catch (e) {
|
||||
error = (e as Error).message;
|
||||
} finally {
|
||||
deleting = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
@@ -74,10 +112,22 @@
|
||||
{photos}
|
||||
{index}
|
||||
{canEdit}
|
||||
{canDelete}
|
||||
onClose={close}
|
||||
onIndexChange={change}
|
||||
onMakeCover={makeCover}
|
||||
onDelete={removePhoto}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if pendingDelete}
|
||||
<ConfirmDialog
|
||||
title="Удалить фото?"
|
||||
message={pendingDelete.original_filename}
|
||||
busy={deleting}
|
||||
onConfirm={confirmDelete}
|
||||
onClose={() => (pendingDelete = null)}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user