improve: Group all friends by the same instance (#1097)

* improve: Group all friends by the same instance

* style
This commit is contained in:
pa
2025-01-25 03:09:57 +09:00
committed by GitHub
parent 51a1897a76
commit 85c6cb6f4f
4 changed files with 145 additions and 223 deletions
+55 -45
View File
@@ -23140,22 +23140,48 @@ console.log(`isLinux: ${LINUX}`);
'VRCX_sidebarGroupByInstance', 'VRCX_sidebarGroupByInstance',
true true
); );
$app.computed.onlineFriendsInSameInstance = function () {
const groupedItems = {};
this.onlineFriends.forEach((item) => { $app.methods.handleSwitchGroupByInstance = function () {
const key = item.ref?.$location.tag; this.isSidebarGroupByInstance = !this.isSidebarGroupByInstance;
configRepository.setBool(
'VRCX_sidebarGroupByInstance',
this.isSidebarGroupByInstance
);
};
$app.data.isSidebarGroupByInstanceCollapsed =
await configRepository.getBool(
'VRCX_sidebarGroupByInstanceCollapsed',
false
);
$app.methods.toggleSwitchGroupByInstanceCollapsed = function () {
this.isSidebarGroupByInstanceCollapsed =
!this.isSidebarGroupByInstanceCollapsed;
configRepository.setBool(
'VRCX_sidebarGroupByInstanceCollapsed',
this.isSidebarGroupByInstanceCollapsed
);
};
$app.computed.friendsInSameInstance = function () {
const friendsList = {};
const allFriends = [...this.vipFriends, ...this.onlineFriends];
allFriends.forEach((friend) => {
const key = friend.ref?.$location.tag;
if (!key || key === 'private' || key === 'offline') return; if (!key || key === 'private' || key === 'offline') return;
if (!groupedItems[key]) { if (!friendsList[key]) {
groupedItems[key] = []; friendsList[key] = [];
} }
groupedItems[key].push(item); friendsList[key].push(friend);
}); });
const sortedGroups = []; const sortedFriendsList = [];
for (const group of Object.values(groupedItems)) { for (const group of Object.values(friendsList)) {
if (group.length > 1) { if (group.length > 1) {
sortedGroups.push( sortedFriendsList.push(
group.sort( group.sort(
(a, b) => a.ref?.$location_at - b.ref?.$location_at (a, b) => a.ref?.$location_at - b.ref?.$location_at
) )
@@ -23163,55 +23189,39 @@ console.log(`isLinux: ${LINUX}`);
} }
} }
return sortedGroups.sort((a, b) => b.length - a.length); return sortedFriendsList.sort((a, b) => b.length - a.length);
}; };
$app.computed.onlineFriendsNotInSameInstance = function () { $app.computed.onlineFriendsByGroupStatus = function () {
const friendsInSameInstance = new Set( const sameInstanceTag = new Set(
this.onlineFriendsInSameInstance.flat().map((friend) => friend.id) this.friendsInSameInstance.flatMap((item) =>
item.map((friend) => friend.ref?.$location.tag)
)
); );
return this.onlineFriends.filter( return this.onlineFriends.filter(
(friend) => !friendsInSameInstance.has(friend.id) (item) => !sameInstanceTag.has(item.ref?.$location.tag)
); );
}; };
$app.computed.vipFriendsInSameInstance = function () { $app.computed.vipFriendsByGroupStatus = function () {
const groupedItems = {}; const sameInstanceTag = new Set(
this.friendsInSameInstance.flatMap((item) =>
this.vipFriends.forEach((item) => { item.map((friend) => friend.ref?.$location.tag)
const key = item.ref?.$location.tag; )
if (!key || key === 'private' || key === 'offline') return;
if (!groupedItems[key]) {
groupedItems[key] = [];
}
groupedItems[key].push(item);
});
const sortedGroups = [];
for (const group of Object.values(groupedItems)) {
if (group.length > 1) {
sortedGroups.push(
group.sort(
(a, b) => a.ref?.$location_at - b.ref?.$location_at
)
);
}
}
return sortedGroups.sort((a, b) => b.length - a.length);
};
$app.computed.vipFriendsNotInSameInstance = function () {
const friendsInSameInstance = new Set(
this.vipFriendsInSameInstance.flat().map((friend) => friend.id)
); );
return this.vipFriends.filter( return this.vipFriends.filter(
(friend) => !friendsInSameInstance.has(friend.id) (item) => !sameInstanceTag.has(item.ref?.$location.tag)
); );
}; };
$app.methods.getFriendsLocations = function (friendsArr) {
// prevent the instance title display as "Traveling".
return friendsArr.find((friend) => !friend.ref?.$location?.isTraveling)
?.ref?.location;
};
// #endregion // #endregion
// #region | Electron // #region | Electron
+1
View File
@@ -633,6 +633,7 @@
"friends": "Friends", "friends": "Friends",
"me": "ME", "me": "ME",
"favorite": "FAVORITES", "favorite": "FAVORITES",
"same_instance": "Same Instance",
"online": "ONLINE", "online": "ONLINE",
"active": "ACTIVE", "active": "ACTIVE",
"offline": "OFFLINE", "offline": "OFFLINE",
+1
View File
@@ -633,6 +633,7 @@
"friends": "好友", "friends": "好友",
"me": "我", "me": "我",
"favorite": "星标好友", "favorite": "星标好友",
"same_instance": "同一房间",
"online": "在线", "online": "在线",
"active": "活跃中(仅登录网页端)", "active": "活跃中(仅登录网页端)",
"offline": "离线", "offline": "离线",
+87 -177
View File
@@ -70,34 +70,72 @@ mixin friendsListSidebar
span.extra(v-else v-text='API.currentUser.statusDescription') span.extra(v-else v-text='API.currentUser.statusDescription')
.x-friend-group.x-link( .x-friend-group.x-link(
@click='isVIPFriends = !isVIPFriends; saveFriendsGroupStates()' @click='isVIPFriends = !isVIPFriends; saveFriendsGroupStates()'
v-show='vipFriends.length') v-show='vipFriendsByGroupStatus.length')
i.el-icon-arrow-right(:class='{ rotate: isVIPFriends }') i.el-icon-arrow-right(:class='{ rotate: isVIPFriends }')
span(style='margin-left: 5px') {{ $t('side_panel.favorite') }} ― {{ vipFriends.length }} span(style='margin-left: 5px') {{ $t('side_panel.favorite') }} ― {{ vipFriendsByGroupStatus.length }}
div(v-show='isVIPFriends') div(v-show='isVIPFriends')
template(v-if='isSidebarGroupByInstance') .x-friend-item(
div( v-for='friend in vipFriendsByGroupStatus'
v-for='(friendArr, idx) in vipFriendsInSameInstance' :key='friend.id'
:key='friendArr[0].ref?.location.tag') @click='showUserDialog(friend.id)')
template(v-if='friend.ref')
.avatar(:class='userStatusClass(friend.ref, friend.pendingOffline)')
img(v-lazy='userImage(friend.ref)')
.detail
span.name(
v-if='!hideNicknames && friend.$nickName'
:style='{ color: friend.ref.$userColour }') {{ friend.ref.displayName }} ({{ friend.$nickName }})
span.name(
v-else
v-text='friend.ref.displayName'
:style='{ color: friend.ref.$userColour }')
span.extra(v-if='friend.pendingOffline') #[i.el-icon-warning-outline] {{ $t('side_panel.pending_offline') }}
location.extra(
v-else
:location='friend.ref.location'
:traveling='friend.ref.travelingToLocation'
:link='false')
template(v-else)
span(v-text='friend.name || friend.id')
el-button(
type='text'
icon='el-icon-close'
size='mini'
@click.stop='confirmDeleteFriend(friend.id)'
style='margin-left: 5px')
//- Group By Instance
template(v-if='isSidebarGroupByInstance && friendsInSameInstance.length')
.x-friend-group.x-link(@click='toggleSwitchGroupByInstanceCollapsed')
i.el-icon-arrow-right(:class='{ rotate: isSidebarGroupByInstanceCollapsed }')
span(style='margin-left: 5px') {{ $t('side_panel.same_instance') }} ― {{ friendsInSameInstance.length }}
div(v-show='!isSidebarGroupByInstanceCollapsed')
div(v-for='friendArr in friendsInSameInstance' :key='friendArr[0].ref?.location.tag')
div(style='margin-bottom: 3px') div(style='margin-bottom: 3px')
location.extra(:location='friendArr[0].ref?.location' style='color: #c7c7c7') location.extra(:location='getFriendsLocations(friendArr)' style='color: #c7c7c7')
span(style='margin-left: 5px') {{ `(${friendArr.length})` }} span(style='margin-left: 5px') {{ `(${friendArr.length})` }}
div(style='margin-bottom: 10px') div
.x-friend-item( .x-friend-item(
v-if='friendArr && friendArr.length' v-if='friendArr && friendArr.length'
v-for='friend in friendArr' v-for='(friend, idx) in friendArr'
:key='friend.id' :key='friend.id'
@click='showUserDialog(friend.id)') @click='showUserDialog(friend.id)'
:style='{ "margin-bottom": idx === friendArr.length - 1 ? "5px" : undefined }')
template(v-if='friend.ref') template(v-if='friend.ref')
.avatar(:class='userStatusClass(friend.ref, friend.pendingOffline)') .avatar(:class='userStatusClass(friend.ref, friend.pendingOffline)')
img(v-lazy='userImage(friend.ref)') img(v-lazy='userImage(friend.ref)')
.detail .detail
span.name( div(style='display: flex; align-items: center')
v-if='!hideNicknames && friend.$nickName' span.name(
:style='{ color: friend.ref.$userColour }') {{ friend.ref.displayName }} ({{ friend.$nickName }}) v-if='!hideNicknames && friend.$nickName'
span.name( :style='{ color: friend.ref.$userColour }') {{ friend.ref.displayName }} ({{ friend.$nickName }})
v-else span.name(
v-text='friend.ref.displayName' v-else
:style='{ color: friend.ref.$userColour }') v-text='friend.ref.displayName'
:style='{ color: friend.ref.$userColour }')
i.el-icon-star-on(
v-if='friend.isVIP'
style='color: #ffd700; margin: 1px 0 0 2px')
span.extra(v-if='friend.pendingOffline') #[i.el-icon-warning-outline] {{ $t('side_panel.pending_offline') }} span.extra(v-if='friend.pendingOffline') #[i.el-icon-warning-outline] {{ $t('side_panel.pending_offline') }}
template(v-else) template(v-else)
i.el-icon.el-icon-loading( i.el-icon.el-icon-loading(
@@ -112,170 +150,42 @@ mixin friendsListSidebar
size='mini' size='mini'
@click.stop='confirmDeleteFriend(friend.id)' @click.stop='confirmDeleteFriend(friend.id)'
style='margin-left: 5px') style='margin-left: 5px')
div(v-if='idx === vipFriendsInSameInstance.length - 1' style='color: #c7c7c7') {{ 'Others' }}
.x-friend-item(
v-for='friend in vipFriendsNotInSameInstance'
:key='friend.id'
@click='showUserDialog(friend.id)')
template(v-if='friend.ref')
.avatar(:class='userStatusClass(friend.ref, friend.pendingOffline)')
img(v-lazy='userImage(friend.ref)')
.detail
span.name(
v-if='!hideNicknames && friend.$nickName'
:style='{ color: friend.ref.$userColour }') {{ friend.ref.displayName }} ({{ friend.$nickName }})
span.name(
v-else
v-text='friend.ref.displayName'
:style='{ color: friend.ref.$userColour }')
span.extra(v-if='friend.pendingOffline') #[i.el-icon-warning-outline] {{ $t('side_panel.pending_offline') }}
location.extra(
v-else
:location='friend.ref.location'
:traveling='friend.ref.travelingToLocation'
:link='false')
template(v-else)
span(v-text='friend.name || friend.id')
el-button(
type='text'
icon='el-icon-close'
size='mini'
@click.stop='confirmDeleteFriend(friend.id)'
style='margin-left: 5px')
template(v-else)
.x-friend-item(
v-for='friend in vipFriends'
:key='friend.id'
@click='showUserDialog(friend.id)')
template(v-if='friend.ref')
.avatar(:class='userStatusClass(friend.ref, friend.pendingOffline)')
img(v-lazy='userImage(friend.ref)')
.detail
span.name(
v-if='!hideNicknames && friend.$nickName'
:style='{ color: friend.ref.$userColour }') {{ friend.ref.displayName }} ({{ friend.$nickName }})
span.name(
v-else
v-text='friend.ref.displayName'
:style='{ color: friend.ref.$userColour }')
span.extra(v-if='friend.pendingOffline') #[i.el-icon-warning-outline] {{ $t('side_panel.pending_offline') }}
location.extra(
v-else
:location='friend.ref.location'
:traveling='friend.ref.travelingToLocation'
:link='false')
template(v-else)
span(v-text='friend.name || friend.id')
el-button(
type='text'
icon='el-icon-close'
size='mini'
@click.stop='confirmDeleteFriend(friend.id)'
style='margin-left: 5px')
.x-friend-group.x-link( .x-friend-group.x-link(
@click='isOnlineFriends = !isOnlineFriends; saveFriendsGroupStates()' @click='isOnlineFriends = !isOnlineFriends; saveFriendsGroupStates()'
v-show='onlineFriends.length') v-show='onlineFriendsByGroupStatus.length')
i.el-icon-arrow-right(:class='{ rotate: isOnlineFriends }') i.el-icon-arrow-right(:class='{ rotate: isOnlineFriends }')
span(style='margin-left: 5px') {{ $t('side_panel.online') }} ― {{ onlineFriends.length }} span(style='margin-left: 5px') {{ $t('side_panel.online') }} ― {{ onlineFriendsByGroupStatus.length }}
div(v-show='isOnlineFriends') div(v-show='isOnlineFriends')
template(v-if='isSidebarGroupByInstance') .x-friend-item(
div( v-for='friend in onlineFriendsByGroupStatus'
v-for='(friendArr, idx) in onlineFriendsInSameInstance' :key='friend.id'
:key='friendArr[0].ref?.location.tag') @click='showUserDialog(friend.id)')
div(style='margin-bottom: 3px') template(v-if='friend.ref')
location.extra(:location='friendArr[0].ref?.location' style='color: #c7c7c7') .avatar(:class='userStatusClass(friend.ref, friend.pendingOffline)')
span(style='margin-left: 5px') {{ `(${friendArr.length})` }} img(v-lazy='userImage(friend.ref)')
div(style='margin-bottom: 10px') .detail
.x-friend-item( span.name(
v-if='friendArr && friendArr.length' v-if='!hideNicknames && friend.$nickName'
v-for='friend in friendArr' :style='{ color: friend.ref.$userColour }') {{ friend.ref.displayName }} ({{ friend.$nickName }})
:key='friend.id' span.name(
@click='showUserDialog(friend.id)') v-else
template(v-if='friend.ref') v-text='friend.ref.displayName'
.avatar(:class='userStatusClass(friend.ref, friend.pendingOffline)') :style='{ color: friend.ref.$userColour }')
img(v-lazy='userImage(friend.ref)') span.extra(v-if='friend.pendingOffline') #[i.el-icon-warning-outline] {{ $t('side_panel.pending_offline') }}
.detail location.extra(
span.name( v-else
v-if='!hideNicknames && friend.$nickName' :location='friend.ref.location'
:style='{ color: friend.ref.$userColour }') {{ friend.ref.displayName }} ({{ friend.$nickName }}) :traveling='friend.ref.travelingToLocation'
span.name( :link='false')
v-else template(v-else)
v-text='friend.ref.displayName' span(v-text='friend.name || friend.id')
:style='{ color: friend.ref.$userColour }') el-button(
span.extra(v-if='friend.pendingOffline') #[i.el-icon-warning-outline] {{ $t('side_panel.pending_offline') }} type='text'
template(v-else) icon='el-icon-close'
i.el-icon.el-icon-loading( size='mini'
v-if='friend.ref.travelingToLocation' @click.stop='confirmDeleteFriend(friend.id)'
style='display: inline-block; margin-right: 5px') style='margin-left: 5px')
timer(:epoch='friend.ref?.$location_at' style='color: #c7c7c7')
template(v-else)
span(v-text='friend.name || friend.id')
el-button(
type='text'
icon='el-icon-close'
size='mini'
@click.stop='confirmDeleteFriend(friend.id)'
style='margin-left: 5px')
div(v-if='idx === onlineFriendsInSameInstance.length - 1' style='color: #c7c7c7') {{ 'Others' }}
.x-friend-item(
v-for='friend in onlineFriendsNotInSameInstance'
:key='friend.id'
@click='showUserDialog(friend.id)')
template(v-if='friend.ref')
.avatar(:class='userStatusClass(friend.ref, friend.pendingOffline)')
img(v-lazy='userImage(friend.ref)')
.detail
span.name(
v-if='!hideNicknames && friend.$nickName'
:style='{ color: friend.ref.$userColour }') {{ friend.ref.displayName }} ({{ friend.$nickName }})
span.name(
v-else
v-text='friend.ref.displayName'
:style='{ color: friend.ref.$userColour }')
span.extra(v-if='friend.pendingOffline') #[i.el-icon-warning-outline] {{ $t('side_panel.pending_offline') }}
location.extra(
v-else
:location='friend.ref.location'
:traveling='friend.ref.travelingToLocation'
:link='false')
template(v-else)
span(v-text='friend.name || friend.id')
el-button(
type='text'
icon='el-icon-close'
size='mini'
@click.stop='confirmDeleteFriend(friend.id)'
style='margin-left: 5px')
template(v-else)
.x-friend-item(
v-for='friend in onlineFriends'
:key='friend.id'
@click='showUserDialog(friend.id)')
template(v-if='friend.ref')
.avatar(:class='userStatusClass(friend.ref, friend.pendingOffline)')
img(v-lazy='userImage(friend.ref)')
.detail
span.name(
v-if='!hideNicknames && friend.$nickName'
:style='{ color: friend.ref.$userColour }') {{ friend.ref.displayName }} ({{ friend.$nickName }})
span.name(
v-else
v-text='friend.ref.displayName'
:style='{ color: friend.ref.$userColour }')
span.extra(v-if='friend.pendingOffline') #[i.el-icon-warning-outline] {{ $t('side_panel.pending_offline') }}
location.extra(
v-else
:location='friend.ref.location'
:traveling='friend.ref.travelingToLocation'
:link='false')
template(v-else)
span(v-text='friend.name || friend.id')
el-button(
type='text'
icon='el-icon-close'
size='mini'
@click.stop='confirmDeleteFriend(friend.id)'
style='margin-left: 5px')
.x-friend-group.x-link( .x-friend-group.x-link(
@click='isActiveFriends = !isActiveFriends; saveFriendsGroupStates()' @click='isActiveFriends = !isActiveFriends; saveFriendsGroupStates()'
v-show='activeFriends.length') v-show='activeFriends.length')