diff --git a/html/src/app.js b/html/src/app.js index 30c061ff..949f19a0 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -22692,7 +22692,7 @@ speechSynthesis.getVoices(); API.$on('LOGIN', async function () { $app.avatarHistory = new Set(); - var historyArray = await database.getAvatarHistory(); + var historyArray = await database.getAvatarHistory(API.currentUser.id); $app.avatarHistoryArray = historyArray; for (var i = 0; i < historyArray.length; i++) { $app.avatarHistory.add(historyArray[i].id); @@ -24641,6 +24641,32 @@ speechSynthesis.getVoices(); } }; + $app.data.groupDialogSortingOptions = { + joinedAtDesc: { + name: $t('dialog.group.members.sorting.joined_at_desc'), + value: 'joinedAt:desc' + }, + joinedAtAsc: { + name: $t('dialog.group.members.sorting.joined_at_asc'), + value: 'joinedAt:asc' + }, + userId: { + name: $t('dialog.group.members.sorting.user_id'), + value: '' + } + }; + + $app.data.groupDialogFilterOptions = { + everyone: { + name: $t('dialog.group.members.filters.everyone'), + id: null + }, + usersWithNoRole: { + name: $t('dialog.group.members.filters.users_with_no_role'), + id: '' + } + }; + $app.data.groupDialog = { visible: false, loading: false, @@ -24654,6 +24680,8 @@ speechSynthesis.getVoices(); members: [], instances: [], memberRoles: [], + memberFilter: $app.data.groupDialogFilterOptions.everyone, + memberSortOrder: $app.data.groupDialogSortingOptions.joinedAtDesc, galleries: {} }; @@ -24678,6 +24706,7 @@ speechSynthesis.getVoices(); } if (this.groupDialogLastMembers !== groupId) { D.members = []; + D.memberFilter = this.groupDialogFilterOptions.everyone; } API.getCachedGroup({ groupId @@ -24950,6 +24979,12 @@ speechSynthesis.getVoices(); offset: 0, groupId: D.id }; + if (D.memberSortOrder.value) { + this.loadMoreGroupMembersParams.sort = D.memberSortOrder.value; + } + if (D.memberFilter.id !== null) { + this.loadMoreGroupMembersParams.roleId = D.memberFilter.id; + } if (D.inGroup) { await API.getGroupMember({ groupId: D.id, @@ -24957,7 +24992,10 @@ speechSynthesis.getVoices(); }).then((args) => { if (args.json) { args.json.user = API.currentUser; - D.members.push(args.json); + if (D.memberFilter.id === null) { + // when flitered by role don't include self + D.members.push(args.json); + } } return args; }); @@ -24969,6 +25007,7 @@ speechSynthesis.getVoices(); if (this.isGroupMembersDone || this.isGroupMembersLoading) { return; } + var D = this.groupDialog; var params = this.loadMoreGroupMembersParams; this.isGroupMembersLoading = true; await API.getGroupMembers(params) @@ -24979,19 +25018,20 @@ speechSynthesis.getVoices(); for (var i = 0; i < args.json.length; i++) { var member = args.json[i]; if (member.userId === API.currentUser.id) { - // remove self from array - // when fetching only friends self is included - args.json.splice(i, 1); + if ( + D.members.length > 0 && + D.members[0].userId === API.currentUser.id + ) { + // remove duplicate and keep sort order + D.members.splice(0, 1); + } break; } } if (args.json.length < params.n) { this.isGroupMembersDone = true; } - this.groupDialog.members = [ - ...this.groupDialog.members, - ...args.json - ]; + D.members = [...D.members, ...args.json]; params.offset += params.n; return args; }) @@ -25011,6 +25051,24 @@ speechSynthesis.getVoices(); } }; + $app.methods.setGroupMemberSortOrder = async function (sortOrder) { + var D = this.groupDialog; + if (D.memberSortOrder === sortOrder) { + return; + } + D.memberSortOrder = sortOrder; + await this.getGroupDialogGroupMembers(); + }; + + $app.methods.setGroupMemberFilter = async function (filter) { + var D = this.groupDialog; + if (D.memberFilter === filter) { + return; + } + D.memberFilter = filter; + await this.getGroupDialogGroupMembers(); + }; + $app.methods.hasGroupPermission = function (ref, permission) { if ( ref && diff --git a/html/src/index.pug b/html/src/index.pug index 4751b6e8..c1b98a2c 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -188,9 +188,9 @@ html location(:location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName" :link="false") span(v-else-if="scope.row.type === 'ChatBoxMessage'" v-text="scope.row.text") span(v-else-if="scope.row.type === 'OnPlayerJoined'") - span(v-if="scope.row.platform === 'Desktop'" style="color:#409eff") PC  + span(v-if="scope.row.platform === 'Desktop'" style="color:#409eff") Desktop  span(v-else-if="scope.row.platform === 'VR'" style="color:#409eff") VR  - span(v-else-if="scope.row.platform === 'Quest'" style="color:#67c23a") Q  + span(v-else-if="scope.row.platform === 'Quest'" style="color:#67c23a") Quest  span.x-link(v-text="scope.row.avatar.name" @click="showAvatarDialog(scope.row.avatar.id)") |   span(v-if="!scope.row.inCache" style="color:#aaa") #[i.el-icon-download]  @@ -252,9 +252,9 @@ html location(:location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName" :link="false") span(v-else-if="scope.row.type === 'ChatBoxMessage'" v-text="scope.row.text") span(v-else-if="scope.row.type === 'OnPlayerJoined'") - span(v-if="scope.row.platform === 'Desktop'" style="color:#409eff") PC  + span(v-if="scope.row.platform === 'Desktop'" style="color:#409eff") Desktop  span(v-else-if="scope.row.platform === 'VR'" style="color:#409eff") VR  - span(v-else-if="scope.row.platform === 'Quest'" style="color:#67c23a") Q  + span(v-else-if="scope.row.platform === 'Quest'" style="color:#67c23a") Quest  span.x-link(v-text="scope.row.avatar.name" @click="showAvatarDialog(scope.row.avatar.id)") |   span(v-if="!scope.row.inCache" style="color:#aaa") #[i.el-icon-download]  @@ -1721,7 +1721,7 @@ html el-tooltip(v-else placement="top" :content="$t('dialog.user.actions.favorite_tooltip')" :disabled="hideTooltips") el-button(type="default" @click="userDialogCommand('Add Favorite')" icon="el-icon-star-off" circle) el-dropdown(trigger="click" @command="userDialogCommand" size="small") - el-button(:type="(userDialog.incomingRequest || userDialog.outgoingRequest || userDialog.isShowAvatar) ? 'success' : (userDialog.isBlock || userDialog.isMute || userDialog.isHideAvatar) ? 'danger' : 'default'" icon="el-icon-more" circle style="margin-left:5px") + el-button(:type="(userDialog.incomingRequest || userDialog.outgoingRequest) ? 'success' : (userDialog.isBlock || userDialog.isMute) ? 'danger' : 'default'" icon="el-icon-more" circle style="margin-left:5px") el-dropdown-menu(#default="dropdown") el-dropdown-item(icon="el-icon-refresh" command="Refresh") {{ $t('dialog.user.actions.refresh') }} el-dropdown-item(icon="el-icon-s-order" command="Copy User") {{ $t('dialog.user.actions.copy_url') }} @@ -2465,7 +2465,21 @@ html el-button(type="default" @click="loadAllGroupMembers" size="mini" icon="el-icon-refresh" :loading="isGroupMembersLoading" circle) el-button(type="default" @click="downloadAndSaveJson(`${groupDialog.id}_members`, groupDialog.members)" size="mini" icon="el-icon-download" circle style="margin-left:5px") span(style="font-size:14px;margin-left:5px;margin-right:5px") {{ groupDialog.members.length }}/{{ groupDialog.ref.memberCount }} - ul.infinite-list.x-friend-list(v-if="groupDialog.members.length > 0" v-infinite-scroll="loadMoreGroupMembers" style="margin-top:10px;overflow:auto;max-height:250px") + div(v-if="hasGroupPermission(groupDialog.ref, 'group-members-manage')" style="float:right") + span(style="margin-right:5px") {{ $t('dialog.group.members.sort_by') }} + el-dropdown(@click.native.stop trigger="click" size="small" style="margin-right:5px" :disabled="isGroupMembersLoading") + el-button(size="mini") + span {{ groupDialog.memberSortOrder.name }} #[i.el-icon-arrow-down.el-icon--right] + el-dropdown-menu(#default="dropdown") + el-dropdown-item(v-for="(item) in groupDialogSortingOptions" v-text="item.name" @click.native="setGroupMemberSortOrder(item)") + span(style="margin-right:5px") {{ $t('dialog.group.members.filter') }} + el-dropdown(@click.native.stop trigger="click" size="small" style="margin-right:5px" :disabled="isGroupMembersLoading") + el-button(size="mini") + span {{ groupDialog.memberFilter.name }} #[i.el-icon-arrow-down.el-icon--right] + el-dropdown-menu(#default="dropdown") + el-dropdown-item(v-for="(item) in groupDialogFilterOptions" v-text="item.name" @click.native="setGroupMemberFilter(item)") + el-dropdown-item(v-for="(item) in groupDialog.memberRoles" v-text="item.name" @click.native="setGroupMemberFilter(item)") + ul.infinite-list.x-friend-list(v-if="groupDialog.members.length > 0" v-infinite-scroll="loadMoreGroupMembers" style="margin-top:10px;overflow:auto;max-height:250px;min-width:130px") li.infinite-list-item.x-friend-item(v-for="user in groupDialog.members" :key="user.id" @click="showUserDialog(user.userId)" class="x-friend-item-border") .avatar img(v-lazy="userImage(user.user)") diff --git a/html/src/localization/strings/en.json b/html/src/localization/strings/en.json index 0263533c..3615d625 100644 --- a/html/src/localization/strings/en.json +++ b/html/src/localization/strings/en.json @@ -746,7 +746,18 @@ "header": "Members", "all_members": "All Members", "friends_only": "Friends Only", - "load_more": "Load more..." + "load_more": "Load more...", + "sort_by": "Sort By:", + "sorting": { + "user_id": "User ID (Ascending)", + "joined_at_asc": "Joined At (Ascending)", + "joined_at_desc": "Joined At (Descending)" + }, + "filter": "Filter:", + "filters": { + "everyone": "Everyone", + "users_with_no_role": "Users With No Role" + } }, "gallery": { "header": "Gallery" diff --git a/html/src/repository/database.js b/html/src/repository/database.js index 144abcf4..2493a25d 100644 --- a/html/src/repository/database.js +++ b/html/src/repository/database.js @@ -1693,7 +1693,7 @@ class Database { ); } - async getAvatarHistory() { + async getAvatarHistory(currentUserId) { var data = []; await sqliteService.execute((dbRow) => { var row = { @@ -1710,7 +1710,7 @@ class Database { version: dbRow[13] }; data.push(row); - }, `SELECT * FROM ${Database.userPrefix}_avatar_history INNER JOIN cache_avatar ON cache_avatar.id = ${Database.userPrefix}_avatar_history.avatar_id ORDER BY ${Database.userPrefix}_avatar_history.created_at DESC LIMIT 100`); + }, `SELECT * FROM ${Database.userPrefix}_avatar_history INNER JOIN cache_avatar ON cache_avatar.id = ${Database.userPrefix}_avatar_history.avatar_id WHERE author_id != "${currentUserId}" ORDER BY ${Database.userPrefix}_avatar_history.created_at DESC LIMIT 100`); return data; } diff --git a/html/src/vr.pug b/html/src/vr.pug index 91c93829..f8a22a1b 100644 --- a/html/src/vr.pug +++ b/html/src/vr.pug @@ -505,9 +505,9 @@ html location(:location="feed.location" :hint="feed.worldName" :grouphint="feed.groupName" :link="false" style="margin-left:10px") template(v-else-if="feed.type === 'OnPlayerJoined'") span(style="margin-left:10px;color:#a3a3a3") has joined - span(v-if="feed.platform === 'Desktop'" style="color:#409eff;margin-left:10px") PC + span(v-if="feed.platform === 'Desktop'" style="color:#409eff;margin-left:10px") Desktop span(v-else-if="feed.platform === 'VR'" style="color:#409eff;margin-left:10px") VR - span(v-else-if="feed.platform === 'Quest'" style="color:#67c23a;margin-left:10px") Q + span(v-else-if="feed.platform === 'Quest'" style="color:#67c23a;margin-left:10px") Quest span(v-if="!feed.inCache" style="color:#aaa;margin-left:10px") #[i.el-icon-download] span(v-text="feed.avatar.name" style="margin-left:10px") template(v-else-if="feed.type === 'SpawnEmoji'")