mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-22 00:03:51 +02:00
Relocate sidebar settings from the Appearance tab to a new popover within the Sidebar
This commit is contained in:
@@ -62,19 +62,150 @@
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center mx-1 gap-1">
|
||||
<TooltipWrapper side="bottom" :content="t('side_panel.refresh_tooltip')">
|
||||
<Button
|
||||
class="rounded-full"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
:disabled="isRefreshFriendsLoading"
|
||||
style="margin-right: 10px"
|
||||
@click="refreshFriendsList">
|
||||
<Spinner v-if="isRefreshFriendsLoading" />
|
||||
<RefreshCw v-else />
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button class="rounded-full" variant="ghost" size="icon-sm">
|
||||
<Settings />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent side="bottom" align="end" class="w-64 p-3" @open-auto-focus.prevent>
|
||||
<div class="flex flex-col gap-2.5 text-xs">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>{{ t('side_panel.settings.group_by_instance') }}</span>
|
||||
<Switch
|
||||
:model-value="isSidebarGroupByInstance"
|
||||
@update:modelValue="setIsSidebarGroupByInstance" />
|
||||
</div>
|
||||
<div v-if="isSidebarGroupByInstance" class="flex items-center justify-between">
|
||||
<span>{{ t('side_panel.settings.hide_friends_in_same_instance') }}</span>
|
||||
<Switch
|
||||
:model-value="isHideFriendsInSameInstance"
|
||||
@update:modelValue="setIsHideFriendsInSameInstance" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>{{ t('side_panel.settings.split_favorite_friends') }}</span>
|
||||
<Switch
|
||||
:model-value="isSidebarDivideByFriendGroup"
|
||||
@update:modelValue="setIsSidebarDivideByFriendGroup" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span>{{ t('side_panel.settings.favorite_groups') }}</span>
|
||||
<Select
|
||||
:model-value="resolvedSidebarFavoriteGroups"
|
||||
multiple
|
||||
@update:modelValue="handleFavoriteGroupsChange">
|
||||
<SelectTrigger size="sm" class="w-full overflow-hidden">
|
||||
<SelectValue
|
||||
:placeholder="t('side_panel.settings.favorite_groups_placeholder')">
|
||||
<template v-if="resolvedSidebarFavoriteGroups.length">
|
||||
<span class="truncate">{{ selectedFavGroupLabel }}</span>
|
||||
<span
|
||||
v-if="resolvedSidebarFavoriteGroups.length > 1"
|
||||
class="bg-primary text-primary-foreground shrink-0 rounded px-1 text-xs">
|
||||
+{{ resolvedSidebarFavoriteGroups.length - 1 }}
|
||||
</span>
|
||||
</template>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="group in favoriteFriendGroups"
|
||||
:key="group.key"
|
||||
:value="group.key">
|
||||
{{ group.displayName }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
<template v-if="localFriendFavoriteGroups.length">
|
||||
<SelectSeparator />
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="group in localFriendFavoriteGroups"
|
||||
:key="'local:' + group"
|
||||
:value="'local:' + group">
|
||||
{{ group }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</template>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span>{{ t('side_panel.settings.sort_primary') }}</span>
|
||||
<Select :model-value="sidebarSortMethod1" @update:modelValue="setSidebarSortMethod1">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue
|
||||
:placeholder="
|
||||
t('view.settings.appearance.side_panel.sorting.placeholder')
|
||||
" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="opt in sortOptions" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span>{{ t('side_panel.settings.sort_secondary') }}</span>
|
||||
<Select
|
||||
:model-value="sidebarSortMethod2"
|
||||
:disabled="!sidebarSortMethod1"
|
||||
@update:modelValue="(v) => setSidebarSortMethod2(v === CLEAR_VALUE ? '' : v)">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue
|
||||
:placeholder="
|
||||
t('view.settings.appearance.side_panel.sorting.placeholder')
|
||||
" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="CLEAR_VALUE">{{
|
||||
t('dialog.gallery_select.none')
|
||||
}}</SelectItem>
|
||||
<SelectItem v-for="opt in sortOptions" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span>{{ t('side_panel.settings.sort_tertiary') }}</span>
|
||||
<Select
|
||||
:model-value="sidebarSortMethod3"
|
||||
:disabled="!sidebarSortMethod2"
|
||||
@update:modelValue="(v) => setSidebarSortMethod3(v === CLEAR_VALUE ? '' : v)">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue
|
||||
:placeholder="
|
||||
t('view.settings.appearance.side_panel.sorting.placeholder')
|
||||
" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="CLEAR_VALUE">{{
|
||||
t('dialog.gallery_select.none')
|
||||
}}</SelectItem>
|
||||
<SelectItem v-for="opt in sortOptions" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<TabsUnderline
|
||||
@@ -108,18 +239,35 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '@/components/ui/select';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { RefreshCw, Settings } from 'lucide-vue-next';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTableEmpty } from '@/components/ui/data-table';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { RefreshCw } from 'lucide-vue-next';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useFriendStore, useGroupStore, useSearchStore } from '../../stores';
|
||||
import {
|
||||
useAppearanceSettingsStore,
|
||||
useFavoriteStore,
|
||||
useFriendStore,
|
||||
useGroupStore,
|
||||
useSearchStore
|
||||
} from '../../stores';
|
||||
import { debounce, userImage } from '../../shared/utils';
|
||||
|
||||
import FriendsSidebar from './components/FriendsSidebar.vue';
|
||||
@@ -131,6 +279,79 @@
|
||||
const { quickSearchItems } = storeToRefs(useSearchStore());
|
||||
const { groupInstances } = storeToRefs(useGroupStore());
|
||||
const { t } = useI18n();
|
||||
|
||||
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||
const {
|
||||
sidebarSortMethod1,
|
||||
sidebarSortMethod2,
|
||||
sidebarSortMethod3,
|
||||
isSidebarGroupByInstance,
|
||||
isHideFriendsInSameInstance,
|
||||
isSidebarDivideByFriendGroup,
|
||||
sidebarFavoriteGroups
|
||||
} = storeToRefs(appearanceSettingsStore);
|
||||
const {
|
||||
setSidebarSortMethod1,
|
||||
setSidebarSortMethod2,
|
||||
setSidebarSortMethod3,
|
||||
setIsSidebarGroupByInstance,
|
||||
setIsHideFriendsInSameInstance,
|
||||
setIsSidebarDivideByFriendGroup,
|
||||
setSidebarFavoriteGroups
|
||||
} = appearanceSettingsStore;
|
||||
|
||||
const favoriteStore = useFavoriteStore();
|
||||
const { favoriteFriendGroups, localFriendFavoriteGroups } = storeToRefs(favoriteStore);
|
||||
|
||||
const allFavoriteGroupKeys = computed(() => {
|
||||
const keys = favoriteFriendGroups.value.map((g) => g.key);
|
||||
for (const group of localFriendFavoriteGroups.value) {
|
||||
keys.push('local:' + group);
|
||||
}
|
||||
return keys;
|
||||
});
|
||||
|
||||
const resolvedSidebarFavoriteGroups = computed(() => {
|
||||
if (sidebarFavoriteGroups.value.length === 0) {
|
||||
return allFavoriteGroupKeys.value;
|
||||
}
|
||||
return sidebarFavoriteGroups.value;
|
||||
});
|
||||
|
||||
function handleFavoriteGroupsChange(value) {
|
||||
if (!value || value.length === 0) {
|
||||
// Deselected all → reset to all (store as empty)
|
||||
setSidebarFavoriteGroups([]);
|
||||
return;
|
||||
}
|
||||
// If all groups are selected, store as empty (= all)
|
||||
const allKeys = allFavoriteGroupKeys.value;
|
||||
if (value.length >= allKeys.length && allKeys.every((k) => value.includes(k))) {
|
||||
setSidebarFavoriteGroups([]);
|
||||
return;
|
||||
}
|
||||
setSidebarFavoriteGroups(value);
|
||||
}
|
||||
|
||||
const selectedFavGroupLabel = computed(() => {
|
||||
const key = resolvedSidebarFavoriteGroups.value[0];
|
||||
if (!key) return '';
|
||||
if (key.startsWith('local:')) return key.slice(6);
|
||||
return favoriteFriendGroups.value.find((g) => g.key === key)?.displayName || key;
|
||||
});
|
||||
|
||||
const CLEAR_VALUE = '__clear__';
|
||||
|
||||
const sortOptions = computed(() => [
|
||||
{ value: 'Sort Alphabetically', label: t('view.settings.appearance.side_panel.sorting.alphabetical') },
|
||||
{ value: 'Sort by Status', label: t('view.settings.appearance.side_panel.sorting.status') },
|
||||
{ value: 'Sort Private to Bottom', label: t('view.settings.appearance.side_panel.sorting.private_to_bottom') },
|
||||
{ value: 'Sort by Last Active', label: t('view.settings.appearance.side_panel.sorting.last_active') },
|
||||
{ value: 'Sort by Last Seen', label: t('view.settings.appearance.side_panel.sorting.last_seen') },
|
||||
{ value: 'Sort by Time in Instance', label: t('view.settings.appearance.side_panel.sorting.time_in_instance') },
|
||||
{ value: 'Sort by Location', label: t('view.settings.appearance.side_panel.sorting.location') }
|
||||
]);
|
||||
|
||||
const sidebarTabs = computed(() => [
|
||||
{ value: 'friends', label: t('side_panel.friends') },
|
||||
{ value: 'groups', label: t('side_panel.groups') }
|
||||
|
||||
Reference in New Issue
Block a user