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

Tooltips in report viewer #1292

Merged
merged 17 commits into from
Sep 18, 2023
56 changes: 39 additions & 17 deletions report-viewer/src/components/ComparisonsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,27 @@
<div class="tableCellSimilarity !flex-col">
<div>Similarity</div>
<div class="flex w-full flex-row">
<div class="flex-1">Average</div>
<div class="flex-1">Maximum</div>
<ToolTipComponent class="flex-1" :direction="displayClusters ? 'top' : 'left'">
<template #default>
<p class="w-full text-center">{{ metricToolTips[MetricType.AVERAGE].shortName }}</p>
</template>
<template #tooltip>
<p class="whitespace-pre text-sm">
{{ metricToolTips[MetricType.AVERAGE].tooltip }}
</p>
</template>
</ToolTipComponent>

<ToolTipComponent class="flex-1" :direction="displayClusters ? 'top' : 'left'">
<template #default>
<p class="w-full text-center">{{ metricToolTips[MetricType.MAXIMUM].shortName }}</p>
</template>
<template #tooltip>
<p class="whitespace-pre text-sm">
{{ metricToolTips[MetricType.MAXIMUM].tooltip }}
</p>
</template>
</ToolTipComponent>
</div>
</div>
<div class="tableCellCluster items-center" v-if="displayClusters">Cluster</div>
Expand Down Expand Up @@ -102,21 +121,23 @@
}"
class="tect-center flex w-full justify-center"
>
<div class="group relative w-fit">
{{ clusters?.[index].members?.length }}
<FontAwesomeIcon
:icon="['fas', 'user-group']"
:style="{ color: clusterIconColors[index] }"
/>
{{ ((clusters?.[index].averageSimilarity as number) * 100).toFixed(2) }}%
<div
class="tooltipArrow absolute left-[-400px] top-0 z-50 hidden h-full items-center rounded-sm bg-gray-950 bg-opacity-90 px-2 text-sm text-white group-hover:flex"
>
{{ clusters?.[index].members?.length }} submissions in cluster with average
similarity of
<ToolTipComponent class="w-fit" direction="left">
<template #default>
{{ clusters?.[index].members?.length }}
<FontAwesomeIcon
:icon="['fas', 'user-group']"
:style="{ color: clusterIconColors[index] }"
/>
{{ ((clusters?.[index].averageSimilarity as number) * 100).toFixed(2) }}%
</div>
</div>
</template>
<template #tooltip>
<p class="whitespace-nowrap text-sm">
{{ clusters?.[index].members?.length }} submissions in cluster with average
similarity of
{{ ((clusters?.[index].averageSimilarity as number) * 100).toFixed(2) }}%
</p>
</template>
</ToolTipComponent>
</RouterLink>
</div>
</div>
Expand All @@ -137,7 +158,8 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faUserGroup } from '@fortawesome/free-solid-svg-icons'
import { generateColors } from '@/utils/ColorUtils'
import { MetricType } from '@/model/MetricType'
import ToolTipComponent from './ToolTipComponent.vue'
import { MetricType, metricToolTips } from '@/model/MetricType'

library.add(faUserGroup)

Expand Down
25 changes: 16 additions & 9 deletions report-viewer/src/components/MatchList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,33 @@
-->
<template>
<div class="flex h-fit min-w-0 max-w-full flex-row space-x-1 overflow-x-hidden text-xs">
<Interactable class="my-2 flex h-6 items-center whitespace-nowrap !rounded-2xl text-center">
Match Files: TokenCount
</Interactable>
<ToolTipComponent direction="right">
<template #default>
<OptionComponent label="Match Files: TokenCount" />
</template>
<template #tooltip>
<p class="whitespace-pre text-sm">Click on a match to show it in the code view.</p>
</template>
</ToolTipComponent>

<div class="flex w-full flex-row space-x-1 overflow-x-auto">
<Interactable
class="my-2 flex h-6 items-center whitespace-nowrap !rounded-2xl !bg-opacity-50 text-center"
<OptionComponent
:style="{ background: match.color }"
v-for="[index, match] in matches?.entries()"
v-bind:key="index"
@click="$emit('matchSelected', match)"
>
{{ getFileName(match.firstFile) }} - {{ getFileName(match.secondFile) }}: {{ match.tokens }}
</Interactable>
:label="
getFileName(match.firstFile) + ' - ' + getFileName(match.secondFile) + ': ' + match.tokens
"
/>
</div>
</div>
</template>

<script setup lang="ts">
import type { Match } from '@/model/Match'
import Interactable from './InteractableComponent.vue'
import OptionComponent from './optionsSelectors/OptionComponent.vue'
import ToolTipComponent from './ToolTipComponent.vue'

defineProps({
/**
Expand Down
56 changes: 0 additions & 56 deletions report-viewer/src/components/OptionsSelectorComponent.vue

This file was deleted.

73 changes: 73 additions & 0 deletions report-viewer/src/components/ToolTipComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<template>
<div class="group relative inline-block">
<slot></slot>
<div
class="invisible absolute z-10 rounded-md px-1 text-center text-white delay-0 group-hover:visible group-hover:delay-200"
:style="tooltipPosition"
>
<slot name="tooltip"></slot>
<div class="absolute border-4 border-solid" :style="arrowStyle"><!-- Arrow --></div>
</div>
</div>
</template>

<script setup lang="ts">
import { computed, type PropType, type StyleValue } from 'vue'
const props = defineProps({
direction: {
type: String as PropType<'top' | 'bottom' | 'left' | 'right'>,
required: false,
default: 'top'
}
})
const opacity = 0.8
const tooltipPosition = computed(() => {
const style: StyleValue = {}
if (props.direction == 'left' || props.direction == 'right') {
style.top = '50%'
style.transform = 'translateY(-50%)'
if (props.direction == 'left') {
style.right = '105%'
} else {
style.left = '105%'
}
} else {
style.left = '50%'
style.transform = 'translateX(-50%)'
if (props.direction == 'top') {
style.bottom = '105%'
} else {
style.top = '105%'
}
}
style.backgroundColor = `rgba(0,0,0,${opacity})`
return style
})
const arrowStyle = computed(() => {
const style: StyleValue = {}
style.content = ' '
style.borderColor = ''
for (const dir of ['top', 'right', 'bottom', 'left']) {
style.borderColor += dir == props.direction ? `rgba(0,0,0,${opacity}) ` : 'transparent '
}
if (props.direction == 'left' || props.direction == 'right') {
style.top = '50%'
style.marginTop = '-4px'
} else {
style.left = '50%'
style.marginLeft = '-4px'
}
style[props.direction] = '100%'
return style
})
</script>
40 changes: 40 additions & 0 deletions report-viewer/src/components/optionsSelectors/MetricSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<template>
<OptionsSelectorComponent
:title="title"
:labels="labels"
@selection-changed="(i) => $emit('selectionChanged', metrics[i])"
/>
</template>

<script setup lang="ts">
import { computed, type PropType, type Ref } from 'vue'
import OptionsSelectorComponent from './OptionsSelectorComponent.vue'
import { MetricType, metricToolTips } from '@/model/MetricType'
const props = defineProps({
metrics: {
type: Array<MetricType>,
required: false,
default: [MetricType.AVERAGE, MetricType.MAXIMUM]
},
title: {
type: String,
required: false,
default: ''
},
defaultSelection: {
type: String as PropType<MetricType>,
required: false,
default: MetricType.AVERAGE
}
})
defineEmits(['selectionChanged'])
const labels: Ref<{ displayValue: string; tooltip: string }[]> = computed(() =>
props.metrics.map((metric) => ({
displayValue: metricToolTips[metric].longName,
tooltip: metricToolTips[metric].tooltip
}))
)
</script>
27 changes: 27 additions & 0 deletions report-viewer/src/components/optionsSelectors/OptionComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<template>
<Interactable
class="mr-2 box-border flex h-6 w-fit items-center justify-center whitespace-nowrap !rounded-2xl px-[12px] text-center hover:!border-[2px] hover:px-[11px]"
:class="{ '!border-accent-dark !bg-accent !bg-opacity-40': selected }"
@click="$emit('click')"
>
{{ label }}
</Interactable>
</template>

<script setup lang="ts">
import Interactable from '../InteractableComponent.vue'
defineProps({
label: {
type: String,
required: true
},
selected: {
type: Boolean,
required: false,
default: false
}
})
defineEmits(['click'])
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!--
Component for selecting one of multiple options.
-->
<template>
<div class="flex h-fit flex-row items-center text-center text-xs">
<div v-if="title != ''" class="mr-3 text-base">
{{ title }}
</div>
<div v-for="[index, label] in labels.entries()" :key="index">
<ToolTipComponent v-if="(label as ToolTipLabel).displayValue !== undefined" direction="right">
<template #default>
<OptionComponent
:label="(label as ToolTipLabel).displayValue"
:selected="index == getSelected()"
@click="select(index)"
/>
</template>

<template #tooltip>
<p class="whitespace-pre text-sm">
{{ (label as ToolTipLabel).tooltip }}
</p>
</template>
</ToolTipComponent>
<OptionComponent
v-else
:label="label as string"
:selected="index == getSelected()"
@click="select(index)"
/>
</div>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import ToolTipComponent from '../ToolTipComponent.vue'
import OptionComponent from './OptionComponent.vue'

type ToolTipLabel = {
displayValue: string
tooltip: string
}

const props = defineProps({
title: {
type: String,
required: false,
default: ''
},
labels: {
type: Array<string | ToolTipLabel>,
required: true
},
defaultSelected: {
type: Number,
required: false,
default: 0
}
})

const emit = defineEmits(['selectionChanged'])
const selected = ref(-1)

function getSelected() {
if (selected.value == -1) {
return props.defaultSelected
}
return selected.value
}

function select(index: number) {
emit('selectionChanged', index)
selected.value = index
}
</script>
Loading