refactor: Group Dialog (#1207)

This commit is contained in:
pa
2025-04-08 19:49:25 +09:00
committed by GitHub
parent dc5f48808f
commit 63cd5421e2
28 changed files with 5553 additions and 4465 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,110 @@
<template>
<el-dialog
class="x-dialog"
:before-close="beforeDialogClose"
:visible="isGroupLogsExportDialogVisible"
:title="t('dialog.group_member_moderation.export_logs')"
width="650px"
append-to-body
@close="setIsGroupLogsExportDialogVisible"
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<el-checkbox-group
v-model="checkedGroupLogsExportLogsOptions"
style="margin-bottom: 10px"
@change="updateGroupLogsExportContent">
<template v-for="option in checkGroupsLogsExportLogsOptions">
<el-checkbox :key="option.label" :label="option.label">
{{ t(option.text) }}
</el-checkbox>
</template>
</el-checkbox-group>
<br />
<el-input
v-model="groupLogsExportContent"
type="textarea"
size="mini"
rows="15"
resize="none"
readonly
style="margin-top: 15px"
@click.native="handleCopyGroupLogsExportContent" />
</el-dialog>
</template>
<script setup>
import { inject, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import utils from '../../../classes/utils';
const beforeDialogClose = inject('beforeDialogClose');
const dialogMouseDown = inject('dialogMouseDown');
const dialogMouseUp = inject('dialogMouseUp');
const { t } = useI18n();
const props = defineProps({
isGroupLogsExportDialogVisible: {
type: Boolean,
default: false
},
groupLogsModerationTable: {
type: Object,
default: () => {}
}
});
const emit = defineEmits(['update:isGroupLogsExportDialogVisible']);
watch(
() => props.isGroupLogsExportDialogVisible,
(newVal) => {
if (newVal) {
updateGroupLogsExportContent();
}
}
);
const groupLogsExportContent = ref('');
const checkGroupsLogsExportLogsOptions = [
{ label: 'created_at', text: 'dialog.group_member_moderation.created_at' },
{ label: 'eventType', text: 'dialog.group_member_moderation.type' },
{ label: 'actorDisplayName', text: 'dialog.group_member_moderation.display_name' },
{ label: 'description', text: 'dialog.group_member_moderation.description' },
{ label: 'data', text: 'dialog.group_member_moderation.data' }
];
const checkedGroupLogsExportLogsOptions = ref([
'created_at',
'eventType',
'actorDisplayName',
'description',
'data'
]);
function updateGroupLogsExportContent() {
const formatter = (str) => (/[\x00-\x1f,"]/.test(str) ? `"${str.replace(/"/g, '""')}"` : str);
const sortedCheckedOptions = checkGroupsLogsExportLogsOptions
.filter((option) => checkedGroupLogsExportLogsOptions.value.includes(option.label))
.map((option) => option.label);
const header = `${sortedCheckedOptions.join(',')}\n`;
const content = props.groupLogsModerationTable.data
.map((item) =>
sortedCheckedOptions
.map((key) => formatter(key === 'data' ? JSON.stringify(item[key]) : item[key]))
.join(',')
)
.join('\n');
groupLogsExportContent.value = header + content; // Update ref
}
function handleCopyGroupLogsExportContent() {
utils.copyToClipboard(groupLogsExportContent.value);
}
function setIsGroupLogsExportDialogVisible() {
emit('update:isGroupLogsExportDialogVisible', false);
}
</script>

View File

@@ -0,0 +1,200 @@
<template>
<el-dialog
ref="groupPostEditDialog"
:before-close="beforeDialogClose"
:visible.sync="groupPostEditDialog.visible"
:title="$t('dialog.group_post_edit.header')"
width="650px"
append-to-body
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<div v-if="groupPostEditDialog.visible">
<h3 v-text="groupPostEditDialog.groupRef.name"></h3>
<el-form :model="groupPostEditDialog" label-width="150px">
<el-form-item :label="$t('dialog.group_post_edit.title')">
<el-input v-model="groupPostEditDialog.title" size="mini"></el-input>
</el-form-item>
<el-form-item :label="$t('dialog.group_post_edit.message')">
<el-input
v-model="groupPostEditDialog.text"
type="textarea"
:rows="4"
:autosize="{ minRows: 4, maxRows: 20 }"
style="margin-top: 10px"
resize="none"></el-input>
</el-form-item>
<el-form-item>
<el-checkbox
v-if="!groupPostEditDialog.postId"
v-model="groupPostEditDialog.sendNotification"
size="small">
{{ $t('dialog.group_post_edit.send_notification') }}
</el-checkbox>
</el-form-item>
<el-form-item :label="$t('dialog.group_post_edit.post_visibility')">
<el-radio-group v-model="groupPostEditDialog.visibility" size="small">
<el-radio label="public">
{{ $t('dialog.group_post_edit.visibility_public') }}
</el-radio>
<el-radio label="group">
{{ $t('dialog.group_post_edit.visibility_group') }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="groupPostEditDialog.visibility === 'group'"
:label="$t('dialog.new_instance.roles')">
<el-select
v-model="groupPostEditDialog.roleIds"
multiple
clearable
:placeholder="$t('dialog.new_instance.role_placeholder')"
style="width: 100%">
<el-option-group :label="$t('dialog.new_instance.role_placeholder')">
<el-option
v-for="role in groupPostEditDialog.groupRef?.roles"
:key="role.id"
:label="role.name"
:value="role.id"
style="height: auto; width: 478px">
<div class="detail">
<span class="name" v-text="role.name"></span>
</div>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item :label="$t('dialog.group_post_edit.image')">
<template v-if="gallerySelectDialog.selectedFileId">
<div style="display: inline-block; flex: none; margin-right: 5px">
<el-popover placement="right" width="500px" trigger="click">
<img
slot="reference"
v-lazy="gallerySelectDialog.selectedImageUrl"
style="
flex: none;
width: 60px;
height: 60px;
border-radius: 4px;
object-fit: cover;
" />
<img
v-lazy="gallerySelectDialog.selectedImageUrl"
style="height: 500px"
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" />
</el-popover>
<el-button size="mini" style="vertical-align: top" @click="clearImageGallerySelect">
{{ $t('dialog.invite_message.clear_selected_image') }}
</el-button>
</div>
</template>
<template v-else>
<el-button size="mini" style="margin-right: 5px" @click="showGallerySelectDialog">
{{ $t('dialog.invite_message.select_image') }}
</el-button>
</template>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button size="small" @click="groupPostEditDialog.visible = false">
{{ $t('dialog.group_post_edit.cancel') }}
</el-button>
<el-button v-if="groupPostEditDialog.postId" size="small" @click="editGroupPost">
{{ $t('dialog.group_post_edit.edit_post') }}
</el-button>
<el-button v-else size="small" @click="createGroupPost">
{{ $t('dialog.group_post_edit.create_post') }}
</el-button>
</template>
</el-dialog>
</template>
<script>
import { groupRequest } from '../../../classes/request';
export default {
name: 'GroupPostEditDialog',
inject: [
'beforeDialogClose',
'showFullscreenImageDialog',
'dialogMouseDown',
'dialogMouseUp',
'showGallerySelectDialog'
],
props: {
dialogData: {
type: Object,
required: true
},
gallerySelectDialog: {
type: Object,
required: true
}
},
computed: {
groupPostEditDialog: {
get() {
return this.dialogData;
},
set(value) {
this.$emit('update:dialog-data', value);
}
}
},
methods: {
editGroupPost() {
const D = this.groupPostEditDialog;
if (!D.groupId || !D.postId) {
return;
}
const params = {
groupId: D.groupId,
postId: D.postId,
title: D.title,
text: D.text,
roleIds: D.roleIds,
visibility: D.visibility,
imageId: null
};
if (this.gallerySelectDialog.selectedFileId) {
params.imageId = this.gallerySelectDialog.selectedFileId;
}
groupRequest.editGroupPost(params).then((args) => {
this.$message({
message: 'Group post edited',
type: 'success'
});
return args;
});
D.visible = false;
},
createGroupPost() {
const D = this.groupPostEditDialog;
const params = {
groupId: D.groupId,
title: D.title,
text: D.text,
roleIds: D.roleIds,
visibility: D.visibility,
sendNotification: D.sendNotification,
imageId: null
};
if (this.gallerySelectDialog.selectedFileId) {
params.imageId = this.gallerySelectDialog.selectedFileId;
}
groupRequest.createGroupPost(params).then((args) => {
this.$message({
message: 'Group post created',
type: 'success'
});
return args;
});
D.visible = false;
},
clearImageGallerySelect() {
this.$emit('clear-image-gallery-select');
}
}
};
</script>

View File

@@ -0,0 +1,307 @@
<template>
<el-dialog
ref="inviteGroupDialog"
:visible.sync="inviteGroupDialog.visible"
:before-close="beforeDialogClose"
:title="$t('dialog.invite_to_group.header')"
width="450px"
@mousedown.native="dialogMouseDown"
@mouseup.native="dialogMouseUp">
<div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading">
<span>{{ $t('dialog.invite_to_group.description') }}</span>
<br />
<el-select
v-model="inviteGroupDialog.groupId"
clearable
:placeholder="$t('dialog.invite_to_group.choose_group_placeholder')"
filterable
:disabled="inviteGroupDialog.loading"
style="margin-top: 15px"
@change="isAllowedToInviteToGroup">
<el-option-group
v-if="API.currentUserGroups.size"
:label="$t('dialog.invite_to_group.groups')"
style="width: 410px">
<el-option
v-for="group in API.currentUserGroups.values()"
:key="group.id"
:label="group.name"
:value="group.id"
style="height: auto"
class="x-friend-item">
<div class="avatar">
<img v-lazy="group.iconUrl" />
</div>
<div class="detail">
<span class="name" v-text="group.name"></span>
</div>
</el-option>
</el-option-group>
</el-select>
<el-select
v-model="inviteGroupDialog.userIds"
multiple
clearable
:placeholder="$t('dialog.invite_to_group.choose_friends_placeholder')"
filterable
:disabled="inviteGroupDialog.loading"
style="width: 100%; margin-top: 15px">
<el-option-group v-if="inviteGroupDialog.userId" :label="$t('dialog.invite_to_group.selected_users')">
<el-option
:key="inviteGroupDialog.userObject.id"
:label="inviteGroupDialog.userObject.displayName"
:value="inviteGroupDialog.userObject.id"
class="x-friend-item">
<template v-if="inviteGroupDialog.userObject.id">
<div class="avatar" :class="userStatusClass(inviteGroupDialog.userObject)">
<img v-lazy="userImage(inviteGroupDialog.userObject)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: inviteGroupDialog.userObject.$userColour }"
v-text="inviteGroupDialog.userObject.displayName"></span>
</div>
</template>
<span v-else v-text="inviteGroupDialog.userId"></span>
</el-option>
</el-option-group>
<el-option-group v-if="vipFriends.length" :label="$t('side_panel.favorite')">
<el-option
v-for="friend in vipFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar" :class="userStatusClass(friend.ref)">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="onlineFriends.length" :label="$t('side_panel.online')">
<el-option
v-for="friend in onlineFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar" :class="userStatusClass(friend.ref)">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="activeFriends.length" :label="$t('side_panel.active')">
<el-option
v-for="friend in activeFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
<el-option-group v-if="offlineFriends.length" :label="$t('side_panel.offline')">
<el-option
v-for="friend in offlineFriends"
:key="friend.id"
:label="friend.name"
:value="friend.id"
style="height: auto"
class="x-friend-item">
<template v-if="friend.ref">
<div class="avatar">
<img v-lazy="userImage(friend.ref)" />
</div>
<div class="detail">
<span
class="name"
:style="{ color: friend.ref.$userColour }"
v-text="friend.ref.displayName"></span>
</div>
</template>
<span v-else v-text="friend.id"></span>
</el-option>
</el-option-group>
</el-select>
</div>
<template #footer>
<el-button
type="primary"
size="small"
:disabled="inviteGroupDialog.loading || !inviteGroupDialog.userIds.length"
@click="sendGroupInvite">
Invite
</el-button>
</template>
</el-dialog>
</template>
<script>
import { groupRequest, userRequest } from '../../../classes/request';
import utils from '../../../classes/utils';
export default {
name: 'InviteGroupDialog',
inject: [
'API',
'dialogMouseDown',
'dialogMouseUp',
'beforeDialogClose',
'userStatusClass',
'userImage',
'adjustDialogZ'
],
props: {
dialogData: {
type: Object,
required: true
},
vipFriends: {
type: Array,
required: true
},
onlineFriends: {
type: Array,
required: true
},
activeFriends: {
type: Array,
required: true
},
offlineFriends: {
type: Array,
required: true
}
},
computed: {
inviteGroupDialog: {
get() {
return this.dialogData;
},
set(value) {
this.$emit('update:dialog-data', value);
}
}
},
watch: {
'dialogData.visible'(value) {
if (value) {
this.initDialog();
}
}
},
methods: {
initDialog() {
this.$nextTick(() => this.adjustDialogZ(this.$refs.inviteGroupDialog.$el));
const D = this.inviteGroupDialog;
if (D.groupId) {
this.API.getCachedGroup({
groupId: D.groupId
})
.then((args) => {
D.groupName = args.ref.name;
})
.catch(() => {
D.groupId = '';
});
this.isAllowedToInviteToGroup();
}
if (D.userId) {
userRequest.getCachedUser({ userId: D.userId }).then((args) => {
D.userObject = args.ref;
D.userIds = [D.userId];
});
}
},
isAllowedToInviteToGroup() {
const D = this.inviteGroupDialog;
const groupId = D.groupId;
if (!groupId) {
return;
}
this.inviteGroupDialog.loading = true;
groupRequest
.getGroup({ groupId })
.then((args) => {
if (utils.hasGroupPermission(args.ref, 'group-invites-manage')) {
return args;
}
// not allowed to invite
this.inviteGroupDialog.groupId = '';
this.$message({
type: 'error',
message: 'You are not allowed to invite to this group'
});
return args;
})
.finally(() => {
this.inviteGroupDialog.loading = false;
});
},
sendGroupInvite() {
this.$confirm('Continue? Invite User(s) To Group', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
const D = this.inviteGroupDialog;
if (action !== 'confirm' || D.loading === true) {
return;
}
D.loading = true;
const inviteLoop = () => {
if (D.userIds.length === 0) {
D.loading = false;
return;
}
const receiverUserId = D.userIds.shift();
groupRequest
.sendGroupInvite({
groupId: D.groupId,
userId: receiverUserId
})
.then(inviteLoop)
.catch(() => {
D.loading = false;
});
};
inviteLoop();
}
});
}
}
};
</script>

View File

@@ -490,7 +490,7 @@
</template>
<script>
import { instanceRequest } from '../../../classes/request';
import { groupRequest, instanceRequest } from '../../../classes/request';
import utils from '../../../classes/utils';
import configRepository from '../../../repository/config';
@@ -508,10 +508,6 @@
'adjustDialogZ'
],
props: {
hasGroupPermission: {
type: Function,
required: true
},
vipFriends: {
type: Array,
required: true
@@ -603,7 +599,7 @@
D.strict = false;
D.shortName = '';
D.secureOrShortName = '';
this.API.getGroupPermissions({ userId: this.API.currentUser.id });
groupRequest.getGroupPermissions({ userId: this.API.currentUser.id });
this.buildInstance();
this.buildLegacyInstance();
this.updateNewInstanceDialog();
@@ -738,13 +734,15 @@
if (typeof ref !== 'undefined') {
D.groupRef = ref;
D.selectedGroupRoles = ref.roles;
this.API.getGroupRoles({
groupId: D.groupId
}).then((args) => {
D.lastSelectedGroupId = D.groupId;
D.selectedGroupRoles = args.json;
ref.roles = args.json;
});
groupRequest
.getGroupRoles({
groupId: D.groupId
})
.then((args) => {
D.lastSelectedGroupId = D.groupId;
D.selectedGroupRoles = args.json;
ref.roles = args.json;
});
}
}
if (!D.groupId) {
@@ -811,13 +809,15 @@
if (typeof ref !== 'undefined') {
D.groupRef = ref;
D.selectedGroupRoles = ref.roles;
this.API.getGroupRoles({
groupId: D.groupId
}).then((args) => {
D.lastSelectedGroupId = D.groupId;
D.selectedGroupRoles = args.json;
ref.roles = args.json;
});
groupRequest
.getGroupRoles({
groupId: D.groupId
})
.then((args) => {
D.lastSelectedGroupId = D.groupId;
D.selectedGroupRoles = args.json;
ref.roles = args.json;
});
}
}
if (!D.groupId) {
@@ -868,6 +868,9 @@
});
console.error(error.message);
}
},
hasGroupPermission(ref, permission) {
return utils.hasGroupPermission(ref, permission);
}
}
};

View File

@@ -756,8 +756,7 @@
:offline-friends="offlineFriends"
:active-friends="activeFriends"
:online-friends="onlineFriends"
:vip-friends="vipFriends"
:has-group-permission="hasGroupPermission" />
:vip-friends="vipFriends" />
</el-dialog>
</template>
@@ -787,7 +786,8 @@
'dialogMouseUp',
'displayPreviousImages',
'showWorldDialog',
'showFavoriteDialog'
'showFavoriteDialog',
'openExternalLink'
],
props: {
worldDialog: Object,
@@ -804,7 +804,6 @@
activeFriends: Array,
onlineFriends: Array,
vipFriends: Array,
hasGroupPermission: Function,
// TODO: Remove
updateInstanceInfo: Number
@@ -1034,7 +1033,7 @@
this.isSetWorldTagsDialogVisible = true;
break;
case 'Download Unity Package':
utils.openExternalLink(this.replaceVrcPackageUrl(this.worldDialog.ref.unityPackageUrl));
this.openExternalLink(this.replaceVrcPackageUrl(this.worldDialog.ref.unityPackageUrl));
break;
case 'Change Image':
this.displayPreviousImages('World', 'Change');

View File

@@ -279,11 +279,11 @@
inject: [
'userImage',
'userImageFull',
'getFaviconUrl',
'showFullscreenImageDialog',
'showUserDialog',
'statusClass',
'openExternalLink'
'openExternalLink',
'languageClass'
],
props: {
friends: {
@@ -293,7 +293,6 @@
hideTooltips: Boolean,
randomUserColours: Boolean,
sortStatus: Function,
languageClass: Function,
confirmDeleteFriend: Function,
friendsListSearch: String,
menuActiveIndex: String,
@@ -504,6 +503,9 @@
},
timeToText(val) {
return utils.timeToText(val);
},
getFaviconUrl(link) {
return utils.getFaviconUrl(link);
}
}
};