From 12ce2dc9545fd7a1981360271511ba83f3514494 Mon Sep 17 00:00:00 2001 From: Julien Touchais <5978-julien.touchais@users.noreply.forgemia.inra.fr> Date: Tue, 23 Apr 2024 10:29:17 +0200 Subject: [PATCH 1/9] fix(target-view): :adhesive_bandage: use good modification colors --- src/components/SequenceBoard.vue | 12 ++++++++---- src/views/TargetView.vue | 31 +++++++++++++------------------ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/components/SequenceBoard.vue b/src/components/SequenceBoard.vue index ef96ffd..d0a03bf 100644 --- a/src/components/SequenceBoard.vue +++ b/src/components/SequenceBoard.vue @@ -41,6 +41,10 @@ export interface highlightGroupModel { end: number /** Color(s) to use to highlight the group. */ color: TailwindDefaultColorNameModel + /** Group name. */ + name?: string + /** Group type. */ + type?: string } /** @@ -149,12 +153,12 @@ const isInAnyHighlightedGroup = (position: number): boolean => * Set of all the boundary positions (start & end) of all highlighted groups on * the sequence. */ -const highlightedGroupsBoundaries = computed( +const highlightedGroupsBoundaryPositions = computed( () => props.highlightedGroups && Object.values(props.highlightedGroups).reduce( - (highlightedGroupsBoundaries, highlightedGroup) => - highlightedGroupsBoundaries + (highlightedGroupsBoundaryPositions, highlightedGroup) => + highlightedGroupsBoundaryPositions .add(highlightedGroup.start) .add(highlightedGroup.end), new Set<number>() @@ -168,7 +172,7 @@ const highlightedGroupsBoundaries = computed( * @returns `true` if the nucleotide is the start of an highlighted group, `false` otherwise. */ const isHighlightedGroupBoundary = (position: number): boolean => - !!highlightedGroupsBoundaries.value?.has(position) + !!highlightedGroupsBoundaryPositions.value?.has(position) /** * Composes the Tailwind color name in which to paint a nucleotide based on its diff --git a/src/views/TargetView.vue b/src/views/TargetView.vue index a0d5414..dbbc451 100644 --- a/src/views/TargetView.vue +++ b/src/views/TargetView.vue @@ -45,6 +45,7 @@ import { FeatureType, Strand } from '@/gql/codegen/graphql' import { formatSpeciesName, separateThousands } from '@/utils/textFormatting' import { targetByIdQuery } from '@/gql/queries' import { isDefined } from '@/typings/typeUtils' +import { getModificationColor } from '@/utils/colors' /** * Utility constant to get the icon component corresponding to each strand value @@ -230,23 +231,13 @@ const interactionList = computed<InteractionCardModel[] | undefined>(() => { }) /** - * Positions of the modifications, when known (only modifications when positive - * modification position) + * Target modifications, filtered by keeping only ones with positive position */ -const modificationPositions = computed(() => +const filteredModifications = computed(() => _uniq( target.value?.modifications.filter( (modification) => modification.position >= 0 ) - ).reduce( - ( - modificationPositions: { [modificationId: string]: number }, - modification - ) => ({ - ...modificationPositions, - [modification.id]: modification.position - }), - {} ) ) @@ -254,16 +245,20 @@ const modificationPositions = computed(() => * Sequence chunks to highlight on the sequence of the target */ const sequenceChunks = computed(() => - Object.entries(modificationPositions.value).reduce( + filteredModifications.value.reduce( ( sequenceChunks: { [groupId: string]: highlightGroupModel }, - [modificationId, modificationPosition] + modification ) => ({ ...sequenceChunks, - [modificationId]: { - start: modificationPosition, - end: modificationPosition, - color: 'slate' as TailwindDefaultColorNameModel + [modification.id]: { + start: modification.position, + end: modification.position, + color: modification.type + ? getModificationColor(modification.type) + : ('slate' as TailwindDefaultColorNameModel), + name: modification.name, + type: modification.type || undefined } }), {} -- GitLab From 73ebfcc5c743f379b5ec100c6dd69a3a002e7df8 Mon Sep 17 00:00:00 2001 From: Julien Touchais <5978-julien.touchais@users.noreply.forgemia.inra.fr> Date: Wed, 24 Apr 2024 09:37:09 +0200 Subject: [PATCH 2/9] feat(base-components): :sparkles: add a 'longFormat' version for FormattedModificationType --- src/components/FormattedModificationType.vue | 8 +++++--- src/views/GuideView.vue | 5 ++++- src/views/TargetView.vue | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/FormattedModificationType.vue b/src/components/FormattedModificationType.vue index b3f2574..c5fe663 100644 --- a/src/components/FormattedModificationType.vue +++ b/src/components/FormattedModificationType.vue @@ -5,14 +5,16 @@ import { ModifType } from '@/gql/codegen/graphql' defineProps<{ - /** The code of the modification type to format */ - typeCode?: ModifType + /** The code of the modification type to format. */ + typeCode: ModifType + /** If to use a long format or a short (default) one. */ + longFormat?: boolean }>() </script> <template> <span v-if="typeCode === ModifType.TwoPOMe" - >2'-<em class="italic">O</em>-me</span + >2'-<em class="italic">O</em>-{{ longFormat ? 'methylation' : 'me' }}</span > <span v-else-if="typeCode === ModifType.Psi">Pseudouridylation</span> <span v-else><em>Unknown type</em></span> diff --git a/src/views/GuideView.vue b/src/views/GuideView.vue index 5287bf4..f4952a8 100644 --- a/src/views/GuideView.vue +++ b/src/views/GuideView.vue @@ -664,7 +664,10 @@ const ontologyLinks = computed(() => ({ <template #item-label-2="{ currentValue }"> <strong>{{ currentValue.modification.name }}</strong> - <em class="italic text-slate-400"> + <em + v-if="currentValue.modification.type" + class="italic text-slate-400" + > {{ ' - ' }} <FormattedModificationType :type-code="currentValue.modification.type" diff --git a/src/views/TargetView.vue b/src/views/TargetView.vue index dbbc451..7ae33c2 100644 --- a/src/views/TargetView.vue +++ b/src/views/TargetView.vue @@ -600,7 +600,7 @@ const ontologyLinks = computed(() => ({ > <template #item-label-1="{ currentValue }"> <strong>{{ currentValue.modification.name }}</strong> - <em class="italic text-slate-400"> + <em v-if="currentValue.modification.type" class="italic text-slate-400"> {{ ' - ' }} <FormattedModificationType :type-code="currentValue.modification.type" -- GitLab From 60257b52d82787830fd536285acc53bc182b3952 Mon Sep 17 00:00:00 2001 From: Julien Touchais <5978-julien.touchais@users.noreply.forgemia.inra.fr> Date: Wed, 24 Apr 2024 09:48:40 +0200 Subject: [PATCH 3/9] feat(linear-sequence): :sparkles: use a Prime's ToolBar for controls --- src/components/SequenceBoard.vue | 95 +++++++++++++++++--------------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/src/components/SequenceBoard.vue b/src/components/SequenceBoard.vue index d0a03bf..04276e9 100644 --- a/src/components/SequenceBoard.vue +++ b/src/components/SequenceBoard.vue @@ -8,6 +8,7 @@ import { ref, computed, onMounted, watch } from 'vue' */ import Dropdown from 'primevue/dropdown' import Button from 'primevue/button' +import Toolbar from 'primevue/toolbar' import IconFa6SolidDna from '~icons/fa6-solid/dna' import IconFa6SolidCircleCheck from '~icons/fa6-solid/circle-check' import IconFa6RegularCopy from '~icons/fa6-regular/copy' @@ -535,51 +536,57 @@ watch(isSequenceContainerElementVisible, (visibility) => { </script> <template> - <div class="controls mb-4 flex items-center justify-between"> - <label class="flex flex-col items-start text-sm"> - Group length - <Dropdown - v-model="nucleotideGroupsSize" - :options="[5, 10, 20, 50, 100]" - class="mt-2 w-52" - @hide="updateAndRedraw" - > - <template #option="{ option }">{{ option }} nucleotides</template> - <template #value="{ value }">{{ value }} nucleotides</template> - </Dropdown> - </label> - <Button - outlined - severity="secondary" - :class="[ - clipboardState === ClipboardStateModel.PostCopy && - '!bg-lime-100 !text-lime-700', - 'flex min-w-[18ch] justify-center' - ]" - :loading="clipboardState === ClipboardStateModel.Busy" - @click="sequenceToClipboard" - > - <span - v-if="clipboardState === ClipboardStateModel.Busy" - class="mr-2 h-5 overflow-hidden text-xl" + <Toolbar> + <template #start> + <label class="flex flex-col items-start text-sm"> + Group length + <Dropdown + v-model="nucleotideGroupsSize" + :options="[5, 10, 20, 50, 100]" + class="mt-2 w-52" + @hide="updateAndRedraw" + > + <template #option="{ option }">{{ option }} nucleotides</template> + <template #value="{ value }">{{ value }} nucleotides</template> + </Dropdown> + </label> + </template> + + <template #end> + <Button + outlined + severity="secondary" + :class="[ + clipboardState === ClipboardStateModel.PostCopy && + '!bg-lime-100 !text-lime-700', + 'flex min-w-[18ch] justify-center' + ]" + :loading="clipboardState === ClipboardStateModel.Busy" + @click="sequenceToClipboard" > - <icon-fa6-solid-dna class="animate-[dnaSpin_.5s_linear_infinite]" /> - <icon-fa6-solid-dna class="animate-[dnaSpin_.5s_linear_infinite]" /> - </span> - <icon-fa6-solid-circle-check - v-else-if="clipboardState === ClipboardStateModel.PostCopy" - class="mr-2 text-xl" - /> - <icon-fa6-regular-copy v-else class="mr-2 text-xl" /> - {{ - clipboardState === ClipboardStateModel.Busy - ? 'Copying...' - : clipboardState === ClipboardStateModel.PostCopy - ? 'Copied !' - : 'Copy sequence' - }} - </Button> - </div> + <span + v-if="clipboardState === ClipboardStateModel.Busy" + class="mr-2 h-5 overflow-hidden text-xl" + > + <icon-fa6-solid-dna class="animate-[dnaSpin_.5s_linear_infinite]" /> + <icon-fa6-solid-dna class="animate-[dnaSpin_.5s_linear_infinite]" /> + </span> + <icon-fa6-solid-circle-check + v-else-if="clipboardState === ClipboardStateModel.PostCopy" + class="mr-2 text-xl" + /> + <icon-fa6-regular-copy v-else class="mr-2 text-xl" /> + {{ + clipboardState === ClipboardStateModel.Busy + ? 'Copying...' + : clipboardState === ClipboardStateModel.PostCopy + ? 'Copied !' + : 'Copy sequence' + }} + </Button> + </template> + </Toolbar> + <div ref="sequenceAndLabelsContainerElement" class="relative px-8"> <span class="absolute left-0 top-8 italic text-slate-400">5' →</span> <div -- GitLab From fc7681a5b8257cf1c0730be70cc7a5870e1e83d9 Mon Sep 17 00:00:00 2001 From: Julien Touchais <5978-julien.touchais@users.noreply.forgemia.inra.fr> Date: Wed, 24 Apr 2024 09:49:13 +0200 Subject: [PATCH 4/9] feat(base-components): :sparkles: add a component to display captions --- src/components/BaseLegendButtonOverlay.vue | 142 +++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/components/BaseLegendButtonOverlay.vue diff --git a/src/components/BaseLegendButtonOverlay.vue b/src/components/BaseLegendButtonOverlay.vue new file mode 100644 index 0000000..69ac0b8 --- /dev/null +++ b/src/components/BaseLegendButtonOverlay.vue @@ -0,0 +1,142 @@ +<script setup lang="ts"> +/** + * Vue imports + */ +import { ref } from 'vue' +/** + * Component imports + */ +import Button from 'primevue/button' +import OverlayPanel from 'primevue/overlaypanel' +import IconCarbonLegend from '~icons/carbon/legend' +/** + * Types imports + */ +import type { TailwindDefaultColorNameModel } from '@/typings/styleTypes' + +/** + * An legend item. + */ +export interface LegendItemModel { + /** Item id. */ + id: string + /** Item title. */ + title: string + /** Item description. */ + description?: string + /** Tailwind color name of the color to legend. */ + color?: TailwindDefaultColorNameModel +} + +/** + * Component props. + */ +defineProps<{ + /** Text to display on the button which reveals the legend. */ + buttonText: string + /** The legend items. */ + items: LegendItemModel[] + /** The opacity to apply on the legend overlay. */ + opacity?: number +}>() + +/** + * Component slots. + */ +defineSlots<{ + /** Custom button template. */ + button: () => any + /** Custom whole legend template. */ + legend: () => any + /** Custom whole legend items template. */ + item: (props: { + /** The item to customise. */ + item: LegendItemModel + }) => any + /** Custom legend items icon template (what is being captioned). */ + 'item-icon': (props: { + /** The item of which to customise the icon. */ + item: LegendItemModel + }) => any + /** Custom legend items title template. */ + 'item-title': (props: { + /** The item of which to customise the title. */ item: LegendItemModel + }) => any + /** Custom legend items title template. */ + 'item-description': (props: { + /** The item of which to customise the description. */ item: LegendItemModel + }) => any +}>() + +/** + * Template ref to the OverlayPanel component containing the legend. + */ +const legendOverlayPanelComponent = ref<OverlayPanel>() + +/** + * Toggles the visibility of the legend overlay. + * @param event The event triggering the function call (used to retrieve the DOM + * element to which link the overlay — probably a button). + */ +const toggleLegend = (event: Event) => { + legendOverlayPanelComponent.value?.toggle(event) +} +</script> + +<template> + <slot name="button"> + <Button + severity="secondary" + outlined + class="flex gap-2 !shadow-none" + @click="toggleLegend" + > + <icon-carbon-legend class="text-xl" /> + {{ buttonText }} + </Button> + </slot> + + <OverlayPanel + ref="legendOverlayPanelComponent" + :style="{ opacity: opacity && 0 <= opacity && opacity <= 1 ? opacity : 1 }" + > + <slot name="legend"> + <ul class="flex max-w-lg flex-col gap-3"> + <li + v-for="(legendItem, index) in items" + :key="index" + class="items-top flex gap-3" + > + <slot name="item" :item="legendItem"> + <slot name="item-icon" :item="legendItem"> + <svg + class="min-w-max" + viewBox="0 0 32 32" + width="1.25em" + height="1.25em" + > + <circle + :class="`fill-${legendItem.color}-100 stroke-${legendItem.color}-600`" + cx="16" + cy="16" + r="14" + stroke-width="4" + /> + </svg> + </slot> + <div class="-mt-0.5 flex flex-col italic text-slate-400"> + <slot name="item-title" :item="legendItem"> + <em class="font-bold not-italic text-slate-600"> + {{ legendItem.title }} + </em> + </slot> + <slot name="item-description" :item="legendItem"> + {{ legendItem.description }} + </slot> + </div> + </slot> + </li> + </ul> + </slot> + </OverlayPanel> +</template> -- GitLab From b7e113bade9bafb8d0f5cbbbe98f0682b2d0e3dc Mon Sep 17 00:00:00 2001 From: Julien Touchais <5978-julien.touchais@users.noreply.forgemia.inra.fr> Date: Wed, 24 Apr 2024 09:50:04 +0200 Subject: [PATCH 5/9] feat(linear-sequence): :sparkles: add optional captions --- src/components/SequenceBoard.vue | 47 +++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/components/SequenceBoard.vue b/src/components/SequenceBoard.vue index 04276e9..5583f97 100644 --- a/src/components/SequenceBoard.vue +++ b/src/components/SequenceBoard.vue @@ -6,6 +6,7 @@ import { ref, computed, onMounted, watch } from 'vue' /** * Components imports */ +import BaseLegendButtonOverlay from '@/components/BaseLegendButtonOverlay.vue' import Dropdown from 'primevue/dropdown' import Button from 'primevue/button' import Toolbar from 'primevue/toolbar' @@ -25,7 +26,8 @@ import { inRange as _inRange } from 'lodash-es' /** * Types imports */ -import { type TailwindDefaultColorNameModel } from '@/typings/styleTypes' +import type { TailwindDefaultColorNameModel } from '@/typings/styleTypes' +import type { LegendItemModel } from '@/components/BaseLegendButtonOverlay.vue' /** * Utils imports */ @@ -68,6 +70,32 @@ const props = defineProps<{ sequence: string /** Groups to highlight on the sequence. */ highlightedGroups?: { [groupId: string]: highlightGroupModel } + /** The items of the legend to display for the highlighted groups. */ + legendItems?: LegendItemModel[] +}>() + +/** + * Component slots. + */ +defineSlots<{ + /** Custom whole legend items template. */ + 'legend-item': (props: { + /** The item to customise. */ + item: LegendItemModel + }) => any + /** Custom legend items icon template (what is being captioned). */ + 'legend-item-icon': (props: { + /** The item of which to customise the icon. */ + item: LegendItemModel + }) => any + /** Custom legend items title template. */ + 'legend-item-title': (props: { + /** The item of which to customise the title. */ item: LegendItemModel + }) => any + /** Custom legend items title template. */ + 'legend-item-description': (props: { + /** The item of which to customise the description. */ item: LegendItemModel + }) => any }>() /** @@ -552,6 +580,23 @@ watch(isSequenceContainerElementVisible, (visibility) => { </label> </template> + <template v-if="legendItems" #center> + <BaseLegendButtonOverlay button-text="Legend" :items="legendItems"> + <template #item="{ item }"> + <slot name="legend-item" :item="item" /> + </template> + <template #item-icon="{ item }"> + <slot name="legend-item-icon" :item="item" /> + </template> + <template #item-title="{ item }"> + <slot name="legend-item-title" :item="item" /> + </template> + <template #item-description="{ item }"> + <slot name="legend-item-description" :item="item" /> + </template> + </BaseLegendButtonOverlay> + </template> + <template #end> <Button outlined -- GitLab From fd4ffd9e27b9a7dcd7c008c64c7ac5d6f3fd6dfe Mon Sep 17 00:00:00 2001 From: Julien Touchais <5978-julien.touchais@users.noreply.forgemia.inra.fr> Date: Wed, 24 Apr 2024 09:50:46 +0200 Subject: [PATCH 6/9] feat(guide-view): :sparkles: add captions to linear sequence on guide view --- src/views/GuideView.vue | 45 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/views/GuideView.vue b/src/views/GuideView.vue index f4952a8..4cab84f 100644 --- a/src/views/GuideView.vue +++ b/src/views/GuideView.vue @@ -36,7 +36,13 @@ import { useTitle } from '@vueuse/core' import type { TailwindDefaultColorNameModel } from '@/typings/styleTypes' import type { InteractionCardModel } from '@/components/InteractionCard.vue' import type { highlightGroupModel } from '@/components/SequenceBoard.vue' -import { FeatureType, GuideType, Strand } from '@/gql/codegen/graphql' +import { + FeatureType, + GuideType, + SequenceType, + Strand +} from '@/gql/codegen/graphql' +import type { LegendItemModel } from '@/components/BaseLegendButtonOverlay.vue' /** * Utils imports */ @@ -59,6 +65,30 @@ const STRAND_CODE_TO_ICON_COMPONENT = { [Strand.NotStranded]: IconFa6SolidCircleXmark } +/** + * The legend to display on the sequence board. + */ +const GUIDE_LEGEND_ITEMS: LegendItemModel[] = [ + { + id: 'CBox', + title: 'C box', + color: getBoxColor(SequenceType.CBox) + }, + { + id: 'CBox', + title: 'D box', + color: getBoxColor(SequenceType.DBox) + }, + { + id: 'ModFacing', + title: 'Facing the modification', + description: + 'In the duplex, the nucleotide which is in front of the modified one on\ + the target.', + color: 'slate' + } +] + /** * Component props. */ @@ -628,7 +658,18 @@ const ontologyLinks = computed(() => ({ v-if="guide?.seq" :sequence="guide.seq.replace(/T/g, 'U')" :highlighted-groups="sequenceChunks" - /> + :legend-items="GUIDE_LEGEND_ITEMS" + > + <template #legend-item-title="{ item }"> + <span + v-if="item.id === 'CBox' || item.id === 'DBox'" + class="font-bold not-italic text-slate-600" + > + <span class="italic">{{ item.title[0] }}</span + >{{ item.title.slice(1) }} + </span> + </template> + </SequenceBoard> </Panel> <Panel -- GitLab From 4dc1e4a0d59e4953098f2c25706d500e46a81001 Mon Sep 17 00:00:00 2001 From: Julien Touchais <5978-julien.touchais@users.noreply.forgemia.inra.fr> Date: Wed, 24 Apr 2024 09:51:08 +0200 Subject: [PATCH 7/9] feat(target-view): :sparkles: add captions to linear sequence --- src/views/TargetView.vue | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/views/TargetView.vue b/src/views/TargetView.vue index 7ae33c2..b93eadb 100644 --- a/src/views/TargetView.vue +++ b/src/views/TargetView.vue @@ -38,7 +38,7 @@ import type { TailwindDefaultColorNameModel } from '@/typings/styleTypes' import type { InteractionCardModel } from '@/components/InteractionCard.vue' import type { C4GGraphModel, C4GNodeModel } from '@/typings/Codev4GraphFormat' import type { highlightGroupModel } from '@/components/SequenceBoard.vue' -import { FeatureType, Strand } from '@/gql/codegen/graphql' +import { FeatureType, ModifType, Strand } from '@/gql/codegen/graphql' /** * Utils imports */ @@ -46,6 +46,7 @@ import { formatSpeciesName, separateThousands } from '@/utils/textFormatting' import { targetByIdQuery } from '@/gql/queries' import { isDefined } from '@/typings/typeUtils' import { getModificationColor } from '@/utils/colors' +import type { LegendItemModel } from '@/components/BaseLegendButtonOverlay.vue' /** * Utility constant to get the icon component corresponding to each strand value @@ -57,6 +58,28 @@ const STRAND_CODE_TO_ICON_COMPONENT = { [Strand.NotStranded]: IconFa6SolidCircleXmark } +/** + * The legend to display on the sequence board. + */ +const TARGET_LEGEND_ITEMS: LegendItemModel[] = [ + { + id: 'Nm', + title: 'Nm', + color: getModificationColor(ModifType.TwoPOMe) + }, + { + id: 'Psi', + title: 'Pseudouridylation', + color: getModificationColor(ModifType.Psi) + }, + { + id: 'Other', + title: 'Unknown', + description: 'Modification of a different type than those listed above.', + color: getModificationColor(ModifType.Other) + } +] + /** * Component props */ @@ -571,7 +594,17 @@ const ontologyLinks = computed(() => ({ v-if="target?.seq" :sequence="target.seq.replace(/T/g, 'U')" :highlighted-groups="sequenceChunks" - /> + :legend-items="TARGET_LEGEND_ITEMS" + > + <template #legend-item-title="{ item }"> + <FormattedModificationType + v-if="item.id === 'Nm'" + class="font-bold not-italic text-slate-600" + :type-code="ModifType.TwoPOMe" + long-format + /> + </template> + </SequenceBoard> </Panel> <Panel -- GitLab From 11c928251707aba1b3a0b9073ad9b63651e10991 Mon Sep 17 00:00:00 2001 From: Julien Touchais <5978-julien.touchais@users.noreply.forgemia.inra.fr> Date: Wed, 24 Apr 2024 10:16:19 +0200 Subject: [PATCH 8/9] docs(config): :bulb: add BaseLegendButtonOverlay to Tailwind whitelist --- tailwind.config.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tailwind.config.js b/tailwind.config.js index 092303f..0e42ba7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -40,11 +40,15 @@ export default { variants: ['hover'] }, { - // InteractionCardCD, InteractionCardHACA - pattern: /^fill-[a-z]+-(1|6)00$/ + // InteractionCardCD, InteractionCardHACA, BaseLegendButtonOverlay + pattern: /^fill-[a-z]+-100$/ }, { // InteractionCardCD, InteractionCardHACA + pattern: /^fill-[a-z]+-600$/ + }, + { + // InteractionCardCD, InteractionCardHACA, BaseLegendButtonOverlay pattern: /^stroke-[a-z]+-600$/ }, 'brightness-90', // ChromosomeMagnify -- GitLab From b8191950785593b65f73fc0bd870378815247216 Mon Sep 17 00:00:00 2001 From: Julien Touchais <5978-julien.touchais@users.noreply.forgemia.inra.fr> Date: Wed, 24 Apr 2024 12:58:51 +0200 Subject: [PATCH 9/9] feat(2D-struct): :sparkles: add captions --- src/components/BaseLegendButtonOverlay.vue | 109 +++++++++++---------- src/components/SecondaryStructure.vue | 60 +++++++++++- 2 files changed, 115 insertions(+), 54 deletions(-) diff --git a/src/components/BaseLegendButtonOverlay.vue b/src/components/BaseLegendButtonOverlay.vue index 69ac0b8..346c550 100644 --- a/src/components/BaseLegendButtonOverlay.vue +++ b/src/components/BaseLegendButtonOverlay.vue @@ -84,59 +84,62 @@ const toggleLegend = (event: Event) => { </script> <template> - <slot name="button"> - <Button - severity="secondary" - outlined - class="flex gap-2 !shadow-none" - @click="toggleLegend" + <div> + <slot name="button"> + <Button + severity="secondary" + outlined + class="flex gap-2 !shadow-none" + @click="toggleLegend" + > + <icon-carbon-legend class="text-xl" /> + {{ buttonText }} + </Button> + </slot> + <OverlayPanel + ref="legendOverlayPanelComponent" + :style="{ + opacity: opacity && 0 <= opacity && opacity <= 1 ? opacity : 1 + }" > - <icon-carbon-legend class="text-xl" /> - {{ buttonText }} - </Button> - </slot> - - <OverlayPanel - ref="legendOverlayPanelComponent" - :style="{ opacity: opacity && 0 <= opacity && opacity <= 1 ? opacity : 1 }" - > - <slot name="legend"> - <ul class="flex max-w-lg flex-col gap-3"> - <li - v-for="(legendItem, index) in items" - :key="index" - class="items-top flex gap-3" - > - <slot name="item" :item="legendItem"> - <slot name="item-icon" :item="legendItem"> - <svg - class="min-w-max" - viewBox="0 0 32 32" - width="1.25em" - height="1.25em" - > - <circle - :class="`fill-${legendItem.color}-100 stroke-${legendItem.color}-600`" - cx="16" - cy="16" - r="14" - stroke-width="4" - /> - </svg> - </slot> - <div class="-mt-0.5 flex flex-col italic text-slate-400"> - <slot name="item-title" :item="legendItem"> - <em class="font-bold not-italic text-slate-600"> - {{ legendItem.title }} - </em> + <slot name="legend"> + <ul class="flex max-w-lg flex-col gap-3"> + <li + v-for="(legendItem, index) in items" + :key="index" + class="items-top flex gap-3" + > + <slot name="item" :item="legendItem"> + <slot name="item-icon" :item="legendItem"> + <svg + class="min-w-max" + viewBox="0 0 32 32" + width="1.25em" + height="1.25em" + > + <circle + :class="`fill-${legendItem.color}-100 stroke-${legendItem.color}-600`" + cx="16" + cy="16" + r="14" + stroke-width="4" + /> + </svg> </slot> - <slot name="item-description" :item="legendItem"> - {{ legendItem.description }} - </slot> - </div> - </slot> - </li> - </ul> - </slot> - </OverlayPanel> + <div class="-mt-0.5 flex flex-col italic text-slate-400"> + <slot name="item-title" :item="legendItem"> + <em class="font-bold not-italic text-slate-600"> + {{ legendItem.title }} + </em> + </slot> + <slot name="item-description" :item="legendItem"> + {{ legendItem.description }} + </slot> + </div> + </slot> + </li> + </ul> + </slot> + </OverlayPanel> + </div> </template> diff --git a/src/components/SecondaryStructure.vue b/src/components/SecondaryStructure.vue index 364952f..5f81c83 100644 --- a/src/components/SecondaryStructure.vue +++ b/src/components/SecondaryStructure.vue @@ -6,7 +6,9 @@ import { ref, onMounted, computed } from 'vue' /** * Components imports */ -import BaseDescribedChip from './BaseDescribedChip.vue' +import BaseDescribedChip from '@/components/BaseDescribedChip.vue' +import BaseLegendButtonOverlay from '@/components/BaseLegendButtonOverlay.vue' +import FormattedModificationType from '@/components/FormattedModificationType.vue' import Button from 'primevue/button' import SelectButton from 'primevue/selectbutton' import Dialog from 'primevue/dialog' @@ -37,6 +39,7 @@ import type { DataUrlType as G6DataUrlType } from '@antv/g6' import { ModifType } from '@/gql/codegen/graphql' +import type { LegendItemModel } from './BaseLegendButtonOverlay.vue' /** * Utils imports */ @@ -78,6 +81,28 @@ const GRAPH_CAPTURE_OPTIONS = [ */ const GRAPH_DOWNLOAD_FORMAT: G6DataUrlType = 'image/png' +/** + * The legend to display on the sequence board. + */ +const SECONDARY_STRUCTURE_LEGEND_ITEMS: LegendItemModel[] = [ + { + id: 'Nm', + title: 'Nm', + color: getModificationColor(ModifType.TwoPOMe) + }, + { + id: 'Psi', + title: 'Pseudouridylation', + color: getModificationColor(ModifType.Psi) + }, + { + id: 'Other', + title: 'Unknown', + description: 'Modification of a different type than those listed above.', + color: getModificationColor(ModifType.Other) + } +] + /** * Applies modifications to the node object based on the metadata of the * modification of the nucleotide represented by that node, and return a node @@ -589,6 +614,39 @@ onMounted(() => { <icon-fluent-scan-camera28-regular class="mr-2 text-3xl" /> Capture </Button> + <BaseLegendButtonOverlay + button-text="Legend" + :items="SECONDARY_STRUCTURE_LEGEND_ITEMS" + class="!absolute left-4 top-4" + > + <template #item-icon="{ item }"> + <svg + class="min-w-max" + viewBox="0 0 32 32" + width="1.25em" + height="1.25em" + > + <rect + :class="`fill-${item.color}-100 stroke-${item.color}-600`" + x="2" + y="2" + width="24" + height="24" + rx="4" + stroke-width="4" + /> + </svg> + </template> + + <template #item-title="{ item }"> + <FormattedModificationType + v-if="item.id === 'Nm'" + class="font-bold not-italic text-slate-600" + :type-code="ModifType.TwoPOMe" + long-format + /> + </template> + </BaseLegendButtonOverlay> </div> <p v-if="graphMetadata.software?.name" -- GitLab