@glossarist/concept-browser 0.4.12 → 0.4.14
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 +2 -2
- package/scripts/generate-ontology-schema.mjs +312 -10
- package/src/adapters/ontology-schema.ts +89 -4
- package/src/components/AppSidebar.vue +281 -43
- package/src/components/ConceptRdfView.vue +3 -3
- package/src/composables/use-ontology-nav.ts +183 -13
- package/src/data/ontology-schema.json +1721 -153
- package/src/router/index.ts +10 -0
- package/src/views/OntologySchemaView.vue +331 -14
package/src/router/index.ts
CHANGED
|
@@ -55,6 +55,16 @@ export const routes: RouteRecordRaw[] = [
|
|
|
55
55
|
name: 'ontology-taxonomy',
|
|
56
56
|
component: () => import('../views/OntologySchemaView.vue'),
|
|
57
57
|
},
|
|
58
|
+
{
|
|
59
|
+
path: '/ontology/shape/:shapeId',
|
|
60
|
+
name: 'ontology-shape',
|
|
61
|
+
component: () => import('../views/OntologySchemaView.vue'),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
path: '/ontology/property/:propertyId',
|
|
65
|
+
name: 'ontology-property',
|
|
66
|
+
component: () => import('../views/OntologySchemaView.vue'),
|
|
67
|
+
},
|
|
58
68
|
{
|
|
59
69
|
path: '/news',
|
|
60
70
|
name: 'news',
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, watch, nextTick } from 'vue';
|
|
3
3
|
import { useRoute } from 'vue-router';
|
|
4
|
-
import { getClass, getAllPropertiesForClass, getAllClasses,
|
|
4
|
+
import { getClass, getAllPropertiesForClass, getAllClasses, getAllShapes, getAllProperties, getShape, getProperty, getShapesForClass } from '../adapters/ontology-schema';
|
|
5
5
|
import { ontology, type TaxonomyConcept } from '../adapters/ontology-registry';
|
|
6
|
+
import { useVocabularyStore } from '../stores/vocabulary';
|
|
6
7
|
import { useOntologyNav, slugToCompact, compactToSlug } from '../composables/use-ontology-nav';
|
|
8
|
+
import taxonomyData from '../data/taxonomies.json';
|
|
7
9
|
|
|
8
10
|
const route = useRoute();
|
|
9
|
-
const
|
|
11
|
+
const vocabStore = useVocabularyStore();
|
|
10
12
|
|
|
11
13
|
const {
|
|
14
|
+
stats,
|
|
15
|
+
ontology: ontologyMeta,
|
|
12
16
|
taxonomyLabels,
|
|
17
|
+
groupedIndividuals,
|
|
18
|
+
totalIndividuals,
|
|
13
19
|
allNavItems,
|
|
14
20
|
treeRoots,
|
|
15
21
|
hasChildren,
|
|
16
22
|
childClasses,
|
|
23
|
+
annotationProperties,
|
|
24
|
+
taxonomyKeyForValuesFrom,
|
|
25
|
+
getShapesForTaxonomy,
|
|
17
26
|
} = useOntologyNav();
|
|
18
27
|
|
|
19
28
|
const activeClassId = computed(() => {
|
|
@@ -27,10 +36,26 @@ const activeTaxonomy = computed(() => {
|
|
|
27
36
|
return route.params.taxonomyKey as string;
|
|
28
37
|
});
|
|
29
38
|
|
|
39
|
+
const activeShapeId = computed(() => {
|
|
40
|
+
if (route.name !== 'ontology-shape') return null;
|
|
41
|
+
const slug = route.params.shapeId as string;
|
|
42
|
+
return slugToCompact(slug);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const activePropertyId = computed(() => {
|
|
46
|
+
if (route.name !== 'ontology-property') return null;
|
|
47
|
+
const slug = route.params.propertyId as string;
|
|
48
|
+
return slugToCompact(slug);
|
|
49
|
+
});
|
|
50
|
+
|
|
30
51
|
const isOverview = computed(() => route.name === 'ontology');
|
|
31
52
|
|
|
32
53
|
const activeClass = computed(() => activeClassId.value ? getClass(activeClassId.value) : null);
|
|
33
54
|
const activeProperties = computed(() => activeClassId.value ? getAllPropertiesForClass(activeClassId.value) : { object: [], datatype: [] });
|
|
55
|
+
const activeClassShapes = computed(() => activeClassId.value ? getShapesForClass(activeClassId.value) : []);
|
|
56
|
+
|
|
57
|
+
const activeShape = computed(() => activeShapeId.value ? getShape(activeShapeId.value) : null);
|
|
58
|
+
const activeProperty = computed(() => activePropertyId.value ? getProperty(activePropertyId.value) : null);
|
|
34
59
|
|
|
35
60
|
function activeTaxonomyData() {
|
|
36
61
|
if (!activeTaxonomy.value) return null;
|
|
@@ -48,11 +73,43 @@ watch(() => route.fullPath, () => {
|
|
|
48
73
|
});
|
|
49
74
|
|
|
50
75
|
const allClasses = getAllClasses();
|
|
76
|
+
const allShapes = getAllShapes();
|
|
77
|
+
const allPropertiesList = getAllProperties();
|
|
78
|
+
|
|
79
|
+
const taxonomyMeta = Object.entries(taxonomyData).map(([key, tax]: [string, any]) => ({
|
|
80
|
+
key,
|
|
81
|
+
label: tax.schemeLabel,
|
|
82
|
+
conceptCount: Object.keys(tax.concepts).length,
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
const totalTaxonomyIndividuals = taxonomyMeta.reduce((sum: number, t: any) => sum + t.conceptCount, 0);
|
|
86
|
+
|
|
87
|
+
const loadedDatasets = computed(() => {
|
|
88
|
+
const datasets: { id: string; title: string; conceptCount: number; languages: string[] }[] = [];
|
|
89
|
+
for (const [id, adapter] of vocabStore.datasets) {
|
|
90
|
+
const m = vocabStore.manifests.get(id);
|
|
91
|
+
if (m) datasets.push({ id, title: m.title, conceptCount: m.conceptCount, languages: m.languages });
|
|
92
|
+
}
|
|
93
|
+
return datasets;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const instanceCounts = computed(() => {
|
|
97
|
+
const counts: Record<string, { datasetId: string; title: string; count: number }[]> = {};
|
|
98
|
+
for (const ds of loadedDatasets.value) {
|
|
99
|
+
const entry = { datasetId: ds.id, title: ds.title, count: ds.conceptCount };
|
|
100
|
+
if (!counts['gloss:Concept']) counts['gloss:Concept'] = [];
|
|
101
|
+
counts['gloss:Concept'].push(entry);
|
|
102
|
+
|
|
103
|
+
if (!counts['gloss:LocalizedConcept']) counts['gloss:LocalizedConcept'] = [];
|
|
104
|
+
counts['gloss:LocalizedConcept'].push({ ...entry, count: ds.conceptCount * ds.languages.length });
|
|
105
|
+
}
|
|
106
|
+
return counts;
|
|
107
|
+
});
|
|
51
108
|
</script>
|
|
52
109
|
|
|
53
110
|
<template>
|
|
54
111
|
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
55
|
-
<!-- Overview: header +
|
|
112
|
+
<!-- Overview: header + grids -->
|
|
56
113
|
<template v-if="isOverview">
|
|
57
114
|
<div class="mb-8">
|
|
58
115
|
<h1 class="text-2xl sm:text-3xl font-semibold text-ink-800">Glossarist Ontology</h1>
|
|
@@ -63,15 +120,34 @@ const allClasses = getAllClasses();
|
|
|
63
120
|
<p>The Glossarist ontology defines the RDF/OWL vocabulary for describing structured terminology data. It models <strong>concepts</strong> with multilingual <strong>localizations</strong> (definitions, notes, examples) and typed <strong>designations</strong> (expressions, abbreviations, symbols) using the SKOS-XL pattern for reified lexical labels.</p>
|
|
64
121
|
<p>It aligns with <strong>SKOS</strong> (concepts and relationships), <strong>SKOS-XL</strong> (designations as labels), <strong>ISO 25964</strong> (hierarchical relationship subtypes — generic, partitive, instantial), <strong>PROV-O</strong> (source provenance), and <strong>Dublin Core Terms</strong> (language, citation). Enumeration values use SKOS ConceptSchemes (10 taxonomies).</p>
|
|
65
122
|
</div>
|
|
123
|
+
|
|
124
|
+
<!-- Ontology metadata (TODO 07) -->
|
|
125
|
+
<div v-if="ontologyMeta" class="mt-4 space-y-2">
|
|
126
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
127
|
+
<code class="text-xs text-ink-400 bg-ink-50 px-2 py-1 rounded">{{ ontologyMeta.iri }}</code>
|
|
128
|
+
<span v-if="ontologyMeta.created" class="text-[10px] text-ink-300">Created {{ ontologyMeta.created }}</span>
|
|
129
|
+
<a v-if="ontologyMeta.license" :href="ontologyMeta.license" target="_blank" rel="noopener" class="text-[10px] text-blue-500 hover:text-blue-700">
|
|
130
|
+
CC BY 4.0
|
|
131
|
+
</a>
|
|
132
|
+
</div>
|
|
133
|
+
<div v-if="ontologyMeta.imports.length" class="flex flex-wrap items-center gap-1.5">
|
|
134
|
+
<span class="text-[10px] text-ink-300">Imports:</span>
|
|
135
|
+
<span v-for="imp in ontologyMeta.imports" :key="imp.iri"
|
|
136
|
+
class="text-[10px] bg-ink-50 text-ink-500 px-1.5 py-0.5 rounded">{{ imp.label }}</span>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
66
140
|
<div class="flex flex-wrap gap-2 mt-4">
|
|
67
141
|
<span class="badge badge-blue text-[10px]">{{ stats.classCount }} classes</span>
|
|
68
142
|
<span class="badge text-[10px] bg-emerald-50 text-emerald-700">{{ stats.objectPropertyCount }} object properties</span>
|
|
69
143
|
<span class="badge text-[10px] bg-amber-50 text-amber-700">{{ stats.datatypePropertyCount }} datatype properties</span>
|
|
70
|
-
<span class="badge text-[10px] bg-purple-50 text-purple-700">
|
|
144
|
+
<span class="badge text-[10px] bg-purple-50 text-purple-700">{{ stats.shapeCount }} SHACL shapes</span>
|
|
145
|
+
<span class="badge text-[10px] bg-rose-50 text-rose-700">{{ totalTaxonomyIndividuals }} named individuals</span>
|
|
146
|
+
<span class="badge text-[10px] bg-pink-50 text-pink-700">{{ annotationProperties.length }} annotation properties</span>
|
|
71
147
|
</div>
|
|
72
|
-
<code class="block text-xs text-ink-400 mt-2">https://www.glossarist.org/ontologies/glossarist</code>
|
|
73
148
|
</div>
|
|
74
149
|
|
|
150
|
+
<!-- Class Overview -->
|
|
75
151
|
<h2 class="text-lg font-semibold text-ink-800 mb-4">Class Overview</h2>
|
|
76
152
|
<div class="grid gap-3 sm:grid-cols-2">
|
|
77
153
|
<router-link v-for="cls in allClasses" :key="cls.compact"
|
|
@@ -88,18 +164,98 @@ const allClasses = getAllClasses();
|
|
|
88
164
|
</router-link>
|
|
89
165
|
</div>
|
|
90
166
|
|
|
91
|
-
|
|
167
|
+
<!-- SHACL Shapes -->
|
|
168
|
+
<h2 class="text-lg font-semibold text-ink-800 mt-8 mb-4">SHACL Shapes</h2>
|
|
92
169
|
<div class="grid gap-3 sm:grid-cols-2">
|
|
93
|
-
<router-link v-for="
|
|
94
|
-
:to="`/ontology/
|
|
170
|
+
<router-link v-for="shape in allShapes" :key="shape.compact"
|
|
171
|
+
:to="`/ontology/shape/${compactToSlug(shape.compact)}`"
|
|
95
172
|
class="border border-ink-100/60 rounded-lg p-3 cursor-pointer hover:border-ink-200 hover:bg-ink-50/50 transition-colors block">
|
|
96
|
-
<
|
|
173
|
+
<div class="flex items-center gap-2">
|
|
174
|
+
<span class="text-sm font-medium text-ink-700">{{ shape.label }}</span>
|
|
175
|
+
<code class="text-[10px] text-ink-400 bg-ink-50 px-1.5 py-0.5 rounded">{{ shape.compact }}</code>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="text-[10px] text-ink-300 mt-1">
|
|
178
|
+
targetClass <code class="text-ink-400">{{ shape.targetClass }}</code>
|
|
179
|
+
· {{ shape.constraints.length }} constraints
|
|
180
|
+
</div>
|
|
97
181
|
</router-link>
|
|
98
182
|
</div>
|
|
183
|
+
|
|
184
|
+
<!-- Named Individuals (TODO 06) -->
|
|
185
|
+
<h2 class="text-lg font-semibold text-ink-800 mt-8 mb-4">Named Individuals</h2>
|
|
186
|
+
<p class="text-sm text-ink-400 mb-4">
|
|
187
|
+
{{ totalTaxonomyIndividuals }} SKOS Concepts across {{ groupedIndividuals.length }} ConceptSchemes serve as controlled vocabulary instances.
|
|
188
|
+
</p>
|
|
189
|
+
<div class="space-y-4">
|
|
190
|
+
<div v-for="group in groupedIndividuals" :key="group.key" class="border border-ink-100/60 rounded-lg p-3">
|
|
191
|
+
<div class="flex items-center gap-2 mb-2">
|
|
192
|
+
<router-link :to="`/ontology/taxonomy/${group.key}`" class="text-sm font-medium text-ink-700 hover:text-blue-600 transition-colors">
|
|
193
|
+
{{ group.label }}
|
|
194
|
+
</router-link>
|
|
195
|
+
<span class="text-[10px] text-rose-600 bg-rose-50 px-1.5 py-0.5 rounded">{{ group.concepts.length }}</span>
|
|
196
|
+
</div>
|
|
197
|
+
<div class="flex flex-wrap gap-1.5">
|
|
198
|
+
<router-link v-for="c in group.concepts" :key="c.id"
|
|
199
|
+
:to="`/ontology/taxonomy/${group.key}`"
|
|
200
|
+
class="text-[10px] bg-ink-50 text-ink-600 px-2 py-1 rounded hover:bg-ink-100 transition-colors">
|
|
201
|
+
{{ c.prefLabel }}
|
|
202
|
+
</router-link>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<!-- Annotation Properties (TODO 09) -->
|
|
208
|
+
<h2 class="text-lg font-semibold text-ink-800 mt-8 mb-4">Annotation Properties</h2>
|
|
209
|
+
<p class="text-sm text-ink-400 mb-4">
|
|
210
|
+
Standard annotation properties from RDFS, Dublin Core Terms, and VANN used for ontology metadata. These do not participate in reasoning.
|
|
211
|
+
</p>
|
|
212
|
+
<div class="grid gap-2 sm:grid-cols-2">
|
|
213
|
+
<div v-for="ap in annotationProperties" :key="ap.compact"
|
|
214
|
+
class="border border-ink-100/60 rounded-lg px-3 py-2 flex items-center gap-2">
|
|
215
|
+
<code class="text-xs text-pink-600 bg-pink-50 px-1.5 py-0.5 rounded">{{ ap.compact }}</code>
|
|
216
|
+
<span class="text-xs text-ink-500">{{ ap.label }}</span>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<!-- Concept Data Model (TODO 08) -->
|
|
221
|
+
<h2 class="text-lg font-semibold text-ink-800 mt-8 mb-4">Concept Data Model</h2>
|
|
222
|
+
<p class="text-sm text-ink-400 mb-4">
|
|
223
|
+
The three-layer architecture maps ontology schema (TBox) to controlled vocabularies (RBox) to instance data (ABox).
|
|
224
|
+
</p>
|
|
225
|
+
<div class="bg-ink-50/50 rounded-lg p-4 text-sm font-mono text-ink-600 leading-relaxed space-y-1 border border-ink-100/60">
|
|
226
|
+
<div class="text-ink-400 text-xs mb-2">TBox — Ontology Classes (schema layer)</div>
|
|
227
|
+
<div class="ml-2">
|
|
228
|
+
<span class="text-blue-600">gloss:ConceptCollection</span> (skos:Collection)<br/>
|
|
229
|
+
<span class="ml-4"><span class="text-blue-600">gloss:Concept</span> (skos:Concept)</span><br/>
|
|
230
|
+
<span class="ml-8"><span class="text-emerald-600">gloss:hasLocalization</span> → <span class="text-blue-600">gloss:LocalizedConcept</span></span><br/>
|
|
231
|
+
<span class="ml-12"><span class="text-emerald-600">gloss:hasDesignation</span> → <span class="text-blue-600">gloss:Designation</span> (skosxl:Label)</span><br/>
|
|
232
|
+
<span class="ml-12"><span class="text-emerald-600">gloss:hasDefinition</span> → <span class="text-blue-600">gloss:DetailedDefinition</span></span><br/>
|
|
233
|
+
<span class="ml-12"><span class="text-emerald-600">gloss:hasSource</span> → <span class="text-blue-600">gloss:ConceptSource</span></span><br/>
|
|
234
|
+
<span class="ml-8"><span class="text-emerald-600">gloss:hasRelatedConcept</span> → <span class="text-blue-600">gloss:RelatedConcept</span></span><br/>
|
|
235
|
+
<span class="ml-8"><span class="text-emerald-600">gloss:hasDate</span> → <span class="text-blue-600">gloss:ConceptDate</span></span><br/>
|
|
236
|
+
<span class="ml-8"><span class="text-emerald-600">gloss:hasStatus</span> → <span class="text-rose-600">gloss:status/*</span></span>
|
|
237
|
+
</div>
|
|
238
|
+
<div class="text-ink-400 text-xs mt-3 mb-2">RBox — Controlled Vocabularies ({{ totalTaxonomyIndividuals }} named individuals)</div>
|
|
239
|
+
<div class="ml-2 text-ink-500">
|
|
240
|
+
<span class="text-rose-600">gloss:status/*</span> — concept lifecycle<br/>
|
|
241
|
+
<span class="text-rose-600">gloss:normativeStatus/*</span> — designation normativity<br/>
|
|
242
|
+
<span class="text-rose-600">gloss:relationshipType/*</span> — concept relations<br/>
|
|
243
|
+
<span class="text-rose-600">gloss:designationType/*</span> — designation kinds<br/>
|
|
244
|
+
<span class="text-rose-600">...</span> and {{ groupedIndividuals.length - 4 }} more SKOS ConceptSchemes
|
|
245
|
+
</div>
|
|
246
|
+
<div class="text-ink-400 text-xs mt-3 mb-2">ABox — Instance Data</div>
|
|
247
|
+
<div v-if="loadedDatasets.length" class="ml-2 space-y-1">
|
|
248
|
+
<div v-for="ds in loadedDatasets" :key="ds.id" class="flex items-center gap-2 text-ink-500">
|
|
249
|
+
<router-link :to="`/dataset/${ds.id}`" class="text-blue-600 hover:text-blue-700">{{ ds.title }}</router-link>
|
|
250
|
+
<span class="text-[10px] text-ink-300">{{ ds.conceptCount.toLocaleString() }} concepts · {{ ds.languages.length }} languages</span>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
<div v-else class="ml-2 text-ink-400 italic text-xs">No datasets loaded.</div>
|
|
254
|
+
</div>
|
|
99
255
|
</template>
|
|
100
256
|
|
|
101
257
|
<!-- Class detail -->
|
|
102
|
-
<template v-if="!activeTaxonomy && activeClass">
|
|
258
|
+
<template v-if="!activeTaxonomy && !activeShapeId && !activePropertyId && activeClass">
|
|
103
259
|
<nav class="flex items-center gap-1.5 text-sm text-ink-400 mb-4">
|
|
104
260
|
<router-link to="/ontology" class="hover:text-ink-700 transition-colors">Overview</router-link>
|
|
105
261
|
<template v-if="activeClass.ancestors.length">
|
|
@@ -124,6 +280,42 @@ const allClasses = getAllClasses();
|
|
|
124
280
|
</template>
|
|
125
281
|
</div>
|
|
126
282
|
<p v-if="activeClass.comment" class="text-sm text-ink-500 mt-3 leading-relaxed">{{ activeClass.comment }}</p>
|
|
283
|
+
|
|
284
|
+
<!-- Instance counts for this class -->
|
|
285
|
+
<div v-if="instanceCounts[activeClassId!]" class="mt-3 pt-3 border-t border-ink-100/40">
|
|
286
|
+
<h3 class="text-[10px] uppercase tracking-wide text-ink-300 font-medium mb-1.5">Instance Data</h3>
|
|
287
|
+
<div class="space-y-0.5">
|
|
288
|
+
<div v-for="entry in instanceCounts[activeClassId!]" :key="entry.datasetId" class="text-xs text-ink-500">
|
|
289
|
+
<router-link :to="`/dataset/${entry.datasetId}`" class="text-blue-600 hover:text-blue-700">{{ entry.title }}</router-link>
|
|
290
|
+
— {{ entry.count.toLocaleString() }} instances
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<!-- SHACL Shape constraints for this class -->
|
|
297
|
+
<div v-if="activeClassShapes.length" class="mb-6">
|
|
298
|
+
<h3 class="text-xs uppercase tracking-wide text-ink-300 font-medium mb-2">
|
|
299
|
+
SHACL Shape{{ activeClassShapes.length > 1 ? 's' : '' }}
|
|
300
|
+
</h3>
|
|
301
|
+
<div v-for="shape in activeClassShapes" :key="shape.compact" class="border border-purple-100 bg-purple-50/30 rounded-lg p-3 mb-3">
|
|
302
|
+
<div class="flex items-center gap-2 mb-2">
|
|
303
|
+
<span class="text-xs font-medium text-purple-700">{{ shape.label }}</span>
|
|
304
|
+
<router-link :to="`/ontology/shape/${compactToSlug(shape.compact)}`" class="text-[10px] text-purple-500 hover:text-purple-700">View full shape →</router-link>
|
|
305
|
+
</div>
|
|
306
|
+
<div class="grid gap-1 text-xs">
|
|
307
|
+
<div v-for="(c, ci) in shape.constraints" :key="c.path ?? ci" class="flex items-start gap-2">
|
|
308
|
+
<code class="text-purple-600 bg-purple-50 px-1 py-0.5 rounded text-[10px] whitespace-nowrap">{{ c.path }}</code>
|
|
309
|
+
<span class="text-ink-400">
|
|
310
|
+
<span v-if="c.datatype">{{ c.datatype }}</span>
|
|
311
|
+
<span v-if="c.class" class="text-blue-600">{{ c.class }}</span>
|
|
312
|
+
<span v-if="c.minCount !== null || c.maxCount !== null" class="text-ink-300 ml-1">
|
|
313
|
+
[{{ c.minCount ?? 0 }}..{{ c.maxCount ?? '*' }}]
|
|
314
|
+
</span>
|
|
315
|
+
</span>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
127
319
|
</div>
|
|
128
320
|
|
|
129
321
|
<div v-if="activeProperties.object.length" class="mb-6">
|
|
@@ -141,7 +333,7 @@ const allClasses = getAllClasses();
|
|
|
141
333
|
<tbody>
|
|
142
334
|
<tr v-for="p in activeProperties.object" :key="p.compact" class="border-b border-ink-100/30">
|
|
143
335
|
<td class="py-2 px-3 align-top">
|
|
144
|
-
<
|
|
336
|
+
<router-link :to="`/ontology/property/${compactToSlug(p.compact)}`" class="text-xs text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded hover:bg-blue-100 transition-colors">{{ p.compact }}</router-link>
|
|
145
337
|
<div v-if="p.inverseOf" class="text-[10px] text-ink-300 mt-0.5">↔ {{ p.inverseOf }}</div>
|
|
146
338
|
</td>
|
|
147
339
|
<td class="py-2 px-3 align-top">
|
|
@@ -168,7 +360,7 @@ const allClasses = getAllClasses();
|
|
|
168
360
|
<tbody>
|
|
169
361
|
<tr v-for="p in activeProperties.datatype" :key="p.compact" class="border-b border-ink-100/30">
|
|
170
362
|
<td class="py-2 px-3 align-top">
|
|
171
|
-
<
|
|
363
|
+
<router-link :to="`/ontology/property/${compactToSlug(p.compact)}`" class="text-xs text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded hover:bg-blue-100 transition-colors">{{ p.compact }}</router-link>
|
|
172
364
|
</td>
|
|
173
365
|
<td class="py-2 px-3 align-top">
|
|
174
366
|
<code class="text-xs text-ink-500">{{ p.range || '—' }}</code>
|
|
@@ -179,8 +371,119 @@ const allClasses = getAllClasses();
|
|
|
179
371
|
</table>
|
|
180
372
|
</div>
|
|
181
373
|
|
|
182
|
-
<div v-if="!activeProperties.object.length && !activeProperties.datatype.length" class="text-sm text-ink-300 italic">
|
|
183
|
-
No properties defined directly on this class.
|
|
374
|
+
<div v-if="!activeProperties.object.length && !activeProperties.datatype.length && !activeClassShapes.length" class="text-sm text-ink-300 italic">
|
|
375
|
+
No properties or shapes defined directly on this class.
|
|
376
|
+
</div>
|
|
377
|
+
</template>
|
|
378
|
+
|
|
379
|
+
<!-- Shape detail -->
|
|
380
|
+
<template v-if="activeShape">
|
|
381
|
+
<nav class="flex items-center gap-1.5 text-sm text-ink-400 mb-4">
|
|
382
|
+
<router-link to="/ontology" class="hover:text-ink-700 transition-colors">Overview</router-link>
|
|
383
|
+
<span class="text-ink-200">/</span>
|
|
384
|
+
<span class="text-ink-700">{{ activeShape.label }} Shape</span>
|
|
385
|
+
</nav>
|
|
386
|
+
|
|
387
|
+
<div class="pb-4 border-b border-ink-100/60 mb-4">
|
|
388
|
+
<h1 class="text-xl font-semibold text-ink-800">{{ activeShape.label }} Shape</h1>
|
|
389
|
+
<code class="block text-xs text-ink-400 mt-1">{{ activeShape.iri }}</code>
|
|
390
|
+
<div class="flex items-center gap-3 mt-2">
|
|
391
|
+
<div v-if="activeShape.targetClass" class="flex items-center gap-2 text-sm">
|
|
392
|
+
<span class="text-ink-400 text-xs">targetClass</span>
|
|
393
|
+
<router-link :to="`/ontology/class/${compactToSlug(activeShape.targetClass)}`" class="text-xs text-blue-600 bg-blue-50 px-2 py-0.5 rounded hover:bg-blue-100 transition-colors">{{ activeShape.targetClass }}</router-link>
|
|
394
|
+
</div>
|
|
395
|
+
<div v-if="activeShape.shapeClass" class="flex items-center gap-2 text-sm">
|
|
396
|
+
<span class="text-ink-400 text-xs">class</span>
|
|
397
|
+
<code class="text-xs text-ink-500 bg-ink-50 px-1.5 py-0.5 rounded">{{ activeShape.shapeClass }}</code>
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
<p v-if="activeShape.comment" class="text-sm text-ink-500 mt-3 leading-relaxed">{{ activeShape.comment }}</p>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<h3 class="text-xs uppercase tracking-wide text-ink-300 font-medium mb-2">
|
|
404
|
+
Constraints ({{ activeShape.constraints.length }})
|
|
405
|
+
</h3>
|
|
406
|
+
<table class="w-full text-sm border-collapse">
|
|
407
|
+
<thead>
|
|
408
|
+
<tr class="border-b border-ink-100/60">
|
|
409
|
+
<th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Path</th>
|
|
410
|
+
<th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Type / Class</th>
|
|
411
|
+
<th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Cardinality</th>
|
|
412
|
+
<th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Values</th>
|
|
413
|
+
</tr>
|
|
414
|
+
</thead>
|
|
415
|
+
<tbody>
|
|
416
|
+
<tr v-for="(c, ci) in activeShape.constraints" :key="c.path ?? ci" class="border-b border-ink-100/30">
|
|
417
|
+
<td class="py-2 px-3 align-top">
|
|
418
|
+
<code class="text-xs text-purple-600 bg-purple-50 px-1.5 py-0.5 rounded">{{ c.path }}</code>
|
|
419
|
+
</td>
|
|
420
|
+
<td class="py-2 px-3 align-top">
|
|
421
|
+
<span v-if="c.datatype"><code class="text-xs text-ink-500">{{ c.datatype }}</code></span>
|
|
422
|
+
<span v-if="c.class"><code class="text-xs text-blue-600">{{ c.class }}</code></span>
|
|
423
|
+
<span v-if="!c.datatype && !c.class" class="text-xs text-ink-300">—</span>
|
|
424
|
+
</td>
|
|
425
|
+
<td class="py-2 px-3 align-top text-xs text-ink-500">
|
|
426
|
+
<span v-if="c.minCount !== null || c.maxCount !== null">
|
|
427
|
+
{{ c.minCount ?? 0 }}..{{ c.maxCount ?? '*' }}
|
|
428
|
+
</span>
|
|
429
|
+
<span v-else class="text-ink-300">*</span>
|
|
430
|
+
</td>
|
|
431
|
+
<td class="py-2 px-3 align-top">
|
|
432
|
+
<span v-if="c.valuesFrom">
|
|
433
|
+
<router-link v-if="taxonomyKeyForValuesFrom(c.valuesFrom)"
|
|
434
|
+
:to="`/ontology/taxonomy/${taxonomyKeyForValuesFrom(c.valuesFrom)}`"
|
|
435
|
+
class="text-[10px] text-rose-600 bg-rose-50 px-1.5 py-0.5 rounded hover:bg-rose-100 transition-colors">{{ c.valuesFrom }}</router-link>
|
|
436
|
+
<span v-else class="text-[10px] text-ink-500 bg-ink-50 px-1.5 py-0.5 rounded">{{ c.valuesFrom }}</span>
|
|
437
|
+
</span>
|
|
438
|
+
<span v-if="c.in" class="text-[10px] text-ink-500">{{ c.in.join(' | ') }}</span>
|
|
439
|
+
<span v-if="!c.valuesFrom && !c.in" class="text-xs text-ink-300">—</span>
|
|
440
|
+
</td>
|
|
441
|
+
</tr>
|
|
442
|
+
</tbody>
|
|
443
|
+
</table>
|
|
444
|
+
</template>
|
|
445
|
+
|
|
446
|
+
<!-- Property detail -->
|
|
447
|
+
<template v-if="activeProperty">
|
|
448
|
+
<nav class="flex items-center gap-1.5 text-sm text-ink-400 mb-4">
|
|
449
|
+
<router-link to="/ontology" class="hover:text-ink-700 transition-colors">Overview</router-link>
|
|
450
|
+
<span class="text-ink-200">/</span>
|
|
451
|
+
<span class="text-ink-700">{{ activeProperty.label }}</span>
|
|
452
|
+
</nav>
|
|
453
|
+
|
|
454
|
+
<div class="pb-4 border-b border-ink-100/60 mb-4">
|
|
455
|
+
<div class="flex items-center gap-3">
|
|
456
|
+
<h1 class="text-xl font-semibold text-ink-800">{{ activeProperty.label }}</h1>
|
|
457
|
+
<span class="badge text-[10px]" :class="activeProperty.type === 'object' ? 'bg-emerald-50 text-emerald-700' : 'bg-amber-50 text-amber-700'">
|
|
458
|
+
{{ activeProperty.type === 'object' ? 'Object Property' : 'Datatype Property' }}
|
|
459
|
+
</span>
|
|
460
|
+
</div>
|
|
461
|
+
<code class="block text-xs text-ink-400 mt-1">{{ activeProperty.iri }}</code>
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
<div class="space-y-3">
|
|
465
|
+
<div v-if="activeProperty.comment" class="text-sm text-ink-500 leading-relaxed">{{ activeProperty.comment }}</div>
|
|
466
|
+
|
|
467
|
+
<div v-if="activeProperty.domain || activeProperty.domainUnion" class="flex items-center gap-2 text-sm">
|
|
468
|
+
<span class="text-ink-400 text-xs">Domain:</span>
|
|
469
|
+
<template v-if="activeProperty.domainUnion">
|
|
470
|
+
<router-link v-for="d in activeProperty.domainUnion" :key="d" :to="`/ontology/class/${compactToSlug(d)}`" class="text-xs text-blue-600 bg-blue-50 px-2 py-0.5 rounded hover:bg-blue-100 transition-colors">{{ d }}</router-link>
|
|
471
|
+
</template>
|
|
472
|
+
<router-link v-else-if="activeProperty.domain" :to="`/ontology/class/${compactToSlug(activeProperty.domain)}`" class="text-xs text-blue-600 bg-blue-50 px-2 py-0.5 rounded hover:bg-blue-100 transition-colors">{{ activeProperty.domain }}</router-link>
|
|
473
|
+
</div>
|
|
474
|
+
|
|
475
|
+
<div v-if="activeProperty.range || activeProperty.rangeUnion" class="flex items-center gap-2 text-sm">
|
|
476
|
+
<span class="text-ink-400 text-xs">Range:</span>
|
|
477
|
+
<template v-if="activeProperty.rangeUnion">
|
|
478
|
+
<code v-for="r in activeProperty.rangeUnion" :key="r" class="text-xs text-ink-500 bg-ink-50 px-1.5 py-0.5 rounded">{{ r }}</code>
|
|
479
|
+
</template>
|
|
480
|
+
<code v-else-if="activeProperty.range" class="text-xs text-ink-500 bg-ink-50 px-1.5 py-0.5 rounded">{{ activeProperty.range }}</code>
|
|
481
|
+
</div>
|
|
482
|
+
|
|
483
|
+
<div v-if="activeProperty.inverseOf" class="flex items-center gap-2 text-sm">
|
|
484
|
+
<span class="text-ink-400 text-xs">Inverse of:</span>
|
|
485
|
+
<router-link :to="`/ontology/property/${compactToSlug(activeProperty.inverseOf)}`" class="text-xs text-blue-600 bg-blue-50 px-2 py-0.5 rounded hover:bg-blue-100 transition-colors">{{ activeProperty.inverseOf }}</router-link>
|
|
486
|
+
</div>
|
|
184
487
|
</div>
|
|
185
488
|
</template>
|
|
186
489
|
|
|
@@ -197,6 +500,20 @@ const allClasses = getAllClasses();
|
|
|
197
500
|
<code class="block text-xs text-ink-400 mt-1">{{ activeTaxonomyData()!.scheme }}</code>
|
|
198
501
|
</div>
|
|
199
502
|
|
|
503
|
+
<!-- Referencing shapes -->
|
|
504
|
+
<div v-if="getShapesForTaxonomy(activeTaxonomy).length" class="mb-4">
|
|
505
|
+
<h3 class="text-xs uppercase tracking-wide text-ink-300 font-medium mb-2">
|
|
506
|
+
Referencing SHACL Shapes
|
|
507
|
+
</h3>
|
|
508
|
+
<div class="flex flex-wrap gap-2">
|
|
509
|
+
<router-link v-for="shape in getShapesForTaxonomy(activeTaxonomy)" :key="shape.compact"
|
|
510
|
+
:to="`/ontology/shape/${compactToSlug(shape.compact)}`"
|
|
511
|
+
class="text-xs text-purple-600 bg-purple-50 px-2 py-1 rounded hover:bg-purple-100 transition-colors">
|
|
512
|
+
{{ shape.label }}
|
|
513
|
+
</router-link>
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
200
517
|
<div class="space-y-2">
|
|
201
518
|
<div v-for="concept in activeTaxonomyData()!.concepts" :key="concept.id"
|
|
202
519
|
class="border border-ink-100/60 rounded-lg p-3">
|