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:
@@ -0,0 +1,114 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* A clickable canvas button object -- one of the two ways to trigger a
|
||||
* macro (the other is the "▶ Ausführen" row in MacrosModal.vue). Caption
|
||||
* (`label`) and colours are edited in the inspector (SettingsPanel.vue);
|
||||
* which macro it runs is assignable both there and from this component's
|
||||
* own right-click menu, for a quicker "build a button, wire it up, done"
|
||||
* loop without leaving the canvas.
|
||||
*/
|
||||
import { computed, reactive } from 'vue';
|
||||
import { useDocument } from '../store/document';
|
||||
import { useToasts } from '../composables/useToasts';
|
||||
import ContextMenu from './ContextMenu.vue';
|
||||
|
||||
const props = defineProps<{ objId: string }>();
|
||||
const store = useDocument();
|
||||
const toasts = useToasts();
|
||||
|
||||
const obj = computed(() => {
|
||||
const o = store.objectById(props.objId);
|
||||
return o && o.kind === 'button' ? o : undefined;
|
||||
});
|
||||
|
||||
function run() {
|
||||
const o = obj.value;
|
||||
if (!o) return;
|
||||
if (!o.macroId || !store.doc.macros.some((m) => m.id === o.macroId)) {
|
||||
toasts.info('Diesem Button ist noch kein Makro zugewiesen — per Rechtsklick oder im Inspektor.');
|
||||
return;
|
||||
}
|
||||
const result = store.runMacro(o.macroId);
|
||||
if (result.ok) toasts.success(result.message);
|
||||
else toasts.error(result.message);
|
||||
}
|
||||
|
||||
const boxStyle = computed(() => {
|
||||
const o = obj.value;
|
||||
if (!o) return {};
|
||||
return {
|
||||
color: o.fg || 'var(--accent)',
|
||||
background: o.bg || 'var(--accent-weak)',
|
||||
} as Record<string, string>;
|
||||
});
|
||||
|
||||
// ---- right-click: run / (re)assign a macro without opening the inspector ----
|
||||
const menu = reactive({ open: false, x: 0, y: 0 });
|
||||
function onContextMenu(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
menu.x = e.clientX;
|
||||
menu.y = e.clientY;
|
||||
menu.open = true;
|
||||
}
|
||||
const menuItems = computed(() => {
|
||||
const o = obj.value;
|
||||
if (!o) return [];
|
||||
const items: { label: string; action: () => void }[] = [];
|
||||
if (o.macroId) {
|
||||
items.push({ label: '▶ Makro ausführen', action: run });
|
||||
}
|
||||
for (const m of store.doc.macros) {
|
||||
items.push({
|
||||
label: `${m.id === o.macroId ? '✓ ' : ''}Makro: ${m.name}`,
|
||||
action: () => store.setButtonMacroId(o.id, m.id),
|
||||
});
|
||||
}
|
||||
if (store.doc.macros.length === 0) {
|
||||
items.push({ label: 'Kein Makro vorhanden — erst in „▶ Makros" anlegen', action: () => {} });
|
||||
}
|
||||
if (o.macroId) {
|
||||
items.push({ label: 'Zuweisung entfernen', action: () => store.setButtonMacroId(o.id, null) });
|
||||
}
|
||||
return items;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="obj"
|
||||
class="btnbox"
|
||||
:style="boxStyle"
|
||||
@click="run"
|
||||
@contextmenu="onContextMenu"
|
||||
>
|
||||
<span class="label">{{ obj.label || 'Button' }}</span>
|
||||
<ContextMenu v-model:open="menu.open" :x="menu.x" :y="menu.y" :items="menuItems" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btnbox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 14px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.btnbox:hover {
|
||||
filter: brightness(1.06);
|
||||
}
|
||||
.btnbox:active {
|
||||
filter: brightness(0.96);
|
||||
}
|
||||
.label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user