🎨 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"> <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>