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
+23 -9
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
/**
* Draggable / resizable wrapper for a table or chart object on the sheet canvas.
* Draggable / resizable wrapper for a table, chart, text or button object on
* the sheet canvas.
*
* - the header strip and the frame border move the object;
* - the SE handle resizes a chart/text freely; for a table, what it does
@@ -25,6 +26,7 @@ import { useDocument } from '../store/document';
import TableGrid from './TableGrid.vue';
import ChartView from './ChartView.vue';
import TextBox from './TextBox.vue';
import ButtonView from './ButtonView.vue';
const props = defineProps<{ object: SheetObject; selected: boolean; zoom: number }>();
const emit = defineEmits<{ (e: 'select'): void }>();
@@ -139,7 +141,7 @@ function lastNonEmptyCol(t: TableObject): number {
* "Automatisch" vs. "Feste Größe" split. */
const resizeKind = computed<'free' | 'viewport' | 'grid-size'>(() => {
const o = props.object;
if (o.kind === 'chart' || o.kind === 'text') return 'free';
if (o.kind === 'chart' || o.kind === 'text' || o.kind === 'button') return 'free';
return o.view.mode === 'fixed' ? 'viewport' : 'grid-size';
});
@@ -150,7 +152,7 @@ function onResizeDown(e: MouseEvent) {
const o = props.object;
let w: number;
let h: number;
if (o.kind === 'chart' || o.kind === 'text') {
if (o.kind === 'chart' || o.kind === 'text' || o.kind === 'button') {
w = o.width;
h = o.height;
} else if (o.view.mode === 'fixed') {
@@ -174,6 +176,7 @@ function onResizeMove(e: MouseEvent) {
if (resizeKind.value === 'free') {
if (o.kind === 'chart') store.resizeChart(o.id, rs.w + dx, rs.h + dy);
else if (o.kind === 'text') store.resizeText(o.id, rs.w + dx, rs.h + dy);
else if (o.kind === 'button') store.resizeButton(o.id, rs.w + dx, rs.h + dy);
} else if (resizeKind.value === 'viewport') {
// "Feste Größe": pure pixel-viewport resize, independent of row/col
// count -- cheap (setTableView never rebuilds the workbook), so a live
@@ -205,14 +208,24 @@ function onResizeUp() {
window.removeEventListener('mouseup', onResizeUp);
}
const icon = computed(() =>
props.object.kind === 'table' ? '▦' : props.object.kind === 'chart' ? '▤' : '¶',
);
const icon = computed(() => {
switch (props.object.kind) {
case 'table':
return '▦';
case 'chart':
return '▤';
case 'button':
return '▶';
case 'text':
default:
return '¶';
}
});
const style = computed(() => {
const o = props.object;
const base: Record<string, string> = { left: o.x + 'px', top: o.y + 'px' };
if (o.kind === 'chart' || o.kind === 'text') {
if (o.kind === 'chart' || o.kind === 'text' || o.kind === 'button') {
base.width = o.width + 'px';
base.height = o.height + 'px';
}
@@ -223,7 +236,7 @@ const style = computed(() => {
<template>
<div
class="canvas-object"
:class="{ selected, flex: object.kind === 'chart' || object.kind === 'text' }"
:class="{ selected, flex: object.kind === 'chart' || object.kind === 'text' || object.kind === 'button' }"
:style="style"
@mousedown="emit('select')"
>
@@ -254,7 +267,8 @@ const style = computed(() => {
<div class="content">
<TableGrid v-if="object.kind === 'table'" :table-id="object.id" />
<ChartView v-else-if="object.kind === 'chart'" :obj-id="object.id" />
<TextBox v-else :obj-id="object.id" />
<TextBox v-else-if="object.kind === 'text'" :obj-id="object.id" />
<ButtonView v-else-if="object.kind === 'button'" :obj-id="object.id" />
</div>
<div