fix issues with dialogs, and issue with vault rename

This commit is contained in:
Nystik 2026-03-18 19:11:13 +01:00
parent e1d484fd28
commit 2418f125f0
6 changed files with 201 additions and 32 deletions

View file

@ -78,16 +78,17 @@ export const vaultService = {
if (window.__vaultConfig) { if (window.__vaultConfig) {
window.__vaultConfig.id = newName; window.__vaultConfig.id = newName;
} }
history.replaceState(null, "", "/?vault=" + encodeURIComponent(newName));
} }
return this.listVaults(); return this.listVaults();
}, },
async deleteVault(id) { async deleteVault(id) {
await fetchJson( await fetchJson(API_BASE + "/remove?vault=" + encodeURIComponent(id), {
API_BASE + "/remove?vault=" + encodeURIComponent(id), method: "DELETE",
{ method: "DELETE" }, });
);
const wasCurrentVault = id === this.getCurrentVaultId(); const wasCurrentVault = id === this.getCurrentVaultId();

View file

@ -1,3 +1,9 @@
import {
showMessageDialog,
showConfirmDialog,
showPromptDialog,
} from "../../../ui/bootstrap.js";
export const dialogShim = { export const dialogShim = {
async showOpenDialog(browserWindow, options) { async showOpenDialog(browserWindow, options) {
// TODO: implement custom modal with server-side file listing // TODO: implement custom modal with server-side file listing
@ -5,21 +11,28 @@ export const dialogShim = {
return { canceled: true, filePaths: [] }; return { canceled: true, filePaths: [] };
}, },
// TODO: replace prompt() with a styled modal (matching vault manager style)
async showSaveDialog(browserWindow, options) { async showSaveDialog(browserWindow, options) {
if (typeof browserWindow === "object" && !options) { if (typeof browserWindow === "object" && !options) {
options = browserWindow; options = browserWindow;
} }
const defaultName = const defaultName =
options?.defaultPath?.split(/[/\\]/).pop() || "download"; options?.defaultPath?.split(/[\/\\]/).pop() || "download";
const name = prompt("Save as:", defaultName); const name = await showPromptDialog(
"Save File",
"Save as:",
"filename",
defaultName,
"Save",
);
if (!name) { if (!name) {
return { canceled: true, filePath: undefined }; return { canceled: true, filePath: undefined };
} }
return { canceled: false, filePath: "/downloads/" + name }; return { canceled: false, filePath: "/downloads/" + name };
}, },
// TODO: replace alert() with a styled modal (matching vault manager style)
async showMessageBox(browserWindow, options) { async showMessageBox(browserWindow, options) {
if (typeof browserWindow === "object" && !options) { if (typeof browserWindow === "object" && !options) {
options = browserWindow; options = browserWindow;
@ -30,21 +43,20 @@ export const dialogShim = {
const message = options.message || ""; const message = options.message || "";
const detail = options.detail || ""; const detail = options.detail || "";
const buttons = options.buttons || ["OK"]; const buttons = options.buttons || ["OK"];
const fullMessage = message + (detail ? "\n\n" + detail : "");
if (buttons.length <= 1) { if (buttons.length <= 1) {
alert(message + (detail ? "\n\n" + detail : "")); await showMessageDialog(options.title || "Message", fullMessage);
return { response: 0, checkboxChecked: false }; return { response: 0, checkboxChecked: false };
} }
const result = confirm( const result = await showConfirmDialog(
message + options.title || "Confirm",
(detail ? "\n\n" + detail : "") + message,
'\n\n[OK] = "' + detail,
buttons[0] + buttons[0],
'", [Cancel] = "' +
buttons[1] +
'"',
); );
return { return {
response: result ? 0 : 1, response: result ? 0 : 1,
checkboxChecked: false, checkboxChecked: false,
@ -53,6 +65,6 @@ export const dialogShim = {
showErrorBox(title, content) { showErrorBox(title, content) {
console.error("[shim:dialog] Error:", title, content); console.error("[shim:dialog] Error:", title, content);
alert(title + "\n\n" + content); showMessageDialog(title, content);
}, },
}; };

63
ui/bootstrap.js vendored
View file

@ -9,3 +9,66 @@ export function showVaultManager() {
props: { vaultService }, props: { vaultService },
}); });
} }
export function showMessageDialog(title, message) {
return new Promise((resolve) => {
const dialog = new window.IgnisUI.MessageDialog({
target: document.body,
props: { title, message },
});
dialog.$on("confirm", () => {
dialog.$destroy();
resolve();
});
});
}
export function showConfirmDialog(
title,
message,
description,
confirmText = "OK",
) {
return new Promise((resolve) => {
const dialog = new window.IgnisUI.ConfirmDialog({
target: document.body,
props: { title, message, description, confirmText },
});
dialog.$on("confirm", () => {
dialog.$destroy();
resolve(true);
});
dialog.$on("cancel", () => {
dialog.$destroy();
resolve(false);
});
});
}
export function showPromptDialog(
title,
label,
placeholder = "",
value = "",
confirmText = "OK",
) {
return new Promise((resolve) => {
const dialog = new window.IgnisUI.PromptDialog({
target: document.body,
props: { title, label, placeholder, value, confirmText },
});
dialog.$on("confirm", (event) => {
dialog.$destroy();
resolve(event.detail);
});
dialog.$on("cancel", () => {
dialog.$destroy();
resolve(null);
});
});
}

View file

@ -0,0 +1,69 @@
<script>
import { createEventDispatcher } from "svelte";
import Modal from "./Modal.svelte";
import Button from "../input/Button.svelte";
import { CircleAlert } from "lucide-svelte";
export let title = "Message";
export let message = "";
export let width = "500px";
const dispatch = createEventDispatcher();
let modalRef;
function onConfirm() {
dispatch("confirm");
modalRef.dismiss();
}
function onEscape() {
onConfirm();
}
export function dismiss() {
modalRef.dismiss();
}
</script>
<Modal
{title}
{width}
bind:this={modalRef}
on:escape={onEscape}
closeOnOverlayClick={false}
>
<svelte:fragment slot="icon">
<CircleAlert size="1.25rem" />
</svelte:fragment>
<div class="message-body">
<p class="message-text">{message}</p>
</div>
<svelte:fragment slot="footer">
<div class="message-footer">
<Button variant="primary" on:click={onConfirm}>OK</Button>
</div>
</svelte:fragment>
</Modal>
<style>
.message-body {
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--background-modifier-border);
}
.message-text {
margin: 0;
font-size: 0.9375rem;
color: var(--text-normal);
line-height: 1.5;
white-space: pre-wrap;
}
.message-footer {
display: flex;
justify-content: flex-end;
}
</style>

View file

@ -1 +1,4 @@
export { default as VaultManager } from "./views/VaultManager.svelte"; export { default as VaultManager } from "./views/VaultManager.svelte";
export { default as MessageDialog } from "./components/layout/MessageDialog.svelte";
export { default as ConfirmDialog } from "./components/layout/ConfirmDialog.svelte";
export { default as PromptDialog } from "./components/layout/PromptDialog.svelte";

View file

@ -12,6 +12,7 @@
import Modal from "../components/layout/Modal.svelte"; import Modal from "../components/layout/Modal.svelte";
import PromptDialog from "../components/layout/PromptDialog.svelte"; import PromptDialog from "../components/layout/PromptDialog.svelte";
import ConfirmDialog from "../components/layout/ConfirmDialog.svelte"; import ConfirmDialog from "../components/layout/ConfirmDialog.svelte";
import MessageDialog from "../components/layout/MessageDialog.svelte";
import SearchInput from "../components/input/SearchInput.svelte"; import SearchInput from "../components/input/SearchInput.svelte";
import Button from "../components/input/Button.svelte"; import Button from "../components/input/Button.svelte";
import ListItem from "../components/display/ListItem.svelte"; import ListItem from "../components/display/ListItem.svelte";
@ -27,6 +28,8 @@
let activeDialog = null; let activeDialog = null;
let targetVault = null; let targetVault = null;
let dialogValue = ""; let dialogValue = "";
let errorMessage = "";
let pendingReload = false;
const menuItems = [ const menuItems = [
{ id: "rename", label: "Rename" }, { id: "rename", label: "Rename" },
@ -108,12 +111,11 @@
try { try {
vaults = await vaultService.createVault(name); vaults = await vaultService.createVault(name);
} catch (err) {
alert("Failed to create vault: " + err.message);
return;
}
closeDialog(); closeDialog();
} catch (err) {
errorMessage = "Failed to create vault: " + err.message;
activeDialog = "error";
}
} }
async function onRenameConfirm(e) { async function onRenameConfirm(e) {
@ -128,15 +130,15 @@
try { try {
vaults = await vaultService.renameVault(targetVault.id, trimmed); vaults = await vaultService.renameVault(targetVault.id, trimmed);
} catch (err) {
alert("Failed to rename vault: " + err.message);
return;
}
closeDialog(); closeDialog();
if (wasCurrentVault) { if (wasCurrentVault) {
currentVaultId = vaultService.getCurrentVaultId(); currentVaultId = vaultService.getCurrentVaultId();
pendingReload = true;
}
} catch (err) {
errorMessage = "Failed to rename vault: " + err.message;
activeDialog = "error";
} }
} }
@ -154,7 +156,14 @@
vaultService.openVault(""); vaultService.openVault("");
} }
} catch (err) { } catch (err) {
alert("Failed to delete vault: " + err.message); errorMessage = "Failed to delete vault: " + err.message;
activeDialog = "error";
}
}
function onModalClose() {
if (pendingReload) {
window.location.reload();
} }
} }
@ -176,6 +185,7 @@
width="600px" width="600px"
bind:this={modalRef} bind:this={modalRef}
on:escape={onEscape} on:escape={onEscape}
on:close={onModalClose}
closeOnOverlayClick={false} closeOnOverlayClick={false}
> >
<svelte:fragment slot="icon"> <svelte:fragment slot="icon">
@ -270,7 +280,7 @@
<PromptDialog <PromptDialog
title="Rename Item" title="Rename Item"
label="New Name:" label="New Name:"
confirmText="Save Name" confirmText="Save"
bind:value={dialogValue} bind:value={dialogValue}
on:confirm={onRenameConfirm} on:confirm={onRenameConfirm}
on:cancel={closeDialog} on:cancel={closeDialog}
@ -303,6 +313,17 @@
</ConfirmDialog> </ConfirmDialog>
{/if} {/if}
{#if activeDialog === "error"}
<MessageDialog
title="Error"
message={errorMessage}
on:confirm={() => {
activeDialog = null;
errorMessage = "";
}}
/>
{/if}
<style> <style>
.section-header { .section-header {
display: flex; display: flex;