@playcanvas/web-components 0.1.0 → 0.1.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.
@@ -0,0 +1,289 @@
1
+ import { SoundSlot } from 'playcanvas';
2
+
3
+ import { AppElement } from '../app';
4
+ import { SoundComponentElement } from './sound-component';
5
+ import { AssetElement } from '../asset';
6
+
7
+ /**
8
+ * Represents a sound slot in the PlayCanvas engine.
9
+ */
10
+ class SoundSlotElement extends HTMLElement {
11
+ private _asset: string = '';
12
+
13
+ private _autoPlay: boolean = false;
14
+
15
+ private _duration: number | null = null;
16
+
17
+ private _loop: boolean = false;
18
+
19
+ private _name: string = '';
20
+
21
+ private _overlap: boolean = false;
22
+
23
+ private _pitch: number = 1;
24
+
25
+ private _startTime: number = 0;
26
+
27
+ private _volume: number = 1;
28
+
29
+ /**
30
+ * The sound slot.
31
+ */
32
+ soundSlot: SoundSlot | null = null;
33
+
34
+ getAsset() {
35
+ const assetElement = document.querySelector(`pc-asset[id="${this._asset}"]`) as AssetElement;
36
+ return assetElement!.asset;
37
+ }
38
+
39
+ async connectedCallback() {
40
+ const appElement = this.closest('pc-app') as AppElement | null;
41
+ if (!appElement) {
42
+ console.error(`${this.tagName.toLowerCase()} should be a descendant of pc-app`);
43
+ return;
44
+ }
45
+
46
+ await appElement.getApplication();
47
+
48
+ const options = {
49
+ autoPlay: this._autoPlay,
50
+ loop: this._loop,
51
+ overlap: this._overlap,
52
+ pitch: this._pitch,
53
+ startTime: this._startTime,
54
+ volume: this._volume
55
+ } as any;
56
+ if (this._duration) {
57
+ options.duration = this._duration;
58
+ }
59
+ this.soundSlot = this.soundElement!.component!.addSlot(this._name, options);
60
+ this.asset = this._asset;
61
+ this.soundSlot!.play();
62
+ }
63
+
64
+ disconnectedCallback() {
65
+ this.soundElement!.component!.removeSlot(this._name);
66
+ }
67
+
68
+ protected get soundElement(): SoundComponentElement | null {
69
+ const soundElement = this.parentElement as SoundComponentElement;
70
+
71
+ if (!(soundElement instanceof SoundComponentElement)) {
72
+ console.warn('pc-sound-slot must be a direct child of a pc-sound element');
73
+ return null;
74
+ }
75
+
76
+ return soundElement;
77
+ }
78
+
79
+ /**
80
+ * Sets the asset of the sound slot.
81
+ * @param value - The asset.
82
+ */
83
+ set asset(value: string) {
84
+ this._asset = value;
85
+ if (this.soundSlot) {
86
+ const id = this.getAsset()?.id;
87
+ if (id) {
88
+ this.soundSlot.asset = id;
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Gets the asset of the sound slot.
95
+ * @returns The asset.
96
+ */
97
+ get asset() {
98
+ return this._asset;
99
+ }
100
+
101
+ /**
102
+ * Sets the auto play flag of the sound slot.
103
+ * @param value - The auto play flag.
104
+ */
105
+ set autoPlay(value: boolean) {
106
+ this._autoPlay = value;
107
+ if (this.soundSlot) {
108
+ this.soundSlot.autoPlay = value;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Gets the auto play flag of the sound slot.
114
+ * @returns The auto play flag.
115
+ */
116
+ get autoPlay() {
117
+ return this._autoPlay;
118
+ }
119
+
120
+ /**
121
+ * Sets the duration of the sound slot.
122
+ * @param value - The duration.
123
+ */
124
+ set duration(value: number) {
125
+ this._duration = value;
126
+ if (this.soundSlot) {
127
+ this.soundSlot.duration = value;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Gets the duration of the sound slot.
133
+ * @returns The duration.
134
+ */
135
+ get duration() {
136
+ return this._duration as number;
137
+ }
138
+
139
+ /**
140
+ * Sets the loop flag of the sound slot.
141
+ * @param value - The loop flag.
142
+ */
143
+ set loop(value: boolean) {
144
+ this._loop = value;
145
+ if (this.soundSlot) {
146
+ this.soundSlot.loop = value;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Gets the loop flag of the sound slot.
152
+ * @returns The loop flag.
153
+ */
154
+ get loop() {
155
+ return this._loop;
156
+ }
157
+
158
+ /**
159
+ * Sets the name of the sound slot.
160
+ * @param value - The name.
161
+ */
162
+ set name(value: string) {
163
+ this._name = value;
164
+ if (this.soundSlot) {
165
+ this.soundSlot.name = value;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Gets the name of the sound slot.
171
+ * @returns The name.
172
+ */
173
+ get name() {
174
+ return this._name;
175
+ }
176
+
177
+ /**
178
+ * Sets the overlap flag of the sound slot.
179
+ * @param value - The overlap flag.
180
+ */
181
+ set overlap(value: boolean) {
182
+ this._overlap = value;
183
+ if (this.soundSlot) {
184
+ this.soundSlot.overlap = value;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Gets the overlap flag of the sound slot.
190
+ * @returns The overlap flag.
191
+ */
192
+ get overlap() {
193
+ return this._overlap;
194
+ }
195
+
196
+ /**
197
+ * Sets the pitch of the sound slot.
198
+ * @param value - The pitch.
199
+ */
200
+ set pitch(value: number) {
201
+ this._pitch = value;
202
+ if (this.soundSlot) {
203
+ this.soundSlot.pitch = value;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Gets the pitch of the sound slot.
209
+ * @returns The pitch.
210
+ */
211
+ get pitch() {
212
+ return this._pitch;
213
+ }
214
+
215
+ /**
216
+ * Sets the start time of the sound slot.
217
+ * @param value - The start time.
218
+ */
219
+ set startTime(value: number) {
220
+ this._startTime = value;
221
+ if (this.soundSlot) {
222
+ this.soundSlot.startTime = value;
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Gets the start time of the sound slot.
228
+ * @returns The start time.
229
+ */
230
+ get startTime() {
231
+ return this._startTime;
232
+ }
233
+
234
+ /**
235
+ * Sets the volume of the sound slot.
236
+ * @param value - The volume.
237
+ */
238
+ set volume(value: number) {
239
+ this._volume = value;
240
+ if (this.soundSlot) {
241
+ this.soundSlot.volume = value;
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Gets the volume of the sound slot.
247
+ * @returns The volume.
248
+ */
249
+ get volume() {
250
+ return this._volume;
251
+ }
252
+
253
+ static get observedAttributes() {
254
+ return ['asset', 'autoPlay', 'duration', 'loop', 'name', 'overlap', 'pitch', 'startTime', 'volume'];
255
+ }
256
+
257
+ attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
258
+ switch (name) {
259
+ case 'asset':
260
+ this.asset = newValue;
261
+ break;
262
+ case 'duration':
263
+ this.duration = parseFloat(newValue);
264
+ break;
265
+ case 'loop':
266
+ this.loop = this.hasAttribute('loop');
267
+ break;
268
+ case 'name':
269
+ this.name = newValue;
270
+ break;
271
+ case 'overlap':
272
+ this.overlap = this.hasAttribute('overlap');
273
+ break;
274
+ case 'pitch':
275
+ this.pitch = parseFloat(newValue);
276
+ break;
277
+ case 'startTime':
278
+ this.startTime = parseFloat(newValue);
279
+ break;
280
+ case 'volume':
281
+ this.volume = parseFloat(newValue);
282
+ break;
283
+ }
284
+ }
285
+ }
286
+
287
+ customElements.define('pc-sound-slot', SoundSlotElement);
288
+
289
+ export { SoundSlotElement };
package/src/entity.ts ADDED
@@ -0,0 +1,230 @@
1
+ import { Entity, Vec3 } from 'playcanvas';
2
+
3
+ import { AppElement } from './app';
4
+ import { parseVec3 } from './utils';
5
+
6
+ /**
7
+ * Represents an entity in the PlayCanvas engine.
8
+ */
9
+ class EntityElement extends HTMLElement {
10
+ /**
11
+ * Whether the entity is enabled.
12
+ */
13
+ private _enabled = true;
14
+
15
+ /**
16
+ * The name of the entity.
17
+ */
18
+ private _name = 'Untitled';
19
+
20
+ /**
21
+ * The position of the entity.
22
+ */
23
+ private _position = new Vec3();
24
+
25
+ /**
26
+ * The rotation of the entity.
27
+ */
28
+ private _rotation = new Vec3();
29
+
30
+ /**
31
+ * The scale of the entity.
32
+ */
33
+ private _scale = new Vec3(1, 1, 1);
34
+
35
+ /**
36
+ * The tags of the entity.
37
+ */
38
+ private _tags: string[] = [];
39
+
40
+ /**
41
+ * The PlayCanvas entity instance.
42
+ */
43
+ entity: Entity | null = null;
44
+
45
+ async connectedCallback() {
46
+ // Get the application
47
+ const appElement = this.closest('pc-app') as AppElement;
48
+ if (!appElement) {
49
+ console.warn(`${this.tagName} must be a child of pc-app`);
50
+ return;
51
+ }
52
+
53
+ const app = await appElement.getApplication();
54
+
55
+ // Create a new entity
56
+ this.entity = new Entity(this._name, app);
57
+
58
+ if (this.parentElement && this.parentElement.tagName === 'pc-entity' && (this.parentElement as EntityElement).entity) {
59
+ (this.parentElement as EntityElement).entity!.addChild(this.entity);
60
+ } else {
61
+ app.root.addChild(this.entity);
62
+ }
63
+
64
+ // Initialize from attributes
65
+ const nameAttr = this.getAttribute('name');
66
+ const positionAttr = this.getAttribute('position');
67
+ const rotationAttr = this.getAttribute('rotation');
68
+ const scaleAttr = this.getAttribute('scale');
69
+ const tagsAttr = this.getAttribute('tags');
70
+
71
+ if (nameAttr) this.name = nameAttr;
72
+ if (positionAttr) this.position = parseVec3(positionAttr);
73
+ if (rotationAttr) this.rotation = parseVec3(rotationAttr);
74
+ if (scaleAttr) this.scale = parseVec3(scaleAttr);
75
+ if (tagsAttr) this.tags = tagsAttr.split(',').map(tag => tag.trim());
76
+ }
77
+
78
+ disconnectedCallback() {
79
+ if (this.entity) {
80
+ this.entity.destroy();
81
+ this.entity = null;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Sets the enabled state of the entity.
87
+ * @param value - Whether the entity is enabled.
88
+ */
89
+ set enabled(value) {
90
+ this._enabled = value;
91
+ if (this.entity) {
92
+ this.entity.enabled = value;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Gets the enabled state of the entity.
98
+ * @returns Whether the entity is enabled.
99
+ */
100
+ get enabled() {
101
+ return this._enabled;
102
+ }
103
+
104
+ /**
105
+ * Sets the name of the entity.
106
+ * @param value - The name of the entity.
107
+ */
108
+ set name(value) {
109
+ this._name = value;
110
+ if (this.entity) {
111
+ this.entity.name = value;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Gets the name of the entity.
117
+ * @returns The name of the entity.
118
+ */
119
+ get name() {
120
+ return this._name;
121
+ }
122
+
123
+ /**
124
+ * Sets the position of the entity.
125
+ * @param value - The position of the entity.
126
+ */
127
+ set position(value) {
128
+ this._position = value;
129
+ if (this.entity) {
130
+ this.entity.setLocalPosition(this._position);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Gets the position of the entity.
136
+ * @returns The position of the entity.
137
+ */
138
+ get position() {
139
+ return this._position;
140
+ }
141
+
142
+ /**
143
+ * Sets the rotation of the entity.
144
+ * @param value - The rotation of the entity.
145
+ */
146
+ set rotation(value) {
147
+ this._rotation = value;
148
+ if (this.entity) {
149
+ this.entity.setLocalEulerAngles(this._rotation);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Gets the rotation of the entity.
155
+ * @returns The rotation of the entity.
156
+ */
157
+ get rotation() {
158
+ return this._rotation;
159
+ }
160
+
161
+ /**
162
+ * Sets the scale of the entity.
163
+ * @param value - The scale of the entity.
164
+ */
165
+ set scale(value) {
166
+ this._scale = value;
167
+ if (this.entity) {
168
+ this.entity.setLocalScale(this._scale);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Gets the scale of the entity.
174
+ * @returns The scale of the entity.
175
+ */
176
+ get scale() {
177
+ return this._scale;
178
+ }
179
+
180
+ /**
181
+ * Sets the tags of the entity.
182
+ * @param value - The tags of the entity.
183
+ */
184
+ set tags(value) {
185
+ this._tags = value;
186
+ if (this.entity) {
187
+ this.entity.tags.clear();
188
+ this.entity.tags.add(this._tags);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Gets the tags of the entity.
194
+ * @returns The tags of the entity.
195
+ */
196
+ get tags() {
197
+ return this._tags;
198
+ }
199
+
200
+ static get observedAttributes() {
201
+ return ['enabled', 'name', 'position', 'rotation', 'scale', 'tags'];
202
+ }
203
+
204
+ attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
205
+ switch (name) {
206
+ case 'enabled':
207
+ this.enabled = newValue !== 'false';
208
+ break;
209
+ case 'name':
210
+ this.name = newValue;
211
+ break;
212
+ case 'position':
213
+ this.position = parseVec3(newValue);
214
+ break;
215
+ case 'rotation':
216
+ this.rotation = parseVec3(newValue);
217
+ break;
218
+ case 'scale':
219
+ this.scale = parseVec3(newValue);
220
+ break;
221
+ case 'tags':
222
+ this.tags = newValue.split(',').map(tag => tag.trim());
223
+ break;
224
+ }
225
+ }
226
+ }
227
+
228
+ customElements.define('pc-entity', EntityElement);
229
+
230
+ export { EntityElement };
package/src/index.ts ADDED
@@ -0,0 +1,46 @@
1
+ /* eslint-disable import/order */
2
+
3
+ // Note that order matters here (e.g. pc-entity must be defined before components)
4
+ import { ModuleElement } from './module';
5
+ import { AppElement } from './app';
6
+ import { EntityElement } from './entity';
7
+ import { AssetElement } from './asset';
8
+ import { ListenerComponentElement } from './components/listener-component';
9
+ import { CameraComponentElement } from './components/camera-component';
10
+ import { CollisionComponentElement } from './components/collision-component';
11
+ import { ComponentElement } from './components/component';
12
+ import { ElementComponentElement } from './components/element-component';
13
+ import { GSplatComponentElement } from './components/gsplat-component';
14
+ import { LightComponentElement } from './components/light-component';
15
+ import { RenderComponentElement } from './components/render-component';
16
+ import { RigidBodyComponentElement } from './components/rigidbody-component';
17
+ import { ScriptComponentElement } from './components/script-component';
18
+ import { ScriptElement } from './components/script';
19
+ import { SoundComponentElement } from './components/sound-component';
20
+ import { SoundSlotElement } from './components/sound-slot';
21
+ import { ModelElement } from './model';
22
+ import { SceneElement } from './scene';
23
+ import { SkyElement } from './sky';
24
+
25
+ export {
26
+ ModuleElement,
27
+ AppElement,
28
+ EntityElement,
29
+ AssetElement,
30
+ CameraComponentElement,
31
+ CollisionComponentElement,
32
+ ComponentElement,
33
+ ElementComponentElement,
34
+ GSplatComponentElement,
35
+ LightComponentElement,
36
+ ListenerComponentElement,
37
+ RenderComponentElement,
38
+ RigidBodyComponentElement,
39
+ ScriptComponentElement,
40
+ ScriptElement,
41
+ SoundComponentElement,
42
+ SoundSlotElement,
43
+ ModelElement,
44
+ SceneElement,
45
+ SkyElement
46
+ };
package/src/model.ts ADDED
@@ -0,0 +1,80 @@
1
+ import { AppElement } from './app';
2
+ import { AssetElement } from './asset';
3
+ import { EntityElement } from './entity';
4
+
5
+ /**
6
+ * Represents a model in the PlayCanvas engine.
7
+ */
8
+ class ModelElement extends HTMLElement {
9
+ private _asset: string = '';
10
+
11
+ async connectedCallback() {
12
+ // Get the application
13
+ const appElement = this.closest('pc-app') as AppElement;
14
+ if (!appElement) {
15
+ console.warn(`${this.tagName} must be a child of pc-app`);
16
+ return;
17
+ }
18
+
19
+ await appElement.getApplication();
20
+
21
+ const asset = this.getAttribute('asset');
22
+ if (asset) {
23
+ this.asset = asset;
24
+ }
25
+ }
26
+
27
+ getAsset() {
28
+ const assetElement = document.querySelector(`pc-asset[id="${this._asset}"]`) as AssetElement;
29
+ return assetElement!.asset;
30
+ }
31
+
32
+ _loadModel() {
33
+ const asset = this.getAsset();
34
+ if (!asset) {
35
+ return;
36
+ }
37
+ const entity = asset.resource.instantiateRenderEntity();
38
+
39
+ const parentEntityElement = this.closest('pc-entity') as EntityElement | null;
40
+ if (parentEntityElement) {
41
+ parentEntityElement.entity!.addChild(entity);
42
+ } else {
43
+ const appElement = this.closest('pc-app') as AppElement;
44
+ appElement.app!.root.addChild(entity);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Sets the asset ID of the model.
50
+ * @param value - The asset ID.
51
+ */
52
+ set asset(value: string) {
53
+ this._asset = value;
54
+ this._loadModel();
55
+ }
56
+
57
+ /**
58
+ * Gets the source URL of the model.
59
+ * @returns The source URL of the model.
60
+ */
61
+ get asset(): string {
62
+ return this._asset;
63
+ }
64
+
65
+ static get observedAttributes() {
66
+ return ['asset'];
67
+ }
68
+
69
+ attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
70
+ switch (name) {
71
+ case 'asset':
72
+ this.asset = newValue;
73
+ break;
74
+ }
75
+ }
76
+ }
77
+
78
+ customElements.define('pc-model', ModelElement);
79
+
80
+ export { ModelElement };
package/src/module.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { WasmModule } from 'playcanvas';
2
+
3
+ class ModuleElement extends HTMLElement {
4
+ private loadPromise: Promise<void>;
5
+
6
+ constructor() {
7
+ super();
8
+ this.loadPromise = this.loadModule();
9
+ }
10
+
11
+ private async loadModule(): Promise<void> {
12
+ const name = this.getAttribute('name')!;
13
+ const glue = this.getAttribute('glue')!;
14
+ const wasm = this.getAttribute('wasm')!;
15
+ const fallback = this.getAttribute('fallback')!;
16
+
17
+ WasmModule.setConfig(name, {
18
+ glueUrl: glue,
19
+ wasmUrl: wasm,
20
+ fallbackUrl: fallback
21
+ });
22
+
23
+ await new Promise<void>((resolve) => {
24
+ WasmModule.getInstance(name, () => resolve());
25
+ });
26
+ }
27
+
28
+ public getLoadPromise(): Promise<void> {
29
+ return this.loadPromise;
30
+ }
31
+ }
32
+
33
+ customElements.define('pc-module', ModuleElement);
34
+
35
+ export { ModuleElement };