diff --git a/package-lock.json b/package-lock.json index a8cb3ec..12a3d69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@vue/eslint-config-typescript": "^14.1.3", "eslint": "^9.15.0", "eslint-plugin-vue": "^9.31.0", + "fake-indexeddb": "^6.2.5", "jsdom": "^25.0.1", "prettier": "^3.3.3", "typescript": "~5.6.3", @@ -2885,6 +2886,16 @@ "node": ">=12.0.0" } }, + "node_modules/fake-indexeddb": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.5.tgz", + "integrity": "sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", diff --git a/package.json b/package.json index c0bd151..6b69fa4 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@vue/eslint-config-typescript": "^14.1.3", "eslint": "^9.15.0", "eslint-plugin-vue": "^9.31.0", + "fake-indexeddb": "^6.2.5", "jsdom": "^25.0.1", "prettier": "^3.3.3", "typescript": "~5.6.3", diff --git a/src/components/AppToolbar.vue b/src/components/AppToolbar.vue index aae8dd6..99d5f17 100644 --- a/src/components/AppToolbar.vue +++ b/src/components/AppToolbar.vue @@ -7,6 +7,9 @@ import BaseModal from './BaseModal.vue'; import CustomFunctionsModal from './CustomFunctionsModal.vue'; import NamedRangesModal from './NamedRangesModal.vue'; import MacrosModal from './MacrosModal.vue'; +import DocumentsModal from './DocumentsModal.vue'; +import { useLibrary } from '../store/library'; +import { parseDocument } from '../io/json'; import TheLogo from './TheLogo.vue'; import ToolbarIcon, { type ToolbarIconName } from './ToolbarIcon.vue'; import { WICOM, WICOM_SOCIAL } from '../config/brand'; @@ -28,6 +31,7 @@ import { import type { Delimiter } from '../io/csv'; const store = useDocument(); +const library = useLibrary(); const { mode: themeMode, cycle: cycleTheme } = useTheme(); const toasts = useToasts(); @@ -105,6 +109,17 @@ const aboutOpen = ref(false); const functionsOpen = ref(false); const namedRangesOpen = ref(false); const macrosOpen = ref(false); +const documentsOpen = ref(false); + +// ---- document title (inline rename) ------------------------------------ +const titleDraft = ref(library.currentTitle.value); +watch(library.currentTitle, (t) => (titleDraft.value = t)); +function commitTitle() { + const id = library.state.currentId; + const t = titleDraft.value.trim(); + if (!id || !t) titleDraft.value = library.currentTitle.value; + else if (t !== library.currentTitle.value) void library.rename(id, t); +} const busy = ref(false); const fileInput = ref(null); @@ -157,7 +172,7 @@ function runCsvExport() { } async function runNumExport() { try { - await exportNum(store); + await exportNum(store, library.currentTitle.value); toasts.success('Dokument als .num gespeichert.'); } catch (err) { toasts.error(`Speichern fehlgeschlagen: ${(err as Error).message}`); @@ -165,13 +180,13 @@ async function runNumExport() { } function runJsonExport() { - exportJson(store); + exportJson(store, library.currentTitle.value); toasts.success('Dokument als JSON exportiert.'); } async function runXlsxExport() { busy.value = true; try { - await exportXlsx(store); + await exportXlsx(store, library.currentTitle.value); toasts.success('Arbeitsmappe als XLSX exportiert.'); } catch (err) { toasts.error(`XLSX-Export fehlgeschlagen: ${(err as Error).message}`); @@ -247,13 +262,15 @@ async function handleFile(file: File) { defineExpose({ handleFile }); -async function doImport(action: 'replace' | 'append' | 'confirm') { +async function doImport(action: 'new' | 'replace' | 'append' | 'confirm') { const ctx = importCtx.value; if (!ctx) return; busy.value = true; try { if (ctx.kind === 'json') { - if (action === 'replace') { + if (action === 'new') { + await library.create(parseDocument(ctx.text), ctx.fileName.replace(/\.(num|json)$/i, '')); + } else if (action === 'replace') { if (!confirm('Aktuelles Dokument vollständig ersetzen?')) return; importJsonReplace(store, ctx.text); } else { @@ -279,18 +296,14 @@ async function doImport(action: 'replace' | 'append' | 'confirm') { } } -function onNew() { - if (confirm('Neues, leeres Dokument anlegen? Alle Änderungen gehen verloren.')) { - store.newDocument(); - toasts.info('Neues Dokument angelegt.'); - } +async function onNew() { + await library.createBlank(); + toasts.info('Neues Dokument angelegt.'); } -function onSample() { - if (confirm('Musterdokument laden? Alle Änderungen gehen verloren.')) { - store.resetToSeed(); - toasts.info('Musterdokument geladen.'); - } +async function onSample() { + await library.createSample(); + toasts.info('Musterdokument als neues Dokument angelegt.'); } @@ -303,6 +316,17 @@ function onSample() { +