✨ Add JSON viewer component for displaying game master data
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="fileContent"
|
||||
class="content-viewer"
|
||||
:class="{
|
||||
'dark-mode': preferences.darkMode,
|
||||
'line-wrap': preferences.lineWrap
|
||||
}"
|
||||
>
|
||||
<RecycleScroller
|
||||
v-if="displayLines.length > 1000"
|
||||
ref="virtualScroller"
|
||||
class="scroller"
|
||||
:items="displayLines"
|
||||
:item-size="lineHeight"
|
||||
key-field="lineNumber"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div
|
||||
:data-line="item.lineNumber"
|
||||
:class="[
|
||||
'line',
|
||||
{ selected: selectedLines.has(item.lineNumber) },
|
||||
{ 'highlight-match': item.hasMatch },
|
||||
{ 'current-result': isCurrentResult(item.lineNumber) }
|
||||
]"
|
||||
@click="toggleLineSelection(item.lineNumber, $event)"
|
||||
>
|
||||
<span v-if="preferences.showLineNumbers" class="line-number">
|
||||
{{ item.lineNumber }}
|
||||
</span>
|
||||
<pre v-highlight="highlightConfig"><code>{{ item.content }}</code></pre>
|
||||
</div>
|
||||
</template>
|
||||
</RecycleScroller>
|
||||
|
||||
<div v-else class="lines-container">
|
||||
<div
|
||||
v-for="line in displayLines"
|
||||
:key="line.lineNumber"
|
||||
:data-line="line.lineNumber"
|
||||
:class="[
|
||||
'line',
|
||||
{ selected: selectedLines.has(line.lineNumber) },
|
||||
{ 'highlight-match': line.hasMatch },
|
||||
{ 'current-result': isCurrentResult(line.lineNumber) }
|
||||
]"
|
||||
@click="toggleLineSelection(line.lineNumber, $event)"
|
||||
>
|
||||
<span v-if="preferences.showLineNumbers" class="line-number">
|
||||
{{ line.lineNumber }}
|
||||
</span>
|
||||
<pre v-highlight="highlightConfig"><code>{{ line.content }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="fileTooLarge" class="warning-banner">
|
||||
⚠️ File exceeds 10,000 lines. Showing first 10,000 lines only. Use search
|
||||
or filters to find specific content.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, toRef, ref } from 'vue';
|
||||
import { RecycleScroller } from 'vue-virtual-scroller';
|
||||
import { useLineSelection } from '../../composables/useLineSelection.js';
|
||||
|
||||
const props = defineProps({
|
||||
displayLines: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
fileContent: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selectedFile: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
fileTooLarge: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
preferences: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
darkMode: false,
|
||||
lineWrap: false,
|
||||
showLineNumbers: true
|
||||
})
|
||||
},
|
||||
searchResults: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
currentResultIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
highlightConfig: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
theme: 'github',
|
||||
language: 'json'
|
||||
})
|
||||
},
|
||||
lineHeight: {
|
||||
type: Number,
|
||||
default: 20
|
||||
}
|
||||
});
|
||||
|
||||
const displayLines = toRef(props, 'displayLines');
|
||||
const fileContent = toRef(props, 'fileContent');
|
||||
const selectedFile = toRef(props, 'selectedFile');
|
||||
|
||||
const virtualScroller = ref(null);
|
||||
|
||||
const { selectedLines, toggleLineSelection } = useLineSelection(
|
||||
displayLines,
|
||||
fileContent,
|
||||
selectedFile
|
||||
);
|
||||
|
||||
const isCurrentResult = lineNumber => {
|
||||
if (!props.searchResults.length) return false;
|
||||
const currentLine = props.searchResults[props.currentResultIndex] + 1;
|
||||
return lineNumber === currentLine;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
selectedLines,
|
||||
toggleLineSelection,
|
||||
virtualScroller
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content-viewer {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
background: #fafafa;
|
||||
max-height: 70vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-viewer.dark-mode {
|
||||
background: #1e1e1e;
|
||||
border-color: #333;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.content-viewer.line-wrap pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.scroller,
|
||||
.lines-container {
|
||||
max-height: 65vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 2px 8px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.line.selected {
|
||||
background: rgba(0, 123, 255, 0.15);
|
||||
}
|
||||
|
||||
.line.highlight-match {
|
||||
background: rgba(255, 234, 0, 0.2);
|
||||
}
|
||||
|
||||
.line.current-result {
|
||||
outline: 1px solid #ff9800;
|
||||
}
|
||||
|
||||
.line-number {
|
||||
width: 50px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.warning-banner {
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
background: #fff3cd;
|
||||
border-top: 1px solid #ffeeba;
|
||||
color: #856404;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user