Start with an empty document; offer Datei › Neu and Datei › Muster
A first start (nothing in localStorage) now opens a blank document -- one sheet "Blatt 1" with an empty "Tabelle 1" -- instead of the pre-filled sample. The file menu gets "Neu" (blank document) and "Muster · Umsatzanalyse" (the former seed), both behind a confirm and both starting a fresh undo history. The toolbar's "Zurücksetzen" button is gone; its job is now Datei › Muster. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1060,11 +1060,13 @@ Alles clientseitig (`Blob` + `URL.createObjectURL` + `<a download>` + revoke).
|
|||||||
## Persistenz
|
## Persistenz
|
||||||
|
|
||||||
Das gesamte Dokument wird (debounced) nach `localStorage` gespiegelt, robust
|
Das gesamte Dokument wird (debounced) nach `localStorage` gespiegelt, robust
|
||||||
gegen leeren/kaputten State — Fallback ist das Seed-Dokument. Ein v1-State im
|
gegen leeren/kaputten State — Fallback ist ein **leeres Dokument** (ein Blatt
|
||||||
Storage wird beim Laden migriert. Button **„Zurücksetzen"** stellt das
|
„Blatt 1" mit einer leeren „Tabelle 1"). Ein v1-State im Storage wird beim
|
||||||
Seed-Dokument wieder her.
|
Laden migriert. **Datei › Neu** legt ein neues leeres Dokument an, **Datei ›
|
||||||
|
Muster · Umsatzanalyse** lädt das vorbefüllte Musterdokument (beides nach
|
||||||
|
Rückfrage, die bisherigen Änderungen gehen verloren).
|
||||||
|
|
||||||
## Vorbefülltes Dokument
|
## Vorbefülltes Dokument (Datei › Muster)
|
||||||
|
|
||||||
Blatt **„Analyse"** mit drei Tabellen-Objekten (**Umsatz**, **Kosten**,
|
Blatt **„Analyse"** mit drei Tabellen-Objekten (**Umsatz**, **Kosten**,
|
||||||
**Übersicht**) und einem **Diagramm** (Umsatz je Region), plus ein leeres Blatt
|
**Übersicht**) und einem **Diagramm** (Umsatz je Region), plus ein leeres Blatt
|
||||||
@@ -1452,6 +1454,6 @@ src/
|
|||||||
numolus-logo.svg # Marke: drei überlappende Pop-Art-Quadrate + Schriftzug
|
numolus-logo.svg # Marke: drei überlappende Pop-Art-Quadrate + Schriftzug
|
||||||
config/brand.ts # Herausgeber-Metadaten (wicom.li) + Social-Media-Liste
|
config/brand.ts # Herausgeber-Metadaten (wicom.li) + Social-Media-Liste
|
||||||
composables/ # Theme, Toasts
|
composables/ # Theme, Toasts
|
||||||
data/seed.ts # vorbefülltes Dokument
|
data/seed.ts # leeres Dokument + Musterdokument
|
||||||
styles/main.css # Design-Tokens (Light/Dark)
|
styles/main.css # Design-Tokens (Light/Dark)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -196,10 +196,17 @@ async function doImport(action: 'replace' | 'append' | 'confirm') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReset() {
|
function onNew() {
|
||||||
if (confirm('Vorbefülltes Dokument wiederherstellen? Alle Änderungen gehen verloren.')) {
|
if (confirm('Neues, leeres Dokument anlegen? Alle Änderungen gehen verloren.')) {
|
||||||
|
store.newDocument();
|
||||||
|
toasts.info('Neues Dokument angelegt.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSample() {
|
||||||
|
if (confirm('Musterdokument laden? Alle Änderungen gehen verloren.')) {
|
||||||
store.resetToSeed();
|
store.resetToSeed();
|
||||||
toasts.info('Dokument zurückgesetzt.');
|
toasts.info('Musterdokument geladen.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -291,6 +298,9 @@ function onReset() {
|
|||||||
<div class="menu-wrap">
|
<div class="menu-wrap">
|
||||||
<button class="btn" @click="fileMenuOpen = !fileMenuOpen">Datei ▾</button>
|
<button class="btn" @click="fileMenuOpen = !fileMenuOpen">Datei ▾</button>
|
||||||
<ul v-if="fileMenuOpen" class="menu" @click="fileMenuOpen = false">
|
<ul v-if="fileMenuOpen" class="menu" @click="fileMenuOpen = false">
|
||||||
|
<li><button @click="onNew">Neu</button></li>
|
||||||
|
<li><button @click="onSample">Muster · Umsatzanalyse</button></li>
|
||||||
|
<li class="sep" />
|
||||||
<li><button @click="pickFile">Importieren … (.json .csv .xlsx)</button></li>
|
<li><button @click="pickFile">Importieren … (.json .csv .xlsx)</button></li>
|
||||||
<li class="sep" />
|
<li class="sep" />
|
||||||
<li><button @click="runJsonExport">Export · Dokument (.json)</button></li>
|
<li><button @click="runJsonExport">Export · Dokument (.json)</button></li>
|
||||||
@@ -310,7 +320,6 @@ function onReset() {
|
|||||||
>
|
>
|
||||||
<ToolbarIcon :name="themeIcon" />
|
<ToolbarIcon :name="themeIcon" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" @click="onReset">Zurücksetzen</button>
|
|
||||||
|
|
||||||
<input ref="fileInput" type="file" accept=".json,.csv,.xlsx" hidden @change="onFileChosen" />
|
<input ref="fileInput" type="file" accept=".json,.csv,.xlsx" hidden @change="onFileChosen" />
|
||||||
<span v-if="busy" class="busy">Verarbeite …</span>
|
<span v-if="busy" class="busy">Verarbeite …</span>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
|
* Document templates: `createBlankDocument()` (Datei › Neu, and what a first
|
||||||
|
* start opens) and `createSeedDocument()` (Datei › Muster).
|
||||||
|
*
|
||||||
* The pre-filled document ("roter Faden" of the study), in the v2 canvas model:
|
* The pre-filled document ("roter Faden" of the study), in the v2 canvas model:
|
||||||
* one sheet ("Analyse") carrying three table objects and a chart object, plus a
|
* one sheet ("Analyse") carrying three table objects and a chart object, plus a
|
||||||
* second empty sheet to try things out.
|
* second empty sheet to try things out.
|
||||||
@@ -144,3 +147,21 @@ export function createSeedDocument(): DocumentState {
|
|||||||
macros: [],
|
macros: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** An empty document: one sheet with one empty table, ready to type into. */
|
||||||
|
export function createBlankDocument(): DocumentState {
|
||||||
|
const table = makeTable('Tabelle 1', 40, 40, 12, 6, []);
|
||||||
|
const sheet = { id: nextId('sheet'), name: 'Blatt 1', objects: [table] };
|
||||||
|
return {
|
||||||
|
version: 2,
|
||||||
|
sheets: [sheet],
|
||||||
|
activeSheetId: sheet.id,
|
||||||
|
activeObjectId: table.id,
|
||||||
|
selection: { tableId: table.id, anchor: { col: 0, row: 0 }, focus: { col: 0, row: 0 } },
|
||||||
|
locale: 'de-DE',
|
||||||
|
csvDelimiter: ',',
|
||||||
|
customFunctions: [],
|
||||||
|
namedRanges: [],
|
||||||
|
macros: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -120,6 +120,19 @@ describe('seed document acceptance (canvas model)', () => {
|
|||||||
expect(val(s, 'Übersicht', 'B2')).toBe(5970);
|
expect(val(s, 'Übersicht', 'B2')).toBe(5970);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('newDocument replaces everything with one empty sheet + table and a fresh history', () => {
|
||||||
|
const s = fresh();
|
||||||
|
setCell(s, 'Umsatz', 'B2', '99999');
|
||||||
|
s.newDocument();
|
||||||
|
expect(s.doc.sheets).toHaveLength(1);
|
||||||
|
expect(s.doc.sheets[0].name).toBe('Blatt 1');
|
||||||
|
const [table] = s.doc.sheets[0].objects;
|
||||||
|
expect(table.kind === 'table' && table.name).toBe('Tabelle 1');
|
||||||
|
expect(table.kind === 'table' && Object.keys(table.cells)).toEqual([]);
|
||||||
|
expect(s.doc.namedRanges).toEqual([]);
|
||||||
|
expect(s.canUndo.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('inserting a new table and referencing it across sheets works', () => {
|
it('inserting a new table and referencing it across sheets works', () => {
|
||||||
const s = fresh();
|
const s = fresh();
|
||||||
const sheet2 = s.doc.sheets[1].id;
|
const sheet2 = s.doc.sheets[1].id;
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ import type {
|
|||||||
TextObject,
|
TextObject,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { defaultTableView, DEFAULT_ROW_H, MIN_GRID_SIZE, MAX_GRID_SIZE } from './types';
|
import { defaultTableView, DEFAULT_ROW_H, MIN_GRID_SIZE, MAX_GRID_SIZE } from './types';
|
||||||
import { createSeedDocument } from '../data/seed';
|
import { createBlankDocument, createSeedDocument } from '../data/seed';
|
||||||
import { validateDocument } from '../io/json';
|
import { validateDocument } from '../io/json';
|
||||||
|
|
||||||
const STORAGE_KEY = 'numolus:doc:v2';
|
const STORAGE_KEY = 'numolus:doc:v2';
|
||||||
@@ -535,7 +535,7 @@ function loadInitial(): DocumentState {
|
|||||||
} catch {
|
} catch {
|
||||||
/* missing / corrupt / stale-schema -> clean fallback */
|
/* missing / corrupt / stale-schema -> clean fallback */
|
||||||
}
|
}
|
||||||
return createSeedDocument();
|
return createBlankDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- store singleton --------------------------------------------------
|
// ---- store singleton --------------------------------------------------
|
||||||
@@ -2848,6 +2848,11 @@ function createStore() {
|
|||||||
applyDocumentState(next);
|
applyDocumentState(next);
|
||||||
resetHistory();
|
resetHistory();
|
||||||
}
|
}
|
||||||
|
/** Datei › Neu: an empty document. */
|
||||||
|
function newDocument() {
|
||||||
|
replaceDocument(createBlankDocument());
|
||||||
|
}
|
||||||
|
/** Datei › Muster: the pre-filled sample document. */
|
||||||
function resetToSeed() {
|
function resetToSeed() {
|
||||||
replaceDocument(createSeedDocument());
|
replaceDocument(createSeedDocument());
|
||||||
}
|
}
|
||||||
@@ -3040,6 +3045,7 @@ function createStore() {
|
|||||||
pointDrag,
|
pointDrag,
|
||||||
// document
|
// document
|
||||||
replaceDocument,
|
replaceDocument,
|
||||||
|
newDocument,
|
||||||
resetToSeed,
|
resetToSeed,
|
||||||
appendSheets,
|
appendSheets,
|
||||||
addTableFromCells,
|
addTableFromCells,
|
||||||
|
|||||||
Reference in New Issue
Block a user