🎨 Improve code readability and maintainability by reformatting and restructuring HTML, CSS, and JavaScript files
This commit is contained in:
@@ -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
|
||||||
</div>
|
v-if="searchQuery"
|
||||||
|
@click="clearSearch"
|
||||||
<div v-if="searchResults.length > 0" class="search-results">
|
class="btn-clear"
|
||||||
<span>{{ currentResultIndex + 1 }} / {{ searchResults.length }}</span>
|
title="Clear search"
|
||||||
<button @click="goToPrevResult" class="btn-nav" :disabled="searchResults.length === 0">↑</button>
|
>
|
||||||
<button @click="goToNextResult" class="btn-nav" :disabled="searchResults.length === 0">↓</button>
|
✕
|
||||||
|
</button>
|
||||||
</div>
|
</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
|
<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;
|
||||||
@@ -361,7 +466,7 @@ async function loadFile() {
|
|||||||
|
|
||||||
fileContent.value = JSON.stringify(data, null, 2);
|
fileContent.value = JSON.stringify(data, null, 2);
|
||||||
fileLines.value = fileContent.value.split('\n');
|
fileLines.value = fileContent.value.split('\n');
|
||||||
|
|
||||||
// Limit to 10K lines
|
// Limit to 10K lines
|
||||||
const linesToDisplay = fileLines.value.slice(0, 10000);
|
const linesToDisplay = fileLines.value.slice(0, 10000);
|
||||||
displayLines.value = linesToDisplay.map((content, index) => ({
|
displayLines.value = linesToDisplay.map((content, index) => ({
|
||||||
@@ -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 = [];
|
||||||
@@ -418,20 +528,23 @@ const onSearchInput = debounce(async () => {
|
|||||||
// Process in chunks
|
// Process in chunks
|
||||||
for (let i = 0; i < displayLines.value.length; i += chunkSize) {
|
for (let i = 0; i < displayLines.value.length; i += chunkSize) {
|
||||||
const chunk = displayLines.value.slice(i, i + chunkSize);
|
const chunk = displayLines.value.slice(i, i + chunkSize);
|
||||||
|
|
||||||
chunk.forEach((line, idx) => {
|
chunk.forEach((line, idx) => {
|
||||||
const actualIndex = i + idx;
|
const actualIndex = i + idx;
|
||||||
const matches = line.content.toLowerCase().includes(searchTerm);
|
const matches = line.content.toLowerCase().includes(searchTerm);
|
||||||
displayLines.value[actualIndex].hasMatch = matches;
|
displayLines.value[actualIndex].hasMatch = matches;
|
||||||
|
|
||||||
if (matches) {
|
if (matches) {
|
||||||
results.push(actualIndex);
|
results.push(actualIndex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 0));
|
await new Promise(resolve => setTimeout(resolve, 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) {
|
||||||
@@ -493,7 +614,7 @@ function toggleLineSelection(lineNumber, event) {
|
|||||||
const lastSelected = Math.max(...selectedLines.value);
|
const lastSelected = Math.max(...selectedLines.value);
|
||||||
const start = Math.min(lastSelected, lineNumber);
|
const start = Math.min(lastSelected, lineNumber);
|
||||||
const end = Math.max(lastSelected, lineNumber);
|
const end = Math.max(lastSelected, lineNumber);
|
||||||
|
|
||||||
for (let i = start; i <= end; i++) {
|
for (let i = start; i <= end; i++) {
|
||||||
selectedLines.value.add(i);
|
selectedLines.value.add(i);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -528,11 +653,15 @@ async function copyAll() {
|
|||||||
|
|
||||||
function exportSelected() {
|
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>
|
||||||
|
|||||||
Reference in New Issue
Block a user