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();