@stuly/anode 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/README.md +26 -9
- package/dist/core/context.d.ts +124 -1
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +118 -2
- package/dist/core/context.js.map +1 -1
- package/dist/core/elements.d.ts +67 -4
- package/dist/core/elements.d.ts.map +1 -1
- package/dist/core/elements.js +67 -4
- package/dist/core/elements.js.map +1 -1
- package/dist/core/history.d.ts +25 -0
- package/dist/core/history.d.ts.map +1 -1
- package/dist/core/history.js +12 -0
- package/dist/core/history.js.map +1 -1
- package/dist/core/layout.d.ts +28 -0
- package/dist/core/layout.d.ts.map +1 -1
- package/dist/core/layout.js +28 -0
- package/dist/core/layout.js.map +1 -1
- package/package.json +17 -3
package/dist/core/history.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { LinkKind, SocketKind } from "./elements.js";
|
|
2
2
|
|
|
3
3
|
//#region src/core/history.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Defines all possible atomic mutations that can be recorded in history.
|
|
6
|
+
* Each action includes the data necessary to both execute ('do')
|
|
7
|
+
* and revert ('undo') the change.
|
|
8
|
+
*/
|
|
4
9
|
type HistoryAction = {
|
|
5
10
|
type: 'MOVE_ENTITY';
|
|
6
11
|
id: number;
|
|
@@ -110,21 +115,41 @@ type HistoryAction = {
|
|
|
110
115
|
type: 'FROM_JSON';
|
|
111
116
|
data: any;
|
|
112
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* A single transaction in the history stack, potentially containing
|
|
120
|
+
* multiple atomic actions (if recorded via a batch).
|
|
121
|
+
*/
|
|
113
122
|
interface Command {
|
|
123
|
+
/** Array of actions to perform during 'redo'. */
|
|
114
124
|
do: HistoryAction[];
|
|
125
|
+
/** Array of actions to perform during 'undo'. */
|
|
115
126
|
undo: HistoryAction[];
|
|
127
|
+
/** A human-readable label for the transaction. */
|
|
116
128
|
label?: string;
|
|
129
|
+
/** ISO timestamp of when the command was recorded. */
|
|
117
130
|
timestamp: number;
|
|
118
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Manages the undo/redo stacks for the Anode Context.
|
|
134
|
+
* Tracks discrete mutations and provides serialization for session persistence.
|
|
135
|
+
*/
|
|
119
136
|
declare class HistoryManager {
|
|
137
|
+
/** The stack of commands that can be undone. */
|
|
120
138
|
undoStack: Command[];
|
|
139
|
+
/** The stack of commands that can be reapplied (cleared on new mutation). */
|
|
121
140
|
redoStack: Command[];
|
|
122
141
|
private maxHistory;
|
|
142
|
+
/**
|
|
143
|
+
* Pushes a new command to the undo stack.
|
|
144
|
+
* Clears the redo stack to prevent branch divergence.
|
|
145
|
+
*/
|
|
123
146
|
push(command: Command): void;
|
|
147
|
+
/** Serializes the history stack for persistence. */
|
|
124
148
|
toJSON(): {
|
|
125
149
|
undoStack: Command[];
|
|
126
150
|
redoStack: Command[];
|
|
127
151
|
};
|
|
152
|
+
/** Restores the history stack from a serialized object. */
|
|
128
153
|
fromJSON(data: any): void;
|
|
129
154
|
}
|
|
130
155
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"history.d.ts","names":[],"sources":["../../src/core/history.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"history.d.ts","names":[],"sources":["../../src/core/history.ts"],"mappings":";;;;;AAOA;;;KAAY,aAAA;EAEN,IAAA;EACA,EAAA;EACA,IAAA;IAAQ,CAAA;IAAW,CAAA;EAAA;EACnB,EAAA;IAAM,CAAA;IAAW,CAAA;EAAA;AAAA;EAEjB,IAAA;EAAoB,EAAA;EAAY,IAAA;IAAQ,CAAA;IAAW,CAAA;EAAA;EAAa,EAAA;IAAM,CAAA;IAAW,CAAA;EAAA;AAAA;EAEjF,IAAA;EACA,EAAA;EACA,KAAA;EACA,QAAA;IAAY,CAAA;IAAW,CAAA;EAAA;EACvB,QAAA;AAAA;EAGA,IAAA;EACA,EAAA;EACA,KAAA;EACA,QAAA;IAAY,CAAA;IAAW,CAAA;EAAA;EACvB,QAAA;AAAA;EAGA,IAAA;EACA,EAAA;EACA,IAAA;EACA,EAAA;EACA,IAAA,EAAM,QAAA;EACN,KAAA;AAAA;EAGA,IAAA;EACA,EAAA;EACA,IAAA;EACA,EAAA;EACA,IAAA,EAAM,QAAA;EACN,KAAA;AAAA;EAGA,IAAA;EACA,EAAA;EACA,IAAA;IAAQ,GAAA;IAAa,GAAA;EAAA;EACrB,EAAA;IAAM,GAAA;IAAa,GAAA;EAAA;EACnB,SAAA;IAAc,GAAA;MAAO,CAAA;MAAW,CAAA;IAAA;IAAe,GAAA;MAAO,CAAA;MAAW,CAAA;IAAA;EAAA;AAAA;EAEjE,IAAA;EAAsB,OAAA;EAAiB,QAAA;EAAkB,WAAA;AAAA;EACzD,IAAA;EAA2B,OAAA;EAAiB,QAAA;EAAkB,WAAA;AAAA;EAE9D,IAAA;EACA,EAAA;EACA,QAAA;EACA,IAAA,EAAM,UAAA;EACN,IAAA;EACA,MAAA;IAAU,CAAA;IAAW,CAAA;EAAA;AAAA;EAGrB,IAAA;EACA,EAAA;EACA,QAAA;EACA,IAAA,EAAM,UAAA;EACN,IAAA;EACA,MAAA;IAAU,CAAA;IAAW,CAAA;EAAA;AAAA;EAErB,IAAA;EAAmB,IAAA;AAAA;;;;;UAMR,OAAA;;EAEf,EAAA,EAAI,aAAA;;EAEJ,IAAA,EAAM,aAAA;EAaK;EAXX,KAAA;EAaW;EAXX,SAAA;AAAA;;;;;cAOW,cAAA;;EAEX,SAAA,EAAW,OAAA;EA0BX;EAxBA,SAAA,EAAW,OAAA;EAAA,QACH,UAAA;EAuBU;;;;EAjBlB,IAAA,CAAK,OAAA,EAAS,OAAA;;EASd,MAAA,CAAA;;;;;EAQA,QAAA,CAAS,IAAA;AAAA"}
|
package/dist/core/history.js
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
//#region src/core/history.ts
|
|
2
|
+
/**
|
|
3
|
+
* Manages the undo/redo stacks for the Anode Context.
|
|
4
|
+
* Tracks discrete mutations and provides serialization for session persistence.
|
|
5
|
+
*/
|
|
2
6
|
var HistoryManager = class {
|
|
7
|
+
/** The stack of commands that can be undone. */
|
|
3
8
|
undoStack = [];
|
|
9
|
+
/** The stack of commands that can be reapplied (cleared on new mutation). */
|
|
4
10
|
redoStack = [];
|
|
5
11
|
maxHistory = 100;
|
|
12
|
+
/**
|
|
13
|
+
* Pushes a new command to the undo stack.
|
|
14
|
+
* Clears the redo stack to prevent branch divergence.
|
|
15
|
+
*/
|
|
6
16
|
push(command) {
|
|
7
17
|
this.undoStack.push(command);
|
|
8
18
|
this.redoStack = [];
|
|
9
19
|
if (this.undoStack.length > this.maxHistory) this.undoStack.shift();
|
|
10
20
|
}
|
|
21
|
+
/** Serializes the history stack for persistence. */
|
|
11
22
|
toJSON() {
|
|
12
23
|
return {
|
|
13
24
|
undoStack: this.undoStack,
|
|
14
25
|
redoStack: this.redoStack
|
|
15
26
|
};
|
|
16
27
|
}
|
|
28
|
+
/** Restores the history stack from a serialized object. */
|
|
17
29
|
fromJSON(data) {
|
|
18
30
|
this.undoStack = data.undoStack || [];
|
|
19
31
|
this.redoStack = data.redoStack || [];
|
package/dist/core/history.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"history.js","names":[],"sources":["../../src/core/history.ts"],"sourcesContent":["import { LinkKind, SocketKind } from './elements';\n\nexport type HistoryAction =\n | {\n type: 'MOVE_ENTITY';\n id: number;\n from: { x: number; y: number };\n to: { x: number; y: number };\n }\n | { type: 'MOVE_GROUP'; id: number; from: { x: number; y: number }; to: { x: number; y: number } }\n | {\n type: 'CREATE_ENTITY';\n id: number;\n inner: any;\n position: { x: number; y: number };\n parentId: number | null;\n }\n | {\n type: 'DROP_ENTITY';\n id: number;\n inner: any;\n position: { x: number; y: number };\n parentId: number | null;\n }\n | {\n type: 'CREATE_LINK';\n id: number;\n from: number;\n to: number;\n kind: LinkKind;\n inner?: any;\n }\n | {\n type: 'DROP_LINK';\n id: number;\n from: number;\n to: number;\n kind: LinkKind;\n inner?: any;\n }\n | {\n type: 'UPDATE_LINK';\n id: number;\n from: { old: number; new: number };\n to: { old: number; new: number };\n waypoints?: { old: { x: number; y: number }[]; new: { x: number; y: number }[] };\n }\n | { type: 'ADD_TO_GROUP'; groupId: number; entityId: number; oldParentId: number | null }\n | { type: 'REMOVE_FROM_GROUP'; groupId: number; entityId: number; oldParentId: number | null }\n | {\n type: 'CREATE_SOCKET';\n id: number;\n entityId: number;\n kind: SocketKind;\n name: string;\n offset: { x: number; y: number };\n }\n | {\n type: 'DROP_SOCKET';\n id: number;\n entityId: number;\n kind: SocketKind;\n name: string;\n offset: { x: number; y: number };\n }\n | { type: 'FROM_JSON'; data: any };\n\nexport interface Command {\n do: HistoryAction[];\n undo: HistoryAction[];\n label?: string;\n timestamp: number;\n}\n\nexport class HistoryManager {\n undoStack: Command[] = [];\n redoStack: Command[] = [];\n private maxHistory: number = 100;\n\n push(command: Command) {\n this.undoStack.push(command);\n this.redoStack = [];\n if (this.undoStack.length > this.maxHistory) {\n this.undoStack.shift();\n }\n }\n\n toJSON() {\n return {\n undoStack: this.undoStack,\n redoStack: this.redoStack\n };\n }\n\n fromJSON(data: any) {\n this.undoStack = data.undoStack || [];\n this.redoStack = data.redoStack || [];\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"history.js","names":[],"sources":["../../src/core/history.ts"],"sourcesContent":["import { LinkKind, SocketKind } from './elements';\n\n/**\n * Defines all possible atomic mutations that can be recorded in history.\n * Each action includes the data necessary to both execute ('do')\n * and revert ('undo') the change.\n */\nexport type HistoryAction =\n | {\n type: 'MOVE_ENTITY';\n id: number;\n from: { x: number; y: number };\n to: { x: number; y: number };\n }\n | { type: 'MOVE_GROUP'; id: number; from: { x: number; y: number }; to: { x: number; y: number } }\n | {\n type: 'CREATE_ENTITY';\n id: number;\n inner: any;\n position: { x: number; y: number };\n parentId: number | null;\n }\n | {\n type: 'DROP_ENTITY';\n id: number;\n inner: any;\n position: { x: number; y: number };\n parentId: number | null;\n }\n | {\n type: 'CREATE_LINK';\n id: number;\n from: number;\n to: number;\n kind: LinkKind;\n inner?: any;\n }\n | {\n type: 'DROP_LINK';\n id: number;\n from: number;\n to: number;\n kind: LinkKind;\n inner?: any;\n }\n | {\n type: 'UPDATE_LINK';\n id: number;\n from: { old: number; new: number };\n to: { old: number; new: number };\n waypoints?: { old: { x: number; y: number }[]; new: { x: number; y: number }[] };\n }\n | { type: 'ADD_TO_GROUP'; groupId: number; entityId: number; oldParentId: number | null }\n | { type: 'REMOVE_FROM_GROUP'; groupId: number; entityId: number; oldParentId: number | null }\n | {\n type: 'CREATE_SOCKET';\n id: number;\n entityId: number;\n kind: SocketKind;\n name: string;\n offset: { x: number; y: number };\n }\n | {\n type: 'DROP_SOCKET';\n id: number;\n entityId: number;\n kind: SocketKind;\n name: string;\n offset: { x: number; y: number };\n }\n | { type: 'FROM_JSON'; data: any };\n\n/**\n * A single transaction in the history stack, potentially containing\n * multiple atomic actions (if recorded via a batch).\n */\nexport interface Command {\n /** Array of actions to perform during 'redo'. */\n do: HistoryAction[];\n /** Array of actions to perform during 'undo'. */\n undo: HistoryAction[];\n /** A human-readable label for the transaction. */\n label?: string;\n /** ISO timestamp of when the command was recorded. */\n timestamp: number;\n}\n\n/**\n * Manages the undo/redo stacks for the Anode Context.\n * Tracks discrete mutations and provides serialization for session persistence.\n */\nexport class HistoryManager {\n /** The stack of commands that can be undone. */\n undoStack: Command[] = [];\n /** The stack of commands that can be reapplied (cleared on new mutation). */\n redoStack: Command[] = [];\n private maxHistory: number = 100;\n\n /**\n * Pushes a new command to the undo stack.\n * Clears the redo stack to prevent branch divergence.\n */\n push(command: Command) {\n this.undoStack.push(command);\n this.redoStack = [];\n if (this.undoStack.length > this.maxHistory) {\n this.undoStack.shift();\n }\n }\n\n /** Serializes the history stack for persistence. */\n toJSON() {\n return {\n undoStack: this.undoStack,\n redoStack: this.redoStack\n };\n }\n\n /** Restores the history stack from a serialized object. */\n fromJSON(data: any) {\n this.undoStack = data.undoStack || [];\n this.redoStack = data.redoStack || [];\n }\n}\n"],"mappings":";;;;;AA2FA,IAAa,iBAAb,MAA4B;;CAE1B,YAAuB,EAAE;;CAEzB,YAAuB,EAAE;CACzB,AAAQ,aAAqB;;;;;CAM7B,KAAK,SAAkB;AACrB,OAAK,UAAU,KAAK,QAAQ;AAC5B,OAAK,YAAY,EAAE;AACnB,MAAI,KAAK,UAAU,SAAS,KAAK,WAC/B,MAAK,UAAU,OAAO;;;CAK1B,SAAS;AACP,SAAO;GACL,WAAW,KAAK;GAChB,WAAW,KAAK;GACjB;;;CAIH,SAAS,MAAW;AAClB,OAAK,YAAY,KAAK,aAAa,EAAE;AACrC,OAAK,YAAY,KAAK,aAAa,EAAE"}
|
package/dist/core/layout.d.ts
CHANGED
|
@@ -2,21 +2,41 @@ import { Link, Vec2 } from "./elements.js";
|
|
|
2
2
|
import { Context } from "./context.js";
|
|
3
3
|
|
|
4
4
|
//#region src/core/layout.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Calculates the start and end world coordinates for a link's path.
|
|
7
|
+
* Resolves entity and parent group positions to get absolute coordinates.
|
|
8
|
+
*/
|
|
5
9
|
declare function getLinkPoints(ctx: Context, link: Link): {
|
|
6
10
|
from: Vec2;
|
|
7
11
|
to: Vec2;
|
|
8
12
|
} | null;
|
|
13
|
+
/**
|
|
14
|
+
* Calculates the visual center point of a link.
|
|
15
|
+
* Used for positioning labels or custom UI overlays.
|
|
16
|
+
*/
|
|
9
17
|
declare function getLinkCenter(ctx: Context, link: Link): Vec2 | null;
|
|
18
|
+
/**
|
|
19
|
+
* Generates an SVG path string for a link based on its `kind` and `waypoints`.
|
|
20
|
+
*/
|
|
10
21
|
declare function getLinkPath(ctx: Context, link: Link): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Represents a rectangular boundary for spatial indexing and queries.
|
|
24
|
+
*/
|
|
11
25
|
declare class Rect {
|
|
12
26
|
x: number;
|
|
13
27
|
y: number;
|
|
14
28
|
w: number;
|
|
15
29
|
h: number;
|
|
16
30
|
constructor(x: number, y: number, w: number, h: number);
|
|
31
|
+
/** Returns true if the given point is inside the rectangle. */
|
|
17
32
|
contains(point: Vec2): boolean;
|
|
33
|
+
/** Returns true if this rectangle intersects with another rectangle. */
|
|
18
34
|
intersects(range: Rect): boolean;
|
|
19
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* A QuadTree implementation for high-performance spatial indexing.
|
|
38
|
+
* Used by Anode to perform spatial culling and optimized entity selection.
|
|
39
|
+
*/
|
|
20
40
|
declare class QuadTree<T> {
|
|
21
41
|
boundary: Rect;
|
|
22
42
|
private capacity;
|
|
@@ -27,9 +47,17 @@ declare class QuadTree<T> {
|
|
|
27
47
|
private southwest;
|
|
28
48
|
private southeast;
|
|
29
49
|
constructor(boundary: Rect);
|
|
50
|
+
/** Internal: Subdivides the node into four quadrants. */
|
|
30
51
|
subdivide(): void;
|
|
52
|
+
/** Inserts a data point at a specific coordinate into the tree. */
|
|
31
53
|
insert(pos: Vec2, data: T): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Recursively queries the tree for all data points within the given range.
|
|
56
|
+
* @param range The Rect boundary to search within.
|
|
57
|
+
* @param found Optional array to collect results.
|
|
58
|
+
*/
|
|
32
59
|
query(range: Rect, found?: T[]): T[];
|
|
60
|
+
/** Clears all points and children from the tree. */
|
|
33
61
|
clear(): void;
|
|
34
62
|
}
|
|
35
63
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.d.ts","names":[],"sources":["../../src/core/layout.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"layout.d.ts","names":[],"sources":["../../src/core/layout.ts"],"mappings":";;;;;;AAOA;;iBAAgB,aAAA,CAAc,GAAA,EAAK,OAAA,EAAS,IAAA,EAAM,IAAA;EAAS,IAAA,EAAM,IAAA;EAAM,EAAA,EAAI,IAAA;AAAA;;;;;iBA0B3D,aAAA,CAAc,GAAA,EAAK,OAAA,EAAS,IAAA,EAAM,IAAA,GAAO,IAAA;;;;iBAiBzC,WAAA,CAAY,GAAA,EAAK,OAAA,EAAS,IAAA,EAAM,IAAA;;;;cAwEnC,IAAA;EAEF,CAAA;EACA,CAAA;EACA,CAAA;EACA,CAAA;cAHA,CAAA,UACA,CAAA,UACA,CAAA,UACA,CAAA;EA9F8C;EAkGvD,QAAA,CAAS,KAAA,EAAO,IAAA;EAlG2C;EA4G3D,UAAA,CAAW,KAAA,EAAO,IAAA;AAAA;;;;;cAcP,QAAA;EAUQ,QAAA,EAAU,IAAA;EAAA,QATrB,QAAA;EAAA,QACA,MAAA;EAAA,QACA,OAAA;EAAA,QAEA,SAAA;EAAA,QACA,SAAA;EAAA,QACA,SAAA;EAAA,QACA,SAAA;cAEW,QAAA,EAAU,IAAA;EAnHqB;EAsHlD,SAAA,CAAA;EA9Ce;EA8Df,MAAA,CAAO,GAAA,EAAK,IAAA,EAAM,IAAA,EAAM,CAAA;EA3CF;;;;;EAsEtB,KAAA,CAAM,KAAA,EAAO,IAAA,EAAM,KAAA,GAAO,CAAA,KAAW,CAAA;EAvF5B;EA6GT,KAAA,CAAA;AAAA"}
|
package/dist/core/layout.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { LinkKind, Vec2 } from "./elements.js";
|
|
2
2
|
|
|
3
3
|
//#region src/core/layout.ts
|
|
4
|
+
/**
|
|
5
|
+
* Calculates the start and end world coordinates for a link's path.
|
|
6
|
+
* Resolves entity and parent group positions to get absolute coordinates.
|
|
7
|
+
*/
|
|
4
8
|
function getLinkPoints(ctx, link) {
|
|
5
9
|
const fromSocket = ctx.sockets.get(link.from);
|
|
6
10
|
const toSocket = ctx.sockets.get(link.to);
|
|
@@ -15,6 +19,10 @@ function getLinkPoints(ctx, link) {
|
|
|
15
19
|
to: new Vec2(toWorldPos.x + toSocket.offset.x, toWorldPos.y + toSocket.offset.y)
|
|
16
20
|
};
|
|
17
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Calculates the visual center point of a link.
|
|
24
|
+
* Used for positioning labels or custom UI overlays.
|
|
25
|
+
*/
|
|
18
26
|
function getLinkCenter(ctx, link) {
|
|
19
27
|
const pts = getLinkPoints(ctx, link);
|
|
20
28
|
if (!pts) return null;
|
|
@@ -29,6 +37,9 @@ function getLinkCenter(ctx, link) {
|
|
|
29
37
|
const p2 = points[midIndex + 1];
|
|
30
38
|
return new Vec2((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
|
|
31
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Generates an SVG path string for a link based on its `kind` and `waypoints`.
|
|
42
|
+
*/
|
|
32
43
|
function getLinkPath(ctx, link) {
|
|
33
44
|
const pts = getLinkPoints(ctx, link);
|
|
34
45
|
if (!pts) return null;
|
|
@@ -82,6 +93,9 @@ function getLinkPath(ctx, link) {
|
|
|
82
93
|
}
|
|
83
94
|
return null;
|
|
84
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Represents a rectangular boundary for spatial indexing and queries.
|
|
98
|
+
*/
|
|
85
99
|
var Rect = class {
|
|
86
100
|
constructor(x, y, w, h) {
|
|
87
101
|
this.x = x;
|
|
@@ -89,13 +103,19 @@ var Rect = class {
|
|
|
89
103
|
this.w = w;
|
|
90
104
|
this.h = h;
|
|
91
105
|
}
|
|
106
|
+
/** Returns true if the given point is inside the rectangle. */
|
|
92
107
|
contains(point) {
|
|
93
108
|
return point.x >= this.x && point.x <= this.x + this.w && point.y >= this.y && point.y <= this.y + this.h;
|
|
94
109
|
}
|
|
110
|
+
/** Returns true if this rectangle intersects with another rectangle. */
|
|
95
111
|
intersects(range) {
|
|
96
112
|
return !(range.x > this.x + this.w || range.x + range.w < this.x || range.y > this.y + this.h || range.y + range.h < this.y);
|
|
97
113
|
}
|
|
98
114
|
};
|
|
115
|
+
/**
|
|
116
|
+
* A QuadTree implementation for high-performance spatial indexing.
|
|
117
|
+
* Used by Anode to perform spatial culling and optimized entity selection.
|
|
118
|
+
*/
|
|
99
119
|
var QuadTree = class QuadTree {
|
|
100
120
|
capacity = 4;
|
|
101
121
|
points = [];
|
|
@@ -107,6 +127,7 @@ var QuadTree = class QuadTree {
|
|
|
107
127
|
constructor(boundary) {
|
|
108
128
|
this.boundary = boundary;
|
|
109
129
|
}
|
|
130
|
+
/** Internal: Subdivides the node into four quadrants. */
|
|
110
131
|
subdivide() {
|
|
111
132
|
const { x, y, w, h } = this.boundary;
|
|
112
133
|
const nw = new Rect(x, y, w / 2, h / 2);
|
|
@@ -119,6 +140,7 @@ var QuadTree = class QuadTree {
|
|
|
119
140
|
this.southeast = new QuadTree(se);
|
|
120
141
|
this.divided = true;
|
|
121
142
|
}
|
|
143
|
+
/** Inserts a data point at a specific coordinate into the tree. */
|
|
122
144
|
insert(pos, data) {
|
|
123
145
|
if (!this.boundary.contains(pos)) return false;
|
|
124
146
|
if (this.points.length < this.capacity) {
|
|
@@ -131,6 +153,11 @@ var QuadTree = class QuadTree {
|
|
|
131
153
|
if (!this.divided) this.subdivide();
|
|
132
154
|
return this.northwest.insert(pos, data) || this.northeast.insert(pos, data) || this.southwest.insert(pos, data) || this.southeast.insert(pos, data);
|
|
133
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Recursively queries the tree for all data points within the given range.
|
|
158
|
+
* @param range The Rect boundary to search within.
|
|
159
|
+
* @param found Optional array to collect results.
|
|
160
|
+
*/
|
|
134
161
|
query(range, found = []) {
|
|
135
162
|
if (!this.boundary.intersects(range)) return found;
|
|
136
163
|
for (const p of this.points) if (range.contains(p.pos)) found.push(p.data);
|
|
@@ -142,6 +169,7 @@ var QuadTree = class QuadTree {
|
|
|
142
169
|
}
|
|
143
170
|
return found;
|
|
144
171
|
}
|
|
172
|
+
/** Clears all points and children from the tree. */
|
|
145
173
|
clear() {
|
|
146
174
|
this.points = [];
|
|
147
175
|
this.divided = false;
|
package/dist/core/layout.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.js","names":[],"sources":["../../src/core/layout.ts"],"sourcesContent":["import { Context } from './context';\nimport { Link, Vec2, LinkKind } from './elements';\n\nexport function getLinkPoints(ctx: Context, link: Link): { from: Vec2; to: Vec2 } | null {\n const fromSocket = ctx.sockets.get(link.from);\n const toSocket = ctx.sockets.get(link.to);\n\n if (!fromSocket || !toSocket) {\n return null;\n }\n\n const fromEntity = ctx.entities.get(fromSocket.entityId);\n const toEntity = ctx.entities.get(toSocket.entityId);\n\n if (!fromEntity || !toEntity) return null;\n\n const fromWorldPos = ctx.getWorldPosition(fromEntity.id);\n const toWorldPos = ctx.getWorldPosition(toEntity.id);\n\n return {\n from: new Vec2(fromWorldPos.x + fromSocket.offset.x, fromWorldPos.y + fromSocket.offset.y),\n to: new Vec2(toWorldPos.x + toSocket.offset.x, toWorldPos.y + toSocket.offset.y)\n };\n}\n\nexport function getLinkCenter(ctx: Context, link: Link): Vec2 | null {\n const pts = getLinkPoints(ctx, link);\n if (!pts) return null;\n\n const points = [pts.from, ...link.waypoints, pts.to];\n if (points.length < 2) return null;\n\n const midIndex = Math.floor((points.length - 1) / 2);\n const p1 = points[midIndex]!;\n const p2 = points[midIndex + 1]!;\n\n return new Vec2((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);\n}\n\nexport function getLinkPath(ctx: Context, link: Link): string | null {\n const pts = getLinkPoints(ctx, link);\n if (!pts) return null;\n\n const { from: fromPos, to: toPos } = pts;\n const points = [fromPos, ...link.waypoints, toPos];\n\n if (link.kind === LinkKind.LINE) {\n return points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ');\n }\n\n if (link.kind === LinkKind.BEZIER) {\n if (points.length === 2) {\n const p1 = points[0]!;\n const p2 = points[1]!;\n const dx = Math.abs(p1.x - p2.x);\n const offset = Math.max(dx / 2, 50);\n return `M ${p1.x} ${p1.y} C ${p1.x + offset} ${p1.y}, ${p2.x - offset} ${p2.y}, ${p2.x} ${p2.y}`;\n }\n\n let path = `M ${points[0]!.x} ${points[0]!.y}`;\n for (let i = 0; i < points.length - 1; i++) {\n const p1 = points[i]!;\n const p2 = points[i + 1]!;\n const dx = Math.abs(p1.x - p2.x);\n const offset = Math.max(dx / 2, 20);\n path += ` C ${p1.x + offset} ${p1.y}, ${p2.x - offset} ${p2.y}, ${p2.x} ${p2.y}`;\n }\n return path;\n }\n\n if (link.kind === LinkKind.STEP || link.kind === LinkKind.SMOOTH_STEP) {\n let path = `M ${points[0]!.x} ${points[0]!.y}`;\n const isSmooth = link.kind === LinkKind.SMOOTH_STEP;\n const borderRadius = 10;\n\n for (let i = 0; i < points.length - 1; i++) {\n const p1 = points[i]!;\n const p2 = points[i + 1]!;\n const midX = (p1.x + p2.x) / 2;\n\n if (!isSmooth) {\n path += ` L ${midX} ${p1.y} L ${midX} ${p2.y} L ${p2.x} ${p2.y}`;\n } else {\n const signX = p2.x > p1.x ? 1 : -1;\n const signY = p2.y > p1.y ? 1 : -1;\n const actualBorder = Math.min(\n borderRadius,\n Math.abs(p1.x - p2.x) / 2,\n Math.abs(p1.y - p2.y) / 2\n );\n\n if (actualBorder < 1) {\n // Points are aligned, draw a straight line for this segment\n path += ` L ${p2.x} ${p2.y}`;\n } else {\n path += ` L ${midX - actualBorder * signX} ${p1.y} \n Q ${midX} ${p1.y} ${midX} ${p1.y + actualBorder * signY}\n L ${midX} ${p2.y - actualBorder * signY}\n Q ${midX} ${p2.y} ${midX + actualBorder * signX} ${p2.y}\n L ${p2.x} ${p2.y}`;\n }\n }\n }\n return path;\n }\n\n return null;\n}\n\nexport class Rect {\n constructor(\n public x: number,\n public y: number,\n public w: number,\n public h: number\n ) {}\n\n contains(point: Vec2) {\n return (\n point.x >= this.x &&\n point.x <= this.x + this.w &&\n point.y >= this.y &&\n point.y <= this.y + this.h\n );\n }\n\n intersects(range: Rect) {\n return !(\n range.x > this.x + this.w ||\n range.x + range.w < this.x ||\n range.y > this.y + this.h ||\n range.y + range.h < this.y\n );\n }\n}\n\nexport class QuadTree<T> {\n private capacity: number = 4;\n private points: { pos: Vec2; data: T }[] = [];\n private divided: boolean = false;\n\n private northwest: QuadTree<T> | null = null;\n private northeast: QuadTree<T> | null = null;\n private southwest: QuadTree<T> | null = null;\n private southeast: QuadTree<T> | null = null;\n\n constructor(public boundary: Rect) {}\n\n subdivide() {\n const { x, y, w, h } = this.boundary;\n const nw = new Rect(x, y, w / 2, h / 2);\n const ne = new Rect(x + w / 2, y, w / 2, h / 2);\n const sw = new Rect(x, y + h / 2, w / 2, h / 2);\n const se = new Rect(x + w / 2, y + h / 2, w / 2, h / 2);\n\n this.northwest = new QuadTree<T>(nw);\n this.northeast = new QuadTree<T>(ne);\n this.southwest = new QuadTree<T>(sw);\n this.southeast = new QuadTree<T>(se);\n\n this.divided = true;\n }\n\n insert(pos: Vec2, data: T): boolean {\n if (!this.boundary.contains(pos)) {\n return false;\n }\n\n if (this.points.length < this.capacity) {\n this.points.push({ pos, data });\n return true;\n }\n\n if (!this.divided) {\n this.subdivide();\n }\n\n return (\n this.northwest!.insert(pos, data) ||\n this.northeast!.insert(pos, data) ||\n this.southwest!.insert(pos, data) ||\n this.southeast!.insert(pos, data)\n );\n }\n\n query(range: Rect, found: T[] = []): T[] {\n if (!this.boundary.intersects(range)) {\n return found;\n }\n\n for (const p of this.points) {\n if (range.contains(p.pos)) {\n found.push(p.data);\n }\n }\n\n if (this.divided) {\n this.northwest!.query(range, found);\n this.northeast!.query(range, found);\n this.southwest!.query(range, found);\n this.southeast!.query(range, found);\n }\n\n return found;\n }\n\n clear() {\n this.points = [];\n this.divided = false;\n this.northwest = null;\n this.northeast = null;\n this.southwest = null;\n this.southeast = null;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"layout.js","names":[],"sources":["../../src/core/layout.ts"],"sourcesContent":["import { Context } from './context';\nimport { Link, Vec2, LinkKind } from './elements';\n\n/**\n * Calculates the start and end world coordinates for a link's path.\n * Resolves entity and parent group positions to get absolute coordinates.\n */\nexport function getLinkPoints(ctx: Context, link: Link): { from: Vec2; to: Vec2 } | null {\n const fromSocket = ctx.sockets.get(link.from);\n const toSocket = ctx.sockets.get(link.to);\n\n if (!fromSocket || !toSocket) {\n return null;\n }\n\n const fromEntity = ctx.entities.get(fromSocket.entityId);\n const toEntity = ctx.entities.get(toSocket.entityId);\n\n if (!fromEntity || !toEntity) return null;\n\n const fromWorldPos = ctx.getWorldPosition(fromEntity.id);\n const toWorldPos = ctx.getWorldPosition(toEntity.id);\n\n return {\n from: new Vec2(fromWorldPos.x + fromSocket.offset.x, fromWorldPos.y + fromSocket.offset.y),\n to: new Vec2(toWorldPos.x + toSocket.offset.x, toWorldPos.y + toSocket.offset.y)\n };\n}\n\n/**\n * Calculates the visual center point of a link.\n * Used for positioning labels or custom UI overlays.\n */\nexport function getLinkCenter(ctx: Context, link: Link): Vec2 | null {\n const pts = getLinkPoints(ctx, link);\n if (!pts) return null;\n\n const points = [pts.from, ...link.waypoints, pts.to];\n if (points.length < 2) return null;\n\n const midIndex = Math.floor((points.length - 1) / 2);\n const p1 = points[midIndex]!;\n const p2 = points[midIndex + 1]!;\n\n return new Vec2((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);\n}\n\n/**\n * Generates an SVG path string for a link based on its `kind` and `waypoints`.\n */\nexport function getLinkPath(ctx: Context, link: Link): string | null {\n const pts = getLinkPoints(ctx, link);\n if (!pts) return null;\n\n const { from: fromPos, to: toPos } = pts;\n const points = [fromPos, ...link.waypoints, toPos];\n\n if (link.kind === LinkKind.LINE) {\n return points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ');\n }\n\n if (link.kind === LinkKind.BEZIER) {\n if (points.length === 2) {\n const p1 = points[0]!;\n const p2 = points[1]!;\n const dx = Math.abs(p1.x - p2.x);\n const offset = Math.max(dx / 2, 50);\n return `M ${p1.x} ${p1.y} C ${p1.x + offset} ${p1.y}, ${p2.x - offset} ${p2.y}, ${p2.x} ${p2.y}`;\n }\n\n let path = `M ${points[0]!.x} ${points[0]!.y}`;\n for (let i = 0; i < points.length - 1; i++) {\n const p1 = points[i]!;\n const p2 = points[i + 1]!;\n const dx = Math.abs(p1.x - p2.x);\n const offset = Math.max(dx / 2, 20);\n path += ` C ${p1.x + offset} ${p1.y}, ${p2.x - offset} ${p2.y}, ${p2.x} ${p2.y}`;\n }\n return path;\n }\n\n if (link.kind === LinkKind.STEP || link.kind === LinkKind.SMOOTH_STEP) {\n let path = `M ${points[0]!.x} ${points[0]!.y}`;\n const isSmooth = link.kind === LinkKind.SMOOTH_STEP;\n const borderRadius = 10;\n\n for (let i = 0; i < points.length - 1; i++) {\n const p1 = points[i]!;\n const p2 = points[i + 1]!;\n const midX = (p1.x + p2.x) / 2;\n\n if (!isSmooth) {\n path += ` L ${midX} ${p1.y} L ${midX} ${p2.y} L ${p2.x} ${p2.y}`;\n } else {\n const signX = p2.x > p1.x ? 1 : -1;\n const signY = p2.y > p1.y ? 1 : -1;\n const actualBorder = Math.min(\n borderRadius,\n Math.abs(p1.x - p2.x) / 2,\n Math.abs(p1.y - p2.y) / 2\n );\n\n if (actualBorder < 1) {\n path += ` L ${p2.x} ${p2.y}`;\n } else {\n path += ` L ${midX - actualBorder * signX} ${p1.y} \n Q ${midX} ${p1.y} ${midX} ${p1.y + actualBorder * signY}\n L ${midX} ${p2.y - actualBorder * signY}\n Q ${midX} ${p2.y} ${midX + actualBorder * signX} ${p2.y}\n L ${p2.x} ${p2.y}`;\n }\n }\n }\n return path;\n }\n\n return null;\n}\n\n/**\n * Represents a rectangular boundary for spatial indexing and queries.\n */\nexport class Rect {\n constructor(\n public x: number,\n public y: number,\n public w: number,\n public h: number\n ) {}\n\n /** Returns true if the given point is inside the rectangle. */\n contains(point: Vec2) {\n return (\n point.x >= this.x &&\n point.x <= this.x + this.w &&\n point.y >= this.y &&\n point.y <= this.y + this.h\n );\n }\n\n /** Returns true if this rectangle intersects with another rectangle. */\n intersects(range: Rect) {\n return !(\n range.x > this.x + this.w ||\n range.x + range.w < this.x ||\n range.y > this.y + this.h ||\n range.y + range.h < this.y\n );\n }\n}\n\n/**\n * A QuadTree implementation for high-performance spatial indexing.\n * Used by Anode to perform spatial culling and optimized entity selection.\n */\nexport class QuadTree<T> {\n private capacity: number = 4;\n private points: { pos: Vec2; data: T }[] = [];\n private divided: boolean = false;\n\n private northwest: QuadTree<T> | null = null;\n private northeast: QuadTree<T> | null = null;\n private southwest: QuadTree<T> | null = null;\n private southeast: QuadTree<T> | null = null;\n\n constructor(public boundary: Rect) {}\n\n /** Internal: Subdivides the node into four quadrants. */\n subdivide() {\n const { x, y, w, h } = this.boundary;\n const nw = new Rect(x, y, w / 2, h / 2);\n const ne = new Rect(x + w / 2, y, w / 2, h / 2);\n const sw = new Rect(x, y + h / 2, w / 2, h / 2);\n const se = new Rect(x + w / 2, y + h / 2, w / 2, h / 2);\n\n this.northwest = new QuadTree<T>(nw);\n this.northeast = new QuadTree<T>(ne);\n this.southwest = new QuadTree<T>(sw);\n this.southeast = new QuadTree<T>(se);\n\n this.divided = true;\n }\n\n /** Inserts a data point at a specific coordinate into the tree. */\n insert(pos: Vec2, data: T): boolean {\n if (!this.boundary.contains(pos)) {\n return false;\n }\n\n if (this.points.length < this.capacity) {\n this.points.push({ pos, data });\n return true;\n }\n\n if (!this.divided) {\n this.subdivide();\n }\n\n return (\n this.northwest!.insert(pos, data) ||\n this.northeast!.insert(pos, data) ||\n this.southwest!.insert(pos, data) ||\n this.southeast!.insert(pos, data)\n );\n }\n\n /**\n * Recursively queries the tree for all data points within the given range.\n * @param range The Rect boundary to search within.\n * @param found Optional array to collect results.\n */\n query(range: Rect, found: T[] = []): T[] {\n if (!this.boundary.intersects(range)) {\n return found;\n }\n\n for (const p of this.points) {\n if (range.contains(p.pos)) {\n found.push(p.data);\n }\n }\n\n if (this.divided) {\n this.northwest!.query(range, found);\n this.northeast!.query(range, found);\n this.southwest!.query(range, found);\n this.southeast!.query(range, found);\n }\n\n return found;\n }\n\n /** Clears all points and children from the tree. */\n clear() {\n this.points = [];\n this.divided = false;\n this.northwest = null;\n this.northeast = null;\n this.southwest = null;\n this.southeast = null;\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stuly/anode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"main": "dist/anode.js",
|
|
5
5
|
"module": "dist/anode.js",
|
|
6
6
|
"types": "dist/anode.d.ts",
|
|
7
|
-
"description": "
|
|
7
|
+
"description": "The high-performance, headless core engine for Anode.",
|
|
8
8
|
"publishConfig": {
|
|
9
9
|
"access": "public"
|
|
10
10
|
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/stulyproject/anode.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/stulyproject/anode/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/stulyproject/anode#readme",
|
|
11
19
|
"files": [
|
|
12
20
|
"dist"
|
|
13
21
|
],
|
|
@@ -18,7 +26,13 @@
|
|
|
18
26
|
"default": "./dist/anode.js"
|
|
19
27
|
}
|
|
20
28
|
},
|
|
21
|
-
"keywords": [
|
|
29
|
+
"keywords": [
|
|
30
|
+
"node-graph",
|
|
31
|
+
"graph-engine",
|
|
32
|
+
"headless",
|
|
33
|
+
"spatial-indexing",
|
|
34
|
+
"quadtree"
|
|
35
|
+
],
|
|
22
36
|
"author": "",
|
|
23
37
|
"license": "MIT",
|
|
24
38
|
"type": "module",
|