Commit ce38ae6a authored by dindigala's avatar dindigala
Browse files

feat: display nested motifs and refactors of current logic

parent 9ad7c3f3
Pipeline #217539 passed with stages
in 2 minutes and 56 seconds
This diff is collapsed.
......@@ -40,6 +40,7 @@
</template>
<script>
import Annotation from '@/mixins/annotation';
import { colors } from 'quasar';
import treestore from '@/stores/treestore.js';
import Header from '@/components/header.vue';
......@@ -50,7 +51,10 @@ export default {
components: {
Header,
},
mixins: [Panels],
mixins: [
Annotation,
Panels,
],
data() {
return {
annotations: [],
......@@ -131,7 +135,9 @@ export default {
*/
async request(url, responsetype = 'json') {
const response = await fetch(url);
const data = await (responsetype === 'text' ? response.text() : response.json());
const data = await (responsetype === 'text'
? response.text()
: response.json());
return data;
},
......@@ -153,10 +159,12 @@ export default {
return;
}
const current = await this.request(annotations.annotationCollection.first);
const current = await this.request(
annotations.annotationCollection.first,
);
if (current.annotationPage.items.length) {
this.annotations = current.annotationPage.items;
this.annotations = current.annotationPage.items.map((x) => ({ ...x, targetId: this.stripTargetId(x, true) }));
} else {
this.annotations = [];
}
......@@ -182,17 +190,15 @@ export default {
this.collection = data;
this.collectiontitle = this.getLabel(data);
this.tree.push(
{
children: [],
handler: (node) => {
this.$root.$emit('update-tree-knots', node.label);
},
label: this.collectiontitle,
'label-key': this.collectiontitle,
selectable: false,
this.tree.push({
children: [],
handler: (node) => {
this.$root.$emit('update-tree-knots', node.label);
},
);
label: this.collectiontitle,
'label-key': this.collectiontitle,
selectable: false,
});
if (Array.isArray(data.sequence)) {
const promises = [];
......@@ -297,20 +303,20 @@ export default {
sequence.forEach((item) => {
const itemLabel = this.getItemLabel(item.id);
urls.push(
{
label: item.id,
'label-key': `${itemLabel}`,
labelSheet: true,
handler: (node) => {
if (this.itemurl === node.label) {
return;
}
this.loaded = false;
this.$router.push({ query: { ...this.$route.query, itemurl: node.label } });
},
urls.push({
label: item.id,
'label-key': `${itemLabel}`,
labelSheet: true,
handler: (node) => {
if (this.itemurl === node.label) {
return;
}
this.loaded = false;
this.$router.push({
query: { ...this.$route.query, itemurl: node.label },
});
},
);
});
});
return urls;
},
......@@ -324,9 +330,13 @@ export default {
*/
getLabel(data) {
if (Object.keys(this.collection).length) {
return data.title && data.title[0].title ? data.title[0].title : data.label;
return data.title && data.title[0].title
? data.title[0].title
: data.label;
}
return data.label ? data.label : 'Manifest <small>(No label available)</small>';
return data.label
? data.label
: 'Manifest <small>(No label available)</small>';
},
/**
* get all the data provided on 'manifest level'
......@@ -339,7 +349,11 @@ export default {
// if the entrypoint points to a single manifest, initialize the tree
if (this.isCollection === false) {
this.tree.push({ label: '', 'label-key': this.config.labels.manifest, children: [] });
this.tree.push({
label: '',
'label-key': this.config.labels.manifest,
children: [],
});
}
if (!Array.isArray(data.sequence)) {
......@@ -351,17 +365,15 @@ export default {
}
this.manifests.push(data);
this.tree[0].children.push(
{
children: this.getItemUrls(data.sequence, data.label),
label: data.label,
'label-key': data.label,
handler: (node) => {
this.$root.$emit('update-tree-knots', node.label);
},
selectable: false,
this.tree[0].children.push({
children: this.getItemUrls(data.sequence, data.label),
label: data.label,
'label-key': data.label,
handler: (node) => {
this.$root.$emit('update-tree-knots', node.label);
},
);
selectable: false,
});
},
/**
* caller: *getItemUrls()*
......
......@@ -13,27 +13,26 @@
:key="annotationTab.key"
:label="$t(annotationTab.collectionTitle)"
:name="annotationTab.key"
@click="activeTab(annotationTab.key,annotationTab.type)"
@click="activeTab(annotationTab.key, annotationTab.type)"
/>
</q-tabs>
<AnnotationToggles />
<Loading
v-if="!isloading || isProcessing"
/>
<Loading v-if="!isloading || isProcessing" />
<AnnotationList
v-else-if="currentAnnotations.length && isloading && !isProcessing"
v-else-if="filteredAnnotations.length && isloading && !isProcessing"
class="custom-font"
:configured-annotations="currentAnnotations"
:active-annotation="activeAnnotation"
:configured-annotations="filteredAnnotations"
:content-ids="contentIds"
:get-icon="getIcon"
:status-check="statusCheck"
:toggle="toggle"
/>
<div
v-else-if="!currentAnnotations.length && isloading && !isProcessing"
v-else-if="!filteredAnnotations.length && isloading && !isProcessing"
class="q-pa-sm"
>
<Notification
......@@ -87,114 +86,163 @@ export default {
type: Object,
default: () => {},
},
panels: {
type: Array,
default: () => [],
},
},
data() {
return {
configuredAnnotations: [],
activeAnnotation: {},
contentIds: {},
currentTab: '',
ids: [],
isProcessing: false,
messages: {
none: 'noAnnotationMessage',
},
selectedAll: false,
selectedNone: true,
};
},
computed: {
annotationTabs() {
return Object.entries(this.tabConfig)
.map(([key, type]) => ({
key,
collectionTitle: key,
type,
}))
.filter((el) => this.annotations.find((x) => el.type.includes(x.body['x-content-type'])));
},
annotationTabConfig() {
return this.config?.annotations?.tabs || {};
},
configuredTypes() {
return this.config.annotations.types.map((type) => type.contenttype);
},
currentAnnotations() {
const contentType = this.annotationTabs.find((collection) => collection.key === this.currentTab);
const contentType = this.annotationTabs.find(
(collection) => collection.key === this.currentTab,
);
if (!contentType) {
return [];
}
const annotationType = this.configuredAnnotations.filter((annotationCollection) => contentType.type.includes(annotationCollection.body['x-content-type']));
const annotationType = this.configuredAnnotations.filter(
(annotationCollection) => contentType.type.includes(annotationCollection.body['x-content-type']),
);
return this.sortAnnotation(annotationType);
},
annotationTabConfig() {
return this.config?.annotations?.tabs || {};
},
annotationTabs() {
const contentTypes = {};
const annotationTab = {};
Object.entries(this.annotationTabConfig).forEach(([key, types]) => {
types.forEach((type) => {
contentTypes[type] = key;
});
filteredAnnotations() {
if (!this.currentTab) {
return [];
}
const output = this.annotations.filter(
(x) => this.tabConfig[this.currentTab].includes(x.body['x-content-type'])
&& this.contentIds[x.targetId],
);
annotationTab[key] = {
key,
collectionTitle: key,
type: [],
};
});
this.configuredAnnotations.forEach((curr) => {
const contentType = curr.body.['x-content-type'];
if (contentTypes[contentType]) {
annotationTab[contentTypes[contentType]].type.push(contentType);
return this.sortAnnotation(output);
},
selectedAll() {
return (
Object.keys(this.activeAnnotation).length
=== this.filteredAnnotations.length
);
},
selectedNone() {
return !Object.keys(this.activeAnnotation).length;
},
tabConfig() {
return this.config.annotations.tabs;
},
},
watch: {
filteredAnnotations: {
handler() {
this.activeAnnotation = {};
},
immediate: true,
},
panels: {
handler(curr, prev) {
const currentState = curr.find(
(x) => x.panel_label === 'Annotations' && x.show,
);
const previousState = prev.find(
(x) => x.panel_label === 'Annotations' && x.show,
);
if (currentState !== previousState) {
this.activeAnnotation = {};
if (currentState) {
[
...document.querySelectorAll('[data-annotation-level]'),
].forEach((el) => el.setAttribute('data-annotation-level', 0));
} else {
[
...document.querySelectorAll('[data-annotation-level]'),
].forEach((el) => el.setAttribute('data-annotation-level', -1));
[
...document.querySelectorAll('[data-annotation-icon]'),
].forEach((el) => el.remove());
}
}
});
return Object.values(annotationTab).filter((x) => x.type.length);
},
},
},
mounted() {
this.$root.$on('update-annotations', (content, isProcessing) => {
this.$root.$on('update-annotations', this.onContentUpdate);
this.$root.$on('update-annotation-loading', (isProcessing) => {
this.isProcessing = !!isProcessing;
const parser = new DOMParser();
const doc = parser.parseFromString(content, 'text/html');
this.ids = [...doc.body.querySelectorAll('[id]')].map((el) => el.getAttribute('id'));
this.configuredAnnotations = [];
const interval = setInterval(() => {
if (this.isloading) {
this.configuredAnnotations = this.filterAnnotationTypes();
const firstTab = this.annotationTabs.find((x) => x.type.length)?.key || '';
this.highlightActiveTabContent(this.annotationTabConfig[firstTab] || []);
this.currentTab = firstTab;
clearInterval(interval);
}
}, 50);
});
this.$root.$on('panels-position', (newPanels) => {
const annotationPanelHidden = newPanels.find((x) => x.panel_label === 'Annotations' && !x.show);
this.currentAnnotations.forEach((annotation) => {
const id = this.stripAnnotationId(annotation.target.id);
const textElement = document.getElementById(id);
if (annotationPanelHidden) {
textElement.classList.remove('annotation');
} else {
textElement.classList.add('annotation');
textElement.classList.add('annotation-disabled');
}
});
});
},
methods: {
activeTab(key, types) {
activeTab(key) {
if (this.currentTab === key) {
return;
}
this.filteredAnnotations.forEach((x) => this.removeAnnotation(x, -1));
this.currentTab = key;
this.selectedAll = false;
this.selectedNone = true;
this.highlightActiveTabContent(types);
this.highlightActiveContent(this.filteredAnnotations);
},
addAnnotation(annotation) {
const updated = { ...this.activeAnnotation };
let selector = this.stripTargetId(annotation, false);
if (selector.startsWith('.')) {
selector = selector.replace(/\./g, '');
}
this.updateHighlightState(selector, 'INC');
this.addIcon(document.getElementById(selector) || document.querySelector(`.${selector}`), annotation);
updated[annotation.targetId] = true;
this.activeAnnotation = updated;
},
addIcon(element, annotation) {
const contentType = annotation.body['x-content-type'];
try {
const svg = this.createSVG(this.getIconName(contentType));
svg.setAttribute(
'data-annotation-icon',
this.stripTargetId(annotation),
);
element.prepend(svg);
} catch (err) {
// TODO : Handle Here
}
},
createSVG(name) {
const [path, viewBox] = Icons[name].split('|');
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
......@@ -205,7 +253,10 @@ export default {
svg.setAttribute('role', 'presentation');
svg.setAttribute('viewBox', viewBox);
const newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
const newPath = document.createElementNS(
'http://www.w3.org/2000/svg',
'path',
);
newPath.setAttribute('d', path);
svg.appendChild(newPath);
......@@ -213,25 +264,14 @@ export default {
return svg;
},
/**
* filter for configured annotation types (index.html)
*
* @return array of annotations excluding unconfigured ones
*/
filterAnnotationTypes() {
return this.annotations.filter((annotation) => {
this.$set(annotation, 'status', this.config.annotations.show);
annotation.strippedId = this.stripAnnotationId(annotation.target.id);
const annotationIds = this.ids.includes(annotation.strippedId);
if (this.configuredTypes.find((type) => type === annotation.body['x-content-type']) && annotationIds) {
this.setText(annotation);
return true;
}
return false;
});
onContentUpdate(ids) {
try {
this.currentTab = this.annotationTabs[0].key;
this.contentIds = ids;
this.highlightActiveContent(this.filteredAnnotations);
} catch (err) {
setTimeout(() => this.onContentUpdate(ids), 100);
}
},
getIcon(contentType) {
......@@ -239,62 +279,56 @@ export default {
},
getIconName(contentType) {
return this.config.annotations.types.filter((annotation) => annotation.contenttype === contentType)[0].icon;
return this.config.annotations.types.filter(
(annotation) => annotation.contenttype === contentType,
)[0].icon;
},
onHighlightAll() {
this.currentAnnotations.forEach((annotation) => this.updateToggleState(annotation, 'remove', 'add'));
this.selectedAll = true;
this.selectedNone = false;
this.filteredAnnotations.forEach(
(annotation) => !this.activeAnnotation[annotation.targetId]
&& this.addAnnotation(annotation),
);
},
onHighlightNone() {
this.currentAnnotations.forEach((annotation) => this.updateToggleState(annotation, 'add', 'remove'));
this.selectedAll = false;
this.selectedNone = true;
this.filteredAnnotations.forEach(
(annotation) => this.activeAnnotation[annotation.targetId]
&& this.removeAnnotation(annotation),
);
},
setText(annotation) {
const contentType = annotation.body['x-content-type'];
const id = this.stripAnnotationId(annotation.target.id);
const textElement = document.getElementById(id);
let svg = null;
try {
svg = this.createSVG(this.getIconName(contentType));
removeIcon(annotation) {
const stripeId = this.stripTargetId(annotation);
const el = document
.querySelector(`svg[data-annotation-icon='${stripeId}']`);
svg.setAttribute('id', `annotation-icon-${id}`);
} catch (err) {
svg = null;
}
if (svg) {
textElement.prepend(svg);
if (el) {
el.remove();
}
},
statusCheck() {
const num = this.currentAnnotations.length;
const active = this.currentAnnotations.filter((annotation) => annotation.status === true).length;
removeAnnotation(annotation, level) {
const updated = { ...this.activeAnnotation };
let selector = this.stripTargetId(annotation, false);
if (num === active) {
this.selectedAll = false;
this.selectedNone = true;
} else if (active === 0) {
this.selectedAll = true;
this.selectedNone = false;
} else {
this.selectedAll = false;
this.selectedNone = false;
if (selector.startsWith('.')) {
selector = selector.replace(/\./g, '');
}
this.updateHighlightState(selector, 'DEC', level);
this.removeIcon(annotation);
delete updated[annotation.targetId];
this.activeAnnotation = updated;
},
toggle(annotation) {
annotation.status = !annotation.status;
this.updateToggleState(annotation, 'toggle', 'toggle');
const exists = !!this.activeAnnotation[annotation.targetId];
if (exists) {
this.removeAnnotation(annotation);
} else {
this.addAnnotation(annotation);
}
},
},
};
......@@ -326,9 +360,49 @@ export default {
padding-bottom: inherit;
}
.annotation-disabled-overlap {
border-bottom: 1px;
border-bottom-style: dotted !important;
padding-bottom: inherit;
}
.annotation-disabled > svg {
display: none;
}
*[data-annotation-level='0'] {
background-color: $grey-3;
}
*[data-annotation-level='1'],
*[data-annotation-level='1'] * {
background-color: $blue-1;
border-bottom: 1px solid #000;
}
*[data-annotation-level='2'],
*[data-annotation-level='2'] * {
background-color: $blue-2;
border-bottom: 1px solid #000;
}