feat: show confirm when deleting project
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { defaultPlant, lottaFaces, plant, simple } from '$lib/graph-templates';
|
||||
import type { Graph } from '$lib/types';
|
||||
import { InputSelect } from '@nodarium/ui';
|
||||
import { Button, ConfirmDialog, InputSelect, Spinner } from '@nodarium/ui';
|
||||
import type { ProjectManager } from './project-manager.svelte';
|
||||
|
||||
const { projectManager } = $props<{ projectManager: ProjectManager }>();
|
||||
@@ -31,16 +31,27 @@
|
||||
newProjectName = '';
|
||||
showNewProject = false;
|
||||
}
|
||||
|
||||
let pendingDeleteId = $state<number | null>(null);
|
||||
let confirmOpen = $state(false);
|
||||
|
||||
function requestDelete(id: number, e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
pendingDeleteId = id;
|
||||
confirmOpen = true;
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
if (pendingDeleteId !== null) {
|
||||
projectManager.handleDeleteProject(pendingDeleteId);
|
||||
pendingDeleteId = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="flex justify-between px-4 h-[70px] border-b-1 border-outline items-center bg-layer-2">
|
||||
<h3>Project</h3>
|
||||
<button
|
||||
class="px-3 py-1 bg-layer-1 rounded"
|
||||
onclick={() => (showNewProject = !showNewProject)}
|
||||
>
|
||||
New
|
||||
</button>
|
||||
<Button onclick={() => (showNewProject = !showNewProject)}>New</Button>
|
||||
</header>
|
||||
|
||||
{#if showNewProject}
|
||||
@@ -53,20 +64,11 @@
|
||||
onkeydown={(e) => e.key === 'Enter' && handleCreate()}
|
||||
/>
|
||||
<InputSelect options={templates.map(t => t.name)} bind:value={selectedTemplateIndex} />
|
||||
<button
|
||||
class="cursor-pointer self-end px-3 py-1 bg-selected rounded"
|
||||
onclick={() => handleCreate()}
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
<Button variant="primary" class="self-end" onclick={() => handleCreate()}>Create</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="text-white min-h-screen">
|
||||
{#if projectManager.loading}
|
||||
<p>Loading...</p>
|
||||
{/if}
|
||||
|
||||
<ul>
|
||||
{#each projectManager.projects as project (project.id)}
|
||||
<li>
|
||||
@@ -89,16 +91,35 @@
|
||||
<div class="flex justify-between items-center grow">
|
||||
<span>{project.meta?.title || 'Untitled'}</span>
|
||||
<button
|
||||
class="text-layer-1! bg-red-500 w-7 text-xl rounded-sm cursor-pointer opacity-20 hover:opacity-80"
|
||||
onclick={() => {
|
||||
projectManager.handleDeleteProject(project.id!);
|
||||
}}
|
||||
class="opacity-20 hover:opacity-70 transition-opacity cursor-pointer p-1 rounded text-red-400"
|
||||
onclick={(e) => requestDelete(project.id!, e)}
|
||||
aria-label="Delete project"
|
||||
>
|
||||
×
|
||||
<span class="i-[tabler--trash] w-4 h-4 block"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{:else}
|
||||
{#if projectManager.loading}
|
||||
<div class="flex items-center gap-2 p-4">
|
||||
<Spinner size={12} />
|
||||
<p>Loading</p>
|
||||
</div>
|
||||
{:else}
|
||||
<li class="px-4 py-8 text-center opacity-40 text-sm">
|
||||
No projects yet.<br />Press <b>New</b> to create one.
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:open={confirmOpen}
|
||||
title="Delete project?"
|
||||
message="This cannot be undone. The project and all its data will be permanently removed."
|
||||
confirmLabel="Delete"
|
||||
cancelLabel="Cancel"
|
||||
onconfirm={confirmDelete}
|
||||
/>
|
||||
|
||||
@@ -10,7 +10,9 @@ export class ProjectManager {
|
||||
'node.activeProjectId',
|
||||
undefined
|
||||
);
|
||||
public readonly loading = $derived(this.graph?.id !== this.activeProjectId.value);
|
||||
public readonly loading = $derived(
|
||||
this.projects.length && this.graph?.id !== this.activeProjectId.value
|
||||
);
|
||||
|
||||
constructor() {
|
||||
this.init();
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import Button from './Button.svelte';
|
||||
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
title?: string;
|
||||
message?: string;
|
||||
confirmLabel?: string;
|
||||
cancelLabel?: string;
|
||||
onconfirm?: () => void;
|
||||
oncancel?: () => void;
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
title = 'Are you sure?',
|
||||
message,
|
||||
confirmLabel = 'Confirm',
|
||||
cancelLabel = 'Cancel',
|
||||
onconfirm,
|
||||
oncancel,
|
||||
children
|
||||
}: Props = $props();
|
||||
|
||||
let dialogEl: HTMLDialogElement;
|
||||
|
||||
$effect(() => {
|
||||
if (!dialogEl) return;
|
||||
if (open) {
|
||||
dialogEl.showModal();
|
||||
} else {
|
||||
dialogEl.close();
|
||||
}
|
||||
});
|
||||
|
||||
function confirm() {
|
||||
open = false;
|
||||
onconfirm?.();
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
open = false;
|
||||
oncancel?.();
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') { e.preventDefault(); confirm(); }
|
||||
}
|
||||
|
||||
function handleCancel(e: Event) {
|
||||
e.preventDefault();
|
||||
cancel();
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
||||
<dialog
|
||||
bind:this={dialogEl}
|
||||
class="m-auto bg-layer-1 border border-outline rounded-md p-0 text-text max-w-md w-full backdrop:bg-black/50"
|
||||
oncancel={handleCancel}
|
||||
onkeydown={handleKeydown}
|
||||
onclick={(e) => { if (e.target === dialogEl) cancel(); }}
|
||||
>
|
||||
<div class="px-6 py-5 flex flex-col gap-3">
|
||||
<h3 class="m-0 text-sm font-semibold">{title}</h3>
|
||||
{#if message}
|
||||
<p class="m-0 text-xs opacity-75 leading-relaxed">{message}</p>
|
||||
{/if}
|
||||
{#if children}
|
||||
<div class="text-xs">
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex justify-end gap-2 mt-1">
|
||||
<Button onclick={cancel}>{cancelLabel}</Button>
|
||||
<Button variant="primary" onclick={confirm}>{confirmLabel}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
dialog {
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
dialog::backdrop {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user