Introduce BackLinkViewer component and associated config #342
4 changed files with 235 additions and 116 deletions
48
src/components/BackLinkViewer.vue
Normal file
48
src/components/BackLinkViewer.vue
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<h4 v-if="Object.keys(refRecords).length" style="margin-bottom: 1em;" >Records referencing <em>{{props.record.displayLabel}}</em>:</h4>
|
||||
<div v-for="(arr, key) in refRecords" style="margin-bottom: 0.5em;">
|
||||
<em>via <strong>{{makeReadable(toCURIE(key, allPrefixes, 'parts').property)}}</strong></em>:
|
||||
<span v-for="r in arr">
|
||||
<br> -
|
||||
<NamedNodeViewer
|
||||
:textVal="r.record_id"
|
||||
:prefLabel="getPrefLabel(r.quad.subject, rdfDS, allPrefixes)"
|
||||
:displayLabel="getRecordDisplayLabel(r.quad.subject, rdfDS, allPrefixes, configVarsMain)"
|
||||
:quad="r.quad"
|
||||
:targetClass="r.class_iri"
|
||||
:allowLink="true"
|
||||
>
|
||||
</NamedNodeViewer>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { inject, onMounted, ref } from 'vue';
|
||||
import { toCURIE, getReferencingRecords, getPrefLabel, getRecordDisplayLabel, makeReadable} from '@/modules/utils';
|
||||
|
||||
const props = defineProps({
|
||||
record: Object,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['has-referencing-records'])
|
||||
|
||||
const fetchFromService = inject('fetchFromService')
|
||||
const allPrefixes = inject('allPrefixes')
|
||||
const rdfDS = inject('rdfDS')
|
||||
const configVarsMain = inject('configVarsMain')
|
||||
const refRecords = ref({});
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
const arg = allPrefixes['dlthings'] + 'Thing';
|
||||
await fetchFromService('get-paginated-records-constrained', arg, allPrefixes, props.record.title )
|
||||
await fetchFromService('get-paginated-records-constrained', arg, allPrefixes, toCURIE(props.record.title, allPrefixes))
|
||||
refRecords.value = getReferencingRecords(props.record.title, rdfDS.data.graph)
|
||||
if (Object.keys(refRecords.value).length > 0) {
|
||||
emit('has-referencing-records')
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
@ -75,46 +75,12 @@
|
|||
</span>
|
||||
</v-card-subtitle>
|
||||
<v-card-text v-if="!props.formOpen" :class="mobile ? 'text-caption' : ''">
|
||||
<strong>Persistent Identifier</strong>: {{ record.title}}<br/>
|
||||
<v-row align="stretch">
|
||||
<v-col :cols="showBackLinks ? 6 : 12">
|
||||
<strong>Persistent Identifier</strong>: {{ record.title}}<br/>
|
||||
|
||||
<!-- Literal nodes -->
|
||||
<span v-for="(v, k, index) in record.triples['Literal']">
|
||||
<span v-if="propertyShapes[k]">
|
||||
<strong>
|
||||
{{
|
||||
nameOrCURIE(
|
||||
propertyShapes[k],
|
||||
shapesDS.data.prefixes,
|
||||
true
|
||||
)
|
||||
}}
|
||||
</strong>:
|
||||
</span>
|
||||
<span v-else>
|
||||
<strong>{{ k }}</strong>:
|
||||
</span>
|
||||
<span v-for="(el, i) in v.values">
|
||||
<span v-if="i < showCounts['Literal'][k]">
|
||||
<span v-if="v.values.length > 1"><br/> -</span>
|
||||
<LiteralNodeViewer v-if="el.value" :textVal="el.value" :wrap="textWrapping" :width="textTruncateWidth"></LiteralNodeViewer>
|
||||
</span>
|
||||
</span>
|
||||
<br/>
|
||||
<MoreOrLessRecordsViewer
|
||||
:records="v.values"
|
||||
v-model:count="showCounts['Literal'][k]"
|
||||
:stepSize="defaultStep"
|
||||
></MoreOrLessRecordsViewer>
|
||||
</span>
|
||||
|
||||
|
||||
<!-- Named nodes -->
|
||||
<span v-if="fetchingRecords">
|
||||
<v-skeleton-loader type="paragraph"></v-skeleton-loader>
|
||||
</span>
|
||||
<span v-else>
|
||||
<span v-for="(v, k, index) in record.triples['NamedNode']">
|
||||
<span v-if="k != RDF.type.value">
|
||||
<!-- Literal nodes -->
|
||||
<span v-for="(v, k, index) in record.triples['Literal']">
|
||||
<span v-if="propertyShapes[k]">
|
||||
<strong>
|
||||
{{
|
||||
|
|
@ -124,92 +90,136 @@
|
|||
true
|
||||
)
|
||||
}}
|
||||
</strong>: <MoreOrLessRecordsViewer
|
||||
:records="v.values"
|
||||
v-model:count="showCounts['NamedNode'][k]"
|
||||
:stepSize="defaultStep"
|
||||
></MoreOrLessRecordsViewer>
|
||||
<span v-for="(el, i) in v.values">
|
||||
<span v-if="i < showCounts['NamedNode'][k]">
|
||||
<span v-if="v.values.length > 1"><br /> - </span>
|
||||
<NamedNodeViewer
|
||||
v-if="el.value"
|
||||
:textVal="el.value"
|
||||
:prefLabel="
|
||||
getPrefLabel(el, rdfDS, allPrefixes)
|
||||
"
|
||||
:displayLabel="
|
||||
getRecordDisplayLabel(el, rdfDS, allPrefixes, configVarsMain)
|
||||
"
|
||||
:quad="
|
||||
getPidQuad(el.value, rdfDS.data.graph)
|
||||
"
|
||||
:targetClass="
|
||||
propertyShapes[k][SHACL.class.value]
|
||||
"
|
||||
>
|
||||
</NamedNodeViewer>
|
||||
</span>
|
||||
</span>
|
||||
</strong>:
|
||||
</span>
|
||||
<span v-else>
|
||||
<strong>{{ k }}</strong
|
||||
>:
|
||||
<span v-for="(el, i) in v.values">
|
||||
<span v-if="i < showCounts['NamedNode'][k]">
|
||||
<span v-if="v.values.length > 1"><br /> -</span>
|
||||
{{ el.value }}
|
||||
</span>
|
||||
<strong>{{ k }}</strong>:
|
||||
</span>
|
||||
<span v-for="(el, i) in v.values">
|
||||
<span v-if="i < showCounts['Literal'][k]">
|
||||
<span v-if="v.values.length > 1"><br/> -</span>
|
||||
<LiteralNodeViewer v-if="el.value" :textVal="el.value" :wrap="textWrapping" :width="textTruncateWidth"></LiteralNodeViewer>
|
||||
</span>
|
||||
</span>
|
||||
<br>
|
||||
<br/>
|
||||
<MoreOrLessRecordsViewer
|
||||
:records="v.values"
|
||||
v-model:count="showCounts['Literal'][k]"
|
||||
:stepSize="defaultStep"
|
||||
></MoreOrLessRecordsViewer>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<!-- Now show all blank nodes for which a display label has been configured, which makes them special-->
|
||||
<!-- TODO: how do we deal with preflabel here ??? -->
|
||||
<span v-for="(v,k) in specialBlankNodes">
|
||||
<strong>
|
||||
{{
|
||||
nameOrCURIE(
|
||||
propertyShapes[k],
|
||||
shapesDS.data.prefixes,
|
||||
true
|
||||
)
|
||||
}}
|
||||
</strong>: <MoreOrLessRecordsViewer
|
||||
:records="v.items.map(i => i.value)"
|
||||
v-model:count="showCounts['BlankNodeSpecial'][k]"
|
||||
:stepSize="defaultStep"
|
||||
></MoreOrLessRecordsViewer>
|
||||
<span v-for="(item, i) in v.items">
|
||||
<span v-if="i < showCounts['BlankNodeSpecial'][k]" class="line-item">
|
||||
<span v-if="item.keyPropertyRole?.classIRI && item.keyPropertyRole?.recordPID">
|
||||
-
|
||||
<v-tooltip location="top start">
|
||||
<template v-slot:activator="{ props }">
|
||||
<a
|
||||
v-bind="props"
|
||||
style="cursor: pointer"
|
||||
@click.prevent="selectNamedNode(item.keyPropertyRole.classIRI, item.keyPropertyRole.recordPID)"
|
||||
>{{ item.displayLabel }}</a
|
||||
>
|
||||
</template>
|
||||
<template v-slot:default="{ isActive }">
|
||||
<v-icon >{{ getClassIcon(item.keyPropertyRole.classIRI, allPrefixes) }}</v-icon>
|
||||
{{ getDisplayName(item.keyPropertyRole.classIRI, configVarsMain, allPrefixes, shapesDS.data.nodeShapes[item.keyPropertyRole.classIRI]) }}
|
||||
</template>
|
||||
</v-tooltip>
|
||||
|
||||
</span>
|
||||
<span v-else>
|
||||
- <LiteralNodeViewer :textVal="item.displayLabel" :wrap="'wrap'" :allowLink="false"></LiteralNodeViewer>
|
||||
|
||||
<!-- Named nodes -->
|
||||
<span v-if="fetchingRecords">
|
||||
<v-skeleton-loader type="paragraph"></v-skeleton-loader>
|
||||
</span>
|
||||
<span v-else>
|
||||
<span v-for="(v, k, index) in record.triples['NamedNode']">
|
||||
<span v-if="k != RDF.type.value">
|
||||
<span v-if="propertyShapes[k]">
|
||||
<strong>
|
||||
{{
|
||||
nameOrCURIE(
|
||||
propertyShapes[k],
|
||||
shapesDS.data.prefixes,
|
||||
true
|
||||
)
|
||||
}}
|
||||
</strong>: <MoreOrLessRecordsViewer
|
||||
:records="v.values"
|
||||
v-model:count="showCounts['NamedNode'][k]"
|
||||
:stepSize="defaultStep"
|
||||
></MoreOrLessRecordsViewer>
|
||||
<span v-for="(el, i) in v.values">
|
||||
<span v-if="i < showCounts['NamedNode'][k]">
|
||||
<span v-if="v.values.length > 1"><br /> - </span>
|
||||
<NamedNodeViewer
|
||||
v-if="el.value"
|
||||
:textVal="el.value"
|
||||
:prefLabel="
|
||||
getPrefLabel(el, rdfDS, allPrefixes)
|
||||
"
|
||||
:displayLabel="
|
||||
getRecordDisplayLabel(el, rdfDS, allPrefixes, configVarsMain)
|
||||
"
|
||||
:quad="
|
||||
getPidQuad(el.value, rdfDS.data.graph)
|
||||
"
|
||||
:targetClass="
|
||||
propertyShapes[k][SHACL.class.value]
|
||||
"
|
||||
>
|
||||
</NamedNodeViewer>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
<strong>{{ k }}</strong
|
||||
>:
|
||||
<span v-for="(el, i) in v.values">
|
||||
<span v-if="i < showCounts['NamedNode'][k]">
|
||||
<span v-if="v.values.length > 1"><br /> -</span>
|
||||
{{ el.value }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<br>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<!-- Now show all blank nodes for which a display label has been configured, which makes them special-->
|
||||
<!-- TODO: how do we deal with preflabel here ??? -->
|
||||
<span v-for="(v,k) in specialBlankNodes">
|
||||
<strong>
|
||||
{{
|
||||
nameOrCURIE(
|
||||
propertyShapes[k],
|
||||
shapesDS.data.prefixes,
|
||||
true
|
||||
)
|
||||
}}
|
||||
</strong>: <MoreOrLessRecordsViewer
|
||||
:records="v.items.map(i => i.value)"
|
||||
v-model:count="showCounts['BlankNodeSpecial'][k]"
|
||||
:stepSize="defaultStep"
|
||||
></MoreOrLessRecordsViewer>
|
||||
<span v-for="(item, i) in v.items">
|
||||
<span v-if="i < showCounts['BlankNodeSpecial'][k]" class="line-item">
|
||||
<span v-if="item.keyPropertyRole?.classIRI && item.keyPropertyRole?.recordPID">
|
||||
-
|
||||
<v-tooltip location="top start">
|
||||
<template v-slot:activator="{ props }">
|
||||
<a
|
||||
v-bind="props"
|
||||
style="cursor: pointer"
|
||||
@click.prevent="selectNamedNode(item.keyPropertyRole.classIRI, item.keyPropertyRole.recordPID)"
|
||||
>{{ item.displayLabel }}</a
|
||||
>
|
||||
</template>
|
||||
<template v-slot:default="{ isActive }">
|
||||
<v-icon >{{ getClassIcon(item.keyPropertyRole.classIRI, allPrefixes) }}</v-icon>
|
||||
{{ getDisplayName(item.keyPropertyRole.classIRI, configVarsMain, allPrefixes, shapesDS.data.nodeShapes[item.keyPropertyRole.classIRI]) }}
|
||||
</template>
|
||||
</v-tooltip>
|
||||
|
||||
</span>
|
||||
<span v-else>
|
||||
- <LiteralNodeViewer :textVal="item.displayLabel" :wrap="'wrap'" :allowLink="false"></LiteralNodeViewer>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</v-col>
|
||||
<v-col v-if="showBackLinks && !hideBackLinks" cols="auto" class="d-flex">
|
||||
<v-divider vertical />
|
||||
</v-col>
|
||||
<v-col>
|
||||
<BackLinkViewer v-if="firstUpdateDone && !hideBackLinks" v-show="showBackLinks" :record="record" @has-referencing-records="showBackLinks = true"></BackLinkViewer>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<!-- Blank nodes -->
|
||||
<br />
|
||||
<span v-if="showBackLinks && !hideBackLinks"><br /></span>
|
||||
<v-btn
|
||||
no-gutters
|
||||
v-if="Object.keys(record.triples['BlankNode']).length > 0"
|
||||
|
|
@ -324,6 +334,7 @@ import MoreOrLessRecordsViewer from '@/components/MoreOrLessRecordsViewer.vue';
|
|||
import SpecialButton from '@/components/SpecialButton.vue'
|
||||
import { useCompConfig } from '@/composables/useCompConfig';
|
||||
import { useDisplay } from 'vuetify'
|
||||
import BackLinkViewer from './BackLinkViewer.vue';
|
||||
const { mobile } = useDisplay()
|
||||
// Define component properties
|
||||
const props = defineProps({
|
||||
|
|
@ -380,6 +391,14 @@ const fetchingRecords = ref(false);
|
|||
const canEditClass = ref(false);
|
||||
const showSpecialButtons = ref(false);
|
||||
const specialButtons = reactive({});
|
||||
const showBackLinks = ref(false);
|
||||
const firstUpdateDone = ref(false);
|
||||
const hideBackLinksConfig = componentConfig?.hideBackLinks;
|
||||
const hideBackLinks = ref(true);
|
||||
if (hideBackLinksConfig === false || Array.isArray(hideBackLinksConfig) &&
|
||||
!hideBackLinksConfig.includes(toCURIE(props.classIRI, allPrefixes))) {
|
||||
hideBackLinks.value = false
|
||||
}
|
||||
|
||||
const emit = defineEmits(['namedNodeSelected']);
|
||||
function selectNamedNode(recordClass, recordPID) {
|
||||
|
|
@ -398,6 +417,7 @@ onBeforeMount(async () => {
|
|||
fetchingRecords.value = true;
|
||||
await updateRecord(true);
|
||||
fetchingRecords.value = false;
|
||||
firstUpdateDone.value = true;
|
||||
let recordPIDprefix = toCURIE(props.quad.subject.value, allPrefixes, 'parts').prefix;
|
||||
if (configVarsMain['idResolvesExternally'].indexOf(recordPIDprefix) >= 0) {
|
||||
resolveExternally.value = true;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ const mainVarsToLoad = {
|
|||
NodeShapeViewer: {
|
||||
recordNumberStepSize: 5,
|
||||
textTruncateWidth: "85%",
|
||||
hideBackLinks: true,
|
||||
},
|
||||
URIEditor: {
|
||||
default: "curie",
|
||||
|
|
|
|||
|
|
@ -302,6 +302,56 @@ export function getSubjectQuad(subj, graph) {
|
|||
}
|
||||
}
|
||||
|
||||
export function getReferencingRecords(objVal, graph) {
|
||||
let referencingRecords = {};
|
||||
const firstLevelQuads = graph.getQuads(
|
||||
null,
|
||||
null,
|
||||
namedNode(objVal),
|
||||
null
|
||||
);
|
||||
for (const q of firstLevelQuads) {
|
||||
if (q.subject.termType === 'BlankNode') {
|
||||
var secondLevelQuads = graph.getQuads(
|
||||
null,
|
||||
null,
|
||||
q.subject,
|
||||
null
|
||||
);
|
||||
if (secondLevelQuads && secondLevelQuads.length) {
|
||||
if (!referencingRecords.hasOwnProperty(secondLevelQuads[0].predicate.value)) {
|
||||
referencingRecords[secondLevelQuads[0].predicate.value] = []
|
||||
}
|
||||
referencingRecords[secondLevelQuads[0].predicate.value].push({
|
||||
record_id: secondLevelQuads[0].subject.value
|
||||
})
|
||||
var sQ = getSubjectQuad(secondLevelQuads[0].subject, graph)
|
||||
if (sQ) {
|
||||
referencingRecords[secondLevelQuads[0].predicate.value].at(-1).class_iri = sQ.object.value
|
||||
referencingRecords[secondLevelQuads[0].predicate.value].at(-1).quad = sQ
|
||||
}
|
||||
if (secondLevelQuads.length > 1) {
|
||||
console.error("! secondLevelQuads has length > 1 !")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!referencingRecords.hasOwnProperty(q.predicate.value)) {
|
||||
referencingRecords[q.predicate.value] = []
|
||||
}
|
||||
referencingRecords[q.predicate.value].push({
|
||||
record_id: q.subject.value
|
||||
})
|
||||
var sQ = getSubjectQuad(q.subject, graph)
|
||||
if (sQ) {
|
||||
referencingRecords[q.predicate.value].at(-1).class_iri = sQ.object.value
|
||||
referencingRecords[q.predicate.value].at(-1).quad = sQ
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return referencingRecords
|
||||
}
|
||||
|
||||
export function objectsEqual(obj1, obj2) {
|
||||
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
|
||||
return false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue