mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-06 14:46:04 +02:00
add search for userdialog tabs (#476)
This commit is contained in:
@@ -27,8 +27,8 @@
|
|||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<template v-if="userDialog.ref.id === currentUser.id">
|
|
||||||
<Input v-model="avatarSearchQuery" class="h-8 w-40 mr-2" placeholder="Search avatars" @click.stop />
|
<Input v-model="avatarSearchQuery" class="h-8 w-40 mr-2" placeholder="Search avatars" @click.stop />
|
||||||
|
<template v-if="userDialog.ref.id === currentUser.id">
|
||||||
<span class="mr-1">{{ t('dialog.user.avatars.sort_by') }}</span>
|
<span class="mr-1">{{ t('dialog.user.avatars.sort_by') }}</span>
|
||||||
<Select
|
<Select
|
||||||
:model-value="userDialog.avatarSorting"
|
:model-value="userDialog.avatarSorting"
|
||||||
@@ -136,9 +136,6 @@
|
|||||||
const avatarSearchQuery = ref('');
|
const avatarSearchQuery = ref('');
|
||||||
const filteredUserDialogAvatars = computed(() => {
|
const filteredUserDialogAvatars = computed(() => {
|
||||||
const avatars = userDialogAvatars.value;
|
const avatars = userDialogAvatars.value;
|
||||||
if (userDialog.value.ref?.id !== currentUser.value.id) {
|
|
||||||
return avatars;
|
|
||||||
}
|
|
||||||
const query = avatarSearchQuery.value.trim().toLowerCase();
|
const query = avatarSearchQuery.value.trim().toLowerCase();
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return avatars;
|
return avatars;
|
||||||
|
|||||||
@@ -14,7 +14,33 @@
|
|||||||
<DeprecationAlert
|
<DeprecationAlert
|
||||||
v-if="userDialog.ref.id === currentUser.id"
|
v-if="userDialog.ref.id === currentUser.id"
|
||||||
:feature-name="t('nav_tooltip.favorite_worlds')" />
|
:feature-name="t('nav_tooltip.favorite_worlds')" />
|
||||||
|
<Input v-model="searchQuery" class="h-8 w-40 mt-2" placeholder="Search worlds" @click.stop />
|
||||||
|
<template v-if="searchActive">
|
||||||
|
<div
|
||||||
|
class="flex flex-wrap items-start"
|
||||||
|
style="margin-top: 8px; min-height: 60px; max-height: 50vh; overflow: auto">
|
||||||
|
<div
|
||||||
|
v-for="world in allFilteredFavoriteWorlds"
|
||||||
|
:key="world.favoriteId"
|
||||||
|
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
|
||||||
|
@click="showWorldDialog(world.id)">
|
||||||
|
<div class="relative inline-block flex-none size-9 mr-2.5">
|
||||||
|
<Avatar class="size-9">
|
||||||
|
<AvatarImage :src="world.thumbnailImageUrl" class="object-cover" />
|
||||||
|
<AvatarFallback>
|
||||||
|
<Image class="size-4 text-muted-foreground" />
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 overflow-hidden">
|
||||||
|
<span class="block truncate font-medium leading-[18px]" v-text="world.name"></span>
|
||||||
|
<span v-if="world.occupants" class="block truncate text-xs">({{ world.occupants }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<TabsUnderline
|
<TabsUnderline
|
||||||
|
v-else
|
||||||
v-model="favoriteWorldsTab"
|
v-model="favoriteWorldsTab"
|
||||||
:items="favoriteWorldTabs"
|
:items="favoriteWorldTabs"
|
||||||
:unmount-on-hide="false"
|
:unmount-on-hide="false"
|
||||||
@@ -69,8 +95,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { Image } from 'lucide-vue-next';
|
import { Image } from 'lucide-vue-next';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
import { DataTableEmpty } from '@/components/ui/data-table';
|
import { DataTableEmpty } from '@/components/ui/data-table';
|
||||||
import { TabsUnderline } from '@/components/ui/tabs';
|
import { TabsUnderline } from '@/components/ui/tabs';
|
||||||
@@ -99,6 +126,17 @@
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const searchQuery = ref('');
|
||||||
|
const searchActive = computed(() => searchQuery.value.trim().length > 0);
|
||||||
|
const allFilteredFavoriteWorlds = computed(() => {
|
||||||
|
const query = searchQuery.value.trim().toLowerCase();
|
||||||
|
if (!query) return [];
|
||||||
|
const lists = userDialog.value.userFavoriteWorlds || [];
|
||||||
|
const all = lists.flatMap((list) => list[2] || []);
|
||||||
|
return all.filter((w) => (w.name || '').toLowerCase().includes(query));
|
||||||
|
});
|
||||||
|
watch(() => userDialog.value.id, () => { searchQuery.value = ''; });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param visibility
|
* @param visibility
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center">
|
<div style="display: flex; align-items: center">
|
||||||
<template v-if="!userDialogGroupEditMode">
|
<template v-if="!userDialogGroupEditMode">
|
||||||
|
<Input v-model="groupSearchQuery" class="h-8 w-40 mr-2" placeholder="Search groups" @click.stop />
|
||||||
<span style="margin-right: 6px">{{ t('dialog.user.groups.sort_by') }}</span>
|
<span style="margin-right: 6px">{{ t('dialog.user.groups.sort_by') }}</span>
|
||||||
<Select
|
<Select
|
||||||
:model-value="userDialogGroupSortingKey"
|
:model-value="userDialogGroupSortingKey"
|
||||||
@@ -236,6 +237,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="groupSearchActive">
|
||||||
|
<div class="flex flex-wrap items-start" style="margin-top: 8px; min-height: 60px">
|
||||||
|
<div
|
||||||
|
v-for="group in allFilteredGroups"
|
||||||
|
:key="group.id"
|
||||||
|
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
|
||||||
|
@click="showGroupDialog(group.id)">
|
||||||
|
<div class="relative inline-block flex-none size-9 mr-2.5">
|
||||||
|
<Avatar class="size-9">
|
||||||
|
<AvatarImage :src="group.iconUrl" class="object-cover" />
|
||||||
|
<AvatarFallback>
|
||||||
|
<Users class="size-4 text-muted-foreground" />
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 overflow-hidden">
|
||||||
|
<span class="block truncate font-medium leading-[18px]" v-text="group.name"></span>
|
||||||
|
<div class="block truncate text-xs inline-flex! items-center">
|
||||||
|
<TooltipWrapper
|
||||||
|
v-if="group.isRepresenting"
|
||||||
|
side="top"
|
||||||
|
:content="t('dialog.group.members.representing')">
|
||||||
|
<Tag style="margin-right: 6px" />
|
||||||
|
</TooltipWrapper>
|
||||||
|
<span>({{ group.memberCount }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<template v-if="userDialog.userGroups.ownGroups.length > 0">
|
<template v-if="userDialog.userGroups.ownGroups.length > 0">
|
||||||
<span class="text-base font-bold">{{ t('dialog.user.groups.own_groups') }}</span>
|
<span class="text-base font-bold">{{ t('dialog.user.groups.own_groups') }}</span>
|
||||||
@@ -384,9 +415,10 @@
|
|||||||
import { ArrowDown, ArrowUp, DownloadIcon, Eye, LogOut, RefreshCw, Tag, Users } from 'lucide-vue-next';
|
import { ArrowDown, ArrowUp, DownloadIcon, Eye, LogOut, RefreshCw, Tag, Users } from 'lucide-vue-next';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { nextTick, ref } from 'vue';
|
import { computed, nextTick, ref, watch } from 'vue';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
import { Spinner } from '@/components/ui/spinner';
|
import { Spinner } from '@/components/ui/spinner';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
@@ -426,6 +458,15 @@
|
|||||||
setUserDialogGroupSorting
|
setUserDialogGroupSorting
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const groupSearchQuery = ref('');
|
||||||
|
const groupSearchActive = computed(() => groupSearchQuery.value.trim().length > 0);
|
||||||
|
const allFilteredGroups = computed(() => {
|
||||||
|
const query = groupSearchQuery.value.trim().toLowerCase();
|
||||||
|
if (!query) return [];
|
||||||
|
return userDialog.value.userGroups.groups.filter((g) => (g.name || '').toLowerCase().includes(query));
|
||||||
|
});
|
||||||
|
watch(() => userDialog.value.id, () => { groupSearchQuery.value = ''; });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param sortOrder
|
* @param sortOrder
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center">
|
<div style="display: flex; align-items: center">
|
||||||
|
<Input v-model="searchQuery" class="h-8 w-40 mr-2" placeholder="Search friends" @click.stop />
|
||||||
<span style="margin-right: 6px">{{ t('dialog.user.groups.sort_by') }}</span>
|
<span style="margin-right: 6px">{{ t('dialog.user.groups.sort_by') }}</span>
|
||||||
<Select
|
<Select
|
||||||
:model-value="userDialogMutualFriendSortingKey"
|
:model-value="userDialogMutualFriendSortingKey"
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul class="flex flex-wrap items-start" style="margin-top: 8px; overflow: auto; max-height: 250px; min-width: 130px">
|
<ul class="flex flex-wrap items-start" style="margin-top: 8px; overflow: auto; max-height: 250px; min-width: 130px">
|
||||||
<li
|
<li
|
||||||
v-for="user in userDialog.mutualFriends"
|
v-for="user in filteredMutualFriends"
|
||||||
:key="user.id"
|
:key="user.id"
|
||||||
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
|
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
|
||||||
@click="showUserDialog(user.id)">
|
@click="showUserDialog(user.id)">
|
||||||
@@ -64,6 +65,8 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { RefreshCw, User } from 'lucide-vue-next';
|
import { RefreshCw, User } from 'lucide-vue-next';
|
||||||
import { Spinner } from '@/components/ui/spinner';
|
import { Spinner } from '@/components/ui/spinner';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
@@ -89,6 +92,15 @@
|
|||||||
setUserDialogMutualFriendSorting
|
setUserDialogMutualFriendSorting
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const searchQuery = ref('');
|
||||||
|
const filteredMutualFriends = computed(() => {
|
||||||
|
const friends = userDialog.value.mutualFriends;
|
||||||
|
const query = searchQuery.value.trim().toLowerCase();
|
||||||
|
if (!query) return friends;
|
||||||
|
return friends.filter((u) => (u.displayName || '').toLowerCase().includes(query));
|
||||||
|
});
|
||||||
|
watch(() => userDialog.value.id, () => { searchQuery.value = ''; });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param sortOrder
|
* @param sortOrder
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center">
|
<div style="display: flex; align-items: center">
|
||||||
|
<Input v-model="searchQuery" class="h-8 w-40 mr-2" placeholder="Search worlds" @click.stop />
|
||||||
<span class="mr-1">{{ t('dialog.user.worlds.sort_by') }}</span>
|
<span class="mr-1">{{ t('dialog.user.worlds.sort_by') }}</span>
|
||||||
<Select
|
<Select
|
||||||
:model-value="userDialogWorldSortingKey"
|
:model-value="userDialogWorldSortingKey"
|
||||||
@@ -54,7 +55,7 @@
|
|||||||
<div class="flex flex-wrap items-start" style="margin-top: 8px; min-height: 60px">
|
<div class="flex flex-wrap items-start" style="margin-top: 8px; min-height: 60px">
|
||||||
<template v-if="userDialog.worlds.length">
|
<template v-if="userDialog.worlds.length">
|
||||||
<div
|
<div
|
||||||
v-for="world in userDialog.worlds"
|
v-for="world in filteredWorlds"
|
||||||
:key="world.id"
|
:key="world.id"
|
||||||
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
|
class="box-border flex items-center p-1.5 text-[13px] cursor-pointer w-[167px] hover:rounded-[25px_5px_5px_25px]"
|
||||||
@click="showWorldDialog(world.id)">
|
@click="showWorldDialog(world.id)">
|
||||||
@@ -87,7 +88,8 @@
|
|||||||
import { Image, RefreshCw } from 'lucide-vue-next';
|
import { Image, RefreshCw } from 'lucide-vue-next';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
import { Spinner } from '@/components/ui/spinner';
|
import { Spinner } from '@/components/ui/spinner';
|
||||||
import { ref } from 'vue';
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
@@ -105,6 +107,15 @@
|
|||||||
|
|
||||||
const userDialogWorldsRequestId = ref(0);
|
const userDialogWorldsRequestId = ref(0);
|
||||||
|
|
||||||
|
const searchQuery = ref('');
|
||||||
|
const filteredWorlds = computed(() => {
|
||||||
|
const worlds = userDialog.value.worlds;
|
||||||
|
const query = searchQuery.value.trim().toLowerCase();
|
||||||
|
if (!query) return worlds;
|
||||||
|
return worlds.filter((w) => (w.name || '').toLowerCase().includes(query));
|
||||||
|
});
|
||||||
|
watch(() => userDialog.value.id, () => { searchQuery.value = ''; });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param userId
|
* @param userId
|
||||||
|
|||||||
Reference in New Issue
Block a user