diff --git a/html/src/app.js b/html/src/app.js
index 71281157..2c325f0b 100644
--- a/html/src/app.js
+++ b/html/src/app.js
@@ -19239,6 +19239,65 @@ speechSynthesis.getVoices();
});
};
+ // App: Previous Instance Info Dialog
+
+ $app.data.previousInstanceInfoDialogTable = {
+ data: [],
+ filters: [
+ {
+ prop: 'displayName',
+ 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.previousInstanceInfoDialog = {
+ visible: false,
+ loading: false,
+ forceUpdate: 0,
+ $location: {}
+ };
+
+ $app.methods.showPreviousInstanceInfoDialog = function (instanceId) {
+ this.$nextTick(() =>
+ adjustDialogZ(this.$refs.previousInstanceInfoDialog.$el)
+ );
+ var D = this.previousInstanceInfoDialog;
+ D.$location = API.parseLocation(instanceId);
+ D.visible = true;
+ D.loading = true;
+ this.refreshPreviousInstanceInfoTable();
+ };
+
+ $app.methods.refreshPreviousInstanceInfoTable = function () {
+ var D = this.previousInstanceInfoDialog;
+ database.getPlayersFromInstance(D.$location.tag).then((data) => {
+ var array = [];
+ for (var entry of Array.from(data.values())) {
+ entry.timer = timeToText(entry.time);
+ array.push(entry);
+ }
+ array.sort(compareByCreatedAt);
+ this.previousInstanceInfoDialogTable.data = array;
+ D.loading = false;
+ workerTimers.setTimeout(() => D.forceUpdate++, 150);
+ });
+ };
+
$app.data.dtHour12 = configRepository.getBool('VRCX_dtHour12');
$app.data.dtIsoFormat = configRepository.getBool('VRCX_dtIsoFormat');
$app.methods.setDatetimeFormat = async function () {
diff --git a/html/src/index.pug b/html/src/index.pug
index 1466ee6e..ca7b3248 100644
--- a/html/src/index.pug
+++ b/html/src/index.pug
@@ -2230,15 +2230,19 @@ html
el-button(v-if="VRCXUpdateDialog.updatePending" type="primary" size="small" @click="restartVRCX") Install
//- dialog: launch
- el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="launchDialog" :visible.sync="launchDialog.visible" title="Launch" width="400px")
+ el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="launchDialog" :visible.sync="launchDialog.visible" title="Launch" width="450px")
div #[span(v-text="launchDialog.shortUrl" style="word-break:break-all;font-size:12px")]
el-tooltip(placement="top" content="Copy to clipboard" :disabled="hideTooltips")
el-button(v-if="launchDialog.shortUrl" @click="copyInstanceUrl(launchDialog.shortUrl)" size="mini" icon="el-icon-s-order" style="margin-left:5px" circle)
div(style="margin-top:10px") #[span(v-text="launchDialog.url" style="word-break:break-all;font-size:12px")]
el-tooltip(placement="top" content="Copy to clipboard" :disabled="hideTooltips")
el-button(@click="copyInstanceUrl(launchDialog.url)" size="mini" icon="el-icon-s-order" style="margin-left:5px" circle)
+ div(style="margin-top:10px") #[span(v-text="launchDialog.location" style="word-break:break-all;font-size:12px")]
+ el-tooltip(placement="top" content="Copy to clipboard" :disabled="hideTooltips")
+ el-button(@click="copyInstanceUrl(launchDialog.location)" size="mini" icon="el-icon-s-order" style="margin-left:5px" circle)
template(#footer)
el-checkbox(v-model="launchDialog.desktop" style="float:left;margin-top:5px") Start as Desktop (No VR)
+ el-button(size="small" @click="showPreviousInstanceInfoDialog(launchDialog.location)") Info
el-button(size="small" @click="showInviteDialog(launchDialog.location)" :disabled="checkCanInvite(launchDialog.location)") Invite
el-button(type="primary" size="small" @click="launchGame(locationToLaunchArg(launchDialog.location))") Launch
@@ -2845,9 +2849,10 @@ html
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")
+ el-table-column(label="Action" width="90" align="right")
template(v-once #default="scope")
el-button(type="text" icon="el-icon-info" size="mini" @click="showLaunchDialog(scope.row.location)")
+ el-button(type="text" icon="el-icon-tickets" size="mini" @click="showPreviousInstanceInfoDialog(scope.row.location)")
el-button(type="text" icon="el-icon-close" size="mini" @click="confirmDeleteGameLogUserInstance(scope.row)")
//- dialog Table: Previous Instances World
@@ -2876,10 +2881,32 @@ html
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")
+ el-table-column(label="Action" width="90" align="right")
template(v-once #default="scope")
+ el-button(type="text" icon="el-icon-tickets" size="mini" @click="showPreviousInstanceInfoDialog(scope.row.location)")
el-button(type="text" icon="el-icon-close" size="mini" @click="confirmDeleteGameLogWorldInstance(scope.row)")
+ //- dialog Table: Previous Instance Info
+ el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="previousInstanceInfoDialog" :visible.sync="previousInstanceInfoDialog.visible" title="Previous Instance Info" width="800px")
+ location(:location="previousInstanceInfoDialog.$location.tag" style="font-size:14px")
+ el-input(v-model="previousInstanceInfoDialogTable.filters[0].value" placeholder="Search" style="display:block;width:150px;margin-top:15px")
+ data-tables(v-if="previousInstanceInfoDialog.visible" v-bind="previousInstanceInfoDialogTable" v-loading="previousInstanceInfoDialog.loading" style="margin-top:10px")
+ el-table-column(label="Date" prop="created_at" sortable width="110")
+ template(v-once #default="scope")
+ el-tooltip(placement="left")
+ template(#content)
+ span {{ scope.row.created_at | formatDate('long') }}
+ span {{ scope.row.created_at | formatDate('short') }}
+ el-table-column(label="Display Name" prop="displayName" sortable)
+ template(v-once #default="scope")
+ span.x-link(v-text="scope.row.displayName" @click="lookupUser(scope.row)")
+ 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="Count" prop="count" width="90" sortable)
+ template(v-once #default="scope")
+ span(v-text="scope.row.count")
+
//- dialog: open source software notice
el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" :visible.sync="ossDialog" title="Open Source Software Notice" width="650px")
div(style="height:350px;overflow:hidden scroll;word-break:break-all")
diff --git a/html/src/repository/database.js b/html/src/repository/database.js
index 3ec74525..568cc201 100644
--- a/html/src/repository/database.js
+++ b/html/src/repository/database.js
@@ -1360,6 +1360,42 @@ class Database {
);
}
+ async getPlayersFromInstance(location) {
+ var players = new Map();
+ await sqliteService.execute(
+ (dbRow) => {
+ var time = 0;
+ var count = 0;
+ var created_at = dbRow[0];
+ if (dbRow[3]) {
+ time = dbRow[3];
+ }
+ var ref = players.get(dbRow[1]);
+ if (typeof ref !== 'undefined') {
+ time += ref.time;
+ count = ref.count;
+ created_at = ref.created_at;
+ }
+ if (dbRow[4] === 'OnPlayerJoined') {
+ count++;
+ }
+ var row = {
+ created_at,
+ displayName: dbRow[1],
+ userId: dbRow[2],
+ time,
+ count
+ };
+ players.set(row.displayName, row);
+ },
+ `SELECT created_at, display_name, user_id, time, type FROM gamelog_join_leave WHERE location = @location`,
+ {
+ '@location': location
+ }
+ );
+ return players;
+ }
+
async getpreviousDisplayNamesByUserId(ref) {
var data = new Map();
await sqliteService.execute(