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