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.
- package/.cursorrules +0 -0
- package/.github/workflows/ci.yml +29 -0
- package/README.md +79 -0
- package/dist/compiler/vite.js +119 -0
- package/dist/compiler/vite.js.map +1 -0
- package/dist/index.d.ts +846 -0
- package/dist/index.js +3340 -0
- package/dist/index.js.map +1 -0
- package/index.d.ts +6 -0
- package/logo.png +0 -0
- package/package.json +84 -18
- package/src/compiler/grammar.pegjs +180 -0
- package/src/compiler/vite.ts +166 -0
- package/src/components/Canvas.ts +134 -0
- package/src/components/Container.ts +46 -0
- package/src/components/DisplayObject.ts +458 -0
- package/src/components/DrawMap/index.ts +65 -0
- package/src/components/Graphic.ts +147 -0
- package/src/components/NineSliceSprite.ts +46 -0
- package/src/components/ParticleEmitter.ts +39 -0
- package/src/components/Scene.ts +6 -0
- package/src/components/Sprite.ts +493 -0
- package/src/components/Text.ts +145 -0
- package/src/components/Tilemap/Tile.ts +79 -0
- package/src/components/Tilemap/TileGroup.ts +207 -0
- package/src/components/Tilemap/TileLayer.ts +163 -0
- package/src/components/Tilemap/TileSet.ts +41 -0
- package/src/components/Tilemap/index.ts +80 -0
- package/src/components/TilingSprite.ts +39 -0
- package/src/components/Viewport.ts +159 -0
- package/src/components/index.ts +12 -0
- package/src/components/types/DisplayObject.ts +68 -0
- package/src/components/types/MouseEvent.ts +3 -0
- package/src/components/types/Spritesheet.ts +389 -0
- package/src/components/types/index.ts +4 -0
- package/src/directives/Drag.ts +84 -0
- package/src/directives/KeyboardControls.ts +922 -0
- package/src/directives/Scheduler.ts +112 -0
- package/src/directives/Sound.ts +91 -0
- package/src/directives/Transition.ts +45 -0
- package/src/directives/ViewportCull.ts +40 -0
- package/src/directives/ViewportFollow.ts +26 -0
- package/src/directives/index.ts +7 -0
- package/src/engine/animation.ts +113 -0
- package/src/engine/bootstrap.ts +19 -0
- package/src/engine/directive.ts +23 -0
- package/src/engine/reactive.ts +379 -0
- package/src/engine/signal.ts +138 -0
- package/src/engine/trigger.ts +40 -0
- package/src/engine/utils.ts +135 -0
- package/src/hooks/addContext.ts +6 -0
- package/src/hooks/useProps.ts +155 -0
- package/src/hooks/useRef.ts +21 -0
- package/src/index.ts +14 -0
- package/src/presets/Bar.ts +89 -0
- package/src/presets/Button.ts +0 -0
- package/src/presets/Joystick.ts +286 -0
- package/src/presets/NightAmbiant.ts +122 -0
- package/src/presets/Particle.ts +53 -0
- package/src/utils/Ease.ts +33 -0
- package/src/utils/RadialGradient.ts +86 -0
- package/starter/assets/logo.png +0 -0
- package/starter/components/app.ce +18 -0
- package/starter/components/hello.ce +35 -0
- package/starter/index.html +21 -0
- package/starter/main.ts +6 -0
- package/starter/package.json +20 -0
- package/starter/tsconfig.json +32 -0
- package/starter/vite.config.ts +12 -0
- package/tsconfig.json +32 -0
- package/tsconfig.node.json +10 -0
- package/tsup.config.ts +28 -0
- package/vitest.config.ts +12 -0
- package/.gitattributes +0 -22
- package/.npmignore +0 -163
- package/canvasengine-1.3.0.all.min.js +0 -21
- package/canvasengine.js +0 -5802
- package/core/DB.js +0 -24
- package/core/ModelServer.js +0 -348
- package/core/Users.js +0 -190
- package/core/engine-common.js +0 -952
- package/doc/cocoonjs.md +0 -36
- package/doc/doc-lang.yml +0 -43
- package/doc/doc-router.yml +0 -14
- package/doc/doc-tuto.yml +0 -9
- package/doc/doc.yml +0 -39
- package/doc/element.md +0 -37
- package/doc/entity.md +0 -90
- package/doc/extend.md +0 -47
- package/doc/get_started.md +0 -19
- package/doc/images/entity.png +0 -0
- package/doc/multitouch.md +0 -58
- package/doc/nodejs.md +0 -142
- package/doc/scene.md +0 -44
- package/doc/text.md +0 -156
- package/examples/server/client.html +0 -31
- package/examples/server/server.js +0 -16
- package/examples/tiled_server/client.html +0 -52
- package/examples/tiled_server/images/tiles_spritesheet.png +0 -0
- package/examples/tiled_server/server/map.json +0 -50
- package/examples/tiled_server/server/map.tmx +0 -16
- package/examples/tiled_server/server/server.js +0 -16
- package/extends/Animation.js +0 -910
- package/extends/Effect.js +0 -252
- package/extends/Gleed2d.js +0 -252
- package/extends/Hit.js +0 -1509
- package/extends/Input.js +0 -699
- package/extends/Marshal.js +0 -716
- package/extends/Scrolling.js +0 -388
- package/extends/Soundmanager2.js +0 -5466
- package/extends/Spritesheet.js +0 -196
- package/extends/Text.js +0 -366
- package/extends/Tiled.js +0 -403
- package/extends/Window.js +0 -575
- package/extends/gamepad.js +0 -397
- package/extends/socket.io.min.js +0 -2
- package/extends/swf/soundmanager2.swf +0 -0
- package/extends/swf/soundmanager2_debug.swf +0 -0
- package/extends/swf/soundmanager2_flash9.swf +0 -0
- package/extends/swf/soundmanager2_flash9_debug.swf +0 -0
- package/extends/swf/soundmanager2_flash_xdomain.zip +0 -0
- package/extends/workers/transition.js +0 -43
- package/index.js +0 -46
- package/license.txt +0 -19
- 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
|
+
}
|