@hkdigital/lib-core 0.4.58 → 0.4.59
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 !==
|
|
21
|
-
throw new Error(`Unsupported format: ${format}
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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:
|
|
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 {
|
|
160
|
+
* @param {Record<string,any>} node - Source node
|
|
144
161
|
* @param {Set<string>} propertiesToRemove - Set of property names to remove
|
|
145
162
|
*
|
|
146
|
-
* @returns {
|
|
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 {
|
|
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]) {
|
|
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 {
|
|
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 {
|
|
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(
|
|
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(
|
|
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(
|
|
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 {
|
|
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 {
|
|
230
|
-
* @param {
|
|
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(
|
|
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
|
|
255
|
-
|
|
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(
|
|
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
|
}
|
|
@@ -176,6 +176,9 @@ export default class SceneBase {
|
|
|
176
176
|
let progressIntervalId = null;
|
|
177
177
|
|
|
178
178
|
let isAborted = false;
|
|
179
|
+
|
|
180
|
+
/** @type {SceneLoadingProgress|null} */
|
|
181
|
+
let lastSentProgress = null;
|
|
179
182
|
|
|
180
183
|
const abort = () => {
|
|
181
184
|
if (isAborted) return;
|
|
@@ -199,7 +202,9 @@ export default class SceneBase {
|
|
|
199
202
|
if (onProgress) {
|
|
200
203
|
progressIntervalId = setInterval(() => {
|
|
201
204
|
if (!isAborted) {
|
|
202
|
-
|
|
205
|
+
const currentProgress = this.progress;
|
|
206
|
+
lastSentProgress = currentProgress;
|
|
207
|
+
onProgress(currentProgress);
|
|
203
208
|
}
|
|
204
209
|
}, 50); // Poll every 50ms
|
|
205
210
|
}
|
|
@@ -229,6 +234,15 @@ export default class SceneBase {
|
|
|
229
234
|
}
|
|
230
235
|
|
|
231
236
|
if (progressIntervalId) {
|
|
237
|
+
if (onProgress) {
|
|
238
|
+
const finalProgress = this.progress;
|
|
239
|
+
// Only send final update if progress has changed
|
|
240
|
+
if (!lastSentProgress ||
|
|
241
|
+
finalProgress.sourcesLoaded !== lastSentProgress.sourcesLoaded ||
|
|
242
|
+
finalProgress.totalBytesLoaded !== lastSentProgress.totalBytesLoaded) {
|
|
243
|
+
onProgress(finalProgress);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
232
246
|
clearInterval(progressIntervalId);
|
|
233
247
|
progressIntervalId = null;
|
|
234
248
|
}
|