🎨 Refactor components to use modular structure and computed properties for improved maintainability and reusability in Gamemaster Explorer and FilterPanel

This commit is contained in:
2026-01-29 04:02:13 +00:00
parent 6cce9ba646
commit 4c50f296fc
2 changed files with 35 additions and 203 deletions

View File

@@ -64,9 +64,16 @@ const props = defineProps({
data: {
type: Array,
default: () => []
},
filterState: {
type: Object,
default: null
}
});
const internalFilterState = useJsonFilter();
const activeFilterState = computed(() => props.filterState || internalFilterState);
const {
filterProperty,
filterValue,
@@ -79,7 +86,7 @@ const {
setFilter,
clearFilters,
getUniqueValues
} = useJsonFilter();
} = activeFilterState.value;
const hasData = computed(
() => Array.isArray(props.data) && props.data.length > 0

View File

@@ -90,120 +90,17 @@
</div>
<!-- File Selector -->
<div class="file-selector">
<label for="file-select">Select File:</label>
<select id="file-select" v-model="selectedFile" @change="onFileChange">
<option value="">-- Choose a file --</option>
<option
v-for="file in uniqueFiles"
:key="file.filename"
:value="getFileType(file.filename)"
>
{{ formatFileName(file.filename) }} ({{ formatSize(file.size) }})
</option>
</select>
<span v-if="fileContent" class="file-info"
>{{ fileLines.length.toLocaleString() }} lines</span
>
</div>
<FileSelector :files-state="filesState" />
<!-- Search Bar -->
<div v-if="fileContent" class="search-bar">
<div class="search-input-wrapper">
<input
ref="searchInput"
type="text"
v-model="searchQuery"
@input="onSearchInput"
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 :title="`Line ${searchResults[currentResultIndex] + 1}`">
{{ currentResultIndex + 1 }} / {{ searchResults.length }} (Line
{{ searchResults[currentResultIndex] + 1 }})
</span>
<button
@click="goToPrevResult"
class="btn-nav"
:disabled="searchResults.length === 0"
title="Previous result (Shift+Ctrl+G)"
>
</button>
<button
@click="goToNextResult"
class="btn-nav"
:disabled="searchResults.length === 0"
title="Next result (Ctrl+G)"
>
</button>
</div>
<div
v-if="searchHistory.history.value.length > 0"
class="search-history"
>
<button
v-for="(item, index) in searchHistory.history.value"
:key="index"
@click="applyHistoryItem(item)"
class="history-item"
>
{{ item }}
</button>
</div>
</div>
<SearchBar
v-if="fileContent"
:file-lines="fileLines"
:display-lines="displayLines"
:search-state="searchState"
/>
<!-- Property Filter -->
<div v-if="fileContent && jsonPaths.length > 0" class="property-filter">
<label for="property-select">Filter by Property:</label>
<select
id="property-select"
v-model="filterProperty"
@change="onFilterChange"
>
<option value="">All Properties</option>
<option v-for="path in jsonPaths" :key="path" :value="path">
{{ path }}
</option>
</select>
<input
v-if="filterProperty"
type="text"
v-model="filterValue"
@input="onFilterChange"
placeholder="Enter value..."
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
</label>
</div>
<FilterPanel v-if="fileContent" :data="filterData" :filter-state="filterState" />
<!-- Progress Bar (for long operations) -->
<div v-if="operationProgress.active" class="progress-bar-container">
@@ -220,100 +117,28 @@
</div>
<!-- JSON Content Viewer -->
<div
<JsonViewer
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"
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':
item.lineNumber === searchResults[currentResultIndex] + 1
}
]"
@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>
<!-- Regular render for smaller files -->
<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':
line.lineNumber === searchResults[currentResultIndex] + 1
}
]"
@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>
<!-- 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.
</div>
</div>
:display-lines="displayLines"
:file-content="fileContent"
:selected-file="selectedFile"
:file-too-large="fileTooLarge"
:preferences="preferences"
:search-results="searchResults"
:current-result-index="currentResultIndex"
:highlight-config="highlightConfig"
:line-height="lineHeight"
:selection-state="selectionState"
/>
<!-- Action Bar -->
<div v-if="fileContent" class="action-bar">
<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"
>
💾 Export Selected
</button>
<button @click="exportAll" class="btn-action">💾 Export All</button>
<button @click="shareUrl" class="btn-action">🔗 Share Link</button>
</div>
<ActionToolbar
v-if="fileContent"
:display-lines="displayLines"
:file-content="fileContent"
:selected-file="selectedFile"
:selection-state="selectionState"
/>
<!-- Toast Notifications -->
<div v-if="clipboard.copied.value" class="toast success">