diff --git a/html/src/app.js b/html/src/app.js index a15306b0..4452ead4 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -810,9 +810,11 @@ speechSynthesis.getVoices(); Vue.component('location', { template: - "" + + "" + '' + - '{{ text }}' + + '{{ text }}' + + '{{ groupName }}' + + '' + '', props: { location: String, @@ -831,12 +833,14 @@ speechSynthesis.getVoices(); text: this.location, region: this.region, strict: this.strict, - isTraveling: this.isTraveling + isTraveling: this.isTraveling, + groupName: this.groupName }; }, methods: { parse() { this.isTraveling = false; + this.groupName = ''; var instanceId = this.location; if ( typeof this.traveling !== 'undefined' && @@ -880,6 +884,13 @@ speechSynthesis.getVoices(); this.text = ref.name; } } + if (L.groupId) { + this.groupName = L.groupId; + API.getCachedGroup({groupId: L.groupId}).then((args) => { + this.groupName = args.json.name; + return args; + }); + } this.region = ''; if ($app.isRealInstance(instanceId)) { this.region = L.region; @@ -902,6 +913,16 @@ speechSynthesis.getVoices(); } API.$emit('SHOW_WORLD_DIALOG', instanceId); } + }, + showGroupDialog() { + if (!this.location) { + return; + } + var L = API.parseLocation(this.location); + if (!L.groupId) { + return; + } + API.$emit('SHOW_GROUP_DIALOG', L.groupId); } }, watch: { @@ -914,6 +935,88 @@ speechSynthesis.getVoices(); } }); + Vue.component('location-world', { + template: + '' + + '' + + ' #{{ instanceName }} {{ accessType }}' + + '{{ groupName }}' + + '' + + '', + props: { + locationobject: Object, + currentuserid: String, + worlddialogshortname: String + }, + data() { + return { + location: this.location, + instanceName: this.instanceName, + accessType: this.accessType, + region: this.region, + isUnlocked: this.isUnlocked, + strict: this.strict, + groupName: this.groupName + }; + }, + methods: { + parse() { + this.location = this.locationobject.tag; + this.instanceName = this.locationobject.instanceName; + this.accessType = this.locationobject.accessType; + this.strict = this.locationobject.strict; + + this.isUnlocked = false; + if ( + (this.worlddialogshortname && + this.locationobject.shortName && + this.worlddialogshortname === + this.locationobject.shortName) || + this.currentuserid === this.locationobject.userId + ) { + this.isUnlocked = true; + } + + this.region = this.locationobject.region; + if (!this.region) { + this.region = 'us'; + } + + this.groupName = ''; + if (this.locationobject.groupId) { + this.groupName = this.locationobject.groupId; + API.getCachedGroup({ + groupId: this.locationobject.groupId + }).then((args) => { + this.groupName = args.json.name; + return args; + }); + } + }, + showLaunchDialog() { + API.$emit('SHOW_LAUNCH_DIALOG', this.location); + }, + showGroupDialog() { + if (!this.location) { + return; + } + var L = API.parseLocation(this.location); + if (!L.groupId) { + return; + } + API.$emit('SHOW_GROUP_DIALOG', L.groupId); + } + }, + watch: { + locationobject() { + this.parse(); + } + }, + created() { + this.parse(); + } + }); + Vue.component('avatar-info', { template: '
{{ avatarName }}{{ avatarType }}
', @@ -2814,10 +2917,10 @@ speechSynthesis.getVoices(); API.$on('NOTIFICATION:RESPONSE', function (args) { this.$emit('NOTIFICATION:HIDE', args); - $app.$message({ - message: args.json, - type: 'success' - }); + new Noty({ + type: 'success', + text: escapeTag(args.json) + }).show(); console.log('NOTIFICATION:RESPONSE', args); }); @@ -4305,7 +4408,20 @@ speechSynthesis.getVoices(); var node = []; for (var key in json) { var value = json[key]; - if (Array.isArray(value)) { + if (Array.isArray(value) && value.length === 0) { + node.push({ + key, + value: '[]' + }); + } else if ( + value === Object(value) && + Object.keys(value).length === 0 + ) { + node.push({ + key, + value: '{}' + }); + } else if (Array.isArray(value)) { node.push({ children: value.map((val, idx) => { if (val === Object(val)) { @@ -4484,6 +4600,7 @@ speechSynthesis.getVoices(); API.$on('SHOW_WORLD_DIALOG_SHORTNAME', (tag) => this.verifyShortName('', tag) ); + API.$on('SHOW_GROUP_DIALOG', (tag) => this.showGroupDialog(tag)); API.$on('SHOW_LAUNCH_DIALOG', (tag) => this.showLaunchDialog(tag)); this.updateLoop(); this.getGameLogTable(); @@ -8928,8 +9045,6 @@ speechSynthesis.getVoices(); } }; - $app.data.recommendedSteamParams = - 'https://gist.github.com/Natsumi-sama/d280a58f08ace3da0e8fc7a9a381d44e'; $app.data.lastPortalList = new Map(); $app.data.moderationEventQueue = new Map(); $app.data.moderationAgainstTable = []; @@ -9494,7 +9609,6 @@ speechSynthesis.getVoices(); break; case 254: // Leave - this.checkPhotonBotLeave(data.Parameters[254], gameLogDate); this.photonUserLeave(data.Parameters[254], gameLogDate); this.photonLobbyCurrent.delete(data.Parameters[254]); this.photonLobbyJointime.delete(data.Parameters[254]); @@ -9904,23 +10018,6 @@ speechSynthesis.getVoices(); } }; - $app.methods.checkPhotonBotLeave = function (photonId, gameLogDate) { - var lobbyJointime = this.photonLobbyJointime.get(photonId); - if ( - typeof lobbyJointime !== 'undefined' && - !lobbyJointime.hasInstantiated - ) { - var time = timeToText(Date.now() - lobbyJointime.joinTime); - this.addEntryPhotonEvent({ - photonId, - text: `User left without instantiating ${time}`, - type: 'PhotonBot', - color: 'yellow', - created_at: gameLogDate - }); - } - }; - $app.methods.parsePhotonUser = async function ( photonId, user, @@ -21159,7 +21256,7 @@ speechSynthesis.getVoices(); $app.data.databaseVersion = configRepository.getInt('VRCX_databaseVersion'); $app.methods.updateDatabaseVersion = async function () { - var databaseVersion = 2; + var databaseVersion = 3; if (this.databaseVersion !== databaseVersion) { console.log( `Updating database from ${this.databaseVersion} to ${databaseVersion}...` @@ -22954,10 +23051,6 @@ speechSynthesis.getVoices(); if (!groupId) { return; } - if (groupId.startsWith('group:')) { - // eslint-disable-next-line no-param-reassign - groupId = groupId.substr(6); - } this.$nextTick(() => adjustDialogZ(this.$refs.groupDialog.$el)); var D = this.groupDialog; D.visible = true; @@ -23178,17 +23271,17 @@ speechSynthesis.getVoices(); $app.methods.sendNotificationResponse = function ( notificationId, - link, + responses, response ) { - if (!link) { + if (!Array.isArray(responses) || responses.length === 0) { return null; } - var groupId = link.split(':').pop(); + var responseData = responses[0].data; return API.sendNotificationResponse({ notificationId, responseType: response, - responseData: groupId + responseData }); }; @@ -23264,7 +23357,8 @@ speechSynthesis.getVoices(); D.ref && D.ref.myMember && D.ref.myMember.permissions && - D.ref.myMember.permissions.includes('group-members-viewall') + (D.ref.myMember.permissions.includes('*') || + D.ref.myMember.permissions.includes('group-members-viewall')) ) { return true; } @@ -23370,13 +23464,13 @@ speechSynthesis.getVoices(); API.getGroup({groupId}) .then((args) => { var group = args.ref; - if (group.joinState === 'open') { - return args; - } if ( group.myMember && group.myMember.permissions && - group.myMember.permissions.includes('group-invites-manage') + (group.myMember.permissions.includes('*') || + group.myMember.permissions.includes( + 'group-invites-manage' + )) ) { return args; } @@ -23393,6 +23487,21 @@ speechSynthesis.getVoices(); }); }; + $app.methods.openNotificationLink = function (link) { + if (!link) { + return; + } + var data = link.split(':'); + switch (data[0]) { + case 'group': + this.showGroupDialog(data[1]); + break; + case 'user': + this.showUserDialog(data[1]); + break; + } + }; + $app = new Vue($app); window.$app = $app; })(); diff --git a/html/src/index.pug b/html/src/index.pug index c8a30bcf..558441ae 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -100,11 +100,7 @@ html span(v-text="currentInstanceWorld.cacheSize") | Cache br - span.x-link(v-if="currentInstanceLocation.instanceName" @click="showLaunchDialog(lastLocation.location)") - span \#{{ currentInstanceLocation.instanceName }} {{ currentInstanceLocation.accessType }} - span.flags(v-if="currentInstanceLocation.region" :class="currentInstanceLocation.region" style="display:inline-block;margin-left:5px") - span.flags(v-else class="us" style="display:inline-block;margin-left:5px") - i.el-icon-lock(v-if="currentInstanceLocation.strict" style="display:inline-block;margin-left:5px") + location-world(:locationobject="currentInstanceLocation" :currentuserid="API.currentUser.id") span(v-if="lastLocation.playerList.size > 0" style="margin-left:5px") | {{ lastLocation.playerList.size }} | #[template(v-if="lastLocation.friendList.size > 0") ({{ lastLocation.friendList.size }})] @@ -763,8 +759,9 @@ html template(#content) location(v-if="scope.row.details" :location="scope.row.details.worldId" :hint="scope.row.details.worldName" :link="false") span.x-link(v-text="scope.row.type" @click="showWorldDialog(scope.row.details.worldId)") - template(v-else-if="scope.row.type && (scope.row.type.startsWith('group.') || scope.row.type === 'moderation.warning.group')") - span.x-link(v-text="scope.row.type" @click="showGroupDialog(scope.row.link)") + template(v-else-if="scope.row.link") + el-tooltip(placement="top" :content="scope.row.linkText" :disabled="hideTooltips") + span.x-link(v-text="scope.row.type" @click="openNotificationLink(scope.row.link)") span(v-else v-text="scope.row.type") el-table-column(label="User" prop="senderUsername" width="150") template(v-once #default="scope") @@ -802,11 +799,26 @@ html el-button(type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="showSendInviteRequestResponseDialog(scope.row)") 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-button(type="text" icon="el-icon-check" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, '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')") + el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, 'decline')") + el-tooltip(placement="top" content="Block invites from group" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-circle-close" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, 'block')") + template(v-else-if="scope.row.type === 'group.joinRequest'") + el-tooltip(placement="top" content="Accept" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-check" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, 'accept')") + el-tooltip(placement="top" content="Decline" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, 'reject')") + el-tooltip(placement="top" content="Block user from requesting" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-circle-close" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, 'block')") + template(v-else-if="scope.row.type === 'group.announcement'") + el-tooltip(placement="top" content="Dismiss" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-check" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, 'delete')") + el-tooltip(placement="top" content="Unsubscribe" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, 'unsubscribe')") + template(v-else-if="scope.row.type === 'group.informative'") + el-tooltip(placement="top" content="Dismiss" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-check" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, 'delete')") template(v-if="scope.row.type !== 'requestInviteResponse' && scope.row.type !== 'inviteResponse' && scope.row.type !== 'message' && !scope.row.type.includes('group.') && !scope.row.type.includes('moderation.')") 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)") @@ -1721,6 +1733,8 @@ html .detail span.name Bio pre.extra(style="font-family:inherit;font-size:12px;white-space:pre-wrap;margin:0 0.5em 0 0") {{ userDialog.ref.bio || '-' }} + div(v-if="userDialog.id === API.currentUser.id" style="float:right") + el-button(type="text" icon="el-icon-edit" size="mini" @click="showBioDialog" style="margin-left:5px") div(style="margin-top:5px") el-tooltip(v-if="link" v-for="(link, index) in userDialog.ref.bioLinks" :key="index") template(#content) @@ -1950,12 +1964,7 @@ html #[i.el-icon-check(style="margin-left:10px")] Capacity {{ worldDialog.ref.capacity | commaNumber }} ({{ worldDialog.ref.capacity * 2 | commaNumber }}) div(v-for="room in worldDialog.rooms" :key="room.id") div(style="margin:5px 0") - span.x-link(@click="showLaunchDialog(room.$location.tag, room.$location.shortName)") - i.el-icon-unlock(v-if="(room.$location.shortName && worldDialog.$location.shortName === room.$location.shortName) || API.currentUser.id === room.$location.userId" style="display:inline-block;margin-right:5px") - span \#{{ room.$location.instanceName }} {{ room.$location.accessType }} - span.flags(v-if="room.$location.region" :class="room.$location.region" style="display:inline-block;margin-left:5px") - span.flags(v-else class="us" style="display:inline-block;margin-left:5px") - i.el-icon-lock(v-if="room.$location.strict" style="display:inline-block;margin-left:5px") + location-world(:locationobject="room.$location" :currentuserid="API.currentUser.id" :worlddialogshortname="worldDialog.$location.shortName") el-tooltip(placement="top" content="Invite yourself" :disabled="hideTooltips") invite-yourself(:location="room.$location.tag" :shortname="room.$location.shortName" style="margin-left:5px") el-tooltip(placement="top" content="Refresh player count" :disabled="hideTooltips") @@ -2199,12 +2208,14 @@ html 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") - span - el-button(type="default" icon="el-icon-close" circle @click="cancelGroupRequest(groupDialog.id)" style="margin-left:5px") - el-tooltip(v-else-if="groupDialog.ref.membershipStatus === 'invited'" placement="top" content="Pending invite" :disabled="hideTooltips") - span - el-button(type="default" icon="el-icon-check" circle @click="joinGroup(groupDialog.id)" style="margin-left:5px") + template(v-else-if="groupDialog.ref.membershipStatus === 'requested'") + el-tooltip(placement="top" content="Cancel join request" :disabled="hideTooltips") + span + el-button(type="default" icon="el-icon-close" circle @click="cancelGroupRequest(groupDialog.id)" style="margin-left:5px") + template(v-else-if="groupDialog.ref.membershipStatus === 'invited'") + el-tooltip(placement="top" content="Pending invite" :disabled="hideTooltips") + span + el-button(type="default" icon="el-icon-check" circle @click="joinGroup(groupDialog.id)" style="margin-left:5px") template(v-else) el-tooltip(v-if="groupDialog.ref.joinState === 'request'" placement="top" content="Request to join" :disabled="hideTooltips") el-button(type="default" icon="el-icon-message" circle @click="joinGroup(groupDialog.id)" style="margin-left:5px") @@ -3289,11 +3300,7 @@ html span {{ scope.row.created_at | formatDate('short') }} el-table-column(label="Instance Name" prop="name") template(v-once #default="scope") - span.x-link(@click="showLaunchDialog(scope.row.location)") - span \#{{ scope.row.$location.instanceName }} {{ scope.row.$location.accessType }} - span.flags(v-if="scope.row.$location.region" :class="scope.row.$location.region" style="display:inline-block;margin-left:5px") - span.flags(v-else class="us" style="display:inline-block;margin-left:5px") - i.el-icon-lock(v-if="scope.row.$location.strict" style="display:inline-block;margin-left:5px") + location-world(:locationobject="scope.row.$location" :currentuserid="API.currentUser.id") el-table-column(label="Instance Creator" prop="location") template(v-once #default="scope") display-name(:userid="scope.row.$location.userId" :location="scope.row.$location.tag" :key="previousInstancesWorldDialog.forceUpdate") diff --git a/html/src/repository/database.js b/html/src/repository/database.js index 2109f995..ae85c2a3 100644 --- a/html/src/repository/database.js +++ b/html/src/repository/database.js @@ -1752,9 +1752,15 @@ class Database { } async fixBrokenGroupInvites() { - await sqliteService.executeNonQuery( - `DELETE FROM ${Database.userPrefix}_notifications WHERE type LIKE '%.%'` - ); + var notificationTables = []; + await sqliteService.execute((dbRow) => { + notificationTables.push(dbRow[0]); + }, `SELECT name FROM sqlite_schema WHERE type='table' AND name LIKE '%_notifications'`); + notificationTables.forEach((tableName) => { + sqliteService.executeNonQuery( + `DELETE FROM ${tableName} WHERE type LIKE '%.%'` + ); + }); } }