@schlenkr/ragents 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/server/src/plugin-support/language-server/contract.ts +6 -1
- package/apps/server/src/plugin-support/language-server/tools.ts +29 -4
- package/apps/server/src/plugin-support/run-scripts.ts +4 -1
- package/apps/server/src/plugin-support/start-entries-contract.ts +4 -3
- package/apps/server/src/plugin-support/workspace-sandbox-host.ts +2 -0
- package/apps/web/src/App.tsx +0 -10
- package/apps/web/src/PluginChat.tsx +8 -8
- package/apps/web/src/PluginRegistry.tsx +0 -12
- package/apps/web/src/StartSurface.tsx +2 -2
- package/apps/web/src/ThemeSettings.tsx +1 -1
- package/apps/web/src/column/ColumnApp.tsx +83 -7
- package/apps/web/src/column/clipboard.ts +64 -0
- package/apps/web/src/column/host-contract.ts +10 -0
- package/apps/web/src/column.tsx +2 -0
- package/apps/web/src/panel/LoginForm.tsx +9 -3
- package/apps/web/src/panel/OverviewPage.tsx +187 -62
- package/apps/web/src/panel/PanelDialogs.tsx +115 -0
- package/apps/web/src/panel/PanelPage.tsx +1 -1
- package/apps/web/src/panel/SettingsPage.tsx +75 -79
- package/apps/web/src/panel/contract.ts +17 -9
- package/apps/web/src/panel/target-state.ts +27 -2
- package/apps/web/src/ui/theme.css +4 -0
- package/package.json +2 -2
- package/packages/ragents/src/plugin-host.ts +5 -2
- package/packages/ragents/src/plugin-types.ts +3 -2
- package/packages/workspace-executor/src/context.ts +4 -0
- package/packages/workspace-executor/src/executor.ts +9 -2
- package/packages/workspace-executor/src/index.ts +2 -0
- package/packages/workspace-executor/src/language-server/adapters/fsharp.ts +1 -0
- package/packages/workspace-executor/src/language-server/adapters/roslyn.ts +1 -0
- package/packages/workspace-executor/src/language-server/adapters/typescript.ts +34 -6
- package/packages/workspace-executor/src/language-server/contract.ts +7 -3
- package/packages/workspace-executor/src/language-server/host.ts +275 -138
- package/plugins/ragents.actor-programs/contract.ts +1 -1
- package/plugins/ragents.actor-programs/prompt.hbs +4 -4
- package/plugins/ragents.actor-programs/server/app-project.ts +0 -2
- package/plugins/ragents.actor-programs/server/runtime.ts +1 -1
- package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -5
- package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +7 -39
- package/plugins/ragents.actor-programs/web/api.ts +1 -10
- package/plugins/ragents.lsp-roslyn/web/language-server-plugin.tsx +73 -44
- package/plugins/ragents.orchestration/canvas.hbs +28 -44
- package/plugins/ragents.orchestration/contract.ts +17 -287
- package/plugins/ragents.orchestration/server/canvas-tool.ts +18 -123
- package/plugins/ragents.orchestration/tiled-layout.ts +12 -4
- package/plugins/ragents.orchestration/web/ActorCanvasControls.tsx +23 -47
- package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +3 -9
- package/plugins/ragents.orchestration/web/ActorShortcut.tsx +3 -5
- package/plugins/ragents.orchestration/web/ActorTile.tsx +3 -2
- package/plugins/ragents.orchestration/web/CanvasTiles.tsx +3 -3
- package/plugins/ragents.orchestration/web/Center.tsx +28 -463
- package/plugins/ragents.orchestration/web/MaterialBody.tsx +6 -87
- package/plugins/ragents.orchestration/web/TiledCanvas.tsx +2 -3
- package/plugins/ragents.orchestration/web/canvas-view-settings.ts +7 -13
- package/plugins/ragents.orchestration/web/column/RunColumn.tsx +44 -19
- package/plugins/ragents.orchestration/web/column/column-state.ts +13 -4
- package/plugins/ragents.orchestration/web/constants.ts +0 -2
- package/plugins/ragents.orchestration/web/index.tsx +0 -6
- package/plugins/ragents.orchestration/web/tile-docking.ts +7 -1
- package/plugins/ragents.orchestration/web/tiled-view-settings.ts +7 -9
- package/plugins/ragents.overseer/server/coordinator-prompt.ts +1 -1
- package/plugins/ragents.reference/examples.ts +1 -2
- package/plugins/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/actors/balcony-app/package.json +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +2 -2
- package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -4
- package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +9 -15
- package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +5 -3
- package/plugins/ragents.reference/run-scripts/learning-afternoon/package.json +1 -1
- package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +14 -4
- package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +1 -1
- package/plugins/ragents.reference/skills/210-moderator-handover/SKILL.md +1 -1
- package/plugins/ragents.reference/walkthroughs.ts +14 -14
- package/plugins/ragents.workspace/client/workspace-client.ts +4 -1
- package/scripts/package/publish-package.ts +4 -5
- package/scripts/workspace-client/run-workspace-client.ts +3 -1
- package/apps/web/src/MaterialSettings.tsx +0 -40
- package/apps/web/src/material-settings.ts +0 -104
- package/plugins/ragents.orchestration/web/ActorNet.tsx +0 -607
- package/plugins/ragents.orchestration/web/CardSizeSettings.tsx +0 -42
- package/plugins/ragents.orchestration/web/ScriptActorCard.tsx +0 -56
- package/plugins/ragents.orchestration/web/ZoomSettings.tsx +0 -38
- package/plugins/ragents.orchestration/web/canvas-layout-storage.ts +0 -39
- package/plugins/ragents.orchestration/web/canvas-layout.ts +0 -326
- package/plugins/ragents.orchestration/web/canvas-scene.ts +0 -311
- package/plugins/ragents.orchestration/web/card-size-settings.ts +0 -36
- package/plugins/ragents.orchestration/web/keyboard-navigation.ts +0 -55
- package/plugins/ragents.orchestration/web/material-layout.ts +0 -14
- package/plugins/ragents.orchestration/web/useCanvasMeasurements.ts +0 -78
- package/plugins/ragents.orchestration/web/zoom-settings.ts +0 -28
- package/plugins/ragents.reference/skills/120-canvas-conversation-circle/SKILL.md +0 -12
- package/plugins/ragents.reference/skills/130-canvas-pipeline-with-approval/SKILL.md +0 -12
- package/plugins/ragents.reference/skills/140-canvas-team-tree-and-topic-grid/SKILL.md +0 -12
|
@@ -1,34 +1,20 @@
|
|
|
1
1
|
import { useAccess } from "@aicontainer/web/AccessContext";
|
|
2
|
-
import {
|
|
3
|
-
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
3
|
import { createPortal } from "react-dom";
|
|
5
|
-
import { Scan } from "lucide-react";
|
|
6
4
|
import { CanvasTiles } from "./CanvasTiles";
|
|
5
|
+
import type { CanvasTileNode } from "../tiled-layout";
|
|
7
6
|
import { CanvasStartup } from "./CanvasStartup";
|
|
8
7
|
import { canvasStartupState } from "./canvas-startup";
|
|
9
8
|
import { tileEntities } from "./tile-docking";
|
|
10
9
|
import { publishStageEntities } from "./canvas-stage";
|
|
11
10
|
import { initialTileLayout, saveCanvasPresentation, useCanvasPresentation } from "./tiled-view-settings";
|
|
12
11
|
import { StatusGroup } from "@aicontainer/web/StatusGroup";
|
|
13
|
-
import {
|
|
14
|
-
import { NetLayer, type NetBounds } from "./ActorNet";
|
|
15
|
-
import { ORCHESTRATION_TAB_ID, statusControlClass, statusOutputClass } from "./constants";
|
|
12
|
+
import { ORCHESTRATION_TAB_ID, statusControlClass } from "./constants";
|
|
16
13
|
import { runViewFrom } from "./run-view";
|
|
17
|
-
import { usePinchZoomSensitivity } from "./zoom-settings";
|
|
18
14
|
import { ActorCanvasControls, ActorHeaderModeControl } from "./ActorCanvasControls";
|
|
19
15
|
import { actorVisibleOnCanvas, saveCanvasViewPreferences, useCanvasViewPreferences } from "./canvas-view-settings";
|
|
20
16
|
import { useCanvasController, type CanvasCenterContext, type EntityReference } from "@aicontainer/web/PluginRegistry";
|
|
21
17
|
|
|
22
|
-
import { canvasLayoutStorageKey, parseCanvasCamera, type CanvasCamera as Camera } from "./canvas-layout-storage";
|
|
23
|
-
import { createKeyboardNavigation, nextCanvasElement, type CanvasNavigationBox } from "./keyboard-navigation";
|
|
24
|
-
|
|
25
|
-
const INITIAL_INSET = 32;
|
|
26
|
-
const ZOOM_MIN = 0.1;
|
|
27
|
-
const ZOOM_MAX = 2.5;
|
|
28
|
-
|
|
29
|
-
const wheelPixels = (deltaY: number, deltaMode: number): number =>
|
|
30
|
-
deltaY * (deltaMode === 1 ? 18 : deltaMode === 2 ? 120 : 1);
|
|
31
|
-
|
|
32
18
|
export function OrchestrationCenter(props: CanvasCenterContext) {
|
|
33
19
|
return <RunOrchestrationCenter key={props.session.session.id} {...props} />;
|
|
34
20
|
}
|
|
@@ -40,23 +26,18 @@ function RunOrchestrationCenter({
|
|
|
40
26
|
onCenterModeChange,
|
|
41
27
|
session,
|
|
42
28
|
statusContainer,
|
|
43
|
-
toolbarContainer,
|
|
44
29
|
runToolbarContainer,
|
|
45
30
|
}: CanvasCenterContext) {
|
|
46
31
|
const inspect = useAccess().can("runs.inspect");
|
|
47
32
|
const canvas = useCanvasController();
|
|
48
|
-
const pinchZoomSensitivity = usePinchZoomSensitivity();
|
|
49
33
|
if (canvas?.tabId !== ORCHESTRATION_TAB_ID) {
|
|
50
34
|
throw new Error("Canvas-Beitrag und Canvas-Provider passen nicht zusammen");
|
|
51
35
|
}
|
|
52
|
-
const selectionHandler = useRef(canvas.acceptSelection);
|
|
53
|
-
useLayoutEffect(() => { selectionHandler.current = canvas.acceptSelection; }, [canvas.acceptSelection]);
|
|
54
36
|
const lastRunView = useRef<ReturnType<typeof runViewFrom>>(undefined);
|
|
55
37
|
const parsedRunView = runViewFrom(session.runView);
|
|
56
38
|
if (parsedRunView !== undefined) lastRunView.current = parsedRunView;
|
|
57
39
|
const runView = parsedRunView ?? lastRunView.current;
|
|
58
40
|
const { programLayout, personalLayout, presentation, programBasis } = useCanvasPresentation(session.session.id, runView);
|
|
59
|
-
const tiled = presentation.mode === "tiled";
|
|
60
41
|
const [presentationError, setPresentationError] = useState<string>();
|
|
61
42
|
const preferences = useCanvasViewPreferences(session.session.id);
|
|
62
43
|
const appActorIds = useMemo(() => new Set(canvasElements.flatMap((contribution) => contribution.select(session)
|
|
@@ -71,54 +52,25 @@ function RunOrchestrationCenter({
|
|
|
71
52
|
const visibleEntities = new Set([
|
|
72
53
|
...visibleApps,
|
|
73
54
|
...(runView?.actors.filter((actor) => actor.kind !== "human" && actor.lifecycle?.kind !== "stopped"
|
|
74
|
-
&& (
|
|
55
|
+
&& (inspect || actor.kind === "agent")).map((actor) => `@${actor.handle}`) ?? []),
|
|
75
56
|
]);
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
57
|
+
/** Without any stored tree the visible participants are split once; only a real change is saved. */
|
|
58
|
+
const fallbackRoot = useMemo(() => personalLayout === null && programLayout.layout?.root === undefined
|
|
59
|
+
? initialTileLayout([
|
|
60
|
+
...visibleApps,
|
|
61
|
+
...(runView?.actors.filter((actor) => actor.kind !== "human" && actor.lifecycle?.kind !== "stopped" && !hiddenActorIds.includes(actor.id)).map((actor) => `@${actor.handle}`) ?? []),
|
|
62
|
+
])
|
|
63
|
+
: null, [personalLayout, programLayout, visibleApps, runView, hiddenActorIds]);
|
|
64
|
+
const root = presentation.root ?? fallbackRoot;
|
|
65
|
+
const hasVisibleContent = tileEntities(root).some((entity) => visibleEntities.has(entity));
|
|
66
|
+
const stageEntities = useMemo(() => new Set(tileEntities(root)), [root]);
|
|
81
67
|
useEffect(() => publishStageEntities(session.session.id, stageEntities), [session.session.id, stageEntities]);
|
|
82
68
|
useEffect(() => () => publishStageEntities(session.session.id, undefined), [session.session.id]);
|
|
83
69
|
const startupState = canvasStartupState({ view: runView, startup: session.startup, connected: session.connected,
|
|
84
70
|
running: session.running, error: session.conversationError });
|
|
85
71
|
const stageRef = useRef<HTMLDivElement | null>(null);
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
const storageKey = canvasLayoutStorageKey(session.session.id, "camera");
|
|
89
|
-
const [savedCamera] = useState(() => parseCanvasCamera(window.localStorage.getItem(storageKey)));
|
|
90
|
-
const camera = useRef<Camera>(savedCamera ?? { x: INITIAL_INSET, y: INITIAL_INSET, zoom: 1 });
|
|
91
|
-
const pendingCamera = useRef<Camera | undefined>(undefined);
|
|
92
|
-
const saveTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
93
|
-
const flushCamera = useCallback(() => {
|
|
94
|
-
clearTimeout(saveTimer.current);
|
|
95
|
-
if (!pendingCamera.current) return;
|
|
96
|
-
window.localStorage.setItem(storageKey, JSON.stringify(pendingCamera.current));
|
|
97
|
-
pendingCamera.current = undefined;
|
|
98
|
-
}, [storageKey]);
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
window.addEventListener("pagehide", flushCamera);
|
|
101
|
-
return () => { window.removeEventListener("pagehide", flushCamera); flushCamera(); };
|
|
102
|
-
}, [flushCamera]);
|
|
103
|
-
const [overviewCamera, setOverviewCamera] = useState<Camera>();
|
|
104
|
-
const [hasScene, setHasScene] = useState<boolean>();
|
|
105
|
-
const showStartup = !hasVisibleContent && (tiled || hasScene !== true) && startupState !== undefined;
|
|
106
|
-
const overview = overviewCamera !== undefined;
|
|
107
|
-
const overviewButtonRef = useRef<HTMLButtonElement>(null);
|
|
108
|
-
const cameraSettled = useRef(savedCamera !== undefined);
|
|
109
|
-
const boundsRef = useRef<NetBounds | null>(null);
|
|
110
|
-
const zoomOutputRef = useRef<HTMLOutputElement>(null);
|
|
111
|
-
const bindZoomOutput = useCallback((element: HTMLOutputElement | null) => {
|
|
112
|
-
zoomOutputRef.current = element;
|
|
113
|
-
if (element) element.textContent = `${Math.round(camera.current.zoom * 100)}%`;
|
|
114
|
-
}, []);
|
|
115
|
-
useEffect(() => {
|
|
116
|
-
onCenterModeChange(canvasReady ? "workflow" : "chat");
|
|
117
|
-
if (!canvasReady) {
|
|
118
|
-
boundsRef.current = null;
|
|
119
|
-
setOverviewCamera(undefined);
|
|
120
|
-
}
|
|
121
|
-
}, [canvasReady, onCenterModeChange]);
|
|
72
|
+
const showStartup = !hasVisibleContent && startupState !== undefined;
|
|
73
|
+
useEffect(() => { onCenterModeChange(canvasReady ? "workflow" : "chat"); }, [canvasReady, onCenterModeChange]);
|
|
122
74
|
|
|
123
75
|
const visibleStageSize = useCallback((): { width: number; height: number } | undefined => {
|
|
124
76
|
const element = stageRef.current;
|
|
@@ -129,358 +81,28 @@ function RunOrchestrationCenter({
|
|
|
129
81
|
return { width: stage.width - Math.max(0, overlap), height: stage.height };
|
|
130
82
|
}, []);
|
|
131
83
|
|
|
132
|
-
const renderCamera = useCallback((next: Camera, persist = true) => {
|
|
133
|
-
const zoom = Math.min(ZOOM_MAX, Math.max(Number.MIN_VALUE, next.zoom));
|
|
134
|
-
camera.current = { x: next.x, y: next.y, zoom };
|
|
135
|
-
if (persist) {
|
|
136
|
-
pendingCamera.current = camera.current;
|
|
137
|
-
clearTimeout(saveTimer.current);
|
|
138
|
-
saveTimer.current = setTimeout(flushCamera, 150);
|
|
139
|
-
}
|
|
140
|
-
const world = worldRef.current;
|
|
141
|
-
if (world) {
|
|
142
|
-
world.style.setProperty("--cam-x", `${next.x}px`);
|
|
143
|
-
world.style.setProperty("--cam-y", `${next.y}px`);
|
|
144
|
-
world.style.setProperty("--cam-zoom", String(zoom));
|
|
145
|
-
}
|
|
146
|
-
const output = zoomOutputRef.current;
|
|
147
|
-
if (output) output.textContent = `${Math.round(zoom * 100)}%`;
|
|
148
|
-
}, [flushCamera]);
|
|
149
|
-
|
|
150
|
-
const keyboardNavigation = useMemo(() => createKeyboardNavigation(() => camera.current, renderCamera), [renderCamera]);
|
|
151
|
-
const keyboardTarget = useRef<string | undefined>(undefined);
|
|
152
|
-
const cancelKeyboardNavigation = useCallback(() => {
|
|
153
|
-
keyboardNavigation.cancel();
|
|
154
|
-
keyboardTarget.current = undefined;
|
|
155
|
-
}, [keyboardNavigation]);
|
|
156
|
-
useEffect(() => () => cancelKeyboardNavigation(), [cancelKeyboardNavigation]);
|
|
157
|
-
const applyCamera = useCallback((next: Camera, persist = true) => {
|
|
158
|
-
cancelKeyboardNavigation();
|
|
159
|
-
renderCamera(next, persist);
|
|
160
|
-
}, [cancelKeyboardNavigation, renderCamera]);
|
|
161
|
-
|
|
162
|
-
useEffect(() => {
|
|
163
|
-
if (tiled || !canvasReady || overview || hasScene === false) return;
|
|
164
|
-
const onKeyDown = (event: KeyboardEvent) => {
|
|
165
|
-
const element = stageRef.current;
|
|
166
|
-
if (!element || event.defaultPrevented || event.isComposing || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey
|
|
167
|
-
|| element.closest("[inert]") || element.getClientRects().length === 0) return;
|
|
168
|
-
const target = event.target;
|
|
169
|
-
if (target !== document.body && target !== document.documentElement
|
|
170
|
-
&& (!(target instanceof Node) || !element.contains(target) || controlUnder(target, element))) return;
|
|
171
|
-
const size = visibleStageSize();
|
|
172
|
-
if (!size || size.width <= 0 || size.height <= 0) return;
|
|
173
|
-
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)) return;
|
|
174
|
-
event.preventDefault();
|
|
175
|
-
const stage = element.getBoundingClientRect();
|
|
176
|
-
const centerX = stage.left + size.width / 2;
|
|
177
|
-
const centerY = stage.top + size.height / 2;
|
|
178
|
-
const elements = Array.from(element.querySelectorAll<HTMLElement>("[data-canvas-navigation-id]"))
|
|
179
|
-
.filter((candidate) => candidate.getClientRects().length > 0 && !candidate.closest("[hidden], [inert]"))
|
|
180
|
-
.map((candidate): CanvasNavigationBox => {
|
|
181
|
-
const box = candidate.getBoundingClientRect();
|
|
182
|
-
return { id: candidate.dataset.canvasNavigationId!, left: box.left, top: box.top, right: box.right, bottom: box.bottom };
|
|
183
|
-
});
|
|
184
|
-
const current = elements.find((candidate) => candidate.id === keyboardTarget.current)
|
|
185
|
-
?? elements.filter((candidate) => candidate.left <= centerX && candidate.right >= centerX && candidate.top <= centerY && candidate.bottom >= centerY)
|
|
186
|
-
.sort((a, b) => Math.hypot((a.left + a.right) / 2 - centerX, (a.top + a.bottom) / 2 - centerY)
|
|
187
|
-
- Math.hypot((b.left + b.right) / 2 - centerX, (b.top + b.bottom) / 2 - centerY))[0];
|
|
188
|
-
const next = nextCanvasElement(elements, current ?? { left: centerX, right: centerX, top: centerY, bottom: centerY }, event.key, current?.id);
|
|
189
|
-
if (!next) return;
|
|
190
|
-
cameraSettled.current = true;
|
|
191
|
-
if (keyboardNavigation.moveTo({
|
|
192
|
-
x: camera.current.x + centerX - (next.left + next.right) / 2,
|
|
193
|
-
y: camera.current.y + centerY - (next.top + next.bottom) / 2,
|
|
194
|
-
zoom: camera.current.zoom,
|
|
195
|
-
}, event.repeat, window.matchMedia("(prefers-reduced-motion: reduce)").matches)) keyboardTarget.current = next.id;
|
|
196
|
-
};
|
|
197
|
-
const cancel = cancelKeyboardNavigation;
|
|
198
|
-
window.addEventListener("keydown", onKeyDown);
|
|
199
|
-
window.addEventListener("blur", cancel);
|
|
200
|
-
document.addEventListener("focusin", cancel);
|
|
201
|
-
document.addEventListener("pointerdown", cancel, true);
|
|
202
|
-
document.addEventListener("visibilitychange", cancel);
|
|
203
|
-
return () => {
|
|
204
|
-
window.removeEventListener("keydown", onKeyDown);
|
|
205
|
-
window.removeEventListener("blur", cancel);
|
|
206
|
-
document.removeEventListener("focusin", cancel);
|
|
207
|
-
document.removeEventListener("pointerdown", cancel, true);
|
|
208
|
-
document.removeEventListener("visibilitychange", cancel);
|
|
209
|
-
cancelKeyboardNavigation();
|
|
210
|
-
};
|
|
211
|
-
}, [tiled, canvasReady, hasScene, keyboardNavigation, cancelKeyboardNavigation, overview, visibleStageSize]);
|
|
212
|
-
|
|
213
|
-
const bindWorld = useCallback((element: HTMLDivElement | null) => {
|
|
214
|
-
worldRef.current = element;
|
|
215
|
-
if (element) applyCamera(camera.current, false);
|
|
216
|
-
}, [applyCamera]);
|
|
217
|
-
|
|
218
|
-
const fitBounds = useCallback((bounds: NetBounds, { complete = false, initial = false } = {}): boolean => {
|
|
219
|
-
const stage = visibleStageSize();
|
|
220
|
-
if (!stage || stage.width <= 0 || stage.height <= 0) return false;
|
|
221
|
-
const padding = initial ? INITIAL_INSET : 48;
|
|
222
|
-
const width = Math.max(1, bounds.right - bounds.left);
|
|
223
|
-
const height = Math.max(1, bounds.bottom - bounds.top);
|
|
224
|
-
const zoom = Math.max(complete ? Number.MIN_VALUE : ZOOM_MIN, Math.min(
|
|
225
|
-
Math.max(1, stage.width - padding * 2) / width,
|
|
226
|
-
Math.max(1, stage.height - padding * 2) / height,
|
|
227
|
-
1));
|
|
228
|
-
applyCamera({
|
|
229
|
-
x: initial ? padding - bounds.left * zoom : stage.width / 2 - (bounds.left + width / 2) * zoom,
|
|
230
|
-
y: initial ? padding - bounds.top * zoom : stage.height / 2 - (bounds.top + height / 2) * zoom,
|
|
231
|
-
zoom,
|
|
232
|
-
}, !complete);
|
|
233
|
-
return true;
|
|
234
|
-
}, [applyCamera, visibleStageSize]);
|
|
235
|
-
|
|
236
|
-
const fitScene = useCallback(() => {
|
|
237
|
-
if (boundsRef.current && fitBounds(boundsRef.current, { initial: true })) cameraSettled.current = true;
|
|
238
|
-
}, [fitBounds]);
|
|
239
|
-
|
|
240
|
-
const fitInitialScene = useCallback(() => {
|
|
241
|
-
if (!cameraSettled.current) fitScene();
|
|
242
|
-
}, [fitScene]);
|
|
243
|
-
|
|
244
|
-
const centerBounds = useCallback((bounds: NetBounds) => {
|
|
245
|
-
const stage = visibleStageSize();
|
|
246
|
-
if (!stage || stage.width <= 0 || stage.height <= 0) return;
|
|
247
|
-
cameraSettled.current = true;
|
|
248
|
-
setOverviewCamera(undefined);
|
|
249
|
-
applyCamera({
|
|
250
|
-
x: stage.width / 2 - (bounds.left + bounds.right) / 2,
|
|
251
|
-
y: stage.height / 2 - (bounds.top + bounds.bottom) / 2,
|
|
252
|
-
zoom: 1,
|
|
253
|
-
});
|
|
254
|
-
}, [applyCamera, visibleStageSize]);
|
|
255
|
-
|
|
256
|
-
const resetScene = useCallback(() => {
|
|
257
|
-
if (boundsRef.current) centerBounds(boundsRef.current);
|
|
258
|
-
}, [centerBounds]);
|
|
259
|
-
|
|
260
|
-
const closeOverview = useCallback(() => {
|
|
261
|
-
if (!overviewCamera) return;
|
|
262
|
-
applyCamera(overviewCamera);
|
|
263
|
-
setOverviewCamera(undefined);
|
|
264
|
-
overviewButtonRef.current?.focus({ preventScroll: true });
|
|
265
|
-
}, [applyCamera, overviewCamera]);
|
|
266
|
-
|
|
267
|
-
const toggleOverview = useCallback(() => {
|
|
268
|
-
if (overviewCamera) { closeOverview(); return; }
|
|
269
|
-
const previous = camera.current;
|
|
270
|
-
if (!boundsRef.current || !fitBounds(boundsRef.current, { complete: true })) return;
|
|
271
|
-
cameraSettled.current = true;
|
|
272
|
-
setOverviewCamera(previous);
|
|
273
|
-
}, [closeOverview, fitBounds, overviewCamera]);
|
|
274
|
-
|
|
275
|
-
const focusOverviewTarget = useCallback((bounds: NetBounds) => {
|
|
276
|
-
centerBounds(bounds);
|
|
277
|
-
overviewButtonRef.current?.focus({ preventScroll: true });
|
|
278
|
-
}, [centerBounds]);
|
|
279
|
-
|
|
280
|
-
useEffect(() => {
|
|
281
|
-
if (!overview) return;
|
|
282
|
-
const onKeyDown = (event: KeyboardEvent) => {
|
|
283
|
-
if (event.key !== "Escape" || event.defaultPrevented || stageRef.current?.closest("[inert]")) return;
|
|
284
|
-
event.preventDefault();
|
|
285
|
-
closeOverview();
|
|
286
|
-
};
|
|
287
|
-
window.addEventListener("keydown", onKeyDown);
|
|
288
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
289
|
-
}, [closeOverview, overview]);
|
|
290
|
-
|
|
291
|
-
const requestedEntity = navigation.selectionFor(ORCHESTRATION_TAB_ID);
|
|
292
|
-
useEffect(() => {
|
|
293
|
-
if (tiled || !requestedEntity || typeof requestedEntity !== "object"
|
|
294
|
-
|| !("type" in requestedEntity) || !("id" in requestedEntity)) return;
|
|
295
|
-
const frame = requestAnimationFrame(() => {
|
|
296
|
-
const stage = stageRef.current;
|
|
297
|
-
const element = Array.from(stage?.querySelectorAll<HTMLElement>("[data-canvas-entity-id]") ?? [])
|
|
298
|
-
.find((candidate) => candidate.dataset.canvasEntityId === requestedEntity.id
|
|
299
|
-
&& candidate.dataset.canvasEntityType === requestedEntity.type);
|
|
300
|
-
if (!stage || !element) return;
|
|
301
|
-
const bounds = element.getBoundingClientRect();
|
|
302
|
-
const viewport = stage.getBoundingClientRect();
|
|
303
|
-
const { x, y, zoom } = camera.current;
|
|
304
|
-
const left = (bounds.left - viewport.left - x) / zoom;
|
|
305
|
-
const top = (bounds.top - viewport.top - y) / zoom;
|
|
306
|
-
if (fitBounds({ left, top, right: left + bounds.width / zoom, bottom: top + bounds.height / zoom })) {
|
|
307
|
-
cameraSettled.current = true;
|
|
308
|
-
setOverviewCamera(undefined);
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
return () => cancelAnimationFrame(frame);
|
|
312
|
-
}, [tiled, requestedEntity, fitBounds]);
|
|
313
|
-
|
|
314
|
-
const zoomAtStagePoint = useCallback((deltaY: number, deltaMode: number, stageX: number, stageY: number, sensitivity = 1) => {
|
|
315
|
-
const current = camera.current;
|
|
316
|
-
const worldX = (stageX - current.x) / current.zoom;
|
|
317
|
-
const worldY = (stageY - current.y) / current.zoom;
|
|
318
|
-
const zoom = Math.min(ZOOM_MAX, Math.max(Math.min(ZOOM_MIN, current.zoom), current.zoom * Math.exp(-wheelPixels(deltaY, deltaMode) * 0.0007 * sensitivity)));
|
|
319
|
-
applyCamera({ x: stageX - worldX * zoom, y: stageY - worldY * zoom, zoom });
|
|
320
|
-
}, [applyCamera]);
|
|
321
|
-
|
|
322
|
-
const panBy = useCallback((dx: number, dy: number) => {
|
|
323
|
-
const current = camera.current;
|
|
324
|
-
applyCamera({ x: current.x + dx, y: current.y + dy, zoom: current.zoom });
|
|
325
|
-
}, [applyCamera]);
|
|
326
|
-
|
|
327
|
-
const zoomStep = useCallback((direction: number) => {
|
|
328
|
-
const stage = visibleStageSize();
|
|
329
|
-
if (!stage || stage.width <= 0 || stage.height <= 0) return;
|
|
330
|
-
cameraSettled.current = true;
|
|
331
|
-
setOverviewCamera(undefined);
|
|
332
|
-
zoomAtStagePoint(direction > 0 ? -400 : 400, 0, stage.width / 2, stage.height / 2);
|
|
333
|
-
}, [visibleStageSize, zoomAtStagePoint]);
|
|
334
|
-
|
|
335
84
|
useEffect(() => {
|
|
336
85
|
const element = stageRef.current;
|
|
337
86
|
if (!element) return;
|
|
338
87
|
const observer = new ResizeObserver(() => {
|
|
339
88
|
const size = visibleStageSize();
|
|
340
89
|
if (size) element.style.setProperty("--canvas-visible-width", `${size.width}px`);
|
|
341
|
-
if (tiled) return;
|
|
342
|
-
if (overview && boundsRef.current) fitBounds(boundsRef.current, { complete: true });
|
|
343
|
-
else fitInitialScene();
|
|
344
90
|
});
|
|
345
91
|
observer.observe(element);
|
|
346
92
|
const panel = document.getElementById("workspace-panel");
|
|
347
93
|
if (panel) observer.observe(panel);
|
|
348
94
|
return () => observer.disconnect();
|
|
349
|
-
}, [
|
|
95
|
+
}, [visibleStageSize]);
|
|
350
96
|
|
|
351
|
-
const
|
|
352
|
-
const visible = bounds.right > bounds.left || bounds.bottom > bounds.top;
|
|
353
|
-
setHasScene(visible);
|
|
354
|
-
if (!visible) {
|
|
355
|
-
boundsRef.current = null;
|
|
356
|
-
setOverviewCamera(undefined);
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
boundsRef.current = bounds;
|
|
360
|
-
if (overview) fitBounds(bounds, { complete: true });
|
|
361
|
-
else fitInitialScene();
|
|
362
|
-
}, [fitBounds, fitInitialScene, overview]);
|
|
97
|
+
const requestedEntity = navigation.selectionFor(ORCHESTRATION_TAB_ID);
|
|
363
98
|
|
|
364
99
|
const handleSelect = useCallback((selection: EntityReference) => {
|
|
365
100
|
canvas?.acceptSelection(selection);
|
|
366
101
|
}, [canvas]);
|
|
367
102
|
|
|
368
|
-
const
|
|
369
|
-
const bindStage = useCallback((element: HTMLDivElement | null) => {
|
|
370
|
-
stageCleanup.current?.();
|
|
371
|
-
stageCleanup.current = undefined;
|
|
372
|
-
stageRef.current = element;
|
|
373
|
-
if (!element || tiled) return;
|
|
374
|
-
let panCleanup: (() => void) | undefined;
|
|
375
|
-
|
|
376
|
-
const onWheel = (event: WheelEvent) => {
|
|
377
|
-
if (event.defaultPrevented || (!overview && controlUnder(event.target, element))) return;
|
|
378
|
-
event.preventDefault();
|
|
379
|
-
setOverviewCamera(undefined);
|
|
380
|
-
const rect = element.getBoundingClientRect();
|
|
381
|
-
cameraSettled.current = true;
|
|
382
|
-
zoomAtStagePoint(event.deltaY, event.deltaMode, event.clientX - rect.left, event.clientY - rect.top,
|
|
383
|
-
event.ctrlKey ? pinchZoomSensitivity : 1);
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
const onPointerDown = (event: PointerEvent) => {
|
|
387
|
-
if (overview) return;
|
|
388
|
-
if (event.defaultPrevented || controlUnder(event.target, element)) return;
|
|
389
|
-
const middleButton = event.button === 1;
|
|
390
|
-
const leftButton = event.button === 0;
|
|
391
|
-
if (!middleButton && !leftButton) return;
|
|
392
|
-
event.preventDefault();
|
|
393
|
-
cancelKeyboardNavigation();
|
|
394
|
-
element.focus({ preventScroll: true });
|
|
395
|
-
panCleanup?.();
|
|
396
|
-
const startX = event.clientX;
|
|
397
|
-
const startY = event.clientY;
|
|
398
|
-
let lastX = event.clientX;
|
|
399
|
-
let lastY = event.clientY;
|
|
400
|
-
let dragged = false;
|
|
401
|
-
let active = true;
|
|
402
|
-
element.setPointerCapture(event.pointerId);
|
|
403
|
-
const move = (moved: PointerEvent) => {
|
|
404
|
-
if (!active || moved.pointerId !== event.pointerId) return;
|
|
405
|
-
dragged ||= Math.hypot(moved.clientX - startX, moved.clientY - startY) > 4;
|
|
406
|
-
cameraSettled.current = true;
|
|
407
|
-
panBy(moved.clientX - lastX, moved.clientY - lastY);
|
|
408
|
-
lastX = moved.clientX;
|
|
409
|
-
lastY = moved.clientY;
|
|
410
|
-
};
|
|
411
|
-
const cleanup = () => {
|
|
412
|
-
if (!active) return;
|
|
413
|
-
active = false;
|
|
414
|
-
element.removeEventListener("pointermove", move);
|
|
415
|
-
element.removeEventListener("pointerup", end);
|
|
416
|
-
element.removeEventListener("pointercancel", end);
|
|
417
|
-
element.removeEventListener("lostpointercapture", end);
|
|
418
|
-
if (element.hasPointerCapture(event.pointerId)) element.releasePointerCapture(event.pointerId);
|
|
419
|
-
panCleanup = undefined;
|
|
420
|
-
};
|
|
421
|
-
const end = (ended: PointerEvent) => {
|
|
422
|
-
if (!active || ended.pointerId !== event.pointerId) return;
|
|
423
|
-
cleanup();
|
|
424
|
-
if (ended.type === "pointerup" && leftButton && !dragged) selectionHandler.current(undefined);
|
|
425
|
-
};
|
|
426
|
-
panCleanup = cleanup;
|
|
427
|
-
element.addEventListener("pointermove", move);
|
|
428
|
-
element.addEventListener("pointerup", end);
|
|
429
|
-
element.addEventListener("pointercancel", end);
|
|
430
|
-
element.addEventListener("lostpointercapture", end);
|
|
431
|
-
};
|
|
432
|
-
|
|
433
|
-
const onDoubleClick = (event: MouseEvent) => {
|
|
434
|
-
if (overview) return;
|
|
435
|
-
if (event.defaultPrevented || controlUnder(event.target, element)) return;
|
|
436
|
-
resetScene();
|
|
437
|
-
};
|
|
438
|
-
|
|
439
|
-
const onMiddleDown = (event: MouseEvent) => {
|
|
440
|
-
if (event.defaultPrevented || controlUnder(event.target, element)) return;
|
|
441
|
-
if (event.button === 1) event.preventDefault();
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
const onScroll = () => {
|
|
445
|
-
element.scrollLeft = 0;
|
|
446
|
-
element.scrollTop = 0;
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
element.addEventListener("wheel", onWheel, { passive: false });
|
|
450
|
-
element.addEventListener("pointerdown", onPointerDown);
|
|
451
|
-
element.addEventListener("dblclick", onDoubleClick);
|
|
452
|
-
element.addEventListener("mousedown", onMiddleDown);
|
|
453
|
-
element.addEventListener("auxclick", onMiddleDown);
|
|
454
|
-
element.addEventListener("scroll", onScroll);
|
|
455
|
-
onScroll();
|
|
456
|
-
stageCleanup.current = () => {
|
|
457
|
-
panCleanup?.();
|
|
458
|
-
element.removeEventListener("wheel", onWheel);
|
|
459
|
-
element.removeEventListener("pointerdown", onPointerDown);
|
|
460
|
-
element.removeEventListener("dblclick", onDoubleClick);
|
|
461
|
-
element.removeEventListener("mousedown", onMiddleDown);
|
|
462
|
-
element.removeEventListener("auxclick", onMiddleDown);
|
|
463
|
-
element.removeEventListener("scroll", onScroll);
|
|
464
|
-
};
|
|
465
|
-
}, [tiled, resetScene, overview, panBy, pinchZoomSensitivity, zoomAtStagePoint, cancelKeyboardNavigation]);
|
|
466
|
-
|
|
467
|
-
useEffect(() => {
|
|
468
|
-
if (!tiled) return;
|
|
469
|
-
cancelKeyboardNavigation();
|
|
470
|
-
if (overviewCamera) {
|
|
471
|
-
applyCamera(overviewCamera);
|
|
472
|
-
setOverviewCamera(undefined);
|
|
473
|
-
}
|
|
474
|
-
}, [tiled, overviewCamera, applyCamera, cancelKeyboardNavigation]);
|
|
475
|
-
|
|
476
|
-
const changeMode = (mode: "free" | "tiled") => {
|
|
477
|
-
if (mode === presentation.mode) return;
|
|
478
|
-
const entities = [
|
|
479
|
-
...canvasElements.flatMap((contribution) => contribution.select(session).filter((element) => element.visible !== false).map((element) => `app:${element.id}`)),
|
|
480
|
-
...(runView?.actors.filter((actor) => actor.kind !== "human" && actor.lifecycle?.kind !== "stopped" && !hiddenActorIds.includes(actor.id)).map((actor) => `@${actor.handle}`) ?? []),
|
|
481
|
-
];
|
|
103
|
+
const changeTiles = (next: CanvasTileNode | null) => {
|
|
482
104
|
try {
|
|
483
|
-
saveCanvasPresentation(session.session.id, {
|
|
105
|
+
saveCanvasPresentation(session.session.id, { root: next, programBasis });
|
|
484
106
|
setPresentationError(undefined);
|
|
485
107
|
} catch (error) {
|
|
486
108
|
setPresentationError(error instanceof Error ? error.message : String(error));
|
|
@@ -488,69 +110,23 @@ function RunOrchestrationCenter({
|
|
|
488
110
|
};
|
|
489
111
|
|
|
490
112
|
return (
|
|
491
|
-
<div className="relative flex h-full min-h-0 min-w-0 flex-1 overflow-clip" data-view=
|
|
492
|
-
{
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
aria-pressed={overview}
|
|
496
|
-
className="inline-flex size-header min-h-header items-center justify-center border-r border-border bg-transparent text-primary transition-colors hover:not-disabled:bg-accent focus-visible:outline-2 focus-visible:outline-primary focus-visible:-outline-offset-3 aria-expanded:bg-primary aria-expanded:text-primary-foreground aria-pressed:bg-primary aria-pressed:text-primary-foreground disabled:opacity-45"
|
|
497
|
-
disabled={tiled || !canvasReady || hasScene === false}
|
|
498
|
-
onClick={toggleOverview}
|
|
499
|
-
ref={overviewButtonRef}
|
|
500
|
-
title={tiled ? "Die Canvas-Übersicht ist im Kachelmodus nicht verfügbar" : overview ? "Canvas-Übersicht beenden (Escape)" : "Alle Elemente anzeigen und anspringen"}
|
|
501
|
-
type="button"
|
|
502
|
-
><Scan aria-hidden size={22} /></button>, toolbarContainer)}
|
|
503
|
-
<StatusGroup container={statusContainer} label="Diagramm steuern" order={10}>
|
|
504
|
-
<button aria-label="Verkleinern" className={statusControlClass} disabled={tiled} onClick={() => zoomStep(-1)} type="button">-</button>
|
|
505
|
-
<output aria-label={tiled ? "Feste Kachelfläche" : "Zoomstufe"} className={statusOutputClass} ref={tiled ? undefined : bindZoomOutput}>{tiled ? "Fest" : "100%"}</output>
|
|
506
|
-
<button aria-label="Vergrößern" className={statusControlClass} disabled={tiled} onClick={() => zoomStep(1)} type="button">+</button>
|
|
507
|
-
<button className={statusControlClass} disabled={tiled} onClick={resetScene} title="Inhalt bei 100 Prozent zentrieren" type="button">Einpassen</button>
|
|
508
|
-
</StatusGroup>
|
|
509
|
-
<StatusGroup container={statusContainer} label="Flächenmodus" order={20}>
|
|
510
|
-
<button className={statusControlClass} aria-pressed={!tiled} onClick={() => changeMode("free")} type="button">Frei</button>
|
|
511
|
-
<button className={statusControlClass} aria-pressed={tiled} onClick={() => changeMode("tiled")} type="button">Kacheln</button>
|
|
512
|
-
{personalLayout && <button className={statusControlClass} onClick={() => saveCanvasPresentation(session.session.id, null)}
|
|
513
|
-
title="Eigene Anordnung zurücksetzen und der Programmanordnung folgen" type="button">Programmvorgabe übernehmen</button>}
|
|
514
|
-
</StatusGroup>
|
|
515
|
-
{inspect && !tiled && <StatusGroup container={statusContainer} label="Canvas-Ansicht" order={30}>
|
|
516
|
-
<ActorCanvasControls control="view" session={session} appActorIds={appActorIds} preferences={preferences}
|
|
517
|
-
onPreferencesChange={(next) => saveCanvasViewPreferences(session.session.id, next)} />
|
|
113
|
+
<div className="relative flex h-full min-h-0 min-w-0 flex-1 overflow-clip" data-view="tiled">
|
|
114
|
+
{personalLayout && <StatusGroup container={statusContainer} label="Kachelfläche" order={20}>
|
|
115
|
+
<button className={statusControlClass} onClick={() => saveCanvasPresentation(session.session.id, null)}
|
|
116
|
+
title="Eigene Anordnung zurücksetzen und der Programmanordnung folgen" type="button">Programmvorgabe übernehmen</button>
|
|
518
117
|
</StatusGroup>}
|
|
519
|
-
{runToolbarContainer && createPortal(<><ActorHeaderModeControl runId={session.session.id} />{inspect && <ActorCanvasControls
|
|
118
|
+
{runToolbarContainer && createPortal(<><ActorHeaderModeControl runId={session.session.id} />{inspect && <ActorCanvasControls session={session}
|
|
520
119
|
appActorIds={appActorIds} preferences={preferences}
|
|
521
120
|
onPreferencesChange={(next) => saveCanvasViewPreferences(session.session.id, next)} />}</>, runToolbarContainer)}
|
|
522
|
-
<div aria-label=
|
|
121
|
+
<div aria-label="Kachelfläche" className="relative min-h-0 min-w-0 flex-[1_1_360px] overflow-clip bg-[image:var(--canvas-backdrop)]" ref={stageRef}>
|
|
523
122
|
<CanvasGrain />
|
|
524
123
|
{(programLayout.error || presentationError) && <p className="absolute inset-x-2 top-2 z-10 bg-secondary p-3 text-destructive" role="alert">{programLayout.error || presentationError}</p>}
|
|
525
|
-
{
|
|
526
|
-
<CanvasTiles root={
|
|
124
|
+
{runView && !showStartup && <div className="absolute inset-y-0 left-0 w-[var(--canvas-visible-width,100%)] overflow-hidden">
|
|
125
|
+
<CanvasTiles root={root} onChange={changeTiles}
|
|
527
126
|
canvasElements={canvasElements} cardSections={cardSections} session={session} view={runView} navigation={navigation} onSelect={handleSelect}
|
|
528
127
|
selected={requestedEntity && typeof requestedEntity === "object" && "type" in requestedEntity && "id" in requestedEntity ? requestedEntity as EntityReference : undefined} />
|
|
529
128
|
</div>}
|
|
530
|
-
{!tiled && <div className="absolute top-0 left-0 h-0 w-0 origin-top-left overflow-visible [transform:translate(var(--cam-x,0px),var(--cam-y,0px))_scale(var(--cam-zoom,1))]" ref={bindWorld}>
|
|
531
|
-
{canvasReady && runView && (
|
|
532
|
-
<NetLayer
|
|
533
|
-
cardSections={cardSections}
|
|
534
|
-
canvasElements={canvasElements}
|
|
535
|
-
hiddenActorIds={hiddenActorIds}
|
|
536
|
-
showConnections={inspect && preferences.showConnections}
|
|
537
|
-
navigation={navigation}
|
|
538
|
-
onLayout={handleLayout}
|
|
539
|
-
onSelect={handleSelect}
|
|
540
|
-
onOverviewTarget={overview ? focusOverviewTarget : undefined}
|
|
541
|
-
selectedActorId={canvas?.selection?.type === "actor" ? canvas.selection.id : undefined}
|
|
542
|
-
session={session}
|
|
543
|
-
view={runView}
|
|
544
|
-
/>
|
|
545
|
-
)}
|
|
546
|
-
</div>}
|
|
547
|
-
{!tiled && overview && <p className="pointer-events-none absolute top-3 left-3 max-w-[calc(var(--canvas-visible-width,100%)-24px)] rounded-lg border border-border bg-card px-3 py-2 text-[.75rem] text-foreground" role="status">Element anklicken zum Anspringen. Escape bricht ab.</p>}
|
|
548
129
|
{showStartup && <CanvasStartup state={startupState} />}
|
|
549
|
-
{!showStartup && !tiled && (!canvasReady || hasScene === false) && (
|
|
550
|
-
<p className="pointer-events-none absolute inset-0 grid place-items-center text-[0.85rem] text-muted-foreground">{canvasReady
|
|
551
|
-
? "Keine Elemente eingeblendet. Über Actors oder Ansicht kannst du die Darstellung ändern."
|
|
552
|
-
: "Die Fläche füllt sich, sobald der Run Actors hat."}</p>
|
|
553
|
-
)}
|
|
554
130
|
</div>
|
|
555
131
|
</div>
|
|
556
132
|
);
|
|
@@ -563,14 +139,3 @@ function CanvasGrain() {
|
|
|
563
139
|
<path d="M0 0h180v180H0z" fill="#887890" filter="url(#canvas-grain)" opacity=".32" />
|
|
564
140
|
</svg>;
|
|
565
141
|
}
|
|
566
|
-
|
|
567
|
-
function controlUnder(target: EventTarget | null, limit: HTMLElement): boolean {
|
|
568
|
-
for (let node = target instanceof Element ? target : null; node; node = node.parentElement) {
|
|
569
|
-
if (node.matches("[data-canvas-card], [data-canvas-scroll], iframe, a, button, input, select, textarea, [contenteditable], [role=button], [role=slider], [role=spinbutton]")) return true;
|
|
570
|
-
const style = getComputedStyle(node);
|
|
571
|
-
if ((style.overflowY === "auto" || style.overflowY === "scroll")
|
|
572
|
-
|| (style.overflowX === "auto" || style.overflowX === "scroll")) return true;
|
|
573
|
-
if (node === limit) break;
|
|
574
|
-
}
|
|
575
|
-
return false;
|
|
576
|
-
}
|