@glossarist/concept-browser 0.2.8 → 0.2.10
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -204,6 +204,31 @@ function scrollToLang(lang: string) {
|
|
|
204
204
|
const outgoingEdges = computed(() => props.edges.filter(e => e.source === props.concept['@id']));
|
|
205
205
|
const incomingEdges = computed(() => props.edges.filter(e => e.target === props.concept['@id']));
|
|
206
206
|
|
|
207
|
+
function isLocalRef(uri: string): boolean {
|
|
208
|
+
const resolution = factory.resolve(uri, props.registerId);
|
|
209
|
+
return resolution.type === 'internal' && resolution.registerId === props.registerId;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function edgeConceptId(uri: string): string {
|
|
213
|
+
const m = uri.match(/\/concept\/([^/]+)$/);
|
|
214
|
+
return m ? m[1] : uri.split('/').pop() || uri;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function edgeNodeData(uri: string) {
|
|
218
|
+
return store.graph.getNode(uri);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function edgeTooltip(uri: string): string {
|
|
222
|
+
const node = edgeNodeData(uri);
|
|
223
|
+
const lines: string[] = [uri];
|
|
224
|
+
if (node) {
|
|
225
|
+
for (const [lang, designation] of Object.entries(node.designations)) {
|
|
226
|
+
lines.push(`${langLabel(lang)}: ${designation}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return lines.join('\n');
|
|
230
|
+
}
|
|
231
|
+
|
|
207
232
|
function edgeDatasetBadge(uri: string): { id: string; title: string } | null {
|
|
208
233
|
const resolution = factory.resolve(uri, props.registerId);
|
|
209
234
|
if (resolution.type === 'internal' && resolution.registerId !== props.registerId) {
|
|
@@ -474,10 +499,14 @@ function plainTruncate(html: string, max: number = 120): string {
|
|
|
474
499
|
v-for="edge in outgoingEdges"
|
|
475
500
|
:key="edge.target"
|
|
476
501
|
@click="navigateEdge(edge)"
|
|
502
|
+
:title="edgeTooltip(edge.target)"
|
|
477
503
|
class="text-sm concept-link block truncate w-full text-left flex items-center gap-1.5"
|
|
504
|
+
:class="isLocalRef(edge.target) ? '' : 'xref-external'"
|
|
478
505
|
>
|
|
479
|
-
{{ edge.
|
|
506
|
+
{{ edgeConceptId(edge.target) }}
|
|
480
507
|
<span v-if="edgeDatasetBadge(edge.target)" class="badge badge-gray text-[9px] flex-shrink-0 truncate max-w-[100px]">{{ edgeDatasetBadge(edge.target)!.title }}</span>
|
|
508
|
+
<span v-if="isLocalRef(edge.target)" class="text-[9px] text-ink-200 flex-shrink-0">local</span>
|
|
509
|
+
<span v-else class="text-[9px] text-amber-500 flex-shrink-0">external</span>
|
|
481
510
|
</button>
|
|
482
511
|
</div>
|
|
483
512
|
</div>
|
|
@@ -488,10 +517,14 @@ function plainTruncate(html: string, max: number = 120): string {
|
|
|
488
517
|
v-for="edge in incomingEdges"
|
|
489
518
|
:key="edge.source"
|
|
490
519
|
@click="navigateEdge(edge)"
|
|
520
|
+
:title="edgeTooltip(edge.source)"
|
|
491
521
|
class="text-sm concept-link block truncate w-full text-left flex items-center gap-1.5"
|
|
522
|
+
:class="isLocalRef(edge.source) ? '' : 'xref-external'"
|
|
492
523
|
>
|
|
493
|
-
{{ edge.
|
|
524
|
+
{{ edgeConceptId(edge.source) }}
|
|
494
525
|
<span v-if="edgeDatasetBadge(edge.source)" class="badge badge-gray text-[9px] flex-shrink-0 truncate max-w-[100px]">{{ edgeDatasetBadge(edge.source)!.title }}</span>
|
|
526
|
+
<span v-if="isLocalRef(edge.source)" class="text-[9px] text-ink-200 flex-shrink-0">local</span>
|
|
527
|
+
<span v-else class="text-[9px] text-amber-500 flex-shrink-0">external</span>
|
|
495
528
|
</button>
|
|
496
529
|
</div>
|
|
497
530
|
</div>
|
|
@@ -27,6 +27,7 @@ const svgRef = ref<SVGSVGElement | null>(null);
|
|
|
27
27
|
const containerRef = ref<HTMLDivElement | null>(null);
|
|
28
28
|
const selectedNode = ref<GraphNode | null>(null);
|
|
29
29
|
const detailCloseRef = ref<HTMLButtonElement | null>(null);
|
|
30
|
+
const labelMode = ref<'designation' | 'identifier'>('designation');
|
|
30
31
|
|
|
31
32
|
// Dataset enable/disable state
|
|
32
33
|
const registerEnabled = reactive<Record<string, boolean>>({});
|
|
@@ -219,7 +220,7 @@ function buildSimulation(width: number, height: number) {
|
|
|
219
220
|
.attr('font-weight', '500')
|
|
220
221
|
.attr('fill', '#636588') // ink-400
|
|
221
222
|
.attr('pointer-events', 'none')
|
|
222
|
-
.text(d => d.designation.slice(0, 18));
|
|
223
|
+
.text(d => (labelMode.value === 'identifier' ? d.conceptId : d.designation).slice(0, 18));
|
|
223
224
|
|
|
224
225
|
const dragBehavior = drag<SVGGElement, SimNode>()
|
|
225
226
|
.on('start', (event: D3DragEvent<SVGGElement, SimNode, SimNode>, d) => {
|
|
@@ -363,6 +364,10 @@ function selectOnly(registerId: string) {
|
|
|
363
364
|
}
|
|
364
365
|
}
|
|
365
366
|
|
|
367
|
+
function registerTitle(id: string): string {
|
|
368
|
+
return props.registers.find(r => r.id === id)?.title ?? id;
|
|
369
|
+
}
|
|
370
|
+
|
|
366
371
|
function selectedNodeColor(): string {
|
|
367
372
|
if (!selectedNode.value) return STUB_COLOR;
|
|
368
373
|
if (!selectedNode.value.loaded) return STUB_COLOR;
|
|
@@ -434,6 +439,22 @@ function selectedNodeColor(): string {
|
|
|
434
439
|
<button @click="rebuildGraph" class="text-[10px] font-semibold text-ink-500 hover:text-ink-700 uppercase tracking-wide transition-colors">Re-layout</button>
|
|
435
440
|
</div>
|
|
436
441
|
|
|
442
|
+
<div v-if="nodeCount > 0" class="mt-3 pt-3 border-t border-ink-100/40">
|
|
443
|
+
<div class="text-[10px] font-semibold text-ink-400 uppercase tracking-wide mb-2">Node labels</div>
|
|
444
|
+
<div class="flex gap-1">
|
|
445
|
+
<button
|
|
446
|
+
@click="labelMode = 'designation'; rebuildGraph()"
|
|
447
|
+
class="text-[10px] px-2 py-1 rounded font-medium transition-colors"
|
|
448
|
+
:class="labelMode === 'designation' ? 'bg-ink-800 text-white' : 'text-ink-500 hover:bg-ink-50'"
|
|
449
|
+
>Designation</button>
|
|
450
|
+
<button
|
|
451
|
+
@click="labelMode = 'identifier'; rebuildGraph()"
|
|
452
|
+
class="text-[10px] px-2 py-1 rounded font-medium transition-colors"
|
|
453
|
+
:class="labelMode === 'identifier' ? 'bg-ink-800 text-white' : 'text-ink-500 hover:bg-ink-50'"
|
|
454
|
+
>Identifier</button>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
437
458
|
<div v-if="nodeCount === 0" class="text-xs text-ink-300 mt-3 leading-relaxed">
|
|
438
459
|
{{ props.edges.length > 0 ? 'Enable datasets to see their graph.' : 'Browse concepts with cross-references to populate the graph.' }}
|
|
439
460
|
</div>
|
|
@@ -441,15 +462,15 @@ function selectedNodeColor(): string {
|
|
|
441
462
|
</div>
|
|
442
463
|
</div>
|
|
443
464
|
|
|
444
|
-
<!-- Legend -->
|
|
445
|
-
<div v-if="nodeCount > 0" class="absolute top-4 right-4 z-10 bg-surface-raised/90 backdrop-blur rounded-lg px-3 py-2.5 border border-ink-100/60 text-xs" style="box-shadow: 0 2px 6px rgba(26, 27, 46, 0.04);">
|
|
465
|
+
<!-- Legend (only when multiple registers) -->
|
|
466
|
+
<div v-if="nodeCount > 0 && registers.length > 1" class="absolute top-4 right-4 z-10 bg-surface-raised/90 backdrop-blur rounded-lg px-3 py-2.5 border border-ink-100/60 text-xs" style="box-shadow: 0 2px 6px rgba(26, 27, 46, 0.04);">
|
|
446
467
|
<div class="font-semibold text-ink-400 text-[10px] uppercase tracking-wide mb-2">Datasets</div>
|
|
447
468
|
<div v-for="reg in registers" :key="reg.id" class="flex items-center gap-2 mb-1.5 last:mb-0">
|
|
448
469
|
<span
|
|
449
470
|
class="w-2.5 h-2.5 rounded-full inline-block flex-shrink-0"
|
|
450
471
|
:style="{ backgroundColor: registerColor(reg.id) }"
|
|
451
472
|
></span>
|
|
452
|
-
<span class="text-ink-500">{{ reg.
|
|
473
|
+
<span class="text-ink-500">{{ reg.title }}</span>
|
|
453
474
|
</div>
|
|
454
475
|
<div class="flex items-center gap-2 mt-2 pt-2 border-t border-ink-100/40">
|
|
455
476
|
<span class="w-2 h-2 rounded-full inline-block" :style="{ backgroundColor: STUB_COLOR }"></span>
|
|
@@ -478,7 +499,7 @@ function selectedNodeColor(): string {
|
|
|
478
499
|
:style="{ backgroundColor: selectedNodeColor() }"
|
|
479
500
|
></span>
|
|
480
501
|
<span class="text-[10px] text-ink-400 uppercase tracking-wide">
|
|
481
|
-
{{ selectedNode.register
|
|
502
|
+
{{ registerTitle(selectedNode.register) }} ·
|
|
482
503
|
{{ selectedNode.loaded ? 'loaded' : 'stub' }}
|
|
483
504
|
</span>
|
|
484
505
|
</div>
|