Skip to content

Commit 74d5b8d

Browse files
authored
Merge pull request #1275 from jplag/report-viewer/rework-codePanel
Rework Code displaying
2 parents 88c94f2 + a669436 commit 74d5b8d

16 files changed

+376
-439
lines changed

report-viewer/src/components/CodePanel.vue

+87-129
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,42 @@
22
Panel which displays a submission files with its line of code.
33
-->
44
<template>
5-
<Interactable
6-
:id="
7-
panelId
8-
?.toString()
9-
.concat(filePath || '')
10-
.concat(fileIndex?.toString() || '-1')
11-
"
12-
class="mx-2 !shadow"
13-
>
14-
<div @click="$emit('toggleCollapse')" class="text-center font-bold">
15-
{{ title }}
5+
<Interactable class="mx-2 !shadow">
6+
<div @click="collapsed = !collapsed" class="text-center font-bold">
7+
{{ getFileDisplayName(file) }}
168
</div>
9+
1710
<div class="mx-1 overflow-x-auto">
18-
<div :class="{ hidden: !collapse }" class="w-fit min-w-full">
19-
<div v-if="!isEmpty(lines)" class="flex w-full flex-col items-start p-0">
20-
<div
21-
class="flex w-full flex-row"
22-
v-for="(line, index) in lines"
23-
:id="
24-
String(panelId)
25-
.concat(filePath || '')
26-
.concat((index + 1).toString())
27-
"
11+
<div v-if="!collapsed" class="w-fit min-w-full !text-xs">
12+
<table
13+
v-if="file.data.trim() !== ''"
14+
class="w-full"
15+
:aria-describedby="`Content of file ${file.fileName}`"
16+
>
17+
<!-- One row in table per code line -->
18+
<tr
19+
v-for="(line, index) in codeLines"
2820
:key="index"
21+
class="w-full cursor-default"
22+
:class="{ 'cursor-pointer': line.match !== null }"
23+
@click="lineSelected(index)"
2924
>
30-
<LineOfCode
31-
class="flex-grow"
32-
:color="coloringArray[index]"
33-
:line-number="index + 1"
34-
:text="line"
35-
:visible="collapse"
36-
@click="
37-
$emit(
38-
'lineSelected',
39-
$event,
40-
linksArray[index].panel,
41-
linksArray[index].file,
42-
linksArray[index].line
43-
)
44-
"
45-
/>
46-
</div>
47-
</div>
25+
<!-- Line number -->
26+
<td class="float-right pr-3">{{ index + 1 }}</td>
27+
<!-- Code line -->
28+
<td
29+
class="w-full"
30+
:style="{
31+
background: line.match !== null ? line.match.color : 'hsla(0, 0%, 0%, 0)'
32+
}"
33+
>
34+
<pre v-html="line.line" class="code-font !bg-transparent" ref="lineRefs"></pre>
35+
</td>
36+
</tr>
37+
</table>
38+
4839
<div v-else class="flex flex-col items-start overflow-x-auto">
49-
<p>Empty File</p>
40+
<i>Empty File</i>
5041
</div>
5142
</div>
5243
</div>
@@ -55,120 +46,87 @@
5546

5647
<script setup lang="ts">
5748
import type { MatchInSingleFile } from '@/model/MatchInSingleFile'
58-
import { computed, ref, type Ref } from 'vue'
59-
import LineOfCode from '@/components/LineOfCode.vue'
49+
import { ref, nextTick, type PropType, computed, type Ref } from 'vue'
6050
import Interactable from './InteractableComponent.vue'
51+
import type { Match } from '@/model/Match'
52+
import type { SubmissionFile } from '@/stores/state'
53+
import { highlight } from '@/utils/CodeHighlighter'
54+
import type { HighlightLanguage } from '@/model/Language'
6155

6256
const props = defineProps({
63-
/**
64-
* Path of the displayed file.
65-
*/
66-
filePath: {
67-
type: String
68-
},
69-
/**
70-
* Title of the displayed file.
71-
*/
72-
title: {
73-
type: String
74-
},
75-
/**
76-
* Index of file amongst other files in submission.
77-
*/
78-
fileIndex: {
79-
type: Number
80-
},
8157
/**
8258
* Code lines of the file.
83-
* type: Array<string>
8459
*/
85-
lines: {
86-
type: Array<string>,
60+
file: {
61+
type: Object as PropType<SubmissionFile>,
8762
required: true
8863
},
8964
/**
9065
* Matches in the file
91-
* type: Array<MatchInSingleFile>
9266
*/
9367
matches: {
94-
type: Array<MatchInSingleFile>
95-
},
96-
/**
97-
* Id of the FilesContainer. Needed for lines link generation.
98-
*/
99-
panelId: {
100-
type: Number
68+
type: Array<MatchInSingleFile>,
69+
required: true
10170
},
10271
/**
103-
* Indicates whether files is collapsed or not.
72+
* Language of the file.
10473
*/
105-
collapse: {
106-
type: Boolean
74+
highlightLanguage: {
75+
type: String as PropType<HighlightLanguage>,
76+
required: true
10777
}
10878
})
10979

110-
defineEmits(['lineSelected', 'toggleCollapse'])
80+
const emit = defineEmits(['lineSelected'])
11181

112-
/**
113-
* An object containing the color of each line in code. Keys are line numbers, values are their color.
114-
* Example: {
115-
* ...
116-
* 100 : "#3333"
117-
* 101 : "#3333"
118-
* 102 : "#3333"
119-
* 103 : "#FFFF"
120-
* ...
121-
* }
122-
*/
123-
const coloringArray: Ref<Record<number, string>> = ref({})
82+
const collapsed = ref(true)
83+
const lineRefs = ref<HTMLElement[]>([])
12484

125-
/**
126-
* @param lines Array of lines to check.
127-
* @returns true if all lines are empty, false otherwise.
128-
*/
129-
function isEmpty(lines: string[]) {
130-
return lines.length === 0 || lines.every((line) => !line.trim())
85+
const codeLines: Ref<{ line: string; match: null | Match }[]> = computed(() =>
86+
highlight(props.file.data, props.highlightLanguage).map((line, index) => {
87+
return {
88+
line,
89+
match: props.matches?.find((m) => m.start <= index + 1 && index + 1 <= m.end)?.match ?? null
90+
}
91+
})
92+
)
93+
94+
function lineSelected(lineIndex: number) {
95+
if (codeLines.value[lineIndex].match !== null) {
96+
emit('lineSelected', codeLines.value[lineIndex].match)
97+
}
13198
}
13299

133100
/**
134-
* An object containing an object from which an id is to of the line to which this is linked is constructed.
135-
* Id object contains panel, file name, first line number of linked matched.
136-
* Example: {
137-
* panel: 1,
138-
* file: "Example.java",
139-
* line: 121
140-
* }
141-
* Constructed ID (generateLineCodeLink from Utils.ts): 1Example.java121
142-
* When a line is clicked it uses this link id
143-
* to scroll into vie the linked line in the linked file of the other submission.
144-
* Key is line number, value is id of linked line.
101+
* Scrolls to the line number in the file.
102+
* @param lineNumber line number in the file
145103
*/
146-
const linksArray: Ref<Record<number, { panel?: number; file?: string; line?: number }>> = computed(
147-
() => {
148-
const links: Record<number, { panel?: number; file?: string; line?: number }> = {}
149-
150-
props.matches?.forEach((m) => {
151-
for (let i = m.start; i <= m.end; i++) {
152-
//assign match color to line
153-
coloringArray.value[i - 1] = m.color
154-
//assign link object to line.
155-
linksArray.value[i - 1] = {
156-
panel: m.linked_panel,
157-
file: m.linked_file,
158-
line: m.linked_line
159-
}
160-
}
161-
})
104+
function scrollTo(lineNumber: number) {
105+
collapsed.value = false
106+
nextTick(function () {
107+
lineRefs.value[lineNumber - 1].scrollIntoView({ block: 'center' })
108+
})
109+
}
162110

163-
return links
164-
}
165-
)
111+
defineExpose({
112+
scrollTo
113+
})
166114

167-
//assign default values for all line which are not contained in matches
168-
for (let i = 0; i < props.lines.length; i++) {
169-
if (!coloringArray.value[i]) {
170-
coloringArray.value[i] = 'hsla(0, 0%, 0%, 0)'
171-
linksArray.value[i] = {}
172-
}
115+
/**
116+
* converts the submissionId to the name in the path of file. If the length of path exceeds 40, then the file path displays the abbreviation.
117+
* @param file submission file
118+
* @return new path of file
119+
*/
120+
function getFileDisplayName(file: SubmissionFile): string {
121+
const filePathLength = file.fileName.length
122+
return filePathLength > 40
123+
? '...' + file.fileName.substring(filePathLength - 40, filePathLength)
124+
: file.fileName
173125
}
174126
</script>
127+
128+
<style scoped>
129+
.code-font {
130+
font-family: 'JetBrains Mono NL', monospace !important;
131+
}
132+
</style>

0 commit comments

Comments
 (0)