Add filtering functionality to the gamemaster panel

This commit is contained in:
2026-01-29 03:56:35 +00:00
parent 92d60f9cfc
commit 139c58cdae

View File

@@ -0,0 +1,159 @@
<template>
<div v-if="hasData" class="filter-panel">
<div class="filter-row">
<label for="property-select">Filter by Property:</label>
<select
id="property-select"
v-model="filterProperty"
@change="applyFilter"
>
<option value="">All Properties</option>
<option v-for="path in availablePaths" :key="path.path" :value="path.path">
{{ path.breadcrumb }}
</option>
</select>
</div>
<div v-if="filterProperty" class="filter-row">
<label for="filter-mode">Mode:</label>
<select id="filter-mode" v-model="filterMode" @change="applyFilter">
<option value="equals">Equals</option>
<option value="contains">Contains</option>
<option value="regex">Regex</option>
</select>
<label for="filter-value" class="filter-value-label">Value:</label>
<input
id="filter-value"
v-model="filterValue"
@input="applyFilter"
:list="valueListId"
type="text"
placeholder="Enter value..."
class="filter-value-input"
/>
<datalist :id="valueListId">
<option v-for="value in valueSuggestions" :key="value" :value="value" />
</datalist>
<button class="btn-clear" @click="clearFilters" title="Clear filter">
Clear
</button>
</div>
<div class="filter-stats">
<span>{{ filterStats.matched }} / {{ filterStats.total }} matched</span>
<span v-if="filterStats.total > 0">({{ filterStats.percentage }}%)</span>
</div>
<div v-if="filterError" class="filter-error">
{{ filterError }}
</div>
</div>
</template>
<script setup>
import { computed, watch } from 'vue';
import useJsonFilter from '../../composables/useJsonFilter.js';
const props = defineProps({
data: {
type: Array,
default: () => []
}
});
const {
filterProperty,
filterValue,
filterMode,
availablePaths,
filterError,
filterStats,
initializeFilter,
extractPathsLazy,
setFilter,
clearFilters,
getUniqueValues
} = useJsonFilter();
const hasData = computed(() => Array.isArray(props.data) && props.data.length > 0);
const valueSuggestions = computed(() => {
if (!filterProperty.value) return [];
return getUniqueValues(filterProperty.value).slice(0, 50);
});
const valueListId = computed(() => `filter-values-${filterProperty.value || 'all'}`);
function applyFilter() {
setFilter(filterProperty.value, filterValue.value, filterMode.value);
}
watch(
() => props.data,
data => {
if (Array.isArray(data)) {
initializeFilter(data);
if (data.length > 100) {
extractPathsLazy(data, 100);
}
}
},
{ immediate: true }
);
</script>
<style scoped>
.filter-panel {
display: flex;
flex-direction: column;
gap: 10px;
margin: 10px 0;
padding: 10px;
border: 1px solid #eee;
border-radius: 8px;
background: #fafafa;
}
.filter-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.filter-row label {
font-weight: 600;
color: #333;
}
.filter-value-label {
margin-left: 6px;
}
.filter-value-input {
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 6px;
min-width: 220px;
}
.btn-clear {
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 6px;
padding: 6px 12px;
cursor: pointer;
}
.filter-stats {
font-size: 13px;
color: #666;
}
.filter-error {
font-size: 13px;
color: #d9534f;
}
</style>