Files
memory-infrastructure-palace/code/websites/pokedex.online/src/components/gamemaster/JsonViewer.vue

329 lines
7.1 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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
},
selectionState: {
type: Object,
default: null
}
});
const displayLines = toRef(props, 'displayLines');
const fileContent = toRef(props, 'fileContent');
const selectedFile = toRef(props, 'selectedFile');
const virtualScroller = ref(null);
const internalSelectionState = useLineSelection(
displayLines,
fileContent,
selectedFile
);
const activeSelectionState = computed(
() => props.selectionState || internalSelectionState
);
const { selectedLines, toggleLineSelection } = activeSelectionState.value;
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: #ffffff;
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;
background: #ffffff;
}
.content-viewer.dark-mode .scroller,
.content-viewer.dark-mode .lines-container {
background: #1e1e1e;
}
.line {
display: flex;
gap: 10px;
padding: 2px 8px;
font-size: 13px;
cursor: pointer;
background: #ffffff !important;
}
.content-viewer.dark-mode .line {
background: #1e1e1e !important;
}
.line.selected {
background: rgba(102, 126, 234, 0.15) !important;
}
.content-viewer.dark-mode .line.selected {
background: rgba(102, 126, 234, 0.25) !important;
}
.line.highlight-match {
background: rgba(255, 234, 0, 0.25) !important;
}
.content-viewer.dark-mode .line.highlight-match {
background: rgba(255, 234, 0, 0.15) !important;
}
.line.current-result {
outline: 2px solid #ff9800;
background: rgba(255, 193, 7, 0.3) !important;
}
.content-viewer.dark-mode .line.current-result {
background: rgba(255, 193, 7, 0.2) !important;
}
.line-number {
width: 50px;
text-align: right;
color: #999;
user-select: none;
flex-shrink: 0;
}
.content-viewer.dark-mode .line-number {
color: #666;
}
/* Force transparent backgrounds on all elements inside lines */
.line * {
background: transparent !important;
}
.line pre {
margin: 0;
background: transparent !important;
color: inherit;
}
.line pre code {
background: transparent !important;
color: inherit;
padding: 0 !important;
}
/* Light mode syntax highlighting */
.content-viewer:not(.dark-mode) .line pre code {
color: #24292e !important;
}
.content-viewer:not(.dark-mode) :deep(.hljs) {
background: transparent !important;
color: #24292e !important;
}
.content-viewer:not(.dark-mode) :deep(.hljs-attr),
.content-viewer:not(.dark-mode) :deep(.hljs-attribute) {
color: #6f42c1 !important;
background: transparent !important;
}
.content-viewer:not(.dark-mode) :deep(.hljs-string) {
color: #032f62 !important;
background: transparent !important;
}
.content-viewer:not(.dark-mode) :deep(.hljs-number) {
color: #005a9c !important;
background: transparent !important;
}
.content-viewer:not(.dark-mode) :deep(.hljs-literal) {
color: #d73a49 !important;
background: transparent !important;
}
/* Dark mode syntax highlighting */
.content-viewer.dark-mode :deep(.hljs) {
background: transparent !important;
color: #e1e4e8 !important;
}
.content-viewer.dark-mode :deep(.hljs-attr),
.content-viewer.dark-mode :deep(.hljs-attribute) {
color: #79b8ff !important;
background: transparent !important;
}
.content-viewer.dark-mode :deep(.hljs-string) {
color: #85e89d !important;
background: transparent !important;
}
.content-viewer.dark-mode :deep(.hljs-number) {
color: #79b8ff !important;
background: transparent !important;
}
.content-viewer.dark-mode :deep(.hljs-literal) {
color: #f97583 !important;
background: transparent !important;
}
.warning-banner {
background: #fff3cd;
border-top: 1px solid #ffc107;
color: #856404;
padding: 12px;
text-align: center;
font-weight: 600;
}
.content-viewer.dark-mode .warning-banner {
background: #3d3400;
color: #ffeb3b;
border-color: #ffc107;
}
.warning-banner {
padding: 8px 10px;
font-size: 13px;
background: #fff3cd;
border-top: 1px solid #ffeeba;
color: #856404;
}
</style>