Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Selecting/Deselecting an annotated text #460

Merged
merged 10 commits into from
Aug 12, 2024
120 changes: 97 additions & 23 deletions src/components/annotations/AnnotationVariantItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
>
<div
class="t-relative t-rounded-3xl t-box-border t-w-75 t-h-8 t-border-2 t-p-[2px]"
:style="{'border-color': getItemColorBasedOnIndex(i)}"
:style="{'border-color': allocateWitnessColorInVariantItem(variant.witness)}"
>
<span
v-if="variant.witness"
Expand All @@ -35,10 +35,15 @@

<script setup lang="ts">
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,
Expand All @@ -51,75 +56,144 @@ const props = withDefaults(defineProps<Props>(), {
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
})
return isClicked
}


function isOnlyThisVariantActive(witness) {
const variantItemsSelection = pickVariantItemsSelection()

let isOnlyThisVariantClicked = true
Object.keys(variantItemsSelection).forEach((wit) => {
if (variantItemsSelection[wit] === true && wit!== witness) isOnlyThisVariantClicked = false
})
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
}


Expand Down
43 changes: 39 additions & 4 deletions src/stores/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import { useConfigStore} from '@/stores/config';
export const useAnnotationsStore = defineStore('annotations', () => {

const activeTab = ref<string>('')
const activeAnnotations = ref<ActiveAnnotation>({} as ActiveAnnotation)
const activeAnnotations = ref({})
const activeAnnotSelectVariantItems = ref({})
const variantItemsColors = ref({})
const annotations = ref<Annotation[]>(null)
const filteredAnnotations = ref<Annotation[]>([])
const isLoading = ref<boolean>(false);
Expand All @@ -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()
Expand Down Expand Up @@ -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 };

Expand Down Expand Up @@ -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)
}
}
}
});
Expand Down Expand Up @@ -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
Expand Down
48 changes: 47 additions & 1 deletion src/utils/annotations.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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")) {
Expand Down Expand Up @@ -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
Expand All @@ -299,6 +301,50 @@ function getWitnessesHtmlEl(selector) {
return witnessesHtmlEl
}

export function getWitnessesList(witnessesHtml) {
// returns the list of witnesses(<string>) 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) => {
Expand Down