diff --git a/src/App.vue b/src/App.vue index 43608a54..4f6c3d1c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -26,6 +26,7 @@ import WorldDialog from './components/dialogs/WorldDialog/WorldDialog.vue'; import AvatarDialog from './components/dialogs/AvatarDialog/AvatarDialog.vue'; import GroupDialog from './components/dialogs/GroupDialog/GroupDialog.vue'; + import GroupMemberModerationDialog from './components/dialogs/GroupDialog/GroupMemberModerationDialog.vue'; import GalleryDialog from './components/dialogs/GalleryDialog.vue'; import FullscreenImageDialog from './components/dialogs/FullscreenImageDialog.vue'; import PreviousInstancesInfoDialog from './components/dialogs/PreviousInstancesDialog/PreviousInstancesInfoDialog.vue'; @@ -65,6 +66,7 @@ WorldDialog, AvatarDialog, GroupDialog, + GroupMemberModerationDialog, GalleryDialog, FullscreenImageDialog, PreviousInstancesInfoDialog, diff --git a/src/app.pug b/src/app.pug index 4e829c94..3d706ff2 100644 --- a/src/app.pug +++ b/src/app.pug @@ -46,6 +46,8 @@ doctype html GroupDialog + GroupMemberModerationDialog + GalleryDialog FullscreenImageDialog diff --git a/src/components/dialogs/GroupDialog/GroupDialog.vue b/src/components/dialogs/GroupDialog/GroupDialog.vue index 536c4e36..85fbe3c5 100644 --- a/src/components/dialogs/GroupDialog/GroupDialog.vue +++ b/src/components/dialogs/GroupDialog/GroupDialog.vue @@ -318,7 +318,10 @@ {{ t('dialog.group.actions.create_post') }} - + {{ t('dialog.group.actions.moderation_tools') }} @@ -1179,6 +1174,7 @@ downloadAndSaveJson, getFaviconUrl, hasGroupPermission, + hasGroupModerationPermission, languageClass, openExternalLink, refreshInstancePlayerCount, @@ -1195,7 +1191,6 @@ useUserStore } from '../../../stores'; import InviteGroupDialog from '../InviteGroupDialog.vue'; - import GroupMemberModerationDialog from './GroupMemberModerationDialog.vue'; import GroupPostEditDialog from './GroupPostEditDialog.vue'; const { t } = useI18n(); @@ -1212,7 +1207,8 @@ setGroupVisibility, applyGroupMember, handleGroupMember, - handleGroupMemberProps + handleGroupMemberProps, + showGroupMemberModerationDialog } = useGroupStore(); const { lastLocation } = storeToRefs(useLocationStore()); @@ -1245,13 +1241,6 @@ postId: '', groupId: '' }); - const groupMemberModeration = reactive({ - visible: false, - loading: false, - id: '', - groupRef: {}, - auditLogTypes: [] - }); let loadMoreGroupMembersParams = ref({ n: 100, @@ -1292,10 +1281,6 @@ handleGroupRepresentationChange(groupId, false); } - function closeMemberModerationDialog() { - groupMemberModeration.visible = false; - } - function groupMembersSearch() { if (groupMembersSearchTimer.value) { groupMembersSearchPending.value = true; @@ -1539,28 +1524,6 @@ }); } - function showGroupMemberModerationDialog(groupId) { - if (groupId !== groupDialog.value.id) { - return; - } - const D = groupMemberModeration; - D.id = groupId; - - D.groupRef = {}; - D.auditLogTypes = []; - groupRequest.getCachedGroup({ groupId }).then((args) => { - D.groupRef = args.ref; - if (hasGroupPermission(D.groupRef, 'group-audit-view')) { - groupRequest.getGroupAuditLogTypes({ groupId }).then((args) => { - if (groupMemberModeration.id !== args.params.groupId) { - return; - } - groupMemberModeration.auditLogTypes = args.json; - }); - } - }); - D.visible = true; - } function joinGroup(id) { if (!id) { return null; diff --git a/src/components/dialogs/GroupDialog/GroupMemberModerationDialog.vue b/src/components/dialogs/GroupDialog/GroupMemberModerationDialog.vue index fa1f06ab..e21c66cf 100644 --- a/src/components/dialogs/GroupDialog/GroupMemberModerationDialog.vue +++ b/src/components/dialogs/GroupDialog/GroupMemberModerationDialog.vue @@ -1,12 +1,11 @@ diff --git a/src/components/dialogs/UserDialog/UserDialog.vue b/src/components/dialogs/UserDialog/UserDialog.vue index 7ce2ce6f..9a3f6e21 100644 --- a/src/components/dialogs/UserDialog/UserDialog.vue +++ b/src/components/dialogs/UserDialog/UserDialog.vue @@ -447,6 +447,9 @@ {{ t('dialog.user.actions.invite_to_group') }} + {{ + t('dialog.user.actions.group_moderation') + }} {{ t('dialog.user.actions.show_avatar_author') @@ -1782,6 +1785,7 @@ + @@ -1855,6 +1859,7 @@ import PronounsDialog from './PronounsDialog.vue'; import SendInviteRequestDialog from './SendInviteRequestDialog.vue'; import SocialStatusDialog from './SocialStatusDialog.vue'; + import ModerateGroupDialog from '../ModerateGroupDialog.vue'; const { t } = useI18n(); @@ -1880,7 +1885,8 @@ leaveGroup, leaveGroupPrompt, setGroupVisibility, - handleGroupList + handleGroupList, + showModerateGroupDialog } = useGroupStore(); const { currentUserGroups, inviteGroupDialog, inGameGroupOrder } = storeToRefs(useGroupStore()); const { lastLocation, lastLocationDestination } = storeToRefs(useLocationStore()); @@ -2299,6 +2305,8 @@ showInviteGroupDialog('', D.id); // } else if (command === 'Send Boop') { // this.showSendBoopDialog(D.id); + } else if (command === 'Group Moderation') { + showModerateGroupDialog(D.id); } else if (command === 'Hide Avatar') { if (D.isHideAvatar) { setPlayerModeration(D.id, 0); diff --git a/src/localization/en/en.json b/src/localization/en/en.json index b0939d1a..70ce83ac 100644 --- a/src/localization/en/en.json +++ b/src/localization/en/en.json @@ -747,6 +747,7 @@ "request_invite": "Request Invite", "request_invite_with_message": "Request Invite With Message", "invite_to_group": "Invite To Group", + "group_moderation": "Group Moderation", "send_boop": "Send Boop", "manage_gallery_inventory_icon": "Manage VRC+ Images & Inventory", "accept_friend_request": "Accept Friend Request", @@ -1407,9 +1408,10 @@ "header": "Invite To Group", "description": "Don't spam invite users, you'll get rate limited.", "choose_group_placeholder": "Choose Group", - "groups": "Groups", + "groups_with_invite_permission": "Groups with Invite Permission", "choose_friends_placeholder": "Choose Friends", - "selected_users": "Selected Users" + "selected_users": "Selected Users", + "invite": "Invite" }, "note_export": { "header": "Note Export", @@ -1600,6 +1602,7 @@ "select_user": "Select User", "selected_users": "Selected Users", "select_all": "Select All", + "user_isnt_in_group": "User isn't in group", "cancel": "Cancel", "choose_roles_placeholder": "Choose Roles", "selected_roles": "Selected Roles", @@ -1636,6 +1639,12 @@ }, "group_calendar": { "header": "Group Calendar" + }, + "moderate_group": { + "header": "Moderate Group Member", + "choose_group_placeholder": "Choose Group", + "groups_with_moderation_permission": "Groups with Moderation Permission", + "moderation_tools": "Moderation Tools" } }, "confirm": { diff --git a/src/shared/utils/group.js b/src/shared/utils/group.js index d46e0f35..f2a4b251 100644 --- a/src/shared/utils/group.js +++ b/src/shared/utils/group.js @@ -20,6 +20,26 @@ function hasGroupPermission(ref, permission) { return false; } +/** + * + * @param {object} group + * @returns {boolean} + */ +function hasGroupModerationPermission(group) { + return ( + hasGroupPermission(group, 'group-invites-manage') || + hasGroupPermission(group, 'group-moderates-manage') || + hasGroupPermission(group, 'group-audit-view') || + hasGroupPermission(group, 'group-bans-manage') || + hasGroupPermission(group, 'group-data-manage') || + hasGroupPermission(group, 'group-members-manage') || + hasGroupPermission(group, 'group-members-remove') || + hasGroupPermission(group, 'group-roles-assign') || + hasGroupPermission(group, 'group-roles-manage') || + hasGroupPermission(group, 'group-default-role-manage') + ); +} + /** * * @param {string} data @@ -49,4 +69,4 @@ async function getGroupName(data) { return groupName; } -export { hasGroupPermission, getGroupName }; +export { hasGroupPermission, hasGroupModerationPermission, getGroupName }; diff --git a/src/stores/group.js b/src/stores/group.js index 6ff51962..fa5c3068 100644 --- a/src/stores/group.js +++ b/src/stores/group.js @@ -11,7 +11,11 @@ import { $app } from '../app'; import configRepository from '../service/config'; import { watchState } from '../service/watchState'; import { groupDialogFilterOptions } from '../shared/constants/'; -import { replaceBioSymbols, convertFileUrlToImageUrl } from '../shared/utils'; +import { + replaceBioSymbols, + convertFileUrlToImageUrl, + hasGroupPermission +} from '../shared/utils'; import { useGameStore } from './game'; import { useInstanceStore } from './instance'; import { useUserStore } from './user'; @@ -62,6 +66,21 @@ export const useGroupStore = defineStore('Group', () => { userIds: [], userObject: {} }, + moderateGroupDialog: { + visible: false, + groupId: '', + groupName: '', + userId: '', + userObject: {} + }, + groupMemberModeration: { + visible: false, + loading: false, + id: '', + groupRef: {}, + auditLogTypes: [], + openWithUserId: '' + }, cachedGroups: new Map(), inGameGroupOrder: [], groupInstances: [], @@ -89,6 +108,20 @@ export const useGroupStore = defineStore('Group', () => { } }); + const moderateGroupDialog = computed({ + get: () => state.moderateGroupDialog, + set: (value) => { + state.moderateGroupDialog = value; + } + }); + + const groupMemberModeration = computed({ + get: () => state.groupMemberModeration, + set: (value) => { + state.groupMemberModeration = value; + } + }); + const cachedGroups = computed({ get: () => state.cachedGroups, set: (value) => { @@ -122,6 +155,8 @@ export const useGroupStore = defineStore('Group', () => { (isLoggedIn) => { state.groupDialog.visible = false; state.inviteGroupDialog.visible = false; + state.moderateGroupDialog.visible = false; + state.groupMemberModeration.visible = false; state.currentUserGroupsInit = false; state.cachedGroups.clear(); state.currentUserGroups.clear(); @@ -1023,11 +1058,41 @@ export const useGroupStore = defineStore('Group', () => { ); } + function showModerateGroupDialog(userId) { + const D = state.moderateGroupDialog; + D.userId = userId; + D.userObject = {}; + D.visible = true; + } + + function showGroupMemberModerationDialog(groupId, userId = '') { + const D = state.groupMemberModeration; + D.id = groupId; + D.openWithUserId = userId; + + D.groupRef = {}; + D.auditLogTypes = []; + groupRequest.getCachedGroup({ groupId }).then((args) => { + D.groupRef = args.ref; + if (hasGroupPermission(D.groupRef, 'group-audit-view')) { + groupRequest.getGroupAuditLogTypes({ groupId }).then((args) => { + if (D.id !== args.params.groupId) { + return; + } + D.auditLogTypes = args.json; + }); + } + }); + D.visible = true; + } + return { state, groupDialog, currentUserGroups, inviteGroupDialog, + moderateGroupDialog, + groupMemberModeration, cachedGroups, inGameGroupOrder, groupInstances, @@ -1052,6 +1117,8 @@ export const useGroupStore = defineStore('Group', () => { handleGroupPermissions, handleGroupMemberProps, handleGroupList, - handleGroupRepresented + handleGroupRepresented, + showModerateGroupDialog, + showGroupMemberModerationDialog }; });