🎨 Improve code readability and maintainability by reformatting and restructuring HTML, CSS, and JavaScript files

This commit is contained in:
2026-01-28 19:52:32 +00:00
parent 59f0b26e63
commit 0060db14a4

View File

@@ -16,7 +16,9 @@
<div v-else-if="!hasFiles" class="no-files-state">
<h2>No Gamemaster Files Available</h2>
<p>Please process gamemaster data first in the Gamemaster Manager.</p>
<router-link to="/gamemaster" class="btn-primary">Go to Gamemaster Manager</router-link>
<router-link to="/gamemaster" class="btn-primary"
>Go to Gamemaster Manager</router-link
>
</div>
<!-- Main Explorer Interface -->
@@ -25,11 +27,21 @@
<header class="explorer-header">
<h1>🔍 Gamemaster Explorer</h1>
<div class="header-controls">
<button @click="showHelp = !showHelp" class="btn-icon" title="Keyboard Shortcuts (?)">
<button
@click="showHelp = !showHelp"
class="btn-icon"
title="Keyboard Shortcuts (?)"
>
<span v-if="!showHelp">?</span>
<span v-else></span>
</button>
<button @click="showSettings = !showSettings" class="btn-icon" title="Settings"></button>
<button
@click="showSettings = !showSettings"
class="btn-icon"
title="Settings"
>
</button>
</div>
</header>
@@ -40,7 +52,10 @@
<li><kbd>Ctrl</kbd>+<kbd>F</kbd> - Focus search</li>
<li><kbd>Ctrl</kbd>+<kbd>C</kbd> - Copy selected lines</li>
<li><kbd>Ctrl</kbd>+<kbd>G</kbd> - Go to next search result</li>
<li><kbd>Shift</kbd>+<kbd>Ctrl</kbd>+<kbd>G</kbd> - Go to previous result</li>
<li>
<kbd>Shift</kbd>+<kbd>Ctrl</kbd>+<kbd>G</kbd> - Go to previous
result
</li>
<li><kbd>Escape</kbd> - Clear selection / Close dialogs</li>
</ul>
</div>
@@ -76,11 +91,19 @@
<label for="file-select">Select File:</label>
<select id="file-select" v-model="selectedFile" @change="onFileChange">
<option value="">-- Choose a file --</option>
<option v-if="status.files?.pokemonFile" value="pokemon">Pokemon ({{ formatSize(status.files.pokemonFile.size) }})</option>
<option v-if="status.files?.movesFile" value="moves">Moves ({{ formatSize(status.files.movesFile.size) }})</option>
<option v-if="status.files?.rawFile" value="raw">Raw Gamemaster ({{ formatSize(status.files.rawFile.size) }})</option>
<option v-if="status.files?.pokemonFile" value="pokemon">
Pokemon ({{ formatSize(status.files.pokemonFile.size) }})
</option>
<option v-if="status.files?.movesFile" value="moves">
Moves ({{ formatSize(status.files.movesFile.size) }})
</option>
<option v-if="status.files?.rawFile" value="raw">
Raw Gamemaster ({{ formatSize(status.files.rawFile.size) }})
</option>
</select>
<span v-if="fileContent" class="file-info">{{ fileLines.length.toLocaleString() }} lines</span>
<span v-if="fileContent" class="file-info"
>{{ fileLines.length.toLocaleString() }} lines</span
>
</div>
<!-- Search Bar -->
@@ -94,16 +117,38 @@
placeholder="Search in file... (Ctrl+F)"
class="search-input"
/>
<button v-if="searchQuery" @click="clearSearch" class="btn-clear" title="Clear search"></button>
</div>
<div v-if="searchResults.length > 0" class="search-results">
<span>{{ currentResultIndex + 1 }} / {{ searchResults.length }}</span>
<button @click="goToPrevResult" class="btn-nav" :disabled="searchResults.length === 0"></button>
<button @click="goToNextResult" class="btn-nav" :disabled="searchResults.length === 0"></button>
<button
v-if="searchQuery"
@click="clearSearch"
class="btn-clear"
title="Clear search"
>
</button>
</div>
<div v-if="searchHistory.history.value.length > 0" class="search-history">
<div v-if="searchResults.length > 0" class="search-results">
<span>{{ currentResultIndex + 1 }} / {{ searchResults.length }}</span>
<button
@click="goToPrevResult"
class="btn-nav"
:disabled="searchResults.length === 0"
>
</button>
<button
@click="goToNextResult"
class="btn-nav"
:disabled="searchResults.length === 0"
>
</button>
</div>
<div
v-if="searchHistory.history.value.length > 0"
class="search-history"
>
<button
v-for="(item, index) in searchHistory.history.value"
:key="index"
@@ -120,7 +165,9 @@
<label>Filter by Property:</label>
<select v-model="filterProperty" @change="onFilterChange">
<option value="">All Properties</option>
<option v-for="path in jsonPaths" :key="path" :value="path">{{ path }}</option>
<option v-for="path in jsonPaths" :key="path" :value="path">
{{ path }}
</option>
</select>
<input
v-if="filterProperty"
@@ -131,21 +178,46 @@
class="filter-value-input"
/>
<label v-if="filterProperty" class="filter-mode">
<input type="radio" value="OR" v-model="filterMode" @change="onFilterChange" /> OR
<input type="radio" value="AND" v-model="filterMode" @change="onFilterChange" /> AND
<input
type="radio"
value="OR"
v-model="filterMode"
@change="onFilterChange"
/>
OR
<input
type="radio"
value="AND"
v-model="filterMode"
@change="onFilterChange"
/>
AND
</label>
</div>
<!-- Progress Bar (for long operations) -->
<div v-if="operationProgress.active" class="progress-bar-container">
<div class="progress-bar" :class="{ 'complete': operationProgress.complete }">
<div class="progress-fill" :style="{ width: operationProgress.percent + '%' }"></div>
<div
class="progress-bar"
:class="{ complete: operationProgress.complete }"
>
<div
class="progress-fill"
:style="{ width: operationProgress.percent + '%' }"
></div>
</div>
<span class="progress-text">{{ operationProgress.message }}</span>
</div>
<!-- JSON Content Viewer -->
<div v-if="fileContent" class="content-viewer" :class="{ 'dark-mode': preferences.darkMode, 'line-wrap': preferences.lineWrap }">
<div
v-if="fileContent"
class="content-viewer"
:class="{
'dark-mode': preferences.darkMode,
'line-wrap': preferences.lineWrap
}"
>
<!-- Virtual Scroller for large files -->
<RecycleScroller
v-if="displayLines.length > 1000"
@@ -158,13 +230,17 @@
<div
:class="[
'line',
{ 'selected': selectedLines.has(item.lineNumber) },
{ selected: selectedLines.has(item.lineNumber) },
{ 'highlight-match': item.hasMatch }
]"
@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>
<span v-if="preferences.showLineNumbers" class="line-number">{{
item.lineNumber
}}</span>
<pre
v-highlight="highlightConfig"
><code>{{ item.content }}</code></pre>
</div>
</template>
</RecycleScroller>
@@ -176,29 +252,42 @@
:key="line.lineNumber"
:class="[
'line',
{ 'selected': selectedLines.has(line.lineNumber) },
{ selected: selectedLines.has(line.lineNumber) },
{ 'highlight-match': line.hasMatch }
]"
@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>
<span v-if="preferences.showLineNumbers" class="line-number">{{
line.lineNumber
}}</span>
<pre
v-highlight="highlightConfig"
><code>{{ line.content }}</code></pre>
</div>
</div>
<!-- Too Large Warning -->
<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.
File exceeds 10,000 lines. Showing first 10,000 lines only. Use
search or filters to find specific content.
</div>
</div>
<!-- Action Bar -->
<div v-if="fileContent" class="action-bar">
<button @click="copySelected" :disabled="selectedLines.size === 0" class="btn-action">
<button
@click="copySelected"
:disabled="selectedLines.size === 0"
class="btn-action"
>
📋 Copy Selected ({{ selectedLines.size }} lines)
</button>
<button @click="copyAll" class="btn-action">📋 Copy All</button>
<button @click="exportSelected" :disabled="selectedLines.size === 0" class="btn-action">
<button
@click="exportSelected"
:disabled="selectedLines.size === 0"
class="btn-action"
>
💾 Export Selected
</button>
<button @click="exportAll" class="btn-action">💾 Export All</button>
@@ -206,8 +295,12 @@
</div>
<!-- Toast Notifications -->
<div v-if="clipboard.copied.value" class="toast success"> Copied to clipboard!</div>
<div v-if="clipboard.error.value" class="toast error"> {{ clipboard.error.value }}</div>
<div v-if="clipboard.copied.value" class="toast success">
Copied to clipboard!
</div>
<div v-if="clipboard.error.value" class="toast error">
{{ clipboard.error.value }}
</div>
</div>
</div>
</template>
@@ -219,9 +312,21 @@ import { GamemasterClient } from '@/utilities/gamemaster-client.js';
import { useKeyboardShortcuts } from '@/composables/useKeyboardShortcuts.js';
import { useUrlState } from '@/composables/useUrlState.js';
import { useClipboard } from '@/composables/useClipboard.js';
import { useLocalStorage, useSearchHistory } from '@/composables/useLocalStorage.js';
import { perfMonitor, debounce, getDevicePerformance } from '@/utilities/performance-utils.js';
import { extractJsonPaths, extractJsonPathsLazy, truncateMiddle, getValueByPath } from '@/utilities/json-utils.js';
import {
useLocalStorage,
useSearchHistory
} from '@/composables/useLocalStorage.js';
import {
perfMonitor,
debounce,
getDevicePerformance
} from '@/utilities/performance-utils.js';
import {
extractJsonPaths,
extractJsonPathsLazy,
truncateMiddle,
getValueByPath
} from '@/utilities/json-utils.js';
// Core state
const loading = ref(true);
@@ -304,7 +409,7 @@ useKeyboardShortcuts({
'ctrl+c': copySelected,
'ctrl+g': goToNextResult,
'shift+ctrl+g': goToPrevResult,
'escape': () => {
escape: () => {
if (showHelp.value || showSettings.value) {
showHelp.value = false;
showSettings.value = false;
@@ -361,7 +466,7 @@ async function loadFile() {
fileContent.value = JSON.stringify(data, null, 2);
fileLines.value = fileContent.value.split('\n');
// Limit to 10K lines
const linesToDisplay = fileLines.value.slice(0, 10000);
displayLines.value = linesToDisplay.map((content, index) => ({
@@ -372,7 +477,7 @@ async function loadFile() {
// Extract JSON paths for filtering (in background)
jsonPaths.value = extractJsonPaths(data);
extractJsonPathsLazy(data, (paths) => {
extractJsonPathsLazy(data, paths => {
jsonPaths.value = paths;
});
@@ -402,13 +507,18 @@ function onFileChange() {
const onSearchInput = debounce(async () => {
if (!searchQuery.value.trim()) {
searchResults.value = [];
displayLines.value.forEach(line => line.hasMatch = false);
displayLines.value.forEach(line => (line.hasMatch = false));
return;
}
// Show progress for long searches
const searchTerm = searchQuery.value.toLowerCase();
operationProgress.value = { active: true, percent: 0, message: 'Searching...', complete: false };
operationProgress.value = {
active: true,
percent: 0,
message: 'Searching...',
complete: false
};
await perfMonitor('Search', async () => {
const results = [];
@@ -418,20 +528,23 @@ const onSearchInput = debounce(async () => {
// Process in chunks
for (let i = 0; i < displayLines.value.length; i += chunkSize) {
const chunk = displayLines.value.slice(i, i + chunkSize);
chunk.forEach((line, idx) => {
const actualIndex = i + idx;
const matches = line.content.toLowerCase().includes(searchTerm);
displayLines.value[actualIndex].hasMatch = matches;
if (matches) {
results.push(actualIndex);
}
});
// Update progress
operationProgress.value.percent = Math.min(((i + chunkSize) / displayLines.value.length) * 100, 100);
operationProgress.value.percent = Math.min(
((i + chunkSize) / displayLines.value.length) * 100,
100
);
// Yield to browser
if (i % (chunkSize * 3) === 0) {
await new Promise(resolve => setTimeout(resolve, 0));
@@ -455,18 +568,21 @@ const onSearchInput = debounce(async () => {
function clearSearch() {
searchQuery.value = '';
searchResults.value = [];
displayLines.value.forEach(line => line.hasMatch = false);
displayLines.value.forEach(line => (line.hasMatch = false));
}
function goToNextResult() {
if (searchResults.value.length === 0) return;
currentResultIndex.value = (currentResultIndex.value + 1) % searchResults.value.length;
currentResultIndex.value =
(currentResultIndex.value + 1) % searchResults.value.length;
scrollToResult();
}
function goToPrevResult() {
if (searchResults.value.length === 0) return;
currentResultIndex.value = (currentResultIndex.value - 1 + searchResults.value.length) % searchResults.value.length;
currentResultIndex.value =
(currentResultIndex.value - 1 + searchResults.value.length) %
searchResults.value.length;
scrollToResult();
}
@@ -484,7 +600,12 @@ function applyHistoryItem(item) {
function onFilterChange() {
// Implement property filtering
// This is complex - needs to parse JSON and filter based on property paths
console.log('Filter change:', filterProperty.value, filterValue.value, filterMode.value);
console.log(
'Filter change:',
filterProperty.value,
filterValue.value,
filterMode.value
);
}
function toggleLineSelection(lineNumber, event) {
@@ -493,7 +614,7 @@ function toggleLineSelection(lineNumber, event) {
const lastSelected = Math.max(...selectedLines.value);
const start = Math.min(lastSelected, lineNumber);
const end = Math.max(lastSelected, lineNumber);
for (let i = start; i <= end; i++) {
selectedLines.value.add(i);
}
@@ -515,9 +636,13 @@ async function copySelected() {
if (selectedLines.value.size === 0) return;
const lines = [...selectedLines.value].sort((a, b) => a - b);
const content = lines.map(lineNum => {
return displayLines.value.find(l => l.lineNumber === lineNum)?.content || '';
}).join('\n');
const content = lines
.map(lineNum => {
return (
displayLines.value.find(l => l.lineNumber === lineNum)?.content || ''
);
})
.join('\n');
await clipboard.copyToClipboard(content);
}
@@ -528,11 +653,15 @@ async function copyAll() {
function exportSelected() {
if (selectedLines.value.size === 0) return;
const lines = [...selectedLines.value].sort((a, b) => a - b);
const content = lines.map(lineNum => {
return displayLines.value.find(l => l.lineNumber === lineNum)?.content || '';
}).join('\n');
const content = lines
.map(lineNum => {
return (
displayLines.value.find(l => l.lineNumber === lineNum)?.content || ''
);
})
.join('\n');
downloadFile(content, `${selectedFile.value}-selected-${Date.now()}.json`);
}
@@ -563,7 +692,7 @@ function formatSize(bytes) {
const k = 1024;
const sizes = ['B', 'KB', 'MB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
// Lifecycle
@@ -581,7 +710,9 @@ onMounted(() => {
}
/* Loading & Error States */
.loading-state, .error-state, .no-files-state {
.loading-state,
.error-state,
.no-files-state {
display: flex;
flex-direction: column;
align-items: center;
@@ -601,8 +732,12 @@ onMounted(() => {
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Header */
@@ -643,7 +778,8 @@ onMounted(() => {
}
/* Help & Settings Panels */
.help-panel, .settings-panel {
.help-panel,
.settings-panel {
background: #f9f9f9;
border: 1px solid #e0e0e0;
border-radius: 8px;
@@ -651,7 +787,8 @@ onMounted(() => {
margin-bottom: 1rem;
}
.help-panel h3, .settings-panel h3 {
.help-panel h3,
.settings-panel h3 {
margin-top: 0;
}
@@ -785,7 +922,8 @@ onMounted(() => {
flex-wrap: wrap;
}
.property-filter select, .filter-value-input {
.property-filter select,
.filter-value-input {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
@@ -821,8 +959,13 @@ onMounted(() => {
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.progress-text {
@@ -978,7 +1121,8 @@ onMounted(() => {
font-size: 1.25rem;
}
.file-selector, .property-filter {
.file-selector,
.property-filter {
flex-direction: column;
align-items: stretch;
}
@@ -993,7 +1137,8 @@ onMounted(() => {
}
/* Primary button styles */
.btn-primary, .btn-retry {
.btn-primary,
.btn-retry {
display: inline-block;
padding: 0.75rem 1.5rem;
background: #3498db;
@@ -1006,7 +1151,8 @@ onMounted(() => {
min-height: 44px;
}
.btn-primary:hover, .btn-retry:hover {
.btn-primary:hover,
.btn-retry:hover {
background: #2980b9;
}
</style>