@glossarist/concept-browser 0.7.21 → 0.7.23
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/index.html +2 -1
- package/package.json +2 -2
- package/scripts/build-edges.js +50 -5
- package/scripts/generate-data.mjs +33 -6
- package/src/App.vue +10 -12
- package/src/__tests__/concept-view.test.ts +7 -1
- package/src/__tests__/dataset-adapter.test.ts +87 -0
- package/src/__tests__/dataset-view.test.ts +1 -0
- package/src/__tests__/factory-lazy.test.ts +183 -0
- package/src/__tests__/graph-engine-fixes.test.ts +104 -0
- package/src/__tests__/ontology-registry.test.ts +1 -1
- package/src/__tests__/performance-v2.test.ts +77 -0
- package/src/__tests__/performance.test.ts +95 -0
- package/src/__tests__/relationship-categories.test.ts +3 -3
- package/src/__tests__/search-utils.test.ts +59 -0
- package/src/__tests__/test-helpers.ts +4 -0
- package/src/__tests__/utils-barrel.test.ts +15 -0
- package/src/__tests__/vocabulary-layered.test.ts +291 -0
- package/src/adapters/DatasetAdapter.ts +41 -1
- package/src/adapters/factory.ts +35 -4
- package/src/adapters/ontology-registry.ts +1 -1
- package/src/adapters/types.ts +12 -0
- package/src/components/AppSidebar.vue +17 -343
- package/src/components/ConceptDetail.vue +124 -55
- package/src/components/GraphPanel.vue +14 -6
- package/src/components/OntologySidebarSection.vue +338 -0
- package/src/config/use-site-config.ts +20 -9
- package/src/data/taxonomies.json +246 -18
- package/src/directives/v-math.ts +2 -3
- package/src/graph/GraphEngine.ts +22 -5
- package/src/i18n/index.ts +1 -1
- package/src/stores/vocabulary.ts +65 -105
- package/src/utils/index.ts +1 -0
- package/src/utils/relationship-categories.ts +15 -6
- package/src/utils/search.ts +15 -0
- package/src/views/ConceptView.vue +0 -2
- package/src/views/DatasetView.vue +64 -39
- package/src/views/HomeView.vue +0 -1
- package/vite.config.ts +94 -6
package/src/data/taxonomies.json
CHANGED
|
@@ -243,24 +243,6 @@
|
|
|
243
243
|
"prefLabel": "related concept (narrower)",
|
|
244
244
|
"definition": "Associative relationship to a concept with narrower scope (ISO 25964 / TBX)."
|
|
245
245
|
},
|
|
246
|
-
"sequentially_related": {
|
|
247
|
-
"id": "sequentially_related",
|
|
248
|
-
"iri": "gloss:rel/sequentially_related",
|
|
249
|
-
"prefLabel": "sequentially related",
|
|
250
|
-
"definition": "Sequential spatiotemporal relationship (ISO 25964 / TBX)."
|
|
251
|
-
},
|
|
252
|
-
"spatially_related": {
|
|
253
|
-
"id": "spatially_related",
|
|
254
|
-
"iri": "gloss:rel/spatially_related",
|
|
255
|
-
"prefLabel": "spatially related",
|
|
256
|
-
"definition": "Spatial relationship (ISO 25964 / TBX)."
|
|
257
|
-
},
|
|
258
|
-
"temporally_related": {
|
|
259
|
-
"id": "temporally_related",
|
|
260
|
-
"iri": "gloss:rel/temporally_related",
|
|
261
|
-
"prefLabel": "temporally related",
|
|
262
|
-
"definition": "Temporal relationship (ISO 25964 / TBX)."
|
|
263
|
-
},
|
|
264
246
|
"homograph": {
|
|
265
247
|
"id": "homograph",
|
|
266
248
|
"iri": "gloss:rel/homograph",
|
|
@@ -284,6 +266,252 @@
|
|
|
284
266
|
"iri": "gloss:rel/short_form_for",
|
|
285
267
|
"prefLabel": "short form for",
|
|
286
268
|
"definition": "This designation is a short form of another designation."
|
|
269
|
+
},
|
|
270
|
+
"deprecated_by": {
|
|
271
|
+
"id": "deprecated_by",
|
|
272
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#deprecated_by",
|
|
273
|
+
"prefLabel": "deprecated by",
|
|
274
|
+
"definition": "This concept has been deprecated by another concept (ISO 10241-1)."
|
|
275
|
+
},
|
|
276
|
+
"replaces": {
|
|
277
|
+
"id": "replaces",
|
|
278
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#replaces",
|
|
279
|
+
"prefLabel": "replaces",
|
|
280
|
+
"definition": "This concept replaces another concept (ISO 10241-1)."
|
|
281
|
+
},
|
|
282
|
+
"replaced_by": {
|
|
283
|
+
"id": "replaced_by",
|
|
284
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#replaced_by",
|
|
285
|
+
"prefLabel": "replaced by",
|
|
286
|
+
"definition": "This concept has been replaced by another concept (ISO 10241-1)."
|
|
287
|
+
},
|
|
288
|
+
"invalidates": {
|
|
289
|
+
"id": "invalidates",
|
|
290
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#invalidates",
|
|
291
|
+
"prefLabel": "invalidates",
|
|
292
|
+
"definition": "This concept invalidates another concept, indicating it contains a substantial error and should not be used (ISO 19135)."
|
|
293
|
+
},
|
|
294
|
+
"invalidated_by": {
|
|
295
|
+
"id": "invalidated_by",
|
|
296
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#invalidated_by",
|
|
297
|
+
"prefLabel": "invalidated by",
|
|
298
|
+
"definition": "This concept has been invalidated by another concept due to a substantial error (ISO 19135)."
|
|
299
|
+
},
|
|
300
|
+
"retires": {
|
|
301
|
+
"id": "retires",
|
|
302
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#retires",
|
|
303
|
+
"prefLabel": "retires",
|
|
304
|
+
"definition": "This concept retires another concept, indicating it is no longer recommended for use (ISO 19135)."
|
|
305
|
+
},
|
|
306
|
+
"retired_by": {
|
|
307
|
+
"id": "retired_by",
|
|
308
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#retired_by",
|
|
309
|
+
"prefLabel": "retired by",
|
|
310
|
+
"definition": "This concept has been retired by another concept (ISO 19135)."
|
|
311
|
+
},
|
|
312
|
+
"has_concept": {
|
|
313
|
+
"id": "has_concept",
|
|
314
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#has_concept",
|
|
315
|
+
"prefLabel": "has concept",
|
|
316
|
+
"definition": "This concept has the nominated concept as a specialisation of its definition (ISO 19135)."
|
|
317
|
+
},
|
|
318
|
+
"is_concept_of": {
|
|
319
|
+
"id": "is_concept_of",
|
|
320
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#is_concept_of",
|
|
321
|
+
"prefLabel": "is concept of",
|
|
322
|
+
"definition": "This concept is a specialisation of the source concept (ISO 19135)."
|
|
323
|
+
},
|
|
324
|
+
"instance_of": {
|
|
325
|
+
"id": "instance_of",
|
|
326
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#instance_of",
|
|
327
|
+
"prefLabel": "instance of",
|
|
328
|
+
"definition": "This concept is an instance of the nominated concept (ISO 19135)."
|
|
329
|
+
},
|
|
330
|
+
"has_instance": {
|
|
331
|
+
"id": "has_instance",
|
|
332
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#has_instance",
|
|
333
|
+
"prefLabel": "has instance",
|
|
334
|
+
"definition": "This concept has the nominated concept as an instance (ISO 19135)."
|
|
335
|
+
},
|
|
336
|
+
"has_definition": {
|
|
337
|
+
"id": "has_definition",
|
|
338
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#has_definition",
|
|
339
|
+
"prefLabel": "has definition",
|
|
340
|
+
"definition": "This concept has the nominated concept as its definition (ISO 19135)."
|
|
341
|
+
},
|
|
342
|
+
"definition_of": {
|
|
343
|
+
"id": "definition_of",
|
|
344
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#definition_of",
|
|
345
|
+
"prefLabel": "definition of",
|
|
346
|
+
"definition": "This concept is the definition of the source concept (ISO 19135)."
|
|
347
|
+
},
|
|
348
|
+
"has_part": {
|
|
349
|
+
"id": "has_part",
|
|
350
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#has_part",
|
|
351
|
+
"prefLabel": "has part",
|
|
352
|
+
"definition": "This concept has the nominated concept as a part of its composition (ISO 19135)."
|
|
353
|
+
},
|
|
354
|
+
"is_part_of": {
|
|
355
|
+
"id": "is_part_of",
|
|
356
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#is_part_of",
|
|
357
|
+
"prefLabel": "is part of",
|
|
358
|
+
"definition": "This concept is a part of the source concept (ISO 19135)."
|
|
359
|
+
},
|
|
360
|
+
"inherits": {
|
|
361
|
+
"id": "inherits",
|
|
362
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#inherits",
|
|
363
|
+
"prefLabel": "inherits",
|
|
364
|
+
"definition": "This concept inherits and extends properties from the nominated concept (ISO 19135)."
|
|
365
|
+
},
|
|
366
|
+
"inherited_by": {
|
|
367
|
+
"id": "inherited_by",
|
|
368
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#inherited_by",
|
|
369
|
+
"prefLabel": "inherited by",
|
|
370
|
+
"definition": "This concept is inherited by the source concept (ISO 19135)."
|
|
371
|
+
},
|
|
372
|
+
"has_version": {
|
|
373
|
+
"id": "has_version",
|
|
374
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#has_version",
|
|
375
|
+
"prefLabel": "has version",
|
|
376
|
+
"definition": "This concept has the nominated concept as a version (ISO 19135)."
|
|
377
|
+
},
|
|
378
|
+
"version_of": {
|
|
379
|
+
"id": "version_of",
|
|
380
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#version_of",
|
|
381
|
+
"prefLabel": "version of",
|
|
382
|
+
"definition": "This concept is a version of the source concept (ISO 19135)."
|
|
383
|
+
},
|
|
384
|
+
"current_version": {
|
|
385
|
+
"id": "current_version",
|
|
386
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#current_version",
|
|
387
|
+
"prefLabel": "current version",
|
|
388
|
+
"definition": "This concept has the nominated concept as its current version (ISO 19135)."
|
|
389
|
+
},
|
|
390
|
+
"current_version_of": {
|
|
391
|
+
"id": "current_version_of",
|
|
392
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#current_version_of",
|
|
393
|
+
"prefLabel": "current version of",
|
|
394
|
+
"definition": "This concept is the current version of the source concept (ISO 19135)."
|
|
395
|
+
},
|
|
396
|
+
"broader": {
|
|
397
|
+
"id": "broader",
|
|
398
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#broader",
|
|
399
|
+
"prefLabel": "broader",
|
|
400
|
+
"definition": "This concept has a broader (more general) scope (SKOS)."
|
|
401
|
+
},
|
|
402
|
+
"narrower": {
|
|
403
|
+
"id": "narrower",
|
|
404
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#narrower",
|
|
405
|
+
"prefLabel": "narrower",
|
|
406
|
+
"definition": "This concept has a narrower (more specific) scope (SKOS)."
|
|
407
|
+
},
|
|
408
|
+
"broader_generic": {
|
|
409
|
+
"id": "broader_generic",
|
|
410
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#broader_generic",
|
|
411
|
+
"prefLabel": "broader (generic)",
|
|
412
|
+
"definition": "Generic (is-a) broader relationship (ISO 25964)."
|
|
413
|
+
},
|
|
414
|
+
"narrower_generic": {
|
|
415
|
+
"id": "narrower_generic",
|
|
416
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#narrower_generic",
|
|
417
|
+
"prefLabel": "narrower (generic)",
|
|
418
|
+
"definition": "Generic (is-a) narrower relationship (ISO 25964)."
|
|
419
|
+
},
|
|
420
|
+
"broader_partitive": {
|
|
421
|
+
"id": "broader_partitive",
|
|
422
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#broader_partitive",
|
|
423
|
+
"prefLabel": "broader (partitive)",
|
|
424
|
+
"definition": "Partitive (whole-of) broader relationship (ISO 25964)."
|
|
425
|
+
},
|
|
426
|
+
"narrower_partitive": {
|
|
427
|
+
"id": "narrower_partitive",
|
|
428
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#narrower_partitive",
|
|
429
|
+
"prefLabel": "narrower (partitive)",
|
|
430
|
+
"definition": "Partitive (part-of) narrower relationship (ISO 25964)."
|
|
431
|
+
},
|
|
432
|
+
"broader_instantial": {
|
|
433
|
+
"id": "broader_instantial",
|
|
434
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#broader_instantial",
|
|
435
|
+
"prefLabel": "broader (instantial)",
|
|
436
|
+
"definition": "Instantial broader relationship (ISO 25964)."
|
|
437
|
+
},
|
|
438
|
+
"narrower_instantial": {
|
|
439
|
+
"id": "narrower_instantial",
|
|
440
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#narrower_instantial",
|
|
441
|
+
"prefLabel": "narrower (instantial)",
|
|
442
|
+
"definition": "Instantial narrower relationship (ISO 25964)."
|
|
443
|
+
},
|
|
444
|
+
"equivalent": {
|
|
445
|
+
"id": "equivalent",
|
|
446
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#equivalent",
|
|
447
|
+
"prefLabel": "equivalent",
|
|
448
|
+
"definition": "This concept is equivalent to another concept (SKOS)."
|
|
449
|
+
},
|
|
450
|
+
"close_match": {
|
|
451
|
+
"id": "close_match",
|
|
452
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#close_match",
|
|
453
|
+
"prefLabel": "close match",
|
|
454
|
+
"definition": "This concept is a close match to another concept (SKOS)."
|
|
455
|
+
},
|
|
456
|
+
"broad_match": {
|
|
457
|
+
"id": "broad_match",
|
|
458
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#broad_match",
|
|
459
|
+
"prefLabel": "broad match",
|
|
460
|
+
"definition": "This concept has a broader match to another concept (SKOS)."
|
|
461
|
+
},
|
|
462
|
+
"narrow_match": {
|
|
463
|
+
"id": "narrow_match",
|
|
464
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#narrow_match",
|
|
465
|
+
"prefLabel": "narrow match",
|
|
466
|
+
"definition": "This concept has a narrower match to another concept (SKOS)."
|
|
467
|
+
},
|
|
468
|
+
"related_match": {
|
|
469
|
+
"id": "related_match",
|
|
470
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#related_match",
|
|
471
|
+
"prefLabel": "related match",
|
|
472
|
+
"definition": "This concept has a related match to another concept (SKOS)."
|
|
473
|
+
},
|
|
474
|
+
"see": {
|
|
475
|
+
"id": "see",
|
|
476
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#see",
|
|
477
|
+
"prefLabel": "see",
|
|
478
|
+
"definition": "Reference to another concept (ISO 10241-1)."
|
|
479
|
+
},
|
|
480
|
+
"related_concept": {
|
|
481
|
+
"id": "related_concept",
|
|
482
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#related_concept",
|
|
483
|
+
"prefLabel": "related concept",
|
|
484
|
+
"definition": "Associative relationship to another concept (ISO 25964)."
|
|
485
|
+
},
|
|
486
|
+
"references": {
|
|
487
|
+
"id": "references",
|
|
488
|
+
"iri": "https://glossarist.org/ontologies/relationship-type#references",
|
|
489
|
+
"prefLabel": "references",
|
|
490
|
+
"definition": "This concept references another concept."
|
|
491
|
+
},
|
|
492
|
+
"sequentially_related": {
|
|
493
|
+
"id": "sequentially_related",
|
|
494
|
+
"iri": "gloss:rel/sequentially_related",
|
|
495
|
+
"prefLabel": "sequentially related",
|
|
496
|
+
"definition": "Sequential spatiotemporal relationship (ISO 25964 / TBX)."
|
|
497
|
+
},
|
|
498
|
+
"spatially_related": {
|
|
499
|
+
"id": "spatially_related",
|
|
500
|
+
"iri": "gloss:rel/spatially_related",
|
|
501
|
+
"prefLabel": "spatially related",
|
|
502
|
+
"definition": "Spatial relationship (ISO 25964 / TBX)."
|
|
503
|
+
},
|
|
504
|
+
"temporally_related": {
|
|
505
|
+
"id": "temporally_related",
|
|
506
|
+
"iri": "gloss:rel/temporally_related",
|
|
507
|
+
"prefLabel": "temporally related",
|
|
508
|
+
"definition": "Temporal relationship (ISO 25964 / TBX)."
|
|
509
|
+
},
|
|
510
|
+
"exact_match": {
|
|
511
|
+
"id": "exact_match",
|
|
512
|
+
"iri": "gloss:rel/exact_match",
|
|
513
|
+
"prefLabel": "exact match",
|
|
514
|
+
"definition": "Cross-vocabulary exact equivalence (SKOS skos:exactMatch)."
|
|
287
515
|
}
|
|
288
516
|
}
|
|
289
517
|
},
|
package/src/directives/v-math.ts
CHANGED
|
@@ -4,8 +4,7 @@ import { loadPlurimath, mathToHtml } from '../utils/plurimath';
|
|
|
4
4
|
let loaded = false;
|
|
5
5
|
|
|
6
6
|
function upgrade(el: HTMLElement) {
|
|
7
|
-
|
|
8
|
-
if (!pending.length) return;
|
|
7
|
+
if (!el.querySelector('.math-pending')) return;
|
|
9
8
|
|
|
10
9
|
if (!loaded) {
|
|
11
10
|
loadPlurimath().then(() => {
|
|
@@ -15,7 +14,7 @@ function upgrade(el: HTMLElement) {
|
|
|
15
14
|
return;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
pending.forEach((span) => {
|
|
17
|
+
el.querySelectorAll('.math-pending').forEach((span) => {
|
|
19
18
|
const expr = (span as HTMLElement).dataset.expr;
|
|
20
19
|
const format = (span as HTMLElement).dataset.format || 'asciimath';
|
|
21
20
|
const bold = span.classList.contains('math-bold');
|
package/src/graph/GraphEngine.ts
CHANGED
|
@@ -72,15 +72,23 @@ export class GraphEngine {
|
|
|
72
72
|
if (from) {
|
|
73
73
|
const adj = this.adjacency.get(from);
|
|
74
74
|
if (!adj) return [];
|
|
75
|
-
|
|
75
|
+
const result: GraphEdge[] = [];
|
|
76
|
+
for (const list of adj.values()) {
|
|
77
|
+
for (const e of list) result.push(e);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
76
80
|
}
|
|
77
|
-
return this.edges;
|
|
81
|
+
return [...this.edges];
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
getIncomingEdges(uri: string): GraphEdge[] {
|
|
81
85
|
const adj = this.reverseAdjacency.get(uri);
|
|
82
86
|
if (!adj) return [];
|
|
83
|
-
|
|
87
|
+
const result: GraphEdge[] = [];
|
|
88
|
+
for (const list of adj.values()) {
|
|
89
|
+
for (const e of list) result.push(e);
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
getNeighbors(uri: string): { outgoing: string[]; incoming: string[] } {
|
|
@@ -100,9 +108,10 @@ export class GraphEngine {
|
|
|
100
108
|
const collectedNodes: GraphNode[] = [];
|
|
101
109
|
const collectedEdges: GraphEdge[] = [];
|
|
102
110
|
const queue: { uri: string; d: number }[] = [{ uri: rootUri, d: 0 }];
|
|
111
|
+
let head = 0;
|
|
103
112
|
|
|
104
|
-
while (queue.length
|
|
105
|
-
const { uri, d } = queue
|
|
113
|
+
while (head < queue.length) {
|
|
114
|
+
const { uri, d } = queue[head++];
|
|
106
115
|
if (visited.has(uri) || d > depth) continue;
|
|
107
116
|
visited.add(uri);
|
|
108
117
|
|
|
@@ -137,4 +146,12 @@ export class GraphEngine {
|
|
|
137
146
|
get edgeCount(): number {
|
|
138
147
|
return this.edges.length;
|
|
139
148
|
}
|
|
149
|
+
|
|
150
|
+
clear(): void {
|
|
151
|
+
this.nodes.clear();
|
|
152
|
+
this.edges.length = 0;
|
|
153
|
+
this.edgeKeys.clear();
|
|
154
|
+
this.adjacency.clear();
|
|
155
|
+
this.reverseAdjacency.clear();
|
|
156
|
+
}
|
|
140
157
|
}
|
package/src/i18n/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ref } from 'vue';
|
|
2
|
+
import { DEFAULT_LANG } from '../utils/lang';
|
|
2
3
|
|
|
3
4
|
// Auto-discover all locale YAML files — adding a new .yml file is all that's needed
|
|
4
5
|
const localeModules = import.meta.glob<{ default: Record<string, string> }>('./locales/*.yml', { eager: true });
|
|
@@ -14,7 +15,6 @@ for (const path of Object.keys(localeModules)) {
|
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
const DEFAULT_LANG = 'eng';
|
|
18
18
|
const stored = typeof localStorage !== 'undefined'
|
|
19
19
|
? (localStorage.getItem('ui-lang') || DEFAULT_LANG)
|
|
20
20
|
: DEFAULT_LANG;
|
package/src/stores/vocabulary.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { defineStore } from 'pinia';
|
|
2
|
-
import { ref,
|
|
2
|
+
import { ref, shallowRef, computed } from 'vue';
|
|
3
3
|
import { getFactory } from '../adapters/factory';
|
|
4
4
|
import type { DatasetAdapter } from '../adapters/DatasetAdapter';
|
|
5
5
|
import type { Manifest, SearchHit, GraphEdge } from '../adapters/types';
|
|
6
6
|
import type { Concept } from 'glossarist';
|
|
7
7
|
import { conceptUri } from '../adapters/model-bridge';
|
|
8
8
|
import { GraphEngine } from '../graph';
|
|
9
|
+
import { deduplicateSearchHits } from '../utils/search';
|
|
9
10
|
|
|
10
11
|
export const useVocabularyStore = defineStore('vocabulary', () => {
|
|
11
12
|
// State
|
|
@@ -16,7 +17,7 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
|
|
|
16
17
|
const currentConceptId = ref<string>('');
|
|
17
18
|
const loading = ref(false);
|
|
18
19
|
const error = ref<string | null>(null);
|
|
19
|
-
const graph =
|
|
20
|
+
const graph = shallowRef(new GraphEngine());
|
|
20
21
|
const conceptEdges = ref<GraphEdge[]>([]);
|
|
21
22
|
const initialized = ref(false);
|
|
22
23
|
|
|
@@ -76,75 +77,19 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
|
|
|
76
77
|
manifests.value.set(registerId, adapter.manifest);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
// Load pre-computed edges (lightweight)
|
|
80
|
-
await loadEdges(adapter);
|
|
81
|
-
|
|
82
80
|
touchGraph();
|
|
83
|
-
|
|
84
|
-
// Seed graph nodes lazily — don't block UI for large datasets
|
|
85
|
-
seedGraphNodes(registerId, adapter);
|
|
86
81
|
} catch (e: unknown) {
|
|
87
82
|
error.value = `Failed to load dataset ${registerId}: ${e instanceof Error ? e.message : String(e)}`;
|
|
88
83
|
throw e;
|
|
89
84
|
}
|
|
90
85
|
}
|
|
91
86
|
|
|
92
|
-
function seedGraphNodes(registerId: string, adapter: DatasetAdapter, sync = false) {
|
|
93
|
-
const entries = adapter.getConcepts();
|
|
94
|
-
|
|
95
|
-
if (sync) {
|
|
96
|
-
for (const entry of entries) {
|
|
97
|
-
if (!entry) continue;
|
|
98
|
-
graph.value.addNode({
|
|
99
|
-
uri: factory.router.buildUri(registerId, entry.id),
|
|
100
|
-
register: registerId,
|
|
101
|
-
conceptId: entry.id,
|
|
102
|
-
designations: entry.designations,
|
|
103
|
-
status: entry.status,
|
|
104
|
-
loaded: false,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
touchGraph();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const batchSize = 500;
|
|
112
|
-
let offset = 0;
|
|
113
|
-
const schedule = typeof requestIdleCallback !== 'undefined'
|
|
114
|
-
? requestIdleCallback
|
|
115
|
-
: (cb: () => void) => setTimeout(cb, 0);
|
|
116
|
-
|
|
117
|
-
function processBatch() {
|
|
118
|
-
const end = Math.min(offset + batchSize, entries.length);
|
|
119
|
-
for (let i = offset; i < end; i++) {
|
|
120
|
-
const entry = entries[i];
|
|
121
|
-
if (!entry) continue;
|
|
122
|
-
graph.value.addNode({
|
|
123
|
-
uri: factory.router.buildUri(registerId, entry.id),
|
|
124
|
-
register: registerId,
|
|
125
|
-
conceptId: entry.id,
|
|
126
|
-
designations: entry.designations,
|
|
127
|
-
status: entry.status,
|
|
128
|
-
loaded: false,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
offset = end;
|
|
132
|
-
if (offset < entries.length) {
|
|
133
|
-
schedule(processBatch);
|
|
134
|
-
} else {
|
|
135
|
-
touchGraph();
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
schedule(processBatch);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
87
|
async function loadAllGraphData() {
|
|
143
88
|
if (!initialized.value) {
|
|
144
89
|
await discoverDatasets();
|
|
145
90
|
}
|
|
146
91
|
|
|
147
|
-
const engine =
|
|
92
|
+
const engine = graph.value;
|
|
148
93
|
const adapters = factory.getAdapters();
|
|
149
94
|
|
|
150
95
|
await Promise.allSettled(adapters.map(async (adapter) => {
|
|
@@ -197,11 +142,12 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
|
|
|
197
142
|
adapter.loadEdgeIndex(),
|
|
198
143
|
adapter.loadDomainNodes(),
|
|
199
144
|
]);
|
|
145
|
+
const engine = graph.value;
|
|
200
146
|
for (const dn of domainNodes) {
|
|
201
|
-
|
|
147
|
+
engine.addNode(dn);
|
|
202
148
|
}
|
|
203
149
|
for (const edge of edges) {
|
|
204
|
-
|
|
150
|
+
engine.addEdge(edge);
|
|
205
151
|
}
|
|
206
152
|
edgeStatus.value[adapter.registerId] = { loaded: true, count: edges.length };
|
|
207
153
|
} catch {
|
|
@@ -209,19 +155,19 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
|
|
|
209
155
|
}
|
|
210
156
|
}
|
|
211
157
|
|
|
212
|
-
async function
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
158
|
+
async function ensureEdgesForDataset(registerId: string) {
|
|
159
|
+
const adapter = datasets.value.get(registerId);
|
|
160
|
+
if (adapter && !edgeStatus.value[registerId]?.loaded) {
|
|
161
|
+
await loadEdges(adapter);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const index = await factory.loadCrossRefIndex();
|
|
165
|
+
const refs = index[registerId] || [];
|
|
166
|
+
for (const refId of refs) {
|
|
167
|
+
if (edgeStatus.value[refId]?.loaded) continue;
|
|
168
|
+
const refAdapter = datasets.value.get(refId);
|
|
169
|
+
if (!refAdapter) continue;
|
|
170
|
+
await loadEdges(refAdapter);
|
|
225
171
|
}
|
|
226
172
|
}
|
|
227
173
|
|
|
@@ -234,16 +180,18 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
|
|
|
234
180
|
const adapter = datasets.value.get(registerId);
|
|
235
181
|
if (!adapter) throw new Error(`Dataset ${registerId} not loaded`);
|
|
236
182
|
|
|
237
|
-
|
|
183
|
+
// Fetch concept and cross-dataset edges in parallel
|
|
184
|
+
const [concept] = await Promise.all([
|
|
185
|
+
adapter.fetchConcept(conceptId),
|
|
186
|
+
ensureEdgesForDataset(registerId),
|
|
187
|
+
]);
|
|
238
188
|
currentConcept.value = concept;
|
|
239
189
|
|
|
240
|
-
// Extract and register edges for this specific concept
|
|
241
190
|
const edges = adapter.extractEdges(concept);
|
|
242
191
|
const domainEdges = adapter.extractDomainEdges(concept);
|
|
243
192
|
const uriBase = adapter.manifest?.uriBase || 'https://glossarist.org';
|
|
244
193
|
const uri = conceptUri(concept, registerId, uriBase);
|
|
245
194
|
|
|
246
|
-
// Update graph node with full data
|
|
247
195
|
const designations: Record<string, string> = {};
|
|
248
196
|
const indexEntry = adapter.getIndexEntry(conceptId);
|
|
249
197
|
if (indexEntry) {
|
|
@@ -258,7 +206,8 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
|
|
|
258
206
|
}
|
|
259
207
|
}
|
|
260
208
|
|
|
261
|
-
graph.value
|
|
209
|
+
const engine = graph.value;
|
|
210
|
+
engine.addNode({
|
|
262
211
|
uri,
|
|
263
212
|
register: registerId,
|
|
264
213
|
conceptId,
|
|
@@ -268,14 +217,14 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
|
|
|
268
217
|
});
|
|
269
218
|
|
|
270
219
|
for (const edge of edges) {
|
|
271
|
-
|
|
220
|
+
engine.addEdge(edge);
|
|
272
221
|
}
|
|
273
222
|
|
|
274
223
|
for (const edge of domainEdges) {
|
|
275
|
-
|
|
276
|
-
const existing =
|
|
224
|
+
engine.addEdge(edge);
|
|
225
|
+
const existing = engine.getNode(edge.target);
|
|
277
226
|
if (!existing || !existing.loaded) {
|
|
278
|
-
|
|
227
|
+
engine.addNode({
|
|
279
228
|
uri: edge.target,
|
|
280
229
|
register: registerId,
|
|
281
230
|
conceptId: '',
|
|
@@ -287,13 +236,10 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
|
|
|
287
236
|
}
|
|
288
237
|
}
|
|
289
238
|
|
|
290
|
-
// Ensure edges from all datasets are loaded for cross-dataset supersession
|
|
291
|
-
await ensureAllEdgesLoaded();
|
|
292
|
-
|
|
293
239
|
touchGraph();
|
|
294
240
|
conceptEdges.value = [
|
|
295
|
-
...
|
|
296
|
-
...
|
|
241
|
+
...engine.getEdges(uri),
|
|
242
|
+
...engine.getIncomingEdges(uri),
|
|
297
243
|
];
|
|
298
244
|
} catch (e: unknown) {
|
|
299
245
|
error.value = `Failed to load concept ${conceptId}: ${e instanceof Error ? e.message : String(e)}`;
|
|
@@ -326,32 +272,46 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
|
|
|
326
272
|
await discoverDatasets();
|
|
327
273
|
}
|
|
328
274
|
|
|
329
|
-
const
|
|
275
|
+
const MIN_RESULTS = 20;
|
|
276
|
+
|
|
277
|
+
// Pass 1: search loaded data only
|
|
278
|
+
const loadedHits: SearchHit[] = [];
|
|
279
|
+
const unloadedAdapters: DatasetAdapter[] = [];
|
|
280
|
+
|
|
330
281
|
for (const adapter of datasets.value.values()) {
|
|
331
|
-
if (adapter.manifest)
|
|
332
|
-
|
|
282
|
+
if (!adapter.manifest) continue;
|
|
283
|
+
if (!adapter.index) {
|
|
284
|
+
try {
|
|
333
285
|
await adapter.loadIndex();
|
|
286
|
+
} catch {
|
|
287
|
+
continue;
|
|
334
288
|
}
|
|
335
|
-
|
|
336
|
-
|
|
289
|
+
}
|
|
290
|
+
const hits = adapter.search(query);
|
|
291
|
+
if (hits.length > 0) {
|
|
292
|
+
loadedHits.push(...hits);
|
|
293
|
+
} else {
|
|
294
|
+
unloadedAdapters.push(adapter);
|
|
337
295
|
}
|
|
338
296
|
}
|
|
339
297
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
298
|
+
const pass1 = deduplicateSearchHits(loadedHits);
|
|
299
|
+
if (pass1.length >= MIN_RESULTS) return pass1;
|
|
300
|
+
|
|
301
|
+
// Pass 2: load chunks lazily for datasets that found nothing in index
|
|
302
|
+
let allHits = [...loadedHits];
|
|
303
|
+
for (const adapter of unloadedAdapters) {
|
|
304
|
+
if (deduplicateSearchHits(allHits).length >= MIN_RESULTS) break;
|
|
305
|
+
try {
|
|
306
|
+
await adapter.ensureAllChunksLoaded();
|
|
307
|
+
const hits = adapter.search(query);
|
|
308
|
+
allHits.push(...hits);
|
|
309
|
+
} catch {
|
|
310
|
+
// Skip datasets that fail to load
|
|
352
311
|
}
|
|
353
312
|
}
|
|
354
|
-
|
|
313
|
+
|
|
314
|
+
return deduplicateSearchHits(allHits);
|
|
355
315
|
}
|
|
356
316
|
|
|
357
317
|
async function getRandomConcept(): Promise<{ registerId: string; conceptId: string } | null> {
|
package/src/utils/index.ts
CHANGED