@hkdigital/lib-core 0.4.58 → 0.4.60

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.
@@ -16,3 +16,4 @@ export function buildTree<T extends object>(flatTree: import("./typedef.js").Fla
16
16
  * @returns {import('./typedef.js').FlatTree<T>} flat tree data
17
17
  */
18
18
  export function flattenTree<T extends object>(hierarchicalTree: T): import("./typedef.js").FlatTree<T>;
19
+ export const FORMAT_FT1: "ft1";
@@ -4,6 +4,8 @@ import * as expect from '../../../util/expect.js';
4
4
 
5
5
  /* ------------------------------------------------------------------ Exports */
6
6
 
7
+ export const FORMAT_FT1 = 'ft1';
8
+
7
9
  /**
8
10
  * Build a hierarchical tree from ft1 flat tree format
9
11
  *
@@ -17,8 +19,8 @@ export function buildTree(flatTree) {
17
19
 
18
20
  const { format, properties, nodes, edges } = flatTree;
19
21
 
20
- if (format !== 'ft1') {
21
- throw new Error(`Unsupported format: ${format}. Expected 'ft1'`);
22
+ if (format !== FORMAT_FT1) {
23
+ throw new Error(`Unsupported format: ${format}.`);
22
24
  }
23
25
 
24
26
  if (!nodes || !nodes.length) {
@@ -34,7 +36,7 @@ export function buildTree(flatTree) {
34
36
  }
35
37
 
36
38
  // Create copies of all nodes to avoid mutating original data
37
- const nodesCopy = nodes.map(node => ({ ...node }));
39
+ const nodesCopy = nodes.map((node) => ({ ...node }));
38
40
 
39
41
  // Process edges in groups of 3: [from, prop, to]
40
42
  for (let i = 0; i < edges.length; i += 3) {
@@ -44,7 +46,9 @@ export function buildTree(flatTree) {
44
46
 
45
47
  // Validate indices
46
48
  if (fromIndex >= nodesCopy.length || toIndex >= nodesCopy.length) {
47
- throw new Error(`Invalid node index in edge [${fromIndex}, ${propIndex}, ${toIndex}]`);
49
+ throw new Error(
50
+ `Invalid node index in edge [${fromIndex}, ${propIndex}, ${toIndex}]`
51
+ );
48
52
  }
49
53
 
50
54
  if (propIndex >= properties.length) {
@@ -88,13 +92,19 @@ export function flattenTree(hierarchicalTree) {
88
92
 
89
93
  /** @type {T[]} */
90
94
  const nodes = [];
91
-
95
+
92
96
  /** @type {number[]} */
93
- const edges = [];
94
-
97
+ const edgeFrom = [];
98
+
99
+ /** @type {string[]} */
100
+ const edgePropertyNames = [];
101
+
102
+ /** @type {number[]} */
103
+ const edgeTo = [];
104
+
95
105
  /** @type {Set<string>} */
96
106
  const propertySet = new Set();
97
-
107
+
98
108
  /** @type {WeakMap<object, number>} */
99
109
  const objectToIndex = new WeakMap();
100
110
 
@@ -104,7 +114,7 @@ export function flattenTree(hierarchicalTree) {
104
114
 
105
115
  // Add root node (always index 0)
106
116
  const rootCopy = extractNodeData(hierarchicalTree, childProperties);
107
- nodes.push(rootCopy);
117
+ nodes.push( /** @type {T} */ (rootCopy) );
108
118
  objectToIndex.set(hierarchicalTree, 0);
109
119
 
110
120
  // Process children recursively
@@ -112,7 +122,9 @@ export function flattenTree(hierarchicalTree) {
112
122
  hierarchicalTree,
113
123
  0,
114
124
  nodes,
115
- edges,
125
+ edgeFrom,
126
+ edgePropertyNames,
127
+ edgeTo,
116
128
  propertySet,
117
129
  objectToIndex,
118
130
  childProperties
@@ -121,14 +133,19 @@ export function flattenTree(hierarchicalTree) {
121
133
  // Convert property set to sorted array for consistent output
122
134
  const properties = Array.from(propertySet).sort();
123
135
 
124
- // Convert property names to indices in edges array
125
- for (let i = 1; i < edges.length; i += 3) {
126
- const propertyName = /** @type {string} */ (edges[i]);
127
- edges[i] = properties.indexOf(propertyName);
136
+ // Build final edges array with property indices
137
+ /** @type {number[]} */
138
+ const edges = [];
139
+ for (let i = 0; i < edgeFrom.length; i++) {
140
+ edges.push(
141
+ edgeFrom[i],
142
+ properties.indexOf(edgePropertyNames[i]),
143
+ edgeTo[i]
144
+ );
128
145
  }
129
146
 
130
147
  return /** @type {import('./typedef.js').FlatTree<T>} */ ({
131
- format: 'ft1',
148
+ format: FORMAT_FT1,
132
149
  properties,
133
150
  nodes,
134
151
  edges
@@ -140,10 +157,10 @@ export function flattenTree(hierarchicalTree) {
140
157
  /**
141
158
  * Extract node data excluding children properties
142
159
  *
143
- * @param {object} node - Source node
160
+ * @param {Record<string,any>} node - Source node
144
161
  * @param {Set<string>} propertiesToRemove - Set of property names to remove
145
162
  *
146
- * @returns {object} node data without children properties
163
+ * @returns {Record<string,any>} node data without children properties
147
164
  */
148
165
  function extractNodeData(node, propertiesToRemove) {
149
166
  /** @type {any} */
@@ -160,7 +177,7 @@ function extractNodeData(node, propertiesToRemove) {
160
177
  /**
161
178
  * Find all properties that contain child objects
162
179
  *
163
- * @param {object} node - Node to analyze
180
+ * @param {Record<string,any>} node - Node to analyze
164
181
  * @param {Set<string>} childProperties - Set to collect property names
165
182
  */
166
183
  function findChildProperties(node, childProperties) {
@@ -168,7 +185,7 @@ function findChildProperties(node, childProperties) {
168
185
  for (const [key, value] of Object.entries(node)) {
169
186
  if (Array.isArray(value) && value.length > 0) {
170
187
  // Check if array contains objects (potential children)
171
- const hasObjects = value.some(item => item && typeof item === 'object');
188
+ const hasObjects = value.some((item) => item && typeof item === 'object');
172
189
  if (hasObjects) {
173
190
  childProperties.add(key);
174
191
  }
@@ -176,7 +193,8 @@ function findChildProperties(node, childProperties) {
176
193
  }
177
194
 
178
195
  // Recursively check child nodes
179
- for (const key of [...childProperties]) { // Copy set to avoid modification during iteration
196
+ for (const key of [...childProperties]) {
197
+ // Copy set to avoid modification during iteration
180
198
  const children = node[key];
181
199
  if (Array.isArray(children)) {
182
200
  for (const child of children) {
@@ -191,31 +209,65 @@ function findChildProperties(node, childProperties) {
191
209
  /**
192
210
  * Recursively process children for ft1 format
193
211
  *
194
- * @param {object} parentNode - Parent node with children
212
+ * @param {Record<string,any>} parentNode - Parent node with children
195
213
  * @param {number} parentIndex - Parent node index
196
214
  * @param {object[]} nodes - Nodes array to populate
197
- * @param {(number|string)[]} edges - Edges array to populate (mixed types temporarily)
215
+ * @param {number[]} edgeFrom - From node indices
216
+ * @param {string[]} edgePropertyNames - Property names
217
+ * @param {number[]} edgeTo - To node indices
198
218
  * @param {Set<string>} propertySet - Set of property names
199
219
  * @param {WeakMap<object, number>} objectToIndex - Map of objects to indices
200
220
  * @param {Set<string>} childProperties - Set of all child-containing properties
201
221
  */
202
- function processChildrenFt1(parentNode, parentIndex, nodes, edges, propertySet, objectToIndex, childProperties) {
222
+ function processChildrenFt1(
223
+ parentNode,
224
+ parentIndex,
225
+ nodes,
226
+ edgeFrom,
227
+ edgePropertyNames,
228
+ edgeTo,
229
+ propertySet,
230
+ objectToIndex,
231
+ childProperties
232
+ ) {
203
233
  // Process all child-containing properties
204
234
  for (const property of childProperties) {
205
235
  const children = parentNode[property];
206
-
236
+
207
237
  if (!children) {
208
238
  continue;
209
239
  }
210
-
240
+
211
241
  if (Array.isArray(children)) {
212
242
  for (const child of children) {
213
243
  if (child && typeof child === 'object') {
214
- processChildFt1(child, parentIndex, property, nodes, edges, propertySet, objectToIndex, childProperties);
244
+ processChildFt1(
245
+ child,
246
+ parentIndex,
247
+ property,
248
+ nodes,
249
+ edgeFrom,
250
+ edgePropertyNames,
251
+ edgeTo,
252
+ propertySet,
253
+ objectToIndex,
254
+ childProperties
255
+ );
215
256
  }
216
257
  }
217
258
  } else if (children && typeof children === 'object') {
218
- processChildFt1(children, parentIndex, property, nodes, edges, propertySet, objectToIndex, childProperties);
259
+ processChildFt1(
260
+ children,
261
+ parentIndex,
262
+ property,
263
+ nodes,
264
+ edgeFrom,
265
+ edgePropertyNames,
266
+ edgeTo,
267
+ propertySet,
268
+ objectToIndex,
269
+ childProperties
270
+ );
219
271
  }
220
272
  }
221
273
  }
@@ -223,16 +275,29 @@ function processChildrenFt1(parentNode, parentIndex, nodes, edges, propertySet,
223
275
  /**
224
276
  * Process a single child node for ft1 format
225
277
  *
226
- * @param {object} child - Child node
278
+ * @param {Record<string,any>} child - Child node
227
279
  * @param {number} parentIndex - Parent node index
228
280
  * @param {string} property - Property name where this child belongs
229
- * @param {object[]} nodes - Nodes array to populate
230
- * @param {(number|string)[]} edges - Edges array to populate (mixed types temporarily)
281
+ * @param {Record<string,any>[]} nodes - Nodes array to populate
282
+ * @param {number[]} edgeFrom - From node indices
283
+ * @param {string[]} edgePropertyNames - Property names
284
+ * @param {number[]} edgeTo - To node indices
231
285
  * @param {Set<string>} propertySet - Set of property names
232
286
  * @param {WeakMap<object, number>} objectToIndex - Map of objects to indices
233
287
  * @param {Set<string>} childProperties - Set of all child-containing properties
234
288
  */
235
- function processChildFt1(child, parentIndex, property, nodes, edges, propertySet, objectToIndex, childProperties) {
289
+ function processChildFt1(
290
+ child,
291
+ parentIndex,
292
+ property,
293
+ nodes,
294
+ edgeFrom,
295
+ edgePropertyNames,
296
+ edgeTo,
297
+ propertySet,
298
+ objectToIndex,
299
+ childProperties
300
+ ) {
236
301
  if (!child || typeof child !== 'object') {
237
302
  return;
238
303
  }
@@ -242,7 +307,7 @@ function processChildFt1(child, parentIndex, property, nodes, edges, propertySet
242
307
 
243
308
  // Check if we've seen this object before
244
309
  let childIndex = objectToIndex.get(child);
245
-
310
+
246
311
  if (childIndex === undefined) {
247
312
  // First time seeing this object - add it to nodes
248
313
  childIndex = nodes.length;
@@ -251,11 +316,23 @@ function processChildFt1(child, parentIndex, property, nodes, edges, propertySet
251
316
  objectToIndex.set(child, childIndex);
252
317
  }
253
318
 
254
- // Add edge (temporarily store property name as string, will convert to index later)
255
- edges.push(parentIndex, property, childIndex);
256
-
319
+ // Add edge using separate arrays
320
+ edgeFrom.push(parentIndex);
321
+ edgePropertyNames.push(property);
322
+ edgeTo.push(childIndex);
323
+
257
324
  // If this is a new object, recursively process its children
258
325
  if (objectToIndex.get(child) === childIndex) {
259
- processChildrenFt1(child, childIndex, nodes, edges, propertySet, objectToIndex, childProperties);
326
+ processChildrenFt1(
327
+ child,
328
+ childIndex,
329
+ nodes,
330
+ edgeFrom,
331
+ edgePropertyNames,
332
+ edgeTo,
333
+ propertySet,
334
+ objectToIndex,
335
+ childProperties
336
+ );
260
337
  }
261
338
  }
@@ -64,11 +64,24 @@ export default class SceneBase {
64
64
  }
65
65
  }
66
66
 
67
+ let percentageLoaded;
68
+ if (totalSize > 0) {
69
+ // Byte-based progress
70
+ percentageLoaded = Math.round((totalBytesLoaded / totalSize) * 100);
71
+ } else if (numberOfSources > 0) {
72
+ // Source-based progress
73
+ percentageLoaded = Math.round((sourcesLoaded / numberOfSources) * 100);
74
+ } else {
75
+ // No sources to load
76
+ percentageLoaded = 0;
77
+ }
78
+
67
79
  return {
68
80
  totalBytesLoaded,
69
81
  totalSize,
70
82
  sourcesLoaded,
71
- numberOfSources
83
+ numberOfSources,
84
+ percentageLoaded
72
85
  };
73
86
  });
74
87
 
@@ -176,6 +189,9 @@ export default class SceneBase {
176
189
  let progressIntervalId = null;
177
190
 
178
191
  let isAborted = false;
192
+
193
+ /** @type {SceneLoadingProgress|null} */
194
+ let lastSentProgress = null;
179
195
 
180
196
  const abort = () => {
181
197
  if (isAborted) return;
@@ -198,8 +214,10 @@ export default class SceneBase {
198
214
  // Set up progress tracking with polling
199
215
  if (onProgress) {
200
216
  progressIntervalId = setInterval(() => {
201
- if (!isAborted) {
202
- onProgress(this.progress);
217
+ if (!isAborted && this.state === STATE_LOADING) {
218
+ const currentProgress = this.progress;
219
+ lastSentProgress = currentProgress;
220
+ onProgress(currentProgress);
203
221
  }
204
222
  }, 50); // Poll every 50ms
205
223
  }
@@ -231,6 +249,14 @@ export default class SceneBase {
231
249
  if (progressIntervalId) {
232
250
  clearInterval(progressIntervalId);
233
251
  progressIntervalId = null;
252
+
253
+ if (onProgress) {
254
+ const finalProgress = this.progress;
255
+ // Always send final progress when loading completes
256
+ if (this.loaded) {
257
+ onProgress(finalProgress);
258
+ }
259
+ }
234
260
  }
235
261
 
236
262
  if (isAborted || this.state === STATE_ABORTED) {
@@ -17,4 +17,8 @@ export type SceneLoadingProgress = {
17
17
  * - Total number of sources
18
18
  */
19
19
  numberOfSources: number;
20
+ /**
21
+ * - Loading percentage (0-100)
22
+ */
23
+ percentageLoaded: number;
20
24
  };
@@ -4,6 +4,7 @@
4
4
  * @property {number} totalSize - Total size across all sources
5
5
  * @property {number} sourcesLoaded - Number of sources fully loaded
6
6
  * @property {number} numberOfSources - Total number of sources
7
+ * @property {number} percentageLoaded - Loading percentage (0-100)
7
8
  */
8
9
 
9
10
  export default {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.4.58",
3
+ "version": "0.4.60",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"