Browse Source

Advanced section

master
user 3 weeks ago
parent
commit
f4d19f3611
  1. 101
      frontend/src/views/PublicBrowser.vue

101
frontend/src/views/PublicBrowser.vue

@ -47,6 +47,58 @@
/>
</div>
<!-- Advanced filters (only for MVD directories) -->
<div v-if="hasMvdData" class="mb-4 flex flex-col items-end">
<button
@click="filtersOpen = !filtersOpen"
class="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
>
<svg class="w-4 h-4 transition-transform" :class="{ 'rotate-90': filtersOpen }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
</svg>
Advanced
<span v-if="activeFiltersCount > 0" class="bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 text-xs px-2 py-0.5 rounded-full">
{{ activeFiltersCount }} active
</span>
</button>
<div v-if="filtersOpen" class="mt-3 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-gray-700 flex flex-wrap gap-4 items-end">
<div>
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Map</label>
<select
v-model="filterMap"
class="px-3 py-1.5 text-sm rounded-lg border border-gray-300 dark:border-gray-600
bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100
focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">All maps</option>
<option v-for="map in availableMaps" :key="map" :value="map">{{ map }}</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Player</label>
<input
v-model="filterPlayer"
type="text"
placeholder="Player name…"
class="px-3 py-1.5 text-sm rounded-lg border border-gray-300 dark:border-gray-600
bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100
focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<button
v-if="activeFiltersCount > 0"
@click="clearFilters"
class="px-3 py-1.5 text-sm rounded-lg border border-gray-300 dark:border-gray-600
text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
>
Clear filters
</button>
</div>
</div>
<!-- Empty -->
<div v-if="entries.length === 0" class="text-gray-400 dark:text-gray-500 text-sm">
Directory is empty.
@ -73,7 +125,7 @@
</thead>
<tbody>
<tr
v-for="entry in entries"
v-for="entry in filteredEntries"
:key="entry.name"
class="border-b border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800"
:class="{ 'bg-blue-50 dark:bg-blue-900/20': selected.has(entry.name) }"
@ -240,7 +292,52 @@ function closeConfirm() {
captchaError.value = ''
}
const fileEntries = computed(() => entries.value.filter(e => !e.is_dir))
// --- Filters ---
const filtersOpen = ref(false)
const filterMap = ref('')
const filterPlayer = ref('')
const hasMvdData = computed(() => entries.value.some(e => e.mvd_info))
const availableMaps = computed(() => {
const maps = new Set()
for (const e of entries.value) {
if (e.mvd_info?.map) maps.add(e.mvd_info.map)
}
return [...maps].sort()
})
const activeFiltersCount = computed(() => (filterMap.value ? 1 : 0) + (filterPlayer.value.trim() ? 1 : 0))
const filteredEntries = computed(() => {
const player = filterPlayer.value.trim().toLowerCase()
return entries.value.filter(e => {
if (e.is_dir) return true
if (filterMap.value && e.mvd_info?.map !== filterMap.value) return false
if (player) {
const a = (e.mvd_info?.players_a ?? '').toLowerCase()
const b = (e.mvd_info?.players_b ?? '').toLowerCase()
if (!a.includes(player) && !b.includes(player)) return false
}
return true
})
})
function clearFilters() {
filterMap.value = ''
filterPlayer.value = ''
selected.value = new Set()
}
watch([filterMap, filterPlayer], () => { selected.value = new Set() })
watch(currentPath, () => {
filterMap.value = ''
filterPlayer.value = ''
filtersOpen.value = false
})
const fileEntries = computed(() => filteredEntries.value.filter(e => !e.is_dir))
const showMultiselect = computed(() => fileEntries.value.length >= 3)
const allFilesSelected = computed(() => fileEntries.value.length > 0 && fileEntries.value.every(e => selected.value.has(e.name)))
const someFilesSelected = computed(() => !allFilesSelected.value && fileEntries.value.some(e => selected.value.has(e.name)))

Loading…
Cancel
Save