Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions src/component_info_sidebar/ComponentPreviewWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { collectParamIO } from './portPreview';
import { IONodeTree } from './IONodeTree';
import type { IONode } from './portPreview';
import { commandIDs } from "../commands/CommandIDs";
import { canvasUpdatedSignal } from '../components/XircuitsBodyWidget';

export interface IComponentInfo {
name: string;
Expand Down Expand Up @@ -156,7 +157,7 @@ class OverviewSection extends ReactWidget {
}
setModel(m: IComponentInfo | null) {
this._model = m;
this.update();
this.update();
}
render(): JSX.Element {
if (!this._model) {
Expand Down Expand Up @@ -321,6 +322,7 @@ export class ComponentPreviewWidget extends SidePanel {
const shell = this._app.shell as ILabShell;
shell.expandRight();
shell.activateById(this.id);
this._bindCanvasListener();
}

private _computeToolbarState(): ToolbarState {
Expand Down Expand Up @@ -357,8 +359,52 @@ export class ComponentPreviewWidget extends SidePanel {
canOpenScript: !!m.node && !isStartFinish,
canCenter: !!(m.node && m.engine),
canOpenWorkflow: nodeType === 'xircuits_workflow',
canCollapse: !isStartFinish
canCollapse: !isStartFinish
};
}

private _isListening = false;

private _bindCanvasListener(): void {
if (this._isListening || this.isDisposed) return;

const onCanvasUpdate = () => {
const engine = this._model?.engine;
const currentNode = this._model?.node;
if (!engine || !currentNode) return;

// Skip updating sidebar if a link is still being dragged (incomplete connection)
const hasUnfinishedLink = Object.values(engine.getModel()?.getLinks?.() ?? {}).some(
(link: any) => !link.getTargetPort?.()
);
if (hasUnfinishedLink) return;

// Refresh node reference in case the model recreated it after a change
const id = currentNode.getID?.();
const latestNode = engine.getModel?.().getNodes?.().find(n => n.getID?.() === id);
if (latestNode && latestNode !== currentNode) {
this._model!.node = latestNode;
}

try {
const { inputs = [], outputs = [] } = collectParamIO(this._model!.node as any);
this._inputs.setData(inputs);
this._outputs.setData(outputs);
} catch (err) {
console.warn('[Sidebar] Failed to collect I/O, keeping previous state:', err);
}


this._topbar?.update();
};

canvasUpdatedSignal.connect(onCanvasUpdate, this);
this._isListening = true;

this.disposed.connect(() => {
canvasUpdatedSignal.disconnect(onCanvasUpdate, this);
this._isListening = false;
});
}

private _navigate(step: -1 | 1) {
Expand Down
7 changes: 7 additions & 0 deletions src/components/XircuitsBodyWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ const ZoomControls = styled.div<{visible: boolean}>`
}
`;

export type CanvasUpdatedPayload = { reason: 'content'; };

export const canvasUpdatedSignal = new Signal<Window, CanvasUpdatedPayload>(window);

export const BodyWidget: FC<BodyWidgetProps> = ({
context,
xircuitsApp,
Expand Down Expand Up @@ -471,6 +475,7 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
return () => clearTimeout(timeout);
},
linksUpdated: (event) => {
canvasUpdatedSignal.emit({ reason: 'content' });
const timeout = setTimeout(() => {
event.link.registerListener({
sourcePortChanged: () => {
Expand All @@ -494,6 +499,8 @@ export const BodyWidget: FC<BodyWidgetProps> = ({
xircuitsApp.getDiagramEngine().setModel(deserializedModel);
clearSearchFlags();

canvasUpdatedSignal.emit({ reason: 'content' });

// On the first load, clear undo history and register global engine listeners
if (initialRender.current) {
currentContext.model.sharedModel.clearUndoHistory();
Expand Down