feat(frontend): replace JS confirm() with native dialog component
- ConfirmDialog: centered <dialog> with backdrop blur, cancel + confirm (danger variant) - tags/[id]: delete tag uses ConfirmDialog - categories/[id]: delete category uses ConfirmDialog - files: bulk delete calls POST /files/bulk/delete, removes files from list, text updated to "Move to trash" (soft delete) - mock: add POST /files/bulk/delete handler Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import { api, ApiError } from '$lib/api/client';
|
||||
import type { Category, Tag, TagOffsetPage } from '$lib/api/types';
|
||||
import TagBadge from '$lib/components/tag/TagBadge.svelte';
|
||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
|
||||
let categoryId = $derived(page.params.id);
|
||||
|
||||
@@ -23,6 +24,7 @@
|
||||
let loadError = $state('');
|
||||
let saveError = $state('');
|
||||
let loaded = $state(false);
|
||||
let confirmDelete = $state(false);
|
||||
|
||||
const TAGS_LIMIT = 100;
|
||||
|
||||
@@ -87,9 +89,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteCategory() {
|
||||
if (deleting) return;
|
||||
if (!confirm(`Delete category "${name}"? Tags in this category will be unassigned.`)) return;
|
||||
async function doDeleteCategory() {
|
||||
confirmDelete = false;
|
||||
deleting = true;
|
||||
try {
|
||||
await api.delete(`/categories/${categoryId}`);
|
||||
@@ -171,7 +172,7 @@
|
||||
<button type="submit" class="submit-btn" disabled={!name.trim() || saving}>
|
||||
{saving ? 'Saving…' : 'Save changes'}
|
||||
</button>
|
||||
<button type="button" class="delete-btn" onclick={deleteCategory} disabled={deleting}>
|
||||
<button type="button" class="delete-btn" onclick={() => (confirmDelete = true)} disabled={deleting}>
|
||||
{deleting ? 'Deleting…' : 'Delete'}
|
||||
</button>
|
||||
</div>
|
||||
@@ -212,6 +213,16 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{#if confirmDelete}
|
||||
<ConfirmDialog
|
||||
message={`Delete category "${name}"? Tags in this category will be unassigned.`}
|
||||
confirmLabel="Delete"
|
||||
danger
|
||||
onConfirm={doDeleteCategory}
|
||||
onCancel={() => (confirmDelete = false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.page { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
||||
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
import InfiniteScroll from '$lib/components/common/InfiniteScroll.svelte';
|
||||
import { fileSorting, type FileSortField } from '$lib/stores/sorting';
|
||||
import { selectionStore, selectionActive } from '$lib/stores/selection';
|
||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
import { parseDslFilter } from '$lib/utils/dsl';
|
||||
import type { File, FileCursorPage } from '$lib/api/types';
|
||||
|
||||
let uploader = $state<{ open: () => void } | undefined>();
|
||||
let confirmDeleteFiles = $state(false);
|
||||
|
||||
function handleUploaded(file: File) {
|
||||
files = [file, ...files];
|
||||
@@ -229,12 +231,27 @@
|
||||
<SelectionBar
|
||||
onEditTags={() => {/* TODO */}}
|
||||
onAddToPool={() => {/* TODO */}}
|
||||
onDelete={() => {
|
||||
if (confirm(`Delete ${$selectionStore.ids.size} file(s)?`)) {
|
||||
// TODO: call delete API
|
||||
selectionStore.exit();
|
||||
onDelete={() => (confirmDeleteFiles = true)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if confirmDeleteFiles}
|
||||
<ConfirmDialog
|
||||
message={`Move ${$selectionStore.ids.size} file(s) to trash?`}
|
||||
confirmLabel="Move to trash"
|
||||
danger
|
||||
onConfirm={async () => {
|
||||
const ids = [...$selectionStore.ids];
|
||||
confirmDeleteFiles = false;
|
||||
selectionStore.exit();
|
||||
try {
|
||||
await api.post('/files/bulk/delete', { file_ids: ids });
|
||||
files = files.filter((f) => !ids.includes(f.id ?? ''));
|
||||
} catch {
|
||||
// silently ignore — file list already updated optimistically
|
||||
}
|
||||
}}
|
||||
onCancel={() => (confirmDeleteFiles = false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { api, ApiError } from '$lib/api/client';
|
||||
import type { Category, CategoryOffsetPage, Tag, TagRule } from '$lib/api/types';
|
||||
import TagRuleEditor from '$lib/components/tag/TagRuleEditor.svelte';
|
||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
|
||||
let tagId = $derived(page.params.id);
|
||||
|
||||
@@ -21,6 +22,7 @@
|
||||
let deleting = $state(false);
|
||||
let loadError = $state('');
|
||||
let saveError = $state('');
|
||||
let confirmDelete = $state(false);
|
||||
|
||||
let loaded = $state(false);
|
||||
|
||||
@@ -68,9 +70,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTag() {
|
||||
if (deleting) return;
|
||||
if (!confirm(`Delete tag "${name}"? This cannot be undone.`)) return;
|
||||
async function doDeleteTag() {
|
||||
confirmDelete = false;
|
||||
deleting = true;
|
||||
try {
|
||||
await api.delete(`/tags/${tagId}`);
|
||||
@@ -162,7 +163,7 @@
|
||||
<button type="submit" class="submit-btn" disabled={!name.trim() || saving}>
|
||||
{saving ? 'Saving…' : 'Save changes'}
|
||||
</button>
|
||||
<button type="button" class="delete-btn" onclick={deleteTag} disabled={deleting}>
|
||||
<button type="button" class="delete-btn" onclick={() => (confirmDelete = true)} disabled={deleting}>
|
||||
{deleting ? 'Deleting…' : 'Delete'}
|
||||
</button>
|
||||
</div>
|
||||
@@ -178,6 +179,16 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{#if confirmDelete}
|
||||
<ConfirmDialog
|
||||
message={`Delete tag "${name}"? This cannot be undone.`}
|
||||
confirmLabel="Delete"
|
||||
danger
|
||||
onConfirm={doDeleteTag}
|
||||
onCancel={() => (confirmDelete = false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.page { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user