diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4f8c231c..9f84c3a3 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,10 +1,13 @@
{
- "i18n-ally.localesPaths": ["html/src/localization/strings"],
- "i18n-ally.keystyle": "nested",
- "i18n-ally.sourceLanguage": "en",
- "editor.defaultFormatter": "esbenp.prettier-vscode",
- "editor.formatOnSave": true,
- "[javascript]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
- }
+ "i18n-ally.localesPaths": ["html/src/localization/strings"],
+ "i18n-ally.keystyle": "nested",
+ "i18n-ally.sourceLanguage": "en",
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true,
+ "[javascript]": {
+ "editor.defaultFormatter": "vscode.typescript-language-features"
+ },
+ "[jade]": {
+ "editor.defaultFormatter": "svipas.prettier-plus"
+ }
}
diff --git a/AppApi.cs b/AppApi.cs
index 5b3961b4..9cbb33f8 100644
--- a/AppApi.cs
+++ b/AppApi.cs
@@ -134,12 +134,12 @@ namespace VRCX
var isGameRunning = false;
var isSteamVRRunning = false;
- if (ProcessMonitor.Instance.IsProcessRunning("VRChat", true))
+ if (ProcessMonitor.Instance.IsProcessRunning("VRChat"))
{
isGameRunning = true;
}
- if (ProcessMonitor.Instance.IsProcessRunning("vrserver", true))
+ if (ProcessMonitor.Instance.IsProcessRunning("vrserver"))
{
isSteamVRRunning = true;
}
@@ -670,6 +670,15 @@ namespace VRCX
}
}
+ // what the fuck even is this
+ // refactor when
+ // #AppApiLivesDontMatter
+ public void SetAppLauncherSettings(bool enabled, bool killOnExit)
+ {
+ AutoAppLaunchManager.Instance.Enabled = enabled;
+ AutoAppLaunchManager.Instance.KillChildrenOnExit = killOnExit;
+ }
+
public void AddScreenshotMetadata(string path, string metadataString, string worldId, bool changeFilename = false)
{
var fileName = Path.GetFileNameWithoutExtension(path);
diff --git a/AutoAppLaunchManager.cs b/AutoAppLaunchManager.cs
index 303b5662..5329440b 100644
--- a/AutoAppLaunchManager.cs
+++ b/AutoAppLaunchManager.cs
@@ -12,19 +12,23 @@ namespace VRCX
public class AutoAppLaunchManager
{
public static AutoAppLaunchManager Instance { get; private set; }
- public bool Enabled = true;
+ public static readonly string VRChatProcessName = "VRChat";
+
+ public bool Enabled = false;
+ /// Whether or not to kill child processes when VRChat closes.
+ public bool KillChildrenOnExit = true;
public readonly string AppShortcutDirectory;
private DateTime startTime = DateTime.Now;
- private List startedProcesses = new List();
+ private Dictionary startedProcesses = new Dictionary();
private static readonly byte[] shortcutSignatureBytes = { 0x4C, 0x00, 0x00, 0x00 }; // signature for ShellLinkHeader\
- static AutoAppLaunchManager()
+ static AutoAppLaunchManager()
{
Instance = new AutoAppLaunchManager();
}
- public AutoAppLaunchManager()
+ public AutoAppLaunchManager()
{
AppShortcutDirectory = Path.Combine(Program.AppDataDirectory, "startup");
@@ -39,11 +43,41 @@ namespace VRCX
private void OnProcessExited(MonitoredProcess monitoredProcess)
{
- if (startedProcesses.Count == 0 || !monitoredProcess.HasName("VRChat"))
+ if (startedProcesses.Count == 0 || !monitoredProcess.HasName(VRChatProcessName))
return;
- foreach (var process in startedProcesses)
+ if (KillChildrenOnExit)
+ KillChildProcesses();
+ }
+
+ private void OnProcessStarted(MonitoredProcess monitoredProcess)
+ {
+ if (!Enabled || !monitoredProcess.HasName(VRChatProcessName) || monitoredProcess.Process.StartTime < startTime)
+ return;
+
+ if (KillChildrenOnExit)
+ KillChildProcesses();
+
+ var shortcutFiles = FindShortcutFiles(AppShortcutDirectory);
+
+ foreach (var file in shortcutFiles)
{
+ if (!IsChildProcessRunning(file))
+ {
+ StartChildProcess(file);
+ }
+ }
+ }
+
+ ///
+ /// Kills all running child processes.
+ ///
+ internal void KillChildProcesses()
+ {
+ foreach (var pair in startedProcesses)
+ {
+ var process = pair.Value;
+
if (!process.HasExited)
process.Kill();
}
@@ -51,32 +85,40 @@ namespace VRCX
startedProcesses.Clear();
}
- private void OnProcessStarted(MonitoredProcess monitoredProcess)
+ ///
+ /// Starts a new child process.
+ ///
+ /// The path.
+ internal void StartChildProcess(string path)
{
- if (!monitoredProcess.HasName("VRChat") || monitoredProcess.Process.StartTime < startTime)
- return;
+ var process = Process.Start(path);
+ startedProcesses.Add(path, process);
+ }
- if (startedProcesses.Count > 0)
+ ///
+ /// Updates the child processes list.
+ /// Removes any processes that have exited.
+ ///
+ internal void UpdateChildProcesses()
+ {
+ foreach (var pair in startedProcesses.ToList())
{
- foreach (var process in startedProcesses)
- {
- if (!process.HasExited)
- process.Kill();
- }
-
- startedProcesses.Clear();
+ var process = pair.Value;
+ if (process.HasExited)
+ startedProcesses.Remove(pair.Key);
}
+ }
- var shortcutFiles = FindShortcutFiles(AppShortcutDirectory);
-
- if (shortcutFiles.Length > 0)
- {
- foreach (var file in shortcutFiles)
- {
- var process = Process.Start(file);
- startedProcesses.Add(process);
- }
- }
+ ///
+ /// Checks to see if a given file matches a current running child process.
+ ///
+ /// The path.
+ ///
+ /// true if child process running; otherwise, false.
+ ///
+ internal bool IsChildProcessRunning(string path)
+ {
+ return startedProcesses.ContainsKey(path);
}
internal void Init()
@@ -88,11 +130,7 @@ namespace VRCX
{
Enabled = false;
- foreach (var process in startedProcesses)
- {
- if (!process.HasExited)
- process.Kill();
- }
+ KillChildProcesses();
}
///
diff --git a/LogWatcher.cs b/LogWatcher.cs
index 2007f5e0..67b49cb9 100644
--- a/LogWatcher.cs
+++ b/LogWatcher.cs
@@ -220,6 +220,7 @@ namespace VRCX
ParseLogAvatarPedestalChange(fileInfo, logContext, line, offset) ||
ParseLogVideoError(fileInfo, logContext, line, offset) ||
ParseLogVideoChange(fileInfo, logContext, line, offset) ||
+ ParseLogAVProVideoChange(fileInfo, logContext, line, offset) ||
ParseLogUsharpVideoPlay(fileInfo, logContext, line, offset) ||
ParseLogUsharpVideoSync(fileInfo, logContext, line, offset) ||
ParseLogWorldVRCX(fileInfo, logContext, line, offset) ||
@@ -617,6 +618,31 @@ namespace VRCX
return true;
}
+ private bool ParseLogAVProVideoChange(FileInfo fileInfo, LogContext logContext, string line, int offset)
+ {
+ // 2023.05.12 15:53:48 Log - [Video Playback] Resolving URL 'rtspt://topaz.chat/live/kiriri520'
+
+ if (string.Compare(line, offset, "[Video Playback] Resolving URL '", 0, 32, StringComparison.Ordinal) != 0)
+ return false;
+
+ var pos = line.LastIndexOf("'");
+ if (pos < 0)
+ return false;
+
+ var data = line.Substring(offset + 32);
+ data = data.Remove(data.Length - 1);
+
+ AppendLog(new[]
+ {
+ fileInfo.Name,
+ ConvertLogTimeToISO8601(line),
+ "video-play",
+ data
+ });
+
+ return true;
+ }
+
private bool ParseLogSDK2VideoPlay(FileInfo fileInfo, LogContext logContext, string line, int offset)
{
// 2021.04.23 13:12:25 Log - User Natsumi-sama added URL https://www.youtube.com/watch?v=dQw4w9WgXcQ
diff --git a/ProcessMonitor.cs b/ProcessMonitor.cs
index a2dc847e..1df87495 100644
--- a/ProcessMonitor.cs
+++ b/ProcessMonitor.cs
@@ -77,8 +77,6 @@ namespace VRCX
monitoredProcess.ProcessExited();
ProcessExited?.Invoke(monitoredProcess);
}
-
- monitoredProcess.Process.Refresh();
}
else
{
diff --git a/Program.cs b/Program.cs
index 0e808143..c784256f 100644
--- a/Program.cs
+++ b/Program.cs
@@ -109,8 +109,8 @@ namespace VRCX
private static void LoadFromConfig()
{
- if (!GPUFix) GPUFix = VRCXStorage.Instance.Get("GPU_Fix") == "true";
- if (!LaunchDebug) LaunchDebug = VRCXStorage.Instance.Get("Launch_Debug") == "true";
+ if (!GPUFix)
+ GPUFix = VRCXStorage.Instance.Get("VRCX_GPUFix") == "true";
}
}
}
\ No newline at end of file
diff --git a/html/src/api.js b/html/src/api.js
new file mode 100644
index 00000000..81475758
--- /dev/null
+++ b/html/src/api.js
@@ -0,0 +1,56 @@
+const API = {}
+
+API.eventHandlers = new Map();
+
+ API.$emit = function (name, ...args) {
+ if ($app.debug) {
+ console.log(name, ...args);
+ }
+ var handlers = this.eventHandlers.get(name);
+ if (typeof handlers === 'undefined') {
+ return;
+ }
+ try {
+ for (var handler of handlers) {
+ handler.apply(this, args);
+ }
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ API.$on = function (name, handler) {
+ var handlers = this.eventHandlers.get(name);
+ if (typeof handlers === 'undefined') {
+ handlers = [];
+ this.eventHandlers.set(name, handlers);
+ }
+ handlers.push(handler);
+ };
+
+ API.$off = function (name, handler) {
+ var handlers = this.eventHandlers.get(name);
+ if (typeof handlers === 'undefined') {
+ return;
+ }
+ var {length} = handlers;
+ for (var i = 0; i < length; ++i) {
+ if (handlers[i] === handler) {
+ if (length > 1) {
+ handlers.splice(i, 1);
+ } else {
+ this.eventHandlers.delete(name);
+ }
+ break;
+ }
+ }
+ };
+
+ API.pendingGetRequests = new Map();
+ API.failedGetRequests = new Map();
+ API.endpointDomainVrchat = 'https://api.vrchat.cloud/api/1';
+ API.websocketDomainVrchat = 'wss://pipeline.vrchat.cloud';
+ API.endpointDomain = 'https://api.vrchat.cloud/api/1';
+ API.websocketDomain = 'wss://pipeline.vrchat.cloud';
+
+module.exports = API
\ No newline at end of file
diff --git a/html/src/app.js b/html/src/app.js
index 674ce7aa..bf2e96c6 100644
--- a/html/src/app.js
+++ b/html/src/app.js
@@ -4911,6 +4911,10 @@ speechSynthesis.getVoices();
}
});
AppApi.CheckGameRunning();
+ AppApi.SetAppLauncherSettings(
+ this.enableAppLauncher,
+ this.enableAppLauncherAutoClose
+ );
API.$on('SHOW_WORLD_DIALOG', (tag) => this.showWorldDialog(tag));
API.$on('SHOW_WORLD_DIALOG_SHORTNAME', (tag) =>
this.verifyShortName('', tag)
@@ -5089,9 +5093,6 @@ speechSynthesis.getVoices();
isGameRunning,
isSteamVRRunning
) {
- console.log(
- `updateIsGameRunning isGameRunning:${isGameRunning} isSteamVRRunning:${isSteamVRRunning}`
- );
if (isGameRunning !== this.isGameRunning) {
this.isGameRunning = isGameRunning;
if (isGameRunning) {
@@ -5113,11 +5114,11 @@ speechSynthesis.getVoices();
60000
);
this.nextDiscordUpdate = 0;
- console.log('isGameRunning changed', isGameRunning);
+ console.log('isGameRunning:', isGameRunning);
}
if (isSteamVRRunning !== this.isSteamVRRunning) {
this.isSteamVRRunning = isSteamVRRunning;
- console.log('isSteamVRRunning changed', isSteamVRRunning);
+ console.log('isSteamVRRunning:', isSteamVRRunning);
}
this.updateOpenVR();
};
@@ -9032,6 +9033,7 @@ speechSynthesis.getVoices();
this.photonLobbyUserData = new Map();
this.photonLobbyWatcherLoopStop();
this.photonLobbyAvatars = new Map();
+ this.photonLobbyLastModeration = new Map();
this.photonLobbyJointime = new Map();
this.photonEvent7List = new Map();
this.photonLastEvent7List = '';
@@ -9674,7 +9676,9 @@ speechSynthesis.getVoices();
this.updateOpenVR();
break;
case 'udon-exception':
- console.log('UdonException', gameLog.data);
+ if (this.udonExceptionLogging) {
+ console.log('UdonException', gameLog.data);
+ }
// var entry = {
// created_at: gameLog.dt,
// type: 'Event',
@@ -9707,6 +9711,7 @@ speechSynthesis.getVoices();
$app.data.photonLobbyUserData = new Map();
$app.data.photonLobbyCurrent = new Map();
$app.data.photonLobbyAvatars = new Map();
+ $app.data.photonLobbyLastModeration = new Map();
$app.data.photonLobbyWatcherLoop = false;
$app.data.photonLobbyTimeout = [];
$app.data.photonLobbyJointime = new Map();
@@ -10305,10 +10310,12 @@ speechSynthesis.getVoices();
break;
case 254:
// Leave
- this.photonUserLeave(data.Parameters[254], gameLogDate);
- this.photonLobbyCurrent.delete(data.Parameters[254]);
- this.photonLobbyJointime.delete(data.Parameters[254]);
- this.photonEvent7List.delete(data.Parameters[254]);
+ var photonId = data.Parameters[254];
+ this.photonUserLeave(photonId, gameLogDate);
+ this.photonLobbyCurrent.delete(photonId);
+ this.photonLobbyLastModeration.delete(photonId);
+ this.photonLobbyJointime.delete(photonId);
+ this.photonEvent7List.delete(photonId);
this.parsePhotonLobbyIds(data.Parameters[252]);
if (typeof data.Parameters[203] !== 'undefined') {
this.setPhotonLobbyMaster(
@@ -10891,6 +10898,7 @@ speechSynthesis.getVoices();
gameLogDate
) {
database.getModeration(ref.id).then((row) => {
+ var lastType = this.photonLobbyLastModeration.get(photonId);
var type = '';
var text = '';
if (block) {
@@ -10910,7 +10918,7 @@ speechSynthesis.getVoices();
}
if (block === row.block && mute === row.mute) {
// no change
- if (type) {
+ if (type && type !== lastType) {
this.addEntryPhotonEvent({
photonId,
text: `Moderation ${text}`,
@@ -10919,9 +10927,11 @@ speechSynthesis.getVoices();
created_at: gameLogDate
});
}
+ this.photonLobbyLastModeration.set(photonId, type);
return;
}
}
+ this.photonLobbyLastModeration.set(photonId, type);
this.moderationAgainstTable.forEach((item) => {
if (item.userId === ref.id && item.type === type) {
removeFromArray(this.moderationAgainstTable, item);
@@ -13419,6 +13429,10 @@ speechSynthesis.getVoices();
'VRCX_randomUserColours',
this.randomUserColours
);
+ configRepository.setBool(
+ 'VRCX_udonExceptionLogging',
+ this.udonExceptionLogging
+ );
this.updateSharedFeed(true);
this.updateVRConfigVars();
this.updateVRLastLocation();
@@ -13493,19 +13507,23 @@ speechSynthesis.getVoices();
);
$app.data.isStartAsMinimizedState = false;
$app.data.isCloseToTray = false;
+ $app.data.gpuFix = false;
VRCXStorage.Get('VRCX_StartAsMinimizedState').then((result) => {
$app.isStartAsMinimizedState = result === 'true';
});
VRCXStorage.Get('VRCX_CloseToTray').then((result) => {
$app.isCloseToTray = result === 'true';
});
+ VRCXStorage.Get('VRCX_GPUFix').then((result) => {
+ $app.gpuFix = result === 'true';
+ });
if (configRepository.getBool('VRCX_CloseToTray')) {
// move back to JSON
$app.data.isCloseToTray = configRepository.getBool('VRCX_CloseToTray');
VRCXStorage.Set('VRCX_CloseToTray', $app.data.isCloseToTray.toString());
configRepository.remove('VRCX_CloseToTray');
}
- var saveVRCXWindowOption = function () {
+ $app.methods.saveVRCXWindowOption = function () {
configRepository.setBool(
'VRCX_StartAtWindowsStartup',
this.isStartAtWindowsStartup
@@ -13515,11 +13533,9 @@ speechSynthesis.getVoices();
this.isStartAsMinimizedState.toString()
);
VRCXStorage.Set('VRCX_CloseToTray', this.isCloseToTray.toString());
+ VRCXStorage.Set('VRCX_GPUFix', this.gpuFix.toString());
AppApi.SetStartup(this.isStartAtWindowsStartup);
};
- $app.watch.isStartAtWindowsStartup = saveVRCXWindowOption;
- $app.watch.isStartAsMinimizedState = saveVRCXWindowOption;
- $app.watch.isCloseToTray = saveVRCXWindowOption;
$app.data.photonEventOverlay = configRepository.getBool(
'VRCX_PhotonEventOverlay'
);
@@ -13539,6 +13555,9 @@ speechSynthesis.getVoices();
$app.data.gameLogDisabled = configRepository.getBool(
'VRCX_gameLogDisabled'
);
+ $app.data.udonExceptionLogging = configRepository.getBool(
+ 'VRCX_udonExceptionLogging'
+ );
$app.data.instanceUsersSortAlphabetical = configRepository.getBool(
'VRCX_instanceUsersSortAlphabetical'
);
@@ -13877,12 +13896,24 @@ speechSynthesis.getVoices();
);
$app.data.screenshotHelper = configRepository.getBool(
- 'VRCX_screenshotHelper'
+ 'VRCX_screenshotHelper',
+ true
);
+
$app.data.screenshotHelperModifyFilename = configRepository.getBool(
'VRCX_screenshotHelperModifyFilename'
);
+ $app.data.enableAppLauncher = configRepository.getBool(
+ 'VRCX_enableAppLauncher',
+ true
+ );
+
+ $app.data.enableAppLauncherAutoClose = configRepository.getBool(
+ 'VRCX_enableAppLauncherAutoClose',
+ true
+ );
+
$app.methods.updateVRConfigVars = function () {
var notificationTheme = 'relax';
if (this.isDarkMode) {
@@ -20662,6 +20693,22 @@ speechSynthesis.getVoices();
AppApi.OpenShortcutFolder();
};
+ $app.methods.updateAppLauncherSettings = function () {
+ configRepository.setBool(
+ 'VRCX_enableAppLauncher',
+ this.enableAppLauncher
+ );
+ configRepository.setBool(
+ 'VRCX_enableAppLauncherAutoClose',
+ this.enableAppLauncherAutoClose
+ );
+
+ AppApi.SetAppLauncherSettings(
+ this.enableAppLauncher,
+ this.enableAppLauncherAutoClose
+ );
+ };
+
// Screenshot Helper
$app.methods.saveScreenshotHelper = function () {
diff --git a/html/src/index.pug b/html/src/index.pug
index 782d7764..c0bdc8d4 100644
--- a/html/src/index.pug
+++ b/html/src/index.pug
@@ -13,44 +13,7 @@ html
body
.x-app#x-app(style="display:none" @dragenter.prevent @dragover.prevent @drop.prevent)
//- login
- .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")
- div(style="margin:15px" v-if="Object.keys(loginForm.savedCredentials).length !== 0")
- h2(style="font-weight:bold;text-align:center;margin:0") {{ $t("view.login.savedAccounts") }}
- .x-friend-list(style="margin-top:10px")
- .x-friend-item(v-for="user in loginForm.savedCredentials" :key="user.user.id")
- .x-friend-item(@click="relogin(user)" style="width:202px;padding:0")
- .avatar
- img(v-lazy="userImage(user.user)")
- .detail
- span.name(v-text="user.user.displayName")
- span.extra(v-text="user.user.username")
- span.extra(v-text="user.loginParmas.endpoint")
- el-button(type="default" @click="deleteSavedLogin(user.user.id)" size="mini" icon="el-icon-delete" circle)
- div(style="margin:15px")
- h2(style="font-weight:bold;text-align:center;margin:0") {{ $t("view.login.login") }}
- el-form(ref="loginForm" :model="loginForm" :rules="loginForm.rules" @submit.native.prevent="login()")
- el-form-item(:label="$t('view.login.field.username')" prop="username" required)
- el-input(v-model="loginForm.username" name="username" :placeholder="$t('view.login.field.username')" clearable)
- el-form-item(:label="$t('view.login.field.password')" prop="password" required style="margin-top:10px")
- el-input(type="password" v-model="loginForm.password" name="password" :placeholder="$t('view.login.field.password')" clearable show-password)
- el-checkbox(v-model="loginForm.saveCredentials" style="margin-top:15px") {{ $t("view.login.field.saveCredentials") }}
- el-checkbox(v-model="enableCustomEndpoint" @change="toggleCustomEndpoint" style="margin-top:10px") {{ $t("view.login.field.devEndpoint") }}
- el-form-item(v-if="enableCustomEndpoint" :label="$t('view.login.field.endpoint')" prop="endpoint" style="margin-top:10px")
- el-input(v-model="loginForm.endpoint" name="endpoint" :placeholder="API.endpointDomainVrchat" clearable)
- el-form-item(v-if="enableCustomEndpoint" :label="$t('view.login.field.websocket')" prop="endpoint" style="margin-top:10px")
- el-input(v-model="loginForm.websocket" name="websocket" :placeholder="API.websocketDomainVrchat" clearable)
- el-form-item(style="margin-top:15px")
- el-button(native-type="submit" type="primary" :loading="loginForm.loading" style="width:100%") {{ $t("view.login.login") }}
- el-button(type="primary" @click="openExternalLink('https://vrchat.com/register')" :loading="loginForm.loading" style="width:100%") {{ $t("view.login.register") }}
- div(style="text-align:center;font-size:12px")
- p #[a.x-link(@click="openExternalLink('https://vrchat.com/home/password')") {{ $t("view.login.forgotPassword") }}]
- p © 2019-2022 #[a.x-link(@click="openExternalLink('https://github.com/pypy-vrc')") pypy] (mina#5656) & #[a.x-link(@click="openExternalLink('https://github.com/Natsumi-sama')") Natsumi]
- p {{ $t("view.settings.general.legal_notice.info") }}
- p {{ $t("view.settings.general.legal_notice.disclaimer1") }}
- p {{ $t("view.settings.general.legal_notice.disclaimer2") }}
+ include ./mixins/loginPage.pug
//- menu
.x-menu-container
@@ -78,1496 +41,53 @@ html
+menuitem('profile', "{{ $t('nav_tooltip.profile') }}", 'el-icon-user')
+menuitem('settings', "{{ $t('nav_tooltip.settings') }}", 'el-icon-s-tools')
- //- playerList
- .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'playerList'")
- div(style="display:flex;flex-direction:column;height:100%")
- div(v-if="currentInstanceWorld.ref.id" style="display:flex")
- el-popover(placement="right" width="500px" trigger="click" style="height:120px")
- img.x-link(slot="reference" v-lazy="currentInstanceWorld.ref.thumbnailImageUrl" style="flex:none;width:160px;height:120px;border-radius:4px")
- img.x-link(v-lazy="currentInstanceWorld.ref.imageUrl" style="width:500px;height:375px" @click="downloadAndSaveImage(currentInstanceWorld.ref.imageUrl)")
- div(style="margin-left:10px;display:flex;flex-direction:column;min-width:320px;width:100%")
- div
- span.x-link(@click="showWorldDialog(currentInstanceWorld.ref.id)" style="font-weight:bold;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1")
- | #[i.el-icon-s-home(v-show="API.currentUser.$homeLocation && API.currentUser.$homeLocation.worldId === currentInstanceWorld.ref.id" style="margin-right:5px")] {{ currentInstanceWorld.ref.name }}
- div
- span.x-link(v-text="currentInstanceWorld.ref.authorName" @click="showUserDialog(currentInstanceWorld.ref.authorId)" style="color:#909399;font-family:monospace")
- div(style="margin-top:5px")
- el-tag(v-if="currentInstanceWorld.ref.$isLabs" type="primary" effect="plain" size="mini" style="margin-right:5px") {{ $t('dialog.world.tags.labs') }}
- el-tag(v-else-if="currentInstanceWorld.ref.releaseStatus === 'public'" type="success" effect="plain" size="mini" style="margin-right:5px") {{ $t('dialog.world.tags.public') }}
- el-tag(v-else-if="currentInstanceWorld.ref.releaseStatus === 'private'" type="danger" effect="plain" size="mini" style="margin-right:5px") {{ $t('dialog.world.tags.private') }}
- el-tag.x-tag-platform-pc(v-if="currentInstanceWorld.isPC" type="info" effect="plain" size="mini" style="margin-right:5px") PC
- el-tag.x-tag-platform-quest(v-if="currentInstanceWorld.isQuest" type="info" effect="plain" size="mini" style="margin-right:5px") Quest
- el-tag(type="info" effect="plain" size="mini" v-text="currentInstanceWorld.fileSize" style="margin-right:5px")
- el-tag(v-if="currentInstanceWorld.inCache" type="info" effect="plain" size="mini" style="margin-right:5px")
- span(v-text="currentInstanceWorld.cacheSize")
- | {{ $t('dialog.world.tags.cache') }}
- br
- 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 }})]
- | #[timer(v-if="lastLocation.date" :epoch="lastLocation.date")]
- div(style="margin-top:5px")
- span(v-show="currentInstanceWorld.ref.name !== currentInstanceWorld.ref.description" v-text="currentInstanceWorld.ref.description" style="font-size:12px;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2")
- div(style="display:flex;flex-direction:column;margin-left:20px")
- .x-friend-item(style="cursor:default")
- .detail
- span.name {{ $t('dialog.world.info.capacity') }}
- span.extra {{ currentInstanceWorld.ref.capacity | commaNumber }} ({{ currentInstanceWorld.ref.capacity * 2 | commaNumber }})
- .x-friend-item(style="cursor:default")
- .detail
- span.name {{ $t('dialog.world.info.last_updated') }}
- span.extra {{ currentInstanceWorld.fileCreatedAt | formatDate('long') }}
- .x-friend-item(style="cursor:default")
- .detail
- span.name {{ $t('dialog.world.info.created_at') }}
- span.extra {{ currentInstanceWorld.ref.created_at | formatDate('long') }}
- div.photon-event-table(v-if="photonLoggingEnabled")
- div(style="position:absolute;width:600px;margin-left:195px;z-index:1")
- el-select(v-model="photonEventTableTypeFilter" @change="photonEventTableFilterChange" multiple clearable collapse-tags style="flex:1;width:220px" :placeholder="$t('view.player_list.photon.filter_placeholder')")
- el-option(v-once v-for="type in photonEventTableTypeFilterList" :key="type" :label="type" :value="type")
- el-input(v-model="photonEventTableFilter" @input="photonEventTableFilterChange" :placeholder="$t('view.player_list.photon.search_placeholder')" clearable style="width:150px;margin-left:10px")
- el-button(@click="showChatboxBlacklistDialog" style="margin-left:10px") {{ $t('view.player_list.photon.chatbox_blacklist') }}
- el-tooltip(placement="bottom" :content="$t('view.player_list.photon.status_tooltip')" :disabled="hideTooltips")
- div(style="display:inline-block;margin-left:15px;font-size:14px;vertical-align:text-top;margin-top:1px")
- span(v-if="ipcEnabled && !photonEventIcon") 🟢
- span(v-else-if="ipcEnabled") ⚪
- span(v-else) 🔴
- el-tabs(type="card")
- el-tab-pane(:label="$t('view.player_list.photon.current')")
- data-tables(v-bind="photonEventTable" style="margin-bottom:10px")
- el-table-column(:label="$t('table.playerList.date')" prop="created_at" width="120")
- template(v-once #default="scope")
- el-tooltip(placement="right")
- template(#content)
- span {{ scope.row.created_at | formatDate('long') }}
- span {{ scope.row.created_at | formatDate('short') }}
- el-table-column(:label="$t('table.playerList.user')" prop="photonId" width="160")
- template(v-once #default="scope")
- span.x-link(v-text="scope.row.displayName" @click="showUserFromPhotonId(scope.row.photonId)" style="padding-right:10px")
- el-table-column(:label="$t('table.playerList.type')" prop="type" width="140")
- el-table-column(:label="$t('table.playerList.detail')" prop="text")
- template(v-once #default="scope")
- template(v-if="scope.row.type === 'ChangeAvatar'")
- span.x-link(v-text="scope.row.avatar.name" @click="showAvatarDialog(scope.row.avatar.id)")
- |
- span(v-if="!scope.row.inCache" style="color:#aaa") #[i.el-icon-download]
- span.avatar-info-public(v-if="scope.row.avatar.releaseStatus === 'public'") {{ $t('dialog.avatar.labels.public') }}
- span.avatar-info-own(v-else-if="scope.row.avatar.releaseStatus === 'private'") {{ $t('dialog.avatar.labels.private') }}
- template(v-if="scope.row.avatar.description && scope.row.avatar.name !== scope.row.avatar.description")
- | - {{ scope.row.avatar.description }}
- template(v-else-if="scope.row.type === 'ChangeStatus'")
- template(v-if="scope.row.status !== scope.row.previousStatus")
- el-tooltip(placement="top")
- template(#content)
- span(v-if="scope.row.previousStatus === 'active'") {{ $t('dialog.user.status.active') }}
- span(v-else-if="scope.row.previousStatus === 'join me'") {{ $t('dialog.user.status.join_me') }}
- span(v-else-if="scope.row.previousStatus === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
- span(v-else-if="scope.row.previousStatus === 'busy'") {{ $t('dialog.user.status.busy') }}
- span(v-else) {{ $t('dialog.user.status.offline') }}
- i.x-user-status(:class="statusClass(scope.row.previousStatus)")
- span
- i.el-icon-right
- el-tooltip(placement="top")
- template(#content)
- span(v-if="scope.row.status === 'active'") Active
- span(v-else-if="scope.row.status === 'join me'") Join Me
- span(v-else-if="scope.row.status === 'ask me'") Ask Me
- span(v-else-if="scope.row.status === 'busy'") Do Not Disturb
- span(v-else) Offline
- i.x-user-status(:class="statusClass(scope.row.status)" style="margin-right:5px")
- span(v-if="scope.row.statusDescription !== scope.row.previousStatusDescription" v-text="scope.row.statusDescription")
- template(v-else-if="scope.row.type === 'ChangeGroup'")
- span.x-link(v-if="scope.row.previousGroupName" v-text="scope.row.previousGroupName" @click="showGroupDialog(scope.row.previousGroupId)" style="margin-right:5px")
- span.x-link(v-else v-text="scope.row.previousGroupId" @click="showGroupDialog(scope.row.previousGroupId)" style="margin-right:5px")
- span
- i.el-icon-right
- span.x-link(v-if="scope.row.groupName" v-text="scope.row.groupName" @click="showGroupDialog(scope.row.groupId)" style="margin-left:5px")
- span.x-link(v-else v-text="scope.row.groupId" @click="showGroupDialog(scope.row.groupId)" style="margin-left:5px")
- span.x-link(v-else-if="scope.row.type === 'PortalSpawn'" @click="showWorldDialog(scope.row.location, scope.row.shortName)")
- location(:location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName" :link="false")
- span(v-else-if="scope.row.type === 'ChatBoxMessage'" v-text="scope.row.text")
- span(v-else-if="scope.row.type === 'OnPlayerJoined'")
- span(v-if="scope.row.platform === 'Desktop'" style="color:#409eff") Desktop
- span(v-else-if="scope.row.platform === 'VR'" style="color:#409eff") VR
- span(v-else-if="scope.row.platform === 'Quest'" style="color:#67c23a") Quest
- span.x-link(v-text="scope.row.avatar.name" @click="showAvatarDialog(scope.row.avatar.id)")
- |
- span(v-if="!scope.row.inCache" style="color:#aaa") #[i.el-icon-download]
- span.avatar-info-public(v-if="scope.row.avatar.releaseStatus === 'public'") {{ $t('dialog.avatar.labels.public') }}
- span.avatar-info-own(v-else-if="scope.row.avatar.releaseStatus === 'private'") {{ $t('dialog.avatar.labels.private') }}
- span(v-else-if="scope.row.color === 'yellow'" v-text="scope.row.text" style="color:yellow")
- span(v-else v-text="scope.row.text")
- el-tab-pane(:label="$t('view.player_list.photon.previous')")
- data-tables(v-bind="photonEventTablePrevious" style="margin-bottom:10px")
- el-table-column(:label="$t('table.playerList.date')" prop="created_at" width="120")
- template(v-once #default="scope")
- el-tooltip(placement="right")
- template(#content)
- span {{ scope.row.created_at | formatDate('long') }}
- span {{ scope.row.created_at | formatDate('short') }}
- el-table-column(:label="$t('table.playerList.user')" prop="photonId" width="160")
- template(v-once #default="scope")
- span.x-link(v-text="scope.row.displayName" @click="lookupUser(scope.row)" style="padding-right:10px")
- el-table-column(:label="$t('table.playerList.type')" prop="type" width="140")
- el-table-column(:label="$t('table.playerList.detail')" prop="text")
- template(v-once #default="scope")
- template(v-if="scope.row.type === 'ChangeAvatar'")
- span.x-link(v-text="scope.row.avatar.name" @click="showAvatarDialog(scope.row.avatar.id)")
- |
- span(v-if="!scope.row.inCache" style="color:#aaa") #[i.el-icon-download]
- span.avatar-info-public(v-if="scope.row.avatar.releaseStatus === 'public'") (Public)
- span.avatar-info-own(v-else-if="scope.row.avatar.releaseStatus === 'private'") (Private)
- template(v-if="scope.row.avatar.description && scope.row.avatar.name !== scope.row.avatar.description")
- | - {{ scope.row.avatar.description }}
- template(v-else-if="scope.row.type === 'ChangeStatus'")
- template(v-if="scope.row.status !== scope.row.previousStatus")
- el-tooltip(placement="top")
- template(#content)
- span(v-if="scope.row.previousStatus === 'active'") {{ $t('dialog.user.status.active') }}
- span(v-else-if="scope.row.previousStatus === 'join me'") {{ $t('dialog.user.status.join_me') }}
- span(v-else-if="scope.row.previousStatus === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
- span(v-else-if="scope.row.previousStatus === 'busy'") {{ $t('dialog.user.status.busy') }}
- span(v-else) {{ $t('dialog.user.status.offline') }}
- i.x-user-status(:class="statusClass(scope.row.previousStatus)")
- span
- i.el-icon-right
- el-tooltip(placement="top")
- template(#content)
- span(v-if="scope.row.status === 'active'") {{ $t('dialog.user.status.active') }}
- span(v-else-if="scope.row.status === 'join me'") {{ $t('dialog.user.status.join_me') }}
- span(v-else-if="scope.row.status === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
- span(v-else-if="scope.row.status === 'busy'") {{ $t('dialog.user.status.busy') }}
- span(v-else) {{ $t('dialog.user.status.offline') }}
- i.x-user-status(:class="statusClass(scope.row.status)")
- span(v-if="scope.row.statusDescription !== scope.row.previousStatusDescription" v-text="scope.row.statusDescription" style="margin-left:5px")
- template(v-else-if="scope.row.type === 'ChangeGroup'")
- span.x-link(v-if="scope.row.previousGroupName" v-text="scope.row.previousGroupName" @click="showGroupDialog(scope.row.previousGroupId)" style="margin-right:5px")
- span.x-link(v-else v-text="scope.row.previousGroupId" @click="showGroupDialog(scope.row.previousGroupId)" style="margin-right:5px")
- span
- i.el-icon-right
- span.x-link(v-if="scope.row.groupName" v-text="scope.row.groupName" @click="showGroupDialog(scope.row.groupId)" style="margin-left:5px")
- span.x-link(v-else v-text="scope.row.groupId" @click="showGroupDialog(scope.row.groupId)" style="margin-left:5px")
- span.x-link(v-else-if="scope.row.type === 'PortalSpawn'" @click="showWorldDialog(scope.row.location, scope.row.shortName)")
- location(:location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName" :link="false")
- span(v-else-if="scope.row.type === 'ChatBoxMessage'" v-text="scope.row.text")
- span(v-else-if="scope.row.type === 'OnPlayerJoined'")
- span(v-if="scope.row.platform === 'Desktop'" style="color:#409eff") Desktop
- span(v-else-if="scope.row.platform === 'VR'" style="color:#409eff") VR
- span(v-else-if="scope.row.platform === 'Quest'" style="color:#67c23a") Quest
- span.x-link(v-text="scope.row.avatar.name" @click="showAvatarDialog(scope.row.avatar.id)")
- |
- span(v-if="!scope.row.inCache" style="color:#aaa") #[i.el-icon-download]
- span.avatar-info-public(v-if="scope.row.avatar.releaseStatus === 'public'") (Public)
- span.avatar-info-own(v-else-if="scope.row.avatar.releaseStatus === 'private'") (Private)
- span(v-else-if="scope.row.color === 'yellow'" v-text="scope.row.text" style="color:yellow")
- span(v-else v-text="scope.row.text")
- div.current-instance-table
- data-tables(v-bind="currentInstanceUserList" @row-click="selectCurrentInstanceRow" style="margin-top:10px;cursor:pointer")
- el-table-column(:label="$t('table.playerList.avatar')" width="70" prop="photo")
- template(v-once #default="scope")
- template(v-if="userImage(scope.row.ref)")
- el-popover(placement="right" height="500px" trigger="hover")
- img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row.ref)")
- img.friends-list-avatar(v-lazy="userImageFull(scope.row.ref)" style="height:500px;cursor:pointer" @click="downloadAndSaveImage(userImageFull(scope.row.ref))")
- el-table-column(:label="$t('table.playerList.timer')" width="90" prop="timer" sortable)
- template(v-once #default="scope")
- timer(:epoch="scope.row.timer")
- el-table-column(v-if="photonLoggingEnabled" :label="$t('table.playerList.photonId')" width="110" prop="photonId" sortable)
- template(v-once #default="scope")
- template(v-if="chatboxUserBlacklist.has(scope.row.ref.id)")
- el-tooltip(placement="left" content="Unblock chatbox messages")
- el-button(type="text" icon="el-icon-turn-off-microphone" size="mini" style="color:red;margin-right:5px" @click.stop="deleteChatboxUserBlacklist(scope.row.ref.id)")
- template(v-else)
- el-tooltip(placement="left" content="Block chatbox messages")
- el-button(type="text" icon="el-icon-microphone" size="mini" style="margin-right:5px" @click.stop="addChatboxUserBlacklist(scope.row.ref)")
- span(v-text="scope.row.photonId")
- el-table-column(:label="$t('table.playerList.icon')" prop="isMaster" width="100")
- template(v-once #default="scope")
- el-tooltip(v-if="scope.row.isMaster" placement="left" content="Instance Master")
- span 👑
- el-tooltip(v-if="scope.row.isFriend" placement="left" content="Friend")
- span 💚
- el-tooltip(v-if="scope.row.timeoutTime" placement="left" content="Timeout")
- span(style="color:red") 🔴{{ scope.row.timeoutTime }}s
- el-table-column(:label="$t('table.playerList.platform')" prop="inVRMode" width="80")
- template(v-once #default="scope")
- template(v-if="scope.row.ref.last_platform")
- span(v-if="scope.row.ref.last_platform === 'standalonewindows'" style="color:#409eff") PC
- span(v-else-if="scope.row.ref.last_platform === 'android'" style="color:#67c23a") Q
- span(v-else) {{ scope.row.ref.last_platform }}
- template(v-if="scope.row.inVRMode !== null")
- span(v-if="scope.row.inVRMode") VR
- span(v-else) D
- el-table-column(:label="$t('table.playerList.displayName')" min-width="140" prop="ref.displayName")
- template(v-once #default="scope")
- span(v-if="randomUserColours" v-text="scope.row.ref.displayName" :style="{'color':scope.row.ref.$userColour}")
- span(v-else v-text="scope.row.ref.displayName")
- el-table-column(:label="$t('table.playerList.status')" min-width="180" prop="ref.status")
- template(v-once #default="scope")
- template(v-if="scope.row.ref.status")
- i.x-user-status(:class="statusClass(scope.row.ref.status)")
- span
- span(v-text="scope.row.ref.statusDescription")
- //- el-table-column(label="Group" min-width="180" prop="groupOnNameplate" sortable)
- //- template(v-once #default="scope")
- //- span(v-text="scope.row.groupOnNameplate")
- el-table-column(:label="$t('table.playerList.rank')" width="110" prop="$trustSortNum" sortable="custom")
- template(v-once #default="scope")
- span.name(v-text="scope.row.ref.$trustLevel" :class="scope.row.ref.$trustClass")
- el-table-column(:label="$t('table.playerList.language')" width="100" prop="ref.$languages")
- template(v-once #default="scope")
- el-tooltip(v-for="item in scope.row.ref.$languages" :key="item.key" placement="top")
- template(#content)
- span {{ item.value }} ({{ item.key }})
- span.flags(:class="languageClass(item.key)" style="display:inline-block;margin-left:5px")
- el-table-column(:label="$t('table.playerList.bioLink')" width="100" prop="ref.bioLinks")
- template(v-once #default="scope")
- el-tooltip(v-if="link" v-for="(link, index) in scope.row.ref.bioLinks" :key="index")
- template(#content)
- span(v-text="link")
- img(:src="getFaviconUrl(link)" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;cursor:pointer" @click.stop="openExternalLink(link)")
+ //- ### Tabs ##
//- feed
- .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'feed'")
- data-tables(v-bind="feedTable" v-loading="feedTable.loading")
- template(#tool)
- div(style="margin:0 0 10px;display:flex;align-items:center")
- div(style="flex:none;margin-right:10px")
- el-tooltip(placement="bottom" :content="$t('view.feed.favorites_only_tooltip')" :disabled="hideTooltips")
- el-switch(v-model="feedTable.vip" @change="feedTableLookup" active-color="#13ce66")
- el-select(v-model="feedTable.filter" @change="feedTableLookup" multiple clearable collapse-tags style="flex:1" :placeholder="$t('view.feed.filter_placeholder')")
- el-option(v-once v-for="type in ['GPS', 'Online', 'Offline', 'Status', 'Avatar', 'Bio']" :key="type" :label="type" :value="type")
- el-input(v-model="feedTable.search" :placeholder="$t('view.feed.search_placeholder')" @keyup.native.13="feedTableLookup" @change="feedTableLookup" clearable style="flex:none;width:150px;margin:0 10px")
- //- el-tooltip(placement="bottom" content="Clear feed" :disabled="hideTooltips")
- //- el-button(type="default" @click="clearFeed()" icon="el-icon-delete" circle style="flex:none")
- el-table-column(type="expand" width="20")
- template(v-once #default="scope")
- div(style="position:relative;font-size:14px")
- template(v-if="scope.row.type === 'GPS'")
- location(v-if="scope.row.previousLocation" :location="scope.row.previousLocation")
- el-tag(type="info" effect="plain" size="mini" style="margin-left:5px") {{ scope.row.time | timeToText }}
- br
- span
- i.el-icon-right
- location(v-if="scope.row.location" :location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
- template(v-else-if="scope.row.type === 'Offline'")
- template(v-if="scope.row.location")
- location(:location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
- el-tag(type="info" effect="plain" size="mini" style="margin-left:5px") {{ scope.row.time | timeToText }}
- template(v-else-if="scope.row.type === 'Online'")
- location(v-if="scope.row.location" :location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
- template(v-else-if="scope.row.type === 'Avatar'")
- el-popover(placement="right" width="500px" trigger="click")
- img.x-link(slot="reference" v-lazy="scope.row.previousCurrentAvatarThumbnailImageUrl" style="flex:none;width:160px;height:120px;border-radius:4px")
- img.x-link(v-lazy="scope.row.previousCurrentAvatarImageUrl" style="width:500px;height:375px" @click="showAvatarAuthorDialog(scope.row.userId, '', scope.row.previousCurrentAvatarImageUrl)")
- span(style="position:relative;top:-50px;margin:0 5px")
- i.el-icon-right
- el-popover(placement="right" width="500px" trigger="click")
- img.x-link(slot="reference" v-lazy="scope.row.currentAvatarThumbnailImageUrl" style="flex:none;width:160px;height:120px;border-radius:4px")
- img.x-link(v-lazy="scope.row.currentAvatarImageUrl" style="width:500px;height:375px" @click="showAvatarAuthorDialog(scope.row.userId, '', scope.row.currentAvatarImageUrl)")
- template(v-else-if="scope.row.type === 'Status'")
- el-tooltip(placement="top")
- template(#content)
- span(v-if="scope.row.previousStatus === 'active'") {{ $t('dialog.user.status.active') }}
- span(v-else-if="scope.row.previousStatus === 'join me'") {{ $t('dialog.user.status.join_me') }}
- span(v-else-if="scope.row.previousStatus === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
- span(v-else-if="scope.row.previousStatus === 'busy'") {{ $t('dialog.user.status.busy') }}
- span(v-else) {{ $t('dialog.user.status.offline') }}
- i.x-user-status(:class="statusClass(scope.row.previousStatus)")
- span(v-text="scope.row.previousStatusDescription")
- br
- span
- i.el-icon-right
- el-tooltip(placement="top")
- template(#content)
- span(v-if="scope.row.status === 'active'") {{ $t('dialog.user.status.active') }}
- span(v-else-if="scope.row.status === 'join me'") {{ $t('dialog.user.status.join_me') }}
- span(v-else-if="scope.row.status === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
- span(v-else-if="scope.row.status === 'busy'") {{ $t('dialog.user.status.busy') }}
- span(v-else) {{ $t('dialog.user.status.offline') }}
- i.x-user-status(:class="statusClass(scope.row.status)")
- span(v-text="scope.row.statusDescription")
- template(v-else-if="scope.row.type === 'Bio'")
- pre(v-text="scope.row.previousBio" style="font-family:inherit;font-size:12px;white-space:pre-wrap;margin:0 0.5em 0 0")
- span
- i.el-icon-right
- pre(v-text="scope.row.bio" style="font-family:inherit;font-size:12px;white-space:pre-wrap;margin:0 0.5em 0 0")
- el-table-column(:label="$t('table.feed.date')" prop="created_at" sortable="custom" width="120")
- template(v-once #default="scope")
- el-tooltip(placement="right")
- template(#content)
- span {{ scope.row.created_at | formatDate('long') }}
- span {{ scope.row.created_at | formatDate('short') }}
- el-table-column(:label="$t('table.feed.type')" prop="type" width="70")
- el-table-column(:label="$t('table.feed.user')" prop="displayName" width="180")
- template(v-once #default="scope")
- span.x-link(v-text="scope.row.displayName" @click="showUserDialog(scope.row.userId)")
- el-table-column(:label="$t('table.feed.detail')")
- template(v-once #default="scope")
- template(v-if="scope.row.type === 'GPS'")
- location(v-if="scope.row.location" :location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
- template(v-else-if="scope.row.type === 'Offline' || scope.row.type === 'Online'")
- location(v-if="scope.row.location" :location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
- template(v-else-if="scope.row.type === 'Status'")
- template(v-if="scope.row.statusDescription === scope.row.previousStatusDescription")
- el-tooltip(placement="top")
- template(#content)
- span(v-if="scope.row.previousStatus === 'active'") {{ $t('dialog.user.status.active') }}
- span(v-else-if="scope.row.previousStatus === 'join me'") {{ $t('dialog.user.status.join_me') }}
- span(v-else-if="scope.row.previousStatus === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
- span(v-else-if="scope.row.previousStatus === 'busy'") {{ $t('dialog.user.status.busy') }}
- span(v-else) {{ $t('dialog.user.status.offline') }}
- i.x-user-status(:class="statusClass(scope.row.previousStatus)")
- span
- i.el-icon-right
- el-tooltip(placement="top")
- template(#content)
- span(v-if="scope.row.status === 'active'") {{ $t('dialog.user.status.active') }}
- span(v-else-if="scope.row.status === 'join me'") {{ $t('dialog.user.status.join_me') }}
- span(v-else-if="scope.row.status === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
- span(v-else-if="scope.row.status === 'busy'") {{ $t('dialog.user.status.busy') }}
- span(v-else) {{ $t('dialog.user.status.offline') }}
- i.x-user-status(:class="statusClass(scope.row.status)")
- template(v-else)
- el-tooltip(placement="top")
- template(#content)
- span(v-if="scope.row.status === 'active'") {{ $t('dialog.user.status.active') }}
- span(v-else-if="scope.row.status === 'join me'") {{ $t('dialog.user.status.join_me') }}
- span(v-else-if="scope.row.status === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
- span(v-else-if="scope.row.status === 'busy'") {{ $t('dialog.user.status.busy') }}
- span(v-else) {{ $t('dialog.user.status.offline') }}
- i.x-user-status(:class="statusClass(scope.row.status)")
- span
- span(v-text="scope.row.statusDescription")
- template(v-else-if="scope.row.type === 'Avatar'")
- avatar-info(:imageurl="scope.row.currentAvatarImageUrl" :userid="scope.row.userId" :hintownerid="scope.row.ownerId" :hintavatarname="scope.row.avatarName")
- template(v-else-if="scope.row.type === 'Bio'")
- span(v-text="scope.row.bio")
+ include ./mixins/tabs/feed.pug
+ +feedTab()
//- gameLog
- .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'gameLog'")
- data-tables(v-bind="gameLogTable" v-loading="gameLogTable.loading")
- template(#tool)
- div(style="margin:0 0 10px;display:flex;align-items:center")
- el-select(v-model="gameLogTable.filter" @change="gameLogTableLookup" multiple clearable collapse-tags style="flex:1" :placeholder="$t('view.game_log.filter_placeholder')")
- el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'PortalSpawn', 'Event', 'VideoPlay', 'StringLoad', 'ImageLoad']" :key="type" :label="type" :value="type")
- el-input(v-model="gameLogTable.search" :placeholder="$t('view.game_log.search_placeholder')" @keyup.native.13="gameLogTableLookup" @change="gameLogTableLookup" clearable style="flex:none;width:150px;margin:0 10px")
- //- el-tooltip(placement="bottom" content="Reload game log" :disabled="hideTooltips")
- //- el-button(type="default" @click="resetGameLog" icon="el-icon-refresh" circle style="flex:none")
- el-table-column(:label="$t('table.gameLog.date')" prop="created_at" sortable="custom" width="120")
- template(v-once #default="scope")
- el-tooltip(placement="right")
- template(#content)
- span {{ scope.row.created_at | formatDate('long') }}
- span {{ scope.row.created_at | formatDate('short') }}
- el-table-column(:label="$t('table.gameLog.type')" prop="type" width="120")
- template(v-once #default="scope")
- span.x-link(v-if="scope.row.location && scope.row.type !== 'Location'" v-text="scope.row.type" @click="showWorldDialog(scope.row.location)")
- span(v-else v-text="scope.row.type")
- el-table-column(:label="$t('table.gameLog.icon')" prop="isFriend" width="60")
- template(v-once #default="scope")
- template(v-if="gameLogIsFriend(scope.row)")
- el-tooltip(v-if="gameLogIsFavorite(scope.row)" placement="top" content="Favorite")
- span ⭐
- el-tooltip(v-else placement="top" content="Friend")
- span 💚
- el-table-column(:label="$t('table.gameLog.user')" prop="displayName" width="180")
- template(v-once #default="scope")
- span.x-link(v-if="scope.row.displayName" v-text="scope.row.displayName" @click="lookupUser(scope.row)" style="padding-right:10px")
- el-table-column(:label="$t('table.gameLog.detail')" prop="data")
- template(v-once #default="scope")
- location(v-if="scope.row.type === 'Location'" :location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
- location(v-else-if="scope.row.type === 'PortalSpawn'" :location="scope.row.instanceId" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
- template(v-else-if="scope.row.type === 'Event'")
- span(v-text="scope.row.data")
- template(v-else-if="scope.row.type === 'VideoPlay'")
- span(v-if="scope.row.videoId" style="margin-right:5px") {{ scope.row.videoId }}:
- span(v-if="scope.row.videoId === 'LSMedia'" v-text="scope.row.videoName")
- span.x-link(v-else-if="scope.row.videoName" @click="openExternalLink(scope.row.videoUrl)" v-text="scope.row.videoName")
- span.x-link(v-else @click="openExternalLink(scope.row.videoUrl)" v-text="scope.row.videoUrl")
- template(v-else-if="scope.row.type === 'ImageLoad'")
- span.x-link(@click="openExternalLink(scope.row.resourceUrl)" v-text="scope.row.resourceUrl")
- template(v-else-if="scope.row.type === 'StringLoad'")
- span.x-link(@click="openExternalLink(scope.row.resourceUrl)" v-text="scope.row.resourceUrl")
- template(v-else-if="scope.row.type === 'Notification' || scope.row.type === 'OnPlayerJoined' || scope.row.type === 'OnPlayerLeft'")
- span.x-link(v-else v-text="scope.row.data")
- el-table-column(:label="$t('table.gameLog.action')" width="80" align="right")
- template(v-once #default="scope")
- el-button(v-if="scope.row.type !== 'OnPlayerJoined' && scope.row.type !== 'OnPlayerLeft' && scope.row.type !== 'Location' && scope.row.type !== 'PortalSpawn'" type="text" icon="el-icon-close" size="mini" @click="deleteGameLogEntry(scope.row)")
+ include ./mixins/tabs/gameLog.pug
+ +gameLogTab()
+
+ //- playerList
+ include ./mixins/tabs/playerList.pug
+ +playerListTab()
//- search
- .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'search'")
- div(style="margin:0 0 10px;display:flex;align-items:center")
- el-input(v-model="searchText" :placeholder="$t('view.search.search_placeholder')" @keyup.native.13="search()" style="flex:1")
- el-tooltip(placement="bottom" :content="$t('view.search.clear_results_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="clearSearch()" icon="el-icon-delete" circle style="flex:none;margin-left:10px")
- el-tabs(ref="searchTab" type="card" style="margin-top:15px")
- el-tab-pane(:label="$t('view.search.user.header')" v-loading="isSearchUserLoading" style="min-height:60px")
- .x-friend-list
- .x-friend-item(v-for="user in searchUserResults" :key="user.id" @click="showUserDialog(user.id)")
- template(v-once)
- .avatar
- img(v-lazy="userImage(user)")
- .detail
- span.name(v-text="user.displayName")
- span.extra(v-if="randomUserColours" v-text="user.$trustLevel" :class="user.$trustClass")
- span.extra(v-else v-text="user.$trustLevel" :style="{'color':user.$userColour}")
- el-button-group(style="margin-top:15px")
- el-button(v-if="searchUserParams.offset" @click="moreSearchUser(-1)" icon="el-icon-back" size="small") {{ $t('view.search.prev_page') }}
- el-button(v-if="searchUserResults.length" @click="moreSearchUser(1)" icon="el-icon-right" size="small") {{ $t('view.search.next_page') }}
- el-tab-pane(:label="$t('view.search.world.header')" v-loading="isSearchWorldLoading" style="min-height:60px")
- el-dropdown(@command="(row) => searchWorld(row)" size="small" trigger="click" style="margin-bottom:15px")
- el-button(size="small") {{ $t('view.search.world.category') }} #[i.el-icon-arrow-down.el-icon--right]
- el-dropdown-menu(#default="dropdown")
- el-dropdown-item(v-for="row in API.cachedConfig.dynamicWorldRows" :key="row.index" v-text="row.name" :command="row")
- el-checkbox(v-model="searchWorldLabs" style="margin-left:10px") {{ $t('view.search.world.community_lab') }}
- .x-friend-list
- .x-friend-item(v-for="world in searchWorldResults" :key="world.id" @click="showWorldDialog(world.id)")
- template(v-once)
- .avatar
- img(v-lazy="world.thumbnailImageUrl")
- .detail
- span.name(v-text="world.name")
- span.extra(v-if="world.occupants") {{ world.authorName }} ({{ world.occupants }})
- span.extra(v-else v-text="world.authorName")
- el-button-group(style="margin-top:15px")
- el-button(v-if="searchWorldParams.offset" @click="moreSearchWorld(-1)" icon="el-icon-back" size="small") {{ $t('view.search.prev_page') }}
- el-button(v-if="searchWorldResults.length >= 10" @click="moreSearchWorld(1)" icon="el-icon-right" size="small") {{ $t('view.search.next_page') }}
- el-tab-pane(:label="$t('view.search.avatar.header')" v-loading="isSearchAvatarLoading" style="min-height:60px")
- el-dropdown(v-if="avatarRemoteDatabaseProviderList.length > 1" trigger="click" @click.native.stop size="mini" style="margin-right:5px")
- el-button(size="small") {{ $t('view.search.avatar.search_provider') }} #[i.el-icon-arrow-down.el-icon--right]
- el-dropdown-menu(#default="dropdown")
- el-dropdown-item(v-for="provider in avatarRemoteDatabaseProviderList" :key="provider" @click.native="setAvatarProvider(provider)") #[i.el-icon-check.el-icon--left(v-if="provider === avatarRemoteDatabaseProvider")] {{ provider }}
- el-tooltip(placement="bottom" :content="$t('view.search.avatar.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" :loading="userDialog.isAvatarsLoading" @click="refreshUserDialogAvatars()" size="mini" icon="el-icon-refresh" circle)
- span(style="font-size:14px;margin-left:5px;margin-right:5px") {{ $t("view.search.avatar.result_count", { count: searchAvatarResults.length }) }}
- el-radio-group(v-model="searchAvatarFilter" size="mini" style="margin:5px;display:block" @change="searchAvatar")
- el-radio(label="all") {{ $t('view.search.avatar.all') }}
- el-radio(label="public") {{ $t('view.search.avatar.public') }}
- el-radio(label="private") {{ $t('view.search.avatar.private') }}
- el-radio-group(v-model="searchAvatarFilterRemote" size="mini" style="margin:5px;display:block" @change="searchAvatar")
- el-radio(label="all") {{ $t('view.search.avatar.all') }}
- el-radio(label="local") {{ $t('view.search.avatar.local') }}
- el-radio(label="remote" :disabled="!avatarRemoteDatabase") {{ $t('view.search.avatar.remote') }}
- el-radio-group(:disabled="searchAvatarFilterRemote !== 'local'" v-model="searchAvatarSort" size="mini" style="margin:5px;display:block" @change="searchAvatar")
- el-radio(label="name") {{ $t('view.search.avatar.sort_name') }}
- el-radio(label="update") {{ $t('view.search.avatar.sort_update') }}
- el-radio(label="created") {{ $t('view.search.avatar.sort_created') }}
- .x-friend-list(style="margin-top:20px")
- .x-friend-item(v-for="avatar in searchAvatarPage" :key="avatar.id" @click="showAvatarDialog(avatar.id)")
- template(v-once)
- .avatar
- img(v-if="avatar.thumbnailImageUrl" v-lazy="avatar.thumbnailImageUrl")
- img(v-else-if="avatar.imageUrl" v-lazy="avatar.imageUrl")
- .detail
- span.name(v-text="avatar.name")
- span.extra(v-text="avatar.releaseStatus" v-if="avatar.releaseStatus === 'public'" style="color: #67c23a;")
- span.extra(v-text="avatar.releaseStatus" v-else-if="avatar.releaseStatus === 'private'" style="color: #f56c6c;")
- span.extra(v-text="avatar.releaseStatus" v-else)
- span.extra(v-text="avatar.authorName")
- el-button-group(style="margin-top:15px")
- el-button(v-if="searchAvatarPageNum" @click="moreSearchAvatar(-1)" icon="el-icon-back" size="small") {{ $t('view.search.prev_page') }}
- el-button(v-if="searchAvatarResults.length > 10 && (searchAvatarPageNum + 1) * 10 < searchAvatarResults.length" @click="moreSearchAvatar(1)" icon="el-icon-right" size="small") {{ $t('view.search.next_page') }}
+ include ./mixins/tabs/search.pug
+ +searchTab()
//- favorite
- .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'favorite'")
- el-tooltip(placement="bottom" :content="$t('view.favorite.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" :loading="API.isFavoriteLoading" @click="API.refreshFavorites(); getLocalWorldFavorites()" size="small" icon="el-icon-refresh" circle style="position:relative;float:right;z-index:1")
- el-tabs(ref="favoriteTabRef" type="card" v-loading="API.isFavoriteLoading")
- el-tab-pane(:label="$t('view.favorite.friends.header')")
- el-collapse(v-if="$refs.menu && $refs.menu.activeIndex === 'favorite' && $refs.favoriteTabRef && $refs.favoriteTabRef.currentName === '0'" style="border:0")
- el-button(size="small" @click="showFriendExportDialog") {{ $t('view.favorite.export') }}
- el-button(size="small" @click="showFriendImportDialog") {{ $t('view.favorite.import') }}
- el-collapse-item(v-for="group in API.favoriteFriendGroups" :key="group.name")
- template(slot="title")
- span(v-text="group.displayName" style="font-weight:bold;font-size:14px;margin-left:10px")
- span(style="color:#909399;font-size:12px;margin-left:10px") {{ group.count }}/{{ group.capacity }}
- el-tooltip(placement="top" :content="$t('view.favorite.rename_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="changeFavoriteGroupName(group)" size="mini" icon="el-icon-edit" circle style="margin-left:10px")
- el-tooltip(placement="right" :content="$t('view.favorite.clear_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="clearFavoriteGroup(group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- .x-friend-list(v-if="group.count" style="margin-top:10px")
- div(style="display:inline-block;width:300px;margin-right:15px" v-for="favorite in favoriteFriends" v-if="favorite.groupKey === group.key" :key="favorite.id" @click="showUserDialog(favorite.id)")
- .x-friend-item
- template(v-if="favorite.ref")
- .avatar(:class="userStatusClass(favorite.ref)")
- img(v-lazy="userImage(favorite.ref)")
- .detail
- span.name(v-text="favorite.ref.displayName" :style="{'color':favorite.ref.$userColour}")
- location.extra(v-if="favorite.ref.location !== 'offline'" :location="favorite.ref.location" :traveling="favorite.ref.travelingToLocation" :link="false")
- span(v-else v-text="favorite.ref.statusDescription")
- el-tooltip(placement="left" :content="$t('view.favorite.move_tooltip')" :disabled="hideTooltips")
- el-dropdown(trigger="click" @click.native.stop size="mini" style="margin-left:5px")
- el-button(type="default" icon="el-icon-back" size="mini" circle)
- el-dropdown-menu(#default="dropdown")
- template(v-if="groupAPI.name !== group.name" v-for="groupAPI in API.favoriteFriendGroups" :key="groupAPI.name")
- el-dropdown-item(style="display:block;margin:10px 0" @click.native="moveFavorite(favorite.ref, groupAPI, 'friend')" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }} / {{ groupAPI.capacity }})
- el-tooltip(placement="right" :content="$t('view.favorite.unfavorite_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="deleteFavorite(favorite.id)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- template(v-else)
- span(v-text="favorite.name || favorite.id")
- el-button(type="text" icon="el-icon-close" size="mini" @click.stop="deleteFavorite(favorite.id)" style="margin-left:5px")
- el-tab-pane(:label="$t('view.favorite.worlds.header')")
- el-collapse(v-if="$refs.menu && $refs.menu.activeIndex === 'favorite' && $refs.favoriteTabRef && $refs.favoriteTabRef.currentName === '1'" style="border:0")
- el-button(size="small" @click="showWorldExportDialog") {{ $t('view.favorite.export') }}
- el-button(size="small" @click="showWorldImportDialog") {{ $t('view.favorite.import') }}
- span(style="display:block;margin-top:20px") {{ $t('view.favorite.worlds.vrchat_favorites') }}
- el-collapse-item(v-for="group in API.favoriteWorldGroups" :key="group.name")
- template(slot="title")
- span(v-text="group.displayName" style="font-weight:bold;font-size:14px;margin-left:10px")
- i.x-user-status(style="margin-left:5px" :class="userFavoriteWorldsStatus(group.visibility)")
- span(style="color:#909399;font-size:12px;margin-left:10px") {{ group.count }}/{{ group.capacity }}
- el-tooltip(placement="top" :content="$t('view.favorite.visibility_tooltip')" :disabled="hideTooltips")
- el-dropdown(trigger="click" @click.native.stop size="mini" style="margin-left:10px")
- el-button(type="default" icon="el-icon-view" size="mini" circle)
- el-dropdown-menu(#default="dropdown")
- el-dropdown-item(v-if="group.visibility !== visibility" v-for="visibility in worldGroupVisibilityOptions" :key="visibility" style="display:block;margin:10px 0" v-text="visibility" @click.native="changeWorldGroupVisibility(group.name, visibility)")
- el-tooltip(placement="top" :content="$t('view.favorite.rename_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="changeFavoriteGroupName(group)" size="mini" icon="el-icon-edit" circle style="margin-left:5px")
- el-tooltip(placement="right" :content="$t('view.favorite.clear_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="clearFavoriteGroup(group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- .x-friend-list(v-if="group.count" style="margin-top:10px")
- div(style="display:inline-block;width:300px;margin-right:15px" v-for="favorite in favoriteWorlds" v-if="favorite.groupKey === group.key" :key="favorite.id" @click="showWorldDialog(favorite.id)")
- .x-friend-item
- template(v-if="favorite.ref")
- .avatar
- img(v-lazy="favorite.ref.thumbnailImageUrl")
- .detail
- span.name(v-text="favorite.ref.name")
- span.extra(v-if="favorite.ref.occupants") {{ favorite.ref.authorName }} ({{ favorite.ref.occupants }})
- span.extra(v-else v-text="favorite.ref.authorName")
- el-tooltip(placement="left" :content="$t('view.favorite.move_tooltip')" :disabled="hideTooltips")
- el-dropdown(trigger="click" @click.native.stop size="mini" style="margin-left:5px")
- el-button(type="default" icon="el-icon-back" size="mini" circle)
- el-dropdown-menu(#default="dropdown")
- template(v-if="groupAPI.name !== group.name" v-for="groupAPI in API.favoriteWorldGroups" :key="groupAPI.name")
- el-dropdown-item(style="display:block;margin:10px 0" @click.native="moveFavorite(favorite.ref, groupAPI, 'world')" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }} / {{ groupAPI.capacity }})
- el-tooltip(placement="right" :content="$t('view.favorite.unfavorite_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="deleteFavorite(favorite.id)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- template(v-else)
- span(v-text="favorite.name || favorite.id")
- el-button(type="text" icon="el-icon-close" size="mini" @click.stop="deleteFavorite(favorite.id)" style="margin-left:5px")
- span(style="display:block;margin-top:20px") {{ $t('view.favorite.worlds.local_favorites') }}
- el-button(size="small" @click="promptNewLocalWorldFavoriteGroup" style="display:block;margin-top:10px") {{ $t('view.favorite.worlds.new_group') }}
- el-collapse-item(v-for="group in localWorldFavoriteGroups" v-if="localWorldFavorites[group]" :key="group")
- template(slot="title")
- span(v-text="group" style="font-weight:bold;font-size:14px;margin-left:10px")
- span(style="color:#909399;font-size:12px;margin-left:10px") {{ getLocalWorldFavoriteGroupLength(group) }}
- el-tooltip(placement="top" :content="$t('view.favorite.rename_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="promptLocalWorldFavoriteGroupRename(group)" size="mini" icon="el-icon-edit" circle style="margin-left:10px")
- el-tooltip(placement="right" :content="$t('view.favorite.delete_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="promptLocalWorldFavoriteGroupDelete(group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- .x-friend-list(style="margin-top:10px")
- div(style="display:inline-block;width:300px;margin-right:15px" v-for="favorite in localWorldFavorites[group]" :key="favorite.id" @click="showWorldDialog(favorite.id)")
- .x-friend-item
- template(v-if="favorite.name")
- .avatar
- img(v-lazy="favorite.thumbnailImageUrl")
- .detail
- span.name(v-text="favorite.name")
- span.extra(v-if="favorite.occupants") {{ favorite.authorName }} ({{ favorite.occupants }})
- span.extra(v-else v-text="favorite.authorName")
- el-tooltip(placement="right" :content="$t('view.favorite.unfavorite_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="removeLocalWorldFavorite(favorite.id, group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- template(v-else)
- span(v-text="favorite.id")
- el-button(type="text" icon="el-icon-close" size="mini" @click.stop="removeLocalWorldFavorite(favorite.id, group)" style="margin-left:5px")
- el-tab-pane(:label="$t('view.favorite.avatars.header')")
- el-collapse(v-if="$refs.menu && $refs.menu.activeIndex === 'favorite' && $refs.favoriteTabRef && $refs.favoriteTabRef.currentName === '2'" style="border:0")
- el-button(size="small" @click="showAvatarExportDialog") {{ $t('view.favorite.export') }}
- el-button(size="small" @click="showAvatarImportDialog") {{ $t('view.favorite.import') }}
- el-collapse-item(v-for="group in API.favoriteAvatarGroups" :key="group.name")
- template(slot="title")
- span(v-text="group.displayName" style="font-weight:bold;font-size:14px;margin-left:10px")
- span(style="color:#909399;font-size:12px;margin-left:10px") {{ group.count }}/{{ group.capacity }}
- el-tooltip(placement="top" :content="$t('view.favorite.rename_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="changeFavoriteGroupName(group)" size="mini" icon="el-icon-edit" circle style="margin-left:10px")
- el-tooltip(placement="right" :content="$t('view.favorite.clear_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="clearFavoriteGroup(group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- .x-friend-list(v-if="group.count" style="margin-top:10px")
- div(style="display:inline-block;width:300px;margin-right:15px" v-for="favorite in favoriteAvatars" v-if="favorite.groupKey === group.key" :key="favorite.id" @click="showAvatarDialog(favorite.id)")
- .x-friend-item
- template(v-if="favorite.ref")
- .avatar
- img(v-lazy="favorite.ref.thumbnailImageUrl")
- .detail
- span.name(v-text="favorite.ref.name")
- span.extra(v-text="favorite.ref.authorName")
- el-tooltip(placement="left" :content="$t('view.favorite.move_tooltip')" :disabled="hideTooltips")
- el-dropdown(trigger="click" @click.native.stop size="mini" style="margin-left:5px")
- el-button(type="default" icon="el-icon-back" size="mini" circle)
- el-dropdown-menu(#default="dropdown")
- template(v-if="groupAPI.name !== group.name" v-for="groupAPI in API.favoriteAvatarGroups" :key="groupAPI.name")
- el-dropdown-item(style="display:block;margin:10px 0" @click.native="moveFavorite(favorite.ref, groupAPI, 'avatar')" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }} / {{ groupAPI.capacity }})
- el-tooltip(placement="right" :content="$t('view.favorite.unfavorite_tooltip')" :disabled="hideTooltips")
- el-button(@click.stop="deleteFavorite(favorite.id)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- template(v-else)
- .detail
- span.name(v-text="favorite.name || favorite.id")
- el-button(type="text" icon="el-icon-close" size="mini" @click.stop="deleteFavorite(favorite.id)" style="margin-left:5px")
- el-collapse-item
- template(slot="title")
- span(style="font-weight:bold;font-size:14px;margin-left:10px") Local History
- span(style="color:#909399;font-size:12px;margin-left:10px") {{ avatarHistoryArray.length }}/100
- el-tooltip(placement="right" content="Clear" :disabled="hideTooltips")
- el-button(@click.stop="promptClearAvatarHistory" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- .x-friend-list(v-if="avatarHistoryArray.length" style="margin-top:10px")
- div(style="display:inline-block;width:300px;margin-right:15px" v-for="favorite in avatarHistoryArray" :key="favorite.id" @click="showAvatarDialog(favorite.id)")
- .x-friend-item
- .avatar
- img(v-lazy="favorite.thumbnailImageUrl")
- .detail
- span.name(v-text="favorite.name")
- span.extra(v-text="favorite.authorName")
- template(v-if="API.cachedFavoritesByObjectId.has(favorite.id)")
- el-tooltip(placement="left" content="Unfavorite" :disabled="hideTooltips")
- el-button(@click.stop="deleteFavorite(favorite.id)" type="default" icon="el-icon-star-on" size="mini" circle)
- template(v-else)
- el-tooltip(placement="left" content="Favorite" :disabled="hideTooltips")
- el-button(@click.stop="showFavoriteDialog('avatar', favorite.id)" type="default" icon="el-icon-star-off" size="mini" circle)
+ include ./mixins/tabs/favorites.pug
+ +favoritesTab()
//- friendLog
- .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'friendLog'" v-if="$refs.menu && $refs.menu.activeIndex === 'friendLog'")
- data-tables(v-bind="friendLogTable")
- template(#tool)
- div(style="margin:0 0 10px;display:flex;align-items:center")
- el-select(v-model="friendLogTable.filters[0].value" @change="saveTableFilters" multiple clearable collapse-tags style="flex:1" :placeholder="$t('view.friend_log.filter_placeholder')")
- el-option(v-once v-for="type in ['Friend', 'Unfriend', 'FriendRequest', 'CancelFriendRequest', 'DisplayName', 'TrustLevel']" :key="type" :label="type" :value="type")
- el-input(v-model="friendLogTable.filters[1].value" :placeholder="$t('view.friend_log.search_placeholder')" style="flex:none;width:150px;margin-left:10px")
- el-table-column(:label="$t('table.friendLog.date')" prop="created_at" sortable="custom" width="120")
- template(v-once #default="scope")
- el-tooltip(placement="right")
- template(#content)
- span {{ scope.row.created_at | formatDate('long') }}
- span {{ scope.row.created_at | formatDate('short') }}
- el-table-column(:label="$t('table.friendLog.type')" prop="type" width="150")
- el-table-column(:label="$t('table.friendLog.user')" prop="displayName")
- template(v-once #default="scope")
- span(v-if="scope.row.type === 'DisplayName'") {{ scope.row.previousDisplayName }} #[i.el-icon-right]
- span.x-link(v-text="scope.row.displayName || scope.row.userId" @click="showUserDialog(scope.row.userId)")
- template(v-if="scope.row.type === 'TrustLevel'")
- span ({{ scope.row.previousTrustLevel }} #[i.el-icon-right] {{ scope.row.trustLevel }})
- el-table-column(:label="$t('table.friendLog.action')" width="80" align="right")
- template(v-once #default="scope")
- el-button(type="text" icon="el-icon-close" size="mini" @click="deleteFriendLog(scope.row)")
+ include ./mixins/tabs/friendLog.pug
+ +friendLogTab()
//- moderation
- .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'moderation'" v-if="$refs.menu && $refs.menu.activeIndex === 'moderation'")
- data-tables(v-bind="playerModerationTable" v-loading="API.isPlayerModerationsLoading")
- template(#tool)
- div(style="margin:0 0 10px;display:flex;align-items:center")
- el-select(v-model="playerModerationTable.filters[0].value" @change="saveTableFilters" multiple clearable collapse-tags style="flex:1" :placeholder="$t('view.moderation.filter_placeholder')")
- el-option(v-once v-for="type in ['block', 'unblock', 'mute', 'unmute', 'interactOn', 'interactOff']" :key="type" :label="type" :value="type")
- el-input(v-model="playerModerationTable.filters[1].value" :placeholder="$t('view.moderation.search_placeholder')" style="flex:none;width:150px;margin:0 10px")
- el-tooltip(placement="bottom" :content="$t('view.moderation.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" :loading="API.isPlayerModerationsLoading" @click="API.refreshPlayerModerations()" icon="el-icon-refresh" circle style="flex:none")
- el-table-column(:label="$t('table.moderation.date')" prop="created" sortable="custom" width="120")
- template(v-once #default="scope")
- el-tooltip(placement="right")
- template(#content)
- span {{ scope.row.created | formatDate('long') }}
- span {{ scope.row.created | formatDate('short') }}
- el-table-column(:label="$t('table.moderation.type')" prop="type" width="100")
- el-table-column(:label="$t('table.moderation.source')" prop="sourceDisplayName")
- template(v-once #default="scope")
- span.x-link(v-text="scope.row.sourceDisplayName" @click="showUserDialog(scope.row.sourceUserId)")
- el-table-column(:label="$t('table.moderation.target')" prop="targetDisplayName")
- template(v-once #default="scope")
- span.x-link(v-text="scope.row.targetDisplayName" @click="showUserDialog(scope.row.targetUserId)")
- el-table-column(:label="$t('table.moderation.action')" width="80" align="right")
- template(v-once #default="scope")
- el-button(v-if="scope.row.sourceUserId === API.currentUser.id" type="text" icon="el-icon-close" size="mini" @click="deletePlayerModeration(scope.row)")
+ include ./mixins/tabs/moderation.pug
+ +moderationTab()
//- notification
- .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'notification'" v-if="$refs.menu && $refs.menu.activeIndex === 'notification'" v-loading="API.isNotificationsLoading")
- data-tables(v-bind="notificationTable")
- 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="$t('view.notification.filter_placeholder')")
- el-option(v-once v-for="type in ['requestInvite', 'invite', 'requestInviteResponse', 'inviteResponse', 'friendRequest', 'hiddenFriendRequest', 'message', 'group.announcement', 'group.informative', 'group.invite', 'group.joinRequest', 'moderation.warning.group']" :key="type" :label="type" :value="type")
- el-input(v-model="notificationTable.filters[1].value" :placeholder="$t('view.notification.search_placeholder')" style="flex:none;width:150px;margin:0 10px")
- el-tooltip(placement="bottom" :content="$t('view.notification.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" :loading="API.isNotificationsLoading" @click="API.refreshNotifications()" icon="el-icon-refresh" circle style="flex:none")
- el-table-column(:label="$t('table.notification.date')" prop="created_at" sortable="custom" width="120")
- template(v-once #default="scope")
- el-tooltip(placement="right")
- template(#content)
- span {{ scope.row.created_at | formatDate('long') }}
- span {{ scope.row.created_at | formatDate('short') }}
- el-table-column(:label="$t('table.notification.type')" prop="type" width="160")
- template(v-once #default="scope")
- el-tooltip(v-if="scope.row.type === 'invite'" placement="top")
- template(#content)
- location(v-if="scope.row.details" :location="scope.row.details.worldId" :hint="scope.row.details.worldName" :grouphint="scope.row.details.groupName" :link="false")
- span.x-link(v-text="scope.row.type" @click="showWorldDialog(scope.row.details.worldId)")
- 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="$t('table.notification.user')" prop="senderUsername" width="150")
- template(v-once #default="scope")
- span.x-link(v-text="scope.row.senderUsername" @click="showUserDialog(scope.row.senderUserId)")
- el-table-column(:label="$t('table.notification.photo')" width="100" prop="photo")
- template(v-once #default="scope")
- template(v-if="scope.row.details && scope.row.details.imageUrl")
- el-popover(placement="right" width="500px" trigger="click")
- img.x-link(slot="reference" v-lazy="scope.row.details.imageUrl" style="flex:none;height:50px;border-radius:4px")
- img.x-link(v-lazy="scope.row.details.imageUrl" style="width:500px" @click="downloadAndSaveImage(scope.row.details.imageUrl)")
- template(v-else-if="scope.row.imageUrl")
- el-popover(placement="right" width="500px" trigger="click")
- img.x-link(slot="reference" v-lazy="scope.row.imageUrl" style="flex:none;height:50px;border-radius:4px")
- img.x-link(v-lazy="scope.row.imageUrl" style="width:500px" @click="downloadAndSaveImage(scope.row.imageUrl)")
- el-table-column(:label="$t('table.notification.message')" prop="message")
- template(v-once #default="scope")
- span(v-if="scope.row.title") {{ scope.row.title }}, {{ scope.row.message }}
- span(v-else-if="scope.row.message" v-text="scope.row.message")
- span(v-else-if='scope.row.details && scope.row.details.inviteMessage' v-text="scope.row.details.inviteMessage")
- span(v-else-if='scope.row.details && scope.row.details.requestMessage' v-text="scope.row.details.requestMessage")
- span(v-else-if='scope.row.details && scope.row.details.responseMessage' v-text="scope.row.details.responseMessage")
- el-table-column(:label="$t('table.notification.action')" width="100" align="right")
- template(v-once #default="scope")
- template(v-if="scope.row.senderUserId !== API.currentUser.id && !scope.row.$isExpired")
- template(v-if="scope.row.type === 'friendRequest'")
- el-tooltip(placement="top" content="Accept" :disabled="hideTooltips")
- el-button(type="text" icon="el-icon-check" size="mini" @click="acceptNotification(scope.row)")
- template(v-else-if="scope.row.type === 'invite'")
- el-tooltip(placement="top" content="Decline with message" :disabled="hideTooltips")
- el-button(type="text" icon="el-icon-chat-line-square" size="mini" @click="showSendInviteResponseDialog(scope.row)")
- template(v-else-if="scope.row.type === 'requestInvite'")
- template(v-if="lastLocation.location && isGameRunning && checkCanInvite(lastLocation.location)")
- el-tooltip(placement="top" content="Invite" :disabled="hideTooltips")
- 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-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" 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, '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)")
- template(v-if="scope.row.type !== 'friendRequest' && scope.row.type !== 'hiddenFriendRequest' && !scope.row.type.includes('group.') && !scope.row.type.includes('moderation.')")
- 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)")
+ include ./mixins/tabs/notifications.pug
+ +notificationsTab()
//- profile
- .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'profile'" v-if="$refs.menu && $refs.menu.activeIndex === 'profile'")
- div.options-container(style="margin-top:0")
- span.header {{ $t('view.profile.profile.header') }}
- .x-friend-list(style="margin-top:10px")
- .x-friend-item(@click="showUserDialog(API.currentUser.id)")
- .avatar
- img(v-lazy="userImage(API.currentUser)")
- .detail
- span.name(v-text="API.currentUser.displayName")
- span.extra(v-text="API.currentUser.username")
- .x-friend-item(style="cursor:default")
- .detail
- span.name {{ $t('view.profile.profile.last_activity') }}
- span.extra {{ API.currentUser.last_activity | formatDate('long') }}
- .x-friend-item(style="cursor:default")
- .detail
- span.name {{ $t('view.profile.profile.two_factor') }}
- span.extra {{ API.currentUser.twoFactorAuthEnabled ? $t('view.profile.profile.two_factor_enabled') : $t('view.profile.profile.two_factor_disabled') }}
- div
- el-button(size="small" icon="el-icon-switch-button" @click="logout()" style="margin-left:0;margin-right:5px;margin-top:10px") {{ $t('view.profile.profile.logout') }}
- el-button(size="small" icon="el-icon-printer" @click="showExportFriendsListDialog()" style="margin-left:0;margin-right:5px;margin-top:10px") {{ $t('view.profile.profile.export_friend_list') }}
- el-button(size="small" icon="el-icon-user" @click="showExportAvatarsListDialog()" style="margin-left:0;margin-right:5px;margin-top:10px") {{ $t('view.profile.profile.export_own_avatars') }}
- el-button(size="small" icon="el-icon-chat-dot-round" @click="showDiscordNamesDialog()" style="margin-left:0;margin-right:5px;margin-top:10px") {{ $t('view.profile.profile.discord_names') }}
- el-button(size="small" icon="el-icon-document-copy" @click="showNoteExportDialog()" style="margin-left:0;margin-right:5px;margin-top:10px") {{ $t('view.profile.profile.export_notes') }}
- div.options-container
- span.header {{ $t('view.profile.game_info.header') }}
- .x-friend-list(style="margin-top:10px")
- .x-friend-item
- .detail(@click="API.getVisits()")
- span.name {{ $t('view.profile.game_info.online_users') }}
- span.extra(v-if="visits") {{ $t('view.profile.game_info.user_online', { count: visits }) }}
- span.extra(v-else) {{ $t('view.profile.game_info.refresh') }}
- div.options-container
- span.header {{ $t('view.profile.vrc_sdk_downloads.header') }}
- el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="API.getConfig()" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
- .x-friend-list(style="margin-top:10px")
- .x-friend-item(v-for="(link, item) in API.cachedConfig.downloadUrls" :key="item" placement="top")
- .detail(@click="openExternalLink(link)")
- span.name(v-text="item")
- span.extra(v-text="link")
- div.options-container
- span.header {{ $t('view.profile.direct_access.header') }}
- div(style="margin-top:10px")
- el-button-group
- el-button(size="small" @click="promptUsernameDialog()") {{ $t('view.profile.direct_access.username') }}
- el-button(size="small" @click="promptUserIdDialog()") {{ $t('view.profile.direct_access.user_id') }}
- el-button(size="small" @click="promptWorldDialog()") {{ $t('view.profile.direct_access.world_instance') }}
- el-button(size="small" @click="promptAvatarDialog()") {{ $t('view.profile.direct_access.avatar') }}
- div.options-container
- span.header {{ $t('view.profile.invite_messages') }}
- el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="inviteMessageTable.visible = true; refreshInviteMessageTable('message')" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
- el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="inviteMessageTable.visible = false" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- data-tables(v-if="inviteMessageTable.visible" v-bind="inviteMessageTable" style="margin-top:10px")
- el-table-column(label="Slot" prop="slot" sortable="custom" width="70")
- el-table-column(label="Message" prop="message")
- el-table-column(label="Cool Down" prop="updatedAt" sortable="custom" width="110" align="right")
- template(v-once #default="scope")
- countdown-timer(:datetime="scope.row.updatedAt" :hours="1")
- el-table-column(label="Action" width="60" align="right")
- template(v-once #default="scope")
- el-button(type="text" icon="el-icon-edit" size="mini" @click="showEditInviteMessageDialog('message', scope.row)")
- div.options-container
- span.header {{ $t('view.profile.invite_response_messages') }}
- el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="inviteResponseMessageTable.visible = true; refreshInviteMessageTable('response')" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
- el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="inviteResponseMessageTable.visible = false" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- data-tables(v-if="inviteResponseMessageTable.visible" v-bind="inviteResponseMessageTable" style="margin-top:10px")
- el-table-column(label="Slot" prop="slot" sortable="custom" width="70")
- el-table-column(label="Message" prop="message")
- el-table-column(label="Cool Down" prop="updatedAt" sortable="custom" width="110" align="right")
- template(v-once #default="scope")
- countdown-timer(:datetime="scope.row.updatedAt" :hours="1")
- el-table-column(label="Action" width="60" align="right")
- template(v-once #default="scope")
- el-button(type="text" icon="el-icon-edit" size="mini" @click="showEditInviteMessageDialog('response', scope.row)")
- div.options-container
- span.header {{ $t('view.profile.invite_request_messages') }}
- el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="inviteRequestMessageTable.visible = true; refreshInviteMessageTable('request')" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
- el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="inviteRequestMessageTable.visible = false" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- data-tables(v-if="inviteRequestMessageTable.visible" v-bind="inviteRequestMessageTable" style="margin-top:10px")
- el-table-column(label="Slot" prop="slot" sortable="custom" width="70")
- el-table-column(label="Message" prop="message")
- el-table-column(label="Cool Down" prop="updatedAt" sortable="custom" width="110" align="right")
- template(v-once #default="scope")
- countdown-timer(:datetime="scope.row.updatedAt" :hours="1")
- el-table-column(label="Action" width="60" align="right")
- template(v-once #default="scope")
- el-button(type="text" icon="el-icon-edit" size="mini" @click="showEditInviteMessageDialog('request', scope.row)")
- div.options-container
- span.header {{ $t('view.profile.invite_request_response_messages') }}
- el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="inviteRequestResponseMessageTable.visible = true; refreshInviteMessageTable('requestResponse')" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
- el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="inviteRequestResponseMessageTable.visible = false" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- data-tables(v-if="inviteRequestResponseMessageTable.visible" v-bind="inviteRequestResponseMessageTable" style="margin-top:10px")
- el-table-column(label="Slot" prop="slot" sortable="custom" width="70")
- el-table-column(label="Message" prop="message")
- el-table-column(label="Cool Down" prop="updatedAt" sortable="custom" width="110" align="right")
- template(v-once #default="scope")
- countdown-timer(:datetime="scope.row.updatedAt" :hours="1")
- el-table-column(label="Action" width="60" align="right")
- template(v-once #default="scope")
- el-button(type="text" icon="el-icon-edit" size="mini" @click="showEditInviteMessageDialog('requestResponse', scope.row)")
- div.options-container
- span.header {{ $t('view.profile.past_display_names') }}
- data-tables(v-bind="pastDisplayNameTable" style="margin-top:10px")
- el-table-column(label="Date" prop="updated_at" sortable="custom")
- template(v-once #default="scope")
- span {{ scope.row.updated_at | formatDate('long') }}
- el-table-column(label="Name" prop="displayName")
- div.options-container
- span.header {{ $t('view.profile.config_json') }}
- el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="refreshConfigTreeData()" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
- el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="configTreeData = []" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- el-tree(v-if="configTreeData.length > 0" :data="configTreeData" style="margin-top:10px;font-size:12px")
- template(#default="scope")
- span
- span(v-text="scope.data.key" style="font-weight:bold;margin-right:5px")
- span(v-if="!scope.data.children" v-text="scope.data.value")
- div.options-container
- span.header {{ $t('view.profile.current_user_json') }}
- el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="refreshCurrentUserTreeData()" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
- el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="currentUserTreeData = []" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- el-tree(v-if="currentUserTreeData.length > 0" :data="currentUserTreeData" style="margin-top:10px;font-size:12px")
- template(#default="scope")
- span
- span(v-text="scope.data.key" style="font-weight:bold;margin-right:5px")
- span(v-if="!scope.data.children" v-text="scope.data.value")
- div.options-container
- span.header {{ $t('view.profile.feedback') }}
- el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="getCurrentUserFeedback()" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
- el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="currentUserFeedbackData = []" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
- el-tree(v-if="currentUserFeedbackData.length > 0" :data="currentUserFeedbackData" style="margin-top:10px;font-size:12px")
- template(#default="scope")
- span
- span(v-text="scope.data.key" style="font-weight:bold;margin-right:5px")
- span(v-if="!scope.data.children" v-text="scope.data.value")
+ include ./mixins/tabs/profile.pug
+ +profileTab()
//- friends list
- .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'friendsList'" v-if="$refs.menu && $refs.menu.activeIndex === 'friendsList'")
- div.options-container(style="margin-top:0")
- span.header {{ $t('view.friend_list.header') }}
- div(style="float:right;font-size:13px")
- div(v-if="friendsListBulkUnfriendMode" style="display:inline-block;margin-right:10px")
- el-button(size="small" @click="showBulkUnfriendSelectionConfirm") {{ $t('view.friend_list.bulk_unfriend_selection') }}
- //- el-button(size="small" @click="showBulkUnfriendAllConfirm" style="margin-right:5px") Bulk Unfriend All
- div(style="display:inline-block;margin-right:10px")
- span.name {{ $t('view.friend_list.bulk_unfriend') }}
- el-switch(v-model="friendsListBulkUnfriendMode" style="margin-left:5px")
- span {{ $t('view.friend_list.load') }}
- el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.friend_list.load_notice')")
- i.el-icon-warning
- template(v-if="friendsListLoading")
- span(v-text="friendsListLoadingProgress" style="margin-left:5px")
- el-tooltip(placement="top" :content="$t('view.friend_list.cancel_tooltip')" :disabled="hideTooltips")
- el-button(@click="friendsListLoading = false" size="mini" icon="el-icon-loading" circle style="margin-left:5px")
- template(v-else)
- el-tooltip(placement="top" :content="$t('view.friend_list.load_tooltip')" :disabled="hideTooltips")
- el-button(@click="friendsListLoadUsers" size="mini" icon="el-icon-refresh-left" circle style="margin-left:5px")
- div(style="margin:10px 0 0 10px;display:flex;align-items:center")
- div(style="flex:none;margin-right:10px")
- el-tooltip(placement="bottom" :content="$t('view.friend_list.favorites_only_tooltip')" :disabled="hideTooltips")
- el-switch(v-model="friendsListSearchFilterVIP" @change="friendsListSearchChange" active-color="#13ce66")
- el-input(v-model="friendsListSearch" :placeholder="$t('view.friend_list.search_placeholder')" @change="friendsListSearchChange" clearable style="flex:1")
- el-select(v-model="friendsListSearchFilters" multiple clearable collapse-tags style="flex:none;width:200px;margin:0 10px" @change="friendsListSearchChange" :placeholder="$t('view.friend_list.filter_placeholder')")
- el-option(v-once v-for="type in ['Display Name', 'User Name', 'Rank', 'Status', 'Bio', 'Memo']" :key="type" :label="type" :value="type")
- el-tooltip(placement="top" :content="$t('view.friend_list.refresh_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="friendsListSearchChange" icon="el-icon-refresh" circle style="flex:none")
- el-tooltip(placement="top" :content="$t('view.friend_list.clear_tooltip')" :disabled="hideTooltips")
- el-button(type="default" @click="friendsListTable.data = []" icon="el-icon-delete" circle style="flex:none;margin-left:5px")
- data-tables(v-bind="friendsListTable" @row-click="selectFriendsListRow" style="margin-top:10px;cursor:pointer")
- el-table-column(v-if="friendsListBulkUnfriendMode" width="55" prop="$selected")
- template(v-once #default="scope")
- el-button(type="text" size="mini" @click.stop)
- el-checkbox(v-model="scope.row.$selected")
- el-table-column(:label="$t('table.friendList.no')" width="70" prop="$friendNum" sortable="custom")
- el-table-column(:label="$t('table.friendList.avatar')" width="70" prop="photo")
- template(v-once #default="scope")
- el-popover(placement="right" height="500px" trigger="hover")
- img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row)")
- img.friends-list-avatar(v-lazy="userImageFull(scope.row)" style="height:500px;cursor:pointer" @click="downloadAndSaveImage(userImageFull(scope.row))")
- el-table-column(:label="$t('table.friendList.displayName')" min-width="140" prop="displayName" sortable :sort-method="(a, b) => sortAlphabetically(a, b, 'displayName')")
- template(v-once #default="scope")
- span.name(v-if="randomUserColours" v-text="scope.row.displayName" :style="{'color':scope.row.$userColour}")
- span.name(v-else v-text="scope.row.displayName")
- el-table-column(:label="$t('table.friendList.rank')" width="110" prop="$trustSortNum" sortable="custom")
- template(v-once #default="scope")
- span.name(v-if="randomUserColours" v-text="scope.row.$trustLevel" :class="scope.row.$trustClass")
- span.name(v-else v-text="scope.row.$trustLevel" :style="{'color':scope.row.$userColour}")
- el-table-column(:label="$t('table.friendList.status')" min-width="180" prop="status" sortable :sort-method="(a, b) => sortStatus(a.status, b.status)")
- template(v-once #default="scope")
- i.x-user-status(v-if="scope.row.status !== 'offline'" :class="statusClass(scope.row.status)")
- span
- span(v-text="scope.row.statusDescription")
- el-table-column(:label="$t('table.friendList.language')" width="110" prop="$languages" sortable :sort-method="(a, b) => sortLanguages(a, b)")
- template(v-once #default="scope")
- el-tooltip(v-for="item in scope.row.$languages" :key="item.key" placement="top")
- template(#content)
- span {{ item.value }} ({{ item.key }})
- span.flags(:class="languageClass(item.key)" style="display:inline-block;margin-left:5px")
- el-table-column(:label="$t('table.friendList.bioLink')" width="100" prop="bioLinks")
- template(v-once #default="scope")
- el-tooltip(v-if="link" v-for="(link, index) in scope.row.bioLinks" :key="index")
- template(#content)
- span(v-text="link")
- img(:src="getFaviconUrl(link)" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;cursor:pointer" @click.stop="openExternalLink(link)")
- el-table-column(:label="$t('table.friendList.joinCount')" width="120" prop="$joinCount" sortable)
- el-table-column(:label="$t('table.friendList.timeTogether')" width="140" prop="$timeSpent" sortable)
- template(v-once #default="scope")
- span(v-if="scope.row.$timeSpent") {{ scope.row.$timeSpent | timeToText }}
- el-table-column(:label="$t('table.friendList.lastSeen')" width="170" prop="$lastSeen" sortable :sort-method="(a, b) => sortAlphabetically(a, b, '$lastSeen')")
- template(v-once #default="scope")
- span {{ scope.row.$lastSeen | formatDate('long') }}
- el-table-column(:label="$t('table.friendList.lastActivity')" width="170" prop="last_activity" sortable :sort-method="(a, b) => sortAlphabetically(a, b, 'last_activity')")
- template(v-once #default="scope")
- span {{ scope.row.last_activity | formatDate('long') }}
- el-table-column(:label="$t('table.friendList.lastLogin')" width="170" prop="last_login" sortable :sort-method="(a, b) => sortAlphabetically(a, b, 'last_login')")
- template(v-once #default="scope")
- span {{ scope.row.last_login | formatDate('long') }}
- el-table-column(:label="$t('table.friendList.dateJoined')" width="120" prop="date_joined" sortable :sort-method="(a, b) => sortAlphabetically(a, b, 'date_joined')")
- el-table-column(:label="$t('table.friendList.unfriend')" width="80")
- template(v-once #default="scope")
- el-button(type="text" icon="el-icon-close" size="mini" @click.stop="confirmDeleteFriend(scope.row.id)")
+ include ./mixins/tabs/friendsList.pug
+ +friendsListTab()
//- settings
- .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'settings'")
- div.options-container(style="margin-top:0")
- span.header {{ $t("view.settings.header") }}
- el-tabs(type="card" style="margin-top:10px")
- el-tab-pane(:label="$t('view.settings.category.general')")
- div.options-container(style="margin-top:0")
- span.header {{ $t("view.settings.general.general.header") }}
- .x-friend-list(style="margin-top:10px")
- .x-friend-item(style="cursor:default")
- .detail
- span.name {{ $t("view.settings.general.general.version") }}
- span.extra(v-text="appVersion")
- .x-friend-item(@click="checkForVRCXUpdate")
- .detail
- span.name {{ $t("view.settings.general.general.latest_app_version") }}
- span.extra(v-if="latestAppVersion" v-text="latestAppVersion")
- span.extra(v-else) {{ $t("view.settings.general.general.latest_app_version_refresh") }}
- .x-friend-item(@click="openExternalLink('https://github.com/vrcx-team/VRCX')")
- .detail
- span.name {{ $t("view.settings.general.general.repository_url") }}
- span.extra https://github.com/vrcx-team/VRCX
- .x-friend-item(@click="openExternalLink('https://vrcx.pypy.moe/discord')")
- .detail
- span.name {{ $t("view.settings.general.general.support") }}
- span.extra https://vrcx.pypy.moe/discord
- div.options-container
- span.header {{ $t("view.settings.general.vrcx_updater.header") }}
- div.options-container-item
- el-button(size="small" icon="el-icon-document" @click="showChangeLogDialog()") {{ $t("view.settings.general.vrcx_updater.change_log") }}
- el-button(size="small" icon="el-icon-upload" @click="showVRCXUpdateDialog()") {{ $t("view.settings.general.vrcx_updater.change_build") }}
- div.options-container-item
- span.name {{ $t("view.settings.general.vrcx_updater.auto_update") }}
- br
- el-radio-group(v-model="autoUpdateVRCX" @change="saveAutoUpdateVRCX" size="mini")
- el-radio-button(label="Off") {{ $t("view.settings.general.vrcx_updater.auto_update_off") }}
- el-radio-button(label="Notify") {{ $t("view.settings.general.vrcx_updater.auto_update_notify") }}
- el-radio-button(label="Auto Download") {{ $t("view.settings.general.vrcx_updater.auto_update_download") }}
- el-radio-button(label="Auto Install") {{ $t("view.settings.general.vrcx_updater.auto_update_install") }}
- div.options-container
- span.header {{ $t("view.settings.general.application.header") }}
- div.options-container-item
- span.name {{ $t("view.settings.general.application.startup") }}
- el-switch(v-model="isStartAtWindowsStartup")
- div.options-container-item
- span.name {{ $t("view.settings.general.application.minimized") }}
- el-switch(v-model="isStartAsMinimizedState")
- div.options-container-item
- span.name {{ $t("view.settings.general.application.tray") }}
- el-switch(v-model="isCloseToTray")
- div.options-container
- div.options-container
- span.header {{ $t("view.settings.general.game_log.header") }}
- div.options-container-item
- span.name {{ $t("view.settings.general.game_log.resource_load") }}
- el-switch(v-model="logResourceLoad" @change="saveGameLogOptions")
- div.options-container
- div.options-container(style="margin-top:45px;border-top:1px solid #eee;padding-top:30px")
- span.header {{ $t("view.settings.general.legal_notice.header" )}}
- div.options-container-item
- p © 2019-2022 #[a.x-link(@click="openExternalLink('https://github.com/pypy-vrc')") pypy] (mina#5656) & #[a.x-link(@click="openExternalLink('https://github.com/Natsumi-sama')") Natsumi]
- p {{ $t("view.settings.general.legal_notice.info" )}}
- p {{ $t("view.settings.general.legal_notice.disclaimer1" )}}
- p {{ $t("view.settings.general.legal_notice.disclaimer2" )}}
- div.options-container-item
- el-button(@click="ossDialog = true" size="small") {{ $t("view.settings.general.legal_notice.open_source_software_notice" )}}
- el-tab-pane(:label="$t('view.settings.category.appearance')")
- div.options-container(style="margin-top:0")
- span.header {{ $t("view.settings.appearance.appearance.header") }}
- div.options-container-item
- span.name {{ $t('view.settings.appearance.appearance.language') }}
- el-dropdown(@click.native.stop trigger="click" size="small")
- el-button(size="mini")
- span {{ $i18n.messages[appLanguage]?.language }} #[i.el-icon-arrow-down.el-icon--right]
- el-dropdown-menu(#default="dropdown")
- el-dropdown-item(v-for="(obj, language) in $i18n.messages" v-text="obj.language" @click.native="changeAppLanguage(language)")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.appearance.theme_mode') }}
- el-radio-group(v-model="themeMode" size="mini")
- el-radio-button(label="system") {{ $t('view.settings.appearance.appearance.theme_mode_system') }}
- el-radio-button(label="light") {{ $t('view.settings.appearance.appearance.theme_mode_light') }}
- el-radio-button(label="dark") {{ $t('view.settings.appearance.appearance.theme_mode_dark') }}
- div.options-container-item
- span.name {{ $t('view.settings.appearance.appearance.vrcplus_profile_icons') }}
- el-switch(v-model="displayVRCPlusIconsAsAvatar" @change="saveOpenVROption")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.appearance.disable_tooltips') }}
- el-switch(v-model="hideTooltips" @change="saveOpenVROption")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.appearance.sort_favorite_by') }}
- el-switch(v-model="sortFavorites" :inactive-text="$t('view.settings.appearance.appearance.sort_favorite_by_name')" :active-text="$t('view.settings.appearance.appearance.sort_favorite_by_date')" @change="saveSortFavoritesOption")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.appearance.sort_instance_users_by') }}
- el-switch(v-model="instanceUsersSortAlphabetical" :inactive-text="$t('view.settings.appearance.appearance.sort_instance_users_by_time')" :active-text="$t('view.settings.appearance.appearance.sort_instance_users_by_alphabet')" @change="saveOpenVROption")
- div.options-container-item
- el-button(size="small" icon="el-icon-notebook-1" @click="promptMaxTableSizeDialog") {{ $t('view.settings.appearance.appearance.table_max_size') }}
- div.options-container-item
- el-dropdown(@click.native.stop trigger="click" size="small")
- el-button(size="mini")
- span {{ $t('view.settings.appearance.appearance.page_size') }} {{ tablePageSize }} #[i.el-icon-arrow-down.el-icon--right]
- el-dropdown-menu(#default="dropdown")
- el-dropdown-item(v-for="(number) in [10, 15, 25, 50, 100]" v-text="number" @click.native="setTablePageSize(number)")
- div.options-container
- span.header {{ $t('view.settings.appearance.timedate.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.appearance.timedate.time_format') }}
- el-switch(v-model="dtHour12" @change="setDatetimeFormat" :inactive-text="$t('view.settings.appearance.timedate.time_format_24')" :active-text="$t('view.settings.appearance.timedate.time_format_12')")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.timedate.force_iso_date_format') }}
- el-switch(v-model="dtIsoFormat" @change="setDatetimeFormat")
- div.options-container
- span.header {{ $t('view.settings.appearance.side_panel.header') }}
- br
- span.sub-header {{ $t('view.settings.appearance.side_panel.sorting.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_private_to_bottom') }}
- el-switch(v-model="orderFriendsGroupPrivate" @change="saveOrderFriendGroup")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_by_status') }}
- el-switch(v-model="orderFriendsGroupStatus" @change="saveOrderFriendGroup")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_gps_to_top') }}
- el-switch(v-model="orderFriendsGroupGPS" @change="saveOrderFriendGroup")
- span.name(style="margin-left:5px") {{ $t('view.settings.appearance.side_panel.sorting.sort_gps_to_top_notice') }}
- div.options-container-item
- span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_favorite_by') }}
- el-switch(v-model="orderFriendsGroup0" :inactive-text="$t('view.settings.appearance.side_panel.sorting.sort_favorite_by_alphabet')" :active-text="$t('view.settings.appearance.side_panel.sorting.sort_favorite_by_online_time')" @change="saveOrderFriendGroup")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_online_by') }}
- el-switch(v-model="orderFriendsGroup1" :inactive-text="$t('view.settings.appearance.side_panel.sorting.sort_online_by_alphabet')" :active-text="$t('view.settings.appearance.side_panel.sorting.sort_online_by_online_time')" @change="saveOrderFriendGroup")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_active_by') }}
- el-switch(v-model="orderFriendsGroup2" :inactive-text="$t('view.settings.appearance.side_panel.sorting.sort_active_by_alphabet')" :active-text="$t('view.settings.appearance.side_panel.sorting.sort_active_by_online_time')" @change="saveOrderFriendGroup")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_offline_by') }}
- el-switch(v-model="orderFriendsGroup3" :inactive-text="$t('view.settings.appearance.side_panel.sorting.sort_offline_by_alphabet')" :active-text="$t('view.settings.appearance.side_panel.sorting.sort_offline_by_offline_time')" @change="saveOrderFriendGroup")
- span.sub-header {{ $t('view.settings.appearance.side_panel.width') }}
- div.options-container-item
- el-slider(v-model="asideWidth" @input="setAsideWidth" :show-tooltip="false" :marks="{236: ''}" :min="141" :max="500" style="width:300px")
- div.options-container
- span.header {{ $t('view.settings.appearance.user_dialog.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.appearance.user_dialog.hide_vrchat_notes') }}
- el-switch(v-model="hideUserNotes" @change="saveUserDialogOption")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.user_dialog.hide_vrcx_memos') }}
- el-switch(v-model="hideUserMemos" @change="saveUserDialogOption")
- div.options-container-item
- span.name {{ $t('view.settings.appearance.user_dialog.export_vrcx_memos_into_vrchat_notes') }}
- br
- el-button(size="small" icon="el-icon-document-copy" @click="showNoteExportDialog") {{ $t('view.settings.appearance.user_dialog.export_notes') }}
- div.options-container
- span.header {{ $t('view.settings.appearance.user_colors.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.appearance.user_colors.random_colors_from_user_id') }}
- el-switch(v-model="randomUserColours" @change="updatetrustColor")
- div.options-container-item
- div
- el-color-picker(v-model="trustColor.untrusted" @change="updatetrustColor" size="mini" :predefine="['#CCCCCC']")
- span.color-picker(slot="trigger" class="x-tag-untrusted") Visitor
- div
- el-color-picker(v-model="trustColor.basic" @change="updatetrustColor" size="mini" :predefine="['#1778ff']")
- span.color-picker(slot="trigger" class="x-tag-basic") New User
- div
- el-color-picker(v-model="trustColor.known" @change="updatetrustColor" size="mini" :predefine="['#2bcf5c']")
- span.color-picker(slot="trigger" class="x-tag-known") User
- div
- el-color-picker(v-model="trustColor.trusted" @change="updatetrustColor" size="mini" :predefine="['#ff7b42']")
- span.color-picker(slot="trigger" class="x-tag-trusted") Known User
- div
- el-color-picker(v-model="trustColor.veteran" @change="updatetrustColor" size="mini" :predefine="['#b18fff', '#8143e6', '#ff69b4', '#b52626', '#ffd000', '#abcdef']")
- span.color-picker(slot="trigger" class="x-tag-veteran") Trusted User
- div
- el-color-picker(v-model="trustColor.vip" @change="updatetrustColor" size="mini" :predefine="['#ff2626']")
- span.color-picker(slot="trigger" class="x-tag-vip") VRChat Team
- div
- el-color-picker(v-model="trustColor.troll" @change="updatetrustColor" size="mini" :predefine="['#782f2f']")
- span.color-picker(slot="trigger" class="x-tag-troll") Nuisance
- el-tab-pane(:label="$t('view.settings.category.notifications')")
- div.options-container(style="margin-top:0")
- span.header {{ $t('view.settings.notifications.notifications.header') }}
- div.options-container-item
- el-button(size="small" icon="el-icon-chat-square" @click="showNotyFeedFiltersDialog") {{ $t('view.settings.notifications.notifications.notification_filter') }}
- span.sub-header {{ $t('view.settings.notifications.notifications.steamvr_notifications.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.notifications.notifications.steamvr_notifications.steamvr_overlay') }}
- el-switch(v-model="openVR" @change="saveOpenVROption")
- div.options-container-item
- span.name {{ $t('view.settings.notifications.notifications.steamvr_notifications.overlay_notifications') }}
- el-switch(v-model="overlayNotifications" @change="saveOpenVROption" :disabled="!openVR")
- div.options-container-item
- el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog" :disabled="!overlayNotifications || !openVR") {{ $t('view.settings.notifications.notifications.steamvr_notifications.notification_position') }}
- div.options-container-item
- span.name {{ $t('view.settings.notifications.notifications.steamvr_notifications.xsoverlay_notifications') }}
- el-switch(v-model="xsNotifications" @change="saveOpenVROption")
- div.options-container-item
- span.name {{ $t('view.settings.notifications.notifications.steamvr_notifications.user_images') }}
- el-switch(v-model="imageNotifications" @change="saveOpenVROption")
- div.options-container-item
- el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout" :disabled="(!overlayNotifications || !openVR) && !xsNotifications") {{ $t('view.settings.notifications.notifications.steamvr_notifications.notification_timeout') }}
- span.sub-header {{ $t('view.settings.notifications.notifications.desktop_notifications.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display') }}
- br
- el-radio-group(v-model="desktopToast" @change="saveOpenVROption" size="mini")
- el-radio-button(label="Never") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_never') }}
- el-radio-button(label="Desktop Mode") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_desktop') }}
- el-radio-button(label="Inside VR") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_inside_vr') }}
- el-radio-button(label="Outside VR") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_outside_vr') }}
- el-radio-button(label="Game Closed") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_game_closed') }}
- el-radio-button(label="Game Running") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_game_running') }}
- el-radio-button(label="Always") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_always') }}
- br
- span.sub-header {{ $t('view.settings.notifications.notifications.text_to_speech.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play') }}
- br
- el-radio-group(v-model="notificationTTS" @change="saveNotificationTTS" size="mini")
- el-radio-button(label="Never") {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play_never') }}
- el-radio-button(label="Inside VR") {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play_inside_vr') }}
- el-radio-button(label="Game Closed") {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play_game_closed') }}
- el-radio-button(label="Game Running") {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play_game_running') }}
- el-radio-button(label="Always") {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play_always') }}
- div.options-container-item
- span.name {{ $t('view.settings.notifications.notifications.text_to_speech.tts_voice') }}
- el-dropdown(@command="(voice) => changeTTSVoice(voice)" trigger="click" size="small")
- el-button(size="mini" :disabled="notificationTTS === 'Never'")
- span {{ getTTSVoiceName() }} #[i.el-icon-arrow-down.el-icon--right]
- el-dropdown-menu(#default="dropdown")
- el-dropdown-item(v-if="voice" v-for="(voice, index) in TTSvoices" :key="index" v-text="voice.name" :command="index")
- el-tab-pane(:label="$t('view.settings.category.wrist_overlay')")
- div.options-container(style="margin-top:0")
- span.header {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.header') }}
- div.options-container-item
- span {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.description') }}
- br
- br
- span {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.grip') }}
- br
- span {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.menu') }}
- br
- div.options-container-item
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.steamvr_overlay') }}
- el-switch(v-model="openVR" @change="saveOpenVROption")
- div.options-container-item
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.wrist_feed_overlay') }}
- el-switch(v-model="overlayWrist" @change="saveOpenVROption" :disabled="!openVR")
- div.options-container-item
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.hide_private_worlds') }}
- el-switch(v-model="hidePrivateFromFeed" @change="saveOpenVROption")
- div.options-container-item(style="min-width:118px")
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.start_overlay_with') }}
- el-switch(v-model="openVRAlways" @change="saveOpenVROption" inactive-text="VRChat" active-text="SteamVR" :disabled="!openVR")
- div.options-container-item
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.overlay_button') }}
- el-switch(v-model="overlaybutton" @change="saveOpenVROption" :inactive-text="$t('view.settings.wrist_overlay.steamvr_wrist_overlay.overlay_button_grip')" :active-text="$t('view.settings.wrist_overlay.steamvr_wrist_overlay.overlay_button_menu')" :disabled="!openVR || !overlayWrist")
- div.options-container-item
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.display_overlay_on') }}
- el-radio-group(v-model="overlayHand" @change="saveOpenVROption" size="mini")
- el-radio-button(label="1") {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.display_overlay_on_left') }}
- el-radio-button(label="2") {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.display_overlay_on_right') }}
- el-radio-button(label="0") {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.display_overlay_on_both') }}
- div.options-container-item
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.background_color') }}
- el-switch(v-model="vrBackgroundEnabled" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
- div.options-container-item
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.minimal_feed_icons') }}
- el-switch(v-model="minimalFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
- div.options-container-item
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.hide_vr_devices') }}
- el-switch(v-model="hideDevicesFromFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
- div.options-container-item
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.hide_cpu_usage') }}
- el-switch(v-model="hideCpuUsageFromFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
- div.options-container-item
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.hide_game_uptime') }}
- el-switch(v-model="hideUptimeFromFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
- div.options-container-item
- span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.show_pc_uptime') }}
- el-switch(v-model="pcUptimeOnFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
- div.options-container-item
- el-button(size="small" icon="el-icon-notebook-2" @click="showWristFeedFiltersDialog" :disabled="!openVR || !overlayWrist") {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.wrist_feed_filters') }}
- el-tab-pane(:label="$t('view.settings.category.discord_presence')")
- div.options-container(style="margin-top:0")
- span.header {{ $t('view.settings.discord_presence.discord_presence.header') }}
- div.options-container-item
- span {{ $t('view.settings.discord_presence.discord_presence.description') }}
- div.options-container-item
- span.name {{ $t('view.settings.discord_presence.discord_presence.enable') }}
- el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.discord_presence.discord_presence.enable_tooltip')")
- i.el-icon-warning(style="cursor:pointer" @click="showVRChatConfig")
- el-switch(v-model="discordActive" @change="saveDiscordOption")
- div.options-container-item
- span.name {{ $t('view.settings.discord_presence.discord_presence.instance_type_player_count') }}
- el-switch(v-model="discordInstance" @change="saveDiscordOption" :disabled="!discordActive")
- div.options-container-item
- span.name {{ $t('view.settings.discord_presence.discord_presence.join_button') }}
- el-switch(v-model="discordJoinButton" @change="saveDiscordOption" :disabled="!discordActive")
- div.options-container-item
- span.name {{ $t('view.settings.discord_presence.discord_presence.hide_details_in_private') }}
- el-switch(v-model="discordHideInvite" @change="saveDiscordOption" :disabled="!discordActive")
- div.options-container-item
- span.name {{ $t('view.settings.discord_presence.discord_presence.hide_images') }}
- el-switch(v-model="discordHideImage" @change="saveDiscordOption" :disabled="!discordActive")
- el-tab-pane(:label="$t('view.settings.category.advanced')")
- div.options-container(style="margin-top:0")
- span.header {{ $t('view.settings.advanced.advanced.header') }}
- div.options-container-item
- el-button-group
- el-button(size="small" icon="el-icon-s-operation" @click="showVRChatConfig()") VRChat config.json
- el-button(size="small" icon="el-icon-s-operation" @click="showLaunchOptions()") {{ $t('view.settings.advanced.advanced.launch_options') }}
- el-button(size="small" icon="el-icon-picture" @click="showScreenshotMetadataDialog()") {{ $t('view.settings.advanced.advanced.screenshot_metadata') }}
- el-button(size="small" icon="el-icon-folder" @click="openShortcutFolder()") {{ $t('view.settings.advanced.advanced.auto_launch') }}
- el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.advanced.auto_launch_tooltip')")
- i.el-icon-warning
- div.options-container
- span.sub-header {{ $t('view.settings.advanced.advanced.primary_password.header') }}
- div.options-container-item
- span.name(style="min-width:300px") {{ $t('view.settings.advanced.advanced.primary_password.description') }}
- el-switch(v-model="enablePrimaryPassword" @change="enablePrimaryPasswordChange" :disabled="!loginForm.savedCredentials[API.currentUser.id]")
- span.sub-header {{ $t('view.settings.advanced.advanced.relaunch_vrchat.header') }}
- div.options-container-item
- span.name(style="min-width:300px") {{ $t('view.settings.advanced.advanced.relaunch_vrchat.description') }}
- el-switch(v-model="relaunchVRChatAfterCrash" @change="saveOpenVROption")
- span.sub-header {{ $t('view.settings.advanced.advanced.vrchat_quit_fix.header') }}
- div.options-container-item
- span.name(style="min-width:300px") {{ $t('view.settings.advanced.advanced.vrchat_quit_fix.description') }}
- el-switch(v-model="vrcQuitFix" @change="saveOpenVROption")
- span.sub-header {{ $t('view.settings.advanced.advanced.auto_cache_management.header') }}
- div.options-container-item
- span.name(style="min-width:300px") {{ $t('view.settings.advanced.advanced.auto_cache_management.description') }}
- el-switch(v-model="autoSweepVRChatCache" @change="saveOpenVROption")
- div.options-container
- span.header {{ $t('view.settings.advanced.advanced.remote_database.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.remote_database.enable') }}
- el-switch(v-model="avatarRemoteDatabase" @change="saveOpenVROption")
- div.options-container-item
- el-button(size="small" icon="el-icon-user-solid" @click="showAvatarProviderDialog") {{ $t('view.settings.advanced.advanced.remote_database.avatar_database_provider') }}
- div.options-container
- span.header {{ $t('view.settings.advanced.advanced.youtube_api.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.youtube_api.enable') }}
- el-switch(v-model="youTubeApi" @change="changeYouTubeApi")
- div.options-container-item
- el-button(size="small" icon="el-icon-caret-right" @click="showYouTubeApiDialog") {{ $t('view.settings.advanced.advanced.youtube_api.youtube_api_key') }}
- span.header {{ $t('view.settings.advanced.advanced.video_progress_pie.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.video_progress_pie.enable') }}
- el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.advanced.video_progress_pie.enable_tooltip')")
- i.el-icon-warning
- el-switch(v-model="progressPie" @change="changeYouTubeApi" :disabled="!openVR")
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.video_progress_pie.dance_world_only') }}
- el-switch(v-model="progressPieFilter" @change="changeYouTubeApi" :disabled="!openVR")
- div.options-container
- span.header {{ $t('view.settings.advanced.advanced.screenshot_helper.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.screenshot_helper.description') }}
- el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.advanced.screenshot_helper.description_tooltip')")
- i.el-icon-info
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.screenshot_helper.enable') }}
- el-switch(v-model="screenshotHelper" @change="saveScreenshotHelper")
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.screenshot_helper.modify_filename') }}
- el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.advanced.screenshot_helper.modify_filename_tooltip')")
- i.el-icon-info
- el-switch(v-model="screenshotHelperModifyFilename" @change="saveScreenshotHelper")
- div.options-container(v-if="photonLoggingEnabled")
- span.header {{ $t('view.settings.advanced.photon.header') }}
- div.options-container-item
- span.sub-header {{ $t('view.settings.advanced.photon.event_hud.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.advanced.photon.event_hud.enable') }}
- el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.photon.event_hud.enable_tooltip')")
- i.el-icon-warning
- el-switch(v-model="photonEventOverlay" @change="saveEventOverlay" :disabled="!openVR")
- div.options-container-item
- span.name {{ $t('view.settings.advanced.photon.event_hud.filter') }}
- el-radio-group(v-model="photonEventOverlayFilter" @change="saveEventOverlay" size="mini" :disabled="!openVR || !photonEventOverlay")
- el-radio-button(label="VIP") {{ $t('view.settings.advanced.photon.event_hud.filter_favorites') }}
- el-radio-button(label="Friends") {{ $t('view.settings.advanced.photon.event_hud.filter_friends') }}
- el-radio-button(label="Everyone") {{ $t('view.settings.advanced.photon.event_hud.filter_everyone') }}
- div.options-container-item
- el-button(size="small" icon="el-icon-time" @click="promptPhotonOverlayMessageTimeout" :disabled="!openVR") {{ $t('view.settings.advanced.photon.event_hud.message_timeout') }}
- div.options-container-item
- el-select(v-model="photonEventTableTypeOverlayFilter" @change="photonEventTableFilterChange" multiple clearable collapse-tags style="flex:1" placeholder="Filter")
- el-option(v-once v-for="type in photonEventTableTypeFilterList" :key="type" :label="type" :value="type")
- br
- span.sub-header {{ $t('view.settings.advanced.photon.timeout_hud.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.advanced.photon.timeout_hud.enable') }}
- el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.photon.timeout_hud.enable_tooltip')")
- i.el-icon-warning
- el-switch(v-model="timeoutHudOverlay" @change="saveEventOverlay" :disabled="!openVR")
- div.options-container-item
- span.name {{ $t('view.settings.advanced.photon.timeout_hud.filter') }}
- el-radio-group(v-model="timeoutHudOverlayFilter" @change="saveEventOverlay" size="mini" :disabled="!openVR || !timeoutHudOverlay")
- el-radio-button(label="VIP") {{ $t('view.settings.advanced.photon.timeout_hud.filter_favorites') }}
- el-radio-button(label="Friends") {{ $t('view.settings.advanced.photon.timeout_hud.filter_friends') }}
- el-radio-button(label="Everyone") {{ $t('view.settings.advanced.photon.timeout_hud.filter_everyone') }}
- div.options-container-item
- el-button(size="small" icon="el-icon-time" @click="promptPhotonLobbyTimeoutThreshold" :disabled="!openVR") {{ $t('view.settings.advanced.photon.timeout_hud.timeout_threshold') }}
- div.options-container
- span.header {{ $t('view.settings.advanced.advanced.cache_debug.header') }}
- br
- span.sub-header {{ $t('view.settings.advanced.advanced.pending_offline.header') }}
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.pending_offline.description') }}
- el-button-group(style="display:block")
- el-button(size="small" icon="el-icon-s-operation" @click="promptSetPendingOffline") {{ $t('view.settings.advanced.advanced.pending_offline.set_delay') }}
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.cache_debug.disable_gamelog') }}
- el-switch(v-model="gameLogDisabled" @change="disableGameLogDialog")
- span.name(style="margin-left:15px") {{ $t('view.settings.advanced.advanced.cache_debug.disable_gamelog_notice') }}
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.cache_debug.user_cache') }} #[span(v-text="API.cachedUsers.size")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.cache_debug.world_cache') }} #[span(v-text="API.cachedWorlds.size")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.cache_debug.avatar_cache') }} #[span(v-text="API.cachedAvatars.size")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.cache_debug.group_cache') }} #[span(v-text="API.cachedGroups.size")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.cache_debug.avatar_name_cache') }} #[span(v-text="API.cachedAvatarNames.size")]
- div.options-container-item
- el-button(size="small" icon="el-icon-delete-solid" @click="clearVRCXCache") {{ $t('view.settings.advanced.advanced.cache_debug.clear_cache') }}
- el-button(size="small" icon="el-icon-time" @click="promptAutoClearVRCXCacheFrequency") {{ $t('view.settings.advanced.advanced.cache_debug.auto_clear_cache') }}
- div.options-container-item
- el-button(size="small" icon="el-icon-download" @click="showDownloadDialog") {{ $t('view.settings.advanced.advanced.cache_debug.download_history') }}
- el-button(size="small" icon="el-icon-tickets" @click="showConsole") {{ $t('view.settings.advanced.advanced.cache_debug.show_console') }}
- div.options-container
- span.sub-header {{ $t('view.settings.advanced.advanced.sqlite_table_size.header') }}
- div.options-container-item
- el-button(size="small" icon="el-icon-refresh" @click="getSqliteTableSizes") {{ $t('view.settings.advanced.advanced.sqlite_table_size.refresh') }}
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.gps') }} #[span(v-text="sqliteTableSizes.gps")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.status') }} #[span(v-text="sqliteTableSizes.status")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.bio') }} #[span(v-text="sqliteTableSizes.bio")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.avatar') }} #[span(v-text="sqliteTableSizes.avatar")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.online_offline') }} #[span(v-text="sqliteTableSizes.onlineOffline")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.friend_log_history') }} #[span(v-text="sqliteTableSizes.friendLogHistory")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.notification') }} #[span(v-text="sqliteTableSizes.notification")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.location') }} #[span(v-text="sqliteTableSizes.location")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.join_leave') }} #[span(v-text="sqliteTableSizes.joinLeave")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.portal_spawn') }} #[span(v-text="sqliteTableSizes.portalSpawn")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.video_play') }} #[span(v-text="sqliteTableSizes.videoPlay")]
- div.options-container-item
- span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.event') }} #[span(v-text="sqliteTableSizes.event")]
+ include ./mixins/tabs/settings.pug
+ +settingsTab()
- //- friends
+ //- friends list sidebar
.x-aside-container(v-show="$refs.menu && $refs.menu.activeIndex !== 'friendsList'" id="aside")
div(style="display:flex;align-items:baseline")
el-select(v-model="quickSearch" clearable :placeholder="$t('side_panel.search_placeholder')" filterable remote :remote-method="quickSearchRemoteMethod" popper-class="x-quick-search" @change="quickSearchChange" @visible-change="quickSearchVisibleChange" style="flex:1;padding:10px")
@@ -1662,6 +182,8 @@ html
span(v-text="friend.name || friend.id")
el-button(type="text" icon="el-icon-close" size="mini" @click.stop="confirmDeleteFriend(friend.id)" style="margin-left:5px")
+ //- ## Dialogs ## -\\
+
//- dialog: user
el-dialog.x-dialog.x-user-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="userDialog" :visible.sync="userDialog.visible" :show-close="false" width="770px")
div(v-loading="userDialog.loading")
diff --git a/html/src/localization/strings/en.json b/html/src/localization/strings/en.json
index 80352fc0..5cd8398a 100644
--- a/html/src/localization/strings/en.json
+++ b/html/src/localization/strings/en.json
@@ -354,8 +354,6 @@
"header": "Advanced",
"launch_options": "Launch Options",
"screenshot_metadata": "Screenshot Metadata",
- "auto_launch": "Auto-Launch Folder",
- "auto_launch_tooltip": "To auto-launch apps with VRChat, place shortcuts in this folder.",
"pending_offline": {
"header": "Pending Offline",
"description": "Delay before marking user as offline (fixes false positives)",
@@ -401,8 +399,17 @@
"modify_filename": "Modify Filename",
"modify_filename_tooltip": "Will add the World ID to screenshot filename, in addition to file metadata."
},
+ "app_launcher": {
+ "header": "App Launcher",
+ "folder": "Auto-Launch Folder",
+ "folder_tooltip": "To auto-launch apps with VRChat, place shortcuts in this folder.",
+ "enable": "Enable",
+ "auto_close": "Auto close apps"
+ },
"cache_debug": {
"header": "VRCX Instance Cache/Debug",
+ "udon_exception_logging": "Udon Exception Logging",
+ "gpu_fix": "SteamVR Overlay GPU Fix",
"disable_gamelog": "Disable GameLog",
"disable_gamelog_notice": "(will likely break things)",
"user_cache": "User cache:",
diff --git a/html/src/mixins/loginPage.pug b/html/src/mixins/loginPage.pug
new file mode 100644
index 00000000..9f353680
--- /dev/null
+++ b/html/src/mixins/loginPage.pug
@@ -0,0 +1,39 @@
+mixin loginPage()
+ .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")
+ div(style="margin:15px" v-if="Object.keys(loginForm.savedCredentials).length !== 0")
+ h2(style="font-weight:bold;text-align:center;margin:0") {{ $t("view.login.savedAccounts") }}
+ .x-friend-list(style="margin-top:10px")
+ .x-friend-item(v-for="user in loginForm.savedCredentials" :key="user.user.id")
+ .x-friend-item(@click="relogin(user)" style="width:202px;padding:0")
+ .avatar
+ img(v-lazy="userImage(user.user)")
+ .detail
+ span.name(v-text="user.user.displayName")
+ span.extra(v-text="user.user.username")
+ span.extra(v-text="user.loginParmas.endpoint")
+ el-button(type="default" @click="deleteSavedLogin(user.user.id)" size="mini" icon="el-icon-delete" circle)
+ div(style="margin:15px")
+ h2(style="font-weight:bold;text-align:center;margin:0") {{ $t("view.login.login") }}
+ el-form(ref="loginForm" :model="loginForm" :rules="loginForm.rules" @submit.native.prevent="login()")
+ el-form-item(:label="$t('view.login.field.username')" prop="username" required)
+ el-input(v-model="loginForm.username" name="username" :placeholder="$t('view.login.field.username')" clearable)
+ el-form-item(:label="$t('view.login.field.password')" prop="password" required style="margin-top:10px")
+ el-input(type="password" v-model="loginForm.password" name="password" :placeholder="$t('view.login.field.password')" clearable show-password)
+ el-checkbox(v-model="loginForm.saveCredentials" style="margin-top:15px") {{ $t("view.login.field.saveCredentials") }}
+ el-checkbox(v-model="enableCustomEndpoint" @change="toggleCustomEndpoint" style="margin-top:10px") {{ $t("view.login.field.devEndpoint") }}
+ el-form-item(v-if="enableCustomEndpoint" :label="$t('view.login.field.endpoint')" prop="endpoint" style="margin-top:10px")
+ el-input(v-model="loginForm.endpoint" name="endpoint" :placeholder="API.endpointDomainVrchat" clearable)
+ el-form-item(v-if="enableCustomEndpoint" :label="$t('view.login.field.websocket')" prop="endpoint" style="margin-top:10px")
+ el-input(v-model="loginForm.websocket" name="websocket" :placeholder="API.websocketDomainVrchat" clearable)
+ el-form-item(style="margin-top:15px")
+ el-button(native-type="submit" type="primary" :loading="loginForm.loading" style="width:100%") {{ $t("view.login.login") }}
+ el-button(type="primary" @click="openExternalLink('https://vrchat.com/register')" :loading="loginForm.loading" style="width:100%") {{ $t("view.login.register") }}
+ div(style="text-align:center;font-size:12px")
+ p #[a.x-link(@click="openExternalLink('https://vrchat.com/home/password')") {{ $t("view.login.forgotPassword") }}]
+ p © 2019-2022 #[a.x-link(@click="openExternalLink('https://github.com/pypy-vrc')") pypy] (mina#5656) & #[a.x-link(@click="openExternalLink('https://github.com/Natsumi-sama')") Natsumi]
+ p {{ $t("view.settings.general.legal_notice.info") }}
+ p {{ $t("view.settings.general.legal_notice.disclaimer1") }}
+ p {{ $t("view.settings.general.legal_notice.disclaimer2") }}
\ No newline at end of file
diff --git a/html/src/mixins/tabs/favorites.pug b/html/src/mixins/tabs/favorites.pug
new file mode 100644
index 00000000..9a24e945
--- /dev/null
+++ b/html/src/mixins/tabs/favorites.pug
@@ -0,0 +1,156 @@
+mixin favoritesTab()
+ .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'favorite'" v-if="$refs.menu && $refs.menu.activeIndex === 'favorite'")
+ el-tooltip(placement="bottom" :content="$t('view.favorite.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" :loading="API.isFavoriteLoading" @click="API.refreshFavorites(); getLocalWorldFavorites()" size="small" icon="el-icon-refresh" circle style="position:relative;float:right;z-index:1")
+ el-tabs(ref="favoriteTabRef" type="card" v-loading="API.isFavoriteLoading")
+ el-tab-pane(:label="$t('view.favorite.friends.header')")
+ el-collapse(v-if="$refs.menu && $refs.menu.activeIndex === 'favorite' && $refs.favoriteTabRef && $refs.favoriteTabRef.currentName === '0'" style="border:0")
+ el-button(size="small" @click="showFriendExportDialog") {{ $t('view.favorite.export') }}
+ el-button(size="small" @click="showFriendImportDialog") {{ $t('view.favorite.import') }}
+ el-collapse-item(v-for="group in API.favoriteFriendGroups" :key="group.name")
+ template(slot="title")
+ span(v-text="group.displayName" style="font-weight:bold;font-size:14px;margin-left:10px")
+ span(style="color:#909399;font-size:12px;margin-left:10px") {{ group.count }}/{{ group.capacity }}
+ el-tooltip(placement="top" :content="$t('view.favorite.rename_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="changeFavoriteGroupName(group)" size="mini" icon="el-icon-edit" circle style="margin-left:10px")
+ el-tooltip(placement="right" :content="$t('view.favorite.clear_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="clearFavoriteGroup(group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ .x-friend-list(v-if="group.count" style="margin-top:10px")
+ div(style="display:inline-block;width:300px;margin-right:15px" v-for="favorite in favoriteFriends" v-if="favorite.groupKey === group.key" :key="favorite.id" @click="showUserDialog(favorite.id)")
+ .x-friend-item
+ template(v-if="favorite.ref")
+ .avatar(:class="userStatusClass(favorite.ref)")
+ img(v-lazy="userImage(favorite.ref)")
+ .detail
+ span.name(v-text="favorite.ref.displayName" :style="{'color':favorite.ref.$userColour}")
+ location.extra(v-if="favorite.ref.location !== 'offline'" :location="favorite.ref.location" :traveling="favorite.ref.travelingToLocation" :link="false")
+ span(v-else v-text="favorite.ref.statusDescription")
+ el-tooltip(placement="left" :content="$t('view.favorite.move_tooltip')" :disabled="hideTooltips")
+ el-dropdown(trigger="click" @click.native.stop size="mini" style="margin-left:5px")
+ el-button(type="default" icon="el-icon-back" size="mini" circle)
+ el-dropdown-menu(#default="dropdown")
+ template(v-if="groupAPI.name !== group.name" v-for="groupAPI in API.favoriteFriendGroups" :key="groupAPI.name")
+ el-dropdown-item(style="display:block;margin:10px 0" @click.native="moveFavorite(favorite.ref, groupAPI, 'friend')" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }} / {{ groupAPI.capacity }})
+ el-tooltip(placement="right" :content="$t('view.favorite.unfavorite_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="deleteFavorite(favorite.id)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ template(v-else)
+ span(v-text="favorite.name || favorite.id")
+ el-button(type="text" icon="el-icon-close" size="mini" @click.stop="deleteFavorite(favorite.id)" style="margin-left:5px")
+ el-tab-pane(:label="$t('view.favorite.worlds.header')")
+ el-collapse(v-if="$refs.menu && $refs.menu.activeIndex === 'favorite' && $refs.favoriteTabRef && $refs.favoriteTabRef.currentName === '1'" style="border:0")
+ el-button(size="small" @click="showWorldExportDialog") {{ $t('view.favorite.export') }}
+ el-button(size="small" @click="showWorldImportDialog") {{ $t('view.favorite.import') }}
+ span(style="display:block;margin-top:20px") {{ $t('view.favorite.worlds.vrchat_favorites') }}
+ el-collapse-item(v-for="group in API.favoriteWorldGroups" :key="group.name")
+ template(slot="title")
+ span(v-text="group.displayName" style="font-weight:bold;font-size:14px;margin-left:10px")
+ i.x-user-status(style="margin-left:5px" :class="userFavoriteWorldsStatus(group.visibility)")
+ span(style="color:#909399;font-size:12px;margin-left:10px") {{ group.count }}/{{ group.capacity }}
+ el-tooltip(placement="top" :content="$t('view.favorite.visibility_tooltip')" :disabled="hideTooltips")
+ el-dropdown(trigger="click" @click.native.stop size="mini" style="margin-left:10px")
+ el-button(type="default" icon="el-icon-view" size="mini" circle)
+ el-dropdown-menu(#default="dropdown")
+ el-dropdown-item(v-if="group.visibility !== visibility" v-for="visibility in worldGroupVisibilityOptions" :key="visibility" style="display:block;margin:10px 0" v-text="visibility" @click.native="changeWorldGroupVisibility(group.name, visibility)")
+ el-tooltip(placement="top" :content="$t('view.favorite.rename_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="changeFavoriteGroupName(group)" size="mini" icon="el-icon-edit" circle style="margin-left:5px")
+ el-tooltip(placement="right" :content="$t('view.favorite.clear_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="clearFavoriteGroup(group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ .x-friend-list(v-if="group.count" style="margin-top:10px")
+ div(style="display:inline-block;width:300px;margin-right:15px" v-for="favorite in favoriteWorlds" v-if="favorite.groupKey === group.key" :key="favorite.id" @click="showWorldDialog(favorite.id)")
+ .x-friend-item
+ template(v-if="favorite.ref")
+ .avatar
+ img(v-lazy="favorite.ref.thumbnailImageUrl")
+ .detail
+ span.name(v-text="favorite.ref.name")
+ span.extra(v-if="favorite.ref.occupants") {{ favorite.ref.authorName }} ({{ favorite.ref.occupants }})
+ span.extra(v-else v-text="favorite.ref.authorName")
+ el-tooltip(placement="left" :content="$t('view.favorite.move_tooltip')" :disabled="hideTooltips")
+ el-dropdown(trigger="click" @click.native.stop size="mini" style="margin-left:5px")
+ el-button(type="default" icon="el-icon-back" size="mini" circle)
+ el-dropdown-menu(#default="dropdown")
+ template(v-if="groupAPI.name !== group.name" v-for="groupAPI in API.favoriteWorldGroups" :key="groupAPI.name")
+ el-dropdown-item(style="display:block;margin:10px 0" @click.native="moveFavorite(favorite.ref, groupAPI, 'world')" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }} / {{ groupAPI.capacity }})
+ el-tooltip(placement="right" :content="$t('view.favorite.unfavorite_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="deleteFavorite(favorite.id)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ template(v-else)
+ span(v-text="favorite.name || favorite.id")
+ el-button(type="text" icon="el-icon-close" size="mini" @click.stop="deleteFavorite(favorite.id)" style="margin-left:5px")
+ span(style="display:block;margin-top:20px") {{ $t('view.favorite.worlds.local_favorites') }}
+ el-button(size="small" @click="promptNewLocalWorldFavoriteGroup" style="display:block;margin-top:10px") {{ $t('view.favorite.worlds.new_group') }}
+ el-collapse-item(v-for="group in localWorldFavoriteGroups" v-if="localWorldFavorites[group]" :key="group")
+ template(slot="title")
+ span(v-text="group" style="font-weight:bold;font-size:14px;margin-left:10px")
+ span(style="color:#909399;font-size:12px;margin-left:10px") {{ getLocalWorldFavoriteGroupLength(group) }}
+ el-tooltip(placement="top" :content="$t('view.favorite.rename_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="promptLocalWorldFavoriteGroupRename(group)" size="mini" icon="el-icon-edit" circle style="margin-left:10px")
+ el-tooltip(placement="right" :content="$t('view.favorite.delete_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="promptLocalWorldFavoriteGroupDelete(group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ .x-friend-list(style="margin-top:10px")
+ div(style="display:inline-block;width:300px;margin-right:15px" v-for="favorite in localWorldFavorites[group]" :key="favorite.id" @click="showWorldDialog(favorite.id)")
+ .x-friend-item
+ template(v-if="favorite.name")
+ .avatar
+ img(v-lazy="favorite.thumbnailImageUrl")
+ .detail
+ span.name(v-text="favorite.name")
+ span.extra(v-if="favorite.occupants") {{ favorite.authorName }} ({{ favorite.occupants }})
+ span.extra(v-else v-text="favorite.authorName")
+ el-tooltip(placement="right" :content="$t('view.favorite.unfavorite_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="removeLocalWorldFavorite(favorite.id, group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ template(v-else)
+ span(v-text="favorite.id")
+ el-button(type="text" icon="el-icon-close" size="mini" @click.stop="removeLocalWorldFavorite(favorite.id, group)" style="margin-left:5px")
+ el-tab-pane(:label="$t('view.favorite.avatars.header')")
+ el-collapse(v-if="$refs.menu && $refs.menu.activeIndex === 'favorite' && $refs.favoriteTabRef && $refs.favoriteTabRef.currentName === '2'" style="border:0")
+ el-button(size="small" @click="showAvatarExportDialog") {{ $t('view.favorite.export') }}
+ el-button(size="small" @click="showAvatarImportDialog") {{ $t('view.favorite.import') }}
+ el-collapse-item(v-for="group in API.favoriteAvatarGroups" :key="group.name")
+ template(slot="title")
+ span(v-text="group.displayName" style="font-weight:bold;font-size:14px;margin-left:10px")
+ span(style="color:#909399;font-size:12px;margin-left:10px") {{ group.count }}/{{ group.capacity }}
+ el-tooltip(placement="top" :content="$t('view.favorite.rename_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="changeFavoriteGroupName(group)" size="mini" icon="el-icon-edit" circle style="margin-left:10px")
+ el-tooltip(placement="right" :content="$t('view.favorite.clear_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="clearFavoriteGroup(group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ .x-friend-list(v-if="group.count" style="margin-top:10px")
+ div(style="display:inline-block;width:300px;margin-right:15px" v-for="favorite in favoriteAvatars" v-if="favorite.groupKey === group.key" :key="favorite.id" @click="showAvatarDialog(favorite.id)")
+ .x-friend-item
+ template(v-if="favorite.ref")
+ .avatar
+ img(v-lazy="favorite.ref.thumbnailImageUrl")
+ .detail
+ span.name(v-text="favorite.ref.name")
+ span.extra(v-text="favorite.ref.authorName")
+ el-tooltip(placement="left" :content="$t('view.favorite.move_tooltip')" :disabled="hideTooltips")
+ el-dropdown(trigger="click" @click.native.stop size="mini" style="margin-left:5px")
+ el-button(type="default" icon="el-icon-back" size="mini" circle)
+ el-dropdown-menu(#default="dropdown")
+ template(v-if="groupAPI.name !== group.name" v-for="groupAPI in API.favoriteAvatarGroups" :key="groupAPI.name")
+ el-dropdown-item(style="display:block;margin:10px 0" @click.native="moveFavorite(favorite.ref, groupAPI, 'avatar')" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }} / {{ groupAPI.capacity }})
+ el-tooltip(placement="right" :content="$t('view.favorite.unfavorite_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="deleteFavorite(favorite.id)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ template(v-else)
+ .detail
+ span.name(v-text="favorite.name || favorite.id")
+ el-button(type="text" icon="el-icon-close" size="mini" @click.stop="deleteFavorite(favorite.id)" style="margin-left:5px")
+ el-collapse-item
+ template(slot="title")
+ span(style="font-weight:bold;font-size:14px;margin-left:10px") Local History
+ span(style="color:#909399;font-size:12px;margin-left:10px") {{ avatarHistoryArray.length }}/100
+ el-tooltip(placement="right" content="Clear" :disabled="hideTooltips")
+ el-button(@click.stop="promptClearAvatarHistory" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ .x-friend-list(v-if="avatarHistoryArray.length" style="margin-top:10px")
+ div(style="display:inline-block;width:300px;margin-right:15px" v-for="favorite in avatarHistoryArray" :key="favorite.id" @click="showAvatarDialog(favorite.id)")
+ .x-friend-item
+ .avatar
+ img(v-lazy="favorite.thumbnailImageUrl")
+ .detail
+ span.name(v-text="favorite.name")
+ span.extra(v-text="favorite.authorName")
+ template(v-if="API.cachedFavoritesByObjectId.has(favorite.id)")
+ el-tooltip(placement="left" content="Unfavorite" :disabled="hideTooltips")
+ el-button(@click.stop="deleteFavorite(favorite.id)" type="default" icon="el-icon-star-on" size="mini" circle)
+ template(v-else)
+ el-tooltip(placement="left" content="Favorite" :disabled="hideTooltips")
+ el-button(@click.stop="showFavoriteDialog('avatar', favorite.id)" type="default" icon="el-icon-star-off" size="mini" circle)
diff --git a/html/src/mixins/tabs/feed.pug b/html/src/mixins/tabs/feed.pug
new file mode 100644
index 00000000..4a08fca9
--- /dev/null
+++ b/html/src/mixins/tabs/feed.pug
@@ -0,0 +1,116 @@
+mixin feedTab()
+ .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'feed'")
+ data-tables(v-bind="feedTable" v-loading="feedTable.loading")
+ template(#tool)
+ div(style="margin:0 0 10px;display:flex;align-items:center")
+ div(style="flex:none;margin-right:10px")
+ el-tooltip(placement="bottom" :content="$t('view.feed.favorites_only_tooltip')" :disabled="hideTooltips")
+ el-switch(v-model="feedTable.vip" @change="feedTableLookup" active-color="#13ce66")
+ el-select(v-model="feedTable.filter" @change="feedTableLookup" multiple clearable collapse-tags style="flex:1" :placeholder="$t('view.feed.filter_placeholder')")
+ el-option(v-once v-for="type in ['GPS', 'Online', 'Offline', 'Status', 'Avatar', 'Bio']" :key="type" :label="type" :value="type")
+ el-input(v-model="feedTable.search" :placeholder="$t('view.feed.search_placeholder')" @keyup.native.13="feedTableLookup" @change="feedTableLookup" clearable style="flex:none;width:150px;margin:0 10px")
+ //- el-tooltip(placement="bottom" content="Clear feed" :disabled="hideTooltips")
+ //- el-button(type="default" @click="clearFeed()" icon="el-icon-delete" circle style="flex:none")
+ el-table-column(type="expand" width="20")
+ template(v-once #default="scope")
+ div(style="position:relative;font-size:14px")
+ template(v-if="scope.row.type === 'GPS'")
+ location(v-if="scope.row.previousLocation" :location="scope.row.previousLocation")
+ el-tag(type="info" effect="plain" size="mini" style="margin-left:5px") {{ scope.row.time | timeToText }}
+ br
+ span
+ i.el-icon-right
+ location(v-if="scope.row.location" :location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
+ template(v-else-if="scope.row.type === 'Offline'")
+ template(v-if="scope.row.location")
+ location(:location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
+ el-tag(type="info" effect="plain" size="mini" style="margin-left:5px") {{ scope.row.time | timeToText }}
+ template(v-else-if="scope.row.type === 'Online'")
+ location(v-if="scope.row.location" :location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
+ template(v-else-if="scope.row.type === 'Avatar'")
+ el-popover(placement="right" width="500px" trigger="click")
+ img.x-link(slot="reference" v-lazy="scope.row.previousCurrentAvatarThumbnailImageUrl" style="flex:none;width:160px;height:120px;border-radius:4px")
+ img.x-link(v-lazy="scope.row.previousCurrentAvatarImageUrl" style="width:500px;height:375px" @click="showAvatarAuthorDialog(scope.row.userId, '', scope.row.previousCurrentAvatarImageUrl)")
+ span(style="position:relative;top:-50px;margin:0 5px")
+ i.el-icon-right
+ el-popover(placement="right" width="500px" trigger="click")
+ img.x-link(slot="reference" v-lazy="scope.row.currentAvatarThumbnailImageUrl" style="flex:none;width:160px;height:120px;border-radius:4px")
+ img.x-link(v-lazy="scope.row.currentAvatarImageUrl" style="width:500px;height:375px" @click="showAvatarAuthorDialog(scope.row.userId, '', scope.row.currentAvatarImageUrl)")
+ template(v-else-if="scope.row.type === 'Status'")
+ el-tooltip(placement="top")
+ template(#content)
+ span(v-if="scope.row.previousStatus === 'active'") {{ $t('dialog.user.status.active') }}
+ span(v-else-if="scope.row.previousStatus === 'join me'") {{ $t('dialog.user.status.join_me') }}
+ span(v-else-if="scope.row.previousStatus === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
+ span(v-else-if="scope.row.previousStatus === 'busy'") {{ $t('dialog.user.status.busy') }}
+ span(v-else) {{ $t('dialog.user.status.offline') }}
+ i.x-user-status(:class="statusClass(scope.row.previousStatus)")
+ span(v-text="scope.row.previousStatusDescription")
+ br
+ span
+ i.el-icon-right
+ el-tooltip(placement="top")
+ template(#content)
+ span(v-if="scope.row.status === 'active'") {{ $t('dialog.user.status.active') }}
+ span(v-else-if="scope.row.status === 'join me'") {{ $t('dialog.user.status.join_me') }}
+ span(v-else-if="scope.row.status === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
+ span(v-else-if="scope.row.status === 'busy'") {{ $t('dialog.user.status.busy') }}
+ span(v-else) {{ $t('dialog.user.status.offline') }}
+ i.x-user-status(:class="statusClass(scope.row.status)")
+ span(v-text="scope.row.statusDescription")
+ template(v-else-if="scope.row.type === 'Bio'")
+ pre(v-text="scope.row.previousBio" style="font-family:inherit;font-size:12px;white-space:pre-wrap;margin:0 0.5em 0 0")
+ span
+ i.el-icon-right
+ pre(v-text="scope.row.bio" style="font-family:inherit;font-size:12px;white-space:pre-wrap;margin:0 0.5em 0 0")
+ el-table-column(:label="$t('table.feed.date')" prop="created_at" sortable="custom" width="120")
+ template(v-once #default="scope")
+ el-tooltip(placement="right")
+ template(#content)
+ span {{ scope.row.created_at | formatDate('long') }}
+ span {{ scope.row.created_at | formatDate('short') }}
+ el-table-column(:label="$t('table.feed.type')" prop="type" width="70")
+ el-table-column(:label="$t('table.feed.user')" prop="displayName" width="180")
+ template(v-once #default="scope")
+ span.x-link(v-text="scope.row.displayName" @click="showUserDialog(scope.row.userId)")
+ el-table-column(:label="$t('table.feed.detail')")
+ template(v-once #default="scope")
+ template(v-if="scope.row.type === 'GPS'")
+ location(v-if="scope.row.location" :location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
+ template(v-else-if="scope.row.type === 'Offline' || scope.row.type === 'Online'")
+ location(v-if="scope.row.location" :location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
+ template(v-else-if="scope.row.type === 'Status'")
+ template(v-if="scope.row.statusDescription === scope.row.previousStatusDescription")
+ el-tooltip(placement="top")
+ template(#content)
+ span(v-if="scope.row.previousStatus === 'active'") {{ $t('dialog.user.status.active') }}
+ span(v-else-if="scope.row.previousStatus === 'join me'") {{ $t('dialog.user.status.join_me') }}
+ span(v-else-if="scope.row.previousStatus === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
+ span(v-else-if="scope.row.previousStatus === 'busy'") {{ $t('dialog.user.status.busy') }}
+ span(v-else) {{ $t('dialog.user.status.offline') }}
+ i.x-user-status(:class="statusClass(scope.row.previousStatus)")
+ span
+ i.el-icon-right
+ el-tooltip(placement="top")
+ template(#content)
+ span(v-if="scope.row.status === 'active'") {{ $t('dialog.user.status.active') }}
+ span(v-else-if="scope.row.status === 'join me'") {{ $t('dialog.user.status.join_me') }}
+ span(v-else-if="scope.row.status === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
+ span(v-else-if="scope.row.status === 'busy'") {{ $t('dialog.user.status.busy') }}
+ span(v-else) {{ $t('dialog.user.status.offline') }}
+ i.x-user-status(:class="statusClass(scope.row.status)")
+ template(v-else)
+ el-tooltip(placement="top")
+ template(#content)
+ span(v-if="scope.row.status === 'active'") {{ $t('dialog.user.status.active') }}
+ span(v-else-if="scope.row.status === 'join me'") {{ $t('dialog.user.status.join_me') }}
+ span(v-else-if="scope.row.status === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
+ span(v-else-if="scope.row.status === 'busy'") {{ $t('dialog.user.status.busy') }}
+ span(v-else) {{ $t('dialog.user.status.offline') }}
+ i.x-user-status(:class="statusClass(scope.row.status)")
+ span
+ span(v-text="scope.row.statusDescription")
+ template(v-else-if="scope.row.type === 'Avatar'")
+ avatar-info(:imageurl="scope.row.currentAvatarImageUrl" :userid="scope.row.userId" :hintownerid="scope.row.ownerId" :hintavatarname="scope.row.avatarName")
+ template(v-else-if="scope.row.type === 'Bio'")
+ span(v-text="scope.row.bio")
diff --git a/html/src/mixins/tabs/friendLog.pug b/html/src/mixins/tabs/friendLog.pug
new file mode 100644
index 00000000..c213e33d
--- /dev/null
+++ b/html/src/mixins/tabs/friendLog.pug
@@ -0,0 +1,24 @@
+mixin friendLogTab()
+ .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'friendLog'" v-if="$refs.menu && $refs.menu.activeIndex === 'friendLog'")
+ data-tables(v-bind="friendLogTable")
+ template(#tool)
+ div(style="margin:0 0 10px;display:flex;align-items:center")
+ el-select(v-model="friendLogTable.filters[0].value" @change="saveTableFilters" multiple clearable collapse-tags style="flex:1" :placeholder="$t('view.friend_log.filter_placeholder')")
+ el-option(v-once v-for="type in ['Friend', 'Unfriend', 'FriendRequest', 'CancelFriendRequest', 'DisplayName', 'TrustLevel']" :key="type" :label="type" :value="type")
+ el-input(v-model="friendLogTable.filters[1].value" :placeholder="$t('view.friend_log.search_placeholder')" style="flex:none;width:150px;margin-left:10px")
+ el-table-column(:label="$t('table.friendLog.date')" prop="created_at" sortable="custom" width="120")
+ template(v-once #default="scope")
+ el-tooltip(placement="right")
+ template(#content)
+ span {{ scope.row.created_at | formatDate('long') }}
+ span {{ scope.row.created_at | formatDate('short') }}
+ el-table-column(:label="$t('table.friendLog.type')" prop="type" width="150")
+ el-table-column(:label="$t('table.friendLog.user')" prop="displayName")
+ template(v-once #default="scope")
+ span(v-if="scope.row.type === 'DisplayName'") {{ scope.row.previousDisplayName }} #[i.el-icon-right]
+ span.x-link(v-text="scope.row.displayName || scope.row.userId" @click="showUserDialog(scope.row.userId)")
+ template(v-if="scope.row.type === 'TrustLevel'")
+ span ({{ scope.row.previousTrustLevel }} #[i.el-icon-right] {{ scope.row.trustLevel }})
+ el-table-column(:label="$t('table.friendLog.action')" width="80" align="right")
+ template(v-once #default="scope")
+ el-button(type="text" icon="el-icon-close" size="mini" @click="deleteFriendLog(scope.row)")
diff --git a/html/src/mixins/tabs/friendsList.pug b/html/src/mixins/tabs/friendsList.pug
new file mode 100644
index 00000000..baea35d2
--- /dev/null
+++ b/html/src/mixins/tabs/friendsList.pug
@@ -0,0 +1,85 @@
+mixin friendsListTab()
+ .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'friendsList'" v-if="$refs.menu && $refs.menu.activeIndex === 'friendsList'")
+ div.options-container(style="margin-top:0")
+ span.header {{ $t('view.friend_list.header') }}
+ div(style="float:right;font-size:13px")
+ div(v-if="friendsListBulkUnfriendMode" style="display:inline-block;margin-right:10px")
+ el-button(size="small" @click="showBulkUnfriendSelectionConfirm") {{ $t('view.friend_list.bulk_unfriend_selection') }}
+ //- el-button(size="small" @click="showBulkUnfriendAllConfirm" style="margin-right:5px") Bulk Unfriend All
+ div(style="display:inline-block;margin-right:10px")
+ span.name {{ $t('view.friend_list.bulk_unfriend') }}
+ el-switch(v-model="friendsListBulkUnfriendMode" style="margin-left:5px")
+ span {{ $t('view.friend_list.load') }}
+ el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.friend_list.load_notice')")
+ i.el-icon-warning
+ template(v-if="friendsListLoading")
+ span(v-text="friendsListLoadingProgress" style="margin-left:5px")
+ el-tooltip(placement="top" :content="$t('view.friend_list.cancel_tooltip')" :disabled="hideTooltips")
+ el-button(@click="friendsListLoading = false" size="mini" icon="el-icon-loading" circle style="margin-left:5px")
+ template(v-else)
+ el-tooltip(placement="top" :content="$t('view.friend_list.load_tooltip')" :disabled="hideTooltips")
+ el-button(@click="friendsListLoadUsers" size="mini" icon="el-icon-refresh-left" circle style="margin-left:5px")
+ div(style="margin:10px 0 0 10px;display:flex;align-items:center")
+ div(style="flex:none;margin-right:10px")
+ el-tooltip(placement="bottom" :content="$t('view.friend_list.favorites_only_tooltip')" :disabled="hideTooltips")
+ el-switch(v-model="friendsListSearchFilterVIP" @change="friendsListSearchChange" active-color="#13ce66")
+ el-input(v-model="friendsListSearch" :placeholder="$t('view.friend_list.search_placeholder')" @change="friendsListSearchChange" clearable style="flex:1")
+ el-select(v-model="friendsListSearchFilters" multiple clearable collapse-tags style="flex:none;width:200px;margin:0 10px" @change="friendsListSearchChange" :placeholder="$t('view.friend_list.filter_placeholder')")
+ el-option(v-once v-for="type in ['Display Name', 'User Name', 'Rank', 'Status', 'Bio', 'Memo']" :key="type" :label="type" :value="type")
+ el-tooltip(placement="top" :content="$t('view.friend_list.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="friendsListSearchChange" icon="el-icon-refresh" circle style="flex:none")
+ el-tooltip(placement="top" :content="$t('view.friend_list.clear_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="friendsListTable.data = []" icon="el-icon-delete" circle style="flex:none;margin-left:5px")
+ data-tables(v-bind="friendsListTable" @row-click="selectFriendsListRow" style="margin-top:10px;cursor:pointer")
+ el-table-column(v-if="friendsListBulkUnfriendMode" width="55" prop="$selected")
+ template(v-once #default="scope")
+ el-button(type="text" size="mini" @click.stop)
+ el-checkbox(v-model="scope.row.$selected")
+ el-table-column(:label="$t('table.friendList.no')" width="70" prop="$friendNum" sortable="custom")
+ el-table-column(:label="$t('table.friendList.avatar')" width="70" prop="photo")
+ template(v-once #default="scope")
+ el-popover(placement="right" height="500px" trigger="hover")
+ img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row)")
+ img.friends-list-avatar(v-lazy="userImageFull(scope.row)" style="height:500px;cursor:pointer" @click="downloadAndSaveImage(userImageFull(scope.row))")
+ el-table-column(:label="$t('table.friendList.displayName')" min-width="140" prop="displayName" sortable :sort-method="(a, b) => sortAlphabetically(a, b, 'displayName')")
+ template(v-once #default="scope")
+ span.name(v-if="randomUserColours" v-text="scope.row.displayName" :style="{'color':scope.row.$userColour}")
+ span.name(v-else v-text="scope.row.displayName")
+ el-table-column(:label="$t('table.friendList.rank')" width="110" prop="$trustSortNum" sortable="custom")
+ template(v-once #default="scope")
+ span.name(v-if="randomUserColours" v-text="scope.row.$trustLevel" :class="scope.row.$trustClass")
+ span.name(v-else v-text="scope.row.$trustLevel" :style="{'color':scope.row.$userColour}")
+ el-table-column(:label="$t('table.friendList.status')" min-width="180" prop="status" sortable :sort-method="(a, b) => sortStatus(a.status, b.status)")
+ template(v-once #default="scope")
+ i.x-user-status(v-if="scope.row.status !== 'offline'" :class="statusClass(scope.row.status)")
+ span
+ span(v-text="scope.row.statusDescription")
+ el-table-column(:label="$t('table.friendList.language')" width="110" prop="$languages" sortable :sort-method="(a, b) => sortLanguages(a, b)")
+ template(v-once #default="scope")
+ el-tooltip(v-for="item in scope.row.$languages" :key="item.key" placement="top")
+ template(#content)
+ span {{ item.value }} ({{ item.key }})
+ span.flags(:class="languageClass(item.key)" style="display:inline-block;margin-left:5px")
+ el-table-column(:label="$t('table.friendList.bioLink')" width="100" prop="bioLinks")
+ template(v-once #default="scope")
+ el-tooltip(v-if="link" v-for="(link, index) in scope.row.bioLinks" :key="index")
+ template(#content)
+ span(v-text="link")
+ img(:src="getFaviconUrl(link)" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;cursor:pointer" @click.stop="openExternalLink(link)")
+ el-table-column(:label="$t('table.friendList.joinCount')" width="120" prop="$joinCount" sortable)
+ el-table-column(:label="$t('table.friendList.timeTogether')" width="140" prop="$timeSpent" sortable)
+ template(v-once #default="scope")
+ span(v-if="scope.row.$timeSpent") {{ scope.row.$timeSpent | timeToText }}
+ el-table-column(:label="$t('table.friendList.lastSeen')" width="170" prop="$lastSeen" sortable :sort-method="(a, b) => sortAlphabetically(a, b, '$lastSeen')")
+ template(v-once #default="scope")
+ span {{ scope.row.$lastSeen | formatDate('long') }}
+ el-table-column(:label="$t('table.friendList.lastActivity')" width="170" prop="last_activity" sortable :sort-method="(a, b) => sortAlphabetically(a, b, 'last_activity')")
+ template(v-once #default="scope")
+ span {{ scope.row.last_activity | formatDate('long') }}
+ el-table-column(:label="$t('table.friendList.lastLogin')" width="170" prop="last_login" sortable :sort-method="(a, b) => sortAlphabetically(a, b, 'last_login')")
+ template(v-once #default="scope")
+ span {{ scope.row.last_login | formatDate('long') }}
+ el-table-column(:label="$t('table.friendList.dateJoined')" width="120" prop="date_joined" sortable :sort-method="(a, b) => sortAlphabetically(a, b, 'date_joined')")
+ el-table-column(:label="$t('table.friendList.unfriend')" width="80")
+ template(v-once #default="scope")
+ el-button(type="text" icon="el-icon-close" size="mini" @click.stop="confirmDeleteFriend(scope.row.id)")
diff --git a/html/src/mixins/tabs/gameLog.pug b/html/src/mixins/tabs/gameLog.pug
new file mode 100644
index 00000000..67c59da5
--- /dev/null
+++ b/html/src/mixins/tabs/gameLog.pug
@@ -0,0 +1,50 @@
+mixin gameLogTab()
+ .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'gameLog'")
+ data-tables(v-bind="gameLogTable" v-loading="gameLogTable.loading")
+ template(#tool)
+ div(style="margin:0 0 10px;display:flex;align-items:center")
+ el-select(v-model="gameLogTable.filter" @change="gameLogTableLookup" multiple clearable collapse-tags style="flex:1" :placeholder="$t('view.game_log.filter_placeholder')")
+ el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'PortalSpawn', 'Event', 'VideoPlay', 'StringLoad', 'ImageLoad']" :key="type" :label="type" :value="type")
+ el-input(v-model="gameLogTable.search" :placeholder="$t('view.game_log.search_placeholder')" @keyup.native.13="gameLogTableLookup" @change="gameLogTableLookup" clearable style="flex:none;width:150px;margin:0 10px")
+ //- el-tooltip(placement="bottom" content="Reload game log" :disabled="hideTooltips")
+ //- el-button(type="default" @click="resetGameLog" icon="el-icon-refresh" circle style="flex:none")
+ el-table-column(:label="$t('table.gameLog.date')" prop="created_at" sortable="custom" width="120")
+ template(v-once #default="scope")
+ el-tooltip(placement="right")
+ template(#content)
+ span {{ scope.row.created_at | formatDate('long') }}
+ span {{ scope.row.created_at | formatDate('short') }}
+ el-table-column(:label="$t('table.gameLog.type')" prop="type" width="120")
+ template(v-once #default="scope")
+ span.x-link(v-if="scope.row.location && scope.row.type !== 'Location'" v-text="scope.row.type" @click="showWorldDialog(scope.row.location)")
+ span(v-else v-text="scope.row.type")
+ el-table-column(:label="$t('table.gameLog.icon')" prop="isFriend" width="60")
+ template(v-once #default="scope")
+ template(v-if="gameLogIsFriend(scope.row)")
+ el-tooltip(v-if="gameLogIsFavorite(scope.row)" placement="top" content="Favorite")
+ span ⭐
+ el-tooltip(v-else placement="top" content="Friend")
+ span 💚
+ el-table-column(:label="$t('table.gameLog.user')" prop="displayName" width="180")
+ template(v-once #default="scope")
+ span.x-link(v-if="scope.row.displayName" v-text="scope.row.displayName" @click="lookupUser(scope.row)" style="padding-right:10px")
+ el-table-column(:label="$t('table.gameLog.detail')" prop="data")
+ template(v-once #default="scope")
+ location(v-if="scope.row.type === 'Location'" :location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
+ location(v-else-if="scope.row.type === 'PortalSpawn'" :location="scope.row.instanceId" :hint="scope.row.worldName" :grouphint="scope.row.groupName")
+ template(v-else-if="scope.row.type === 'Event'")
+ span(v-text="scope.row.data")
+ template(v-else-if="scope.row.type === 'VideoPlay'")
+ span(v-if="scope.row.videoId" style="margin-right:5px") {{ scope.row.videoId }}:
+ span(v-if="scope.row.videoId === 'LSMedia'" v-text="scope.row.videoName")
+ span.x-link(v-else-if="scope.row.videoName" @click="openExternalLink(scope.row.videoUrl)" v-text="scope.row.videoName")
+ span.x-link(v-else @click="openExternalLink(scope.row.videoUrl)" v-text="scope.row.videoUrl")
+ template(v-else-if="scope.row.type === 'ImageLoad'")
+ span.x-link(@click="openExternalLink(scope.row.resourceUrl)" v-text="scope.row.resourceUrl")
+ template(v-else-if="scope.row.type === 'StringLoad'")
+ span.x-link(@click="openExternalLink(scope.row.resourceUrl)" v-text="scope.row.resourceUrl")
+ template(v-else-if="scope.row.type === 'Notification' || scope.row.type === 'OnPlayerJoined' || scope.row.type === 'OnPlayerLeft'")
+ span.x-link(v-else v-text="scope.row.data")
+ el-table-column(:label="$t('table.gameLog.action')" width="80" align="right")
+ template(v-once #default="scope")
+ el-button(v-if="scope.row.type !== 'OnPlayerJoined' && scope.row.type !== 'OnPlayerLeft' && scope.row.type !== 'Location' && scope.row.type !== 'PortalSpawn'" type="text" icon="el-icon-close" size="mini" @click="deleteGameLogEntry(scope.row)")
diff --git a/html/src/mixins/tabs/moderation.pug b/html/src/mixins/tabs/moderation.pug
new file mode 100644
index 00000000..f7fc8736
--- /dev/null
+++ b/html/src/mixins/tabs/moderation.pug
@@ -0,0 +1,26 @@
+mixin moderationTab()
+ .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'moderation'" v-if="$refs.menu && $refs.menu.activeIndex === 'moderation'")
+ data-tables(v-bind="playerModerationTable" v-loading="API.isPlayerModerationsLoading")
+ template(#tool)
+ div(style="margin:0 0 10px;display:flex;align-items:center")
+ el-select(v-model="playerModerationTable.filters[0].value" @change="saveTableFilters" multiple clearable collapse-tags style="flex:1" :placeholder="$t('view.moderation.filter_placeholder')")
+ el-option(v-once v-for="type in ['block', 'unblock', 'mute', 'unmute', 'interactOn', 'interactOff']" :key="type" :label="type" :value="type")
+ el-input(v-model="playerModerationTable.filters[1].value" :placeholder="$t('view.moderation.search_placeholder')" style="flex:none;width:150px;margin:0 10px")
+ el-tooltip(placement="bottom" :content="$t('view.moderation.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" :loading="API.isPlayerModerationsLoading" @click="API.refreshPlayerModerations()" icon="el-icon-refresh" circle style="flex:none")
+ el-table-column(:label="$t('table.moderation.date')" prop="created" sortable="custom" width="120")
+ template(v-once #default="scope")
+ el-tooltip(placement="right")
+ template(#content)
+ span {{ scope.row.created | formatDate('long') }}
+ span {{ scope.row.created | formatDate('short') }}
+ el-table-column(:label="$t('table.moderation.type')" prop="type" width="100")
+ el-table-column(:label="$t('table.moderation.source')" prop="sourceDisplayName")
+ template(v-once #default="scope")
+ span.x-link(v-text="scope.row.sourceDisplayName" @click="showUserDialog(scope.row.sourceUserId)")
+ el-table-column(:label="$t('table.moderation.target')" prop="targetDisplayName")
+ template(v-once #default="scope")
+ span.x-link(v-text="scope.row.targetDisplayName" @click="showUserDialog(scope.row.targetUserId)")
+ el-table-column(:label="$t('table.moderation.action')" width="80" align="right")
+ template(v-once #default="scope")
+ el-button(v-if="scope.row.sourceUserId === API.currentUser.id" type="text" icon="el-icon-close" size="mini" @click="deletePlayerModeration(scope.row)")
diff --git a/html/src/mixins/tabs/notifications.pug b/html/src/mixins/tabs/notifications.pug
new file mode 100644
index 00000000..9cd918c9
--- /dev/null
+++ b/html/src/mixins/tabs/notifications.pug
@@ -0,0 +1,89 @@
+mixin notificationsTab()
+ .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'notification'" v-if="$refs.menu && $refs.menu.activeIndex === 'notification'" v-loading="API.isNotificationsLoading")
+ data-tables(v-bind="notificationTable")
+ 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="$t('view.notification.filter_placeholder')")
+ el-option(v-once v-for="type in ['requestInvite', 'invite', 'requestInviteResponse', 'inviteResponse', 'friendRequest', 'hiddenFriendRequest', 'message', 'group.announcement', 'group.informative', 'group.invite', 'group.joinRequest', 'moderation.warning.group']" :key="type" :label="type" :value="type")
+ el-input(v-model="notificationTable.filters[1].value" :placeholder="$t('view.notification.search_placeholder')" style="flex:none;width:150px;margin:0 10px")
+ el-tooltip(placement="bottom" :content="$t('view.notification.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" :loading="API.isNotificationsLoading" @click="API.refreshNotifications()" icon="el-icon-refresh" circle style="flex:none")
+ el-table-column(:label="$t('table.notification.date')" prop="created_at" sortable="custom" width="120")
+ template(v-once #default="scope")
+ el-tooltip(placement="right")
+ template(#content)
+ span {{ scope.row.created_at | formatDate('long') }}
+ span {{ scope.row.created_at | formatDate('short') }}
+ el-table-column(:label="$t('table.notification.type')" prop="type" width="160")
+ template(v-once #default="scope")
+ el-tooltip(v-if="scope.row.type === 'invite'" placement="top")
+ template(#content)
+ location(v-if="scope.row.details" :location="scope.row.details.worldId" :hint="scope.row.details.worldName" :grouphint="scope.row.details.groupName" :link="false")
+ span.x-link(v-text="scope.row.type" @click="showWorldDialog(scope.row.details.worldId)")
+ 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="$t('table.notification.user')" prop="senderUsername" width="150")
+ template(v-once #default="scope")
+ span.x-link(v-text="scope.row.senderUsername" @click="showUserDialog(scope.row.senderUserId)")
+ el-table-column(:label="$t('table.notification.photo')" width="100" prop="photo")
+ template(v-once #default="scope")
+ template(v-if="scope.row.details && scope.row.details.imageUrl")
+ el-popover(placement="right" width="500px" trigger="click")
+ img.x-link(slot="reference" v-lazy="scope.row.details.imageUrl" style="flex:none;height:50px;border-radius:4px")
+ img.x-link(v-lazy="scope.row.details.imageUrl" style="width:500px" @click="downloadAndSaveImage(scope.row.details.imageUrl)")
+ template(v-else-if="scope.row.imageUrl")
+ el-popover(placement="right" width="500px" trigger="click")
+ img.x-link(slot="reference" v-lazy="scope.row.imageUrl" style="flex:none;height:50px;border-radius:4px")
+ img.x-link(v-lazy="scope.row.imageUrl" style="width:500px" @click="downloadAndSaveImage(scope.row.imageUrl)")
+ el-table-column(:label="$t('table.notification.message')" prop="message")
+ template(v-once #default="scope")
+ span(v-if="scope.row.title") {{ scope.row.title }}, {{ scope.row.message }}
+ span(v-else-if="scope.row.message" v-text="scope.row.message")
+ span(v-else-if='scope.row.details && scope.row.details.inviteMessage' v-text="scope.row.details.inviteMessage")
+ span(v-else-if='scope.row.details && scope.row.details.requestMessage' v-text="scope.row.details.requestMessage")
+ span(v-else-if='scope.row.details && scope.row.details.responseMessage' v-text="scope.row.details.responseMessage")
+ el-table-column(:label="$t('table.notification.action')" width="100" align="right")
+ template(v-once #default="scope")
+ template(v-if="scope.row.senderUserId !== API.currentUser.id && !scope.row.$isExpired")
+ template(v-if="scope.row.type === 'friendRequest'")
+ el-tooltip(placement="top" content="Accept" :disabled="hideTooltips")
+ el-button(type="text" icon="el-icon-check" size="mini" @click="acceptNotification(scope.row)")
+ template(v-else-if="scope.row.type === 'invite'")
+ el-tooltip(placement="top" content="Decline with message" :disabled="hideTooltips")
+ el-button(type="text" icon="el-icon-chat-line-square" size="mini" @click="showSendInviteResponseDialog(scope.row)")
+ template(v-else-if="scope.row.type === 'requestInvite'")
+ template(v-if="lastLocation.location && isGameRunning && checkCanInvite(lastLocation.location)")
+ el-tooltip(placement="top" content="Invite" :disabled="hideTooltips")
+ 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-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" 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, '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)")
+ template(v-if="scope.row.type !== 'friendRequest' && scope.row.type !== 'hiddenFriendRequest' && !scope.row.type.includes('group.') && !scope.row.type.includes('moderation.')")
+ 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)")
diff --git a/html/src/mixins/tabs/playerList.pug b/html/src/mixins/tabs/playerList.pug
new file mode 100644
index 00000000..274ba724
--- /dev/null
+++ b/html/src/mixins/tabs/playerList.pug
@@ -0,0 +1,249 @@
+mixin playerListTab()
+ .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'playerList'")
+ div(style="display:flex;flex-direction:column;height:100%")
+ div(v-if="currentInstanceWorld.ref.id" style="display:flex")
+ el-popover(placement="right" width="500px" trigger="click" style="height:120px")
+ img.x-link(slot="reference" v-lazy="currentInstanceWorld.ref.thumbnailImageUrl" style="flex:none;width:160px;height:120px;border-radius:4px")
+ img.x-link(v-lazy="currentInstanceWorld.ref.imageUrl" style="width:500px;height:375px" @click="downloadAndSaveImage(currentInstanceWorld.ref.imageUrl)")
+ div(style="margin-left:10px;display:flex;flex-direction:column;min-width:320px;width:100%")
+ div
+ span.x-link(@click="showWorldDialog(currentInstanceWorld.ref.id)" style="font-weight:bold;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1")
+ | #[i.el-icon-s-home(v-show="API.currentUser.$homeLocation && API.currentUser.$homeLocation.worldId === currentInstanceWorld.ref.id" style="margin-right:5px")] {{ currentInstanceWorld.ref.name }}
+ div
+ span.x-link(v-text="currentInstanceWorld.ref.authorName" @click="showUserDialog(currentInstanceWorld.ref.authorId)" style="color:#909399;font-family:monospace")
+ div(style="margin-top:5px")
+ el-tag(v-if="currentInstanceWorld.ref.$isLabs" type="primary" effect="plain" size="mini" style="margin-right:5px") {{ $t('dialog.world.tags.labs') }}
+ el-tag(v-else-if="currentInstanceWorld.ref.releaseStatus === 'public'" type="success" effect="plain" size="mini" style="margin-right:5px") {{ $t('dialog.world.tags.public') }}
+ el-tag(v-else-if="currentInstanceWorld.ref.releaseStatus === 'private'" type="danger" effect="plain" size="mini" style="margin-right:5px") {{ $t('dialog.world.tags.private') }}
+ el-tag.x-tag-platform-pc(v-if="currentInstanceWorld.isPC" type="info" effect="plain" size="mini" style="margin-right:5px") PC
+ el-tag.x-tag-platform-quest(v-if="currentInstanceWorld.isQuest" type="info" effect="plain" size="mini" style="margin-right:5px") Quest
+ el-tag(type="info" effect="plain" size="mini" v-text="currentInstanceWorld.fileSize" style="margin-right:5px")
+ el-tag(v-if="currentInstanceWorld.inCache" type="info" effect="plain" size="mini" style="margin-right:5px")
+ span(v-text="currentInstanceWorld.cacheSize")
+ | {{ $t('dialog.world.tags.cache') }}
+ br
+ 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 }})]
+ | #[timer(v-if="lastLocation.date" :epoch="lastLocation.date")]
+ div(style="margin-top:5px")
+ span(v-show="currentInstanceWorld.ref.name !== currentInstanceWorld.ref.description" v-text="currentInstanceWorld.ref.description" style="font-size:12px;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2")
+ div(style="display:flex;flex-direction:column;margin-left:20px")
+ .x-friend-item(style="cursor:default")
+ .detail
+ span.name {{ $t('dialog.world.info.capacity') }}
+ span.extra {{ currentInstanceWorld.ref.capacity | commaNumber }} ({{ currentInstanceWorld.ref.capacity * 2 | commaNumber }})
+ .x-friend-item(style="cursor:default")
+ .detail
+ span.name {{ $t('dialog.world.info.last_updated') }}
+ span.extra {{ currentInstanceWorld.fileCreatedAt | formatDate('long') }}
+ .x-friend-item(style="cursor:default")
+ .detail
+ span.name {{ $t('dialog.world.info.created_at') }}
+ span.extra {{ currentInstanceWorld.ref.created_at | formatDate('long') }}
+ div.photon-event-table(v-if="photonLoggingEnabled")
+ div(style="position:absolute;width:600px;margin-left:195px;z-index:1")
+ el-select(v-model="photonEventTableTypeFilter" @change="photonEventTableFilterChange" multiple clearable collapse-tags style="flex:1;width:220px" :placeholder="$t('view.player_list.photon.filter_placeholder')")
+ el-option(v-once v-for="type in photonEventTableTypeFilterList" :key="type" :label="type" :value="type")
+ el-input(v-model="photonEventTableFilter" @input="photonEventTableFilterChange" :placeholder="$t('view.player_list.photon.search_placeholder')" clearable style="width:150px;margin-left:10px")
+ el-button(@click="showChatboxBlacklistDialog" style="margin-left:10px") {{ $t('view.player_list.photon.chatbox_blacklist') }}
+ el-tooltip(placement="bottom" :content="$t('view.player_list.photon.status_tooltip')" :disabled="hideTooltips")
+ div(style="display:inline-block;margin-left:15px;font-size:14px;vertical-align:text-top;margin-top:1px")
+ span(v-if="ipcEnabled && !photonEventIcon") 🟢
+ span(v-else-if="ipcEnabled") ⚪
+ span(v-else) 🔴
+ el-tabs(type="card")
+ el-tab-pane(:label="$t('view.player_list.photon.current')")
+ data-tables(v-bind="photonEventTable" style="margin-bottom:10px")
+ el-table-column(:label="$t('table.playerList.date')" prop="created_at" width="120")
+ template(v-once #default="scope")
+ el-tooltip(placement="right")
+ template(#content)
+ span {{ scope.row.created_at | formatDate('long') }}
+ span {{ scope.row.created_at | formatDate('short') }}
+ el-table-column(:label="$t('table.playerList.user')" prop="photonId" width="160")
+ template(v-once #default="scope")
+ span.x-link(v-text="scope.row.displayName" @click="showUserFromPhotonId(scope.row.photonId)" style="padding-right:10px")
+ el-table-column(:label="$t('table.playerList.type')" prop="type" width="140")
+ el-table-column(:label="$t('table.playerList.detail')" prop="text")
+ template(v-once #default="scope")
+ template(v-if="scope.row.type === 'ChangeAvatar'")
+ span.x-link(v-text="scope.row.avatar.name" @click="showAvatarDialog(scope.row.avatar.id)")
+ |
+ span(v-if="!scope.row.inCache" style="color:#aaa") #[i.el-icon-download]
+ span.avatar-info-public(v-if="scope.row.avatar.releaseStatus === 'public'") {{ $t('dialog.avatar.labels.public') }}
+ span.avatar-info-own(v-else-if="scope.row.avatar.releaseStatus === 'private'") {{ $t('dialog.avatar.labels.private') }}
+ template(v-if="scope.row.avatar.description && scope.row.avatar.name !== scope.row.avatar.description")
+ | - {{ scope.row.avatar.description }}
+ template(v-else-if="scope.row.type === 'ChangeStatus'")
+ template(v-if="scope.row.status !== scope.row.previousStatus")
+ el-tooltip(placement="top")
+ template(#content)
+ span(v-if="scope.row.previousStatus === 'active'") {{ $t('dialog.user.status.active') }}
+ span(v-else-if="scope.row.previousStatus === 'join me'") {{ $t('dialog.user.status.join_me') }}
+ span(v-else-if="scope.row.previousStatus === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
+ span(v-else-if="scope.row.previousStatus === 'busy'") {{ $t('dialog.user.status.busy') }}
+ span(v-else) {{ $t('dialog.user.status.offline') }}
+ i.x-user-status(:class="statusClass(scope.row.previousStatus)")
+ span
+ i.el-icon-right
+ el-tooltip(placement="top")
+ template(#content)
+ span(v-if="scope.row.status === 'active'") Active
+ span(v-else-if="scope.row.status === 'join me'") Join Me
+ span(v-else-if="scope.row.status === 'ask me'") Ask Me
+ span(v-else-if="scope.row.status === 'busy'") Do Not Disturb
+ span(v-else) Offline
+ i.x-user-status(:class="statusClass(scope.row.status)" style="margin-right:5px")
+ span(v-if="scope.row.statusDescription !== scope.row.previousStatusDescription" v-text="scope.row.statusDescription")
+ template(v-else-if="scope.row.type === 'ChangeGroup'")
+ span.x-link(v-if="scope.row.previousGroupName" v-text="scope.row.previousGroupName" @click="showGroupDialog(scope.row.previousGroupId)" style="margin-right:5px")
+ span.x-link(v-else v-text="scope.row.previousGroupId" @click="showGroupDialog(scope.row.previousGroupId)" style="margin-right:5px")
+ span
+ i.el-icon-right
+ span.x-link(v-if="scope.row.groupName" v-text="scope.row.groupName" @click="showGroupDialog(scope.row.groupId)" style="margin-left:5px")
+ span.x-link(v-else v-text="scope.row.groupId" @click="showGroupDialog(scope.row.groupId)" style="margin-left:5px")
+ span.x-link(v-else-if="scope.row.type === 'PortalSpawn'" @click="showWorldDialog(scope.row.location, scope.row.shortName)")
+ location(:location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName" :link="false")
+ span(v-else-if="scope.row.type === 'ChatBoxMessage'" v-text="scope.row.text")
+ span(v-else-if="scope.row.type === 'OnPlayerJoined'")
+ span(v-if="scope.row.platform === 'Desktop'" style="color:#409eff") Desktop
+ span(v-else-if="scope.row.platform === 'VR'" style="color:#409eff") VR
+ span(v-else-if="scope.row.platform === 'Quest'" style="color:#67c23a") Quest
+ span.x-link(v-text="scope.row.avatar.name" @click="showAvatarDialog(scope.row.avatar.id)")
+ |
+ span(v-if="!scope.row.inCache" style="color:#aaa") #[i.el-icon-download]
+ span.avatar-info-public(v-if="scope.row.avatar.releaseStatus === 'public'") {{ $t('dialog.avatar.labels.public') }}
+ span.avatar-info-own(v-else-if="scope.row.avatar.releaseStatus === 'private'") {{ $t('dialog.avatar.labels.private') }}
+ span(v-else-if="scope.row.color === 'yellow'" v-text="scope.row.text" style="color:yellow")
+ span(v-else v-text="scope.row.text")
+ el-tab-pane(:label="$t('view.player_list.photon.previous')")
+ data-tables(v-bind="photonEventTablePrevious" style="margin-bottom:10px")
+ el-table-column(:label="$t('table.playerList.date')" prop="created_at" width="120")
+ template(v-once #default="scope")
+ el-tooltip(placement="right")
+ template(#content)
+ span {{ scope.row.created_at | formatDate('long') }}
+ span {{ scope.row.created_at | formatDate('short') }}
+ el-table-column(:label="$t('table.playerList.user')" prop="photonId" width="160")
+ template(v-once #default="scope")
+ span.x-link(v-text="scope.row.displayName" @click="lookupUser(scope.row)" style="padding-right:10px")
+ el-table-column(:label="$t('table.playerList.type')" prop="type" width="140")
+ el-table-column(:label="$t('table.playerList.detail')" prop="text")
+ template(v-once #default="scope")
+ template(v-if="scope.row.type === 'ChangeAvatar'")
+ span.x-link(v-text="scope.row.avatar.name" @click="showAvatarDialog(scope.row.avatar.id)")
+ |
+ span(v-if="!scope.row.inCache" style="color:#aaa") #[i.el-icon-download]
+ span.avatar-info-public(v-if="scope.row.avatar.releaseStatus === 'public'") (Public)
+ span.avatar-info-own(v-else-if="scope.row.avatar.releaseStatus === 'private'") (Private)
+ template(v-if="scope.row.avatar.description && scope.row.avatar.name !== scope.row.avatar.description")
+ | - {{ scope.row.avatar.description }}
+ template(v-else-if="scope.row.type === 'ChangeStatus'")
+ template(v-if="scope.row.status !== scope.row.previousStatus")
+ el-tooltip(placement="top")
+ template(#content)
+ span(v-if="scope.row.previousStatus === 'active'") {{ $t('dialog.user.status.active') }}
+ span(v-else-if="scope.row.previousStatus === 'join me'") {{ $t('dialog.user.status.join_me') }}
+ span(v-else-if="scope.row.previousStatus === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
+ span(v-else-if="scope.row.previousStatus === 'busy'") {{ $t('dialog.user.status.busy') }}
+ span(v-else) {{ $t('dialog.user.status.offline') }}
+ i.x-user-status(:class="statusClass(scope.row.previousStatus)")
+ span
+ i.el-icon-right
+ el-tooltip(placement="top")
+ template(#content)
+ span(v-if="scope.row.status === 'active'") {{ $t('dialog.user.status.active') }}
+ span(v-else-if="scope.row.status === 'join me'") {{ $t('dialog.user.status.join_me') }}
+ span(v-else-if="scope.row.status === 'ask me'") {{ $t('dialog.user.status.ask_me') }}
+ span(v-else-if="scope.row.status === 'busy'") {{ $t('dialog.user.status.busy') }}
+ span(v-else) {{ $t('dialog.user.status.offline') }}
+ i.x-user-status(:class="statusClass(scope.row.status)")
+ span(v-if="scope.row.statusDescription !== scope.row.previousStatusDescription" v-text="scope.row.statusDescription" style="margin-left:5px")
+ template(v-else-if="scope.row.type === 'ChangeGroup'")
+ span.x-link(v-if="scope.row.previousGroupName" v-text="scope.row.previousGroupName" @click="showGroupDialog(scope.row.previousGroupId)" style="margin-right:5px")
+ span.x-link(v-else v-text="scope.row.previousGroupId" @click="showGroupDialog(scope.row.previousGroupId)" style="margin-right:5px")
+ span
+ i.el-icon-right
+ span.x-link(v-if="scope.row.groupName" v-text="scope.row.groupName" @click="showGroupDialog(scope.row.groupId)" style="margin-left:5px")
+ span.x-link(v-else v-text="scope.row.groupId" @click="showGroupDialog(scope.row.groupId)" style="margin-left:5px")
+ span.x-link(v-else-if="scope.row.type === 'PortalSpawn'" @click="showWorldDialog(scope.row.location, scope.row.shortName)")
+ location(:location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName" :link="false")
+ span(v-else-if="scope.row.type === 'ChatBoxMessage'" v-text="scope.row.text")
+ span(v-else-if="scope.row.type === 'OnPlayerJoined'")
+ span(v-if="scope.row.platform === 'Desktop'" style="color:#409eff") Desktop
+ span(v-else-if="scope.row.platform === 'VR'" style="color:#409eff") VR
+ span(v-else-if="scope.row.platform === 'Quest'" style="color:#67c23a") Quest
+ span.x-link(v-text="scope.row.avatar.name" @click="showAvatarDialog(scope.row.avatar.id)")
+ |
+ span(v-if="!scope.row.inCache" style="color:#aaa") #[i.el-icon-download]
+ span.avatar-info-public(v-if="scope.row.avatar.releaseStatus === 'public'") (Public)
+ span.avatar-info-own(v-else-if="scope.row.avatar.releaseStatus === 'private'") (Private)
+ span(v-else-if="scope.row.color === 'yellow'" v-text="scope.row.text" style="color:yellow")
+ span(v-else v-text="scope.row.text")
+ div.current-instance-table
+ data-tables(v-bind="currentInstanceUserList" @row-click="selectCurrentInstanceRow" style="margin-top:10px;cursor:pointer")
+ el-table-column(:label="$t('table.playerList.avatar')" width="70" prop="photo")
+ template(v-once #default="scope")
+ template(v-if="userImage(scope.row.ref)")
+ el-popover(placement="right" height="500px" trigger="hover")
+ img.friends-list-avatar(slot="reference" v-lazy="userImage(scope.row.ref)")
+ img.friends-list-avatar(v-lazy="userImageFull(scope.row.ref)" style="height:500px;cursor:pointer" @click="downloadAndSaveImage(userImageFull(scope.row.ref))")
+ el-table-column(:label="$t('table.playerList.timer')" width="90" prop="timer" sortable)
+ template(v-once #default="scope")
+ timer(:epoch="scope.row.timer")
+ el-table-column(v-if="photonLoggingEnabled" :label="$t('table.playerList.photonId')" width="110" prop="photonId" sortable)
+ template(v-once #default="scope")
+ template(v-if="chatboxUserBlacklist.has(scope.row.ref.id)")
+ el-tooltip(placement="left" content="Unblock chatbox messages")
+ el-button(type="text" icon="el-icon-turn-off-microphone" size="mini" style="color:red;margin-right:5px" @click.stop="deleteChatboxUserBlacklist(scope.row.ref.id)")
+ template(v-else)
+ el-tooltip(placement="left" content="Block chatbox messages")
+ el-button(type="text" icon="el-icon-microphone" size="mini" style="margin-right:5px" @click.stop="addChatboxUserBlacklist(scope.row.ref)")
+ span(v-text="scope.row.photonId")
+ el-table-column(:label="$t('table.playerList.icon')" prop="isMaster" width="100")
+ template(v-once #default="scope")
+ el-tooltip(v-if="scope.row.isMaster" placement="left" content="Instance Master")
+ span 👑
+ el-tooltip(v-if="scope.row.isFriend" placement="left" content="Friend")
+ span 💚
+ el-tooltip(v-if="scope.row.timeoutTime" placement="left" content="Timeout")
+ span(style="color:red") 🔴{{ scope.row.timeoutTime }}s
+ el-table-column(:label="$t('table.playerList.platform')" prop="inVRMode" width="80")
+ template(v-once #default="scope")
+ template(v-if="scope.row.ref.last_platform")
+ span(v-if="scope.row.ref.last_platform === 'standalonewindows'" style="color:#409eff") PC
+ span(v-else-if="scope.row.ref.last_platform === 'android'" style="color:#67c23a") Q
+ span(v-else) {{ scope.row.ref.last_platform }}
+ template(v-if="scope.row.inVRMode !== null")
+ span(v-if="scope.row.inVRMode") VR
+ span(v-else) D
+ el-table-column(:label="$t('table.playerList.displayName')" min-width="140" prop="ref.displayName")
+ template(v-once #default="scope")
+ span(v-if="randomUserColours" v-text="scope.row.ref.displayName" :style="{'color':scope.row.ref.$userColour}")
+ span(v-else v-text="scope.row.ref.displayName")
+ el-table-column(:label="$t('table.playerList.status')" min-width="180" prop="ref.status")
+ template(v-once #default="scope")
+ template(v-if="scope.row.ref.status")
+ i.x-user-status(:class="statusClass(scope.row.ref.status)")
+ span
+ span(v-text="scope.row.ref.statusDescription")
+ //- el-table-column(label="Group" min-width="180" prop="groupOnNameplate" sortable)
+ //- template(v-once #default="scope")
+ //- span(v-text="scope.row.groupOnNameplate")
+ el-table-column(:label="$t('table.playerList.rank')" width="110" prop="$trustSortNum" sortable="custom")
+ template(v-once #default="scope")
+ span.name(v-text="scope.row.ref.$trustLevel" :class="scope.row.ref.$trustClass")
+ el-table-column(:label="$t('table.playerList.language')" width="100" prop="ref.$languages")
+ template(v-once #default="scope")
+ el-tooltip(v-for="item in scope.row.ref.$languages" :key="item.key" placement="top")
+ template(#content)
+ span {{ item.value }} ({{ item.key }})
+ span.flags(:class="languageClass(item.key)" style="display:inline-block;margin-left:5px")
+ el-table-column(:label="$t('table.playerList.bioLink')" width="100" prop="ref.bioLinks")
+ template(v-once #default="scope")
+ el-tooltip(v-if="link" v-for="(link, index) in scope.row.ref.bioLinks" :key="index")
+ template(#content)
+ span(v-text="link")
+ img(:src="getFaviconUrl(link)" style="width:16px;height:16px;vertical-align:middle;margin-right:5px;cursor:pointer" @click.stop="openExternalLink(link)")
diff --git a/html/src/mixins/tabs/profile.pug b/html/src/mixins/tabs/profile.pug
new file mode 100644
index 00000000..fbccb235
--- /dev/null
+++ b/html/src/mixins/tabs/profile.pug
@@ -0,0 +1,150 @@
+mixin profileTab()
+ .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'profile'" v-if="$refs.menu && $refs.menu.activeIndex === 'profile'")
+ div.options-container(style="margin-top:0")
+ span.header {{ $t('view.profile.profile.header') }}
+ .x-friend-list(style="margin-top:10px")
+ .x-friend-item(@click="showUserDialog(API.currentUser.id)")
+ .avatar
+ img(v-lazy="userImage(API.currentUser)")
+ .detail
+ span.name(v-text="API.currentUser.displayName")
+ span.extra(v-text="API.currentUser.username")
+ .x-friend-item(style="cursor:default")
+ .detail
+ span.name {{ $t('view.profile.profile.last_activity') }}
+ span.extra {{ API.currentUser.last_activity | formatDate('long') }}
+ .x-friend-item(style="cursor:default")
+ .detail
+ span.name {{ $t('view.profile.profile.two_factor') }}
+ span.extra {{ API.currentUser.twoFactorAuthEnabled ? $t('view.profile.profile.two_factor_enabled') : $t('view.profile.profile.two_factor_disabled') }}
+ div
+ el-button(size="small" icon="el-icon-switch-button" @click="logout()" style="margin-left:0;margin-right:5px;margin-top:10px") {{ $t('view.profile.profile.logout') }}
+ el-button(size="small" icon="el-icon-printer" @click="showExportFriendsListDialog()" style="margin-left:0;margin-right:5px;margin-top:10px") {{ $t('view.profile.profile.export_friend_list') }}
+ el-button(size="small" icon="el-icon-user" @click="showExportAvatarsListDialog()" style="margin-left:0;margin-right:5px;margin-top:10px") {{ $t('view.profile.profile.export_own_avatars') }}
+ el-button(size="small" icon="el-icon-chat-dot-round" @click="showDiscordNamesDialog()" style="margin-left:0;margin-right:5px;margin-top:10px") {{ $t('view.profile.profile.discord_names') }}
+ el-button(size="small" icon="el-icon-document-copy" @click="showNoteExportDialog()" style="margin-left:0;margin-right:5px;margin-top:10px") {{ $t('view.profile.profile.export_notes') }}
+ div.options-container
+ span.header {{ $t('view.profile.game_info.header') }}
+ .x-friend-list(style="margin-top:10px")
+ .x-friend-item
+ .detail(@click="API.getVisits()")
+ span.name {{ $t('view.profile.game_info.online_users') }}
+ span.extra(v-if="visits") {{ $t('view.profile.game_info.user_online', { count: visits }) }}
+ span.extra(v-else) {{ $t('view.profile.game_info.refresh') }}
+ div.options-container
+ span.header {{ $t('view.profile.vrc_sdk_downloads.header') }}
+ el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="API.getConfig()" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
+ .x-friend-list(style="margin-top:10px")
+ .x-friend-item(v-for="(link, item) in API.cachedConfig.downloadUrls" :key="item" placement="top")
+ .detail(@click="openExternalLink(link)")
+ span.name(v-text="item")
+ span.extra(v-text="link")
+ div.options-container
+ span.header {{ $t('view.profile.direct_access.header') }}
+ div(style="margin-top:10px")
+ el-button-group
+ el-button(size="small" @click="promptUsernameDialog()") {{ $t('view.profile.direct_access.username') }}
+ el-button(size="small" @click="promptUserIdDialog()") {{ $t('view.profile.direct_access.user_id') }}
+ el-button(size="small" @click="promptWorldDialog()") {{ $t('view.profile.direct_access.world_instance') }}
+ el-button(size="small" @click="promptAvatarDialog()") {{ $t('view.profile.direct_access.avatar') }}
+ div.options-container
+ span.header {{ $t('view.profile.invite_messages') }}
+ el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="inviteMessageTable.visible = true; refreshInviteMessageTable('message')" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
+ el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="inviteMessageTable.visible = false" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ data-tables(v-if="inviteMessageTable.visible" v-bind="inviteMessageTable" style="margin-top:10px")
+ el-table-column(label="Slot" prop="slot" sortable="custom" width="70")
+ el-table-column(label="Message" prop="message")
+ el-table-column(label="Cool Down" prop="updatedAt" sortable="custom" width="110" align="right")
+ template(v-once #default="scope")
+ countdown-timer(:datetime="scope.row.updatedAt" :hours="1")
+ el-table-column(label="Action" width="60" align="right")
+ template(v-once #default="scope")
+ el-button(type="text" icon="el-icon-edit" size="mini" @click="showEditInviteMessageDialog('message', scope.row)")
+ div.options-container
+ span.header {{ $t('view.profile.invite_response_messages') }}
+ el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="inviteResponseMessageTable.visible = true; refreshInviteMessageTable('response')" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
+ el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="inviteResponseMessageTable.visible = false" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ data-tables(v-if="inviteResponseMessageTable.visible" v-bind="inviteResponseMessageTable" style="margin-top:10px")
+ el-table-column(label="Slot" prop="slot" sortable="custom" width="70")
+ el-table-column(label="Message" prop="message")
+ el-table-column(label="Cool Down" prop="updatedAt" sortable="custom" width="110" align="right")
+ template(v-once #default="scope")
+ countdown-timer(:datetime="scope.row.updatedAt" :hours="1")
+ el-table-column(label="Action" width="60" align="right")
+ template(v-once #default="scope")
+ el-button(type="text" icon="el-icon-edit" size="mini" @click="showEditInviteMessageDialog('response', scope.row)")
+ div.options-container
+ span.header {{ $t('view.profile.invite_request_messages') }}
+ el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="inviteRequestMessageTable.visible = true; refreshInviteMessageTable('request')" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
+ el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="inviteRequestMessageTable.visible = false" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ data-tables(v-if="inviteRequestMessageTable.visible" v-bind="inviteRequestMessageTable" style="margin-top:10px")
+ el-table-column(label="Slot" prop="slot" sortable="custom" width="70")
+ el-table-column(label="Message" prop="message")
+ el-table-column(label="Cool Down" prop="updatedAt" sortable="custom" width="110" align="right")
+ template(v-once #default="scope")
+ countdown-timer(:datetime="scope.row.updatedAt" :hours="1")
+ el-table-column(label="Action" width="60" align="right")
+ template(v-once #default="scope")
+ el-button(type="text" icon="el-icon-edit" size="mini" @click="showEditInviteMessageDialog('request', scope.row)")
+ div.options-container
+ span.header {{ $t('view.profile.invite_request_response_messages') }}
+ el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="inviteRequestResponseMessageTable.visible = true; refreshInviteMessageTable('requestResponse')" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
+ el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="inviteRequestResponseMessageTable.visible = false" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ data-tables(v-if="inviteRequestResponseMessageTable.visible" v-bind="inviteRequestResponseMessageTable" style="margin-top:10px")
+ el-table-column(label="Slot" prop="slot" sortable="custom" width="70")
+ el-table-column(label="Message" prop="message")
+ el-table-column(label="Cool Down" prop="updatedAt" sortable="custom" width="110" align="right")
+ template(v-once #default="scope")
+ countdown-timer(:datetime="scope.row.updatedAt" :hours="1")
+ el-table-column(label="Action" width="60" align="right")
+ template(v-once #default="scope")
+ el-button(type="text" icon="el-icon-edit" size="mini" @click="showEditInviteMessageDialog('requestResponse', scope.row)")
+ div.options-container
+ span.header {{ $t('view.profile.past_display_names') }}
+ data-tables(v-bind="pastDisplayNameTable" style="margin-top:10px")
+ el-table-column(label="Date" prop="updated_at" sortable="custom")
+ template(v-once #default="scope")
+ span {{ scope.row.updated_at | formatDate('long') }}
+ el-table-column(label="Name" prop="displayName")
+ div.options-container
+ span.header {{ $t('view.profile.config_json') }}
+ el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="refreshConfigTreeData()" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
+ el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="configTreeData = []" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ el-tree(v-if="configTreeData.length > 0" :data="configTreeData" style="margin-top:10px;font-size:12px")
+ template(#default="scope")
+ span
+ span(v-text="scope.data.key" style="font-weight:bold;margin-right:5px")
+ span(v-if="!scope.data.children" v-text="scope.data.value")
+ div.options-container
+ span.header {{ $t('view.profile.current_user_json') }}
+ el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="refreshCurrentUserTreeData()" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
+ el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="currentUserTreeData = []" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ el-tree(v-if="currentUserTreeData.length > 0" :data="currentUserTreeData" style="margin-top:10px;font-size:12px")
+ template(#default="scope")
+ span
+ span(v-text="scope.data.key" style="font-weight:bold;margin-right:5px")
+ span(v-if="!scope.data.children" v-text="scope.data.value")
+ div.options-container
+ span.header {{ $t('view.profile.feedback') }}
+ el-tooltip(placement="top" :content="$t('view.profile.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="getCurrentUserFeedback()" size="mini" icon="el-icon-refresh" circle style="margin-left:5px")
+ el-tooltip(placement="top" :content="$t('view.profile.clear_results_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="currentUserFeedbackData = []" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ el-tree(v-if="currentUserFeedbackData.length > 0" :data="currentUserFeedbackData" style="margin-top:10px;font-size:12px")
+ template(#default="scope")
+ span
+ span(v-text="scope.data.key" style="font-weight:bold;margin-right:5px")
+ span(v-if="!scope.data.children" v-text="scope.data.value")
diff --git a/html/src/mixins/tabs/search.pug b/html/src/mixins/tabs/search.pug
new file mode 100644
index 00000000..f0e3811f
--- /dev/null
+++ b/html/src/mixins/tabs/search.pug
@@ -0,0 +1,73 @@
+mixin searchTab()
+ .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'search'")
+ div(style="margin:0 0 10px;display:flex;align-items:center")
+ el-input(v-model="searchText" :placeholder="$t('view.search.search_placeholder')" @keyup.native.13="search()" style="flex:1")
+ el-tooltip(placement="bottom" :content="$t('view.search.clear_results_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" @click="clearSearch()" icon="el-icon-delete" circle style="flex:none;margin-left:10px")
+ el-tabs(ref="searchTab" type="card" style="margin-top:15px")
+ el-tab-pane(:label="$t('view.search.user.header')" v-loading="isSearchUserLoading" style="min-height:60px")
+ .x-friend-list
+ .x-friend-item(v-for="user in searchUserResults" :key="user.id" @click="showUserDialog(user.id)")
+ template(v-once)
+ .avatar
+ img(v-lazy="userImage(user)")
+ .detail
+ span.name(v-text="user.displayName")
+ span.extra(v-if="randomUserColours" v-text="user.$trustLevel" :class="user.$trustClass")
+ span.extra(v-else v-text="user.$trustLevel" :style="{'color':user.$userColour}")
+ el-button-group(style="margin-top:15px")
+ el-button(v-if="searchUserParams.offset" @click="moreSearchUser(-1)" icon="el-icon-back" size="small") {{ $t('view.search.prev_page') }}
+ el-button(v-if="searchUserResults.length" @click="moreSearchUser(1)" icon="el-icon-right" size="small") {{ $t('view.search.next_page') }}
+ el-tab-pane(:label="$t('view.search.world.header')" v-loading="isSearchWorldLoading" style="min-height:60px")
+ el-dropdown(@command="(row) => searchWorld(row)" size="small" trigger="click" style="margin-bottom:15px")
+ el-button(size="small") {{ $t('view.search.world.category') }} #[i.el-icon-arrow-down.el-icon--right]
+ el-dropdown-menu(#default="dropdown")
+ el-dropdown-item(v-for="row in API.cachedConfig.dynamicWorldRows" :key="row.index" v-text="row.name" :command="row")
+ el-checkbox(v-model="searchWorldLabs" style="margin-left:10px") {{ $t('view.search.world.community_lab') }}
+ .x-friend-list
+ .x-friend-item(v-for="world in searchWorldResults" :key="world.id" @click="showWorldDialog(world.id)")
+ template(v-once)
+ .avatar
+ img(v-lazy="world.thumbnailImageUrl")
+ .detail
+ span.name(v-text="world.name")
+ span.extra(v-if="world.occupants") {{ world.authorName }} ({{ world.occupants }})
+ span.extra(v-else v-text="world.authorName")
+ el-button-group(style="margin-top:15px")
+ el-button(v-if="searchWorldParams.offset" @click="moreSearchWorld(-1)" icon="el-icon-back" size="small") {{ $t('view.search.prev_page') }}
+ el-button(v-if="searchWorldResults.length >= 10" @click="moreSearchWorld(1)" icon="el-icon-right" size="small") {{ $t('view.search.next_page') }}
+ el-tab-pane(:label="$t('view.search.avatar.header')" v-loading="isSearchAvatarLoading" style="min-height:60px")
+ el-dropdown(v-if="avatarRemoteDatabaseProviderList.length > 1" trigger="click" @click.native.stop size="mini" style="margin-right:5px")
+ el-button(size="small") {{ $t('view.search.avatar.search_provider') }} #[i.el-icon-arrow-down.el-icon--right]
+ el-dropdown-menu(#default="dropdown")
+ el-dropdown-item(v-for="provider in avatarRemoteDatabaseProviderList" :key="provider" @click.native="setAvatarProvider(provider)") #[i.el-icon-check.el-icon--left(v-if="provider === avatarRemoteDatabaseProvider")] {{ provider }}
+ el-tooltip(placement="bottom" :content="$t('view.search.avatar.refresh_tooltip')" :disabled="hideTooltips")
+ el-button(type="default" :loading="userDialog.isAvatarsLoading" @click="refreshUserDialogAvatars()" size="mini" icon="el-icon-refresh" circle)
+ span(style="font-size:14px;margin-left:5px;margin-right:5px") {{ $t("view.search.avatar.result_count", { count: searchAvatarResults.length }) }}
+ el-radio-group(v-model="searchAvatarFilter" size="mini" style="margin:5px;display:block" @change="searchAvatar")
+ el-radio(label="all") {{ $t('view.search.avatar.all') }}
+ el-radio(label="public") {{ $t('view.search.avatar.public') }}
+ el-radio(label="private") {{ $t('view.search.avatar.private') }}
+ el-radio-group(v-model="searchAvatarFilterRemote" size="mini" style="margin:5px;display:block" @change="searchAvatar")
+ el-radio(label="all") {{ $t('view.search.avatar.all') }}
+ el-radio(label="local") {{ $t('view.search.avatar.local') }}
+ el-radio(label="remote" :disabled="!avatarRemoteDatabase") {{ $t('view.search.avatar.remote') }}
+ el-radio-group(:disabled="searchAvatarFilterRemote !== 'local'" v-model="searchAvatarSort" size="mini" style="margin:5px;display:block" @change="searchAvatar")
+ el-radio(label="name") {{ $t('view.search.avatar.sort_name') }}
+ el-radio(label="update") {{ $t('view.search.avatar.sort_update') }}
+ el-radio(label="created") {{ $t('view.search.avatar.sort_created') }}
+ .x-friend-list(style="margin-top:20px")
+ .x-friend-item(v-for="avatar in searchAvatarPage" :key="avatar.id" @click="showAvatarDialog(avatar.id)")
+ template(v-once)
+ .avatar
+ img(v-if="avatar.thumbnailImageUrl" v-lazy="avatar.thumbnailImageUrl")
+ img(v-else-if="avatar.imageUrl" v-lazy="avatar.imageUrl")
+ .detail
+ span.name(v-text="avatar.name")
+ span.extra(v-text="avatar.releaseStatus" v-if="avatar.releaseStatus === 'public'" style="color: #67c23a;")
+ span.extra(v-text="avatar.releaseStatus" v-else-if="avatar.releaseStatus === 'private'" style="color: #f56c6c;")
+ span.extra(v-text="avatar.releaseStatus" v-else)
+ span.extra(v-text="avatar.authorName")
+ el-button-group(style="margin-top:15px")
+ el-button(v-if="searchAvatarPageNum" @click="moreSearchAvatar(-1)" icon="el-icon-back" size="small") {{ $t('view.search.prev_page') }}
+ el-button(v-if="searchAvatarResults.length > 10 && (searchAvatarPageNum + 1) * 10 < searchAvatarResults.length" @click="moreSearchAvatar(1)" icon="el-icon-right" size="small") {{ $t('view.search.next_page') }}
diff --git a/html/src/mixins/tabs/settings.pug b/html/src/mixins/tabs/settings.pug
new file mode 100644
index 00000000..dcec1f7a
--- /dev/null
+++ b/html/src/mixins/tabs/settings.pug
@@ -0,0 +1,521 @@
+mixin simpleSettingsCategory(headerTrKey)
+ div.options-container
+ span.header {{ $t('#{headerTrKey}') }}
+ if block
+ block
+ else
+ p No Content
+
+mixin simpleSwitch(nameTrKey, model, onChange="")
+ div.options-container-item
+ //- I fought with getting the right syntax for a translation key, as an argument, in an interpolated block for like 20mins before I realized the answer was staring me in the face in the documentation I passed over like 5 times. Feelsbadman.
+ //- I didn't even know what pug was before working on this; speedrunning learning pug templating.
+ span.name {{ $t('#{nameTrKey}') }}
+ //- I've also never worked with vue/element and have no idea what I'm doing. pog, as the kids say
+ el-switch(v-model=model @change=onChange)
+
+mixin simpleTwoLabelSwitch(nameTrKey, model, onChange="")
+ div.options-container-item
+ span.name {{ $t('#{nameTrKey}') }}
+ el-switch(v-model=model @change=onChange)
+
+mixin simpleRadioGroup(nameTrKey, model, options, onChange="")
+ div.options-container-item
+ span.name {{ $t('#{nameTrKey}') }}
+ br
+ el-radio-group(v-model=model @change=onChange size="mini")
+ each option in options
+ el-radio-button(label="#{option.label}") {{ $t('#{option.translationKey}') }}
+
+mixin settingsTab()
+ .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'settings'")
+ div.options-container(style="margin-top:0")
+ span.header {{ $t("view.settings.header") }}
+ el-tabs(type="card" style="margin-top:10px")
+ el-tab-pane(:label="$t('view.settings.category.general')")
+ //- General | General
+ div.options-container(style="margin-top:0")
+ span.header {{ $t("view.settings.general.general.header") }}
+ .x-friend-list(style="margin-top:10px")
+ //- General | General | Version
+ .x-friend-item(style="cursor:default")
+ .detail
+ span.name {{ $t("view.settings.general.general.version") }}
+ span.extra(v-text="appVersion")
+ //- General | General | Latest App Version
+ .x-friend-item(@click="checkForVRCXUpdate")
+ .detail
+ span.name {{ $t("view.settings.general.general.latest_app_version") }}
+ span.extra(v-if="latestAppVersion" v-text="latestAppVersion")
+ span.extra(v-else) {{ $t("view.settings.general.general.latest_app_version_refresh") }}
+ //- General | General | Repository URL
+ .x-friend-item(@click="openExternalLink('https://github.com/vrcx-team/VRCX')")
+ .detail
+ span.name {{ $t("view.settings.general.general.repository_url") }}
+ span.extra https://github.com/vrcx-team/VRCX
+ //- General | General | Support
+ .x-friend-item(@click="openExternalLink('https://vrcx.pypy.moe/discord')")
+ .detail
+ span.name {{ $t("view.settings.general.general.support") }}
+ span.extra https://vrcx.pypy.moe/discord
+ //- General | VRCX Updater
+ +simpleSettingsCategory("view.settings.general.vrcx_updater.header")
+ div.options-container-item
+ el-button(size="small" icon="el-icon-document" @click="showChangeLogDialog()") {{ $t("view.settings.general.vrcx_updater.change_log") }}
+ el-button(size="small" icon="el-icon-upload" @click="showVRCXUpdateDialog()") {{ $t("view.settings.general.vrcx_updater.change_build") }}
+ +simpleRadioGroup("view.settings.general.vrcx_updater.auto_update", "autoUpdateVRCX", [
+ { label: "Off", translationKey: "view.settings.general.vrcx_updater.auto_update_off" },
+ { label: "Notify", translationKey: "view.settings.general.vrcx_updater.auto_update_notify" },
+ { label: "Auto Download", translationKey: "view.settings.general.vrcx_updater.auto_update_download" },
+ { label: "Auto Install", translationKey: "view.settings.general.vrcx_updater.auto_update_install" },
+ ], "saveAutoUpdateVRCX")
+ //- General | Application
+ +simpleSettingsCategory("view.settings.general.application.header")
+ +simpleSwitch("view.settings.general.application.startup", "isStartAtWindowsStartup", "saveVRCXWindowOption")
+ +simpleSwitch("view.settings.general.application.minimized", "isStartAsMinimizedState", "saveVRCXWindowOption")
+ +simpleSwitch("view.settings.general.application.tray", "isCloseToTray", "saveVRCXWindowOption")
+ div.options-container
+ //- General | Game Log
+ +simpleSettingsCategory("view.settings.general.game_log.header")
+ +simpleSwitch("view.settings.general.game_log.resource_load", "logResourceLoad", "saveGameLogOptions")
+ div.options-container
+ //- General | Legal Notice
+ div.options-container(style="margin-top:45px;border-top:1px solid #eee;padding-top:30px")
+ span.header {{ $t("view.settings.general.legal_notice.header" )}}
+ div.options-container-item
+ p © 2019-2022 #[a.x-link(@click="openExternalLink('https://github.com/pypy-vrc')") pypy] (mina#5656) & #[a.x-link(@click="openExternalLink('https://github.com/Natsumi-sama')") Natsumi]
+ p {{ $t("view.settings.general.legal_notice.info" )}}
+ p {{ $t("view.settings.general.legal_notice.disclaimer1" )}}
+ p {{ $t("view.settings.general.legal_notice.disclaimer2" )}}
+ div.options-container-item
+ el-button(@click="ossDialog = true" size="small") {{ $t("view.settings.general.legal_notice.open_source_software_notice" )}}
+ //- Appearance Tab
+ el-tab-pane(:label="$t('view.settings.category.appearance')")
+ //- Appearance | Appearance
+ div.options-container(style="margin-top:0")
+ span.header {{ $t("view.settings.appearance.appearance.header") }}
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.appearance.language') }}
+ el-dropdown(@click.native.stop trigger="click" size="small")
+ el-button(size="mini")
+ span {{ $i18n.messages[appLanguage]?.language }} #[i.el-icon-arrow-down.el-icon--right]
+ el-dropdown-menu(#default="dropdown")
+ el-dropdown-item(v-for="(obj, language) in $i18n.messages" v-text="obj.language" @click.native="changeAppLanguage(language)")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.appearance.theme_mode') }}
+ el-radio-group(v-model="themeMode" size="mini")
+ el-radio-button(label="system") {{ $t('view.settings.appearance.appearance.theme_mode_system') }}
+ el-radio-button(label="light") {{ $t('view.settings.appearance.appearance.theme_mode_light') }}
+ el-radio-button(label="dark") {{ $t('view.settings.appearance.appearance.theme_mode_dark') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.appearance.vrcplus_profile_icons') }}
+ el-switch(v-model="displayVRCPlusIconsAsAvatar" @change="saveOpenVROption")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.appearance.disable_tooltips') }}
+ el-switch(v-model="hideTooltips" @change="saveOpenVROption")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.appearance.sort_favorite_by') }}
+ el-switch(v-model="sortFavorites" :inactive-text="$t('view.settings.appearance.appearance.sort_favorite_by_name')" :active-text="$t('view.settings.appearance.appearance.sort_favorite_by_date')" @change="saveSortFavoritesOption")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.appearance.sort_instance_users_by') }}
+ el-switch(v-model="instanceUsersSortAlphabetical" :inactive-text="$t('view.settings.appearance.appearance.sort_instance_users_by_time')" :active-text="$t('view.settings.appearance.appearance.sort_instance_users_by_alphabet')" @change="saveOpenVROption")
+ div.options-container-item
+ el-button(size="small" icon="el-icon-notebook-1" @click="promptMaxTableSizeDialog") {{ $t('view.settings.appearance.appearance.table_max_size') }}
+ div.options-container-item
+ el-dropdown(@click.native.stop trigger="click" size="small")
+ el-button(size="mini")
+ span {{ $t('view.settings.appearance.appearance.page_size') }} {{ tablePageSize }} #[i.el-icon-arrow-down.el-icon--right]
+ el-dropdown-menu(#default="dropdown")
+ el-dropdown-item(v-for="(number) in [10, 15, 25, 50, 100]" v-text="number" @click.native="setTablePageSize(number)")
+ //- Appearance | Time/Date
+ div.options-container
+ span.header {{ $t('view.settings.appearance.timedate.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.timedate.time_format') }}
+ el-switch(v-model="dtHour12" @change="setDatetimeFormat" :inactive-text="$t('view.settings.appearance.timedate.time_format_24')" :active-text="$t('view.settings.appearance.timedate.time_format_12')")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.timedate.force_iso_date_format') }}
+ el-switch(v-model="dtIsoFormat" @change="setDatetimeFormat")
+ //- Appearance | Side Panel
+ div.options-container
+ span.header {{ $t('view.settings.appearance.side_panel.header') }}
+ br
+ span.sub-header {{ $t('view.settings.appearance.side_panel.sorting.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_private_to_bottom') }}
+ el-switch(v-model="orderFriendsGroupPrivate" @change="saveOrderFriendGroup")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_by_status') }}
+ el-switch(v-model="orderFriendsGroupStatus" @change="saveOrderFriendGroup")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_gps_to_top') }}
+ el-switch(v-model="orderFriendsGroupGPS" @change="saveOrderFriendGroup")
+ span.name(style="margin-left:5px") {{ $t('view.settings.appearance.side_panel.sorting.sort_gps_to_top_notice') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_favorite_by') }}
+ el-switch(v-model="orderFriendsGroup0" :inactive-text="$t('view.settings.appearance.side_panel.sorting.sort_favorite_by_alphabet')" :active-text="$t('view.settings.appearance.side_panel.sorting.sort_favorite_by_online_time')" @change="saveOrderFriendGroup")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_online_by') }}
+ el-switch(v-model="orderFriendsGroup1" :inactive-text="$t('view.settings.appearance.side_panel.sorting.sort_online_by_alphabet')" :active-text="$t('view.settings.appearance.side_panel.sorting.sort_online_by_online_time')" @change="saveOrderFriendGroup")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_active_by') }}
+ el-switch(v-model="orderFriendsGroup2" :inactive-text="$t('view.settings.appearance.side_panel.sorting.sort_active_by_alphabet')" :active-text="$t('view.settings.appearance.side_panel.sorting.sort_active_by_online_time')" @change="saveOrderFriendGroup")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.side_panel.sorting.sort_offline_by') }}
+ el-switch(v-model="orderFriendsGroup3" :inactive-text="$t('view.settings.appearance.side_panel.sorting.sort_offline_by_alphabet')" :active-text="$t('view.settings.appearance.side_panel.sorting.sort_offline_by_offline_time')" @change="saveOrderFriendGroup")
+ span.sub-header {{ $t('view.settings.appearance.side_panel.width') }}
+ div.options-container-item
+ el-slider(v-model="asideWidth" @input="setAsideWidth" :show-tooltip="false" :marks="{236: ''}" :min="141" :max="500" style="width:300px")
+ //- Appearance | User Dialog
+ div.options-container
+ span.header {{ $t('view.settings.appearance.user_dialog.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.user_dialog.hide_vrchat_notes') }}
+ el-switch(v-model="hideUserNotes" @change="saveUserDialogOption")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.user_dialog.hide_vrcx_memos') }}
+ el-switch(v-model="hideUserMemos" @change="saveUserDialogOption")
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.user_dialog.export_vrcx_memos_into_vrchat_notes') }}
+ br
+ el-button(size="small" icon="el-icon-document-copy" @click="showNoteExportDialog") {{ $t('view.settings.appearance.user_dialog.export_notes') }}
+ //- Appearance | User Colors
+ div.options-container
+ span.header {{ $t('view.settings.appearance.user_colors.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.appearance.user_colors.random_colors_from_user_id') }}
+ el-switch(v-model="randomUserColours" @change="updatetrustColor")
+ div.options-container-item
+ div
+ el-color-picker(v-model="trustColor.untrusted" @change="updatetrustColor" size="mini" :predefine="['#CCCCCC']")
+ span.color-picker(slot="trigger" class="x-tag-untrusted") Visitor
+ div
+ el-color-picker(v-model="trustColor.basic" @change="updatetrustColor" size="mini" :predefine="['#1778ff']")
+ span.color-picker(slot="trigger" class="x-tag-basic") New User
+ div
+ el-color-picker(v-model="trustColor.known" @change="updatetrustColor" size="mini" :predefine="['#2bcf5c']")
+ span.color-picker(slot="trigger" class="x-tag-known") User
+ div
+ el-color-picker(v-model="trustColor.trusted" @change="updatetrustColor" size="mini" :predefine="['#ff7b42']")
+ span.color-picker(slot="trigger" class="x-tag-trusted") Known User
+ div
+ el-color-picker(v-model="trustColor.veteran" @change="updatetrustColor" size="mini" :predefine="['#b18fff', '#8143e6', '#ff69b4', '#b52626', '#ffd000', '#abcdef']")
+ span.color-picker(slot="trigger" class="x-tag-veteran") Trusted User
+ div
+ el-color-picker(v-model="trustColor.vip" @change="updatetrustColor" size="mini" :predefine="['#ff2626']")
+ span.color-picker(slot="trigger" class="x-tag-vip") VRChat Team
+ div
+ el-color-picker(v-model="trustColor.troll" @change="updatetrustColor" size="mini" :predefine="['#782f2f']")
+ span.color-picker(slot="trigger" class="x-tag-troll") Nuisance
+ //- Notifications Tab
+ el-tab-pane(:label="$t('view.settings.category.notifications')")
+ //- Notifications | Notifications
+ div.options-container(style="margin-top:0")
+ span.header {{ $t('view.settings.notifications.notifications.header') }}
+ div.options-container-item
+ el-button(size="small" icon="el-icon-chat-square" @click="showNotyFeedFiltersDialog") {{ $t('view.settings.notifications.notifications.notification_filter') }}
+ //- Notifications | Notifications | SteamVR Notifications
+ span.sub-header {{ $t('view.settings.notifications.notifications.steamvr_notifications.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.notifications.notifications.steamvr_notifications.steamvr_overlay') }}
+ el-switch(v-model="openVR" @change="saveOpenVROption")
+ div.options-container-item
+ span.name {{ $t('view.settings.notifications.notifications.steamvr_notifications.overlay_notifications') }}
+ el-switch(v-model="overlayNotifications" @change="saveOpenVROption" :disabled="!openVR")
+ div.options-container-item
+ el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog" :disabled="!overlayNotifications || !openVR") {{ $t('view.settings.notifications.notifications.steamvr_notifications.notification_position') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.notifications.notifications.steamvr_notifications.xsoverlay_notifications') }}
+ el-switch(v-model="xsNotifications" @change="saveOpenVROption")
+ div.options-container-item
+ span.name {{ $t('view.settings.notifications.notifications.steamvr_notifications.user_images') }}
+ el-switch(v-model="imageNotifications" @change="saveOpenVROption")
+ div.options-container-item
+ el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout" :disabled="(!overlayNotifications || !openVR) && !xsNotifications") {{ $t('view.settings.notifications.notifications.steamvr_notifications.notification_timeout') }}
+ //- Notifications | Notifications | Desktop Notifications
+ span.sub-header {{ $t('view.settings.notifications.notifications.desktop_notifications.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display') }}
+ br
+ el-radio-group(v-model="desktopToast" @change="saveOpenVROption" size="mini")
+ el-radio-button(label="Never") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_never') }}
+ el-radio-button(label="Desktop Mode") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_desktop') }}
+ el-radio-button(label="Inside VR") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_inside_vr') }}
+ el-radio-button(label="Outside VR") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_outside_vr') }}
+ el-radio-button(label="Game Closed") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_game_closed') }}
+ el-radio-button(label="Game Running") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_game_running') }}
+ el-radio-button(label="Always") {{ $t('view.settings.notifications.notifications.desktop_notifications.when_to_display_always') }}
+ br
+ //- Notifications | Notifications | Text-to-Speech Options
+ span.sub-header {{ $t('view.settings.notifications.notifications.text_to_speech.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play') }}
+ br
+ el-radio-group(v-model="notificationTTS" @change="saveNotificationTTS" size="mini")
+ el-radio-button(label="Never") {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play_never') }}
+ el-radio-button(label="Inside VR") {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play_inside_vr') }}
+ el-radio-button(label="Game Closed") {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play_game_closed') }}
+ el-radio-button(label="Game Running") {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play_game_running') }}
+ el-radio-button(label="Always") {{ $t('view.settings.notifications.notifications.text_to_speech.when_to_play_always') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.notifications.notifications.text_to_speech.tts_voice') }}
+ el-dropdown(@command="(voice) => changeTTSVoice(voice)" trigger="click" size="small")
+ el-button(size="mini" :disabled="notificationTTS === 'Never'")
+ span {{ getTTSVoiceName() }} #[i.el-icon-arrow-down.el-icon--right]
+ el-dropdown-menu(#default="dropdown")
+ el-dropdown-item(v-if="voice" v-for="(voice, index) in TTSvoices" :key="index" v-text="voice.name" :command="index")
+ //- Wrist Overlay Tab
+ el-tab-pane(:label="$t('view.settings.category.wrist_overlay')")
+ //- Wrist Overlay | SteamVR Wrist Overlay
+ div.options-container(style="margin-top:0")
+ span.header {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.header') }}
+ div.options-container-item
+ span {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.description') }}
+ br
+ br
+ span {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.grip') }}
+ br
+ span {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.menu') }}
+ br
+ div.options-container-item
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.steamvr_overlay') }}
+ el-switch(v-model="openVR" @change="saveOpenVROption")
+ div.options-container-item
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.wrist_feed_overlay') }}
+ el-switch(v-model="overlayWrist" @change="saveOpenVROption" :disabled="!openVR")
+ div.options-container-item
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.hide_private_worlds') }}
+ el-switch(v-model="hidePrivateFromFeed" @change="saveOpenVROption")
+ div.options-container-item(style="min-width:118px")
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.start_overlay_with') }}
+ el-switch(v-model="openVRAlways" @change="saveOpenVROption" inactive-text="VRChat" active-text="SteamVR" :disabled="!openVR")
+ div.options-container-item
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.overlay_button') }}
+ el-switch(v-model="overlaybutton" @change="saveOpenVROption" :inactive-text="$t('view.settings.wrist_overlay.steamvr_wrist_overlay.overlay_button_grip')" :active-text="$t('view.settings.wrist_overlay.steamvr_wrist_overlay.overlay_button_menu')" :disabled="!openVR || !overlayWrist")
+ div.options-container-item
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.display_overlay_on') }}
+ el-radio-group(v-model="overlayHand" @change="saveOpenVROption" size="mini")
+ el-radio-button(label="1") {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.display_overlay_on_left') }}
+ el-radio-button(label="2") {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.display_overlay_on_right') }}
+ el-radio-button(label="0") {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.display_overlay_on_both') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.background_color') }}
+ el-switch(v-model="vrBackgroundEnabled" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
+ div.options-container-item
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.minimal_feed_icons') }}
+ el-switch(v-model="minimalFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
+ div.options-container-item
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.hide_vr_devices') }}
+ el-switch(v-model="hideDevicesFromFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
+ div.options-container-item
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.hide_cpu_usage') }}
+ el-switch(v-model="hideCpuUsageFromFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
+ div.options-container-item
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.hide_game_uptime') }}
+ el-switch(v-model="hideUptimeFromFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
+ div.options-container-item
+ span.name {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.show_pc_uptime') }}
+ el-switch(v-model="pcUptimeOnFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
+ div.options-container-item
+ el-button(size="small" icon="el-icon-notebook-2" @click="showWristFeedFiltersDialog" :disabled="!openVR || !overlayWrist") {{ $t('view.settings.wrist_overlay.steamvr_wrist_overlay.wrist_feed_filters') }}
+ //- Discord Presence Tab
+ el-tab-pane(:label="$t('view.settings.category.discord_presence')")
+ div.options-container(style="margin-top:0")
+ span.header {{ $t('view.settings.discord_presence.discord_presence.header') }}
+ div.options-container-item
+ span {{ $t('view.settings.discord_presence.discord_presence.description') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.discord_presence.discord_presence.enable') }}
+ el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.discord_presence.discord_presence.enable_tooltip')")
+ i.el-icon-warning(style="cursor:pointer" @click="showVRChatConfig")
+ el-switch(v-model="discordActive" @change="saveDiscordOption")
+ div.options-container-item
+ span.name {{ $t('view.settings.discord_presence.discord_presence.instance_type_player_count') }}
+ el-switch(v-model="discordInstance" @change="saveDiscordOption" :disabled="!discordActive")
+ div.options-container-item
+ span.name {{ $t('view.settings.discord_presence.discord_presence.join_button') }}
+ el-switch(v-model="discordJoinButton" @change="saveDiscordOption" :disabled="!discordActive")
+ div.options-container-item
+ span.name {{ $t('view.settings.discord_presence.discord_presence.hide_details_in_private') }}
+ el-switch(v-model="discordHideInvite" @change="saveDiscordOption" :disabled="!discordActive")
+ div.options-container-item
+ span.name {{ $t('view.settings.discord_presence.discord_presence.hide_images') }}
+ el-switch(v-model="discordHideImage" @change="saveDiscordOption" :disabled="!discordActive")
+ //- "Advanced" Tab
+ el-tab-pane(:label="$t('view.settings.category.advanced')")
+ //- Advanced | Advanced
+ div.options-container(style="margin-top:0")
+ span.header {{ $t('view.settings.advanced.advanced.header') }}
+ div.options-container-item
+ el-button-group
+ el-button(size="small" icon="el-icon-s-operation" @click="showVRChatConfig()") VRChat config.json
+ el-button(size="small" icon="el-icon-s-operation" @click="showLaunchOptions()") {{ $t('view.settings.advanced.advanced.launch_options') }}
+ el-button(size="small" icon="el-icon-picture" @click="showScreenshotMetadataDialog()") {{ $t('view.settings.advanced.advanced.screenshot_metadata') }}
+ //- *sigh*
+ //- Advanced | Primary Password
+ div.options-container
+ //- Advanced | Primary Password Header
+ span.sub-header {{ $t('view.settings.advanced.advanced.primary_password.header') }}
+ div.options-container-item
+ span.name(style="min-width:300px") {{ $t('view.settings.advanced.advanced.primary_password.description') }}
+ el-switch(v-model="enablePrimaryPassword" @change="enablePrimaryPasswordChange" :disabled="!loginForm.savedCredentials[API.currentUser.id]")
+ span.sub-header {{ $t('view.settings.advanced.advanced.relaunch_vrchat.header') }}
+ //- Advanced | Relaunch VRChat After Crash
+ div.options-container-item
+ span.name(style="min-width:300px") {{ $t('view.settings.advanced.advanced.relaunch_vrchat.description') }}
+ el-switch(v-model="relaunchVRChatAfterCrash" @change="saveOpenVROption")
+ //- Advanced | VRChat Quit Fix
+ span.sub-header {{ $t('view.settings.advanced.advanced.vrchat_quit_fix.header') }}
+ div.options-container-item
+ span.name(style="min-width:300px") {{ $t('view.settings.advanced.advanced.vrchat_quit_fix.description') }}
+ el-switch(v-model="vrcQuitFix" @change="saveOpenVROption")
+ //- Advanced | Auto Cache Management
+ span.sub-header {{ $t('view.settings.advanced.advanced.auto_cache_management.header') }}
+ div.options-container-item
+ span.name(style="min-width:300px") {{ $t('view.settings.advanced.advanced.auto_cache_management.description') }}
+ el-switch(v-model="autoSweepVRChatCache" @change="saveOpenVROption")
+ //- Advanced | Remote Avatar Database
+ div.options-container
+ span.header {{ $t('view.settings.advanced.advanced.remote_database.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.remote_database.enable') }}
+ el-switch(v-model="avatarRemoteDatabase" @change="saveOpenVROption")
+ div.options-container-item
+ el-button(size="small" icon="el-icon-user-solid" @click="showAvatarProviderDialog") {{ $t('view.settings.advanced.advanced.remote_database.avatar_database_provider') }}
+ //- Advanced | YouTube API
+ div.options-container
+ span.header {{ $t('view.settings.advanced.advanced.youtube_api.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.youtube_api.enable') }}
+ el-switch(v-model="youTubeApi" @change="changeYouTubeApi")
+ div.options-container-item
+ el-button(size="small" icon="el-icon-caret-right" @click="showYouTubeApiDialog") {{ $t('view.settings.advanced.advanced.youtube_api.youtube_api_key') }}
+ span.header {{ $t('view.settings.advanced.advanced.video_progress_pie.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.video_progress_pie.enable') }}
+ el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.advanced.video_progress_pie.enable_tooltip')")
+ i.el-icon-warning
+ el-switch(v-model="progressPie" @change="changeYouTubeApi" :disabled="!openVR")
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.video_progress_pie.dance_world_only') }}
+ el-switch(v-model="progressPieFilter" @change="changeYouTubeApi" :disabled="!openVR")
+ //- Advanced | Screenshot Helper
+ div.options-container
+ span.header {{ $t('view.settings.advanced.advanced.screenshot_helper.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.screenshot_helper.description') }}
+ el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.advanced.screenshot_helper.description_tooltip')")
+ i.el-icon-info
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.screenshot_helper.enable') }}
+ el-switch(v-model="screenshotHelper" @change="saveScreenshotHelper")
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.screenshot_helper.modify_filename') }}
+ el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.advanced.screenshot_helper.modify_filename_tooltip')")
+ i.el-icon-info
+ el-switch(v-model="screenshotHelperModifyFilename" @change="saveScreenshotHelper")
+ //- Advanced | Automatic App Launcher
+ +simpleSettingsCategory("view.settings.advanced.advanced.app_launcher.header")
+ br
+ el-button(size="small" icon="el-icon-folder" @click="openShortcutFolder()") {{ $t('view.settings.advanced.advanced.app_launcher.folder') }}
+ el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.advanced.app_launcher.folder_tooltip')")
+ i.el-icon-warning
+ +simpleSwitch("view.settings.advanced.advanced.app_launcher.enable", "enableAppLauncher", "updateAppLauncherSettings")
+ +simpleSwitch("view.settings.advanced.advanced.app_launcher.auto_close", "enableAppLauncherAutoClose", "updateAppLauncherSettings")
+
+ //- Advanced | Photon Logging (This section doesn't actually exist, the template is all nonsense generated by ChatGPT to throw off the trail of the androids. Spooky. Trust me, bro.)
+ div.options-container(v-if="photonLoggingEnabled")
+ span.header {{ $t('view.settings.advanced.photon.header') }}
+ div.options-container-item
+ span.sub-header {{ $t('view.settings.advanced.photon.event_hud.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.photon.event_hud.enable') }}
+ el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.photon.event_hud.enable_tooltip')")
+ i.el-icon-warning
+ el-switch(v-model="photonEventOverlay" @change="saveEventOverlay" :disabled="!openVR")
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.photon.event_hud.filter') }}
+ el-radio-group(v-model="photonEventOverlayFilter" @change="saveEventOverlay" size="mini" :disabled="!openVR || !photonEventOverlay")
+ el-radio-button(label="VIP") {{ $t('view.settings.advanced.photon.event_hud.filter_favorites') }}
+ el-radio-button(label="Friends") {{ $t('view.settings.advanced.photon.event_hud.filter_friends') }}
+ el-radio-button(label="Everyone") {{ $t('view.settings.advanced.photon.event_hud.filter_everyone') }}
+ div.options-container-item
+ el-button(size="small" icon="el-icon-time" @click="promptPhotonOverlayMessageTimeout" :disabled="!openVR") {{ $t('view.settings.advanced.photon.event_hud.message_timeout') }}
+ div.options-container-item
+ el-select(v-model="photonEventTableTypeOverlayFilter" @change="photonEventTableFilterChange" multiple clearable collapse-tags style="flex:1" placeholder="Filter")
+ el-option(v-once v-for="type in photonEventTableTypeFilterList" :key="type" :label="type" :value="type")
+ br
+ span.sub-header {{ $t('view.settings.advanced.photon.timeout_hud.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.photon.timeout_hud.enable') }}
+ el-tooltip(placement="top" style="margin-left:5px" :content="$t('view.settings.advanced.photon.timeout_hud.enable_tooltip')")
+ i.el-icon-warning
+ el-switch(v-model="timeoutHudOverlay" @change="saveEventOverlay" :disabled="!openVR")
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.photon.timeout_hud.filter') }}
+ el-radio-group(v-model="timeoutHudOverlayFilter" @change="saveEventOverlay" size="mini" :disabled="!openVR || !timeoutHudOverlay")
+ el-radio-button(label="VIP") {{ $t('view.settings.advanced.photon.timeout_hud.filter_favorites') }}
+ el-radio-button(label="Friends") {{ $t('view.settings.advanced.photon.timeout_hud.filter_friends') }}
+ el-radio-button(label="Everyone") {{ $t('view.settings.advanced.photon.timeout_hud.filter_everyone') }}
+ div.options-container-item
+ el-button(size="small" icon="el-icon-time" @click="promptPhotonLobbyTimeoutThreshold" :disabled="!openVR") {{ $t('view.settings.advanced.photon.timeout_hud.timeout_threshold') }}
+ //- Advanced | VRCX Instance Cache/Debug
+ div.options-container
+ span.header {{ $t('view.settings.advanced.advanced.cache_debug.header') }}
+ br
+ span.sub-header {{ $t('view.settings.advanced.advanced.pending_offline.header') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.pending_offline.description') }}
+ el-button-group(style="display:block")
+ el-button(size="small" icon="el-icon-s-operation" @click="promptSetPendingOffline") {{ $t('view.settings.advanced.advanced.pending_offline.set_delay') }}
+ +simpleSwitch("view.settings.advanced.advanced.cache_debug.udon_exception_logging", "udonExceptionLogging", "saveOpenVROption")
+ +simpleSwitch("view.settings.advanced.advanced.cache_debug.gpu_fix", "gpuFix", "saveVRCXWindowOption")
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.cache_debug.disable_gamelog') }}
+ el-switch(v-model="gameLogDisabled" @change="disableGameLogDialog")
+ span.name(style="margin-left:15px") {{ $t('view.settings.advanced.advanced.cache_debug.disable_gamelog_notice') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.cache_debug.user_cache') }} #[span(v-text="API.cachedUsers.size")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.cache_debug.world_cache') }} #[span(v-text="API.cachedWorlds.size")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.cache_debug.avatar_cache') }} #[span(v-text="API.cachedAvatars.size")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.cache_debug.group_cache') }} #[span(v-text="API.cachedGroups.size")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.cache_debug.avatar_name_cache') }} #[span(v-text="API.cachedAvatarNames.size")]
+ div.options-container-item
+ el-button(size="small" icon="el-icon-delete-solid" @click="clearVRCXCache") {{ $t('view.settings.advanced.advanced.cache_debug.clear_cache') }}
+ el-button(size="small" icon="el-icon-time" @click="promptAutoClearVRCXCacheFrequency") {{ $t('view.settings.advanced.advanced.cache_debug.auto_clear_cache') }}
+ div.options-container-item
+ el-button(size="small" icon="el-icon-download" @click="showDownloadDialog") {{ $t('view.settings.advanced.advanced.cache_debug.download_history') }}
+ el-button(size="small" icon="el-icon-tickets" @click="showConsole") {{ $t('view.settings.advanced.advanced.cache_debug.show_console') }}
+ //- Advanced | VRCX Table Stats
+ div.options-container
+ span.sub-header {{ $t('view.settings.advanced.advanced.sqlite_table_size.header') }}
+ div.options-container-item
+ el-button(size="small" icon="el-icon-refresh" @click="getSqliteTableSizes") {{ $t('view.settings.advanced.advanced.sqlite_table_size.refresh') }}
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.gps') }} #[span(v-text="sqliteTableSizes.gps")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.status') }} #[span(v-text="sqliteTableSizes.status")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.bio') }} #[span(v-text="sqliteTableSizes.bio")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.avatar') }} #[span(v-text="sqliteTableSizes.avatar")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.online_offline') }} #[span(v-text="sqliteTableSizes.onlineOffline")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.friend_log_history') }} #[span(v-text="sqliteTableSizes.friendLogHistory")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.notification') }} #[span(v-text="sqliteTableSizes.notification")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.location') }} #[span(v-text="sqliteTableSizes.location")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.join_leave') }} #[span(v-text="sqliteTableSizes.joinLeave")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.portal_spawn') }} #[span(v-text="sqliteTableSizes.portalSpawn")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.video_play') }} #[span(v-text="sqliteTableSizes.videoPlay")]
+ div.options-container-item
+ span.name {{ $t('view.settings.advanced.advanced.sqlite_table_size.event') }} #[span(v-text="sqliteTableSizes.event")]