diff --git a/html/src/app.js b/html/src/app.js index f264806d..4d5aad90 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -2558,7 +2558,8 @@ speechSynthesis.getVoices(); args.ref = ref; if ( ref.type === 'friendRequest' || - ref.type === 'hiddenFriendRequest' + ref.type === 'hiddenFriendRequest' || + ref.type === 'group.invite' ) { for (var i = array.length - 1; i >= 0; i--) { if (array[i].id === ref.id) { @@ -2674,6 +2675,19 @@ speechSynthesis.getVoices(); offset: 0 }; var count = 50; // 5000 max + for (var i = 0; i < count; i++) { + var args = await this.getGroupNotifications(params); + $app.unseenNotifications = []; + params.offset += 100; + if (args.json.length < 100) { + break; + } + } + var params = { + n: 100, + offset: 0 + }; + var count = 50; // 5000 max for (var i = 0; i < count; i++) { var args = await this.getHiddenFriendRequests(params); $app.unseenNotifications = []; @@ -2739,6 +2753,63 @@ speechSynthesis.getVoices(); }); }; + API.getNotificationsV2 = function (params) { + return this.call('notifications', { + method: 'GET', + params + }).then((json) => { + var args = { + json, + params + }; + this.$emit('NOTIFICATION:V2:LIST', args); + return args; + }); + }; + + API.$on('NOTIFICATION:V2:LIST', function (args) { + for (var json of args.json) { + this.$emit('NOTIFICATION:V2', json); + } + console.log('NOTIFICATION:V2:LIST', args); + }); + + API.$on('NOTIFICATION:V2', function (json) { + json.created_at = json.createdAt; + this.$emit('NOTIFICATION', { + json, + params: { + notificationId: json.id + } + }); + }); + + /* + params: { + notificationId: string, + responseType: string, + responseData: string + } + */ + API.sendNotificationResponse = function (params) { + return this.call(`notifications/${params.notificationId}/respond`, { + method: 'POST', + params + }).then((json) => { + var args = { + json, + params + }; + this.$emit('NOTIFICATION:RESPONSE', args); + return args; + }); + }; + + API.$on('NOTIFICATION:RESPONSE', function (args) { + this.$emit('NOTIFICATION:HIDE', args); + console.log('NOTIFICATION:RESPONSE', args); + }); + /* params: { receiverUserId: string, @@ -2748,7 +2819,6 @@ speechSynthesis.getVoices(); details: json-string } */ - API.sendInvite = function (params, receiverUserId) { return this.call(`invite/${receiverUserId}`, { method: 'POST', @@ -3934,6 +4004,16 @@ speechSynthesis.getVoices(); }); break; + case 'notification-v2': + console.log('notification-v2', content); + this.$emit('NOTIFICATION:V2', { + json: content, + params: { + notificationId: content.id + } + }); + break; + case 'see-notification': this.$emit('NOTIFICATION:SEE', { params: { @@ -11980,7 +12060,8 @@ speechSynthesis.getVoices(); if (ref.senderUserId !== this.currentUser.id) { if ( ref.type !== 'friendRequest' && - ref.type !== 'hiddenFriendRequest' + ref.type !== 'hiddenFriendRequest' && + ref.type !== 'group.invite' ) { database.addNotificationToDatabase(ref); } @@ -22607,6 +22688,47 @@ speechSynthesis.getVoices(); } }); + /* + userId: string, + groupId: string, + params: { + visibility: string + } + */ + API.setGroupVisibility = function (userId, groupId, params) { + return this.call(`groups/${groupId}/members/${userId}`, { + method: 'PUT', + params + }).then((json) => { + var args = { + json, + userId, + groupId, + params + }; + this.$emit('GROUP:VISIBILITY', args); + return args; + }); + }; + + API.$on('GROUP:VISIBILITY', function (args) { + console.log('VISIBILITY', args); + var json = args.json; + if ($app.groupDialog.visible && $app.groupDialog.id === json.groupId) { + $app.groupDialog.ref.myMember.visibility = json.visibility; + } + json.$memberId = json.id; + json.id = json.groupId; + delete json.visibility; + this.$emit('GROUP', { + json, + params: { + groupId: json.groupId, + userId: args.params.userId + } + }); + }); + /* params: { groupId: string @@ -22690,6 +22812,7 @@ speechSynthesis.getVoices(); } else { Object.assign(ref, json); } + ref.rules = $app.replaceBioSymbols(ref.rules); ref.$url = `https://vrc.group/${ref.shortCode}.${ref.discriminator}`; this.applyGroupLanguage(ref); return ref; @@ -22786,11 +22909,17 @@ speechSynthesis.getVoices(); case 'Refresh': this.showGroupDialog(D.id); break; - case 'Set Represented Group': - API.setGroupRepresentation(D.id, {isRepresenting: true}); + case 'Leave Group': + this.leaveGroup(D.id); break; - case 'Clear Represented Group': - API.setGroupRepresentation(D.id, {isRepresenting: false}); + case 'Visibility Everyone': + this.setGroupVisibility(D.id, 'visible'); + break; + case 'Visibility Friends': + this.setGroupVisibility(D.id, 'friends'); + break; + case 'Visibility Hidden': + this.setGroupVisibility(D.id, 'hidden'); break; } }; @@ -22831,6 +22960,36 @@ speechSynthesis.getVoices(); }); }; + $app.methods.setGroupRepresentation = function (groupId) { + return API.setGroupRepresentation(groupId, {isRepresenting: true}); + }; + + $app.methods.clearGroupRepresentation = function (groupId) { + return API.setGroupRepresentation(groupId, {isRepresenting: false}); + }; + + $app.methods.setGroupVisibility = function (groupId, visibility) { + return API.setGroupVisibility(API.currentUser.id, groupId, { + visibility + }); + }; + + $app.methods.sendNotificationResponse = function ( + notificationId, + link, + response + ) { + if (!link) { + return null; + } + var groupId = link.split(':').pop(); + return API.sendNotificationResponse({ + notificationId, + responseType: response, + responseData: groupId + }); + }; + $app = new Vue($app); window.$app = $app; })(); diff --git a/html/src/index.pug b/html/src/index.pug index 3712a2ae..77a96b5b 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -13,10 +13,10 @@ html body .x-app#x-app(style="display:none") //- login - .x-login-container(v-show="!API.isLoggedIn") + .x-login-container(v-show="!API.isLoggedIn" v-loading="loginForm.loading") div(style="position:absolute;margin:5px") el-button(type="default" @click="showVRCXUpdateDialog" size="mini" icon="el-icon-download" circle) - div(style="width:300px;margin:auto" v-loading="loginForm.loading") + div(style="width:300px;margin:auto") div(style="margin:15px" v-if="Object.keys(loginForm.savedCredentials).length !== 0") h2(style="font-weight:bold;text-align:center;margin:0") Saved Accounts .x-friend-list(style="margin-top:10px") @@ -752,7 +752,7 @@ html template(#tool) div(style="margin:0 0 10px;display:flex;align-items:center") el-select(v-model="notificationTable.filters[0].value" @change="saveTableFilters" multiple clearable collapse-tags style="flex:1" placeholder="Filter") - el-option(v-once v-for="type in ['requestInvite', 'invite', 'requestInviteResponse', 'inviteResponse', 'friendRequest', 'hiddenFriendRequest', 'message']" :key="type" :label="type" :value="type") + el-option(v-once v-for="type in ['requestInvite', 'invite', 'requestInviteResponse', 'inviteResponse', 'friendRequest', 'hiddenFriendRequest', 'message', 'group.invite']" :key="type" :label="type" :value="type") el-input(v-model="notificationTable.filters[1].value" placeholder="Search" style="flex:none;width:150px;margin:0 10px") el-tooltip(placement="bottom" content="Refresh" :disabled="hideTooltips") el-button(type="default" :loading="API.isNotificationsLoading" @click="API.refreshNotifications()" icon="el-icon-refresh" circle style="flex:none") @@ -799,10 +799,18 @@ html el-button(type="text" icon="el-icon-check" size="mini" @click="acceptRequestInvite(scope.row)") el-tooltip(placement="top" content="Decline with message" :disabled="hideTooltips") el-button(type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="showSendInviteRequestResponseDialog(scope.row)") - template(v-if="scope.row.type !== 'requestInviteResponse' && scope.row.type !== 'inviteResponse' && scope.row.type !== 'message'") + template(v-else-if="scope.row.type === 'group.invite'") + el-tooltip(placement="top" content="Accept" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-check" size="mini" @click="sendNotificationResponse(scope.row.id, scope.row.link, 'accept')") + el-tooltip(placement="top" content="Decline" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-close" size="mini" @click="sendNotificationResponse(scope.row.id, scope.row.link, 'decline')") + el-tooltip(placement="top" content="Block Group" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-circle-close" size="mini" @click="sendNotificationResponse(scope.row.id, scope.row.link, 'block')") + + template(v-if="scope.row.type !== 'requestInviteResponse' && scope.row.type !== 'inviteResponse' && scope.row.type !== 'message' && scope.row.type !== 'group.invite'") el-tooltip(placement="top" content="Decline" :disabled="hideTooltips") el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="hideNotification(scope.row)") - template(v-if="scope.row.type !== 'friendRequest' && scope.row.type !== 'hiddenFriendRequest'") + template(v-if="scope.row.type !== 'friendRequest' && scope.row.type !== 'hiddenFriendRequest' && scope.row.type !== 'group.invite'") el-tooltip(placement="top" content="Delete log" :disabled="hideTooltips") el-button(type="text" icon="el-icon-delete" size="mini" style="margin-left:5px" @click="deleteNotificationLog(scope.row)") @@ -1642,7 +1650,7 @@ html el-dropdown-item(v-if="userDialog.isInteractOff" icon="el-icon-thumb" command="Enable Avatar Interaction" style="color:#F56C6C") Enable Avatar Interaction el-dropdown-item(v-else icon="el-icon-circle-close" command="Disable Avatar Interaction") Disable Avatar Interaction template(v-if="userDialog.isFriend") - el-dropdown-item(icon="el-icon-delete" command="Unfriend" divided) Unfriend + el-dropdown-item(icon="el-icon-delete" command="Unfriend" divided style="color:#F56C6C") Unfriend el-tabs(ref="userDialogTabs" @tab-click="userDialogTabClick") el-tab-pane(label="Info") template(v-if="isFriendOnline(userDialog.friend) || API.currentUser.id === userDialog.id") @@ -2160,11 +2168,26 @@ html span.flags(:class="languageClass(item.key)" style="display:inline-block;margin-right:5px") div(style="margin-top:5px") span.x-link(v-text="groupDialog.ownerDisplayName" @click="showUserDialog(groupDialog.ref.ownerId)" style="color:#909399;font-family:monospace") + div + el-tag(v-if="groupDialog.ref.isVerified" type="info" effect="plain" size="mini" style="margin-right:5px;margin-top:5px") Verified + + el-tag(v-if="groupDialog.ref.privacy === 'private'" type="danger" effect="plain" size="mini" style="margin-right:5px;margin-top:5px") Private + el-tag(v-else-if="groupDialog.ref.joinState === 'open'" type="success" effect="plain" size="mini" style="margin-right:5px;margin-top:5px") Public + el-tag(v-else-if="groupDialog.ref.joinState === 'request'" type="warning" effect="plain" size="mini" style="margin-right:5px;margin-top:5px") Invite + el-tag(v-else-if="groupDialog.ref.joinState === 'invite'" type="danger" effect="plain" size="mini" style="margin-right:5px;margin-top:5px") Closed + + el-tag(v-if="groupDialog.inGroup" type="info" effect="plain" size="mini" style="margin-right:5px;margin-top:5px") Joined + + el-tag(v-if="groupDialog.ref.myMember && groupDialog.ref.myMember.bannedAt" type="danger" effect="plain" size="mini" style="margin-right:5px;margin-top:5px") Banned div(style="margin-top:5px") span(v-show="groupDialog.ref.name !== groupDialog.ref.description" v-text="groupDialog.ref.description" style="font-size:12px") div(style="flex:none;margin-left:10px") - el-tooltip(v-if="groupDialog.inGroup" placement="top" content="Leave Group" :disabled="hideTooltips") - el-button(type="warning" icon="el-icon-star-on" circle @click="leaveGroup(groupDialog.id)" style="margin-left:5px") + template(v-if="groupDialog.inGroup") + el-tooltip(v-if="groupDialog.ref.isRepresenting" placement="top" content="Stop Representing" :disabled="hideTooltips") + el-button(type="warning" icon="el-icon-star-on" circle @click="clearGroupRepresentation(groupDialog.id)" style="margin-left:5px") + el-tooltip(v-else placement="top" content="Set Representing" :disabled="hideTooltips") + span + el-button(type="default" icon="el-icon-star-off" circle @click="setGroupRepresentation(groupDialog.id)" style="margin-left:5px" :disabled="groupDialog.ref.privacy === 'private'") el-tooltip(v-else-if="groupDialog.ref.membershipStatus === 'requested'" placement="top" content="Cancel join request" :disabled="hideTooltips") el-button(type="default" icon="el-icon-close" circle @click="cancelGroupRequest(groupDialog.id)" style="margin-left:5px") template(v-else) @@ -2174,15 +2197,18 @@ html span el-button(type="default" icon="el-icon-message" disabled circle style="margin-left:5px") el-tooltip(v-if="groupDialog.ref.joinState === 'open'" placement="top" content="Join Group" :disabled="hideTooltips") - el-button(type="default" icon="el-icon-star-off" circle @click="joinGroup(groupDialog.id)" style="margin-left:5px") - + el-button(type="default" icon="el-icon-check" circle @click="joinGroup(groupDialog.id)" style="margin-left:5px") el-dropdown(trigger="click" @command="groupDialogCommand" size="small" style="margin-left:5px") el-button(type="default" icon="el-icon-more" circle) el-dropdown-menu(#default="dropdown") el-dropdown-item(icon="el-icon-refresh" command="Refresh") Refresh - template(v-if="groupDialog.inGroup") - el-dropdown-item(v-if="groupDialog.ref.isRepresenting" icon="el-icon-close" command="Clear Represented Group") Clear Represented Group - el-dropdown-item(v-else icon="el-icon-check" command="Set Represented Group") Set Represented Group + + template(v-if="groupDialog.ref.myMember") + el-dropdown-item(icon="el-icon-view" command="Visibility Everyone" divided) #[i.el-icon-check(v-if="groupDialog.ref.myMember.visibility === 'visible'")] Visibility Everyone + el-dropdown-item(icon="el-icon-view" command="Visibility Friends") #[i.el-icon-check(v-if="groupDialog.ref.myMember.visibility === 'friends'")] Visibility Friends + el-dropdown-item(icon="el-icon-view" command="Visibility Hidden") #[i.el-icon-check(v-if="groupDialog.ref.myMember.visibility === 'hidden'")] Visibility Hidden + + el-dropdown-item(v-if="groupDialog.inGroup" icon="el-icon-delete" command="Leave Group" style="color:#F56C6C" divided) Leave Group el-tabs el-tab-pane(label="Info") el-popover(placement="right" width="500px" trigger="click") @@ -2200,8 +2226,16 @@ html span.name Group ID span.extra {{ groupDialog.id }} el-tooltip(placement="top" content="Copy ID to clipboard" :disabled="hideTooltips") - el-button(type="default" @click="copyGroupId(groupDialog.id)" size="mini" icon="el-icon-s-order" style="margin-left:5px") - .x-friend-item(style="width:100%;cursor:default") + el-button(type="default" @click="copyGroupId(groupDialog.id)" size="mini" icon="el-icon-s-order" circle style="margin-left:5px") + .x-friend-item(style="cursor:default") + .detail + span.name Members + .extra {{ groupDialog.ref.memberCount }} ({{ groupDialog.ref.onlineMemberCount }}) + .x-friend-item(style="cursor:default") + .detail + span.name Created + span.extra {{ groupDialog.ref.createdAt | formatDate('long') }} + .x-friend-item(style="cursor:default") .detail span.name Links div(v-if="groupDialog.ref.links && groupDialog.ref.links.length > 0" style="margin-top:5px") @@ -2214,8 +2248,8 @@ html .detail span.name Rules pre.extra(style="font-family:inherit;font-size:12px;white-space:pre-wrap;margin:0 0.5em 0 0") {{ groupDialog.ref.rules || '-' }} - template(v-if="groupDialog.ref.membershipStatus === 'member'") - div(style="width:100%;display:flex") + div(v-if="groupDialog.ref.membershipStatus === 'member'" style="width:100%;margin-top:10px;border-top:1px solid #e4e7ed14") + div(style="width:100%;display:flex;margin-top:10px") .x-friend-item(style="cursor:default") .detail span.name Joined At