mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-28 19:23:47 +02:00
add local favorites friend
This commit is contained in:
@@ -183,6 +183,86 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group-section">
|
||||
<div class="group-section__header">
|
||||
<span>{{ t('view.favorite.worlds.local_favorites') }}</span>
|
||||
<Button
|
||||
class="rounded-full"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
@click.stop="getLocalFriendFavorites"
|
||||
><RefreshCcw />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="group-section__list">
|
||||
<template v-if="localFriendFavoriteGroups.length">
|
||||
<div
|
||||
v-for="group in localFriendFavoriteGroups"
|
||||
:key="group"
|
||||
:class="[
|
||||
'group-item',
|
||||
{ 'is-active': !hasSearchInput && isGroupActive('local', group) }
|
||||
]"
|
||||
@click="handleGroupClick('local', group)">
|
||||
<div class="group-item__top">
|
||||
<span class="group-item__name">{{ group }}</span>
|
||||
<div class="group-item__right">
|
||||
<span class="group-item__count">{{
|
||||
localFriendFavGroupLength(group)
|
||||
}}</span>
|
||||
<div class="group-item__bottom">
|
||||
<DropdownMenu
|
||||
:open="activeGroupMenu === localGroupMenuKey(group)"
|
||||
@update:open="
|
||||
handleGroupMenuVisible(localGroupMenuKey(group), $event)
|
||||
">
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
class="rounded-full"
|
||||
size="icon-sm"
|
||||
variant="ghost"
|
||||
@click.stop
|
||||
><Ellipsis
|
||||
/></Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="right" class="w-50">
|
||||
<DropdownMenuItem @click="handleLocalRename(group)">
|
||||
<span>{{ t('view.favorite.rename_tooltip') }}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
@click="handleLocalDelete(group)">
|
||||
<span>{{ t('view.favorite.delete_tooltip') }}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="group-empty">
|
||||
<DataTableEmpty type="nodata" />
|
||||
</div>
|
||||
<div
|
||||
v-if="!isCreatingLocalGroup"
|
||||
class="group-item group-item--new"
|
||||
@click="startLocalGroupCreation">
|
||||
<Plus />
|
||||
<span>{{ t('view.favorite.worlds.new_group') }}</span>
|
||||
</div>
|
||||
<InputGroupField
|
||||
v-else
|
||||
ref="newLocalGroupInput"
|
||||
v-model="newLocalGroupName"
|
||||
size="sm"
|
||||
class="group-item__input"
|
||||
:placeholder="t('view.favorite.worlds.new_group')"
|
||||
@keyup.enter="handleLocalGroupCreationConfirm"
|
||||
@keyup.esc="cancelLocalGroupCreation"
|
||||
@blur="cancelLocalGroupCreation" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle @dragging="setFriendSplitterDragging" />
|
||||
@@ -197,11 +277,17 @@
|
||||
<small>{{ activeRemoteGroup.count }}/{{ activeRemoteGroup.capacity }}</small>
|
||||
</span>
|
||||
</template>
|
||||
<span v-else-if="activeLocalGroupName">
|
||||
{{ activeLocalGroupName }}
|
||||
<small>{{ activeLocalGroupCount }}</small>
|
||||
</span>
|
||||
<span v-else>No Group Selected</span>
|
||||
</div>
|
||||
<div class="favorites-content__edit">
|
||||
<span>{{ t('view.favorite.edit_mode') }}</span>
|
||||
<Switch v-model="friendEditMode" :disabled="isSearchActive || !activeRemoteGroup" />
|
||||
<Switch
|
||||
v-model="friendEditMode"
|
||||
:disabled="isSearchActive || (!activeRemoteGroup && !activeLocalGroupName)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="favorites-content__edit-actions">
|
||||
@@ -259,6 +345,28 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="!isSearchActive && activeLocalGroupName && isLocalGroupSelected">
|
||||
<div class="favorites-content__scroll favorites-content__scroll--native">
|
||||
<template v-if="currentLocalFriendFavorites.length">
|
||||
<div
|
||||
class="favorites-card-list"
|
||||
:style="friendFavoritesGridStyle(currentLocalFriendFavorites.length)">
|
||||
<FavoritesFriendItem
|
||||
v-for="favorite in currentLocalFriendFavorites"
|
||||
:key="favorite.id"
|
||||
:favorite="favorite"
|
||||
:group="{ key: activeLocalGroupName, type: 'local' }"
|
||||
:selected="selectedFavoriteFriends.includes(favorite.id)"
|
||||
:edit-mode="friendEditMode"
|
||||
@toggle-select="toggleFriendSelection(favorite.id, $event)"
|
||||
@click="showUserDialog(favorite.id)" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="favorites-empty">
|
||||
<DataTableEmpty type="nodata" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="!isSearchActive">
|
||||
<div class="favorites-empty">No Group Selected</div>
|
||||
</template>
|
||||
@@ -309,11 +417,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ArrowUpDown, Check, Ellipsis, MoreHorizontal, Plus, RefreshCcw, RefreshCw } from 'lucide-vue-next';
|
||||
import { computed, nextTick, onBeforeMount, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { ArrowUpDown, Check, Ellipsis, MoreHorizontal, RefreshCw } from 'lucide-vue-next';
|
||||
import { InputGroupField, InputGroupSearch } from '@/components/ui/input-group';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTableEmpty } from '@/components/ui/data-table';
|
||||
import { InputGroupSearch } from '@/components/ui/input-group';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
@@ -372,10 +480,24 @@
|
||||
groupedByGroupKeyFavoriteFriends,
|
||||
selectedFavoriteFriends,
|
||||
friendImportDialogInput,
|
||||
isFavoriteLoading
|
||||
isFavoriteLoading,
|
||||
localFriendFavorites,
|
||||
localFriendFavoriteGroups
|
||||
} = storeToRefs(favoriteStore);
|
||||
const { showFriendImportDialog, refreshFavorites, getLocalWorldFavorites, handleFavoriteGroup } = favoriteStore;
|
||||
const { showUserDialog } = useUserStore();
|
||||
const {
|
||||
showFriendImportDialog,
|
||||
refreshFavorites,
|
||||
getLocalWorldFavorites,
|
||||
getLocalFriendFavorites,
|
||||
handleFavoriteGroup,
|
||||
localFriendFavGroupLength,
|
||||
deleteLocalFriendFavoriteGroup,
|
||||
renameLocalFriendFavoriteGroup,
|
||||
newLocalFriendFavoriteGroup
|
||||
} = favoriteStore;
|
||||
const userStore = useUserStore();
|
||||
const { showUserDialog } = userStore;
|
||||
const { cachedUsers } = storeToRefs(userStore);
|
||||
const { t } = useI18n();
|
||||
|
||||
const {
|
||||
@@ -431,6 +553,9 @@
|
||||
const selectedGroup = ref(null);
|
||||
const activeGroupMenu = ref(null);
|
||||
const friendToolbarMenuOpen = ref(false);
|
||||
const isCreatingLocalGroup = ref(false);
|
||||
const newLocalGroupName = ref('');
|
||||
const newLocalGroupInput = ref(null);
|
||||
|
||||
function handleSortFavoritesChange(value) {
|
||||
const next = Boolean(value);
|
||||
@@ -443,6 +568,8 @@
|
||||
const hasSearchInput = computed(() => friendFavoriteSearch.value.trim().length > 0);
|
||||
const isSearchActive = computed(() => friendFavoriteSearch.value.trim().length >= 3);
|
||||
const isRemoteGroupSelected = computed(() => selectedGroup.value?.type === 'remote');
|
||||
const isLocalGroupSelected = computed(() => selectedGroup.value?.type === 'local');
|
||||
const localGroupMenuKey = (key) => `local:${key}`;
|
||||
|
||||
const closeFriendToolbarMenu = () => {
|
||||
friendToolbarMenuOpen.value = false;
|
||||
@@ -603,6 +730,36 @@
|
||||
return groupedByGroupKeyFavoriteFriends.value[activeRemoteGroup.value.key] || [];
|
||||
});
|
||||
|
||||
const activeLocalGroupName = computed(() => {
|
||||
if (!isLocalGroupSelected.value) {
|
||||
return '';
|
||||
}
|
||||
return selectedGroup.value.key;
|
||||
});
|
||||
|
||||
const activeLocalGroupCount = computed(() => {
|
||||
if (!activeLocalGroupName.value) {
|
||||
return 0;
|
||||
}
|
||||
const favorites = localFriendFavorites.value[activeLocalGroupName.value];
|
||||
return favorites ? favorites.length : 0;
|
||||
});
|
||||
|
||||
const currentLocalFriendFavorites = computed(() => {
|
||||
if (!activeLocalGroupName.value) {
|
||||
return [];
|
||||
}
|
||||
const userIds = localFriendFavorites.value[activeLocalGroupName.value] || [];
|
||||
return userIds.map((userId) => {
|
||||
const ref = cachedUsers.value.get(userId);
|
||||
return {
|
||||
id: userId,
|
||||
ref: ref || undefined,
|
||||
name: ref?.displayName || userId
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const isAllFriendsSelected = computed(() => {
|
||||
if (!activeRemoteGroup.value || !currentFriendFavorites.value.length) {
|
||||
return false;
|
||||
@@ -642,6 +799,7 @@
|
||||
function handleRefreshFavorites() {
|
||||
refreshFavorites();
|
||||
getLocalWorldFavorites();
|
||||
getLocalFriendFavorites();
|
||||
}
|
||||
|
||||
function handleGroupMenuVisible(key, visible) {
|
||||
@@ -670,6 +828,10 @@
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (localFriendFavoriteGroups.value.length) {
|
||||
selectGroup('local', localFriendFavoriteGroups.value[0]);
|
||||
return;
|
||||
}
|
||||
selectedGroup.value = null;
|
||||
clearSelectedFriends();
|
||||
}
|
||||
@@ -681,6 +843,9 @@
|
||||
if (group.type === 'remote') {
|
||||
return favoriteFriendGroups.value.some((item) => item.key === group.key);
|
||||
}
|
||||
if (group.type === 'local') {
|
||||
return localFriendFavoriteGroups.value.includes(group.key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -875,6 +1040,75 @@
|
||||
}
|
||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||
}
|
||||
|
||||
function startLocalGroupCreation() {
|
||||
isCreatingLocalGroup.value = true;
|
||||
newLocalGroupName.value = '';
|
||||
nextTick(() => {
|
||||
newLocalGroupInput.value?.$el?.focus?.();
|
||||
});
|
||||
}
|
||||
|
||||
function cancelLocalGroupCreation() {
|
||||
isCreatingLocalGroup.value = false;
|
||||
newLocalGroupName.value = '';
|
||||
}
|
||||
|
||||
function handleLocalGroupCreationConfirm() {
|
||||
const name = newLocalGroupName.value.trim();
|
||||
if (!name) {
|
||||
cancelLocalGroupCreation();
|
||||
return;
|
||||
}
|
||||
newLocalFriendFavoriteGroup(name);
|
||||
isCreatingLocalGroup.value = false;
|
||||
newLocalGroupName.value = '';
|
||||
selectGroup('local', name);
|
||||
}
|
||||
|
||||
function handleLocalRename(group) {
|
||||
handleGroupMenuVisible(localGroupMenuKey(group), false);
|
||||
modalStore
|
||||
.prompt({
|
||||
title: t('prompt.change_favorite_group_name.header'),
|
||||
description: t('prompt.change_favorite_group_name.description'),
|
||||
confirmText: t('prompt.change_favorite_group_name.change'),
|
||||
cancelText: t('prompt.change_favorite_group_name.cancel'),
|
||||
pattern: /\S+/,
|
||||
inputValue: group,
|
||||
errorMessage: t('prompt.change_favorite_group_name.input_error')
|
||||
})
|
||||
.then(({ ok, value }) => {
|
||||
if (!ok) return;
|
||||
const newName = value.trim();
|
||||
if (!newName || newName === group) {
|
||||
return;
|
||||
}
|
||||
renameLocalFriendFavoriteGroup(newName, group);
|
||||
if (isGroupActive('local', group)) {
|
||||
selectGroup('local', newName);
|
||||
}
|
||||
toast.success(t('prompt.change_favorite_group_name.message.success'));
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function handleLocalDelete(group) {
|
||||
handleGroupMenuVisible(localGroupMenuKey(group), false);
|
||||
modalStore
|
||||
.confirm({
|
||||
description: 'Continue? Delete Group',
|
||||
title: 'Confirm'
|
||||
})
|
||||
.then(({ ok }) => {
|
||||
if (!ok) return;
|
||||
deleteLocalFriendFavoriteGroup(group);
|
||||
if (isGroupActive('local', group)) {
|
||||
selectDefaultGroup();
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -1007,6 +1241,19 @@
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.group-item--new {
|
||||
border-style: dashed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.group-item__input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.favorites-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -24,7 +24,10 @@
|
||||
<Checkbox v-model="isSelected" />
|
||||
</div>
|
||||
<div class="favorites-search-card__action-group">
|
||||
<div class="favorites-search-card__action favorites-search-card__action--full" @click.stop>
|
||||
<div
|
||||
v-if="group?.type !== 'local'"
|
||||
class="favorites-search-card__action favorites-search-card__action--full"
|
||||
@click.stop>
|
||||
<FavoritesMoveDropdown
|
||||
:favoriteGroup="favoriteFriendGroups"
|
||||
:currentGroup="group"
|
||||
@@ -106,7 +109,7 @@
|
||||
const emit = defineEmits(['click', 'toggle-select']);
|
||||
|
||||
const { favoriteFriendGroups } = storeToRefs(useFavoriteStore());
|
||||
const { showFavoriteDialog } = useFavoriteStore();
|
||||
const { showFavoriteDialog, removeLocalFriendFavorite } = useFavoriteStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const isSelected = computed({
|
||||
@@ -133,8 +136,12 @@
|
||||
});
|
||||
|
||||
function handleDeleteFavorite() {
|
||||
favoriteRequest.deleteFavorite({
|
||||
objectId: props.favorite.id
|
||||
});
|
||||
if (props.group?.type === 'local') {
|
||||
removeLocalFriendFavorite(props.favorite.id, props.group.key);
|
||||
} else {
|
||||
favoriteRequest.deleteFavorite({
|
||||
objectId: props.favorite.id
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -119,9 +119,22 @@
|
||||
<SelectValue :placeholder="t('view.settings.general.favorites.group_placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="group in favoriteFriendGroups" :key="group.key" :value="group.key">
|
||||
{{ group.displayName }}
|
||||
</SelectItem>
|
||||
<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>
|
||||
@@ -189,7 +202,15 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../../components/ui/select';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '../../../../components/ui/select';
|
||||
import { useFavoriteStore, useGeneralSettingsStore, useVRCXUpdaterStore } from '../../../../stores';
|
||||
import { ToggleGroup, ToggleGroupItem } from '../../../../components/ui/toggle-group';
|
||||
import { links } from '../../../../shared/constants';
|
||||
@@ -231,7 +252,7 @@
|
||||
promptProxySettings
|
||||
} = generalSettingsStore;
|
||||
|
||||
const { favoriteFriendGroups } = storeToRefs(favoriteStore);
|
||||
const { favoriteFriendGroups, localFriendFavoriteGroups } = storeToRefs(favoriteStore);
|
||||
|
||||
const { appVersion, autoUpdateVRCX, latestAppVersion, noUpdater } = storeToRefs(vrcxUpdaterStore);
|
||||
const { setAutoUpdateVRCX, checkForVRCXUpdate, showVRCXUpdateDialog, showChangeLogDialog } = vrcxUpdaterStore;
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
useFavoriteStore,
|
||||
useFriendStore,
|
||||
useGameStore,
|
||||
useGeneralSettingsStore,
|
||||
useLocationStore,
|
||||
useUserStore
|
||||
} from '../../../stores';
|
||||
@@ -114,6 +115,8 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const generalSettingsStore = useGeneralSettingsStore();
|
||||
|
||||
const friendStore = useFriendStore();
|
||||
const { vipFriends, onlineFriends, activeFriends, offlineFriends, friendsInSameInstance } =
|
||||
storeToRefs(friendStore);
|
||||
@@ -121,7 +124,8 @@
|
||||
storeToRefs(useAppearanceSettingsStore());
|
||||
const { gameLogDisabled } = storeToRefs(useAdvancedSettingsStore());
|
||||
const { showUserDialog } = useUserStore();
|
||||
const { favoriteFriendGroups, groupedByGroupKeyFavoriteFriends } = storeToRefs(useFavoriteStore());
|
||||
const { favoriteFriendGroups, groupedByGroupKeyFavoriteFriends, localFriendFavorites } =
|
||||
storeToRefs(useFavoriteStore());
|
||||
const { lastLocation, lastLocationDestination } = storeToRefs(useLocationStore());
|
||||
const { isGameRunning } = storeToRefs(useGameStore());
|
||||
const { currentUser } = storeToRefs(useUserStore());
|
||||
@@ -191,6 +195,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
for (const selectedKey of generalSettingsStore.localFavoriteFriendsGroups) {
|
||||
if (selectedKey.startsWith('local:')) {
|
||||
const groupName = selectedKey.slice(6);
|
||||
const userIds = localFriendFavorites.value?.[groupName];
|
||||
if (userIds && userIds.length) {
|
||||
const filteredFriends = vipFriends.value.filter((friend) => {
|
||||
if (isSidebarGroupByInstance.value && isHideFriendsInSameInstance.value) {
|
||||
return userIds.includes(friend.id) && !sameInstanceFriendId.value.has(friend.id);
|
||||
}
|
||||
return userIds.includes(friend.id);
|
||||
});
|
||||
if (filteredFriends.length > 0) {
|
||||
result.push(
|
||||
filteredFriends.map((item) => ({
|
||||
groupName,
|
||||
key: selectedKey,
|
||||
...item
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.sort((a, b) => a[0].key.localeCompare(b[0].key));
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user