mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-04 13:56:07 +02:00
add local favorites friend
This commit is contained in:
@@ -30,6 +30,27 @@
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'friend'" style="margin-top: 20px">
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_favorites') }}</span>
|
||||
<template v-for="group in localFriendFavoriteGroups" :key="group">
|
||||
<Button
|
||||
variant="outline"
|
||||
v-if="hasLocalFriendFavorite(favoriteDialog.objectId, group)"
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="removeLocalFriendFavorite(favoriteDialog.objectId, group)">
|
||||
<Check />{{ group }} ({{ localFriendFavGroupLength(group) }})
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
v-else
|
||||
style="width: 100%; white-space: initial"
|
||||
class="my-1"
|
||||
@click="addLocalFriendFavorite(favoriteDialog.objectId, group)">
|
||||
{{ group }} ({{ localFriendFavGroupLength(group) }})
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'world'" style="margin-top: 20px">
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_favorites') }}</span>
|
||||
<template v-for="group in localWorldFavoriteGroups" :key="group">
|
||||
@@ -99,7 +120,8 @@
|
||||
favoriteWorldGroups,
|
||||
favoriteDialog,
|
||||
localWorldFavoriteGroups,
|
||||
localAvatarFavoriteGroups
|
||||
localAvatarFavoriteGroups,
|
||||
localFriendFavoriteGroups
|
||||
} = storeToRefs(favoriteStore);
|
||||
const {
|
||||
localWorldFavGroupLength,
|
||||
@@ -110,7 +132,11 @@
|
||||
localAvatarFavGroupLength,
|
||||
removeLocalAvatarFavorite,
|
||||
removeLocalWorldFavorite,
|
||||
deleteFavoriteNoConfirm
|
||||
deleteFavoriteNoConfirm,
|
||||
localFriendFavGroupLength,
|
||||
addLocalFriendFavorite,
|
||||
hasLocalFriendFavorite,
|
||||
removeLocalFriendFavorite
|
||||
} = favoriteStore;
|
||||
const { isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { avatarFavorites } from './database/avatarFavorites.js';
|
||||
import { feed } from './database/feed.js';
|
||||
import { friendFavorites } from './database/friendFavorites.js';
|
||||
import { friendLogCurrent } from './database/friendLogCurrent.js';
|
||||
import { friendLogHistory } from './database/friendLogHistory.js';
|
||||
import { gameLog } from './database/gameLog.js';
|
||||
@@ -30,6 +31,7 @@ const database = {
|
||||
...friendLogCurrent,
|
||||
...memos,
|
||||
...avatarFavorites,
|
||||
...friendFavorites,
|
||||
...worldFavorites,
|
||||
...tableAlter,
|
||||
...tableFixes,
|
||||
@@ -126,6 +128,9 @@ const database = {
|
||||
await sqliteService.executeNonQuery(
|
||||
`CREATE TABLE IF NOT EXISTS favorite_avatar (id INTEGER PRIMARY KEY, created_at TEXT, avatar_id TEXT, group_name TEXT)`
|
||||
);
|
||||
await sqliteService.executeNonQuery(
|
||||
`CREATE TABLE IF NOT EXISTS favorite_friend (id INTEGER PRIMARY KEY, created_at TEXT, user_id TEXT, group_name TEXT)`
|
||||
);
|
||||
await sqliteService.executeNonQuery(
|
||||
`CREATE TABLE IF NOT EXISTS memos (user_id TEXT PRIMARY KEY, edited_at TEXT, memo TEXT)`
|
||||
);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import sqliteService from '../sqlite.js';
|
||||
|
||||
const friendFavorites = {
|
||||
addFriendToLocalFavorites(userId, groupName) {
|
||||
sqliteService.executeNonQuery(
|
||||
'INSERT OR REPLACE INTO favorite_friend (user_id, group_name, created_at) VALUES (@user_id, @group_name, @created_at)',
|
||||
{
|
||||
'@user_id': userId,
|
||||
'@group_name': groupName,
|
||||
'@created_at': new Date().toJSON()
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
removeFriendFromLocalFavorites(userId, groupName) {
|
||||
sqliteService.executeNonQuery(
|
||||
`DELETE FROM favorite_friend WHERE user_id = @user_id AND group_name = @group_name`,
|
||||
{
|
||||
'@user_id': userId,
|
||||
'@group_name': groupName
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
renameFriendFavoriteGroup(newGroupName, groupName) {
|
||||
sqliteService.executeNonQuery(
|
||||
`UPDATE favorite_friend SET group_name = @new_group_name WHERE group_name = @group_name`,
|
||||
{
|
||||
'@new_group_name': newGroupName,
|
||||
'@group_name': groupName
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
deleteFriendFavoriteGroup(groupName) {
|
||||
sqliteService.executeNonQuery(
|
||||
`DELETE FROM favorite_friend WHERE group_name = @group_name`,
|
||||
{
|
||||
'@group_name': groupName
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
async getFriendFavorites() {
|
||||
const data = [];
|
||||
await sqliteService.execute((dbRow) => {
|
||||
const row = {
|
||||
created_at: dbRow[1],
|
||||
userId: dbRow[2],
|
||||
groupName: dbRow[3]
|
||||
};
|
||||
data.push(row);
|
||||
}, 'SELECT * FROM favorite_friend');
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
export { friendFavorites };
|
||||
+179
-1
@@ -81,6 +81,8 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
|
||||
const localAvatarFavorites = reactive({});
|
||||
|
||||
const localFriendFavorites = reactive({});
|
||||
|
||||
const selectedFavoriteFriends = ref([]);
|
||||
const selectedFavoriteWorlds = ref([]);
|
||||
const selectedFavoriteAvatars = ref([]);
|
||||
@@ -189,6 +191,18 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
return favoriteGroup.length;
|
||||
});
|
||||
|
||||
const localFriendFavoriteGroups = computed(() =>
|
||||
Object.keys(localFriendFavorites).sort()
|
||||
);
|
||||
|
||||
const localFriendFavGroupLength = computed(() => (group) => {
|
||||
const favoriteGroup = localFriendFavorites[group];
|
||||
if (!favoriteGroup) {
|
||||
return 0;
|
||||
}
|
||||
return favoriteGroup.length;
|
||||
});
|
||||
|
||||
function syncFavoriteSelection(list, selectionRef) {
|
||||
if (!Array.isArray(list)) {
|
||||
selectionRef.value = [];
|
||||
@@ -1514,6 +1528,157 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
sortLocalWorldFavorites();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} userId
|
||||
* @param {string} group
|
||||
*/
|
||||
function addLocalFriendFavorite(userId, group) {
|
||||
if (hasLocalFriendFavorite(userId, group)) {
|
||||
return;
|
||||
}
|
||||
if (!localFriendFavorites[group]) {
|
||||
localFriendFavorites[group] = [];
|
||||
}
|
||||
localFriendFavorites[group].unshift(userId);
|
||||
database.addFriendToLocalFavorites(userId, group);
|
||||
if (
|
||||
favoriteDialog.value.visible &&
|
||||
favoriteDialog.value.objectId === userId
|
||||
) {
|
||||
updateFavoriteDialog(userId);
|
||||
}
|
||||
if (
|
||||
generalSettingsStore.localFavoriteFriendsGroups.includes(
|
||||
`local:${group}`
|
||||
)
|
||||
) {
|
||||
friendStore.updateLocalFavoriteFriends();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} userId
|
||||
* @param {string} group
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasLocalFriendFavorite(userId, group) {
|
||||
const favoriteGroup = localFriendFavorites[group];
|
||||
if (!favoriteGroup) {
|
||||
return false;
|
||||
}
|
||||
return favoriteGroup.includes(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} userId
|
||||
* @param {string} group
|
||||
*/
|
||||
function removeLocalFriendFavorite(userId, group) {
|
||||
const favoriteGroup = localFriendFavorites[group];
|
||||
if (favoriteGroup) {
|
||||
const idx = favoriteGroup.indexOf(userId);
|
||||
if (idx !== -1) {
|
||||
favoriteGroup.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
database.removeFriendFromLocalFavorites(userId, group);
|
||||
if (
|
||||
favoriteDialog.value.visible &&
|
||||
favoriteDialog.value.objectId === userId
|
||||
) {
|
||||
updateFavoriteDialog(userId);
|
||||
}
|
||||
if (
|
||||
generalSettingsStore.localFavoriteFriendsGroups.includes(
|
||||
`local:${group}`
|
||||
)
|
||||
) {
|
||||
friendStore.updateLocalFavoriteFriends();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} group
|
||||
*/
|
||||
function deleteLocalFriendFavoriteGroup(group) {
|
||||
delete localFriendFavorites[group];
|
||||
database.deleteFriendFavoriteGroup(group);
|
||||
if (
|
||||
generalSettingsStore.localFavoriteFriendsGroups.includes(
|
||||
`local:${group}`
|
||||
)
|
||||
) {
|
||||
friendStore.updateLocalFavoriteFriends();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} newName
|
||||
* @param {string} group
|
||||
*/
|
||||
function renameLocalFriendFavoriteGroup(newName, group) {
|
||||
if (localFriendFavoriteGroups.value.includes(newName)) {
|
||||
toast.error(
|
||||
t('prompt.local_favorite_group_rename.message.error', {
|
||||
name: newName
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
localFriendFavorites[newName] = localFriendFavorites[group];
|
||||
delete localFriendFavorites[group];
|
||||
database.renameFriendFavoriteGroup(newName, group);
|
||||
const oldKey = `local:${group}`;
|
||||
const idx =
|
||||
generalSettingsStore.localFavoriteFriendsGroups.indexOf(oldKey);
|
||||
if (idx !== -1) {
|
||||
const updated = [
|
||||
...generalSettingsStore.localFavoriteFriendsGroups
|
||||
];
|
||||
updated[idx] = `local:${newName}`;
|
||||
generalSettingsStore.setLocalFavoriteFriendsGroups(updated);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} group
|
||||
*/
|
||||
function newLocalFriendFavoriteGroup(group) {
|
||||
if (localFriendFavoriteGroups.value.includes(group)) {
|
||||
toast.error(
|
||||
t('prompt.new_local_favorite_group.message.error', {
|
||||
name: group
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!localFriendFavorites[group]) {
|
||||
localFriendFavorites[group] = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function getLocalFriendFavorites() {
|
||||
const localFavorites = Object.create(null);
|
||||
|
||||
const favorites = await database.getFriendFavorites();
|
||||
for (let i = 0; i < favorites.length; ++i) {
|
||||
const favorite = favorites[i];
|
||||
if (!localFavorites[favorite.groupName]) {
|
||||
localFavorites[favorite.groupName] = [];
|
||||
}
|
||||
localFavorites[favorite.groupName].unshift(favorite.userId);
|
||||
}
|
||||
|
||||
if (Object.keys(localFavorites).length === 0) {
|
||||
localFavorites.Favorites = [];
|
||||
}
|
||||
|
||||
replaceReactiveObject(localFriendFavorites, localFavorites);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} objectId
|
||||
@@ -1545,6 +1710,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
|
||||
async function saveSortFavoritesOption() {
|
||||
getLocalWorldFavorites();
|
||||
getLocalFriendFavorites();
|
||||
appearanceSettingsStore.setSortFavorites();
|
||||
}
|
||||
|
||||
@@ -1552,6 +1718,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
refreshFavorites();
|
||||
getLocalWorldFavorites();
|
||||
getLocalAvatarFavorites();
|
||||
getLocalFriendFavorites();
|
||||
}
|
||||
|
||||
function compareByFavoriteSortOrder(a, b) {
|
||||
@@ -1588,6 +1755,10 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
localWorldFavoritesList,
|
||||
|
||||
localWorldFavoriteGroups,
|
||||
localFriendFavorites,
|
||||
localFriendFavoriteGroups,
|
||||
|
||||
localFriendFavGroupLength,
|
||||
groupedByGroupKeyFavoriteFriends,
|
||||
selectedFavoriteFriends,
|
||||
selectedFavoriteWorlds,
|
||||
@@ -1632,6 +1803,13 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
||||
getCachedFavoritesByObjectId,
|
||||
checkInvalidLocalAvatars,
|
||||
removeInvalidLocalAvatars,
|
||||
getCachedFavoriteGroupsByTypeName
|
||||
getCachedFavoriteGroupsByTypeName,
|
||||
addLocalFriendFavorite,
|
||||
hasLocalFriendFavorite,
|
||||
removeLocalFriendFavorite,
|
||||
deleteLocalFriendFavoriteGroup,
|
||||
renameLocalFriendFavoriteGroup,
|
||||
newLocalFriendFavoriteGroup,
|
||||
getLocalFriendFavorites
|
||||
};
|
||||
});
|
||||
|
||||
@@ -317,6 +317,17 @@ export const useFriendStore = defineStore('Friend', () => {
|
||||
localFavoriteFriends.add(ref.favoriteId);
|
||||
}
|
||||
}
|
||||
for (const selectedKey of generalSettingsStore.localFavoriteFriendsGroups) {
|
||||
if (selectedKey.startsWith('local:')) {
|
||||
const groupName = selectedKey.slice(6);
|
||||
const userIds = favoriteStore.localFriendFavorites[groupName];
|
||||
if (userIds) {
|
||||
for (let i = 0; i < userIds.length; ++i) {
|
||||
localFavoriteFriends.add(userIds[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
updateSidebarFavorites();
|
||||
}
|
||||
|
||||
|
||||
@@ -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