feat(cards): Karten speichern und Meine Karten
- account/cards.ts: Kartendienst (neu/bestehend speichern mit Aufräumen halber Uploads, Umbenennen, Löschen, Laden) über ein schmales CardsApi-Interface, Appwrite-Anschluss mit 22.4.1-Objektparametern, fetchFile über client.prepareRequest (Projekt-Header + Cookie, kein getFileView) - account/with-login.ts: bei 401 Nutzer neu prüfen, einmal anmelden, wiederholen - editor/save.ts: Speichern-Ablauf (ein Lauf zur Zeit, 401 -> Login -> einmal wiederholen, deutsche Fehlermeldung) - Editor: Speichern mit Schnappschuss und 480-px-Vorschau, Karte öffnen über ?karte=<id>, Bildwechsel während des Speicherns gesperrt - pages/cards-page.ts: /karten mit Kacheln, Öffnen, Herunterladen, Umbenennen, Löschen (eigener Dialog), Abmelde- und Leer-Ansicht - Tests für Dienst, withLogin, Speichern-Ablauf, Karten-Seite und Editor-Anbindung Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a147d3b496
commit
ba4d9f76d4
14 files changed
+2849
-16
No files matched your search
@@ -17,6 +17,8 @@ export interface PreviewView {
|
||||
hasImage: boolean;
|
||||
zoom: number;
|
||||
saving: boolean;
|
||||
/** true für kurze Zeit nach erfolgreichem Speichern (Knopf zeigt „Gespeichert“). */
|
||||
saved: boolean;
|
||||
exporting: boolean;
|
||||
}
|
||||
|
||||
@@ -27,9 +29,14 @@ export interface Preview {
|
||||
setDragging(active: boolean): void;
|
||||
setError(message: string | null): void;
|
||||
setWarning(message: string | null): void;
|
||||
/** Neutraler Hinweis, z. B. „Karte wird geladen …“. */
|
||||
setNotice(message: string | null): void;
|
||||
}
|
||||
|
||||
const NO_IMAGE_HINT = 'Erst ein Bild wählen';
|
||||
const SAVE_LABEL = 'Speichern';
|
||||
const SAVING_LABEL = 'Speichert …';
|
||||
const SAVED_LABEL = 'Gespeichert';
|
||||
const KEY_STEP = 24;
|
||||
const KEY_STEP_BIG = 96;
|
||||
const KEY_ZOOM_STEP = 0.1;
|
||||
@@ -185,13 +192,14 @@ export function createPreview(handlers: PreviewHandlers): Preview {
|
||||
// Meldungen
|
||||
const errorBox = el('p', { class: 'msg msg-error', role: 'alert' });
|
||||
const warningBox = el('p', { class: 'msg msg-warning', role: 'status' });
|
||||
const noticeBox = el('p', { class: 'msg msg-notice', role: 'status' });
|
||||
|
||||
// Knopfleiste
|
||||
const replaceButton = el('button', { type: 'button', class: 'btn btn-ghost' }, 'Bild ersetzen');
|
||||
replaceButton.addEventListener('click', openPicker);
|
||||
const downloadButton = el('button', { type: 'button', class: 'btn btn-primary' }, 'Herunterladen');
|
||||
downloadButton.addEventListener('click', () => handlers.onDownload());
|
||||
const saveButton = el('button', { type: 'button', class: 'btn btn-primary' }, 'Speichern');
|
||||
const saveButton = el('button', { type: 'button', class: 'btn btn-primary btn-save' }, SAVE_LABEL);
|
||||
saveButton.addEventListener('click', () => handlers.onSave());
|
||||
const actions = el(
|
||||
'div',
|
||||
@@ -202,7 +210,7 @@ export function createPreview(handlers: PreviewHandlers): Preview {
|
||||
|
||||
const note = el('p', { class: 'note' }, icon('lock', 'note-icon'), 'Dein Bild bleibt auf deinem Gerät, bis du speicherst.');
|
||||
|
||||
const inner = el('div', { class: 'preview-inner' }, stage, zoomRow, errorBox, warningBox, actions, note, input);
|
||||
const inner = el('div', { class: 'preview-inner' }, stage, zoomRow, errorBox, warningBox, noticeBox, actions, note, input);
|
||||
const section = el('section', { class: 'editor-center', 'aria-label': 'Kartenvorschau' }, inner);
|
||||
|
||||
function setDisabled(button: HTMLButtonElement, disabled: boolean, hint: string | null): void {
|
||||
@@ -227,6 +235,10 @@ export function createPreview(handlers: PreviewHandlers): Preview {
|
||||
zoomValue.textContent = `${Math.round(view.zoom * 100)} %`;
|
||||
setDisabled(downloadButton, !view.hasImage || view.exporting, view.hasImage ? null : NO_IMAGE_HINT);
|
||||
setDisabled(saveButton, !view.hasImage || view.saving, view.hasImage ? null : NO_IMAGE_HINT);
|
||||
saveButton.textContent = view.saving ? SAVING_LABEL : view.saved ? SAVED_LABEL : SAVE_LABEL;
|
||||
// Ein Bildwechsel mitten im Speichern würde die gespeicherte Karte durcheinanderbringen.
|
||||
replaceButton.disabled = view.saving;
|
||||
input.disabled = view.saving;
|
||||
downloadButton.setAttribute('aria-busy', String(view.exporting));
|
||||
saveButton.setAttribute('aria-busy', String(view.saving));
|
||||
},
|
||||
@@ -239,5 +251,8 @@ export function createPreview(handlers: PreviewHandlers): Preview {
|
||||
setWarning(message) {
|
||||
setMessage(warningBox, message);
|
||||
},
|
||||
setNotice(message) {
|
||||
setMessage(noticeBox, message);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user