feat: Friends Sidebar Group By Instance (#1069) (#1087)

* feat: Friends Sidebar Group By Instance

* fix and improve performance of filter data

* rm log
This commit is contained in:
pa
2025-01-22 01:43:58 +09:00
committed by GitHub
parent f27d213a0d
commit fbf4c7cd4b
4 changed files with 187 additions and 26 deletions

View File

@@ -185,6 +185,10 @@ console.log(`isLinux: ${LINUX}`);
this.checkForVRCXUpdate();
}
await AppApi.CheckGameRunning();
this.isSidebarGroupByInstance =
(await configRepository.getBool(
'VRCX_sidebarGroupByInstance'
)) ?? true;
this.isGameNoVR = await configRepository.getBool('isGameNoVR');
await AppApi.SetAppLauncherSettings(
this.enableAppLauncher,
@@ -23108,6 +23112,91 @@ console.log(`isLinux: ${LINUX}`);
return LINUX;
};
// friendsListSiderBar
$app.methods.handleSwitchGroupByInstance = async function () {
this.isSidebarGroupByInstance = !this.isSidebarGroupByInstance;
await configRepository.setBool(
'VRCX_sidebarGroupByInstance',
this.isSidebarGroupByInstance
);
};
// friendsListSidebar
// - SidebarGroupByInstance
$app.data.isSidebarGroupByInstance = true;
$app.computed.onlineFriendsInSameInstance = function () {
const groupedItems = {};
this.onlineFriends.forEach((item) => {
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.onlineFriendsNotInSameInstance = function () {
const friendsInSameInstance = new Set(
this.onlineFriendsInSameInstance.flat().map((friend) => friend.id)
);
return this.onlineFriends.filter(
(friend) => !friendsInSameInstance.has(friend.id)
);
};
$app.computed.vipFriendsInSameInstance = function () {
const groupedItems = {};
this.vipFriends.forEach((item) => {
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(
(friend) => !friendsInSameInstance.has(friend.id)
);
};
// #endregion
// #region | Electron

View File

@@ -383,7 +383,9 @@
"placeholder": "Sort Order",
"dropdown_header": "Choose Sort Order"
},
"width": "Width"
"width": "Width",
"group_by_instance": "Group by Instance",
"group_by_instance_tooltip": "Enabling this will group friends by instance when there is more than one friend in the same instance."
},
"user_dialog": {
"header": "User Dialog",
@@ -2011,4 +2013,4 @@
}
}
}
}
}

View File

@@ -40,34 +40,102 @@ mixin friendsListSidebar()
i.el-icon-arrow-right(:class="{ rotate: isVIPFriends }")
span(style="margin-left:5px") {{ $t('side_panel.favorite') }} ― {{ vipFriends.length }}
div(v-show="isVIPFriends")
.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")
template(v-if='isSidebarGroupByInstance')
div(v-for="(friendArr, idx) in vipFriendsInSameInstance" :key="friendArr[0].ref?.location.tag")
div(style="margin-bottom: 3px")
location.extra(:location="friendArr[0].ref?.location" style="color:#c7c7c7")
span(style="margin-left: 5px") {{ `(${friendArr.length})` }}
div(style="margin-bottom: 10px")
div.x-friend-item(v-if="friendArr && friendArr.length" v-for="friend in friendArr" :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') }}
template(v-else)
i(v-if="friend.ref.travelingToLocation" class="el-icon el-icon-loading" style="display:inline-block;margin-right: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 === 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(@click="isOnlineFriends = !isOnlineFriends; saveFriendsGroupStates()" v-show="onlineFriends.length")
i.el-icon-arrow-right(:class="{ rotate: isOnlineFriends }")
span(style="margin-left:5px") {{ $t('side_panel.online') }} ― {{ onlineFriends.length }}
div(v-show="isOnlineFriends")
.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")
template(v-if='isSidebarGroupByInstance')
div(v-for="(friendArr, idx) in onlineFriendsInSameInstance" :key="friendArr[0].ref?.location.tag")
div(style="margin-bottom: 3px")
location.extra(:location="friendArr[0].ref?.location" style="color:#c7c7c7")
span(style="margin-left: 5px") {{ `(${friendArr.length})` }}
div(style="margin-bottom: 10px")
div.x-friend-item(v-if="friendArr && friendArr.length" v-for="friend in friendArr" :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') }}
template(v-else)
i(v-if="friend.ref.travelingToLocation" class="el-icon el-icon-loading" style="display:inline-block;margin-right: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(@click="isActiveFriends = !isActiveFriends; saveFriendsGroupStates()" v-show="activeFriends.length")
i.el-icon-arrow-right(:class="{ rotate: isActiveFriends }")
span(style="margin-left:5px") {{ $t('side_panel.active') }} ― {{ activeFriends.length }}

View File

@@ -231,6 +231,8 @@ mixin settingsTab()
div.options-container-item
span.name(style="vertical-align:top;padding-top:10px") {{ $t('view.settings.appearance.side_panel.width') }}
el-slider(v-model="asideWidth" @input="setAsideWidth" :show-tooltip="false" :marks="{300: ''}" :min="200" :max="500" style="display:inline-block;width:300px")
div.options-container-item
simple-switch(:label='$t("view.settings.appearance.side_panel.group_by_instance")' :value='isSidebarGroupByInstance' @change='handleSwitchGroupByInstance' :tooltip='$t("view.settings.appearance.side_panel.group_by_instance_tooltip")')
//- Appearance | User Dialog
div.options-container
span.header {{ $t('view.settings.appearance.user_dialog.header') }}