✨ Add filtering functionality to the gamemaster panel
This commit is contained in:
@@ -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>
|
||||||
Reference in New Issue
Block a user