Makros: UI (Button-Objekt, Verwaltungspanel, Symbolleiste)

Zweiter von zwei Commits (Fundament in e670f13). Neues, frei platzierbares
Button-Objekt auf der Fläche, das per Klick ein zugewiesenes Makro
ausführt (Zuweisung im Inspektor oder per Rechtsklick direkt am Button).
Neues "▶ Makros"-Panel (Liste + Formular, dritter "▶ Ausführen"-Button pro
Zeile) für Anlegen/Bearbeiten/Löschen/Testlauf ohne Canvas-Button.

Beim Live-Testen gefunden und behoben: ADDTABLE/ADDSHEET navigieren als
Nebeneffekt zur eingefügten Tabelle/zum eingefügten Blatt (richtig für
einen Symbolleisten-Klick, überraschend für einen Makro-Lauf im
Hintergrund) — runMacro() sichert und stellt jetzt aktives Blatt/Objekt/
Auswahl nach dem Lauf wieder her. Nebenbei zwei bestehende
"impliziter-Else"-Fehler behoben, die mit einer vierten Objektart zu
echten Fehlerquellen geworden wären (io/json.ts normaliseObject,
CanvasObject.vue Render-Dispatch).

Vollständig live per Playwright End-zu-Ende verifiziert: Makro über das
echte Formular angelegt, per Listen-Button und per Button-Objekt
ausgeführt, Rechtsklick-Zuweisung geprüft, ein absichtlich fehlerhaftes
Makro zeigt einen Fehler-Toast statt abzustürzen — keine Konsolenfehler.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-09 10:07:56 +02:00
co-authored by Claude Sonnet 5
parent e670f13077
commit a59fda25b0
11 changed files with 759 additions and 30 deletions
+59
View File
@@ -21,6 +21,7 @@ const table = computed(() =>
);
const chart = computed(() => (obj.value?.kind === 'chart' ? obj.value : null));
const text = computed(() => (obj.value?.kind === 'text' ? obj.value : null));
const button = computed(() => (obj.value?.kind === 'button' ? obj.value : null));
const sheet = computed(() => store.activeSheet.value);
const num = (e: Event) => Number((e.target as HTMLInputElement).value);
@@ -352,6 +353,64 @@ function resetDoc() {
<button class="danger" @click="store.deleteObject(text.id)">Textfeld löschen</button>
</template>
<template v-else-if="button">
<label class="f">
<span>Name</span>
<input :value="button.name" @change="store.renameObject(button.id, str($event))" />
</label>
<label class="f">
<span>Beschriftung</span>
<input :value="button.label" @change="store.setButtonLabel(button.id, str($event))" />
</label>
<label class="f">
<span>Makro</span>
<select :value="button.macroId ?? ''" @change="store.setButtonMacroId(button.id, str($event) || null)">
<option value="">— keins —</option>
<option v-for="m in store.doc.macros" :key="m.id" :value="m.id">{{ m.name }}</option>
</select>
</label>
<p v-if="store.doc.macros.length === 0" class="muted">
Noch kein Makro vorhanden — über „▶ Makros" in der Symbolleiste anlegen.
</p>
<label class="f">
<span>Textfarbe</span>
<span class="color">
<input type="color" :value="button.fg ?? '#0a84ff'" @input="store.setObjectTextColor(button.id, str($event))" />
<button class="mini" title="Standard" @click="store.setObjectTextColor(button.id, null)">↺</button>
<em v-if="!button.fg">Design</em>
</span>
</label>
<label class="f">
<span>Hintergrund</span>
<span class="color">
<input type="color" :value="button.bg ?? '#e8f2ff'" @input="store.setObjectBg(button.id, str($event))" />
<button class="mini" title="Ohne" @click="store.setObjectBg(button.id, null)">↺</button>
<em v-if="!button.bg">Design</em>
</span>
</label>
<div class="grid2">
<label class="f">
<span>Breite (px)</span>
<input type="number" min="60" step="10" :value="button.width" @change="store.resizeButton(button.id, num($event), button.height)" />
</label>
<label class="f">
<span>Höhe (px)</span>
<input type="number" min="28" step="10" :value="button.height" @change="store.resizeButton(button.id, button.width, num($event))" />
</label>
</div>
<div class="grid2">
<label class="f">
<span>Position X</span>
<input type="number" min="0" step="4" :value="button.x" @change="store.moveObject(button.id, num($event), button.y)" />
</label>
<label class="f">
<span>Position Y</span>
<input type="number" min="0" step="4" :value="button.y" @change="store.moveObject(button.id, button.x, num($event))" />
</label>
</div>
<button class="danger" @click="store.deleteObject(button.id)">Button löschen</button>
</template>
<p v-else class="muted">Ein Objekt auf der Fläche anklicken, um es hier zu bearbeiten.</p>
</section>