canvasengine 1.3.0 → 2.0.1-beta.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.
Files changed (125) hide show
  1. package/.cursorrules +0 -0
  2. package/.github/workflows/ci.yml +29 -0
  3. package/README.md +79 -0
  4. package/dist/compiler/vite.js +119 -0
  5. package/dist/compiler/vite.js.map +1 -0
  6. package/dist/index.d.ts +846 -0
  7. package/dist/index.js +3340 -0
  8. package/dist/index.js.map +1 -0
  9. package/index.d.ts +6 -0
  10. package/logo.png +0 -0
  11. package/package.json +84 -18
  12. package/src/compiler/grammar.pegjs +180 -0
  13. package/src/compiler/vite.ts +166 -0
  14. package/src/components/Canvas.ts +134 -0
  15. package/src/components/Container.ts +46 -0
  16. package/src/components/DisplayObject.ts +458 -0
  17. package/src/components/DrawMap/index.ts +65 -0
  18. package/src/components/Graphic.ts +147 -0
  19. package/src/components/NineSliceSprite.ts +46 -0
  20. package/src/components/ParticleEmitter.ts +39 -0
  21. package/src/components/Scene.ts +6 -0
  22. package/src/components/Sprite.ts +493 -0
  23. package/src/components/Text.ts +145 -0
  24. package/src/components/Tilemap/Tile.ts +79 -0
  25. package/src/components/Tilemap/TileGroup.ts +207 -0
  26. package/src/components/Tilemap/TileLayer.ts +163 -0
  27. package/src/components/Tilemap/TileSet.ts +41 -0
  28. package/src/components/Tilemap/index.ts +80 -0
  29. package/src/components/TilingSprite.ts +39 -0
  30. package/src/components/Viewport.ts +159 -0
  31. package/src/components/index.ts +12 -0
  32. package/src/components/types/DisplayObject.ts +68 -0
  33. package/src/components/types/MouseEvent.ts +3 -0
  34. package/src/components/types/Spritesheet.ts +389 -0
  35. package/src/components/types/index.ts +4 -0
  36. package/src/directives/Drag.ts +84 -0
  37. package/src/directives/KeyboardControls.ts +922 -0
  38. package/src/directives/Scheduler.ts +112 -0
  39. package/src/directives/Sound.ts +91 -0
  40. package/src/directives/Transition.ts +45 -0
  41. package/src/directives/ViewportCull.ts +40 -0
  42. package/src/directives/ViewportFollow.ts +26 -0
  43. package/src/directives/index.ts +7 -0
  44. package/src/engine/animation.ts +113 -0
  45. package/src/engine/bootstrap.ts +19 -0
  46. package/src/engine/directive.ts +23 -0
  47. package/src/engine/reactive.ts +379 -0
  48. package/src/engine/signal.ts +138 -0
  49. package/src/engine/trigger.ts +40 -0
  50. package/src/engine/utils.ts +135 -0
  51. package/src/hooks/addContext.ts +6 -0
  52. package/src/hooks/useProps.ts +155 -0
  53. package/src/hooks/useRef.ts +21 -0
  54. package/src/index.ts +14 -0
  55. package/src/presets/Bar.ts +89 -0
  56. package/src/presets/Button.ts +0 -0
  57. package/src/presets/Joystick.ts +286 -0
  58. package/src/presets/NightAmbiant.ts +122 -0
  59. package/src/presets/Particle.ts +53 -0
  60. package/src/utils/Ease.ts +33 -0
  61. package/src/utils/RadialGradient.ts +86 -0
  62. package/starter/assets/logo.png +0 -0
  63. package/starter/components/app.ce +18 -0
  64. package/starter/components/hello.ce +35 -0
  65. package/starter/index.html +21 -0
  66. package/starter/main.ts +6 -0
  67. package/starter/package.json +20 -0
  68. package/starter/tsconfig.json +32 -0
  69. package/starter/vite.config.ts +12 -0
  70. package/tsconfig.json +32 -0
  71. package/tsconfig.node.json +10 -0
  72. package/tsup.config.ts +28 -0
  73. package/vitest.config.ts +12 -0
  74. package/.gitattributes +0 -22
  75. package/.npmignore +0 -163
  76. package/canvasengine-1.3.0.all.min.js +0 -21
  77. package/canvasengine.js +0 -5802
  78. package/core/DB.js +0 -24
  79. package/core/ModelServer.js +0 -348
  80. package/core/Users.js +0 -190
  81. package/core/engine-common.js +0 -952
  82. package/doc/cocoonjs.md +0 -36
  83. package/doc/doc-lang.yml +0 -43
  84. package/doc/doc-router.yml +0 -14
  85. package/doc/doc-tuto.yml +0 -9
  86. package/doc/doc.yml +0 -39
  87. package/doc/element.md +0 -37
  88. package/doc/entity.md +0 -90
  89. package/doc/extend.md +0 -47
  90. package/doc/get_started.md +0 -19
  91. package/doc/images/entity.png +0 -0
  92. package/doc/multitouch.md +0 -58
  93. package/doc/nodejs.md +0 -142
  94. package/doc/scene.md +0 -44
  95. package/doc/text.md +0 -156
  96. package/examples/server/client.html +0 -31
  97. package/examples/server/server.js +0 -16
  98. package/examples/tiled_server/client.html +0 -52
  99. package/examples/tiled_server/images/tiles_spritesheet.png +0 -0
  100. package/examples/tiled_server/server/map.json +0 -50
  101. package/examples/tiled_server/server/map.tmx +0 -16
  102. package/examples/tiled_server/server/server.js +0 -16
  103. package/extends/Animation.js +0 -910
  104. package/extends/Effect.js +0 -252
  105. package/extends/Gleed2d.js +0 -252
  106. package/extends/Hit.js +0 -1509
  107. package/extends/Input.js +0 -699
  108. package/extends/Marshal.js +0 -716
  109. package/extends/Scrolling.js +0 -388
  110. package/extends/Soundmanager2.js +0 -5466
  111. package/extends/Spritesheet.js +0 -196
  112. package/extends/Text.js +0 -366
  113. package/extends/Tiled.js +0 -403
  114. package/extends/Window.js +0 -575
  115. package/extends/gamepad.js +0 -397
  116. package/extends/socket.io.min.js +0 -2
  117. package/extends/swf/soundmanager2.swf +0 -0
  118. package/extends/swf/soundmanager2_debug.swf +0 -0
  119. package/extends/swf/soundmanager2_flash9.swf +0 -0
  120. package/extends/swf/soundmanager2_flash9_debug.swf +0 -0
  121. package/extends/swf/soundmanager2_flash_xdomain.zip +0 -0
  122. package/extends/workers/transition.js +0 -43
  123. package/index.js +0 -46
  124. package/license.txt +0 -19
  125. 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
+ }