From 64ca37eab415110ae4c22cb34721f2e0db772908 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 6 Jan 2025 11:42:14 +0700 Subject: [PATCH] confirm deleting fwlite entries (#1337) * enable detailed errors in web circuit * disable prerendering * allow accessing the project testing page to streamline ui dev * create a delete dialog in the project root to reuse across components * confirm deletes * fix the height of delete dialog so it's not full screen, also change the confirm button to show a trash can and style it appropriately * allow opening dialog again after dismissing by clicking outside * simplify delete dialog closing --- .../FwLiteShared/Pages/TestingProject.razor | 3 ++ backend/FwLite/FwLiteWeb/Components/App.razor | 4 +- backend/FwLite/FwLiteWeb/FwLiteWebServer.cs | 2 +- frontend/viewer/src/ProjectView.svelte | 7 +++ .../src/lib/entry-editor/DeleteDialog.svelte | 43 +++++++++++++++++++ .../src/lib/entry-editor/dialog-service.ts | 28 ++++++++++++ .../object-editors/EntryEditor.svelte | 12 ++++-- 7 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 backend/FwLite/FwLiteShared/Pages/TestingProject.razor create mode 100644 frontend/viewer/src/lib/entry-editor/DeleteDialog.svelte create mode 100644 frontend/viewer/src/lib/entry-editor/dialog-service.ts diff --git a/backend/FwLite/FwLiteShared/Pages/TestingProject.razor b/backend/FwLite/FwLiteShared/Pages/TestingProject.razor new file mode 100644 index 000000000..8c182a2e0 --- /dev/null +++ b/backend/FwLite/FwLiteShared/Pages/TestingProject.razor @@ -0,0 +1,3 @@ +@page "/testing/project-view" +@using FwLiteShared.Layout +@layout SvelteLayout; diff --git a/backend/FwLite/FwLiteWeb/Components/App.razor b/backend/FwLite/FwLiteWeb/Components/App.razor index 61ad16ed7..d99a9ce51 100644 --- a/backend/FwLite/FwLiteWeb/Components/App.razor +++ b/backend/FwLite/FwLiteWeb/Components/App.razor @@ -6,7 +6,7 @@ - + diff --git a/backend/FwLite/FwLiteWeb/FwLiteWebServer.cs b/backend/FwLite/FwLiteWeb/FwLiteWebServer.cs index 00dda4cb5..34ae69475 100644 --- a/backend/FwLite/FwLiteWeb/FwLiteWebServer.cs +++ b/backend/FwLite/FwLiteWeb/FwLiteWebServer.cs @@ -38,7 +38,7 @@ public static WebApplication SetupAppServer(WebApplicationOptions options, Actio config.LexboxServers = [new(new("https://staging.languagedepot.org"), "Lexbox Staging")]); builder.Services.Configure(c => c.ClientId = "becf2856-0690-434b-b192-a4032b72067f"); builder.Logging.AddDebug(); - builder.Services.AddRazorComponents().AddInteractiveServerComponents(); + builder.Services.AddRazorComponents().AddInteractiveServerComponents(circuitOptions => circuitOptions.DetailedErrors = true); if (builder.Configuration.GetValue("FwLiteWeb:LogFileName") is { Length: > 0 } logFileName) { builder.Logging.AddFile(logFileName); diff --git a/frontend/viewer/src/ProjectView.svelte b/frontend/viewer/src/ProjectView.svelte index 6477cc073..d3d5cbab4 100644 --- a/frontend/viewer/src/ProjectView.svelte +++ b/frontend/viewer/src/ProjectView.svelte @@ -39,6 +39,8 @@ import {initProjectCommands, type NewEntryDialogOptions} from './lib/commands'; import throttle from 'just-throttle'; import {SortField} from '$lib/dotnet-types/generated-types/MiniLcm/SortField'; + import DeleteDialog from '$lib/entry-editor/DeleteDialog.svelte'; + import {initDialogService} from '$lib/entry-editor/dialog-service'; export let loading = false; export let about: string | undefined = undefined; @@ -268,6 +270,10 @@ if (entry) onEntryCreated(entry, options); return entry; } + let deleteDialog: DeleteDialog; + $: dialogHolder.dialog = deleteDialog; + const dialogHolder: {dialog?: DeleteDialog} = {}; + initDialogService(() => dialogHolder.dialog); initProjectCommands({ createNewEntry: openNewEntryDialog, @@ -427,3 +433,4 @@ {/if} + diff --git a/frontend/viewer/src/lib/entry-editor/DeleteDialog.svelte b/frontend/viewer/src/lib/entry-editor/DeleteDialog.svelte new file mode 100644 index 000000000..b8d450e44 --- /dev/null +++ b/frontend/viewer/src/lib/entry-editor/DeleteDialog.svelte @@ -0,0 +1,43 @@ + + +
Delete {subject}
+
+

Are you sure you want to delete {subject}?

+
+
+ + +
+
diff --git a/frontend/viewer/src/lib/entry-editor/dialog-service.ts b/frontend/viewer/src/lib/entry-editor/dialog-service.ts new file mode 100644 index 000000000..209788100 --- /dev/null +++ b/frontend/viewer/src/lib/entry-editor/dialog-service.ts @@ -0,0 +1,28 @@ +import type DeleteDialog from '$lib/entry-editor/DeleteDialog.svelte'; +import {getContext, setContext} from 'svelte'; + +export function initDialogService(rootDialog: () => DeleteDialog | undefined): DialogService { + const dialogService = new DialogService(rootDialog); + setContext('DialogService', dialogService); + return dialogService; +} + +export function useDialogService(): DialogService { + const rootDialog = getContext('DialogService'); + if (!rootDialog) { + throw new Error('DialogService not found'); + } + return rootDialog as DialogService; +} + +export class DialogService { + + constructor(private deleteDialog: () => DeleteDialog | undefined) { + } + + promptDelete(subject: string): Promise { + const deleteDialog = this.deleteDialog(); + if (!deleteDialog) throw new Error('no deleted dialog found'); + return deleteDialog.prompt(subject); + } +} diff --git a/frontend/viewer/src/lib/entry-editor/object-editors/EntryEditor.svelte b/frontend/viewer/src/lib/entry-editor/object-editors/EntryEditor.svelte index 0b9070c73..07bfe2d0b 100644 --- a/frontend/viewer/src/lib/entry-editor/object-editors/EntryEditor.svelte +++ b/frontend/viewer/src/lib/entry-editor/object-editors/EntryEditor.svelte @@ -16,7 +16,8 @@ import ComplexFormComponents from '../field-editors/ComplexFormComponents.svelte'; import ComplexForms from '../field-editors/ComplexForms.svelte'; import ComplexFormTypes from '../field-editors/ComplexFormTypes.svelte'; - + import {useDialogService} from '$lib/entry-editor/dialog-service'; + const dialogService = useDialogService(); const dispatch = createEventDispatcher<{ change: { entry: IEntry, sense?: ISense, example?: IExampleSentence}; delete: { entry: IEntry, sense?: ISense, example?: IExampleSentence}; @@ -36,11 +37,13 @@ sense.exampleSentences = [...sense.exampleSentences, sentence]; entry = entry; // examples counts are not updated without this } - function deleteEntry() { + async function deleteEntry() { + if (!await dialogService.promptDelete('Entry')) return; dispatch('delete', {entry}); } - function deleteSense(sense: ISense) { + async function deleteSense(sense: ISense) { + if (!await dialogService.promptDelete('Sense')) return; entry.senses = entry.senses.filter(s => s !== sense); dispatch('delete', {entry, sense}); } @@ -50,7 +53,8 @@ dispatch('change', {entry, sense}); highlightedEntity = sense; } - function deleteExample(sense: ISense, example: IExampleSentence) { + async function deleteExample(sense: ISense, example: IExampleSentence) { + if (!await dialogService.promptDelete('Example sentence')) return; sense.exampleSentences = sense.exampleSentences.filter(e => e !== example); dispatch('delete', {entry, sense, example}); entry = entry; // examples are not updated without this