Skip to content

Commit

Permalink
feat: implement selecting/deselecting an annotated text (#460)
Browse files Browse the repository at this point in the history
* feat: create an initial version of 'selecting text'

* refactor: commented out the first approach for this task

* refactor: remove the first method of 'select text'

* feat: deal with coloring of the witnesses chip

* feat: show witnesses when clicking on text

* feat: remove witnesses chips when deselecting text

* chore: minor

* refactor: minor

* docs: reason why we use pickVariantItemsSelection()

* style: rename initVariantItemsSelection variable

---------

Co-authored-by: malkja <[email protected]>
  • Loading branch information
2 people authored and paulpestov committed Aug 21, 2024
1 parent 031d827 commit 9c203e3
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 28 deletions.
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

0 comments on commit 9c203e3

Please sign in to comment.