@tscircuit/core 0.0.132 → 0.0.134
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/dist/index.d.ts +12 -0
- package/dist/index.js +205 -95
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ type RenderPhase = (typeof orderedRenderPhases)[number];
|
|
|
13
13
|
type RenderPhaseFn<K extends RenderPhase = RenderPhase> = `doInitial${K}` | `update${K}` | `remove${K}`;
|
|
14
14
|
type RenderPhaseStates = Record<RenderPhase, {
|
|
15
15
|
initialized: boolean;
|
|
16
|
+
dirty: boolean;
|
|
16
17
|
}>;
|
|
17
18
|
type RenderPhaseFunctions = {
|
|
18
19
|
[T in RenderPhaseFn]?: () => void;
|
|
@@ -34,7 +35,12 @@ declare abstract class Renderable implements IRenderable {
|
|
|
34
35
|
/** Schematic-only, lines, boxes, indicators etc. */
|
|
35
36
|
isSchematicPrimitive: boolean;
|
|
36
37
|
_renderId: string;
|
|
38
|
+
_currentRenderPhase: RenderPhase | null;
|
|
39
|
+
private _asyncEffects;
|
|
37
40
|
constructor(props: any);
|
|
41
|
+
protected _markDirty(phase: RenderPhase): void;
|
|
42
|
+
protected _queueAsyncEffect(effect: () => Promise<void>): void;
|
|
43
|
+
_hasIncompleteAsyncEffects(): boolean;
|
|
38
44
|
runRenderCycle(): void;
|
|
39
45
|
/**
|
|
40
46
|
* This runs all the render methods for a given phase, calling one of:
|
|
@@ -53,6 +59,7 @@ type ReactSubtree = {
|
|
|
53
59
|
component: NormalComponent;
|
|
54
60
|
};
|
|
55
61
|
|
|
62
|
+
type RootCircuitEventName = "asyncEffectComplete";
|
|
56
63
|
declare class Circuit {
|
|
57
64
|
firstChild: PrimitiveComponent | null;
|
|
58
65
|
children: PrimitiveComponent[];
|
|
@@ -71,6 +78,8 @@ declare class Circuit {
|
|
|
71
78
|
};
|
|
72
79
|
_guessRootComponent(): void;
|
|
73
80
|
render(): void;
|
|
81
|
+
renderUntilSettled(): Promise<void>;
|
|
82
|
+
private _hasIncompleteAsyncEffects;
|
|
74
83
|
getSoup(): AnyCircuitElement[];
|
|
75
84
|
getCircuitJson(): AnyCircuitElement[];
|
|
76
85
|
toJson(): AnyCircuitElement[];
|
|
@@ -88,6 +97,9 @@ declare class Circuit {
|
|
|
88
97
|
selectOne(selector: string, opts?: {
|
|
89
98
|
type?: "component" | "port";
|
|
90
99
|
}): PrimitiveComponent | null;
|
|
100
|
+
_eventListeners: Record<RootCircuitEventName, Array<(...args: any[]) => void>>;
|
|
101
|
+
emit(event: RootCircuitEventName, ...args: any[]): void;
|
|
102
|
+
on(event: RootCircuitEventName, listener: (...args: any[]) => void): void;
|
|
91
103
|
}
|
|
92
104
|
/**
|
|
93
105
|
* @deprecated
|
package/dist/index.js
CHANGED
|
@@ -87,14 +87,61 @@ var Renderable = class {
|
|
|
87
87
|
/** Schematic-only, lines, boxes, indicators etc. */
|
|
88
88
|
isSchematicPrimitive = false;
|
|
89
89
|
_renderId;
|
|
90
|
+
_currentRenderPhase = null;
|
|
91
|
+
_asyncEffects = [];
|
|
90
92
|
constructor(props) {
|
|
91
93
|
this._renderId = `${globalRenderCounter++}`;
|
|
92
94
|
this.children = [];
|
|
93
95
|
this.renderPhaseStates = {};
|
|
94
96
|
for (const phase of orderedRenderPhases) {
|
|
95
|
-
this.renderPhaseStates[phase] = {
|
|
97
|
+
this.renderPhaseStates[phase] = {
|
|
98
|
+
initialized: false,
|
|
99
|
+
dirty: false
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
_markDirty(phase) {
|
|
104
|
+
this.renderPhaseStates[phase].dirty = true;
|
|
105
|
+
const phaseIndex = orderedRenderPhases.indexOf(phase);
|
|
106
|
+
for (let i = phaseIndex + 1; i < orderedRenderPhases.length; i++) {
|
|
107
|
+
this.renderPhaseStates[orderedRenderPhases[i]].dirty = true;
|
|
96
108
|
}
|
|
97
109
|
}
|
|
110
|
+
_queueAsyncEffect(effect) {
|
|
111
|
+
const asyncEffect = {
|
|
112
|
+
promise: effect(),
|
|
113
|
+
// TODO don't start effects until end of render cycle
|
|
114
|
+
phase: this._currentRenderPhase,
|
|
115
|
+
complete: false
|
|
116
|
+
};
|
|
117
|
+
this._asyncEffects.push(asyncEffect);
|
|
118
|
+
asyncEffect.promise.then(() => {
|
|
119
|
+
asyncEffect.complete = true;
|
|
120
|
+
if ("root" in this && this.root) {
|
|
121
|
+
;
|
|
122
|
+
this.root.emit("asyncEffectComplete", {
|
|
123
|
+
component: this,
|
|
124
|
+
asyncEffect
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}).catch((error) => {
|
|
128
|
+
console.error(
|
|
129
|
+
`Async effect error in ${this._currentRenderPhase}:`,
|
|
130
|
+
error
|
|
131
|
+
);
|
|
132
|
+
asyncEffect.complete = true;
|
|
133
|
+
if ("root" in this && this.root) {
|
|
134
|
+
;
|
|
135
|
+
this.root.emit("asyncEffectComplete", {
|
|
136
|
+
component: this,
|
|
137
|
+
asyncEffect
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
_hasIncompleteAsyncEffects() {
|
|
143
|
+
return this._asyncEffects.some((effect) => !effect.complete);
|
|
144
|
+
}
|
|
98
145
|
runRenderCycle() {
|
|
99
146
|
for (const renderPhase of orderedRenderPhases) {
|
|
100
147
|
this.runRenderPhaseForChildren(renderPhase);
|
|
@@ -109,22 +156,35 @@ var Renderable = class {
|
|
|
109
156
|
* ...depending on the current state of the component.
|
|
110
157
|
*/
|
|
111
158
|
runRenderPhase(phase) {
|
|
112
|
-
|
|
159
|
+
this._currentRenderPhase = phase;
|
|
160
|
+
const phaseState = this.renderPhaseStates[phase];
|
|
161
|
+
const isInitialized = phaseState.initialized;
|
|
162
|
+
const isDirty = phaseState.dirty;
|
|
113
163
|
if (!isInitialized && this.shouldBeRemoved) return;
|
|
114
164
|
if (this.shouldBeRemoved && isInitialized) {
|
|
115
165
|
;
|
|
116
166
|
this?.[`remove${phase}`]?.();
|
|
117
|
-
|
|
167
|
+
phaseState.initialized = false;
|
|
168
|
+
phaseState.dirty = false;
|
|
118
169
|
return;
|
|
119
170
|
}
|
|
171
|
+
const prevPhaseIndex = orderedRenderPhases.indexOf(phase) - 1;
|
|
172
|
+
if (prevPhaseIndex >= 0) {
|
|
173
|
+
const prevPhase = orderedRenderPhases[prevPhaseIndex];
|
|
174
|
+
const hasIncompleteEffects = this._asyncEffects.filter((e) => e.phase === prevPhase).some((e) => !e.complete);
|
|
175
|
+
if (hasIncompleteEffects) return;
|
|
176
|
+
}
|
|
120
177
|
if (isInitialized) {
|
|
121
|
-
|
|
122
|
-
|
|
178
|
+
if (isDirty) {
|
|
179
|
+
;
|
|
180
|
+
this?.[`update${phase}`]?.();
|
|
181
|
+
phaseState.dirty = false;
|
|
182
|
+
}
|
|
123
183
|
return;
|
|
124
184
|
}
|
|
125
|
-
;
|
|
185
|
+
phaseState.dirty = false;
|
|
126
186
|
this?.[`doInitial${phase}`]?.();
|
|
127
|
-
|
|
187
|
+
phaseState.initialized = true;
|
|
128
188
|
}
|
|
129
189
|
runRenderPhaseForChildren(phase) {
|
|
130
190
|
for (const child of this.children) {
|
|
@@ -1543,9 +1603,10 @@ var Port = class extends PrimitiveComponent {
|
|
|
1543
1603
|
console.warn(`Could not find parent symbol for ${this}`);
|
|
1544
1604
|
return { x: 0, y: 0 };
|
|
1545
1605
|
}
|
|
1606
|
+
const offsetY = -0.045;
|
|
1546
1607
|
const transform = compose3(
|
|
1547
1608
|
this.parent.computeSchematicGlobalTransform(),
|
|
1548
|
-
translate3(-symbol.center.x, -symbol.center.y)
|
|
1609
|
+
translate3(-symbol.center.x, -symbol.center.y + offsetY)
|
|
1549
1610
|
);
|
|
1550
1611
|
return applyToPoint4(transform, this.schematicSymbolPortDef);
|
|
1551
1612
|
}
|
|
@@ -2554,11 +2615,49 @@ var stringProxy = new Proxy(
|
|
|
2554
2615
|
var FTYPE = stringProxy;
|
|
2555
2616
|
|
|
2556
2617
|
// lib/components/primitive-components/Trace.ts
|
|
2557
|
-
import { traceProps } from "@tscircuit/props";
|
|
2558
2618
|
import {
|
|
2559
2619
|
MultilayerIjump,
|
|
2560
2620
|
getObstaclesFromSoup
|
|
2561
2621
|
} from "@tscircuit/infgrid-ijump-astar";
|
|
2622
|
+
import { traceProps } from "@tscircuit/props";
|
|
2623
|
+
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map";
|
|
2624
|
+
|
|
2625
|
+
// lib/utils/autorouting/DirectLineRouter.ts
|
|
2626
|
+
var DirectLineRouter = class {
|
|
2627
|
+
input;
|
|
2628
|
+
constructor({ input }) {
|
|
2629
|
+
this.input = input;
|
|
2630
|
+
}
|
|
2631
|
+
solveAndMapToTraces() {
|
|
2632
|
+
const traces = [];
|
|
2633
|
+
for (const connection of this.input.connections) {
|
|
2634
|
+
if (connection.pointsToConnect.length !== 2) continue;
|
|
2635
|
+
const [start, end] = connection.pointsToConnect;
|
|
2636
|
+
const trace = {
|
|
2637
|
+
type: "pcb_trace",
|
|
2638
|
+
pcb_trace_id: "",
|
|
2639
|
+
route: [
|
|
2640
|
+
{
|
|
2641
|
+
route_type: "wire",
|
|
2642
|
+
x: start.x,
|
|
2643
|
+
y: start.y,
|
|
2644
|
+
layer: "top",
|
|
2645
|
+
width: 0.1
|
|
2646
|
+
},
|
|
2647
|
+
{
|
|
2648
|
+
route_type: "wire",
|
|
2649
|
+
x: end.x,
|
|
2650
|
+
y: end.y,
|
|
2651
|
+
layer: "top",
|
|
2652
|
+
width: 0.1
|
|
2653
|
+
}
|
|
2654
|
+
]
|
|
2655
|
+
};
|
|
2656
|
+
traces.push(trace);
|
|
2657
|
+
}
|
|
2658
|
+
return traces;
|
|
2659
|
+
}
|
|
2660
|
+
};
|
|
2562
2661
|
|
|
2563
2662
|
// lib/utils/autorouting/computeObstacleBounds.ts
|
|
2564
2663
|
var computeObstacleBounds = (obstacles) => {
|
|
@@ -2569,22 +2668,6 @@ var computeObstacleBounds = (obstacles) => {
|
|
|
2569
2668
|
return { minX, maxX, minY, maxY };
|
|
2570
2669
|
};
|
|
2571
2670
|
|
|
2572
|
-
// lib/utils/projectPointInDirection.ts
|
|
2573
|
-
var projectPointInDirection = (point, direction, distance) => {
|
|
2574
|
-
switch (direction) {
|
|
2575
|
-
case "up":
|
|
2576
|
-
return { x: point.x, y: point.y - distance };
|
|
2577
|
-
case "down":
|
|
2578
|
-
return { x: point.x, y: point.y + distance };
|
|
2579
|
-
case "left":
|
|
2580
|
-
return { x: point.x - distance, y: point.y };
|
|
2581
|
-
case "right":
|
|
2582
|
-
return { x: point.x + distance, y: point.y };
|
|
2583
|
-
default:
|
|
2584
|
-
throw new Error(`Unknown direction "${direction}"`);
|
|
2585
|
-
}
|
|
2586
|
-
};
|
|
2587
|
-
|
|
2588
2671
|
// lib/utils/autorouting/findPossibleTraceLayerCombinations.ts
|
|
2589
2672
|
var LAYER_SELECTION_PREFERENCE = ["top", "bottom", "inner1", "inner2"];
|
|
2590
2673
|
var findPossibleTraceLayerCombinations = (hints, layer_path = []) => {
|
|
@@ -2718,8 +2801,37 @@ function getClosest(point, candidates) {
|
|
|
2718
2801
|
return closest;
|
|
2719
2802
|
}
|
|
2720
2803
|
|
|
2721
|
-
// lib/
|
|
2722
|
-
|
|
2804
|
+
// lib/utils/projectPointInDirection.ts
|
|
2805
|
+
var projectPointInDirection = (point, direction, distance) => {
|
|
2806
|
+
switch (direction) {
|
|
2807
|
+
case "up":
|
|
2808
|
+
return { x: point.x, y: point.y + distance };
|
|
2809
|
+
case "down":
|
|
2810
|
+
return { x: point.x, y: point.y - distance };
|
|
2811
|
+
case "left":
|
|
2812
|
+
return { x: point.x + distance, y: point.y };
|
|
2813
|
+
case "right":
|
|
2814
|
+
return { x: point.x - distance, y: point.y };
|
|
2815
|
+
default:
|
|
2816
|
+
throw new Error(`Unknown direction "${direction}"`);
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
|
|
2820
|
+
// lib/utils/projectPointInOppositeDirection.ts
|
|
2821
|
+
var projectPointInOppositeDirection = (point, direction, distance) => {
|
|
2822
|
+
switch (direction) {
|
|
2823
|
+
case "up":
|
|
2824
|
+
return { x: point.x, y: point.y + distance };
|
|
2825
|
+
case "down":
|
|
2826
|
+
return { x: point.x, y: point.y - distance };
|
|
2827
|
+
case "left":
|
|
2828
|
+
return { x: point.x + distance, y: point.y };
|
|
2829
|
+
case "right":
|
|
2830
|
+
return { x: point.x - distance, y: point.y };
|
|
2831
|
+
default:
|
|
2832
|
+
throw new Error(`Unknown direction "${direction}"`);
|
|
2833
|
+
}
|
|
2834
|
+
};
|
|
2723
2835
|
|
|
2724
2836
|
// lib/utils/try-now.ts
|
|
2725
2837
|
function tryNow(fn) {
|
|
@@ -2731,46 +2843,7 @@ function tryNow(fn) {
|
|
|
2731
2843
|
}
|
|
2732
2844
|
|
|
2733
2845
|
// lib/components/primitive-components/Trace.ts
|
|
2734
|
-
import
|
|
2735
|
-
|
|
2736
|
-
// lib/utils/autorouting/DirectLineRouter.ts
|
|
2737
|
-
var DirectLineRouter = class {
|
|
2738
|
-
input;
|
|
2739
|
-
constructor({ input }) {
|
|
2740
|
-
this.input = input;
|
|
2741
|
-
}
|
|
2742
|
-
solveAndMapToTraces() {
|
|
2743
|
-
const traces = [];
|
|
2744
|
-
for (const connection of this.input.connections) {
|
|
2745
|
-
if (connection.pointsToConnect.length !== 2) continue;
|
|
2746
|
-
const [start, end] = connection.pointsToConnect;
|
|
2747
|
-
const trace = {
|
|
2748
|
-
type: "pcb_trace",
|
|
2749
|
-
pcb_trace_id: "",
|
|
2750
|
-
route: [
|
|
2751
|
-
{
|
|
2752
|
-
route_type: "wire",
|
|
2753
|
-
x: start.x,
|
|
2754
|
-
y: start.y,
|
|
2755
|
-
layer: "top",
|
|
2756
|
-
width: 0.1
|
|
2757
|
-
},
|
|
2758
|
-
{
|
|
2759
|
-
route_type: "wire",
|
|
2760
|
-
x: end.x,
|
|
2761
|
-
y: end.y,
|
|
2762
|
-
layer: "top",
|
|
2763
|
-
width: 0.1
|
|
2764
|
-
}
|
|
2765
|
-
]
|
|
2766
|
-
};
|
|
2767
|
-
traces.push(trace);
|
|
2768
|
-
}
|
|
2769
|
-
return traces;
|
|
2770
|
-
}
|
|
2771
|
-
};
|
|
2772
|
-
|
|
2773
|
-
// lib/components/primitive-components/Trace.ts
|
|
2846
|
+
import "zod";
|
|
2774
2847
|
var portToObjective = (port) => {
|
|
2775
2848
|
const portPosition = port._getGlobalPcbPositionAfterLayout();
|
|
2776
2849
|
return {
|
|
@@ -3204,17 +3277,19 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
|
|
|
3204
3277
|
});
|
|
3205
3278
|
}
|
|
3206
3279
|
}
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
layer: "top"
|
|
3216
|
-
});
|
|
3280
|
+
const portsWithPosition = ports.map(({ port }) => ({
|
|
3281
|
+
port,
|
|
3282
|
+
position: port._getGlobalSchematicPositionAfterLayout(),
|
|
3283
|
+
schematic_port_id: port.schematic_port_id ?? void 0,
|
|
3284
|
+
facingDirection: port.facingDirection
|
|
3285
|
+
}));
|
|
3286
|
+
if (portsWithPosition.length < 2) {
|
|
3287
|
+
return;
|
|
3217
3288
|
}
|
|
3289
|
+
connection.pointsToConnect = portsWithPosition.map(({ position }) => ({
|
|
3290
|
+
...position,
|
|
3291
|
+
layer: "top"
|
|
3292
|
+
}));
|
|
3218
3293
|
const bounds = computeObstacleBounds(obstacles);
|
|
3219
3294
|
const simpleRouteJsonInput = {
|
|
3220
3295
|
minTraceWidth: 0.1,
|
|
@@ -3223,19 +3298,6 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
|
|
|
3223
3298
|
bounds,
|
|
3224
3299
|
layerCount: 1
|
|
3225
3300
|
};
|
|
3226
|
-
if (this.getSubcircuit().props._schDebugObjectsEnabled) {
|
|
3227
|
-
for (const obstacle of obstacles) {
|
|
3228
|
-
db.schematic_debug_object.insert({
|
|
3229
|
-
shape: "rect",
|
|
3230
|
-
center: obstacle.center,
|
|
3231
|
-
size: {
|
|
3232
|
-
width: obstacle.width,
|
|
3233
|
-
height: obstacle.height
|
|
3234
|
-
},
|
|
3235
|
-
label: "obstacle"
|
|
3236
|
-
});
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
3239
3301
|
let Autorouter = MultilayerIjump;
|
|
3240
3302
|
if (this.getSubcircuit().props._schDirectLineRoutingEnabled) {
|
|
3241
3303
|
Autorouter = DirectLineRouter;
|
|
@@ -3250,14 +3312,34 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
|
|
|
3250
3312
|
const { route } = result;
|
|
3251
3313
|
const edges = [];
|
|
3252
3314
|
for (let i = 0; i < route.length - 1; i++) {
|
|
3253
|
-
const from = route[i];
|
|
3254
|
-
const to = route[i + 1];
|
|
3255
3315
|
edges.push({
|
|
3256
|
-
from,
|
|
3257
|
-
to
|
|
3258
|
-
// TODO to_schematic_port_id and from_schematic_port_id
|
|
3316
|
+
from: route[i],
|
|
3317
|
+
to: route[i + 1]
|
|
3259
3318
|
});
|
|
3260
3319
|
}
|
|
3320
|
+
const STUB_LENGTH = 0.15;
|
|
3321
|
+
edges.unshift({
|
|
3322
|
+
from: {
|
|
3323
|
+
...projectPointInDirection(
|
|
3324
|
+
route[0],
|
|
3325
|
+
portsWithPosition[0].facingDirection,
|
|
3326
|
+
STUB_LENGTH
|
|
3327
|
+
)
|
|
3328
|
+
},
|
|
3329
|
+
to: route[0],
|
|
3330
|
+
from_schematic_port_id: portsWithPosition[0].schematic_port_id
|
|
3331
|
+
});
|
|
3332
|
+
edges.push({
|
|
3333
|
+
from: route[route.length - 1],
|
|
3334
|
+
to: {
|
|
3335
|
+
...projectPointInOppositeDirection(
|
|
3336
|
+
route[route.length - 1],
|
|
3337
|
+
portsWithPosition[1].facingDirection,
|
|
3338
|
+
STUB_LENGTH
|
|
3339
|
+
)
|
|
3340
|
+
},
|
|
3341
|
+
from_schematic_port_id: portsWithPosition[1].schematic_port_id
|
|
3342
|
+
});
|
|
3261
3343
|
const trace = db.schematic_trace.insert({
|
|
3262
3344
|
source_trace_id: this.source_trace_id,
|
|
3263
3345
|
edges
|
|
@@ -4374,6 +4456,21 @@ var Circuit = class {
|
|
|
4374
4456
|
firstChild.runRenderCycle();
|
|
4375
4457
|
this._hasRenderedAtleastOnce = true;
|
|
4376
4458
|
}
|
|
4459
|
+
async renderUntilSettled() {
|
|
4460
|
+
this.render();
|
|
4461
|
+
while (this._hasIncompleteAsyncEffects()) {
|
|
4462
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
4463
|
+
this.render();
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
_hasIncompleteAsyncEffects() {
|
|
4467
|
+
return this.children.some((child) => {
|
|
4468
|
+
if (child._hasIncompleteAsyncEffects()) return true;
|
|
4469
|
+
return child.children.some(
|
|
4470
|
+
(grandchild) => grandchild._hasIncompleteAsyncEffects()
|
|
4471
|
+
);
|
|
4472
|
+
});
|
|
4473
|
+
}
|
|
4377
4474
|
getSoup() {
|
|
4378
4475
|
if (!this._hasRenderedAtleastOnce) this.render();
|
|
4379
4476
|
return this.db.toArray();
|
|
@@ -4410,6 +4507,19 @@ var Circuit = class {
|
|
|
4410
4507
|
selectOne(selector, opts) {
|
|
4411
4508
|
return this.firstChild?.selectOne(selector, opts) ?? null;
|
|
4412
4509
|
}
|
|
4510
|
+
_eventListeners = { asyncEffectComplete: [] };
|
|
4511
|
+
emit(event, ...args) {
|
|
4512
|
+
if (!this._eventListeners[event]) return;
|
|
4513
|
+
for (const listener of this._eventListeners[event]) {
|
|
4514
|
+
listener(...args);
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
on(event, listener) {
|
|
4518
|
+
if (!this._eventListeners[event]) {
|
|
4519
|
+
this._eventListeners[event] = [];
|
|
4520
|
+
}
|
|
4521
|
+
this._eventListeners[event].push(listener);
|
|
4522
|
+
}
|
|
4413
4523
|
};
|
|
4414
4524
|
var Project = Circuit;
|
|
4415
4525
|
|