@glossarist/concept-browser 0.4.5 → 0.4.6
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.4.
|
|
3
|
+
"version": "0.4.6",
|
|
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": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue';
|
|
2
|
+
import { computed, watch } from 'vue';
|
|
3
3
|
import { useVocabularyStore } from '../stores/vocabulary';
|
|
4
4
|
import { useUiStore } from '../stores/ui';
|
|
5
5
|
import { useRoute, useRouter } from 'vue-router';
|
|
@@ -25,6 +25,7 @@ const {
|
|
|
25
25
|
taxonomyLabels,
|
|
26
26
|
treeRoots,
|
|
27
27
|
supportingClasses,
|
|
28
|
+
isOverview,
|
|
28
29
|
toggleExpand,
|
|
29
30
|
childClasses,
|
|
30
31
|
hasChildren,
|
|
@@ -72,6 +73,22 @@ function isActive(page: { route: string; datasetScoped?: boolean }): boolean {
|
|
|
72
73
|
}
|
|
73
74
|
return route.name === page.route;
|
|
74
75
|
}
|
|
76
|
+
|
|
77
|
+
function selectClass(id: string) {
|
|
78
|
+
activeClassId.value = id;
|
|
79
|
+
activeTaxonomy.value = null;
|
|
80
|
+
scrollMainToTop();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function selectTaxonomy(key: string) {
|
|
84
|
+
activeTaxonomy.value = key;
|
|
85
|
+
scrollMainToTop();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function scrollMainToTop() {
|
|
89
|
+
const main = document.querySelector('main');
|
|
90
|
+
if (main) main.scrollTo({ top: 0, behavior: 'smooth' });
|
|
91
|
+
}
|
|
75
92
|
</script>
|
|
76
93
|
|
|
77
94
|
<template>
|
|
@@ -88,85 +105,92 @@ function isActive(page: { route: string; datasetScoped?: boolean }): boolean {
|
|
|
88
105
|
<!-- Navigation -->
|
|
89
106
|
<div class="section-label">Navigation</div>
|
|
90
107
|
<nav class="space-y-0.5 mb-6">
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
</router-link>
|
|
102
|
-
</nav>
|
|
108
|
+
<template v-for="page in globalPages" :key="page.route || 'home'">
|
|
109
|
+
<router-link
|
|
110
|
+
:to="pageRoute(page)"
|
|
111
|
+
class="btn-ghost w-full text-left flex items-center gap-2"
|
|
112
|
+
:class="isActive(page) ? 'active' : ''"
|
|
113
|
+
@click="closeMobile"
|
|
114
|
+
>
|
|
115
|
+
<NavIcon :name="page.icon" />
|
|
116
|
+
{{ page.title }}
|
|
117
|
+
</router-link>
|
|
103
118
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<template v-for="root in treeRoots" :key="root.compact">
|
|
109
|
-
<button @click="activeClassId = root.compact; activeTaxonomy = null; toggleExpand(root)"
|
|
119
|
+
<!-- Ontology class tree nested under Ontology nav item -->
|
|
120
|
+
<div v-if="page.route === 'ontology' && isOntologyRoute" class="ml-4 mt-1 mb-2 space-y-0.5">
|
|
121
|
+
<!-- Overview -->
|
|
122
|
+
<button @click="activeClassId = null; activeTaxonomy = null; scrollMainToTop()"
|
|
110
123
|
class="w-full flex items-center gap-1.5 px-2 py-1.5 rounded-lg text-xs transition-colors"
|
|
111
|
-
:class="
|
|
124
|
+
:class="isOverview ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-600 hover:bg-ink-50'"
|
|
112
125
|
>
|
|
113
|
-
<span
|
|
114
|
-
<span
|
|
115
|
-
<span class="flex-1 text-left">{{ root.label }}</span>
|
|
126
|
+
<span class="w-3 text-ink-200">·</span>
|
|
127
|
+
<span class="flex-1 text-left">Overview</span>
|
|
116
128
|
</button>
|
|
117
|
-
|
|
118
|
-
<div
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
|
|
130
|
+
<div class="text-[10px] uppercase tracking-wide text-ink-300 mt-2 mb-1 px-2">Classes</div>
|
|
131
|
+
<template v-for="root in treeRoots" :key="root.compact">
|
|
132
|
+
<button @click="selectClass(root.compact); toggleExpand(root)"
|
|
133
|
+
class="w-full flex items-center gap-1.5 px-2 py-1.5 rounded-lg text-xs transition-colors"
|
|
134
|
+
:class="activeClassId === root.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-600 hover:bg-ink-50'"
|
|
135
|
+
>
|
|
136
|
+
<span v-if="hasChildren(root)" class="text-[10px] text-ink-300 w-3">{{ expandedClasses.has(root.compact) ? '▾' : '▸' }}</span>
|
|
137
|
+
<span v-else class="w-3 text-ink-200">·</span>
|
|
138
|
+
<span class="flex-1 text-left">{{ root.label }}</span>
|
|
139
|
+
</button>
|
|
140
|
+
<!-- Children -->
|
|
141
|
+
<div v-if="expandedClasses.has(root.compact) && hasChildren(root)" class="ml-3">
|
|
142
|
+
<template v-for="child in childClasses(root.compact)" :key="child.compact">
|
|
143
|
+
<button @click="selectClass(child.compact); toggleExpand(child)"
|
|
132
144
|
class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
|
|
133
|
-
:class="activeClassId ===
|
|
145
|
+
:class="activeClassId === child.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-500 hover:bg-ink-50'"
|
|
134
146
|
>
|
|
135
|
-
<span class="
|
|
136
|
-
<span class="
|
|
147
|
+
<span v-if="hasChildren(child)" class="text-[10px] text-ink-300 w-3">{{ expandedClasses.has(child.compact) ? '▾' : '▸' }}</span>
|
|
148
|
+
<span v-else class="w-3 text-ink-200">·</span>
|
|
149
|
+
<span class="flex-1 text-left">{{ child.label }}</span>
|
|
137
150
|
</button>
|
|
138
|
-
|
|
139
|
-
|
|
151
|
+
<!-- Grandchildren -->
|
|
152
|
+
<div v-if="expandedClasses.has(child.compact) && hasChildren(child)" class="ml-3">
|
|
153
|
+
<button v-for="gc in childClasses(child.compact)" :key="gc.compact"
|
|
154
|
+
@click="selectClass(gc.compact)"
|
|
155
|
+
class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
|
|
156
|
+
:class="activeClassId === gc.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-400 hover:bg-ink-50'"
|
|
157
|
+
>
|
|
158
|
+
<span class="w-3 text-ink-200">·</span>
|
|
159
|
+
<span class="flex-1 text-left">{{ gc.label }}</span>
|
|
160
|
+
</button>
|
|
161
|
+
</div>
|
|
162
|
+
</template>
|
|
163
|
+
</div>
|
|
164
|
+
</template>
|
|
165
|
+
|
|
166
|
+
<!-- Supporting classes -->
|
|
167
|
+
<div v-if="supportingClasses.length" class="mt-2 pt-2 border-t border-ink-100/40">
|
|
168
|
+
<div class="text-[10px] uppercase tracking-wide text-ink-300 mb-1 px-2">Supporting</div>
|
|
169
|
+
<button v-for="cls in supportingClasses" :key="cls.compact"
|
|
170
|
+
@click="selectClass(cls.compact)"
|
|
171
|
+
class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
|
|
172
|
+
:class="activeClassId === cls.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-400 hover:bg-ink-50'"
|
|
173
|
+
>
|
|
174
|
+
<span class="w-3 text-ink-200">·</span>
|
|
175
|
+
<span class="flex-1 text-left">{{ cls.label }}</span>
|
|
176
|
+
</button>
|
|
140
177
|
</div>
|
|
141
|
-
</template>
|
|
142
|
-
|
|
143
|
-
<!-- Supporting classes -->
|
|
144
|
-
<div v-if="supportingClasses.length" class="mt-3 pt-2 border-t border-ink-100/40">
|
|
145
|
-
<div class="text-[10px] uppercase tracking-wide text-ink-300 mb-1 px-2">Supporting</div>
|
|
146
|
-
<button v-for="cls in supportingClasses" :key="cls.compact"
|
|
147
|
-
@click="activeClassId = cls.compact; activeTaxonomy = null"
|
|
148
|
-
class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
|
|
149
|
-
:class="activeClassId === cls.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-400 hover:bg-ink-50'"
|
|
150
|
-
>
|
|
151
|
-
<span class="w-3 text-ink-200">·</span>
|
|
152
|
-
<span class="flex-1 text-left">{{ cls.label }}</span>
|
|
153
|
-
</button>
|
|
154
|
-
</div>
|
|
155
178
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
179
|
+
<!-- SKOS Taxonomies -->
|
|
180
|
+
<div class="mt-2 pt-2 border-t border-ink-100/40">
|
|
181
|
+
<div class="text-[10px] uppercase tracking-wide text-ink-300 mb-1 px-2">Taxonomies</div>
|
|
182
|
+
<button v-for="tk in taxonomyKeys" :key="tk"
|
|
183
|
+
@click="selectTaxonomy(tk)"
|
|
184
|
+
class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
|
|
185
|
+
:class="activeTaxonomy === tk ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-400 hover:bg-ink-50'"
|
|
186
|
+
>
|
|
187
|
+
<span class="w-3 text-ink-200">·</span>
|
|
188
|
+
<span class="flex-1 text-left">{{ taxonomyLabels[tk] }}</span>
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
167
191
|
</div>
|
|
168
|
-
</
|
|
169
|
-
</
|
|
192
|
+
</template>
|
|
193
|
+
</nav>
|
|
170
194
|
|
|
171
195
|
<!-- Dataset-level navigation (shown when viewing a dataset) -->
|
|
172
196
|
<div v-if="showDatasetNav" class="mb-6">
|
|
@@ -6,9 +6,11 @@ import {
|
|
|
6
6
|
type OwlClass,
|
|
7
7
|
} from '../adapters/ontology-schema';
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const OVERVIEW_ID = '__overview__';
|
|
10
|
+
|
|
11
|
+
const activeClassId = ref<string | null>(null);
|
|
10
12
|
const activeTaxonomy = ref<string | null>(null);
|
|
11
|
-
const expandedClasses = ref(new Set<string>(['gloss:
|
|
13
|
+
const expandedClasses = ref(new Set<string>(['gloss:Designation']));
|
|
12
14
|
|
|
13
15
|
const taxonomyKeys = [
|
|
14
16
|
'conceptStatus', 'entryStatus', 'normativeStatus', 'sourceType', 'sourceStatus',
|
|
@@ -56,6 +58,8 @@ const supportingClasses = computed(() =>
|
|
|
56
58
|
)
|
|
57
59
|
);
|
|
58
60
|
|
|
61
|
+
const isOverview = computed(() => activeClassId.value === null && activeTaxonomy.value === null);
|
|
62
|
+
|
|
59
63
|
const allNavItems = computed(() => {
|
|
60
64
|
const items: { id: string; label: string; depth: number }[] = [];
|
|
61
65
|
function walk(classes: OwlClass[], depth: number) {
|
|
@@ -80,6 +84,7 @@ export function useOntologyNav() {
|
|
|
80
84
|
treeRoots,
|
|
81
85
|
supportingClasses,
|
|
82
86
|
allNavItems,
|
|
87
|
+
isOverview,
|
|
83
88
|
toggleExpand,
|
|
84
89
|
childClasses,
|
|
85
90
|
hasChildren,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue';
|
|
3
|
-
import { getClass, getAllPropertiesForClass, getStats } from '../adapters/ontology-schema';
|
|
2
|
+
import { computed, watch } from 'vue';
|
|
3
|
+
import { getClass, getAllPropertiesForClass, getAllClasses, getStats } from '../adapters/ontology-schema';
|
|
4
4
|
import { ontology, type TaxonomyConcept } from '../adapters/ontology-registry';
|
|
5
5
|
import { useOntologyNav } from '../composables/use-ontology-nav';
|
|
6
6
|
|
|
@@ -11,10 +11,14 @@ const {
|
|
|
11
11
|
activeTaxonomy,
|
|
12
12
|
taxonomyLabels,
|
|
13
13
|
allNavItems,
|
|
14
|
+
isOverview,
|
|
15
|
+
treeRoots,
|
|
16
|
+
hasChildren,
|
|
17
|
+
childClasses,
|
|
14
18
|
} = useOntologyNav();
|
|
15
19
|
|
|
16
|
-
const activeClass = computed(() => getClass(activeClassId.value));
|
|
17
|
-
const activeProperties = computed(() => getAllPropertiesForClass(activeClassId.value));
|
|
20
|
+
const activeClass = computed(() => activeClassId.value ? getClass(activeClassId.value) : null);
|
|
21
|
+
const activeProperties = computed(() => activeClassId.value ? getAllPropertiesForClass(activeClassId.value) : { object: [], datatype: [] });
|
|
18
22
|
|
|
19
23
|
function activeTaxonomyData() {
|
|
20
24
|
if (!activeTaxonomy.value) return null;
|
|
@@ -23,6 +27,14 @@ function activeTaxonomyData() {
|
|
|
23
27
|
const top = all.filter(c => !c.broader);
|
|
24
28
|
return { scheme: ontology.getScheme(key), concepts: all, top };
|
|
25
29
|
}
|
|
30
|
+
|
|
31
|
+
// Scroll to top when selection changes
|
|
32
|
+
watch([activeClassId, activeTaxonomy], () => {
|
|
33
|
+
const main = document.querySelector('main');
|
|
34
|
+
if (main) main.scrollTo({ top: 0, behavior: 'smooth' });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const allClasses = getAllClasses();
|
|
26
38
|
</script>
|
|
27
39
|
|
|
28
40
|
<template>
|
|
@@ -49,6 +61,10 @@ function activeTaxonomyData() {
|
|
|
49
61
|
<!-- Sticky mobile chips (for small screens where sidebar is hidden) -->
|
|
50
62
|
<div class="lg:hidden sticky top-14 z-10 bg-surface -mx-4 px-4 py-2 border-b border-ink-100/60 mb-4">
|
|
51
63
|
<div class="flex gap-2 overflow-x-auto scrollbar-none">
|
|
64
|
+
<button @click="activeClassId = null; activeTaxonomy = null"
|
|
65
|
+
class="flex-shrink-0 px-3 py-2.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors min-h-[44px] flex items-center gap-1.5"
|
|
66
|
+
:class="isOverview ? 'bg-ink-800 text-white' : 'bg-surface-raised border border-ink-100 text-ink-600 hover:bg-ink-50'"
|
|
67
|
+
>Overview</button>
|
|
52
68
|
<button v-for="item in allNavItems" :key="item.id"
|
|
53
69
|
@click="activeClassId = item.id; activeTaxonomy = null"
|
|
54
70
|
class="flex-shrink-0 px-3 py-2.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors min-h-[44px] flex items-center gap-1.5"
|
|
@@ -70,6 +86,25 @@ function activeTaxonomyData() {
|
|
|
70
86
|
|
|
71
87
|
<!-- Detail panel -->
|
|
72
88
|
<div>
|
|
89
|
+
<!-- Overview: all classes summary -->
|
|
90
|
+
<template v-if="isOverview">
|
|
91
|
+
<h2 class="text-lg font-semibold text-ink-800 mb-4">Class Overview</h2>
|
|
92
|
+
<div class="grid gap-3 sm:grid-cols-2">
|
|
93
|
+
<div v-for="cls in allClasses" :key="cls.compact"
|
|
94
|
+
@click="activeClassId = cls.compact"
|
|
95
|
+
class="border border-ink-100/60 rounded-lg p-3 cursor-pointer hover:border-ink-200 hover:bg-ink-50/50 transition-colors">
|
|
96
|
+
<div class="flex items-center gap-2">
|
|
97
|
+
<span class="text-sm font-medium text-ink-700">{{ cls.label }}</span>
|
|
98
|
+
<code class="text-[10px] text-ink-400 bg-ink-50 px-1.5 py-0.5 rounded">{{ cls.compact }}</code>
|
|
99
|
+
</div>
|
|
100
|
+
<p v-if="cls.comment" class="text-xs text-ink-400 mt-1 line-clamp-2">{{ cls.comment }}</p>
|
|
101
|
+
<div v-if="cls.subClassOf" class="text-[10px] text-ink-300 mt-1">
|
|
102
|
+
subClassOf <code class="text-ink-400">{{ cls.subClassOf }}</code>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
73
108
|
<!-- Class detail -->
|
|
74
109
|
<template v-if="!activeTaxonomy && activeClass">
|
|
75
110
|
<div class="pb-4 border-b border-ink-100/60 mb-4">
|