Skip to content

Commit 0559302

Browse files
authored
fix: fix resolve unstable state on empty graph (#151)
1 parent 2c30bba commit 0559302

File tree

4 files changed

+76
-16
lines changed

4 files changed

+76
-16
lines changed

src/graph.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,17 @@ export type TGraphConfig<Block extends TBlock = TBlock, Connection extends TConn
2828
configurationName?: string;
2929
blocks?: Block[];
3030
connections?: TConnection[];
31+
/**
32+
* @deprecated use Graph.zoom api
33+
*/
3134
rect?: TRect;
35+
/**
36+
* @deprecated use Graph.zoom api
37+
* */
3238
cameraXY?: TPoint;
39+
/**
40+
* @deprecated use Graph.zoom api
41+
* */
3342
cameraScale?: number;
3443
settings?: Partial<TGraphSettingsConfig<Block, Connection>>;
3544
layers?: LayerConfig[];
@@ -56,7 +65,7 @@ export class Graph {
5665

5766
public rootStore: RootStore = new RootStore(this);
5867

59-
public hitTest = new HitTest();
68+
public hitTest = new HitTest(this);
6069

6170
protected graphLayer: GraphLayer;
6271

src/services/HitTest.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { signal } from "@preact/signals-core";
22
import RBush from "rbush";
33

4+
import { Graph } from "../graph";
45
import { ESchedulerPriority } from "../lib";
56
import { Component } from "../lib/Component";
67
import { Emitter } from "../utils/Emitter";
@@ -61,17 +62,40 @@ export class HitTest extends Emitter {
6162
// Single queue replaces all complex state tracking
6263
protected queue = new Map<HitBox, HitBoxData | null>();
6364

64-
public get isUnstable() {
65+
constructor(protected graph: Graph) {
66+
super();
67+
}
68+
69+
/**
70+
* Check if graph has any elements (blocks or connections)
71+
* @returns true if graph has elements, false if empty
72+
*/
73+
private hasGraphElements(): boolean {
74+
if (!this.graph) {
75+
return false;
76+
}
6577
return (
66-
this.processQueue.isScheduled() ||
67-
this.queue.size > 0 ||
68-
(this.$usableRect.value.height === 0 &&
69-
this.$usableRect.value.width === 0 &&
70-
this.$usableRect.value.x === 0 &&
71-
this.$usableRect.value.y === 0)
78+
this.graph.rootStore.blocksList.$blocks.value.length > 0 ||
79+
this.graph.rootStore.connectionsList.$connections.value.length > 0
7280
);
7381
}
7482

83+
public get isUnstable() {
84+
const hasProcessingQueue = this.processQueue.isScheduled() || this.queue.size > 0;
85+
const hasZeroUsableRect =
86+
this.$usableRect.value.height === 0 &&
87+
this.$usableRect.value.width === 0 &&
88+
this.$usableRect.value.x === 0 &&
89+
this.$usableRect.value.y === 0;
90+
91+
// If graph has no elements, it's stable even with zero usableRect
92+
if (hasZeroUsableRect && !this.hasGraphElements()) {
93+
return hasProcessingQueue;
94+
}
95+
96+
return hasProcessingQueue || hasZeroUsableRect;
97+
}
98+
7599
/**
76100
* Load array of HitBox items
77101
* @param items Array of HitBox items to load
@@ -172,6 +196,12 @@ export class HitTest extends Emitter {
172196
* @returns Unsubscribe function
173197
*/
174198
public waitUsableRectUpdate(callback: (rect: TRect) => void): () => void {
199+
// For empty graphs, immediately call callback with current usableRect
200+
if (!this.hasGraphElements()) {
201+
callback(this.$usableRect.value);
202+
return noop;
203+
}
204+
175205
if (this.isUnstable) {
176206
const removeListener = this.$usableRect.subscribe(() => {
177207
if (!this.isUnstable) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { TGraphConfig } from "../../graph";
2+
3+
import { storiesSettings } from "./definitions";
4+
5+
export const emptyGraphConfig: TGraphConfig = {
6+
configurationName: "emptyGraph",
7+
blocks: [],
8+
connections: [],
9+
cameraScale: 1,
10+
rect: {
11+
x: -400,
12+
y: -300,
13+
width: 800,
14+
height: 600,
15+
},
16+
settings: storiesSettings,
17+
};

src/stories/main/GraphEditor.stories.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { CustomLayerConfig } from "../configurations/CustomLayerConfig";
1313
import { oneBezierConnectionConfig } from "../configurations/bezierConnection";
1414
import { coloredConnections } from "../configurations/coloredConnections";
1515
import { customSchematicViewConfig } from "../configurations/customBlocksView";
16+
import { emptyGraphConfig } from "../configurations/emptyGraph";
1617
import { generatePrettyBlocks } from "../configurations/generatePretty";
1718
import { oneBlockConfig } from "../configurations/oneBlock";
1819
import { oneStraightConfig } from "../configurations/oneConnection";
@@ -198,11 +199,14 @@ export const SnappingGraph: Story = {
198199
),
199200
};
200201

201-
//
202-
// export const ThousandBlocksAnd9000Connections: Story = {
203-
// render: () => <GraphApp config={generatePrettyBlocks(25, 15)}></GraphApp>,
204-
// };
205-
//
206-
// export const FiveThousandBlocksAnd5000Connections: Story = {
207-
// render: () => <GraphApp config={generatePrettyBlocks(40, 500)}></GraphApp>,
208-
// };
202+
export const EmptyGraph: Story = {
203+
render: (args) => <GraphApp config={emptyGraphConfig} {...args}></GraphApp>,
204+
parameters: {
205+
docs: {
206+
description: {
207+
story:
208+
"Demonstrates behavior of an empty graph. This tests the fix for HitTest service where waitUsableRectUpdate would never resolve for graphs initialized without elements.",
209+
},
210+
},
211+
},
212+
};

0 commit comments

Comments
 (0)