Makros: GET/POST-Anfragen mit JSON-Ergebnis und FOR-Iteration

Erweitert die Makro-Sprache um Netzwerkzugriff: "x = GET(url)"/
"x = POST(url; body)" binden die geparste JSON-Antwort an eine Variable;
FOR item IN x DO ... END iteriert, wenn x ein JSON-Array ist, über dessen
Elemente; JSONGET(wert; pfad) liest ein Feld heraus (punktgetrennt,
Zahl = Array-Index) und funktioniert – anders als GET/POST – überall in
einem Ausdruck verschachtelt, nicht nur als ganze Zuweisung.

Da ein echter Netzwerkaufruf nicht synchron laufen kann, ohne den Tab
einzufrieren, bekommt der Makro-Interpreter einen separaten asynchronen
Zweig (execBlockAsync/execStatementAsync); der bestehende synchrone
Interpreter, den auch gewöhnliche "Mit Schleife"-Funktionen mitten in der
synchronen Neuberechnung nutzen, bleibt unangetastet. Netzwerkaufrufe
werden pro Makro einmalig mit Zielname bestätigt (dauerhaft gemerkt); die
bestehende "ein Lauf = ein Undo-Schritt"-Garantie, die bislang auf
Synchronität beruhte, wird jetzt explizit wiederhergestellt statt sich
weiter auf die Zeit-Koaleszenz zu verlassen.

Zwei echte Lücken erst beim eigenen Live-Test gefunden und behoben: (1)
JSONGET direkt in eine Aktion verschachtelt (statt erst einer Variable
zugewiesen) scheiterte zunächst an #NAME?; (2) JSONGET(...) direkt als
FOR-Quelle (ohne Zwischenvariable) scheiterte sowohl an der
Speicher-Zeit-Prüfung als auch zur Laufzeit, da substitute() einen
Funktionsaufruf nie auswertet.

Live gegen eine neue same-origin-Fixture (public/test-fixtures/sample.json)
verifiziert: echter GET, echter Bestätigungsdialog, FOR/JSONGET über die
echte Antwort, kein erneuter Dialog beim zweiten Lauf.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-09 11:10:19 +02:00
co-authored by Claude Sonnet 5
parent a59fda25b0
commit 3ae3d81066
15 changed files with 1043 additions and 101 deletions
+22 -7
View File
@@ -7,7 +7,7 @@
* 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 { computed, reactive, ref } from 'vue';
import { useDocument } from '../store/document';
import { useToasts } from '../composables/useToasts';
import ContextMenu from './ContextMenu.vue';
@@ -21,16 +21,26 @@ const obj = computed(() => {
return o && o.kind === 'button' ? o : undefined;
});
function run() {
// A macro can now involve a real network round-trip (GET/POST) and even a
// blocking confirm() prompt -- guard against a second click starting a
// second overlapping run while one is still in flight.
const running = ref(false);
async function run() {
const o = obj.value;
if (!o) return;
if (!o || running.value) 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);
running.value = true;
try {
const result = await store.runMacro(o.macroId);
if (result.ok) toasts.success(result.message);
else toasts.error(result.message);
} finally {
running.value = false;
}
}
const boxStyle = computed(() => {
@@ -77,11 +87,12 @@ const menuItems = computed(() => {
<div
v-if="obj"
class="btnbox"
:class="{ running }"
:style="boxStyle"
@click="run"
@contextmenu="onContextMenu"
>
<span class="label">{{ obj.label || 'Button' }}</span>
<span class="label">{{ running ? '…' : obj.label || 'Button' }}</span>
<ContextMenu v-model:open="menu.open" :x="menu.x" :y="menu.y" :items="menuItems" />
</div>
</template>
@@ -100,6 +111,10 @@ const menuItems = computed(() => {
cursor: pointer;
user-select: none;
}
.btnbox.running {
opacity: 0.6;
cursor: default;
}
.btnbox:hover {
filter: brightness(1.06);
}