(0);
+ // Subscribe to theme changes from Python
+ useEffect(() => {
+ if (!model) return;
+
+ // Read initial theme
+ const initialTheme = model.get("theme") || "light";
+ setTheme(initialTheme);
+
+ // Listen for theme changes
+ const handleThemeChange = () => {
+ const newTheme = model.get("theme") || "light";
+ setTheme(newTheme);
+ };
+
+ model.on("change:theme", handleThemeChange);
+
+ return () => {
+ model.off("change:theme", handleThemeChange);
+ };
+ }, [model]);
+
// Subscribe to diagram state from Python (like DiagramCanvas does)
useEffect(() => {
if (!model) return;
// Initial load
const initialState = getDiagramState(model);
- console.log("[CaptureCanvas] Initial state:", initialState.blocks.length, "blocks");
setNodes(initialState.blocks.map(blockToNode));
- setEdges(initialState.connections.map(connectionToEdge));
+ setEdges(
+ initialState.connections.map((conn) => connectionToEdge(conn, "var(--color-primary-600)"))
+ );
// Subscribe to changes (in case state updates after mount)
const unsubscribe = onDiagramStateChange(model, (state: DiagramState) => {
- console.log("[CaptureCanvas] State updated:", state.blocks.length, "blocks");
setNodes(state.blocks.map(blockToNode));
- setEdges(state.connections.map(connectionToEdge));
+ setEdges(state.connections.map((conn) => connectionToEdge(conn, "var(--color-primary-600)")));
});
return unsubscribe;
@@ -349,7 +292,6 @@ export default function CaptureCanvas() {
if (request.timestamp <= lastTimestamp.current) return;
lastTimestamp.current = request.timestamp;
- console.log("[CaptureCanvas] Received capture request:", request);
setCaptureRequest(request);
};
@@ -372,21 +314,14 @@ export default function CaptureCanvas() {
(result: CaptureResult) => {
if (!model) return;
- console.log("[CaptureCanvas] Capture complete:", result.success ? "success" : result.error);
-
if (result.success && result.data) {
const displayInline = captureRequest?.displayInline ?? true;
const mimeType = result.format === "png" ? "image/png" : "image/svg+xml";
if (displayInline) {
// Display inline - inject
into the widget container
- console.log("[CaptureCanvas] Displaying inline");
- console.log("[CaptureCanvas] outerRef.current:", outerRef.current);
-
- // Find the anywidget container (parent of our React root)
// Navigate up from our ref to find the .lynx-widget container
const lynxWidget = outerRef.current?.closest(".lynx-widget");
- console.log("[CaptureCanvas] Found .lynx-widget:", lynxWidget);
if (lynxWidget) {
// Create image element
@@ -418,17 +353,10 @@ export default function CaptureCanvas() {
}
parent = parent.parentElement;
}
-
- console.log("[CaptureCanvas] Image injected successfully");
} else {
- console.error("[CaptureCanvas] Could not find .lynx-widget container");
- // Fallback: try to find any parent and log the DOM structure
- let parent = outerRef.current?.parentElement;
- console.log("[CaptureCanvas] Parent chain:");
- while (parent) {
- console.log(" -", parent.tagName, parent.className);
- parent = parent.parentElement;
- }
+ console.error(
+ "[CaptureCanvas] Could not find .lynx-widget container for inline display"
+ );
}
}
}
@@ -446,6 +374,7 @@ export default function CaptureCanvas() {
return (
{
+ const waypoints = edge.data?.waypoints;
+ if (waypoints && Array.isArray(waypoints)) {
+ waypoints.forEach((waypoint: { x: number; y: number }) => {
+ minX = Math.min(minX, waypoint.x);
+ minY = Math.min(minY, waypoint.y);
+ maxX = Math.max(maxX, waypoint.x);
+ maxY = Math.max(maxY, waypoint.y);
+ });
+ }
+ });
+
+ // Recalculate bounds including edges
+ const combinedBounds = {
+ x: minX,
+ y: minY,
+ width: maxX - minX,
+ height: maxY - minY,
+ };
// Add padding
const paddedBounds: ContentBounds = {
- x: bounds.x - DEFAULT_PADDING,
- y: bounds.y - DEFAULT_PADDING,
- width: bounds.width + DEFAULT_PADDING * 2,
- height: bounds.height + DEFAULT_PADDING * 2,
+ x: combinedBounds.x - DEFAULT_PADDING,
+ y: combinedBounds.y - DEFAULT_PADDING,
+ width: combinedBounds.width + DEFAULT_PADDING * 2,
+ height: combinedBounds.height + DEFAULT_PADDING * 2,
};
// If both dimensions specified, use them directly
@@ -112,23 +141,25 @@ function createCaptureFilter() {
* @param width - Output width in pixels
* @param height - Output height in pixels
* @param transparent - If true, background is transparent
+ * @param backgroundColor - Background color (actual hex/rgb value, not CSS variable)
* @returns Base64-encoded PNG data (without data URL prefix)
*/
export async function captureToPng(
element: HTMLElement,
width: number,
height: number,
- transparent: boolean
+ transparent: boolean,
+ backgroundColor: string
): Promise {
const dataUrl = await toPng(element, {
width,
height,
- backgroundColor: transparent ? undefined : "var(--color-slate-200)",
+ backgroundColor: transparent ? undefined : backgroundColor,
pixelRatio: 2, // 2x resolution for crisp output
filter: createCaptureFilter(),
// Force the captured element itself to have the background
style: {
- background: transparent ? "transparent" : "var(--color-slate-200)",
+ background: transparent ? "transparent" : backgroundColor,
},
});
diff --git a/js/src/palette/BlockPalette.tsx b/js/src/palette/BlockPalette.tsx
index b833dc1..99ca43c 100644
--- a/js/src/palette/BlockPalette.tsx
+++ b/js/src/palette/BlockPalette.tsx
@@ -131,9 +131,7 @@ export default function BlockPalette() {
{/* Transfer Function Block */}