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

View File

@@ -90,120 +90,17 @@
</div> </div>
<!-- File Selector --> <!-- File Selector -->
<div class="file-selector"> <FileSelector :files-state="filesState" />
<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>
<!-- Search Bar --> <SearchBar
<div v-if="fileContent" class="search-bar"> v-if="fileContent"
<div class="search-input-wrapper"> :file-lines="fileLines"
<input :display-lines="displayLines"
ref="searchInput" :search-state="searchState"
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>
<!-- Property Filter --> <!-- Property Filter -->
<div v-if="fileContent && jsonPaths.length > 0" class="property-filter"> <FilterPanel v-if="fileContent" :data="filterData" :filter-state="filterState" />
<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>
<!-- 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">
@@ -220,100 +117,28 @@
</div> </div>
<!-- JSON Content Viewer --> <!-- JSON Content Viewer -->
<div <JsonViewer
v-if="fileContent" v-if="fileContent"
class="content-viewer" :display-lines="displayLines"
:class="{ :file-content="fileContent"
'dark-mode': preferences.darkMode, :selected-file="selectedFile"
'line-wrap': preferences.lineWrap :file-too-large="fileTooLarge"
}" :preferences="preferences"
> :search-results="searchResults"
<!-- Virtual Scroller for large files --> :current-result-index="currentResultIndex"
<RecycleScroller :highlight-config="highlightConfig"
v-if="displayLines.length > 1000" :line-height="lineHeight"
ref="virtualScroller" :selection-state="selectionState"
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>
<!-- Action Bar --> <!-- Action Bar -->
<div v-if="fileContent" class="action-bar"> <ActionToolbar
<button v-if="fileContent"
@click="copySelected" :display-lines="displayLines"
:disabled="selectedLines.size === 0" :file-content="fileContent"
class="btn-action" :selected-file="selectedFile"
> :selection-state="selectionState"
📋 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>
<!-- Toast Notifications --> <!-- Toast Notifications -->
<div v-if="clipboard.copied.value" class="toast success"> <div v-if="clipboard.copied.value" class="toast success">