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

Add Tool Tip for Bread Crumbs in Comparison View #1717

Merged
merged 12 commits into from
May 3, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,20 @@ private Match convertMatchToReportMatch(JPlagComparison comparison, de.jplag.Mat

int startLineFirst = startOfFirst.getLine();
int startColumnFirst = startOfFirst.getColumn();
int startTokenFirst = match.startOfFirst();
int endLineFirst = endOfFirst.getLine();
int endColumnFirst = endOfFirst.getColumn() + endOfFirst.getLength() - 1;
int endTokenFirst = match.endOfFirst();

int startLineSecond = startOfSecond.getLine();
int startColumnSecond = startOfSecond.getColumn();
int startTokenSecond = match.startOfSecond();
int endLineSecond = endOfSecond.getLine();
int endColumnSecond = endOfSecond.getColumn() + endOfSecond.getLength() - 1;
int endTokenSecond = match.endOfSecond();

return new Match(firstFileName, secondFileName, startLineFirst, startColumnFirst, endLineFirst, endColumnFirst, startLineSecond,
startColumnSecond, endLineSecond, endColumnSecond, match.length());
return new Match(firstFileName, secondFileName, startLineFirst, startColumnFirst, startTokenFirst, endLineFirst, endColumnFirst,
endTokenFirst, startLineSecond, startColumnSecond, startTokenSecond, endLineSecond, endColumnSecond, endTokenSecond, match.length());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tsaglam Maybe we should think about splitting up the Match class in the future. For now I would keep it as is, so we don't break our API more than necessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was thinking the same, already have made a card for that on the board

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree. We could just do I class for token subsequences, and a match just takes two subsequences.

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.fasterxml.jackson.annotation.JsonProperty;

public record Match(@JsonProperty("file1") String firstFileName, @JsonProperty("file2") String secondFileName,
@JsonProperty("start1") int startInFirst, @JsonProperty("start1_col") int startColumnInFirst, @JsonProperty("end1") int endInFirst,
@JsonProperty("end1_col") int endColumnInFirst, @JsonProperty("start2") int startInSecond,
@JsonProperty("start2_col") int startColumnInSecond, @JsonProperty("end2") int endInSecond, @JsonProperty("end2_col") int endColumnInSecond,
@JsonProperty("start1") int startInFirst, @JsonProperty("start1_col") int startColumnInFirst,
@JsonProperty("startToken1") int startTokenInFirst, @JsonProperty("end1") int endInFirst, @JsonProperty("end1_col") int endColumnInFirst,
@JsonProperty("endToken1") int endTokenInFirst, @JsonProperty("start2") int startInSecond,
@JsonProperty("start2_col") int startColumnInSecond, @JsonProperty("startToken2") int startTokenInSecond,
@JsonProperty("end2") int endInSecond, @JsonProperty("endToken2") int endTokenInSecond, @JsonProperty("end2_col") int endColumnInSecond,
@JsonProperty("tokens") int tokens) {
}
2 changes: 1 addition & 1 deletion report-viewer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JPlag Report Viewer</title>
</head>
<body>
<body style="overflow: hidden">
<noscript>
<strong
>We're sorry but the JPlag Report Viewer does not work properly without JavaScript enabled.
Expand Down
6 changes: 5 additions & 1 deletion report-viewer/src/components/ComparisonsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@
}"
class="flex w-full justify-center text-center"
>
<ToolTipComponent class="w-fit" direction="left">
<ToolTipComponent
class="w-fit"
direction="left"
:tool-tip-container-will-be-centered="true"
>
<template #default>
{{ clusters?.[item.clusterIndex].members?.length }}
<FontAwesomeIcon
Expand Down
117 changes: 69 additions & 48 deletions report-viewer/src/components/ToolTipComponent.vue
Original file line number Diff line number Diff line change
@@ -1,76 +1,97 @@
<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"
<div class="group pointer-events-none inline">
<div ref="contentRef" class="pointer-events-auto"><slot></slot></div>
<span
class="invisible absolute box-border delay-0 group-hover:visible group-hover:delay-200"
ref="tooltipRef"
v-if="$slots.tooltip"
>
<slot name="tooltip"></slot>
<div class="absolute border-4 border-solid" :style="arrowStyle" v-if="$slots.tooltip">
<!-- Arrow -->
</div>
</div>
<span
class="arrowBase pointer-events-auto relative z-10 block rounded-md bg-tooltip px-1 text-center text-white after:absolute after:border-4 after:border-solid after:border-transparent"
:style="tooltipPosition"
:class="{
'after:top-1/2 after:-mt-1': props.direction == 'left' || props.direction == 'right',
'after:!left-1/2 after:-ml-1': props.direction == 'top' || props.direction == 'bottom',
'after:top-full after:!border-t-tooltip': props.direction == 'top',
'after:bottom-full after:!border-b-tooltip': props.direction == 'bottom',
'after:left-full after:!border-l-tooltip': props.direction == 'left',
'after:right-full after:!border-r-tooltip': props.direction == 'right'
}"
>
<slot name="tooltip"></slot>
</span>
</span>
</div>
</template>

<script setup lang="ts">
import type { ToolTipDirection } from '@/model/ui/ToolTip'
import { computed, type PropType, type StyleValue } from 'vue'
import { computed, ref, type PropType, type Ref, type StyleValue } from 'vue'

const props = defineProps({
direction: {
type: String as PropType<ToolTipDirection>,
required: false,
default: 'top'
},
/** Sometimes the absolute div is centered horizontally on the content. Set this to true if that is the case. */
toolTipContainerWillBeCentered: {
type: Boolean,
required: false,
default: false
},
/** Can be set if the tooltip is inside a scrollable container */
scrollOffsetX: {
type: Number,
required: false,
default: 0
},
scrollOffsetY: {
type: Number,
required: false,
default: 0
}
})

const opacity = 0.8
const contentRef: Ref<HTMLElement | null> = ref(null)
const tooltipRef: Ref<HTMLElement | null> = ref(null)
const arrowOffset = 4

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%'
}
const contentDiv = contentRef.value
const tooltipDiv = tooltipRef.value
if (!contentDiv || !tooltipDiv) {
return style
}
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 '
// zeros the tooltip on the topleft of the content
let top = -contentDiv.offsetHeight - props.scrollOffsetY
let left =
(props.toolTipContainerWillBeCentered ? -contentDiv.offsetWidth / 2 : 0) - props.scrollOffsetX
if (props.direction == 'right' || props.direction == 'left') {
top += (contentDiv.offsetHeight - tooltipDiv.offsetHeight) / 2
} else {
left -= (tooltipDiv.offsetWidth - contentDiv.offsetWidth) / 2
}

if (props.direction == 'left' || props.direction == 'right') {
style.top = '50%'
style.marginTop = '-4px'
if (props.direction == 'right') {
left += contentDiv.offsetWidth + arrowOffset
} else if (props.direction == 'left') {
left -= tooltipDiv.offsetWidth + arrowOffset
} else if (props.direction == 'bottom') {
top += contentDiv.offsetHeight + arrowOffset
} else {
style.left = '50%'
style.marginLeft = '-4px'
top -= tooltipDiv.offsetHeight + arrowOffset
}

style[props.direction] = '100%'

style.top = top + 'px'
style.left = left + 'px'
return style
})
</script>

<style scoped>
.arrowBase::after {
content: ' ';
}
</style>
78 changes: 69 additions & 9 deletions report-viewer/src/components/fileDisplaying/MatchList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,48 @@

<div
class="print-excact flex w-full flex-row space-x-1 overflow-x-auto print:flex-wrap print:space-y-1 print:overflow-x-hidden"
ref="scrollableList"
@scroll="updateScrollOffset()"
>
<OptionComponent
<ToolTipComponent
:direction="getTooltipDirection(index)"
v-for="[index, match] in matches?.entries()"
:style="{ background: getMatchColor(0.3, match.colorIndex) }"
v-bind:key="index"
@click="$emit('matchSelected', match)"
:label="
getFileName(match.firstFile) + ' - ' + getFileName(match.secondFile) + ': ' + match.tokens
"
/>
:key="index"
:scrollOffsetX="scrollOffsetX"
>
<template #default>
<OptionComponent
:style="{ background: getMatchColor(0.3, match.colorIndex) }"
@click="$emit('matchSelected', match)"
:label="
getFileName(match.firstFile) +
' - ' +
getFileName(match.secondFile) +
': ' +
match.tokens
"
/>
</template>
<template #tooltip>
<p class="whitespace-pre text-sm">
Match between {{ getFileName(match.firstFile) }} (Line {{ match.startInFirst.line }}-{{
match.endInFirst.line
}}) and {{ getFileName(match.secondFile) }} (Line {{ match.startInSecond.line }}-{{
match.endInSecond.line
}}) <br />
Match is {{ match.tokens }} tokens long. <br />
<span v-if="showTokenRanges(match)">
Token indeces of match: {{ match.startInFirst.tokenListIndex }}-{{
match.endInFirst.tokenListIndex
}}
and {{ match.startInSecond.tokenListIndex }}-{{ match.endInSecond.tokenListIndex }}.
<br />
</span>

Click to show in code view.
</p>
</template>
</ToolTipComponent>
</div>
</div>

Expand Down Expand Up @@ -59,8 +91,10 @@ import type { Match } from '@/model/Match'
import OptionComponent from '../optionsSelectors/OptionComponent.vue'
import ToolTipComponent from '@/components/ToolTipComponent.vue'
import { getMatchColor } from '@/utils/ColorUtils'
import type { ToolTipDirection } from '@/model/ui/ToolTip'
import { ref, type Ref } from 'vue'

defineProps({
const props = defineProps({
/**
* Matches of the comparison.
* type: Array<Match>
Expand All @@ -87,4 +121,30 @@ defineEmits(['matchSelected'])
function getFileName(fullPath: string) {
return fullPath.split(/[/\\]/g).pop() || ''
}

function getTooltipDirection(index: number): ToolTipDirection {
if (index == 0) return 'right'
if (index >= 2 && index + 2 >= (props.matches?.length ?? Infinity)) {
return 'left'
}
return 'bottom'
}

function showTokenRanges(match: Match) {
return (
!isNaN(match.startInFirst.tokenListIndex) &&
!isNaN(match.startInSecond.tokenListIndex) &&
!isNaN(match.endInFirst.tokenListIndex) &&
!isNaN(match.endInSecond.tokenListIndex)
)
}

const scrollableList: Ref<HTMLElement | null> = ref(null)
const scrollOffsetX = ref(0)

function updateScrollOffset() {
if (scrollableList.value) {
scrollOffsetX.value = scrollableList.value.scrollLeft
}
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
{{ title }}
</div>
<div v-for="[index, label] in labels.entries()" :key="index">
<ToolTipComponent v-if="(label as ToolTipLabel).displayValue !== undefined" direction="right">
<ToolTipComponent
v-if="(label as ToolTipLabel).displayValue !== undefined"
direction="right"
:tool-tip-container-will-be-centered="true"
>
<template #default>
<OptionComponent
:label="(label as ToolTipLabel).displayValue"
Expand Down
18 changes: 10 additions & 8 deletions report-viewer/src/model/Match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@
export interface Match {
firstFile: string
secondFile: string
startInFirst: number
startColumnInFirst: number
endInFirst: number
endColumnInFirst: number
startInSecond: number
startColumnInSecond: number
endInSecond: number
endColumnInSecond: number
startInFirst: CodePosition
endInFirst: CodePosition
startInSecond: CodePosition
endInSecond: CodePosition
tokens: number
colorIndex?: number
}

export interface CodePosition {
line: number
column: number
tokenListIndex: number
}
16 changes: 8 additions & 8 deletions report-viewer/src/model/MatchInSingleFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ export class MatchInSingleFile {
*/
get start(): number {
if (this._index === 1) {
return this._match.startInFirst
return this._match.startInFirst.line
} else {
return this._match.startInSecond
return this._match.startInSecond.line
}
}

Expand All @@ -35,25 +35,25 @@ export class MatchInSingleFile {
*/
get end(): number {
if (this._index === 1) {
return this._match.endInFirst
return this._match.endInFirst.line
} else {
return this._match.endInSecond
return this._match.endInSecond.line
}
}

get startColumn(): number {
if (this._index === 1) {
return this._match.startColumnInFirst
return this._match.startInFirst.column
} else {
return this._match.startColumnInSecond
return this._match.startInSecond.column
}
}

get endColumn(): number {
if (this._index === 1) {
return this._match.endColumnInFirst
return this._match.endInFirst.column
} else {
return this._match.endColumnInSecond
return this._match.endInSecond.column
}
}
}
Loading