diff --git a/html/src/app.js b/html/src/app.js index 03f4bcf8..1e9f228a 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -13173,6 +13173,8 @@ speechSynthesis.getVoices(); } } else if (command === 'Previous Images') { this.displayPreviousImages('User', 'Display'); + } else if (command === 'Previous Instances') { + this.showPreviousInstancesUserDialog(D.ref); } else if (command === 'Manage Gallery') { this.showGalleryDialog(); } else if (command === 'Copy User') { @@ -13546,6 +13548,9 @@ speechSynthesis.getVoices(); case 'Previous Images': this.displayPreviousImages('World', 'Display'); break; + case 'Previous Instances': + this.showPreviousInstancesWorldDialog(D.ref); + break; case 'Change Description': this.promptChangeWorldDescription(D); break; @@ -18476,6 +18481,184 @@ speechSynthesis.getVoices(); }); }; + // App: Previous Instances User Dialog + + $app.data.previousInstancesUserDialogTable = { + data: [], + filters: [ + { + prop: 'name', + value: '' + } + ], + tableProps: { + stripe: true, + size: 'mini', + defaultSort: { + prop: 'created_at', + order: 'descending' + } + }, + pageSize: 10, + paginationProps: { + small: true, + layout: 'sizes,prev,pager,next,total', + pageSizes: [10, 25, 50, 100] + } + }; + + $app.data.previousInstancesUserDialog = { + visible: false, + loading: false, + userRef: {} + }; + + $app.methods.showPreviousInstancesUserDialog = function (userRef) { + this.$nextTick(() => + adjustDialogZ(this.$refs.previousInstancesUserDialog.$el) + ); + var D = this.previousInstancesUserDialog; + D.userRef = userRef; + D.visible = true; + D.loading = true; + this.refreshPreviousInstancesUserTable(); + }; + + $app.methods.refreshPreviousInstancesUserTable = function () { + var D = this.previousInstancesUserDialog; + database.getpreviousInstancesByUserId(D.userRef).then((data) => { + var array = []; + for (var ref of data.values()) { + ref.$location = API.parseLocation(ref.location); + if (ref.time > 0) { + ref.timer = timeToText(ref.time); + } else { + ref.timer = ''; + } + array.push(ref); + } + array.sort(compareByCreatedAt); + this.previousInstancesUserDialogTable.data = array; + D.loading = false; + }); + }; + + $app.methods.getDisplayNameFromUserId = function (userId) { + var displayName = userId; + var ref = API.cachedUsers.get(userId); + if ( + typeof ref !== 'undefined' && + typeof ref.displayName !== 'undefined' + ) { + displayName = ref.displayName; + } + return displayName; + }; + + $app.methods.confirmDeleteGameLogUserInstance = function (row) { + this.$confirm('Continue? Delete', 'Confirm', { + confirmButtonText: 'Confirm', + cancelButtonText: 'Cancel', + type: 'info', + callback: (action) => { + if (action === 'confirm') { + database.deleteGameLogInstance({ + id: this.previousInstancesUserDialog.userRef.id, + displayName: + this.previousInstancesUserDialog.userRef + .displayName, + location: row.location + }); + removeFromArray( + this.previousInstancesUserDialogTable.data, + row + ); + } + } + }); + }; + + // App: Previous Instances World Dialog + + $app.data.previousInstancesWorldDialogTable = { + data: [], + filters: [ + { + prop: 'name', + value: '' + } + ], + tableProps: { + stripe: true, + size: 'mini', + defaultSort: { + prop: 'created_at', + order: 'descending' + } + }, + pageSize: 10, + paginationProps: { + small: true, + layout: 'sizes,prev,pager,next,total', + pageSizes: [10, 25, 50, 100] + } + }; + + $app.data.previousInstancesWorldDialog = { + visible: false, + loading: false, + worldRef: {} + }; + + $app.methods.showPreviousInstancesWorldDialog = function (worldRef) { + this.$nextTick(() => + adjustDialogZ(this.$refs.previousInstancesWorldDialog.$el) + ); + var D = this.previousInstancesWorldDialog; + D.worldRef = worldRef; + D.visible = true; + D.loading = true; + this.refreshPreviousInstancesWorldTable(); + }; + + $app.methods.refreshPreviousInstancesWorldTable = function () { + var D = this.previousInstancesWorldDialog; + database.getpreviousInstancesByWorldId(D.worldRef).then((data) => { + var array = []; + for (var ref of data.values()) { + ref.$location = API.parseLocation(ref.location); + if (ref.time > 0) { + ref.timer = timeToText(ref.time); + } else { + ref.timer = ''; + } + array.push(ref); + } + array.sort(compareByCreatedAt); + this.previousInstancesWorldDialogTable.data = array; + D.loading = false; + }); + }; + + $app.methods.confirmDeleteGameLogWorldInstance = function (row) { + this.$confirm('Continue? Delete', 'Confirm', { + confirmButtonText: 'Confirm', + cancelButtonText: 'Cancel', + type: 'info', + callback: (action) => { + if (action === 'confirm') { + database.deleteGameLogInstanceByInstanceId({ + location: row.location + }); + removeFromArray( + this.previousInstancesWorldDialogTable.data, + row + ); + } + } + }); + }; + $app = new Vue($app); window.$app = $app; })(); diff --git a/html/src/index.pug b/html/src/index.pug index 95ec6f63..d433af6b 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -1396,6 +1396,7 @@ html el-dropdown-item(v-else icon="el-icon-plus" command="Send Friend Request") Send Friend Request el-dropdown-item(icon="el-icon-s-custom" command="Show Avatar Author" divided) Show Avatar Author el-dropdown-item(icon="el-icon-s-custom" command="Show Fallback Avatar Details") Show Fallback Avatar Details + el-dropdown-item(icon="el-icon-tickets" command="Previous Instances") Show Previous Instances el-dropdown-item(v-if="userDialog.ref.currentAvatarImageUrl !== robotUrl" icon="el-icon-picture-outline" command="Previous Images") Show Avatar Previous Images el-dropdown-item(v-if="userDialog.isBlock" icon="el-icon-circle-check" command="Unblock" divided style="color:#F56C6C") Unblock el-dropdown-item(v-else icon="el-icon-circle-close" command="Block" divided :disabled="userDialog.ref.$isModerator") Block @@ -1461,7 +1462,7 @@ html .detail span.name Last Seen span.extra {{ userDialog.lastSeen | formatDate('YYYY-MM-DD HH24:MI:SS') || '-' }} - .x-friend-item(style="cursor:default") + .x-friend-item(@click="showPreviousInstancesUserDialog(userDialog.ref)") .detail span.name Join Count span.extra(v-if="userDialog.joinCount === 0") - @@ -1601,8 +1602,9 @@ html el-dropdown-item(icon="el-icon-s-flag" command="New Instance" divided) New Instance el-dropdown-item(v-if="API.currentUser.$homeLocation && API.currentUser.$homeLocation.worldId === worldDialog.id" icon="el-icon-magic-stick" command="Reset Home" divided) Reset Home el-dropdown-item(v-else icon="el-icon-s-home" command="Make Home" divided) Make Home + el-dropdown-item(icon="el-icon-tickets" command="Previous Instances") Show Previous Instances template(v-if="API.currentUser.id !== worldDialog.ref.authorId") - el-dropdown-item(icon="el-icon-picture-outline" command="Previous Images") Previous Images + el-dropdown-item(icon="el-icon-picture-outline" command="Previous Images") Show Previous Images template(v-else) el-dropdown-item(icon="el-icon-edit" command="Rename") Rename el-dropdown-item(icon="el-icon-edit" command="Change Description") Change Description @@ -1709,7 +1711,7 @@ html .detail span.name Last Visit span.extra {{ worldDialog.lastVisit | formatDate('YYYY-MM-DD HH24:MI:SS') || '-' }} - .x-friend-item(style="cursor:default") + .x-friend-item(@click="showPreviousInstancesWorldDialog(worldDialog.ref)") .detail span.name Visit Count span.extra(v-text="worldDialog.visitCount") @@ -2715,6 +2717,60 @@ html div(style="float:right;margin-top:5px") el-button(type="default" @click="openExternalLink(image.versions[1].file.url)" size="mini" icon="el-icon-paperclip" circle) el-button(type="default" @click="deleteVRCPlusIcon(image.id)" size="mini" icon="el-icon-delete" circle style="margin-left:5px") + + //- dialog Table: Previous Instances User + el-dialog.x-dialog(ref="previousInstancesUserDialog" :visible.sync="previousInstancesUserDialog.visible" title="Previous Instances" width="800px") + span(v-text="previousInstancesUserDialog.userRef.displayName" style="font-size:14px") + el-input(v-model="previousInstancesUserDialogTable.filters[0].value" placeholder="Search" style="display:block;width:150px;margin-top:15px") + data-tables(v-bind="previousInstancesUserDialogTable" v-loading="previousInstancesUserDialog.loading" style="margin-top:10px") + el-table-column(label="Date" prop="created_at" sortable width="90") + template(v-once #default="scope") + el-tooltip(placement="left") + template(#content) + span {{ scope.row.created_at | formatDate('YYYY-MM-DD HH24:MI:SS') }} + span {{ scope.row.created_at | formatDate('MM-DD HH24:MI') }} + el-table-column(label="World" prop="name" sortable) + template(v-once #default="scope") + location(:location="scope.row.location" :hint="scope.row.name") + el-table-column(label="Instance Creator" prop="location" width="160") + template(v-once #default="scope") + span.x-link(v-text="getDisplayNameFromUserId(scope.row.$location.userId)" @click="showUserDialog(scope.row.$location.userId)" style="cursor:pointer") + el-table-column(label="Time" prop="time" width="90" sortable) + template(v-once #default="scope") + span(v-text="scope.row.timer") + el-table-column(label="Action" width="60" align="right") + template(v-once #default="scope") + el-button(type="text" icon="el-icon-message" size="mini" @click="showLaunchDialog(scope.row.location)") + el-button(type="text" icon="el-icon-close" size="mini" @click="confirmDeleteGameLogUserInstance(scope.row)") + + //- dialog Table: Previous Instances World + el-dialog.x-dialog(ref="previousInstancesWorldDialog" :visible.sync="previousInstancesWorldDialog.visible" title="Previous Instances" width="800px") + span(v-text="previousInstancesWorldDialog.worldRef.name" style="font-size:14px") + el-input(v-model="previousInstancesWorldDialogTable.filters[0].value" placeholder="Search" style="display:block;width:150px;margin-top:15px") + data-tables(v-bind="previousInstancesWorldDialogTable" v-loading="previousInstancesWorldDialog.loading" style="margin-top:10px") + el-table-column(label="Date" prop="created_at" sortable width="90") + template(v-once #default="scope") + el-tooltip(placement="left") + template(#content) + span {{ scope.row.created_at | formatDate('YYYY-MM-DD HH24:MI:SS') }} + span {{ scope.row.created_at | formatDate('MM-DD HH24:MI') }} + 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.famfamfam-flags(v-if="scope.row.$location.region === 'eu'" class="europeanunion" style="display:inline-block;margin-left:5px") + span.famfamfam-flags(v-else-if="scope.row.$location.region === 'jp'" class="jp" style="display:inline-block;margin-left:5px") + span.flag-icon-use(v-else-if="scope.row.$location.region === 'use'" style="display:inline-block;margin-left:5px") + span.flag-icon-usw(v-else style="display:inline-block;margin-left:5px") + el-table-column(label="Instance Creator" prop="location") + template(v-once #default="scope") + span.x-link(v-text="getDisplayNameFromUserId(scope.row.$location.userId)" @click="showUserDialog(scope.row.$location.userId)" style="cursor:pointer") + el-table-column(label="Time" prop="time" width="90" sortable) + template(v-once #default="scope") + span(v-text="scope.row.timer") + el-table-column(label="Action" width="60" align="right") + template(v-once #default="scope") + el-button(type="text" icon="el-icon-close" size="mini" @click="confirmDeleteGameLogWorldInstance(scope.row)") //- dialog: open source software notice el-dialog.x-dialog(:visible.sync="ossDialog" title="Open Source Software Notice" width="650px") diff --git a/html/src/repository/database.js b/html/src/repository/database.js index 276fa690..243c0018 100644 --- a/html/src/repository/database.js +++ b/html/src/repository/database.js @@ -140,16 +140,21 @@ class Database { sqliteService.executeNonQuery('COMMIT'); } - async getMemo(input) { - var userId = input.replaceAll("'", ''); + async getMemo(userId) { var row = {}; - await sqliteService.execute((dbRow) => { - row = { - userId: dbRow[0], - editedAt: dbRow[1], - memo: dbRow[2] - }; - }, `SELECT * FROM memos WHERE user_id = '${userId}'`); + await sqliteService.execute( + (dbRow) => { + row = { + userId: dbRow[0], + editedAt: dbRow[1], + memo: dbRow[2] + }; + }, + `SELECT * FROM memos WHERE user_id = @user_id`, + { + '@user_id': userId + } + ); return row; } @@ -721,52 +726,68 @@ class Database { return size; } - async getLastVisit(input, currentWorldMatch) { + async getLastVisit(worldId, currentWorldMatch) { if (currentWorldMatch) { var count = 2; } else { var count = 1; } - var worldId = input.replaceAll("'", ''); var ref = { created_at: '', worldId: '' }; - await sqliteService.execute((row) => { - ref = { - created_at: row[0], - worldId: row[1] - }; - }, `SELECT created_at, world_id FROM gamelog_location WHERE world_id = '${worldId}' ORDER BY id DESC LIMIT ${count}`); + await sqliteService.execute( + (row) => { + ref = { + created_at: row[0], + worldId: row[1] + }; + }, + `SELECT created_at, world_id FROM gamelog_location WHERE world_id = @worldId ORDER BY id DESC LIMIT @count`, + { + '@worldId': worldId, + '@count': count + } + ); return ref; } - async getVisitCount(input) { - var worldId = input.replaceAll("'", ''); + async getVisitCount(worldId) { var ref = { visitCount: '', worldId: '' }; - await sqliteService.execute((row) => { - ref = { - visitCount: row[0], - worldId: input - }; - }, `SELECT COUNT(*) FROM gamelog_location WHERE world_id = '${worldId}'`); + await sqliteService.execute( + (row) => { + ref = { + visitCount: row[0], + worldId + }; + }, + `SELECT COUNT(DISTINCT location) FROM gamelog_location WHERE world_id = @worldId`, + { + '@worldId': worldId + } + ); return ref; } - async getTimeSpentInWorld(input) { - var worldId = input.replaceAll("'", ''); + async getTimeSpentInWorld(worldId) { var ref = { timeSpent: 0, - worldId: input + worldId }; - await sqliteService.execute((row) => { - if (typeof row[0] === 'number') { - ref.timeSpent += row[0]; + await sqliteService.execute( + (row) => { + if (typeof row[0] === 'number') { + ref.timeSpent += row[0]; + } + }, + `SELECT time FROM gamelog_location WHERE world_id = @worldId`, + { + '@worldId': worldId } - }, `SELECT time FROM gamelog_location WHERE world_id = '${worldId}'`); + ); return ref; } @@ -776,63 +797,79 @@ class Database { } else { var count = 1; } - var userId = input.id.replaceAll("'", ''); - var displayName = input.displayName.replaceAll("'", "''"); var ref = { created_at: '', userId: '' }; - await sqliteService.execute((row) => { - if (row[1]) { - ref = { - created_at: row[0], - userId: row[1] - }; - } else { - ref = { - created_at: row[0], - userId - }; + await sqliteService.execute( + (row) => { + if (row[1]) { + ref = { + created_at: row[0], + userId: row[1] + }; + } else { + ref = { + created_at: row[0], + userId: input.id + }; + } + }, + `SELECT created_at, user_id FROM gamelog_join_leave WHERE user_id = @userId OR display_name = @displayName ORDER BY id DESC LIMIT @count`, + { + '@userId': input.id, + '@displayName': input.displayName, + '@count': count } - }, `SELECT created_at, user_id FROM gamelog_join_leave WHERE user_id = '${userId}' OR display_name = '${displayName}' ORDER BY id DESC LIMIT ${count}`); + ); return ref; } async getJoinCount(input) { - var userId = input.id.replaceAll("'", ''); - var displayName = input.displayName.replaceAll("'", "''"); var ref = { joinCount: '', userId: '' }; - await sqliteService.execute((row) => { - if (row[1]) { - ref = { - joinCount: row[0], - userId: row[1] - }; - } else { - ref = { - joinCount: row[0], - userId - }; + await sqliteService.execute( + (row) => { + if (row[1]) { + ref = { + joinCount: row[0], + userId: row[1] + }; + } else { + ref = { + joinCount: row[0], + userId: input.id + }; + } + }, + `SELECT COUNT(DISTINCT location) FROM gamelog_join_leave WHERE (type = 'OnPlayerJoined') AND (user_id = @userId OR display_name = @displayName)`, + { + '@userId': input.id, + '@displayName': input.displayName } - }, `SELECT COUNT(*) FROM gamelog_join_leave WHERE (type = 'OnPlayerJoined') AND (user_id = '${userId}' OR display_name = '${displayName}')`); + ); return ref; } async getTimeSpent(input) { - var userId = input.id.replaceAll("'", ''); - var displayName = input.displayName.replaceAll("'", "''"); var ref = { timeSpent: 0, - userId + userId: input.id }; - await sqliteService.execute((row) => { - if (typeof row[0] === 'number') { - ref.timeSpent += row[0]; + await sqliteService.execute( + (row) => { + if (typeof row[0] === 'number') { + ref.timeSpent += row[0]; + } + }, + `SELECT time FROM gamelog_join_leave WHERE (type = 'OnPlayerLeft') AND (user_id = @userId OR display_name = @displayName)`, + { + '@userId': input.id, + '@displayName': input.displayName } - }, `SELECT time FROM gamelog_join_leave WHERE (type = 'OnPlayerLeft') AND (user_id = '${userId}' OR display_name = '${displayName}')`); + ); return ref; } @@ -1135,26 +1172,31 @@ class Database { return date; } - async getModeration(input) { - var userId = input.replaceAll("'", ''); + async getModeration(userId) { var row = {}; - await sqliteService.execute((dbRow) => { - var block = false; - var mute = false; - if (dbRow[3] === 1) { - block = true; + await sqliteService.execute( + (dbRow) => { + var block = false; + var mute = false; + if (dbRow[3] === 1) { + block = true; + } + if (dbRow[4] === 1) { + mute = true; + } + row = { + userId: dbRow[0], + updatedAt: dbRow[1], + displayName: dbRow[2], + block, + mute + }; + }, + `SELECT * FROM ${Database.userPrefix}_moderation WHERE user_id = @userId`, + { + '@userId': userId } - if (dbRow[4] === 1) { - mute = true; - } - row = { - userId: dbRow[0], - updatedAt: dbRow[1], - displayName: dbRow[2], - block, - mute - }; - }, `SELECT * FROM ${Database.userPrefix}_moderation WHERE user_id = '${userId}'`); + ); return row; } @@ -1187,6 +1229,90 @@ class Database { } ); } + + async getpreviousInstancesByUserId(input) { + var data = new Map(); + await sqliteService.execute( + (dbRow) => { + var time = 0; + if (dbRow[2] && dbRow[2] > 0) { + time = dbRow[2]; + } + var ref = data.get(dbRow[1]); + if (typeof ref !== 'undefined') { + time = +Number(ref.time); + } + var row = { + created_at: dbRow[0], + location: dbRow[1], + time, + name: dbRow[3] + }; + data.set(row.location, row); + }, + `SELECT gamelog_join_leave.created_at, gamelog_join_leave.location, gamelog_join_leave.time, gamelog_location.world_name + FROM gamelog_join_leave + INNER JOIN gamelog_location ON gamelog_join_leave.location = gamelog_location.location + WHERE user_id = @userId OR display_name = @displayName + ORDER BY gamelog_join_leave.id DESC`, + { + '@userId': input.id, + '@displayName': input.displayName + } + ); + return data; + } + + deleteGameLogInstance(input) { + sqliteService.executeNonQuery( + `DELETE FROM gamelog_join_leave WHERE (user_id = @user_id OR display_name = @displayName) AND (location = @location)`, + { + '@user_id': input.id, + '@displayName': input.displayName, + '@location': input.location + } + ); + } + + async getpreviousInstancesByWorldId(input) { + var data = new Map(); + await sqliteService.execute( + (dbRow) => { + var time = 0; + if (dbRow[2] && dbRow[2] > 0) { + time = dbRow[2]; + } + var ref = data.get(dbRow[1]); + if (typeof ref !== 'undefined') { + time = +Number(ref.time); + } + var row = { + created_at: dbRow[0], + location: dbRow[1], + time, + name: dbRow[3] + }; + data.set(row.location, row); + }, + `SELECT created_at, location, time, world_name + FROM gamelog_location + WHERE world_id = @worldId + ORDER BY id DESC`, + { + '@worldId': input.id + } + ); + return data; + } + + deleteGameLogInstanceByInstanceId(input) { + sqliteService.executeNonQuery( + `DELETE FROM gamelog_location WHERE location = @location`, + { + '@location': input.location + } + ); + } } var self = new Database();