canvasengine 1.3.0 → 2.0.0-beta.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.
Files changed (96) hide show
  1. package/package.json +51 -17
  2. package/src/components/Canvas.ts +134 -0
  3. package/src/components/Container.ts +46 -0
  4. package/src/components/DisplayObject.ts +458 -0
  5. package/src/components/DrawMap/index.ts +65 -0
  6. package/src/components/Graphic.ts +147 -0
  7. package/src/components/NineSliceSprite.ts +46 -0
  8. package/src/components/ParticleEmitter.ts +39 -0
  9. package/src/components/Scene.ts +6 -0
  10. package/src/components/Sprite.ts +493 -0
  11. package/src/components/Text.ts +145 -0
  12. package/src/components/Tilemap/Tile.ts +79 -0
  13. package/src/components/Tilemap/TileGroup.ts +207 -0
  14. package/src/components/Tilemap/TileLayer.ts +163 -0
  15. package/src/components/Tilemap/TileSet.ts +41 -0
  16. package/src/components/Tilemap/index.ts +80 -0
  17. package/src/components/TilingSprite.ts +39 -0
  18. package/src/components/Viewport.ts +159 -0
  19. package/src/components/index.ts +13 -0
  20. package/src/components/types/DisplayObject.ts +69 -0
  21. package/src/components/types/MouseEvent.ts +3 -0
  22. package/src/components/types/Spritesheet.ts +389 -0
  23. package/src/components/types/index.ts +4 -0
  24. package/src/directives/Drag.ts +84 -0
  25. package/src/directives/KeyboardControls.ts +922 -0
  26. package/src/directives/Scheduler.ts +101 -0
  27. package/src/directives/Sound.ts +91 -0
  28. package/src/directives/Transition.ts +45 -0
  29. package/src/directives/ViewportCull.ts +40 -0
  30. package/src/directives/ViewportFollow.ts +26 -0
  31. package/src/directives/index.ts +7 -0
  32. package/src/engine/animation.ts +113 -0
  33. package/src/engine/bootstrap.ts +19 -0
  34. package/src/engine/directive.ts +23 -0
  35. package/src/engine/reactive.ts +379 -0
  36. package/src/engine/signal.ts +138 -0
  37. package/src/engine/trigger.ts +40 -0
  38. package/src/engine/utils.ts +135 -0
  39. package/src/hooks/addContext.ts +6 -0
  40. package/src/hooks/useProps.ts +155 -0
  41. package/src/hooks/useRef.ts +21 -0
  42. package/src/index.ts +13 -0
  43. package/src/utils/Ease.ts +33 -0
  44. package/src/utils/RadialGradient.ts +86 -0
  45. package/.gitattributes +0 -22
  46. package/.npmignore +0 -163
  47. package/canvasengine-1.3.0.all.min.js +0 -21
  48. package/canvasengine.js +0 -5802
  49. package/core/DB.js +0 -24
  50. package/core/ModelServer.js +0 -348
  51. package/core/Users.js +0 -190
  52. package/core/engine-common.js +0 -952
  53. package/doc/cocoonjs.md +0 -36
  54. package/doc/doc-lang.yml +0 -43
  55. package/doc/doc-router.yml +0 -14
  56. package/doc/doc-tuto.yml +0 -9
  57. package/doc/doc.yml +0 -39
  58. package/doc/element.md +0 -37
  59. package/doc/entity.md +0 -90
  60. package/doc/extend.md +0 -47
  61. package/doc/get_started.md +0 -19
  62. package/doc/images/entity.png +0 -0
  63. package/doc/multitouch.md +0 -58
  64. package/doc/nodejs.md +0 -142
  65. package/doc/scene.md +0 -44
  66. package/doc/text.md +0 -156
  67. package/examples/server/client.html +0 -31
  68. package/examples/server/server.js +0 -16
  69. package/examples/tiled_server/client.html +0 -52
  70. package/examples/tiled_server/images/tiles_spritesheet.png +0 -0
  71. package/examples/tiled_server/server/map.json +0 -50
  72. package/examples/tiled_server/server/map.tmx +0 -16
  73. package/examples/tiled_server/server/server.js +0 -16
  74. package/extends/Animation.js +0 -910
  75. package/extends/Effect.js +0 -252
  76. package/extends/Gleed2d.js +0 -252
  77. package/extends/Hit.js +0 -1509
  78. package/extends/Input.js +0 -699
  79. package/extends/Marshal.js +0 -716
  80. package/extends/Scrolling.js +0 -388
  81. package/extends/Soundmanager2.js +0 -5466
  82. package/extends/Spritesheet.js +0 -196
  83. package/extends/Text.js +0 -366
  84. package/extends/Tiled.js +0 -403
  85. package/extends/Window.js +0 -575
  86. package/extends/gamepad.js +0 -397
  87. package/extends/socket.io.min.js +0 -2
  88. package/extends/swf/soundmanager2.swf +0 -0
  89. package/extends/swf/soundmanager2_debug.swf +0 -0
  90. package/extends/swf/soundmanager2_flash9.swf +0 -0
  91. package/extends/swf/soundmanager2_flash9_debug.swf +0 -0
  92. package/extends/swf/soundmanager2_flash_xdomain.zip +0 -0
  93. package/extends/workers/transition.js +0 -43
  94. package/index.js +0 -46
  95. package/license.txt +0 -19
  96. package/readme.md +0 -483
@@ -0,0 +1,379 @@
1
+ import { Signal, WritableArraySignal, isSignal } from "@signe/reactive";
2
+ import {
3
+ Observable,
4
+ Subject,
5
+ Subscription,
6
+ defer,
7
+ from,
8
+ map,
9
+ of,
10
+ switchMap,
11
+ } from "rxjs";
12
+ import { ComponentInstance } from "../components/DisplayObject";
13
+ import { Directive, applyDirective } from "./directive";
14
+ import { isObject, isPromise, set } from "./utils";
15
+
16
+ export interface Props {
17
+ [key: string]: any;
18
+ }
19
+
20
+ export type ArrayChange<T> = {
21
+ type: "add" | "remove" | "update" | "init" | "reset";
22
+ index?: number;
23
+ items: T[];
24
+ };
25
+
26
+ type ElementObservable<T> = Observable<
27
+ ArrayChange<T> & {
28
+ value: Element | Element[];
29
+ }
30
+ >;
31
+
32
+ type NestedSignalObjects = {
33
+ [Key in string]: NestedSignalObjects | Signal<any>;
34
+ };
35
+
36
+ export interface Element<T = ComponentInstance> {
37
+ tag: string;
38
+ props: Props;
39
+ componentInstance: T;
40
+ propSubscriptions: Subscription[];
41
+ effectSubscriptions: Subscription[];
42
+ effectMounts: (() => void)[];
43
+ effectUnmounts: ((element?: Element) => void)[];
44
+ propObservables: NestedSignalObjects | undefined;
45
+ parent: Element | null;
46
+ context?: {
47
+ [key: string]: any;
48
+ };
49
+ directives: {
50
+ [key: string]: Directive;
51
+ };
52
+ destroy: () => void;
53
+ allElements: Subject<void>;
54
+ }
55
+
56
+ type FlowObservable = Observable<{
57
+ elements: Element[];
58
+ prev?: Element;
59
+ }>;
60
+
61
+ const components: { [key: string]: any } = {};
62
+
63
+ export const isElement = (value: any): value is Element => {
64
+ return (
65
+ value &&
66
+ typeof value === "object" &&
67
+ "tag" in value &&
68
+ "props" in value &&
69
+ "componentInstance" in value
70
+ );
71
+ };
72
+
73
+ export const isPrimitive = (value) => {
74
+ return (
75
+ typeof value === "string" ||
76
+ typeof value === "number" ||
77
+ typeof value === "boolean" ||
78
+ value === null ||
79
+ value === undefined
80
+ );
81
+ };
82
+
83
+ export function registerComponent(name, component) {
84
+ components[name] = component;
85
+ }
86
+
87
+ function destroyElement(element: Element | Element[]) {
88
+ if (Array.isArray(element)) {
89
+ element.forEach((e) => destroyElement(e));
90
+ return;
91
+ }
92
+ if (!element) {
93
+ return;
94
+ }
95
+ element.propSubscriptions.forEach((sub) => sub.unsubscribe());
96
+ element.effectSubscriptions.forEach((sub) => sub.unsubscribe());
97
+ for (let name in element.directives) {
98
+ element.directives[name].onDestroy?.();
99
+ }
100
+ element.componentInstance.onDestroy?.(element.parent as any);
101
+ element.effectUnmounts.forEach((fn) => fn?.());
102
+ }
103
+
104
+ /**
105
+ * Creates a virtual element or a representation thereof, with properties that can be dynamically updated based on BehaviorSubjects.
106
+ *
107
+ * @param {string} tag - The tag name of the element to create.
108
+ * @param {Object} props - An object containing properties for the element. Each property can either be a direct value
109
+ * or an array where the first element is a function that returns a value based on input parameters,
110
+ * and the second element is an array of BehaviorSubjects. The property is updated dynamically
111
+ * using the combineLatest RxJS operator to wait for all BehaviorSubjects to emit.
112
+ * @returns {Object} An object representing the created element, including tag name and dynamic properties.
113
+ */
114
+ export function createComponent(tag: string, props?: Props): Element {
115
+ if (!components[tag]) {
116
+ throw new Error(`Component ${tag} is not registered`);
117
+ }
118
+ const instance = new components[tag]();
119
+ const element: Element = {
120
+ tag,
121
+ props: {},
122
+ componentInstance: instance,
123
+ propSubscriptions: [],
124
+ propObservables: props,
125
+ parent: null,
126
+ directives: {},
127
+ effectUnmounts: [],
128
+ effectSubscriptions: [],
129
+ effectMounts: [],
130
+ destroy() {
131
+ destroyElement(this);
132
+ },
133
+ allElements: new Subject(),
134
+ };
135
+
136
+ // Iterate over each property in the props object
137
+ if (props) {
138
+ const recursiveProps = (props, path = "") => {
139
+ const _set = (path, key, value) => {
140
+ if (path == "") {
141
+ element.props[key] = value;
142
+ return;
143
+ }
144
+ set(element.props, path + "." + key, value);
145
+ };
146
+
147
+ Object.entries(props).forEach(([key, value]: [string, unknown]) => {
148
+ if (isSignal(value)) {
149
+ const _value = value as Signal<any>;
150
+ if ("dependencies" in _value && _value.dependencies.size == 0) {
151
+ _set(path, key, _value());
152
+ return;
153
+ }
154
+ element.propSubscriptions.push(
155
+ _value.observable.subscribe((value) => {
156
+ _set(path, key, value);
157
+ if (element.directives[key]) {
158
+ element.directives[key].onUpdate?.(value);
159
+ }
160
+ if (key == "tick") {
161
+ return
162
+ }
163
+ instance.onUpdate?.(
164
+ path == ""
165
+ ? {
166
+ [key]: value,
167
+ }
168
+ : set({}, path + "." + key, value)
169
+ );
170
+ })
171
+ );
172
+ } else {
173
+ if (isObject(value) && key != "context" && !isElement(value)) {
174
+ recursiveProps(value, (path ? path + "." : "") + key);
175
+ } else {
176
+ _set(path, key, value);
177
+ }
178
+ }
179
+ });
180
+ };
181
+ recursiveProps(props);
182
+ }
183
+
184
+ instance.onInit?.(element.props);
185
+ instance.onUpdate?.(element.props);
186
+
187
+ const onMount = (parent: Element, element: Element, index?: number) => {
188
+ element.props.context = parent.props.context;
189
+ element.parent = parent;
190
+ element.componentInstance.onMount?.(element, index);
191
+ for (let name in element.directives) {
192
+ element.directives[name].onMount?.(element);
193
+ }
194
+ element.effectMounts.forEach((fn: any) => {
195
+ element.effectUnmounts.push(fn(element));
196
+ });
197
+ };
198
+
199
+ const elementsListen = new Subject<any>()
200
+
201
+ if (props?.isRoot) {
202
+ // propagate recrusively context in all children
203
+ const propagateContext = async (element) => {
204
+ if (!element.props.children) {
205
+ return;
206
+ }
207
+ for (let child of element.props.children) {
208
+ if (!child) continue;
209
+ if (isPromise(child)) {
210
+ child = await child;
211
+ }
212
+ if (child instanceof Observable) {
213
+ child.subscribe(
214
+ ({
215
+ elements: comp,
216
+ prev,
217
+ }: {
218
+ elements: Element[];
219
+ prev?: Element;
220
+ }) => {
221
+ // if prev, insert element after this
222
+ const components = comp.filter((c) => c !== null);
223
+ if (prev) {
224
+ components.forEach((c) => {
225
+ const index = element.props.children.indexOf(prev.props.key);
226
+ onMount(element, c, index + 1);
227
+ propagateContext(c);
228
+ });
229
+ return;
230
+ }
231
+ components.forEach((component) => {
232
+ if (!Array.isArray(component)) {
233
+ onMount(element, component);
234
+ propagateContext(component);
235
+ } else {
236
+ component.forEach((comp) => {
237
+ onMount(element, comp);
238
+ propagateContext(comp);
239
+ });
240
+ }
241
+ });
242
+ elementsListen.next(undefined)
243
+ }
244
+ );
245
+ } else {
246
+ onMount(element, child);
247
+ await propagateContext(child);
248
+ }
249
+ }
250
+ };
251
+ element.allElements = elementsListen
252
+ element.props.context.rootElement = element;
253
+ element.componentInstance.onMount?.(element);
254
+ propagateContext(element);
255
+ }
256
+
257
+ if (props) {
258
+ for (let key in props) {
259
+ const directive = applyDirective(element, key);
260
+ if (directive) element.directives[key] = directive;
261
+ }
262
+ }
263
+
264
+ // Return the created element representation
265
+ return element;
266
+ }
267
+
268
+ /**
269
+ * Observes a BehaviorSubject containing an array of items and dynamically creates child elements for each item.
270
+ *
271
+ * @param {BehaviorSubject<Array>} itemsSubject - A BehaviorSubject that emits an array of items.
272
+ * @param {Function} createElementFn - A function that takes an item and returns an element representation.
273
+ * @returns {Observable} An observable that emits the list of created child elements.
274
+ */
275
+ export function loop<T = any>(
276
+ itemsSubject: WritableArraySignal<T>,
277
+ createElementFn: (item: any, index: number) => Element | Promise<Element>
278
+ ): FlowObservable {
279
+ let elements: Element[] = [];
280
+
281
+ const addAt = (items, insertIndex: number) => {
282
+ return items.map((item, index) => {
283
+ const element = createElementFn(item, insertIndex + index);
284
+ elements.splice(insertIndex + index, 0, element as Element);
285
+ return element;
286
+ });
287
+ };
288
+
289
+ return defer(() => {
290
+ let initialItems = [...itemsSubject._subject.items];
291
+ let init = true;
292
+ return itemsSubject.observable.pipe(
293
+ map((event: ArrayChange<T>) => {
294
+ const { type, items, index } = event;
295
+ if (init) {
296
+ if (elements.length > 0) {
297
+ return {
298
+ elements: elements,
299
+ fullElements: elements,
300
+ };
301
+ }
302
+ const newElements = addAt(initialItems, 0);
303
+ initialItems = [];
304
+ init = false;
305
+ return {
306
+ elements: newElements,
307
+ fullElements: elements,
308
+ };
309
+ } else if (type == "reset") {
310
+ if (elements.length != 0) {
311
+ elements.forEach((element) => {
312
+ destroyElement(element);
313
+ });
314
+ elements = [];
315
+ }
316
+ const newElements = addAt(items, 0);
317
+ return {
318
+ elements: newElements,
319
+ fullElements: elements,
320
+ };
321
+ } else if (type == "add" && index != undefined) {
322
+ const lastElement = elements[index - 1];
323
+ const newElements = addAt(items, index);
324
+ return {
325
+ prev: lastElement,
326
+ elements: newElements,
327
+ fullElements: elements,
328
+ };
329
+ } else if (index != undefined && type == "remove") {
330
+ const currentElement = elements[index];
331
+ destroyElement(currentElement);
332
+ elements.splice(index, 1);
333
+ return {
334
+ elements: [],
335
+ };
336
+ }
337
+ return {
338
+ elements: [],
339
+ fullElements: elements,
340
+ };
341
+ })
342
+ );
343
+ });
344
+ }
345
+
346
+ export function cond(
347
+ condition: Signal,
348
+ createElementFn: () => Element | Promise<Element>
349
+ ): FlowObservable {
350
+ let element: Element | null = null;
351
+ return (condition.observable as Observable<boolean>).pipe(
352
+ switchMap((bool) => {
353
+ if (bool) {
354
+ let _el = createElementFn();
355
+ if (isPromise(_el)) {
356
+ return from(_el as Promise<Element>).pipe(
357
+ map((el) => {
358
+ element = _el as Element;
359
+ return {
360
+ type: "init",
361
+ elements: [el],
362
+ };
363
+ })
364
+ );
365
+ }
366
+ element = _el as Element;
367
+ return of({
368
+ type: "init",
369
+ elements: [element],
370
+ });
371
+ } else if (element) {
372
+ destroyElement(element);
373
+ }
374
+ return of({
375
+ elements: [],
376
+ });
377
+ })
378
+ );
379
+ }
@@ -0,0 +1,138 @@
1
+ import {
2
+ Subscription
3
+ } from "rxjs";
4
+ import type { Element } from "./reactive";
5
+ import { Tick } from "../directives/Scheduler";
6
+
7
+ type MountFunction = (fn: (element: Element) => void) => void;
8
+
9
+ // Define ComponentFunction type
10
+ export type ComponentFunction<P = {}> = (props: P) => Element | Promise<Element>;
11
+
12
+ export let currentSubscriptionsTracker: ((subscription: Subscription) => void) | null = null;
13
+ export let mountTracker: MountFunction | null = null;
14
+
15
+ /**
16
+ * Registers a mount function to be called when the component is mounted.
17
+ * To unmount the component, the function must return a function that will be called by the engine.
18
+ *
19
+ * @param {(element: Element) => void} fn - The function to be called on mount.
20
+ * @example
21
+ * ```ts
22
+ * mount((el) => {
23
+ * console.log('mounted', el);
24
+ * });
25
+ * ```
26
+ * Unmount the component by returning a function:
27
+ * ```ts
28
+ * mount((el) => {
29
+ * console.log('mounted', el);
30
+ * return () => {
31
+ * console.log('unmounted', el);
32
+ * }
33
+ * });
34
+ * ```
35
+ */
36
+ export function mount(fn: (element: Element) => void) {
37
+ mountTracker?.(fn);
38
+ }
39
+
40
+ /**
41
+ * Registers a tick function to be called on each tick of the component's context.
42
+ * @param {(tickValue: Tick, element: Element) => void} fn - The function to be called on each tick.
43
+ * @example
44
+ * ```ts
45
+ * tick((tickValue, el) => {
46
+ * console.log('tick', tickValue, el);
47
+ * });
48
+ * ```
49
+ */
50
+ export function tick(fn: (tickValue: Tick, element: Element) => void) {
51
+ mount((el: Element) => {
52
+ const { context } = el.props
53
+ let subscription: Subscription | undefined
54
+ if (context.tick) {
55
+ subscription = context.tick.observable.subscribe(({ value }) => {
56
+ fn(value, el)
57
+ })
58
+ }
59
+ return () => {
60
+ subscription?.unsubscribe()
61
+ }
62
+ })
63
+ }
64
+
65
+ /**
66
+ * Add tracking for subscriptions and mounts, then create an element from a component function.
67
+ * @template C
68
+ * @param {C} componentFunction - The component function to create an element from.
69
+ * @param {Parameters<C>[0]} [props={}] - The props to pass to the component function.
70
+ * @param {...any[]} children - The children elements of the component.
71
+ * @returns {ReturnType<C>}
72
+ * @example
73
+ * ```ts
74
+ * const el = h(MyComponent, {
75
+ * x: 100,
76
+ * y: 100,
77
+ * });
78
+ * ```
79
+ *
80
+ * with children:
81
+ * ```ts
82
+ * const el = h(MyComponent, {
83
+ * x: 100,
84
+ * y: 100,
85
+ * },
86
+ * h(MyChildComponent, {
87
+ * x: 50,
88
+ * y: 50,
89
+ * }),
90
+ * );
91
+ * ```
92
+ */
93
+ export function h<C extends ComponentFunction<any>>(
94
+ componentFunction: C,
95
+ props: Parameters<C>[0] = {} as Parameters<C>[0],
96
+ ...children: any[]
97
+ ): ReturnType<C> {
98
+ const allSubscriptions = new Set<Subscription>();
99
+ const allMounts = new Set<MountFunction>();
100
+
101
+ currentSubscriptionsTracker = (subscription) => {
102
+ allSubscriptions.add(subscription);
103
+ };
104
+
105
+ mountTracker = (fn: any) => {
106
+ allMounts.add(fn);
107
+ };
108
+
109
+ if (children[0] instanceof Array) {
110
+ children = children[0]
111
+ }
112
+
113
+ let component = componentFunction({ ...props, children }) as Element;
114
+
115
+ if (!component) {
116
+ component = {} as any
117
+ }
118
+
119
+ component.effectSubscriptions = Array.from(allSubscriptions);
120
+ component.effectMounts = [
121
+ ...Array.from(allMounts),
122
+ ...((component as any).effectMounts ?? [])
123
+ ];
124
+
125
+ // call mount hook for root component
126
+ if (component instanceof Promise) {
127
+ component.then((component) => {
128
+ if (component.props.isRoot) {
129
+ allMounts.forEach((fn) => fn(component));
130
+ }
131
+ })
132
+ }
133
+
134
+ currentSubscriptionsTracker = null;
135
+ mountTracker = null;
136
+
137
+ return component as ReturnType<C>;
138
+ }
@@ -0,0 +1,40 @@
1
+ import { effect, signal } from "@signe/reactive";
2
+
3
+ interface Listen<T = any> {
4
+ config: T | undefined;
5
+ seed: number;
6
+ }
7
+
8
+ interface Trigger<T = any> {
9
+ start: () => void;
10
+ listen: () => Listen<T> | undefined;
11
+ }
12
+
13
+ export function isTrigger(arg: any): arg is Trigger<any> {
14
+ return arg?.start && arg?.listen;
15
+ }
16
+
17
+ export function trigger<T = any>(config?: T): Trigger<T> {
18
+ const _signal = signal(0);
19
+ return {
20
+ start: () => {
21
+ _signal.set(Math.random());
22
+ },
23
+ listen: (): Listen<T> | undefined => {
24
+ return {
25
+ config,
26
+ seed: _signal(),
27
+ };
28
+ },
29
+ };
30
+ }
31
+
32
+ export function on(triggerSignal: any, callback: (config: any) => void) {
33
+ if (!isTrigger(triggerSignal)) {
34
+ throw new Error("In 'on(arg)' must have a trigger signal type");
35
+ }
36
+ effect(() => {
37
+ const result = triggerSignal.listen();
38
+ if (result?.seed) callback(result.config);
39
+ });
40
+ }
@@ -0,0 +1,135 @@
1
+ import { ObservablePoint } from "pixi.js"
2
+
3
+ export function isBrowser(): boolean {
4
+ return typeof window !== 'undefined'
5
+ }
6
+
7
+ export function preciseNow(): number {
8
+ return typeof performance !== 'undefined' ? performance.now() : Date.now()
9
+ }
10
+
11
+ export function fps2ms(fps: number): number {
12
+ return 1000 / fps
13
+ }
14
+
15
+ export function isPromise(value: any): boolean {
16
+ return value && value instanceof Promise
17
+ }
18
+
19
+ export function arrayEquals(a: any[], b: any[]): boolean {
20
+ if (a.length !== b.length) return false;
21
+ for (let i = 0; i < a.length; i++) {
22
+ const v = a[i];
23
+ const bv = b[i];
24
+ if (typeof v === 'object' && v !== null) {
25
+ if (typeof bv !== 'object' || bv === null) return false;
26
+ if (Array.isArray(v)) {
27
+ if (!Array.isArray(bv) || !arrayEquals(v, bv)) return false;
28
+ } else if (!objectEquals(v, bv)) {
29
+ return false;
30
+ }
31
+ } else if (v !== bv) {
32
+ return false;
33
+ }
34
+ }
35
+ return true;
36
+ }
37
+
38
+ function objectEquals(a: object, b: object): boolean {
39
+ const keysA = Object.keys(a);
40
+ const keysB = Object.keys(b);
41
+ if (keysA.length !== keysB.length) return false;
42
+ for (let key of keysA) {
43
+ if (!b.hasOwnProperty(key)) return false;
44
+ if (!deepEquals(a[key], b[key])) return false;
45
+ }
46
+ return true;
47
+ }
48
+
49
+ function deepEquals(a: any, b: any): boolean {
50
+ if (a === b) return true;
51
+ if (typeof a !== typeof b) return false;
52
+ if (typeof a === 'object' && a !== null) {
53
+ if (Array.isArray(a)) {
54
+ return Array.isArray(b) && arrayEquals(a, b);
55
+ }
56
+ return objectEquals(a, b);
57
+ }
58
+ return false;
59
+ }
60
+
61
+ export function isFunction(val: unknown): boolean {
62
+ return {}.toString.call(val) === '[object Function]'
63
+ }
64
+
65
+ export function isObject(val: unknown): boolean {
66
+ return typeof val == 'object' && val != null && !Array.isArray(val)
67
+ }
68
+
69
+ export function set(obj, path, value, onlyPlainObject = false) {
70
+ if (Object(obj) !== obj) return obj;
71
+
72
+ if (typeof path === 'string') {
73
+ path = path.split('.');
74
+ }
75
+
76
+ let len = path.length;
77
+ if (!len) return obj;
78
+
79
+ let current = obj;
80
+ for (let i = 0; i < len - 1; i++) {
81
+ let segment = path[i];
82
+ let nextSegment = path[i + 1];
83
+ let isNextNumeric = !isNaN(nextSegment) && isFinite(nextSegment);
84
+
85
+ if (!current[segment] || typeof current[segment] !== 'object') {
86
+ current[segment] = (isNextNumeric && !onlyPlainObject) ? [] : {};
87
+ }
88
+
89
+ current = current[segment];
90
+ }
91
+
92
+ current[path[len - 1]] = value;
93
+
94
+ return obj;
95
+ }
96
+
97
+ export function get(obj, path) {
98
+ const keys = path.split('.');
99
+ let current = obj;
100
+
101
+ for (let key of keys) {
102
+ if (current[key] === undefined) {
103
+ return undefined;
104
+ }
105
+ current = current[key];
106
+ }
107
+
108
+ return current;
109
+ }
110
+
111
+ export function log(text) {
112
+ console.log(text)
113
+ }
114
+
115
+ export function error(text) {
116
+ console.error(text)
117
+ }
118
+
119
+ export function setObservablePoint(observablePoint: ObservablePoint, point: { x: number, y: number } | number | [number, number]): void {
120
+ if (typeof point === 'number') {
121
+ observablePoint.set(point);
122
+ }
123
+ else if (Array.isArray(point)) {
124
+ observablePoint.set(point[0], point[1]);
125
+ }
126
+ else {
127
+ observablePoint.set(point.x, point.y);
128
+ }
129
+ }
130
+
131
+ export function calculateDistance(x1, y1, x2, y2) {
132
+ const dx = x1 - x2;
133
+ const dy = y1 - y2;
134
+ return Math.sqrt(dx * dx + dy * dy);
135
+ }
@@ -0,0 +1,6 @@
1
+ export const addContext = (element, key, value) => {
2
+ element.props.context = {
3
+ ...(element.props.context ?? {}),
4
+ [key]: value
5
+ }
6
+ }