diff --git a/src/components/annotations/AnnotationVariantItem.vue b/src/components/annotations/AnnotationVariantItem.vue index 5288e8e1..24894b7f 100644 --- a/src/components/annotations/AnnotationVariantItem.vue +++ b/src/components/annotations/AnnotationVariantItem.vue @@ -12,7 +12,7 @@ >
import { getItemColorBasedOnIndex } from '@/utils/color'; -import { reactive, watch } from 'vue'; +import { reactive, watch, computed } from 'vue'; +import { useAnnotationsStore } from '@/stores/annotations'; import * as AnnotationUtils from '@/utils/annotations'; +const annotationStore = useAnnotationsStore(); +const activeAnnotSelectVariantItems = computed(() => annotationStore.activeAnnotSelectVariantItems); +const variantItemsColors = computed(() => annotationStore.variantItemsColors) + export interface Props { annotation: Annotation, isActive: (annotation: Annotation) => boolean, @@ -51,47 +56,91 @@ const props = withDefaults(defineProps(), { toggle: () => null, }) -let variantItemsSelection = reactive({}) -let variantItemsColors = {} - +let initialVariantItemsSelection = {} + watch(() => props.annotation, () => { + props.annotation.body.value.forEach((variantItem) => { const witness = variantItem.witness - variantItemsSelection[witness] = false + initialVariantItemsSelection[witness] = false }) }) - - + function handleClick(witness: string, i: number) { // if at least one variant item is selected, then we don't toggle this annotation // for each variant item: we should save a state of selected or not, so that to show the icon or not... - const witnessColor = getItemColorBasedOnIndex(i) - if (witness in variantItemsColors === false) variantItemsColors[witness] = witnessColor + + // get the number of keys in the variantItemsColors - if the witness is not in the variantItemsColors then we request the next color - which has index the same as this count + + + const witnessColor = getWitnessColor(witness) + if (witness in variantItemsColors.value === false) { + updateVariantItemsColors(witness, witnessColor) + } + + const variantItemsSelection = pickVariantItemsSelection() if (!isAtLeastOneVariantItemClicked()) { // for the first variant item of each variant object props.toggle(props.annotation) - } + if ((isOnlyThisVariantActive(witness)) && (isVariantItemActive(witness))) { // when we have only one variant item of a certain variant object selected and then we deselect it -> remove the blue highlight from the text props.toggle(props.annotation) } + // update the state of 'false' or 'true' whether this variant item is selected or not - variantItemsSelection[witness] = !variantItemsSelection[witness] + variantItemsSelection[witness] = !variantItemsSelection[witness] + annotationStore.updateActiveAnnotSelectVariantItems(props.annotation.id, [props.annotation, variantItemsSelection]) const selector = props.annotation.target[0].selector.value - if (variantItemsSelection[witness] === true) { - AnnotationUtils.addWitness(selector, witness, variantItemsColors) + if (variantItemsSelection[witness] === true) { // to change + AnnotationUtils.addWitness(selector, witness, variantItemsColors.value) } else { AnnotationUtils.removeWitness(selector, witness) } } + +function updateVariantItemsColors(witness: string, witnessColor: string) { + variantItemsColors.value[witness] = witnessColor + annotationStore.setVariantItemsColors(variantItemsColors.value) +} + + +function allocateWitnessColorInVariantItem(witness: string): string { + const witnessColor = getWitnessColor(witness) + if (witness in variantItemsColors.value === false) { + updateVariantItemsColors(witness, witnessColor) + } + return witnessColor +} + +function getWitnessColor(witness): string { + + let indexColor; + if (Object.keys(variantItemsColors.value).length === 0){ + // the first variant item to be selected + return getItemColorBasedOnIndex(0) + } + else if ((witness in variantItemsColors.value) === false) { + // this variant item was not yet selected, but there are already at least one selected + indexColor = Object.keys(variantItemsColors.value).length + return getItemColorBasedOnIndex(indexColor) + } + + // if the variant item with this witness was already selected somewhere in the annotation + return variantItemsColors.value[witness] +} + + function isAtLeastOneVariantItemClicked() { + const variantItemsSelection = pickVariantItemsSelection() + let isClicked = false Object.keys(variantItemsSelection).forEach((witness) => { if (variantItemsSelection[witness] === true) isClicked = true @@ -99,7 +148,10 @@ function isAtLeastOneVariantItemClicked() { return isClicked } + function isOnlyThisVariantActive(witness) { + const variantItemsSelection = pickVariantItemsSelection() + let isOnlyThisVariantClicked = true Object.keys(variantItemsSelection).forEach((wit) => { if (variantItemsSelection[wit] === true && wit!== witness) isOnlyThisVariantClicked = false @@ -107,19 +159,41 @@ function isOnlyThisVariantActive(witness) { return isOnlyThisVariantClicked } -function isVariantItemActive(witness): boolean{ - return variantItemsSelection[witness] === true -} +function pickVariantItemsSelection() { + // we use this function in order to distinguish the state of variant items selection, before clicking on a certain variant item: either + // a) no variant item is selected - initial state, variantItemsSelection copies the value of initialVariantItemsSelection (every variant item has false value) + // b) the variant item belongs to an active annotation - use the store computed property : 'activeAnnotSelectVariantItems' + // if we have active annotation - we use the value of 'activeAnnotSelectVariantItems' property in the annotation store + // else: we use the initial variant items selection - all false values + let variantItemsSelection; + + if (Object.keys(activeAnnotSelectVariantItems.value).length > 0) { + if((props.annotation.id in activeAnnotSelectVariantItems.value) === false) { + variantItemsSelection = { ...initialVariantItemsSelection }; + } + else { + variantItemsSelection = activeAnnotSelectVariantItems.value[props.annotation.id][1] + } + } + else { + variantItemsSelection = { ...initialVariantItemsSelection }; + } + return variantItemsSelection +} -function getVariantItemsSelected(): string[] { - let variantItemsSelected: string[] = [] - Object.keys(variantItemsSelection).forEach((wit) => { - if (variantItemsSelection[wit] === true) variantItemsSelected.push(wit) - }) +function isVariantItemActive(witness): boolean{ - return variantItemsSelected + if(Object.keys(activeAnnotSelectVariantItems.value).length > 0) { + if( (props.annotation.id in activeAnnotSelectVariantItems.value) === false) { + return false + } + else { + return activeAnnotSelectVariantItems.value[props.annotation.id][1][witness] + } + } + return false } diff --git a/src/stores/annotations.ts b/src/stores/annotations.ts index 8a026748..bc99abf9 100644 --- a/src/stores/annotations.ts +++ b/src/stores/annotations.ts @@ -13,7 +13,9 @@ import { useConfigStore} from '@/stores/config'; export const useAnnotationsStore = defineStore('annotations', () => { const activeTab = ref('') - const activeAnnotations = ref({} as ActiveAnnotation) + const activeAnnotations = ref({}) + const activeAnnotSelectVariantItems = ref({}) + const variantItemsColors = ref({}) const annotations = ref(null) const filteredAnnotations = ref([]) const isLoading = ref(false); @@ -39,6 +41,18 @@ export const useAnnotationsStore = defineStore('annotations', () => { filteredAnnotations.value = payload } + function setVariantItemsColors(payload) { + variantItemsColors.value = payload + } + + function setActiveAnnotSelectVariantItems(payload) { + activeAnnotSelectVariantItems.value = payload + } + + function updateActiveAnnotSelectVariantItems(id, payload) { + activeAnnotSelectVariantItems.value[id] = payload + } + const addActiveAnnotation = (id: string) => { const annotationStore = useAnnotationsStore() const configStore = useConfigStore() @@ -120,9 +134,13 @@ export const useAnnotationsStore = defineStore('annotations', () => { const removeActiveAnnotation = (id) => { const annotationStore = useAnnotationsStore() const removeAnnotation = activeAnnotations.value[id]; + if (!removeAnnotation) { return; } + + // If removed active annotation is variant - then set all the variant items selection to false for this annotation + const activeAnnotationsList = { ...activeAnnotations.value }; @@ -268,11 +286,28 @@ export const useAnnotationsStore = defineStore('annotations', () => { // We need to check here if the right annotations panel tab is active // a.k.a. it exists in the current filteredAnnotations const annotation = filteredAnnotations.value.find((filtered) => filtered.id === id); + const selector = annotation.target[0].selector.value if (annotation) { if (targetIsSelected) { removeActiveAnnotation(id) + if (AnnotationUtils.isVariant(annotation)) { + // we need to know which witnesses belong to this annotation AND are selected - so that we can remove the witnesses chips from the text + const witnessesHtml = AnnotationUtils.getWitnessesHtmlEl(selector) + const witnessesList = AnnotationUtils.getWitnessesList(witnessesHtml) + // remove the 'witnesses chips' which are selected + AnnotationUtils.removeWitnessesChipsWhenDeselectText(witnessesList, selector) + delete activeAnnotSelectVariantItems.value[annotation.id] + } } else { addActiveAnnotation(id) + if(AnnotationUtils.isVariant(annotation)) { + // if annotation is variant - additionally set the variant items selection to true + const variantItemsSelect = AnnotationUtils.initVariantItemsSelection(annotation, true) + + activeAnnotSelectVariantItems.value[annotation.id] = [activeAnnotations.value[annotation.id], variantItemsSelect] + // add all the 'witnesses chips' for this annotation variant + AnnotationUtils.addWitnessesChipsWhenSelectText(variantItemsSelect, selector, variantItemsColors.value) + } } } }); @@ -321,10 +356,10 @@ export const useAnnotationsStore = defineStore('annotations', () => { } return { - activeTab, activeAnnotations, annotations, filteredAnnotations, isLoading, // states + activeTab, activeAnnotations, activeAnnotSelectVariantItems, annotations, filteredAnnotations, isLoading, variantItemsColors, // states isAllAnnotationSelected, isNoAnnotationSelected, // computed - setActiveAnnotations, setAnnotations, updateAnnotationLoading, setFilteredAnnotations, // functions - addActiveAnnotation, selectFilteredAnnotations, addHighlightAttributesToText, + setActiveAnnotations, setAnnotations, updateAnnotationLoading, setFilteredAnnotations, setActiveAnnotSelectVariantItems, setVariantItemsColors, // functions + addActiveAnnotation, selectFilteredAnnotations, addHighlightAttributesToText, updateActiveAnnotSelectVariantItems, annotationLoaded, removeActiveAnnotation, resetAnnotations, initAnnotations, addHighlightHoverListeners, addHighlightClickListeners, getNearestParentAnnotation, selectAll, selectNone, discoverParentAnnotationIds, discoverChildAnnotationIds diff --git a/src/utils/annotations.js b/src/utils/annotations.js index 0c6a2845..67c9064b 100644 --- a/src/utils/annotations.js +++ b/src/utils/annotations.js @@ -2,6 +2,7 @@ import * as Utils from '@/utils/index'; import { getIcon } from '@/utils/icons'; import { i18n } from '@/i18n'; + // utility functions that we can use as generic way for perform tranformation on annotations. export function addHighlightToElements(selector, root, annotationId) { @@ -239,6 +240,7 @@ export function addWitness(selector, witness, variantItemsColors) { const parentEl = targetHtmlEl.parentElement const indexOfTarget = [].slice.call(parentEl.children).indexOf(targetHtmlEl) + const witHtml = createCurrWitHtml(witness, variantItemsColors[witness]) if(!parentEl.children[indexOfTarget-1].classList.contains("witnesses")) { @@ -287,7 +289,7 @@ export function removeWitness(selector, witness) { witHtml[0].remove() } -function getWitnessesHtmlEl(selector) { +export function getWitnessesHtmlEl(selector) { // selector represents the target text of a certain variant item // we aim to get the html element which contains the 'witnesses chips' related to the target. // this html element which contains the 'witnesses chips' is located before the target element @@ -299,6 +301,50 @@ function getWitnessesHtmlEl(selector) { return witnessesHtmlEl } +export function getWitnessesList(witnessesHtml) { + // returns the list of witnesses() which are already selected + let witnessesList= [] + Array.from(witnessesHtml.children).forEach((witnessHtml) => { + witnessesList.push(witnessHtml.innerHTML) + }) + return witnessesList +} + +export function unselectVariantItems(variantItemsSelection) { + let newVariantItemsSelection = {} + Object.keys(variantItemsSelection).forEach((wit) => { + newVariantItemsSelection[wit] = false + }) + return newVariantItemsSelection +} + +export function addWitnessesChipsWhenSelectText(variantItemsSelection, selector, variantItemsColors) { + // variantItemsSelection: JSON object of 'witness name': 'true' + // this function aims to add all witnesses on the highlighted text when we click on the text + + Object.keys(variantItemsSelection).forEach((witness) => { + addWitness(selector, witness, variantItemsColors) + }) +} + +export function removeWitnessesChipsWhenDeselectText(witnessesList, selector) { + witnessesList.forEach((witness) => { + removeWitness(selector, witness) + }) +} + +export function isVariant(annotation) { + return annotation.body['x-content-type'] === 'Variant'; +} + +export function initVariantItemsSelection(annotation, value) { + // initialize with the boolean of 'value' variable + let variantItemsSelection = {} + annotation.body.value.forEach((variantItem) => { + variantItemsSelection[variantItem.witness] = value + } ) + return variantItemsSelection +} export function getAnnotationListElement(id, container) { return [...container.querySelectorAll('.q-item')].find((annotationItem) => {