@stuly/anode 0.1.0 → 0.1.2

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.
@@ -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":";;;KAEY,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;AAAA,UAER,OAAA;EACf,EAAA,EAAI,aAAA;EACJ,IAAA,EAAM,aAAA;EACN,KAAA;EACA,SAAA;AAAA;AAAA,cAGW,cAAA;EACX,SAAA,EAAW,OAAA;EACX,SAAA,EAAW,OAAA;EAAA,QACH,UAAA;EAER,IAAA,CAAK,OAAA,EAAS,OAAA;EAQd,MAAA,CAAA;;;;EAOA,QAAA,CAAS,IAAA;AAAA"}
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"}
@@ -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 || [];
@@ -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":";AA0EA,IAAa,iBAAb,MAA4B;CAC1B,YAAuB,EAAE;CACzB,YAAuB,EAAE;CACzB,AAAQ,aAAqB;CAE7B,KAAK,SAAkB;AACrB,OAAK,UAAU,KAAK,QAAQ;AAC5B,OAAK,YAAY,EAAE;AACnB,MAAI,KAAK,UAAU,SAAS,KAAK,WAC/B,MAAK,UAAU,OAAO;;CAI1B,SAAS;AACP,SAAO;GACL,WAAW,KAAK;GAChB,WAAW,KAAK;GACjB;;CAGH,SAAS,MAAW;AAClB,OAAK,YAAY,KAAK,aAAa,EAAE;AACrC,OAAK,YAAY,KAAK,aAAa,EAAE"}
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"}
@@ -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":";;;;iBAGgB,aAAA,CAAc,GAAA,EAAK,OAAA,EAAS,IAAA,EAAM,IAAA;EAAS,IAAA,EAAM,IAAA;EAAM,EAAA,EAAI,IAAA;AAAA;AAAA,iBAsB3D,aAAA,CAAc,GAAA,EAAK,OAAA,EAAS,IAAA,EAAM,IAAA,GAAO,IAAA;AAAA,iBAczC,WAAA,CAAY,GAAA,EAAK,OAAA,EAAS,IAAA,EAAM,IAAA;AAAA,cAsEnC,IAAA;EAEF,CAAA;EACA,CAAA;EACA,CAAA;EACA,CAAA;cAHA,CAAA,UACA,CAAA,UACA,CAAA,UACA,CAAA;EAGT,QAAA,CAAS,KAAA,EAAO,IAAA;EAShB,UAAA,CAAW,KAAA,EAAO,IAAA;AAAA;AAAA,cAUP,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;EAE7B,SAAA,CAAA;EAeA,MAAA,CAAO,GAAA,EAAK,IAAA,EAAM,IAAA,EAAM,CAAA;EAsBxB,KAAA,CAAM,KAAA,EAAO,IAAA,EAAM,KAAA,GAAO,CAAA,KAAW,CAAA;EAqBrC,KAAA,CAAA;AAAA"}
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"}
@@ -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;
@@ -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.0",
3
+ "version": "0.1.2",
4
4
  "main": "dist/anode.js",
5
5
  "module": "dist/anode.js",
6
6
  "types": "dist/anode.d.ts",
7
- "description": "A simple node library",
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",