@playcanvas/web-components 0.1.13 → 0.2.0

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.
@@ -1,8 +1,9 @@
1
- import { ScriptComponent, Script, Vec2, Vec3, Vec4 } from 'playcanvas';
1
+ import { Color, ScriptComponent, Script, Vec2, Vec3, Vec4 } from 'playcanvas';
2
2
 
3
+ import { AssetElement } from '../asset';
3
4
  import { ComponentElement } from './component';
5
+ import { EntityElement } from '../entity';
4
6
  import { ScriptElement } from './script';
5
- import { AssetElement } from '../asset';
6
7
 
7
8
  // Add these interfaces at the top of the file, after the imports
8
9
  interface ScriptAttributesChangeEvent extends CustomEvent {
@@ -58,66 +59,123 @@ class ScriptComponentElement extends ComponentElement {
58
59
  });
59
60
  }
60
61
 
61
- private applyAttributes(script: any, attributes: string | null) {
62
- try {
63
- const attributesObject = attributes ? JSON.parse(attributes) : {};
64
-
65
- const applyValue = (target: any, key: string, value: any) => {
66
- // Handle asset references
67
- if (typeof value === 'string' && value.startsWith('asset:')) {
68
- const assetId = value.slice(6);
69
- const assetElement = document.querySelector(`pc-asset#${assetId}`) as AssetElement;
70
- if (assetElement) {
71
- target[key] = assetElement.asset;
72
- return;
73
- }
62
+ /**
63
+ * Recursively converts raw attribute data into proper PlayCanvas types. Supported conversions:
64
+ * - "asset:assetId" resolves to an Asset instance
65
+ * - "entity:entityId" → resolves to an Entity instance
66
+ * - "vec2:1,2" new Vec2(1,2)
67
+ * - "vec3:1,2,3" → new Vec3(1,2,3)
68
+ * - "vec4:1,2,3,4" new Vec4(1,2,3,4)
69
+ * - "color:1,0.5,0.5,1" → new Color(1,0.5,0.5,1)
70
+ * @param item - The item to convert.
71
+ * @returns The converted item.
72
+ */
73
+ private convertAttributes(item: any): any {
74
+ if (typeof item === 'string') {
75
+ if (item.startsWith('asset:')) {
76
+ const assetId = item.slice(6);
77
+ const assetElement = document.querySelector(`pc-asset#${assetId}`) as AssetElement;
78
+ if (assetElement) {
79
+ return assetElement.asset;
80
+ }
81
+ }
82
+ if (item.startsWith('entity:')) {
83
+ const entityId = item.slice(7);
84
+ const entityElement = document.querySelector(`pc-entity[name="${entityId}"]`) as EntityElement;
85
+ if (entityElement) {
86
+ return entityElement.entity;
87
+ }
88
+ }
89
+ if (item.startsWith('vec2:')) {
90
+ const parts = item.slice(5).split(',').map(Number);
91
+ if (parts.length === 2 && parts.every(v => !isNaN(v))) {
92
+ return new Vec2(parts[0], parts[1]);
74
93
  }
94
+ }
95
+ if (item.startsWith('vec3:')) {
96
+ const parts = item.slice(5).split(',').map(Number);
97
+ if (parts.length === 3 && parts.every(v => !isNaN(v))) {
98
+ return new Vec3(parts[0], parts[1], parts[2]);
99
+ }
100
+ }
101
+ if (item.startsWith('vec4:')) {
102
+ const parts = item.slice(5).split(',').map(Number);
103
+ if (parts.length === 4 && parts.every(v => !isNaN(v))) {
104
+ return new Vec4(parts[0], parts[1], parts[2], parts[3]);
105
+ }
106
+ }
107
+ if (item.startsWith('color:')) {
108
+ const parts = item.slice(6).split(',').map(Number);
109
+ if (parts.length === 4 && parts.every(v => !isNaN(v))) {
110
+ return new Color(parts[0], parts[1], parts[2], parts[3]);
111
+ }
112
+ }
113
+ return item;
114
+ }
75
115
 
76
- // Handle arrays
77
- if (value && typeof value === 'object' && Array.isArray(value)) {
78
- // If it's an array of objects, recursively apply to each object
79
- if (value.length > 0 && typeof value[0] === 'object') {
80
- target[key] = value.map((item) => {
81
- const obj = {};
82
- for (const itemKey in item) {
83
- applyValue(obj, itemKey, item[itemKey]);
84
- }
85
- return obj;
86
- });
87
- return;
88
- }
116
+ if (Array.isArray(item)) {
117
+ // If it's an array of objects, convert each element individually.
118
+ if (item.length > 0 && typeof item[0] === 'object') {
119
+ return item.map((el: any) => this.convertAttributes(el));
120
+ }
121
+ // Otherwise, leave the numeric array unchanged but process each element.
122
+ return item.map((el: any) => this.convertAttributes(el));
123
+ }
89
124
 
90
- // Handle vectors
91
- if (value.length === 2 && typeof value[0] === 'number') {
92
- target[key] = new Vec2(value[0], value[1]);
93
- return;
94
- }
95
- if (value.length === 3 && typeof value[0] === 'number') {
96
- target[key] = new Vec3(value[0], value[1], value[2]);
97
- return;
98
- }
99
- if (value.length === 4 && typeof value[0] === 'number') {
100
- target[key] = new Vec4(value[0], value[1], value[2], value[3]);
101
- return;
102
- }
103
- }
125
+ if (item && typeof item === 'object') {
126
+ const result: any = {};
127
+ for (const key in item) {
128
+ result[key] = this.convertAttributes(item[key]);
129
+ }
130
+ return result;
131
+ }
104
132
 
105
- // Handle nested objects (non-array)
106
- if (value && typeof value === 'object' && !Array.isArray(value)) {
107
- if (!target[key] || typeof target[key] !== 'object') {
108
- target[key] = {};
109
- }
110
- for (const nestedKey in value) {
111
- applyValue(target[key], nestedKey, value[nestedKey]);
112
- }
113
- } else {
114
- target[key] = value;
115
- }
116
- };
133
+ return item;
134
+ }
135
+
136
+ /**
137
+ * Preprocess the attributes object by converting its values.
138
+ * @param attrs - The attributes object to preprocess.
139
+ * @returns The preprocessed attributes object.
140
+ */
141
+ private preprocessAttributes(attrs: any): any {
142
+ return this.convertAttributes(attrs);
143
+ }
117
144
 
118
- for (const key in attributesObject) {
119
- applyValue(script, key, attributesObject[key]);
145
+ /**
146
+ * Recursively merge properties from source into target.
147
+ * @param target - The target object to merge into.
148
+ * @param source - The source object to merge from.
149
+ * @returns The merged object.
150
+ */
151
+ private mergeDeep(target: any, source: any): any {
152
+ for (const key in source) {
153
+ if (
154
+ source[key] &&
155
+ typeof source[key] === 'object' &&
156
+ !Array.isArray(source[key])
157
+ ) {
158
+ if (!target[key] || typeof target[key] !== 'object') {
159
+ target[key] = {};
160
+ }
161
+ this.mergeDeep(target[key], source[key]);
162
+ } else {
163
+ target[key] = source[key];
120
164
  }
165
+ }
166
+ return target;
167
+ }
168
+
169
+ /**
170
+ * Update script attributes by merging preprocessed values into the script.
171
+ * @param script - The script to update.
172
+ * @param attributes - The attributes to merge into the script.
173
+ */
174
+ private applyAttributes(script: any, attributes: string | null) {
175
+ try {
176
+ const attributesObject = attributes ? JSON.parse(attributes) : {};
177
+ const converted = this.convertAttributes(attributesObject);
178
+ this.mergeDeep(script, converted);
121
179
  } catch (error) {
122
180
  console.error(`Error parsing attributes JSON string ${attributes}:`, error);
123
181
  }
@@ -148,10 +206,19 @@ class ScriptComponentElement extends ComponentElement {
148
206
  private createScript(name: string, attributes: string | null): Script | null {
149
207
  if (!this.component) return null;
150
208
 
151
- this.component.on(`create:${name}`, (script) => {
152
- this.applyAttributes(script, attributes);
209
+ let attributesObject = {};
210
+ if (attributes) {
211
+ try {
212
+ attributesObject = JSON.parse(attributes);
213
+ // Preprocess attributes: convert arrays or strings into vectors, colors, asset references, etc.
214
+ attributesObject = this.preprocessAttributes(attributesObject);
215
+ } catch (error) {
216
+ console.error(`Error parsing attributes JSON string ${attributes}:`, error);
217
+ }
218
+ }
219
+ return this.component.create(name, {
220
+ properties: attributesObject
153
221
  });
154
- return this.component.create(name);
155
222
  }
156
223
 
157
224
  private destroyScript(name: string): void {