feat: Add favorites world search by tag (#1275)

This commit is contained in:
pa
2026-03-18 11:32:33 +09:00
parent 0292cbb80c
commit 7dbefdb951
3 changed files with 52 additions and 4 deletions

View File

@@ -315,6 +315,9 @@
"favorite": { "favorite": {
"worlds": { "worlds": {
"search": "Search", "search": "Search",
"search_by_tag": "Search by Tag",
"search_mode_name": "Name",
"search_mode_tag": "Tag",
"vrchat_favorites": "VRChat Favorites", "vrchat_favorites": "VRChat Favorites",
"local_favorites": "Local Favorites", "local_favorites": "Local Favorites",
"new_group": "New Group", "new_group": "New Group",

View File

@@ -5,7 +5,9 @@
:sort-value="worldSortValue" :sort-value="worldSortValue"
:extra-sort-options="worldExtraSortOptions" :extra-sort-options="worldExtraSortOptions"
v-model:search-query="worldFavoriteSearch" v-model:search-query="worldFavoriteSearch"
:search-placeholder="t('view.favorite.worlds.search')" :search-placeholder="worldSearchPlaceholder"
v-model:search-mode="worldSearchMode"
:search-mode-visible="true"
v-model:toolbar-menu-open="worldToolbarMenuOpen" v-model:toolbar-menu-open="worldToolbarMenuOpen"
v-model:card-scale-value="worldCardScaleValue" v-model:card-scale-value="worldCardScaleValue"
:card-scale-percent="worldCardScalePercent" :card-scale-percent="worldCardScalePercent"
@@ -502,6 +504,13 @@
const worldEditMode = ref(false); const worldEditMode = ref(false);
const worldToolbarMenuOpen = ref(false); const worldToolbarMenuOpen = ref(false);
const worldSortMode = ref('none'); const worldSortMode = ref('none');
const worldSearchMode = ref('name');
const worldSearchPlaceholder = computed(() =>
worldSearchMode.value === 'tag'
? t('view.favorite.worlds.search_by_tag')
: t('view.favorite.worlds.search')
);
const worldExtraSortOptions = computed(() => [ const worldExtraSortOptions = computed(() => [
{ value: 'players', label: t('view.settings.appearance.appearance.sort_favorite_by_players') } { value: 'players', label: t('view.settings.appearance.appearance.sort_favorite_by_players') }
@@ -745,6 +754,12 @@
} }
}); });
watch(worldSearchMode, () => {
if (isSearchActive.value) {
doSearchWorldFavorites();
}
});
watch([currentLocalFavorites, worldCardScale, worldCardSpacing, activeLocalGroupName], () => { watch([currentLocalFavorites, worldCardScale, worldCardSpacing, activeLocalGroupName], () => {
nextTick(() => { nextTick(() => {
localVirtualizer.value?.measure?.(); localVirtualizer.value?.measure?.();
@@ -1000,16 +1015,25 @@
* *
* @param worldFavoriteSearch * @param worldFavoriteSearch
*/ */
function doSearchWorldFavorites(worldFavoriteSearch) { function doSearchWorldFavorites(searchInput) {
const search = worldFavoriteSearch.trim().toLowerCase(); const search = (searchInput ?? worldFavoriteSearch.value).trim().toLowerCase();
if (search.length < 3) { if (search.length < 3) {
worldFavoriteSearchResults.value = []; worldFavoriteSearchResults.value = [];
return; return;
} }
const isTagMode = worldSearchMode.value === 'tag';
const filtered = searchableWorldEntries.value.filter((ref) => { const filtered = searchableWorldEntries.value.filter((ref) => {
if (!ref || typeof ref.id === 'undefined' || typeof ref.name === 'undefined') { if (!ref || typeof ref.id === 'undefined' || typeof ref.name === 'undefined') {
return false; return false;
} }
if (isTagMode) {
if (Array.isArray(ref.tags)) {
return ref.tags.some(
(tag) => tag.startsWith('author_tag_') && tag.substring(11).toLowerCase().includes(search)
);
}
return false;
}
const authorName = ref.authorName || ''; const authorName = ref.authorName || '';
return ref.name.toLowerCase().includes(search) || authorName.toLowerCase().includes(search); return ref.name.toLowerCase().includes(search) || authorName.toLowerCase().includes(search);
}); });

View File

@@ -37,7 +37,24 @@
class="flex-1" class="flex-1"
:placeholder="searchPlaceholder" :placeholder="searchPlaceholder"
@update:modelValue="$emit('update:searchQuery', $event)" @update:modelValue="$emit('update:searchQuery', $event)"
@input="$emit('search')" /> @input="$emit('search')">
<template v-if="searchModeVisible" #trailing>
<ToggleGroup
type="single"
:model-value="searchMode"
variant="outline"
size="xs"
class="mr-0.5"
@update:modelValue="$emit('update:searchMode', $event)">
<ToggleGroupItem value="name" class="h-5! px-1.5! text-[11px]">
{{ t('view.favorite.worlds.search_mode_name') }}
</ToggleGroupItem>
<ToggleGroupItem value="tag" class="h-5! px-1.5! text-[11px]">
{{ t('view.favorite.worlds.search_mode_tag') }}
</ToggleGroupItem>
</ToggleGroup>
</template>
</InputGroupSearch>
<DropdownMenu :open="toolbarMenuOpen" @update:open="$emit('update:toolbarMenuOpen', $event)"> <DropdownMenu :open="toolbarMenuOpen" @update:open="$emit('update:toolbarMenuOpen', $event)">
<DropdownMenuTrigger as-child> <DropdownMenuTrigger as-child>
<Button class="rounded-full" size="icon-sm" variant="ghost"><Ellipsis /></Button> <Button class="rounded-full" size="icon-sm" variant="ghost"><Ellipsis /></Button>
@@ -91,6 +108,7 @@
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import { ArrowUpDown, Ellipsis } from 'lucide-vue-next'; import { ArrowUpDown, Ellipsis } from 'lucide-vue-next';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupSearch } from '@/components/ui/input-group'; import { InputGroupSearch } from '@/components/ui/input-group';
@@ -102,6 +120,8 @@
extraSortOptions: { type: Array, default: () => [] }, extraSortOptions: { type: Array, default: () => [] },
searchQuery: { type: String, default: '' }, searchQuery: { type: String, default: '' },
searchPlaceholder: { type: String, default: '' }, searchPlaceholder: { type: String, default: '' },
searchMode: { type: String, default: 'name' },
searchModeVisible: { type: Boolean, default: false },
toolbarMenuOpen: { type: Boolean, default: false }, toolbarMenuOpen: { type: Boolean, default: false },
cardScaleValue: { type: Array, default: () => [50] }, cardScaleValue: { type: Array, default: () => [50] },
cardScalePercent: { type: Number, default: 100 }, cardScalePercent: { type: Number, default: 100 },
@@ -114,6 +134,7 @@
defineEmits([ defineEmits([
'update:sortValue', 'update:sortValue',
'update:searchQuery', 'update:searchQuery',
'update:searchMode',
'update:toolbarMenuOpen', 'update:toolbarMenuOpen',
'update:cardScaleValue', 'update:cardScaleValue',
'update:cardSpacingValue', 'update:cardSpacingValue',