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")]