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": {
"worlds": {
"search": "Search",
"search_by_tag": "Search by Tag",
"search_mode_name": "Name",
"search_mode_tag": "Tag",
"vrchat_favorites": "VRChat Favorites",
"local_favorites": "Local Favorites",
"new_group": "New Group",

View File

@@ -5,7 +5,9 @@
:sort-value="worldSortValue"
:extra-sort-options="worldExtraSortOptions"
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:card-scale-value="worldCardScaleValue"
:card-scale-percent="worldCardScalePercent"
@@ -502,6 +504,13 @@
const worldEditMode = ref(false);
const worldToolbarMenuOpen = ref(false);
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(() => [
{ 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], () => {
nextTick(() => {
localVirtualizer.value?.measure?.();
@@ -1000,16 +1015,25 @@
*
* @param worldFavoriteSearch
*/
function doSearchWorldFavorites(worldFavoriteSearch) {
const search = worldFavoriteSearch.trim().toLowerCase();
function doSearchWorldFavorites(searchInput) {
const search = (searchInput ?? worldFavoriteSearch.value).trim().toLowerCase();
if (search.length < 3) {
worldFavoriteSearchResults.value = [];
return;
}
const isTagMode = worldSearchMode.value === 'tag';
const filtered = searchableWorldEntries.value.filter((ref) => {
if (!ref || typeof ref.id === 'undefined' || typeof ref.name === 'undefined') {
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 || '';
return ref.name.toLowerCase().includes(search) || authorName.toLowerCase().includes(search);
});

View File

@@ -37,7 +37,24 @@
class="flex-1"
:placeholder="searchPlaceholder"
@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)">
<DropdownMenuTrigger as-child>
<Button class="rounded-full" size="icon-sm" variant="ghost"><Ellipsis /></Button>
@@ -91,6 +108,7 @@
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import { ArrowUpDown, Ellipsis } from 'lucide-vue-next';
import { Button } from '@/components/ui/button';
import { InputGroupSearch } from '@/components/ui/input-group';
@@ -102,6 +120,8 @@
extraSortOptions: { type: Array, default: () => [] },
searchQuery: { type: String, default: '' },
searchPlaceholder: { type: String, default: '' },
searchMode: { type: String, default: 'name' },
searchModeVisible: { type: Boolean, default: false },
toolbarMenuOpen: { type: Boolean, default: false },
cardScaleValue: { type: Array, default: () => [50] },
cardScalePercent: { type: Number, default: 100 },
@@ -114,6 +134,7 @@
defineEmits([
'update:sortValue',
'update:searchQuery',
'update:searchMode',
'update:toolbarMenuOpen',
'update:cardScaleValue',
'update:cardSpacingValue',