refactor: app.js (#1291)

* refactor: frontend

* Fix avatar gallery sort

* Update .NET dependencies

* Update npm dependencies

electron v37.1.0

* bulkRefreshFriends

* fix dark theme

* Remove crowdin

* Fix config.json dialog not updating

* VRCX log file fixes & add Cef log

* Remove SharedVariable, fix startup

* Revert init theme change

* Logging date not working? Fix WinformThemer designer error

* Add Cef request hander, no more escaping main page

* clean

* fix

* fix

* clean

* uh

* Apply thememode at startup, fixes random user colours

* Split database into files

* Instance info remove empty lines

* Open external VRC links with VRCX

* Electron fixes

* fix userdialog style

* ohhhh

* fix store

* fix store

* fix: load all group members after kicking a user

* fix: world dialog favorite button style

* fix: Clear VRCX Cache Timer input value

* clean

* Fix VR overlay

* Fix VR overlay 2

* Fix Discord discord rich presence for RPC worlds

* Clean up age verified user tags

* Fix playerList being occupied after program reload

* no `this`

* Fix login stuck loading

* writable: false

* Hide dialogs on logout

* add flush sync option

* rm LOGIN event

* rm LOGOUT event

* remove duplicate event listeners

* remove duplicate event listeners

* clean

* remove duplicate event listeners

* clean

* fix theme style

* fix t

* clearable

* clean

* fix ipcEvent

* Small changes

* Popcorn Palace support

* Remove checkActiveFriends

* Clean up

* Fix dragEnterCef

* Block API requests when not logged in

* Clear state on login & logout

* Fix worldDialog instances not updating

* use <script setup>

* Fix avatar change event, CheckGameRunning at startup

* Fix image dragging

* fix

* Remove PWI

* fix updateLoop

* add webpack-dev-server to dev environment

* rm unnecessary chunks

* use <script setup>

* webpack-dev-server changes

* use <script setup>

* use <script setup>

* Fix UGC text size

* Split login event

* t

* use <script setup>

* fix

* Update .gitignore and enable checkJs in jsconfig

* fix i18n t

* use <script setup>

* use <script setup>

* clean

* global types

* fix

* use checkJs for debugging

* Add watchState for login watchers

* fix .vue template

* type fixes

* rm Vue.filter

* Cef v138.0.170, VC++ 2022

* Settings fixes

* Remove 'USER:CURRENT'

* clean up 2FA callbacks

* remove userApply

* rm i18n import

* notification handling to use notification store methods

* refactor favorite handling to use favorite store methods and clean up event emissions

* refactor moderation handling to use dedicated functions for player moderation events

* refactor friend handling to use dedicated functions for friend events

* Fix program startup, move lang init

* Fix friend state

* Fix status change error

* Fix user notes diff

* fix

* rm group event

* rm auth event

* rm avatar event

* clean

* clean

* getUser

* getFriends

* getFavoriteWorlds, getFavoriteAvatars

* AvatarGalleryUpload btn style & package.json update

* Fix friend requests

* Apply user

* Apply world

* Fix note diff

* Fix VR overlay

* Fixes

* Update build scripts

* Apply avatar

* Apply instance

* Apply group

* update hidden VRC+ badge

* Fix sameInstance "private"

* fix 502/504 API errors

* fix 502/504 API errors

* clean

* Fix friend in same instance on orange showing twice in friends list

* Add back in broken friend state repair methods

* add types

---------

Co-authored-by: Natsumi <cmcooper123@hotmail.com>
This commit is contained in:
pa
2025-07-14 12:00:08 +09:00
committed by GitHub
parent 952fd77ed5
commit f4f78bb5ec
323 changed files with 47745 additions and 43326 deletions

View File

@@ -26,7 +26,7 @@
type="default"
size="small"
icon="el-icon-upload2"
:disabled="!API.currentUser.$isVRCPlus"
:disabled="!currentUser.$isVRCPlus"
@click="displayGalleryUpload"
>{{ t('dialog.gallery_select.upload') }}</el-button
>
@@ -50,30 +50,27 @@
</template>
<script setup>
import { inject, getCurrentInstance } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { vrcPlusImageRequest } from '../../../api';
import { useGalleryStore, useUserStore } from '../../../stores';
const { t } = useI18n();
const { proxy } = getCurrentInstance();
const { $message } = proxy;
const API = inject('API');
const { galleryTable } = storeToRefs(useGalleryStore());
const { refreshGalleryTable, handleGalleryImageAdd } = useGalleryStore();
const { currentUser } = storeToRefs(useUserStore());
const props = defineProps({
gallerySelectDialog: {
type: Object,
required: true
},
galleryTable: {
type: Array,
required: true
}
});
const emit = defineEmits(['refreshGalleryTable']);
function selectImageGallerySelect(imageUrl, fileId) {
const D = props.gallerySelectDialog;
D.selectedFileId = fileId;
@@ -116,13 +113,13 @@
r.onload = function () {
const base64Body = btoa(r.result);
vrcPlusImageRequest.uploadGalleryImage(base64Body).then((args) => {
handleGalleryImageAdd(args);
$message({
message: t('message.gallery.uploaded'),
type: 'success'
});
// API.$on('GALLERYIMAGE:ADD')
if (Object.keys(props.galleryTable).length !== 0) {
props.galleryTable.unshift(args.json);
if (Object.keys(galleryTable.value).length !== 0) {
galleryTable.value.unshift(args.json);
}
return args;
});
@@ -130,7 +127,4 @@
r.readAsBinaryString(files[0]);
clearFile();
}
function refreshGalleryTable() {
emit('refreshGalleryTable');
}
</script>

View File

@@ -36,7 +36,7 @@
</el-popover>
<div style="flex: 1; display: flex; align-items: center; margin-left: 15px">
<div class="group-header" style="flex: 1">
<span v-if="groupDialog.ref.ownerId === API.currentUser.id" style="margin-right: 5px">👑</span>
<span v-if="groupDialog.ref.ownerId === currentUser.id" style="margin-right: 5px">👑</span>
<span class="dialog-title" style="margin-right: 5px" v-text="groupDialog.ref.name"></span>
<span
class="group-discriminator x-grey"
@@ -399,9 +399,9 @@
</span>
<div v-for="room in groupDialog.instances" :key="room.tag" style="width: 100%">
<div style="margin: 5px 0">
<location :location="room.tag" style="display: inline-block" />
<Location :location="room.tag" style="display: inline-block" />
<el-tooltip placement="top" content="Invite yourself" :disabled="hideTooltips">
<invite-yourself :location="room.tag" style="margin-left: 5px" />
<InviteYourself :location="room.tag" style="margin-left: 5px" />
</el-tooltip>
<el-tooltip placement="top" content="Refresh player count" :disabled="hideTooltips">
<el-button
@@ -411,12 +411,11 @@
circle
@click="refreshInstancePlayerCount(room.tag)" />
</el-tooltip>
<last-join :location="room.tag" :currentlocation="lastLocation.location" />
<instance-info
<LastJoin :location="room.tag" :currentlocation="lastLocation.location" />
<InstanceInfo
:location="room.tag"
:instance="room.ref"
:friendcount="room.friendCount"
:updateelement="updateInstanceInfo" />
:friendcount="room.friendCount" />
</div>
<div
v-if="room.users.length"
@@ -437,10 +436,10 @@
v-text="user.displayName" />
<span v-if="user.location === 'traveling'" class="extra">
<i class="el-icon-loading" style="margin-right: 5px" />
<timer :epoch="user.$travelingToTime" />
<Timer :epoch="user.$travelingToTime" />
</span>
<span v-else class="extra">
<timer :epoch="user.$location_at" />
<Timer :epoch="user.$location_at" />
</span>
</div>
</div>
@@ -494,13 +493,14 @@
<span>{{ t('dialog.group.posts.visibility') }}</span>
<br />
<template v-for="roleId in groupDialog.announcement.roleIds">
<template v-for="role in groupDialog.ref.roles"
><span
v-if="role.id === roleId"
:key="roleId + role.id"
v-text="role.name"
/></template>
<span
v-for="(role, rIndex) in groupDialog.ref.roles"
v-if="role.id === roleId"
:key="rIndex"
v-text="role.name" />
<span
:key="rIndex"
:key="roleId"
v-if="
groupDialog.announcement.roleIds.indexOf(roleId) <
groupDialog.announcement.roleIds.length - 1
@@ -511,18 +511,18 @@
</template>
<i class="el-icon-view" style="margin-right: 5px" />
</el-tooltip>
<display-name
<DisplayName
:userid="groupDialog.announcement.authorId"
style="margin-right: 5px" />
<span v-if="groupDialog.announcement.editorId" style="margin-right: 5px">
({{ t('dialog.group.posts.edited_by') }}
<display-name :userid="groupDialog.announcement.editorId" />)
<DisplayName :userid="groupDialog.announcement.editorId" />)
</span>
<el-tooltip placement="bottom">
<template #content>
<span
>{{ t('dialog.group.posts.created_at') }}
{{ groupDialog.announcement.createdAt | formatDate('long') }}</span
{{ formatDateFilter(groupDialog.announcement.createdAt, 'long') }}</span
>
<template
v-if="
@@ -532,11 +532,13 @@
<br />
<span
>{{ t('dialog.group.posts.edited_at') }}
{{ groupDialog.announcement.updatedAt | formatDate('long') }}</span
{{
formatDateFilter(groupDialog.announcement.updatedAt, 'long')
}}</span
>
</template>
</template>
<timer :epoch="Date.parse(groupDialog.announcement.updatedAt)" />
<Timer :epoch="Date.parse(groupDialog.announcement.updatedAt)" />
</el-tooltip>
<template v-if="hasGroupPermission(groupDialog.ref, 'group-announcement-manage')">
<el-tooltip
@@ -593,7 +595,7 @@
<div class="x-friend-item" style="cursor: default">
<div class="detail">
<span class="name">{{ t('dialog.group.info.created_at') }}</span>
<span class="extra">{{ groupDialog.ref.createdAt | formatDate('long') }}</span>
<span class="extra">{{ formatDateFilter(groupDialog.ref.createdAt, 'long') }}</span>
</div>
</div>
<div class="x-friend-item" style="cursor: default">
@@ -667,7 +669,7 @@
<div class="detail">
<span class="name">{{ t('dialog.group.info.joined_at') }}</span>
<span class="extra">{{
groupDialog.ref.myMember.joinedAt | formatDate('long')
formatDateFilter(groupDialog.ref.myMember.joinedAt, 'long')
}}</span>
</div>
</div>
@@ -688,18 +690,18 @@
<br />
<span v-if="role.updatedAt"
>{{ t('dialog.group.info.role_updated_at') }}
{{ role.updatedAt | formatDate('long') }}</span
{{ formatDateFilter(role.updatedAt, 'long') }}</span
>
<span v-else
>{{ t('dialog.group.info.role_created_at') }}
{{ role.createdAt | formatDate('long') }}</span
{{ formatDateFilter(role.createdAt, 'long') }}</span
>
<br />
<span>{{ t('dialog.group.info.role_permissions') }}</span>
<br />
<template v-for="(permission, pIndex) in role.permissions">
<span :key="pIndex">{{ permission }}</span>
<br />
<br :key="pIndex + permission" />
</template>
</template>
<span
@@ -776,38 +778,40 @@
<span>{{ t('dialog.group.posts.visibility') }}</span>
<br />
<template v-for="roleId in post.roleIds">
<span
v-for="(role, rIndex) in groupDialog.ref.roles"
v-if="role.id === roleId"
:key="rIndex"
v-text="role.name" />
<span v-if="post.roleIds.indexOf(roleId) < post.roleIds.length - 1"
>,&nbsp;</span
<template v-for="role in groupDialog.ref.roles"
><span
v-if="role.id === roleId"
:key="role.id + roleId"
v-text="role.name" />
</template>
<template
v-if="post.roleIds.indexOf(roleId) < post.roleIds.length - 1"
><span :key="roleId">,&nbsp;</span></template
>
</template>
</template>
<i class="el-icon-view" style="margin-right: 5px" />
</el-tooltip>
<display-name :userid="post.authorId" style="margin-right: 5px" />
<DisplayName :userid="post.authorId" style="margin-right: 5px" />
<span v-if="post.editorId" style="margin-right: 5px"
>({{ t('dialog.group.posts.edited_by') }}
<display-name :userid="post.editorId" />)</span
<DisplayName :userid="post.editorId" />)</span
>
<el-tooltip placement="bottom">
<template slot="content">
<span
>{{ t('dialog.group.posts.created_at') }}
{{ post.createdAt | formatDate('long') }}</span
{{ formatDateFilter(post.createdAt, 'long') }}</span
>
<template v-if="post.updatedAt !== post.createdAt">
<br />
<span
>{{ t('dialog.group.posts.edited_at') }}
{{ post.updatedAt | formatDate('long') }}</span
{{ formatDateFilter(post.updatedAt, 'long') }}</span
>
</template>
</template>
<timer :epoch="Date.parse(post.updatedAt)" />
<Timer :epoch="Date.parse(post.updatedAt)" />
</el-tooltip>
<template
v-if="hasGroupPermission(groupDialog.ref, 'group-announcement-manage')">
@@ -885,7 +889,7 @@
@click.native.stop>
<el-button size="mini">
<span
>{{ groupDialog.memberSortOrder.name }}
>{{ t(groupDialog.memberSortOrder.name) }}
<i class="el-icon-arrow-down el-icon--right"
/></span>
</el-button>
@@ -894,7 +898,7 @@
v-for="item in groupDialogSortingOptions"
:key="item.name"
@click.native="setGroupMemberSortOrder(item)"
v-text="item.name" />
v-text="t(item.name)" />
</el-dropdown-menu>
</el-dropdown>
<span style="margin-right: 5px">{{ t('dialog.group.members.filter') }}</span>
@@ -906,7 +910,7 @@
@click.native.stop>
<el-button size="mini">
<span
>{{ groupDialog.memberFilter.name }}
>{{ t(groupDialog.memberFilter.name) }}
<i class="el-icon-arrow-down el-icon--right"
/></span>
</el-button>
@@ -915,7 +919,7 @@
v-for="item in groupDialogFilterOptions"
:key="item.name"
@click.native="setGroupMemberFilter(item)"
v-text="item.name" />
v-text="t(item.name)" />
<el-dropdown-item
v-for="item in groupDialog.ref.roles"
v-if="!item.defaultRole"
@@ -984,13 +988,13 @@
</el-tooltip>
</template>
<template v-for="roleId in user.roleIds">
<span
v-for="(role, rIndex) in groupDialog.ref.roles"
v-if="role.id === roleId"
:key="rIndex"
v-text="role.name" />
<span v-if="user.roleIds.indexOf(roleId) < user.roleIds.length - 1"
>,&nbsp;</span
<template v-for="role in groupDialog.ref.roles"
><span
v-if="role.id === roleId"
:key="role.id + roleId"
v-text="role.name" /></template
><template v-if="user.roleIds.indexOf(roleId) < user.roleIds.length - 1"
><span :key="roleId">,&nbsp;</span></template
>
</template>
</span>
@@ -1048,19 +1052,18 @@
</el-tooltip>
</template>
<template v-for="roleId in user.roleIds">
<span
v-for="(role, rIndex) in groupDialog.ref.roles"
v-if="role.id === roleId"
:key="rIndex"
v-text="role.name" />
<span v-if="user.roleIds.indexOf(roleId) < user.roleIds.length - 1"
>,&nbsp;</span
<template v-for="role in groupDialog.ref.roles"
><span
v-if="role.id === roleId"
:key="roleId + role"
v-text="role.name" /></template
><template v-if="user.roleIds.indexOf(roleId) < user.roleIds.length - 1"
><span :key="roleId">&nbsp;</span></template
>
</template>
</span>
</div>
</li>
<!--FIXME: div in ul-->
<div
v-if="!isGroupMembersDone"
v-loading="isGroupMembersLoading"
@@ -1150,108 +1153,75 @@
<!--Nested-->
<GroupPostEditDialog :dialog-data.sync="groupPostEditDialog" :selected-gallery-file="selectedGalleryFile" />
<GroupMemberModerationDialog
:group-dialog="groupDialog"
:is-group-members-loading.sync="isGroupMembersLoading"
:group-dialog-filter-options="groupDialogFilterOptions"
:group-dialog-sorting-options="groupDialogSortingOptions"
:random-user-colours="randomUserColours"
:group-member-moderation="groupMemberModeration"
@close-dialog="closeMemberModerationDialog"
@group-members-search="groupMembersSearch"
@load-all-group-members="loadAllGroupMembers"
@set-group-member-filter="setGroupMemberFilter"
@set-group-member-sort-order="setGroupMemberSortOrder" />
<InviteGroupDialog
:dialog-data.sync="inviteGroupDialog"
:vip-friends="vipFriends"
:online-friends="onlineFriends"
:offline-friends="offlineFriends"
:active-friends="activeFriends" />
<InviteGroupDialog />
</safe-dialog>
</template>
<script setup>
import { getCurrentInstance, inject, nextTick, reactive, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance, nextTick, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import * as workerTimers from 'worker-timers';
import { groupRequest } from '../../../api';
import utils from '../../../classes/utils';
import { hasGroupPermission } from '../../../composables/group/utils';
import { refreshInstancePlayerCount } from '../../../composables/instance/utils';
import { copyToClipboard, downloadAndSaveJson, getFaviconUrl } from '../../../composables/shared/utils';
import { languageClass } from '../../../composables/user/utils';
import Location from '../../Location.vue';
import { $app } from '../../../app';
import { groupDialogFilterOptions, groupDialogSortingOptions } from '../../../shared/constants';
import {
adjustDialogZ,
buildTreeData,
copyToClipboard,
downloadAndSaveJson,
getFaviconUrl,
hasGroupPermission,
languageClass,
openExternalLink,
refreshInstancePlayerCount,
removeFromArray,
userImage,
userStatusClass,
formatDateFilter
} from '../../../shared/utils';
import {
useAppearanceSettingsStore,
useGalleryStore,
useGroupStore,
useLocationStore,
useUserStore
} from '../../../stores';
import InviteGroupDialog from '../InviteGroupDialog.vue';
import GroupMemberModerationDialog from './GroupMemberModerationDialog.vue';
import GroupPostEditDialog from './GroupPostEditDialog.vue';
const API = inject('API');
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
const showUserDialog = inject('showUserDialog');
const userStatusClass = inject('userStatusClass');
const userImage = inject('userImage');
const openExternalLink = inject('openExternalLink');
const adjustDialogZ = inject('adjustDialogZ');
const { t } = useI18n();
const { hideTooltips } = storeToRefs(useAppearanceSettingsStore());
const { showUserDialog } = useUserStore();
const { currentUser } = storeToRefs(useUserStore());
const { groupDialog, inviteGroupDialog } = storeToRefs(useGroupStore());
const {
getGroupDialogGroup,
updateGroupPostSearch,
showGroupDialog,
leaveGroupPrompt,
setGroupVisibility,
applyGroupMember,
handleGroupMember,
handleGroupMemberProps
} = useGroupStore();
const { lastLocation } = storeToRefs(useLocationStore());
const { showFullscreenImageDialog } = useGalleryStore();
const instance = getCurrentInstance();
const $confirm = instance.proxy.$confirm;
const $message = instance.proxy.$message;
const props = defineProps({
groupDialog: {
type: Object,
required: true
},
hideTooltips: {
type: Boolean,
default: false
},
lastLocation: {
type: Object,
required: true
},
updateInstanceInfo: {
type: Number,
required: true
},
groupDialogSortingOptions: {
type: Object,
required: true
},
groupDialogFilterOptions: {
type: Object,
required: true
},
randomUserColours: {
type: Boolean,
default: true
},
vipFriends: {
type: Array,
default: () => []
},
onlineFriends: {
type: Array,
default: () => []
},
offlineFriends: {
type: Array,
default: () => []
},
activeFriends: {
type: Array,
default: () => []
}
});
const emit = defineEmits([
'update:group-dialog',
'groupDialogCommand',
'getGroupDialogGroup',
'updateGroupPostSearch'
]);
const groupDialogRef = ref(null);
const isGroupMembersDone = ref(false);
const isGroupMembersLoading = ref(false);
@@ -1283,20 +1253,10 @@
auditLogTypes: []
});
const inviteGroupDialog = ref({
visible: false,
loading: false,
groupId: '',
groupName: '',
userId: '',
userIds: [],
userObject: {}
});
let loadMoreGroupMembersParams = {};
watch(
() => props.groupDialog.loading,
() => groupDialog.value.loading,
(val) => {
if (val) {
nextTick(() => adjustDialogZ(groupDialogRef.value.$el));
@@ -1305,7 +1265,7 @@
);
watch(
() => props.groupDialog.isGetGroupDialogGroupLoading,
() => groupDialog.value.isGetGroupDialogGroupLoading,
(val) => {
if (val) {
getCurrentTabData();
@@ -1314,14 +1274,9 @@
);
function showInviteGroupDialog(groupId, userId) {
const D = inviteGroupDialog.value;
D.userIds = '';
D.groups = [];
D.groupId = groupId;
D.groupName = groupId;
D.userId = userId;
D.userObject = {};
D.visible = true;
inviteGroupDialog.value.groupId = groupId;
inviteGroupDialog.value.userId = userId;
inviteGroupDialog.value.visible = true;
}
function setGroupRepresentation(groupId) {
@@ -1360,7 +1315,7 @@
}
function groupMembersSearchDebounce() {
const D = props.groupDialog;
const D = groupDialog.value;
const search = D.memberSearch;
D.memberSearchResults = [];
if (!search || search.length < 3) {
@@ -1375,16 +1330,14 @@
offset: 0
})
.then((args) => {
// API.$on('GROUP:MEMBERS:SEARCH', function (args) {
for (const json of args.json.results) {
API.$emit('GROUP:MEMBER', {
handleGroupMember({
json,
params: {
groupId: args.params.groupId
}
});
}
// });
if (D.id === args.params.groupId) {
D.memberSearchResults = args.json.results;
}
@@ -1400,11 +1353,10 @@
isRepresenting: isSet
})
.then((args) => {
// API.$on('GROUP:SETREPRESENTATION', function (args) {
if (props.groupDialog.visible && props.groupDialog.id === groupId) {
if (groupDialog.value.visible && groupDialog.value.id === args.groupId) {
updateGroupDialogData({
...props.groupDialog,
ref: { ...props.groupDialog.ref, isRepresenting: args.params.isRepresenting }
...groupDialog.value,
ref: { ...groupDialog.value.ref, isRepresenting: args.params.isRepresenting }
});
getGroupDialogGroup(groupId);
}
@@ -1417,11 +1369,9 @@
groupId: id
})
.then((args) => {
// API.$on('GROUP:CANCELJOINREQUEST', function (args) {
if (props.groupDialog.visible && props.groupDialog.id === id) {
if (groupDialog.value.visible && groupDialog.value.id === id) {
getGroupDialogGroup(id);
}
// });
});
}
function confirmDeleteGroupPost(post) {
@@ -1437,8 +1387,7 @@
postId: post.id
})
.then((args) => {
// API.$on('GROUP:POST:DELETE', function (args) {
const D = props.groupDialog;
const D = groupDialog.value;
if (D.id !== args.params.groupId) {
return;
}
@@ -1447,7 +1396,7 @@
// remove existing post
for (const item of D.posts) {
if (item.id === postId) {
utils.removeFromArray(D.posts, item);
removeFromArray(D.posts, item);
break;
}
}
@@ -1460,7 +1409,6 @@
}
}
updateGroupPostSearch();
// });
});
}
}
@@ -1480,30 +1428,113 @@
}
function groupDialogCommand(command) {
const D = props.groupDialog;
const D = groupDialog.value;
if (D.visible === false) {
return;
}
switch (command) {
case 'Share':
copyToClipboard(props.groupDialog.ref.$url);
copyToClipboard(groupDialog.value.ref.$url);
break;
case 'Create Post':
showGroupPostEditDialog(props.groupDialog.id, null);
showGroupPostEditDialog(groupDialog.value.id, null);
break;
case 'Moderation Tools':
showGroupMemberModerationDialog(props.groupDialog.id);
showGroupMemberModerationDialog(groupDialog.value.id);
break;
case 'Invite To Group':
showInviteGroupDialog(D.id, '');
break;
default:
emit('groupDialogCommand', command);
case 'Refresh':
showGroupDialog(D.id);
break;
case 'Leave Group':
leaveGroupPrompt(D.id);
break;
case 'Block Group':
blockGroup(D.id);
break;
case 'Unblock Group':
unblockGroup(D.id);
break;
case 'Visibility Everyone':
setGroupVisibility(D.id, 'visible');
break;
case 'Visibility Friends':
setGroupVisibility(D.id, 'friends');
break;
case 'Visibility Hidden':
setGroupVisibility(D.id, 'hidden');
break;
case 'Subscribe To Announcements':
setGroupSubscription(D.id, true);
break;
case 'Unsubscribe To Announcements':
setGroupSubscription(D.id, false);
break;
}
}
function setGroupSubscription(groupId, subscribe) {
return groupRequest
.setGroupMemberProps(currentUser.value.id, groupId, {
isSubscribedToAnnouncements: subscribe
})
.then((args) => {
handleGroupMemberProps(args);
$app.$message({
message: 'Group subscription updated',
type: 'success'
});
return args;
});
}
function blockGroup(groupId) {
$app.$confirm('Are you sure you want to block this group?', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
groupRequest
.blockGroup({
groupId
})
.then((args) => {
if (groupDialog.value.visible && groupDialog.value.id === args.params.groupId) {
showGroupDialog(args.params.groupId);
}
});
}
}
});
}
function unblockGroup(groupId) {
$app.$confirm('Are you sure you want to unblock this group?', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
callback: (action) => {
if (action === 'confirm') {
groupRequest
.unblockGroup({
groupId,
userId: currentUser.value.id
})
.then((args) => {
if (groupDialog.value.visible && groupDialog.value.id === args.params.groupId) {
showGroupDialog(args.params.groupId);
}
});
}
}
});
}
function showGroupMemberModerationDialog(groupId) {
if (groupId !== props.groupDialog.id) {
if (groupId !== groupDialog.value.id) {
return;
}
const D = groupMemberModeration;
@@ -1511,16 +1542,14 @@
D.groupRef = {};
D.auditLogTypes = [];
API.getCachedGroup({ groupId }).then((args) => {
groupRequest.getCachedGroup({ groupId }).then((args) => {
D.groupRef = args.ref;
if (hasGroupPermission(D.groupRef, 'group-audit-view')) {
groupRequest.getGroupAuditLogTypes({ groupId }).then((args) => {
// API.$on('GROUP:AUDITLOGTYPES', function (args) {
if (groupMemberModeration.id !== args.params.groupId) {
return;
}
groupMemberModeration.auditLogTypes = args.json;
// });
});
}
});
@@ -1535,16 +1564,14 @@
groupId: id
})
.then((args) => {
// API.$on('GROUP:JOIN', function (args) {
if (props.groupDialog.visible && props.groupDialog.id === id) {
if (groupDialog.value.visible && groupDialog.value.id === id) {
updateGroupDialogData({
...props.groupDialog,
...groupDialog.value,
inGroup: args.json.membershipStatus === 'member'
});
// props.groupDialog.inGroup = args.json.membershipStatus === 'member';
// groupDialog.value.inGroup = json.membershipStatus === 'member';
getGroupDialogGroup(id);
}
// });
if (args.json.membershipStatus === 'member') {
$message({
message: 'Group joined',
@@ -1595,14 +1622,14 @@
selectedImageUrl: post.imageUrl
};
}
API.getCachedGroup({ groupId }).then((args) => {
groupRequest.getCachedGroup({ groupId }).then((args) => {
D.groupRef = args.ref;
});
D.visible = true;
}
async function getGroupDialogGroupMembers() {
const D = props.groupDialog;
const D = groupDialog.value;
D.members = [];
isGroupMembersDone.value = false;
loadMoreGroupMembersParams = {
@@ -1620,12 +1647,12 @@
await groupRequest
.getGroupMember({
groupId: D.id,
userId: API.currentUser.id
userId: currentUser.value.id
})
.then((args) => {
args.ref = API.applyGroupMember(args.json);
args.ref = applyGroupMember(args.json);
if (args.json) {
args.json.user = API.currentUser;
args.json.user = currentUser.value;
if (D.memberFilter.id === null) {
// when flitered by role don't include self
D.members.push(args.json);
@@ -1641,7 +1668,7 @@
if (isGroupMembersDone.value || isGroupMembersLoading.value) {
return;
}
const D = props.groupDialog;
const D = groupDialog.value;
const params = loadMoreGroupMembersParams;
D.memberSearch = '';
isGroupMembersLoading.value = true;
@@ -1651,10 +1678,18 @@
isGroupMembersLoading.value = false;
})
.then((args) => {
for (const json of args.json) {
handleGroupMember({
json,
params: {
groupId: args.params.groupId
}
});
}
for (let i = 0; i < args.json.length; i++) {
const member = args.json[i];
if (member.userId === API.currentUser.id) {
if (D.members.length > 0 && D.members[0].userId === API.currentUser.id) {
if (member.userId === currentUser.value.id) {
if (D.members.length > 0 && D.members[0].userId === currentUser.value.id) {
// remove duplicate and keep sort order
D.members.splice(0, 1);
}
@@ -1685,12 +1720,12 @@
}
async function getGroupGalleries() {
updateGroupDialogData({ ...props.groupDialog, galleries: {} });
updateGroupDialogData({ ...groupDialog.value, galleries: {} });
groupDialogGalleryCurrentName.value = '0';
isGroupGalleryLoading.value = true;
for (let i = 0; i < props.groupDialog.ref.galleries.length; i++) {
const gallery = props.groupDialog.ref.galleries[i];
await getGroupGallery(props.groupDialog.id, gallery.id);
for (let i = 0; i < groupDialog.value.ref.galleries.length; i++) {
const gallery = groupDialog.value.ref.galleries[i];
await getGroupGallery(groupDialog.value.id, gallery.id);
}
isGroupGalleryLoading.value = false;
}
@@ -1707,16 +1742,14 @@
for (let i = 0; i < count; i++) {
const args = await groupRequest.getGroupGallery(params);
if (args) {
// API.$on('GROUP:GALLERY', function (args) {
for (const json of args.json) {
if (props.groupDialog.id === json.groupId) {
if (!props.groupDialog.galleries[json.galleryId]) {
props.groupDialog.galleries[json.galleryId] = [];
if (groupDialog.value.id === json.groupId) {
if (!groupDialog.value.galleries[json.galleryId]) {
groupDialog.value.galleries[json.galleryId] = [];
}
props.groupDialog.galleries[json.galleryId].push(json);
groupDialog.value.galleries[json.galleryId].push(json);
}
}
// });
}
params.offset += 100;
if (args.json.length < 100) {
@@ -1729,8 +1762,8 @@
}
function refreshGroupDialogTreeData() {
const D = props.groupDialog;
const treeData = utils.buildTreeData({
const D = groupDialog.value;
const treeData = buildTreeData({
group: D.ref,
posts: D.posts,
instances: D.instances,
@@ -1738,7 +1771,7 @@
galleries: D.galleries
});
updateGroupDialogData({
...props.groupDialog,
...groupDialog.value,
treeData
});
}
@@ -1748,19 +1781,19 @@
return;
}
await getGroupDialogGroupMembers();
while (props.groupDialog.visible && !isGroupMembersDone.value) {
while (groupDialog.value.visible && !isGroupMembersDone.value) {
isGroupMembersLoading.value = true;
await new Promise((resolve) => {
workerTimers.setTimeout(resolve, 1000);
});
isGroupMembersLoading.value = false;
await this.loadMoreGroupMembers();
await loadMoreGroupMembers();
}
}
async function setGroupMemberSortOrder(sortOrder) {
const D = props.groupDialog;
if (D.memberSortOrder === sortOrder) {
const D = groupDialog.value;
if (D.memberSortOrder.value === sortOrder) {
return;
}
D.memberSortOrder = sortOrder;
@@ -1768,8 +1801,8 @@
}
async function setGroupMemberFilter(filter) {
const D = props.groupDialog;
if (D.memberFilter === filter) {
const D = groupDialog.value;
if (D.memberFilter.value === filter) {
return;
}
D.memberFilter = filter;
@@ -1777,13 +1810,9 @@
}
function updateGroupDialogData(obj) {
// Be careful with the deep merge
emit('update:group-dialog', obj);
}
function getGroupDialogGroup(groupId) {
emit('getGroupDialogGroup', groupId);
}
function updateGroupPostSearch() {
emit('updateGroupPostSearch');
groupDialog.value = {
...groupDialog.value,
...obj
};
}
</script>

View File

@@ -18,7 +18,7 @@
icon="el-icon-refresh"
:loading="isGroupMembersLoading"
circle
@click="loadAllGroupMembers"></el-button>
@click="loadAllGroupMembers" />
<span style="font-size: 14px; margin-left: 5px; margin-right: 5px">
{{ groupMemberModerationTable.data.length }}/{{
groupMemberModeration.groupRef.memberCount
@@ -40,7 +40,7 @@
@click.native.stop>
<el-button size="mini">
<span
>{{ groupDialog.memberSortOrder.name }}
>{{ t(groupDialog.memberSortOrder.name) }}
<i class="el-icon-arrow-down el-icon--right"></i
></span>
</el-button>
@@ -49,7 +49,7 @@
v-for="item in groupDialogSortingOptions"
:key="item.name"
@click.native="setGroupMemberSortOrder(item)">
{{ item.name }}
{{ t(item.name) }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
@@ -68,7 +68,7 @@
@click.native.stop>
<el-button size="mini">
<span
>{{ groupDialog.memberFilter.name }}
>{{ t(groupDialog.memberFilter.name) }}
<i class="el-icon-arrow-down el-icon--right"></i
></span>
</el-button>
@@ -77,7 +77,7 @@
v-for="item in groupDialogFilterOptions"
:key="item.name"
@click.native="setGroupMemberFilter(item)"
v-text="item.name"></el-dropdown-item>
v-text="t(item.name)"></el-dropdown-item>
<el-dropdown-item
v-for="item in groupDialog.ref.roles"
v-if="!item.defaultRole"
@@ -147,12 +147,11 @@
<el-table-column :label="t('dialog.group_member_moderation.roles')" prop="roleIds" sortable>
<template slot-scope="scope">
<template v-for="(roleId, index) in scope.row.roleIds">
<span
v-for="(role, rIndex) in groupMemberModeration.groupRef.roles"
v-if="role?.id === roleId"
:key="rIndex"
>{{ role.name
}}<span v-if="index < scope.row.roleIds.length - 1">, </span></span
<template v-for="(role, rIndex) in groupMemberModeration.groupRef.roles">
<span v-if="role?.id === roleId" :key="roleId + rIndex"
>{{ role.name
}}<span v-if="index < scope.row.roleIds.length - 1">, </span></span
></template
>
</template>
</template>
@@ -171,7 +170,7 @@
prop="joinedAt"
sortable>
<template slot-scope="scope">
<span>{{ scope.row.joinedAt | formatDate('long') }}</span>
<span>{{ formatDateFilter(scope.row.joinedAt, 'long') }}</span>
</template>
</el-table-column>
<el-table-column
@@ -284,7 +283,7 @@
prop="joinedAt"
sortable>
<template slot-scope="scope">
<span>{{ scope.row.joinedAt | formatDate('long') }}</span>
<span>{{ formatDateFilter(scope.row.joinedAt, 'long') }}</span>
</template>
</el-table-column>
<el-table-column
@@ -293,7 +292,7 @@
prop="bannedAt"
sortable>
<template slot-scope="scope">
<span>{{ scope.row.bannedAt | formatDate('long') }}</span>
<span>{{ formatDateFilter(scope.row.bannedAt, 'long') }}</span>
</template>
</el-table-column>
</data-tables>
@@ -629,7 +628,7 @@
prop="created_at"
sortable>
<template slot-scope="scope">
<span>{{ scope.row.created_at | formatDate('long') }}</span>
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
</template>
</el-table-column>
<el-table-column
@@ -656,7 +655,7 @@
:label="t('dialog.group_member_moderation.description')"
prop="description">
<template slot-scope="scope">
<location
<Location
v-if="scope.row?.targetId.startsWith('wrld_')"
:location="scope.row.targetId" />
<span v-text="scope.row.description"></span>
@@ -810,45 +809,165 @@
</template>
<script setup>
import Location from '../../Location.vue';
import { getCurrentInstance, inject, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { getCurrentInstance, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { groupRequest, userRequest } from '../../../api';
import { useModerationTable, useSelectedUsers } from '../../../composables/group/useGroupMemberModeration';
import { hasGroupPermission } from '../../../composables/group/utils';
import { groupDialogFilterOptions, groupDialogSortingOptions } from '../../../shared/constants';
import { hasGroupPermission, userImage, userImageFull, formatDateFilter } from '../../../shared/utils';
import { useAppearanceSettingsStore, useGalleryStore, useGroupStore, useUserStore } from '../../../stores';
import GroupMemberModerationExportDialog from './GroupMemberModerationExportDialog.vue';
const API = inject('API');
const showUserDialog = inject('showUserDialog');
const userImage = inject('userImage');
const userImageFull = inject('userImageFull');
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
const { showUserDialog } = useUserStore();
const { currentUser } = storeToRefs(useUserStore());
const { groupDialog } = storeToRefs(useGroupStore());
const { applyGroupMember, handleGroupMemberProps } = useGroupStore();
const { showFullscreenImageDialog } = useGalleryStore();
const { t } = useI18n();
const instance = getCurrentInstance();
const $message = instance.proxy.$message;
const selectedUsers = reactive({});
const selectedUsersArray = ref([]);
function setSelectedUsers(usersId, user) {
if (!user) {
return;
}
selectedUsers[usersId] = user;
selectedUsersArray.value = Object.values(selectedUsers);
}
function deselectedUsers(userId, isAll = false) {
if (isAll) {
for (const id in selectedUsers) {
if (Object.prototype.hasOwnProperty.call(selectedUsers, id)) {
delete selectedUsers[id];
}
}
} else {
if (Object.prototype.hasOwnProperty.call(selectedUsers, userId)) {
delete selectedUsers[userId];
}
}
selectedUsersArray.value = Object.values(selectedUsers);
}
function groupMemberModerationTableSelectionChange(row) {
if (row.$selected && !selectedUsers[row.userId]) {
setSelectedUsers(row.userId, row);
} else if (!row.$selected && selectedUsers[row.userId]) {
deselectedUsers(row.userId);
}
}
const groupInvitesModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupJoinRequestsModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupBlockedModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupLogsModerationTable = reactive({
data: [],
filters: [{ prop: ['description'], value: '' }],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupBansModerationTable = reactive({
data: [],
filters: [{ prop: ['$displayName'], value: '' }],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
const groupMemberModerationTable = reactive({
data: [],
tableProps: { stripe: true, size: 'mini' },
pageSize: 15,
paginationProps: {
small: true,
layout: 'sizes,prev,pager,next,total',
pageSizes: [10, 15, 20, 25, 50, 100]
}
});
async function initializePageSize() {
try {
const { tablePageSize } = storeToRefs(useAppearanceSettingsStore());
groupMemberModerationTable.pageSize = tablePageSize.value;
groupBansModerationTable.pageSize = tablePageSize.value;
groupLogsModerationTable.pageSize = tablePageSize.value;
groupInvitesModerationTable.pageSize = tablePageSize.value;
groupJoinRequestsModerationTable.pageSize = tablePageSize.value;
groupBlockedModerationTable.pageSize = tablePageSize.value;
} catch (error) {
console.error('Failed to initialize table page size:', error);
}
}
function deselectGroupMember(userId) {
const deselectInTable = (tableData) => {
if (userId) {
const row = tableData.find((item) => item.userId === userId);
if (row) {
row.$selected = false;
}
} else {
tableData.forEach((row) => {
if (row.$selected) {
row.$selected = false;
}
});
}
};
deselectInTable(groupMemberModerationTable.data);
deselectInTable(groupBansModerationTable.data);
deselectInTable(groupInvitesModerationTable.data);
deselectInTable(groupJoinRequestsModerationTable.data);
deselectInTable(groupBlockedModerationTable.data);
}
const props = defineProps({
isGroupMembersLoading: {
type: Boolean,
default: false
},
groupDialog: {
type: Object,
required: true
},
groupDialogSortingOptions: {
type: Object,
required: true
},
groupDialogFilterOptions: {
type: Object,
required: true
},
randomUserColours: {
type: Boolean,
default: false
},
groupMemberModeration: {
type: Object,
required: true
@@ -864,25 +983,6 @@
'group-members-search'
]);
const {
groupInvitesModerationTable,
groupJoinRequestsModerationTable,
groupBlockedModerationTable,
groupLogsModerationTable,
groupBansModerationTable,
groupMemberModerationTable,
initializePageSize,
deselectGroupMember
} = useModerationTable();
const {
selectedUsers,
selectedUsersArray,
groupMemberModerationTableSelectionChange,
deselectedUsers,
setSelectedUsers
} = useSelectedUsers();
const selectUserId = ref('');
const progressCurrent = ref(0);
const progressTotal = ref(0);
@@ -895,7 +995,7 @@
() => props.groupMemberModeration.visible,
(newVal) => {
if (newVal) {
if (props.groupMemberModeration.id !== props.groupDialog.id) {
if (props.groupMemberModeration.id !== groupDialog.value.id) {
return;
}
groupMemberModerationTable.data = [];
@@ -905,6 +1005,7 @@
groupBlockedModerationTable.data = [];
groupLogsModerationTable.data = [];
Object.assign(selectedUsers, {});
selectedUsersArray.value = [];
selectUserId.value = '';
selectedRoles.value = [];
note.value = '';
@@ -913,7 +1014,7 @@
);
watch(
() => props.groupDialog.members,
() => groupDialog.value.members,
(newVal) => {
if (newVal) {
setGroupMemberModerationTable(newVal);
@@ -923,7 +1024,7 @@
);
watch(
() => props.groupDialog.memberSearchResults,
() => groupDialog.value.memberSearchResults,
(newVal) => {
if (newVal) {
setGroupMemberModerationTable(newVal);
@@ -965,9 +1066,8 @@
}
function handleGroupMemberRoleChange(args) {
// 'GROUP:MEMBER:ROLE:CHANGE'
if (props.groupDialog.id === args.params.groupId) {
props.groupDialog.members.forEach((member) => {
if (groupDialog.value.id === args.params.groupId) {
groupDialog.value.members.forEach((member) => {
if (member.userId === args.params.userId) {
member.roleIds = args.json;
return true;
@@ -988,7 +1088,7 @@
}
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) {
if (user.userId === currentUser.value.id) {
continue;
}
console.log(`Deleting group invite ${user.userId} ${i + 1}/${memberCount}`);
@@ -1014,7 +1114,7 @@
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvites(D.id, groupInvitesModerationTable);
getAllGroupInvites(D.id);
}
function selectAllGroupMembers() {
@@ -1038,7 +1138,7 @@
continue;
}
args.json.forEach((json) => {
const ref = API.applyGroupMember(json);
const ref = applyGroupMember(json);
fetchedBans.push(ref);
});
if (args.json.length < params.n) {
@@ -1076,7 +1176,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Banning ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.banGroupMember({ groupId: D.id, userId: user.userId });
@@ -1105,7 +1205,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Unbanning ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.unbanGroupMember({ groupId: D.id, userId: user.userId });
@@ -1138,7 +1238,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Kicking ${user.userId} ${i + 1}/${memberCount}`);
try {
@@ -1158,6 +1258,7 @@
progressCurrent.value = 0;
progressTotal.value = 0;
deselectedUsers(null, true);
loadAllGroupMembers();
}
async function groupMembersSaveNote() {
@@ -1176,7 +1277,8 @@
}
console.log(`Setting note ${noteToSave} for ${user.userId} ${i + 1}/${memberCount}`);
try {
await groupRequest.setGroupMemberProps(user.userId, D.id, { managerNotes: noteToSave });
const args = await groupRequest.setGroupMemberProps(user.userId, D.id, { managerNotes: noteToSave });
handleGroupMemberProps(args);
} catch (err) {
console.error(err);
$message({
@@ -1334,7 +1436,7 @@
let member = {};
const memberArgs = await groupRequest.getGroupMember({ groupId: D.id, userId });
if (memberArgs && memberArgs.json) {
member = API.applyGroupMember(memberArgs.json);
member = applyGroupMember(memberArgs.json);
}
if (member && member.user) {
setSelectedUsers(member.userId, member);
@@ -1400,7 +1502,7 @@
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) {
if (user.userId === currentUser.value.id) {
continue;
}
@@ -1424,7 +1526,7 @@
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests();
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
@@ -1438,7 +1540,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Blocking group join request from ${user.userId} ${i + 1}/${memberCount}`);
try {
@@ -1460,7 +1562,7 @@
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests();
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
@@ -1475,7 +1577,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Rejecting group join request from ${user.userId} ${i + 1}/${memberCount}`);
try {
@@ -1497,7 +1599,7 @@
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests();
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
@@ -1511,7 +1613,7 @@
if (!progressTotal.value) break;
const user = users[i];
progressCurrent.value = i + 1;
if (user.userId === API.currentUser.id) continue;
if (user.userId === currentUser.value.id) continue;
console.log(`Accepting group join request from ${user.userId} ${i + 1}/${memberCount}`);
try {
@@ -1533,7 +1635,7 @@
}
progressCurrent.value = 0;
progressTotal.value = 0;
getAllGroupInvitesAndJoinRequests();
getAllGroupInvitesAndJoinRequests(D.id);
deselectedUsers(null, true);
}
@@ -1565,7 +1667,7 @@
? groupBlockedModerationTable
: groupJoinRequestsModerationTable;
for (const json of args.json) {
const ref = API.applyGroupMember(json);
const ref = applyGroupMember(json);
targetTable.data.push(ref);
}
params.offset += params.n;
@@ -1598,7 +1700,7 @@
? groupBlockedModerationTable
: groupJoinRequestsModerationTable;
for (const json of args.json) {
const ref = API.applyGroupMember(json);
const ref = applyGroupMember(json);
targetTable.data.push(ref);
}
params.offset += params.n;
@@ -1631,7 +1733,7 @@
}
for (const json of args.json) {
const ref = API.applyGroupMember(json);
const ref = applyGroupMember(json);
groupInvitesModerationTable.data.push(ref);
}
}
@@ -1649,7 +1751,7 @@
type: 'error'
});
} finally {
updateIsGroupMembersLoading(false); // Use emit
updateIsGroupMembersLoading(false);
}
}

View File

@@ -32,7 +32,7 @@
<script setup>
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { copyToClipboard } from '../../../composables/shared/utils';
import { copyToClipboard } from '../../../shared/utils';
const { t } = useI18n();

View File

@@ -1,16 +1,16 @@
<template>
<safe-dialog
:visible.sync="groupPostEditDialog.visible"
:title="$t('dialog.group_post_edit.header')"
:title="t('dialog.group_post_edit.header')"
width="650px"
append-to-body>
<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-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-form-item :label="t('dialog.group_post_edit.message')">
<el-input
v-model="groupPostEditDialog.text"
type="textarea"
@@ -24,29 +24,27 @@
v-if="!groupPostEditDialog.postId"
v-model="groupPostEditDialog.sendNotification"
size="small">
{{ $t('dialog.group_post_edit.send_notification') }}
{{ t('dialog.group_post_edit.send_notification') }}
</el-checkbox>
</el-form-item>
<el-form-item :label="$t('dialog.group_post_edit.post_visibility')">
<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') }}
{{ t('dialog.group_post_edit.visibility_public') }}
</el-radio>
<el-radio label="group">
{{ $t('dialog.group_post_edit.visibility_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-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')"
:placeholder="t('dialog.new_instance.role_placeholder')"
style="width: 100%">
<el-option-group :label="$t('dialog.new_instance.role_placeholder')">
<el-option-group :label="t('dialog.new_instance.role_placeholder')">
<el-option
v-for="role in groupPostEditDialog.groupRef?.roles"
:key="role.id"
@@ -60,7 +58,7 @@
</el-option-group>
</el-select>
</el-form-item>
<el-form-item :label="$t('dialog.group_post_edit.image')">
<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">
@@ -80,13 +78,13 @@
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" />
</el-popover>
<el-button size="mini" style="vertical-align: top" @click="clearImageGallerySelect">
{{ $t('dialog.invite_message.clear_selected_image') }}
{{ 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') }}
{{ t('dialog.invite_message.select_image') }}
</el-button>
</template>
</el-form-item>
@@ -94,13 +92,13 @@
</div>
<template #footer>
<el-button size="small" @click="groupPostEditDialog.visible = false">
{{ $t('dialog.group_post_edit.cancel') }}
{{ 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') }}
{{ t('dialog.group_post_edit.edit_post') }}
</el-button>
<el-button v-else size="small" @click="createGroupPost">
{{ $t('dialog.group_post_edit.create_post') }}
{{ t('dialog.group_post_edit.create_post') }}
</el-button>
</template>
<GallerySelectDialog
@@ -110,114 +108,115 @@
</safe-dialog>
</template>
<script>
<script setup>
import { ref, computed, getCurrentInstance } from 'vue';
import { useI18n } from 'vue-i18n-bridge';
import { groupRequest, vrcPlusIconRequest } from '../../../api';
import { useGalleryStore, useGroupStore } from '../../../stores';
import GallerySelectDialog from './GallerySelectDialog.vue';
export default {
name: 'GroupPostEditDialog',
components: {
GallerySelectDialog
const props = defineProps({
dialogData: {
type: Object,
required: true
},
inject: ['showFullscreenImageDialog'],
props: {
dialogData: {
type: Object,
required: true
},
selectedGalleryFile: { type: Object, default: () => ({}) }
selectedGalleryFile: { type: Object, default: () => ({}) }
});
const emit = defineEmits(['update:dialogData']);
const { proxy } = getCurrentInstance();
const { t } = useI18n();
const { showFullscreenImageDialog, handleFilesList } = useGalleryStore();
const { handleGroupPost } = useGroupStore();
const gallerySelectDialog = ref({
visible: false,
selectedFileId: '',
selectedImageUrl: ''
});
const galleryTable = ref([]);
const groupPostEditDialog = computed({
get() {
return props.dialogData;
},
data() {
return {
gallerySelectDialog: {
visible: false,
selectedFileId: '',
selectedImageUrl: ''
},
galleryTable: []
};
},
computed: {
groupPostEditDialog: {
get() {
return this.dialogData;
},
set(value) {
this.$emit('update:dialog-data', value);
}
}
},
methods: {
showGallerySelectDialog() {
const D = this.gallerySelectDialog;
D.visible = true;
this.refreshGalleryTable();
},
async refreshGalleryTable() {
const params = {
n: 100,
tag: 'gallery'
};
const args = await vrcPlusIconRequest.getFileList(params);
// API.$on('FILES:LIST')
if (args.params.tag === 'gallery') {
this.galleryTable = args.json.reverse();
}
},
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() {
const D = this.gallerySelectDialog;
D.selectedFileId = '';
D.selectedImageUrl = '';
}
set(value) {
emit('update:dialogData', value);
}
};
});
function showGallerySelectDialog() {
const D = gallerySelectDialog.value;
D.visible = true;
refreshGalleryTable();
}
async function refreshGalleryTable() {
const params = {
n: 100,
tag: 'gallery'
};
const args = await vrcPlusIconRequest.getFileList(params);
handleFilesList(args);
if (args.params.tag === 'gallery') {
galleryTable.value = args.json.reverse();
}
}
function editGroupPost() {
const D = groupPostEditDialog.value;
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 (gallerySelectDialog.value.selectedFileId) {
params.imageId = gallerySelectDialog.value.selectedFileId;
}
groupRequest.editGroupPost(params).then((args) => {
handleGroupPost();
proxy.$message({
message: 'Group post edited',
type: 'success'
});
return args;
});
D.visible = false;
}
function createGroupPost() {
const D = groupPostEditDialog.value;
const params = {
groupId: D.groupId,
title: D.title,
text: D.text,
roleIds: D.roleIds,
visibility: D.visibility,
sendNotification: D.sendNotification,
imageId: null
};
if (gallerySelectDialog.value.selectedFileId) {
params.imageId = gallerySelectDialog.value.selectedFileId;
}
groupRequest.createGroupPost(params).then((args) => {
handleGroupPost();
proxy.$message({
message: 'Group post created',
type: 'success'
});
return args;
});
D.visible = false;
}
function clearImageGallerySelect() {
const D = gallerySelectDialog.value;
D.selectedFileId = '';
D.selectedImageUrl = '';
}
</script>