mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-30 04:03:48 +02:00
refactor: app.js (#1291)
* refactor: frontend * Fix avatar gallery sort * Update .NET dependencies * Update npm dependencies electron v37.1.0 * bulkRefreshFriends * fix dark theme * Remove crowdin * Fix config.json dialog not updating * VRCX log file fixes & add Cef log * Remove SharedVariable, fix startup * Revert init theme change * Logging date not working? Fix WinformThemer designer error * Add Cef request hander, no more escaping main page * clean * fix * fix * clean * uh * Apply thememode at startup, fixes random user colours * Split database into files * Instance info remove empty lines * Open external VRC links with VRCX * Electron fixes * fix userdialog style * ohhhh * fix store * fix store * fix: load all group members after kicking a user * fix: world dialog favorite button style * fix: Clear VRCX Cache Timer input value * clean * Fix VR overlay * Fix VR overlay 2 * Fix Discord discord rich presence for RPC worlds * Clean up age verified user tags * Fix playerList being occupied after program reload * no `this` * Fix login stuck loading * writable: false * Hide dialogs on logout * add flush sync option * rm LOGIN event * rm LOGOUT event * remove duplicate event listeners * remove duplicate event listeners * clean * remove duplicate event listeners * clean * fix theme style * fix t * clearable * clean * fix ipcEvent * Small changes * Popcorn Palace support * Remove checkActiveFriends * Clean up * Fix dragEnterCef * Block API requests when not logged in * Clear state on login & logout * Fix worldDialog instances not updating * use <script setup> * Fix avatar change event, CheckGameRunning at startup * Fix image dragging * fix * Remove PWI * fix updateLoop * add webpack-dev-server to dev environment * rm unnecessary chunks * use <script setup> * webpack-dev-server changes * use <script setup> * use <script setup> * Fix UGC text size * Split login event * t * use <script setup> * fix * Update .gitignore and enable checkJs in jsconfig * fix i18n t * use <script setup> * use <script setup> * clean * global types * fix * use checkJs for debugging * Add watchState for login watchers * fix .vue template * type fixes * rm Vue.filter * Cef v138.0.170, VC++ 2022 * Settings fixes * Remove 'USER:CURRENT' * clean up 2FA callbacks * remove userApply * rm i18n import * notification handling to use notification store methods * refactor favorite handling to use favorite store methods and clean up event emissions * refactor moderation handling to use dedicated functions for player moderation events * refactor friend handling to use dedicated functions for friend events * Fix program startup, move lang init * Fix friend state * Fix status change error * Fix user notes diff * fix * rm group event * rm auth event * rm avatar event * clean * clean * getUser * getFriends * getFavoriteWorlds, getFavoriteAvatars * AvatarGalleryUpload btn style & package.json update * Fix friend requests * Apply user * Apply world * Fix note diff * Fix VR overlay * Fixes * Update build scripts * Apply avatar * Apply instance * Apply group * update hidden VRC+ badge * Fix sameInstance "private" * fix 502/504 API errors * fix 502/504 API errors * clean * Fix friend in same instance on orange showing twice in friends list * Add back in broken friend state repair methods * add types --------- Co-authored-by: Natsumi <cmcooper123@hotmail.com>
This commit is contained in:
@@ -1,60 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"extends": ["eslint:recommended", "plugin:vue/recommended", "prettier"],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"commonjs": true,
|
|
||||||
"es2021": true
|
|
||||||
},
|
|
||||||
"parser": "vue-eslint-parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"parser": "@babel/eslint-parser",
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"sourceType": "module",
|
|
||||||
"ecmaFeatures": {
|
|
||||||
"impliedStrict": true,
|
|
||||||
"jsx": true
|
|
||||||
},
|
|
||||||
"requireConfigFile": false,
|
|
||||||
"babelOptions": {
|
|
||||||
"parserOpts": {
|
|
||||||
"plugins": ["importAssertions"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"CefSharp": "readonly",
|
|
||||||
"VRCX": "readonly",
|
|
||||||
"VRCXStorage": "readonly",
|
|
||||||
"SQLite": "readonly",
|
|
||||||
"LogWatcher": "readonly",
|
|
||||||
"Discord": "readonly",
|
|
||||||
"AppApi": "readonly",
|
|
||||||
"AppApiVr": "readonly",
|
|
||||||
"SharedVariable": "readonly",
|
|
||||||
"WebApi": "readonly",
|
|
||||||
"AssetBundleManager": "readonly",
|
|
||||||
"WINDOWS": "readonly",
|
|
||||||
"LINUX": "readonly"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-console": 0,
|
|
||||||
"no-control-regex": 0,
|
|
||||||
"no-empty": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"allowEmptyCatch": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-var": "warn",
|
|
||||||
"prefer-const": "warn",
|
|
||||||
"no-loop-func": 0,
|
|
||||||
"vars-on-top": 0,
|
|
||||||
"object-curly-spacing": ["error", "always"],
|
|
||||||
"require-atomic-updates": 0,
|
|
||||||
"no-unused-vars": 1,
|
|
||||||
"vue/require-default-prop": 0,
|
|
||||||
"vue/no-mutating-props": 1,
|
|
||||||
"vue/no-v-text-v-html-on-component": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,4 +5,6 @@ obj1/
|
|||||||
.idea/
|
.idea/
|
||||||
*.log
|
*.log
|
||||||
*.DotSettings.user
|
*.DotSettings.user
|
||||||
|
.vs/
|
||||||
Installer/version_define.nsh
|
Installer/version_define.nsh
|
||||||
|
bun.lock
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="NLog" Version="5.5.0" />
|
<PackageReference Include="NLog" Version="6.0.1" />
|
||||||
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
||||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using NLog.Fluent;
|
|
||||||
using SQLite;
|
using SQLite;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|||||||
@@ -168,10 +168,8 @@ namespace DBMerger
|
|||||||
{
|
{
|
||||||
FileName = "DBMerger.log",
|
FileName = "DBMerger.log",
|
||||||
Layout = "${longdate} [${level:uppercase=true:padding=-5}] ${logger:padding=-20} - ${message} ${exception:format=tostring}",
|
Layout = "${longdate} [${level:uppercase=true:padding=-5}] ${logger:padding=-20} - ${message} ${exception:format=tostring}",
|
||||||
ArchiveFileName = Path.Combine("DBMerger_Logs", "DBMerger.{#}.log"),
|
ArchiveSuffixFormat = "{1:yyyy-MM-dd.HH-mm-ss}",
|
||||||
ArchiveNumbering = ArchiveNumberingMode.DateAndSequence,
|
|
||||||
ArchiveOldFileOnStartup = true,
|
ArchiveOldFileOnStartup = true,
|
||||||
ConcurrentWrites = true,
|
|
||||||
KeepFileOpen = true,
|
KeepFileOpen = true,
|
||||||
AutoFlush = true,
|
AutoFlush = true,
|
||||||
Encoding = System.Text.Encoding.UTF8
|
Encoding = System.Text.Encoding.UTF8
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace VRCX
|
|||||||
public void VrInit()
|
public void VrInit()
|
||||||
{
|
{
|
||||||
if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
||||||
MainForm.Instance.Browser.ExecuteScriptAsync("$app.vrInit", "");
|
MainForm.Instance.Browser.ExecuteScriptAsync("$app.store.vr.vrInit", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleSystemMonitor(bool enabled)
|
public void ToggleSystemMonitor(bool enabled)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace VRCX
|
|||||||
|
|
||||||
// TODO: fix this throwing an exception for being called before the browser is ready. somehow it gets past the checks
|
// TODO: fix this throwing an exception for being called before the browser is ready. somehow it gets past the checks
|
||||||
if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
||||||
MainForm.Instance.Browser.ExecuteScriptAsync("$app.updateIsGameRunning", isGameRunning, isSteamVRRunning, isHmdAfk);
|
MainForm.Instance.Browser.ExecuteScriptAsync("$app.store.game.updateIsGameRunning", isGameRunning, isSteamVRRunning, isHmdAfk);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsGameRunning()
|
public override bool IsGameRunning()
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ namespace VRCX
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// forgive me father for i have sinned once again
|
if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
||||||
Program.AppApiInstance.ExecuteAppFunction("dragEnterCef", file);
|
MainForm.Instance.Browser.ExecuteScriptAsync("$app.store.vrcx.dragEnterCef", file);
|
||||||
|
|
||||||
dragData.Dispose();
|
dragData.Dispose();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
74
Dotnet/Cef/CefCustomRequestHandler.cs
Normal file
74
Dotnet/Cef/CefCustomRequestHandler.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright(c) 2019-2025 pypy, Natsumi and individual contributors.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This work is licensed under the terms of the MIT license.
|
||||||
|
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||||
|
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using CefSharp;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
|
namespace VRCX
|
||||||
|
{
|
||||||
|
public class CustomRequestHandler : IRequestHandler
|
||||||
|
{
|
||||||
|
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
|
||||||
|
{
|
||||||
|
if (Program.LaunchDebug ||
|
||||||
|
request.Url.StartsWith("file://vrcx/") ||
|
||||||
|
request.Url.StartsWith("chrome-extension://"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_logger.Error("Blocking navigation to: {Url}", request.Url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDocumentAvailableInMainFrame(IWebBrowser chromiumWebBrowser, IBrowser browser)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnOpenUrlFromTab(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl,
|
||||||
|
WindowOpenDisposition targetDisposition, bool userGesture)
|
||||||
|
{
|
||||||
|
_logger.Debug("Blocking OnOpenUrlFromTab: {TargetUrl}",
|
||||||
|
targetUrl);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame,
|
||||||
|
IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetAuthCredentials(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, bool isProxy, string host,
|
||||||
|
int port, string realm, string scheme, IAuthCallback callback)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnCertificateError(IWebBrowser chromiumWebBrowser, IBrowser browser, CefErrorCode errorCode, string requestUrl,
|
||||||
|
ISslInfo sslInfo, IRequestCallback callback)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnSelectClientCertificate(IWebBrowser chromiumWebBrowser, IBrowser browser, bool isProxy, string host, int port,
|
||||||
|
X509Certificate2Collection certificates, ISelectClientCertificateCallback callback)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnRenderViewReady(IWebBrowser chromiumWebBrowser, IBrowser browser)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnRenderProcessTerminated(IWebBrowser chromiumWebBrowser, IBrowser browser, CefTerminationStatus status,
|
||||||
|
int errorCode, string errorMessage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,8 @@ namespace VRCX
|
|||||||
{
|
{
|
||||||
RootCachePath = userDataDir,
|
RootCachePath = userDataDir,
|
||||||
CachePath = Path.Join(userDataDir, "cache"),
|
CachePath = Path.Join(userDataDir, "cache"),
|
||||||
LogSeverity = LogSeverity.Disable,
|
LogSeverity = Program.LaunchDebug ? LogSeverity.Verbose : LogSeverity.Error,
|
||||||
|
LogFile = Path.Join(Program.AppDataDirectory, "logs", "cef.log"),
|
||||||
WindowlessRenderingEnabled = true,
|
WindowlessRenderingEnabled = true,
|
||||||
PersistSessionCookies = true,
|
PersistSessionCookies = true,
|
||||||
UserAgent = Program.Version,
|
UserAgent = Program.Version,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ namespace VRCX
|
|||||||
{
|
{
|
||||||
repository.NameConverter = null;
|
repository.NameConverter = null;
|
||||||
repository.Register("AppApi", Program.AppApiInstance);
|
repository.Register("AppApi", Program.AppApiInstance);
|
||||||
repository.Register("SharedVariable", SharedVariable.Instance);
|
|
||||||
repository.Register("WebApi", WebApi.Instance);
|
repository.Register("WebApi", WebApi.Instance);
|
||||||
repository.Register("VRCXStorage", VRCXStorage.Instance);
|
repository.Register("VRCXStorage", VRCXStorage.Instance);
|
||||||
repository.Register("SQLite", SQLiteLegacy.Instance);
|
repository.Register("SQLite", SQLiteLegacy.Instance);
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Net;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
@@ -15,6 +15,7 @@ using NLog;
|
|||||||
|
|
||||||
namespace VRCX
|
namespace VRCX
|
||||||
{
|
{
|
||||||
|
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
|
||||||
public partial class MainForm : WinformBase
|
public partial class MainForm : WinformBase
|
||||||
{
|
{
|
||||||
public static MainForm Instance;
|
public static MainForm Instance;
|
||||||
@@ -50,30 +51,40 @@ namespace VRCX
|
|||||||
logger.Error(ex);
|
logger.Error(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Browser = new ChromiumWebBrowser("file://vrcx/index.html")
|
Browser = new ChromiumWebBrowser(Program.LaunchDebug ? "http://localhost:9000/index.html" : "file://vrcx/index.html")
|
||||||
{
|
{
|
||||||
DragHandler = new CustomDragHandler(),
|
DragHandler = new CustomDragHandler(),
|
||||||
MenuHandler = new CustomMenuHandler(),
|
MenuHandler = new CustomMenuHandler(),
|
||||||
DownloadHandler = new CustomDownloadHandler(),
|
DownloadHandler = new CustomDownloadHandler(),
|
||||||
|
RequestHandler = new CustomRequestHandler(),
|
||||||
BrowserSettings =
|
BrowserSettings =
|
||||||
{
|
{
|
||||||
DefaultEncoding = "UTF-8",
|
DefaultEncoding = "UTF-8",
|
||||||
},
|
},
|
||||||
Dock = DockStyle.Fill
|
Dock = DockStyle.Fill
|
||||||
};
|
};
|
||||||
|
Browser.IsBrowserInitializedChanged += (_, _) =>
|
||||||
Browser.IsBrowserInitializedChanged += (A, B) =>
|
|
||||||
{
|
{
|
||||||
if (Program.LaunchDebug)
|
if (Program.LaunchDebug)
|
||||||
Browser.ShowDevTools();
|
Browser.ShowDevTools();
|
||||||
};
|
};
|
||||||
|
Browser.AddressChanged += (_, addressChangedEventArgs) =>
|
||||||
JavascriptBindings.ApplyAppJavascriptBindings(Browser.JavascriptObjectRepository);
|
|
||||||
Browser.ConsoleMessage += (_, args) =>
|
|
||||||
{
|
{
|
||||||
logger.Debug(args.Message + " (" + args.Source + ":" + args.Line + ")");
|
logger.Debug("Address changed: " + addressChangedEventArgs.Address);
|
||||||
|
};
|
||||||
|
Browser.LoadingStateChanged += (_, loadingFailedEventArgs) =>
|
||||||
|
{
|
||||||
|
if (loadingFailedEventArgs.IsLoading)
|
||||||
|
logger.Debug("Loading page");
|
||||||
|
else
|
||||||
|
logger.Debug("Loaded page: " + loadingFailedEventArgs.Browser.MainFrame.Url);
|
||||||
|
};
|
||||||
|
Browser.ConsoleMessage += (_, consoleMessageEventArgs) =>
|
||||||
|
{
|
||||||
|
logger.Debug(consoleMessageEventArgs.Message + " (" + consoleMessageEventArgs.Source + ":" + consoleMessageEventArgs.Line + ")");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
JavascriptBindings.ApplyAppJavascriptBindings(Browser.JavascriptObjectRepository);
|
||||||
Controls.Add(Browser);
|
Controls.Add(Browser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ namespace VRCX
|
|||||||
{
|
{
|
||||||
protected override void OnHandleCreated(EventArgs e)
|
protected override void OnHandleCreated(EventArgs e)
|
||||||
{
|
{
|
||||||
WinformThemer.SetThemeToGlobal(this);
|
if (!DesignMode)
|
||||||
|
WinformThemer.SetThemeToGlobal(this);
|
||||||
base.OnHandleCreated(e);
|
base.OnHandleCreated(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,17 @@
|
|||||||
// This work is licensed under the terms of the MIT license.
|
// This work is licensed under the terms of the MIT license.
|
||||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||||
|
|
||||||
|
using System;
|
||||||
using DiscordRPC;
|
using DiscordRPC;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
namespace VRCX
|
namespace VRCX
|
||||||
{
|
{
|
||||||
public class Discord
|
public class Discord
|
||||||
{
|
{
|
||||||
|
private readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
public static readonly Discord Instance;
|
public static readonly Discord Instance;
|
||||||
private readonly ReaderWriterLockSlim m_Lock;
|
private readonly ReaderWriterLockSlim m_Lock;
|
||||||
private readonly RichPresence m_Presence;
|
private readonly RichPresence m_Presence;
|
||||||
@@ -54,8 +57,9 @@ namespace VRCX
|
|||||||
{
|
{
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.Error(ex, "Error updating Discord Rich Presence {Error}", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +136,7 @@ namespace VRCX
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAssets(string largeKey, string largeText, string smallKey, string smallText, string partyId, int partySize, int partyMax, string buttonText, string buttonUrl, string appId)
|
public void SetAssets(string largeKey, string largeText, string smallKey, string smallText, string partyId, int partySize, int partyMax, string buttonText, string buttonUrl, string appId, int activityType = 0)
|
||||||
{
|
{
|
||||||
m_Lock.EnterWriteLock();
|
m_Lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
@@ -153,6 +157,7 @@ namespace VRCX
|
|||||||
m_Presence.Party.ID = partyId;
|
m_Presence.Party.ID = partyId;
|
||||||
m_Presence.Party.Size = partySize;
|
m_Presence.Party.Size = partySize;
|
||||||
m_Presence.Party.Max = partyMax;
|
m_Presence.Party.Max = partyMax;
|
||||||
|
m_Presence.Type = (ActivityType)activityType;
|
||||||
Button[] buttons = [];
|
Button[] buttons = [];
|
||||||
if (!string.IsNullOrEmpty(buttonUrl))
|
if (!string.IsNullOrEmpty(buttonUrl))
|
||||||
{
|
{
|
||||||
@@ -207,6 +212,10 @@ namespace VRCX
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Error setting timestamps in Discord Rich Presence {Error}", ex.Message);
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
m_Lock.ExitWriteLock();
|
m_Lock.ExitWriteLock();
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ namespace VRCX
|
|||||||
|
|
||||||
#if !LINUX
|
#if !LINUX
|
||||||
if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
||||||
MainForm.Instance.Browser.ExecuteScriptAsync("$app.ipcEvent", packet);
|
MainForm.Instance.Browser.ExecuteScriptAsync("$app.store.vrcx.ipcEvent", packet);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ namespace VRCX
|
|||||||
m_LogQueue.Enqueue(logLine);
|
m_LogQueue.Enqueue(logLine);
|
||||||
#else
|
#else
|
||||||
if (MainForm.Instance != null && MainForm.Instance.Browser != null)
|
if (MainForm.Instance != null && MainForm.Instance.Browser != null)
|
||||||
MainForm.Instance.Browser.ExecuteScriptAsync("$app.addGameLogEvent", logLine);
|
MainForm.Instance.Browser.ExecuteScriptAsync("$app.store.gameLog.addGameLogEvent", logLine);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,9 +730,8 @@ namespace VRCX
|
|||||||
|
|
||||||
var data = line.Substring(offset + 13);
|
var data = line.Substring(offset + 13);
|
||||||
|
|
||||||
#if !LINUX
|
// PWI, deprecated
|
||||||
WorldDBManager.Instance.ProcessLogWorldDataRequest(data);
|
logger.Info("VRCX-World data: {0}", data);
|
||||||
#endif
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -188,13 +188,13 @@ namespace VRCX
|
|||||||
var dashboardHandle = 0UL;
|
var dashboardHandle = 0UL;
|
||||||
|
|
||||||
_wristOverlay = new OffScreenBrowser(
|
_wristOverlay = new OffScreenBrowser(
|
||||||
"file://vrcx/vr.html?1",
|
Program.LaunchDebug ? "http://localhost:9000/vr.html?1": "file://vrcx/vr.html?1",
|
||||||
512,
|
512,
|
||||||
512
|
512
|
||||||
);
|
);
|
||||||
|
|
||||||
_hmdOverlay = new OffScreenBrowser(
|
_hmdOverlay = new OffScreenBrowser(
|
||||||
"file://vrcx/vr.html?2",
|
Program.LaunchDebug ? "http://localhost:9000/vr.html?2": "file://vrcx/vr.html?2",
|
||||||
1024,
|
1024,
|
||||||
1024
|
1024
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace VRCX
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
DragHandler = new CefNoopDragHandler(),
|
DragHandler = new CefNoopDragHandler(),
|
||||||
|
RequestHandler = new CustomRequestHandler(),
|
||||||
BrowserSettings =
|
BrowserSettings =
|
||||||
{
|
{
|
||||||
DefaultEncoding = "UTF-8",
|
DefaultEncoding = "UTF-8",
|
||||||
|
|||||||
@@ -1,750 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using System.Runtime.Serialization.Formatters.Binary;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CefSharp;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using NLog;
|
|
||||||
|
|
||||||
namespace VRCX
|
|
||||||
{
|
|
||||||
public class WorldDBManager
|
|
||||||
{
|
|
||||||
public static readonly WorldDBManager Instance;
|
|
||||||
private readonly HttpListener listener;
|
|
||||||
private readonly WorldDatabase worldDB;
|
|
||||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
|
||||||
private const string WorldDBServerUrl = "http://127.0.0.1:22500/";
|
|
||||||
|
|
||||||
private string lastError = null;
|
|
||||||
private bool debugWorld = false;
|
|
||||||
|
|
||||||
static WorldDBManager()
|
|
||||||
{
|
|
||||||
Instance = new WorldDBManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
public WorldDBManager()
|
|
||||||
{
|
|
||||||
// http://localhost:22500
|
|
||||||
listener = new HttpListener();
|
|
||||||
listener.Prefixes.Add(WorldDBServerUrl);
|
|
||||||
|
|
||||||
worldDB = new WorldDatabase(Path.Join(Program.AppDataDirectory, "VRCX-WorldData.db"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Init()
|
|
||||||
{
|
|
||||||
if (VRCXStorage.Instance.Get("VRCX_DisableWorldDatabase") == "true")
|
|
||||||
{
|
|
||||||
logger.Info("World database is disabled. Not starting.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.Run(Start);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Start()
|
|
||||||
{
|
|
||||||
// typing this in vr gonna kms
|
|
||||||
try
|
|
||||||
{
|
|
||||||
listener.Start();
|
|
||||||
}
|
|
||||||
catch (HttpListenerException e)
|
|
||||||
{
|
|
||||||
logger.Error(e, "Failed to start HTTP listener. Is VRCX already running?");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Listening for requests on {0}", listener.Prefixes.First());
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var context = await listener.GetContextAsync();
|
|
||||||
var request = context.Request;
|
|
||||||
var responseData = new WorldDataRequestResponse(false, null, null);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (MainForm.Instance?.Browser == null || MainForm.Instance.Browser.IsLoading || !MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
|
||||||
{
|
|
||||||
logger.Error("Received a request to {0} while VRCX is still initializing the browser window. Responding with error 503.", request.Url);
|
|
||||||
|
|
||||||
responseData.Error = "VRCX not yet initialized. Try again in a moment.";
|
|
||||||
responseData.StatusCode = 503;
|
|
||||||
responseData.ConnectionKey = null;
|
|
||||||
SendJsonResponse(context.Response, responseData);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.Debug("Received a request to '{0}'", request.Url);
|
|
||||||
|
|
||||||
// TODO: Maybe an endpoint for getting a group of arbitrary keys by a group 'name'? eg; /getgroup?name=testgroup1 would return all keys with the column group set to 'testgroup1'
|
|
||||||
switch (request.Url.LocalPath)
|
|
||||||
{
|
|
||||||
case "/vrcx/data/init":
|
|
||||||
responseData = await HandleInitRequest(context);
|
|
||||||
SendJsonResponse(context.Response, responseData);
|
|
||||||
break;
|
|
||||||
case "/vrcx/data/get":
|
|
||||||
responseData = await HandleDataRequest(context);
|
|
||||||
SendJsonResponse(context.Response, responseData);
|
|
||||||
break;
|
|
||||||
case "/vrcx/data/getall":
|
|
||||||
responseData = await HandleAllDataRequest(context);
|
|
||||||
SendJsonResponse(context.Response, responseData);
|
|
||||||
break;
|
|
||||||
case "/vrcx/data/lasterror":
|
|
||||||
responseData.OK = lastError == null;
|
|
||||||
responseData.StatusCode = 200;
|
|
||||||
responseData.Data = lastError;
|
|
||||||
responseData.ConnectionKey = null;
|
|
||||||
lastError = null;
|
|
||||||
SendJsonResponse(context.Response, responseData);
|
|
||||||
break;
|
|
||||||
case "/vrcx/data/getbulk":
|
|
||||||
responseData = await HandleBulkDataRequest(context);
|
|
||||||
SendJsonResponse(context.Response, responseData);
|
|
||||||
break;
|
|
||||||
case "/vrcx/data/settings":
|
|
||||||
responseData = await HandleSetSettingsRequest(context);
|
|
||||||
SendJsonResponse(context.Response, responseData);
|
|
||||||
break;
|
|
||||||
case "/vrcx/status":
|
|
||||||
// Send a blank 200 response to indicate that the server is running.
|
|
||||||
context.Response.StatusCode = 200;
|
|
||||||
context.Response.Close();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
responseData.Error = "Invalid VRCX endpoint.";
|
|
||||||
responseData.StatusCode = 404;
|
|
||||||
responseData.ConnectionKey = null;
|
|
||||||
SendJsonResponse(context.Response, responseData);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.Response.StatusCode != 200)
|
|
||||||
{
|
|
||||||
logger.Warn("Received a request to '{0}' that returned a non-successful response. Error: {1} - {2}", request.Url, responseData.StatusCode, responseData.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.Error(ex, $"Exception while processing a request to endpoint '{request.Url}'.");
|
|
||||||
|
|
||||||
responseData.Error = $"VRCX has encountered an exception while processing the url '{request.Url}': {ex.Message}";
|
|
||||||
responseData.StatusCode = 500;
|
|
||||||
responseData.ConnectionKey = null;
|
|
||||||
SendJsonResponse(context.Response, responseData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<WorldDataRequestResponse> HandleSetSettingsRequest(HttpListenerContext context)
|
|
||||||
{
|
|
||||||
var request = context.Request;
|
|
||||||
|
|
||||||
string worldId = await GetCurrentWorldID();
|
|
||||||
string set = request.QueryString["set"];
|
|
||||||
string value = request.QueryString["value"];
|
|
||||||
|
|
||||||
if (!TryInitializeWorld(worldId, out string connectionKey))
|
|
||||||
{
|
|
||||||
return ConstructErrorResponse(500, "Failed to get/verify current world ID.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (set != null && value != null)
|
|
||||||
{
|
|
||||||
switch (set)
|
|
||||||
{
|
|
||||||
case "externalReads":
|
|
||||||
if (request.QueryString["value"] == "true")
|
|
||||||
{
|
|
||||||
worldDB.SetWorldAllowExternalRead(worldId, true);
|
|
||||||
}
|
|
||||||
else if (request.QueryString["value"] == "false")
|
|
||||||
{
|
|
||||||
worldDB.SetWorldAllowExternalRead(worldId, false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return ConstructErrorResponse(400, "Invalid value for 'externalReads' setting.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return ConstructErrorResponse(400, "Invalid setting name.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConstructSuccessResponse(null, connectionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles an HTTP listener request to initialize a connection to the world db manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The HTTP listener context object.</param>
|
|
||||||
/// <returns>A <see cref="WorldDataRequestResponse"/> object containing the response data.</returns>
|
|
||||||
private async Task<WorldDataRequestResponse> HandleInitRequest(HttpListenerContext context)
|
|
||||||
{
|
|
||||||
var request = context.Request;
|
|
||||||
|
|
||||||
if (request.QueryString["debug"] == "true")
|
|
||||||
{
|
|
||||||
debugWorld = true;
|
|
||||||
}
|
|
||||||
else if (request.QueryString["debug"] == "false")
|
|
||||||
{
|
|
||||||
debugWorld = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
string worldId = await GetCurrentWorldID();
|
|
||||||
|
|
||||||
if (TryInitializeWorld(worldId, out string connectionKey))
|
|
||||||
{
|
|
||||||
logger.Info("Initialized a connection to the world database for world ID '{0}' with connection key {1}.", worldId, connectionKey);
|
|
||||||
return ConstructSuccessResponse(connectionKey, connectionKey);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return ConstructErrorResponse(500, "Failed to get/verify current world ID.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles an HTTP listener request for data from the world database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The HTTP listener context object.</param>
|
|
||||||
/// <returns>A <see cref="WorldDataRequestResponse"/> object containing the response data.</returns>
|
|
||||||
private async Task<WorldDataRequestResponse> HandleDataRequest(HttpListenerContext context)
|
|
||||||
{
|
|
||||||
var request = context.Request;
|
|
||||||
|
|
||||||
var key = request.QueryString["key"];
|
|
||||||
if (key == null)
|
|
||||||
{
|
|
||||||
return ConstructErrorResponse(400, "Missing key parameter.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var worldId = await GetCurrentWorldID();
|
|
||||||
|
|
||||||
if (!TryInitializeWorld(worldId, out string connectionKey))
|
|
||||||
{
|
|
||||||
return ConstructErrorResponse(500, "Failed to get/verify current world ID.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var worldOverride = request.QueryString["world"];
|
|
||||||
if (worldOverride == "global")
|
|
||||||
{
|
|
||||||
TryInitializeWorld(worldOverride, out connectionKey);
|
|
||||||
}
|
|
||||||
if (worldOverride != null && worldId != worldOverride)
|
|
||||||
{
|
|
||||||
var allowed = worldDB.GetWorldAllowExternalRead(worldOverride) || worldOverride == "global";
|
|
||||||
if (!allowed)
|
|
||||||
{
|
|
||||||
return ConstructSuccessResponse(null, connectionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
var otherValue = worldDB.GetDataEntry(worldOverride, key);
|
|
||||||
|
|
||||||
logger.Debug("Serving a request for data with key '{0}' from world ID '{1}' requested by world ID '{2}' with connection key {3}.", key, worldOverride, worldId, connectionKey);
|
|
||||||
|
|
||||||
// This value is intended to be null if the key doesn't exist.
|
|
||||||
return ConstructSuccessResponse(otherValue?.Value, connectionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
var value = worldDB.GetDataEntry(worldId, key);
|
|
||||||
|
|
||||||
logger.Debug("Serving a request for data with key '{0}' from world ID '{1}' with connection key {2}.", key, worldId, connectionKey);
|
|
||||||
// This value is intended to be null if the key doesn't exist.
|
|
||||||
return ConstructSuccessResponse(value?.Value, connectionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles an HTTP listener request for all data from the world database for a given world.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The HTTP listener context object.</param>
|
|
||||||
/// <returns>A <see cref="WorldDataRequestResponse"/> object containing the response data.</returns>
|
|
||||||
private async Task<WorldDataRequestResponse> HandleAllDataRequest(HttpListenerContext context)
|
|
||||||
{
|
|
||||||
var request = context.Request;
|
|
||||||
var worldId = await GetCurrentWorldID();
|
|
||||||
|
|
||||||
if (!TryInitializeWorld(worldId, out string connectionKey))
|
|
||||||
{
|
|
||||||
return ConstructErrorResponse(500, "Failed to get/verify current world ID.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var worldOverride = request.QueryString["world"];
|
|
||||||
if (worldOverride == "global")
|
|
||||||
{
|
|
||||||
TryInitializeWorld(worldOverride, out connectionKey);
|
|
||||||
}
|
|
||||||
if (worldOverride != null && worldId != worldOverride)
|
|
||||||
{
|
|
||||||
var allowed = worldDB.GetWorldAllowExternalRead(worldOverride) || worldOverride == "global";
|
|
||||||
if (!allowed)
|
|
||||||
{
|
|
||||||
return ConstructSuccessResponse(null, connectionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
var otherEntries = worldDB.GetAllDataEntries(worldOverride);
|
|
||||||
|
|
||||||
var otherData = new Dictionary<string, string>();
|
|
||||||
foreach (var entry in otherEntries)
|
|
||||||
{
|
|
||||||
otherData.Add(entry.Key, entry.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug("Serving a request for all data ({0} entries) for world ID '{1}' requested by {2} with connection key {3}.", otherData.Count, worldOverride, worldId, connectionKey);
|
|
||||||
return ConstructSuccessResponse(JsonConvert.SerializeObject(otherData), connectionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries = worldDB.GetAllDataEntries(worldId);
|
|
||||||
|
|
||||||
var data = new Dictionary<string, string>();
|
|
||||||
foreach (var entry in entries)
|
|
||||||
{
|
|
||||||
data.Add(entry.Key, entry.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug("Serving a request for all data ({0} entries) for world ID '{1}' with connection key {2}.", data.Count, worldId, connectionKey);
|
|
||||||
return ConstructSuccessResponse(JsonConvert.SerializeObject(data), connectionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles an HTTP listener request for bulk data from the world database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The HTTP listener context object.</param>
|
|
||||||
/// <returns>A <see cref="WorldDataRequestResponse"/> object containing the response data.</returns>
|
|
||||||
private async Task<WorldDataRequestResponse> HandleBulkDataRequest(HttpListenerContext context)
|
|
||||||
{
|
|
||||||
var request = context.Request;
|
|
||||||
|
|
||||||
var keys = request.QueryString["keys"];
|
|
||||||
if (keys == null)
|
|
||||||
{
|
|
||||||
return ConstructErrorResponse(400, "Missing/invalid keys parameter.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyArray = keys.Split(',');
|
|
||||||
|
|
||||||
var worldId = await GetCurrentWorldID();
|
|
||||||
|
|
||||||
if (!TryInitializeWorld(worldId, out string connectionKey))
|
|
||||||
{
|
|
||||||
return ConstructErrorResponse(500, "Failed to get/verify current world ID.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var worldOverride = request.QueryString["world"];
|
|
||||||
if (worldOverride == "global")
|
|
||||||
{
|
|
||||||
TryInitializeWorld(worldOverride, out connectionKey);
|
|
||||||
}
|
|
||||||
if (worldOverride != null && worldId != worldOverride)
|
|
||||||
{
|
|
||||||
var allowed = worldDB.GetWorldAllowExternalRead(worldOverride) || worldOverride == "global";
|
|
||||||
if (!allowed)
|
|
||||||
{
|
|
||||||
return ConstructSuccessResponse(null, connectionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
var otherEntries = worldDB.GetAllDataEntries(worldOverride);
|
|
||||||
|
|
||||||
var otherData = new Dictionary<string, string>();
|
|
||||||
foreach (var entry in otherEntries)
|
|
||||||
{
|
|
||||||
otherData.Add(entry.Key, entry.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug("Serving a request for all data ({0} entries) for world ID '{1}' requested by {2} with connection key {3}.", otherData.Count, worldOverride, worldId, connectionKey);
|
|
||||||
return ConstructSuccessResponse(JsonConvert.SerializeObject(otherData), connectionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
var values = worldDB.GetDataEntries(worldId, keyArray).ToList();
|
|
||||||
|
|
||||||
// Build a dictionary of key/value pairs to send back. If a key doesn't exist in the database, the key will be included in the response as requested but with a null value.
|
|
||||||
var data = new Dictionary<string, string>();
|
|
||||||
for (int i = 0; i < keyArray.Length; i++)
|
|
||||||
{
|
|
||||||
string dataKey = keyArray[i];
|
|
||||||
string dataValue = values?.Where(x => x.Key == dataKey).FirstOrDefault()?.Value; // Get the value from the list of data entries, if it exists, otherwise null
|
|
||||||
|
|
||||||
data.Add(dataKey, dataValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug("Serving a request for bulk data with keys '{0}' from world ID '{1}' with connection key {2}.", keys, worldId, connectionKey);
|
|
||||||
return ConstructSuccessResponse(JsonConvert.SerializeObject(data), connectionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to initialize a world with the given ID by generating a connection key and adding it to the world database if it does not already exist.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to initialize.</param>
|
|
||||||
/// <param name="connectionKey">The connection key generated for the world.</param>
|
|
||||||
/// <returns>True if the world was successfully initialized, false otherwise.</returns>
|
|
||||||
private bool TryInitializeWorld(string worldId, out string connectionKey)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(worldId))
|
|
||||||
{
|
|
||||||
connectionKey = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var existsInDB = worldDB.DoesWorldExist(worldId);
|
|
||||||
|
|
||||||
if (!existsInDB)
|
|
||||||
{
|
|
||||||
connectionKey = GenerateWorldConnectionKey();
|
|
||||||
worldDB.AddWorld(worldId, connectionKey);
|
|
||||||
logger.Info("Added new world ID '{0}' with connection key '{1}' to the database.", worldId, connectionKey);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
connectionKey = worldDB.GetWorldConnectionKey(worldId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generates a unique identifier for a world connection request.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string representation of a GUID that can be used to identify the world on requests.</returns>
|
|
||||||
private string GenerateWorldConnectionKey()
|
|
||||||
{
|
|
||||||
if (debugWorld) return "12345";
|
|
||||||
|
|
||||||
// Ditched the old method of generating a short key, since we're just going with json anyway who cares about a longer identifier
|
|
||||||
// Since we can rely on this GUID being unique, we can use it to identify the world on requests instead of trying to keep track of the user's current world.
|
|
||||||
// I uhh, should probably make sure this is actually unique though. Just in case. I'll do that later.
|
|
||||||
return Guid.NewGuid().ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the ID of the current world by evaluating a JavaScript function in the main browser instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The ID of the current world as a string, or null if it could not be retrieved.</returns>
|
|
||||||
private async Task<string> GetCurrentWorldID()
|
|
||||||
{
|
|
||||||
if (debugWorld) return "wrld_12345";
|
|
||||||
|
|
||||||
JavascriptResponse funcResult;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
funcResult = await MainForm.Instance.Browser.EvaluateScriptAsync("$app.API.actuallyGetCurrentLocation();", TimeSpan.FromSeconds(5));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.Error(ex, "Failed to evaluate actuallyGetCurrentLocation JS function to get current world ID.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
string worldId = funcResult?.Result?.ToString();
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(worldId))
|
|
||||||
{
|
|
||||||
// implement
|
|
||||||
// wait what was i going to do here again
|
|
||||||
// seriously i forgot, hope it wasn't important
|
|
||||||
logger.Warn("actuallyGetCurrentLocation returned null or empty.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return worldId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private WorldDataRequestResponse ConstructSuccessResponse(string data = null, string connectionKey = null)
|
|
||||||
{
|
|
||||||
var responseData = new WorldDataRequestResponse(true, null, null);
|
|
||||||
|
|
||||||
responseData.StatusCode = 200;
|
|
||||||
responseData.Error = null;
|
|
||||||
responseData.OK = true;
|
|
||||||
responseData.Data = data;
|
|
||||||
responseData.ConnectionKey = connectionKey;
|
|
||||||
return responseData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private WorldDataRequestResponse ConstructErrorResponse(int statusCode, string error)
|
|
||||||
{
|
|
||||||
var responseData = new WorldDataRequestResponse(true, null, null);
|
|
||||||
|
|
||||||
responseData.StatusCode = statusCode;
|
|
||||||
responseData.Error = error;
|
|
||||||
responseData.OK = false;
|
|
||||||
responseData.Data = null;
|
|
||||||
responseData.ConnectionKey = null;
|
|
||||||
|
|
||||||
return responseData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a JSON response to an HTTP listener request with the specified response data and status code.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="response">The HTTP listener response object.</param>
|
|
||||||
/// <param name="responseData">The response data to be serialized to JSON.</param>
|
|
||||||
/// <param name="statusCode">The HTTP status code to be returned.</param>
|
|
||||||
/// <returns>The HTTP listener response object.</returns>
|
|
||||||
private HttpListenerResponse SendJsonResponse(HttpListenerResponse response, WorldDataRequestResponse responseData)
|
|
||||||
{
|
|
||||||
response.ContentType = "application/json";
|
|
||||||
response.StatusCode = responseData.StatusCode;
|
|
||||||
response.AddHeader("Cache-Control", "no-cache");
|
|
||||||
|
|
||||||
// Use newtonsoft.json to serialize WorldDataRequestResponse to json
|
|
||||||
var json = JsonConvert.SerializeObject(responseData);
|
|
||||||
var buffer = System.Text.Encoding.UTF8.GetBytes(json);
|
|
||||||
response.ContentLength64 = buffer.Length;
|
|
||||||
response.OutputStream.Write(buffer, 0, buffer.Length);
|
|
||||||
response.Close();
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes a JSON request containing world data and logs it to the world database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="json">The JSON request containing the world data.</param>
|
|
||||||
public void ProcessLogWorldDataRequest(string json)
|
|
||||||
{
|
|
||||||
// Current format:
|
|
||||||
// {
|
|
||||||
// "requestType": "store",
|
|
||||||
// "connectionKey": "abc123",
|
|
||||||
// "key": "example_key",
|
|
||||||
// "value": "example_value"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// * I could rate limit the processing of this, but I don't think it's necessary.
|
|
||||||
// * At the amount of data you'd need to be spitting out to lag vrcx, you'd fill up the log file and lag out VRChat far before VRCX would have any issues; at least in my testing.
|
|
||||||
// As long as malicious worlds can't permanently *store* stupid amounts of unculled data, this is pretty safe with the 10MB cap. If a world wants to just fill up a users HDD with logs, they can do that already anyway.
|
|
||||||
|
|
||||||
WorldDataRequest request;
|
|
||||||
|
|
||||||
try // try to deserialize the json into a WorldDataRequest object
|
|
||||||
{
|
|
||||||
request = JsonConvert.DeserializeObject<WorldDataRequest>(json);
|
|
||||||
}
|
|
||||||
catch (JsonReaderException ex)
|
|
||||||
{
|
|
||||||
logger.Error(ex, json.ToString());
|
|
||||||
this.lastError = ex.Message;
|
|
||||||
// invalid json
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.Error(ex, json.ToString());
|
|
||||||
this.lastError = ex.Message;
|
|
||||||
// something else happened lol
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(request.RequestType))
|
|
||||||
{
|
|
||||||
logger.Warn("World tried to store data with no request type provided. Request: ", json);
|
|
||||||
this.lastError = "`requestType` is missing or null";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the connection key is a valid GUID. No point in doing anything else if it's not.
|
|
||||||
if (!debugWorld && !Guid.TryParse(request.ConnectionKey, out Guid _))
|
|
||||||
{
|
|
||||||
logger.Warn("World tried to store data with an invalid GUID as a connection key '{0}'", request.ConnectionKey);
|
|
||||||
this.lastError = "Invalid GUID provided as connection key";
|
|
||||||
// invalid guid
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the world ID from the connection key
|
|
||||||
string worldId = worldDB.GetWorldByConnectionKey(request.ConnectionKey);
|
|
||||||
|
|
||||||
// Global override
|
|
||||||
if (request.ConnectionKey == "global")
|
|
||||||
{
|
|
||||||
worldId = "global";
|
|
||||||
}
|
|
||||||
|
|
||||||
// World ID is null, which means the connection key is invalid (or someone just deleted a world from the DB while VRCX was running lol).
|
|
||||||
if (worldId == null)
|
|
||||||
{
|
|
||||||
logger.Warn("World tried to store data under {0} with an invalid connection key {1}", request.Key, request.ConnectionKey);
|
|
||||||
this.lastError = "Invalid connection key";
|
|
||||||
// invalid connection key
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string requestType = request.RequestType.ToLower();
|
|
||||||
switch (requestType)
|
|
||||||
{
|
|
||||||
case "store":
|
|
||||||
if (String.IsNullOrEmpty(request.Key))
|
|
||||||
{
|
|
||||||
logger.Warn("World {0} tried to store data with no key provided", worldId);
|
|
||||||
this.lastError = "`key` is missing or null";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.Key.Length > 255)
|
|
||||||
{
|
|
||||||
logger.Warn("World {0} tried to store data with a key that was too long ({1}/256 characters)", worldId, request.Key.Length);
|
|
||||||
this.lastError = "`key` is too long. Keep it below <256 characters.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.Value == null)
|
|
||||||
{
|
|
||||||
logger.Warn("World {0} tried to store data under key {1} with null value", worldId, request.Key);
|
|
||||||
this.lastError = "`value` is null";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StoreWorldData(worldId, request.Key, request.Value);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "delete":
|
|
||||||
if (String.IsNullOrEmpty(request.Key))
|
|
||||||
{
|
|
||||||
logger.Warn("World {0} tried to delete data with no key provided", worldId);
|
|
||||||
this.lastError = "`key` is missing or null";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
DeleteWorldData(worldId, request.Key);
|
|
||||||
break;
|
|
||||||
case "delete-all":
|
|
||||||
|
|
||||||
logger.Info("World {0} requested to delete all data.", worldId);
|
|
||||||
|
|
||||||
|
|
||||||
worldDB.DeleteAllDataEntriesForWorld(worldId);
|
|
||||||
worldDB.UpdateWorldDataSize(worldId, 0);
|
|
||||||
break;
|
|
||||||
case "set-setting":
|
|
||||||
if (String.IsNullOrEmpty(request.Key))
|
|
||||||
{
|
|
||||||
logger.Warn("World {0} tried to delete data with no key provided", worldId);
|
|
||||||
this.lastError = "`key` is missing or null";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(request.Value))
|
|
||||||
{
|
|
||||||
logger.Warn("World {0} tried to set settings with no value provided", worldId);
|
|
||||||
this.lastError = "`value` is missing or null";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetWorldProperty(worldId, request.Key, request.Value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
logger.Warn("World {0} sent an invalid request type '{0}'", worldId, request.RequestType);
|
|
||||||
this.lastError = "Invalid request type";
|
|
||||||
// invalid request type
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.Error(ex, "Failed to process world data request for world {0}", worldId);
|
|
||||||
logger.Error("Failed Request: {0}", json);
|
|
||||||
this.lastError = ex.Message;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets a property for a given world in the world database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to set the property for.</param>
|
|
||||||
/// <param name="key">The key of the property to set.</param>
|
|
||||||
/// <param name="value">The value to set the property to.</param>
|
|
||||||
public void SetWorldProperty(string worldId, string key, string value)
|
|
||||||
{
|
|
||||||
switch (key)
|
|
||||||
{
|
|
||||||
case "externalReads":
|
|
||||||
if (bool.TryParse(value, out bool result))
|
|
||||||
{
|
|
||||||
worldDB.SetWorldAllowExternalRead(worldId, result);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.Warn("World {0} tried to set externalReads to an invalid value '{1}'", worldId, value);
|
|
||||||
this.lastError = "Invalid value for externalReads";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stores a data entry for a given world in the world database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to store the data entry for.</param>
|
|
||||||
/// <param name="key">The key of the data entry to store.</param>
|
|
||||||
/// <param name="value">The value of the data entry to store.</param>
|
|
||||||
public void StoreWorldData(string worldId, string key, string value)
|
|
||||||
{
|
|
||||||
// Get/calculate the old and new data sizes for this key/the world
|
|
||||||
int oldTotalDataSize = worldDB.GetWorldDataSize(worldId);
|
|
||||||
int oldDataSize = worldDB.GetDataEntrySize(worldId, key);
|
|
||||||
int newDataSize = Encoding.UTF8.GetByteCount(value);
|
|
||||||
int newTotalDataSize = oldTotalDataSize + newDataSize - oldDataSize;
|
|
||||||
|
|
||||||
// Make sure we don't exceed 10MB total size for a world
|
|
||||||
// This works, I tested it. Hopefully this prevents/limits any possible abuse.
|
|
||||||
if (newTotalDataSize > 1024 * 1024 * 10)
|
|
||||||
{
|
|
||||||
logger.Warn("World {0} exceeded 10MB total data size trying to store key {0}. {1}:{2} + {3} = {4}", key, worldId, oldTotalDataSize - oldDataSize, newDataSize, newTotalDataSize);
|
|
||||||
this.lastError = $"You have hit the 10MB total data cap. The previous data entry was *not* stored. Your request was {newDataSize} bytes, your current shared byte total is {oldTotalDataSize} and you went over the table limit by {newTotalDataSize - (1024 * 1024 * 10)} bytes.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
worldDB.AddDataEntry(worldId, key, value, newDataSize);
|
|
||||||
worldDB.UpdateWorldDataSize(worldId, newTotalDataSize);
|
|
||||||
logger.Info("World {0} stored data entry {1} with size {2} bytes", worldId, key, newDataSize);
|
|
||||||
logger.Debug("{0} : {1}", key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deletes a data entry for a given world from the world database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to delete the data entry from.</param>
|
|
||||||
/// <param name="key">The key of the data entry to delete.</param>
|
|
||||||
public void DeleteWorldData(string worldId, string key)
|
|
||||||
{
|
|
||||||
int oldTotalDataSize = worldDB.GetWorldDataSize(worldId);
|
|
||||||
int oldDataSize = worldDB.GetDataEntrySize(worldId, key);
|
|
||||||
int newTotalDataSize = oldTotalDataSize - oldDataSize;
|
|
||||||
|
|
||||||
worldDB.DeleteDataEntry(worldId, key);
|
|
||||||
worldDB.UpdateWorldDataSize(worldId, newTotalDataSize);
|
|
||||||
logger.Info("World {0} deleted data entry {1} with size {2} bytes.", worldId, key, oldDataSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
worldDB?.Close();
|
|
||||||
listener?.Stop();
|
|
||||||
listener?.Close();
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace VRCX
|
|
||||||
{
|
|
||||||
public class WorldDataRequestResponse
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the request was successful.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("ok")]
|
|
||||||
public bool OK { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the error message if the request was not successful.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("error")]
|
|
||||||
public string Error { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the data returned by the request.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("data")]
|
|
||||||
public string Data { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the response code.
|
|
||||||
/// </summary>
|
|
||||||
/// <value></value>
|
|
||||||
[JsonProperty("statusCode")]
|
|
||||||
public int StatusCode { get; set; }
|
|
||||||
[JsonProperty("connectionKey")]
|
|
||||||
public string ConnectionKey { get; set; }
|
|
||||||
|
|
||||||
public WorldDataRequestResponse(bool ok, string error, string data)
|
|
||||||
{
|
|
||||||
OK = ok;
|
|
||||||
Error = error;
|
|
||||||
Data = data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WorldDataRequest
|
|
||||||
{
|
|
||||||
[JsonProperty("requestType")]
|
|
||||||
public string RequestType;
|
|
||||||
[JsonProperty("connectionKey")]
|
|
||||||
public string ConnectionKey;
|
|
||||||
[JsonProperty("key")]
|
|
||||||
public string Key;
|
|
||||||
[JsonProperty("value")]
|
|
||||||
public string Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,322 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using SQLite;
|
|
||||||
|
|
||||||
namespace VRCX
|
|
||||||
{
|
|
||||||
[Table("data")]
|
|
||||||
public class WorldData
|
|
||||||
{
|
|
||||||
[PrimaryKey, AutoIncrement]
|
|
||||||
[Column("id")]
|
|
||||||
public int Id { get; set; }
|
|
||||||
[Column("world_id"), NotNull]
|
|
||||||
public string WorldId { get; set; }
|
|
||||||
[Column("key"), NotNull]
|
|
||||||
public string Key { get; set; }
|
|
||||||
[Column("value"), NotNull]
|
|
||||||
public string Value { get; set; }
|
|
||||||
[Column("value_size"), NotNull]
|
|
||||||
public int ValueSize { get; set; }
|
|
||||||
[Column("last_accessed")]
|
|
||||||
public DateTimeOffset LastAccessed { get; set; }
|
|
||||||
[Column("last_modified")]
|
|
||||||
public DateTimeOffset LastModified { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Table("worlds")]
|
|
||||||
public class World
|
|
||||||
{
|
|
||||||
[PrimaryKey, AutoIncrement]
|
|
||||||
[Column("id")]
|
|
||||||
public int Id { get; set; }
|
|
||||||
[Column("world_id"), NotNull]
|
|
||||||
public string WorldId { get; set; }
|
|
||||||
[Column("connection_key"), NotNull]
|
|
||||||
public string ConnectionKey { get; set; }
|
|
||||||
[Column("total_data_size"), NotNull]
|
|
||||||
public int TotalDataSize { get; set; }
|
|
||||||
[Column("allow_external_read")]
|
|
||||||
public bool AllowExternalRead { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class WorldDatabase
|
|
||||||
{
|
|
||||||
private static SQLiteConnection sqlite;
|
|
||||||
private readonly static string dbInitQuery = @"
|
|
||||||
CREATE TABLE IF NOT EXISTS worlds (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
world_id TEXT NOT NULL UNIQUE,
|
|
||||||
connection_key TEXT NOT NULL,
|
|
||||||
total_data_size INTEGER DEFAULT 0,
|
|
||||||
allow_external_read INTEGER DEFAULT 0
|
|
||||||
);
|
|
||||||
\
|
|
||||||
CREATE TABLE IF NOT EXISTS data (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
world_id TEXT NOT NULL,
|
|
||||||
key TEXT NOT NULL,
|
|
||||||
value TEXT NOT NULL,
|
|
||||||
value_size INTEGER NOT NULL DEFAULT 0,
|
|
||||||
last_accessed INTEGER DEFAULT (strftime('%s', 'now')),
|
|
||||||
last_modified INTEGER DEFAULT (strftime('%s', 'now')),
|
|
||||||
FOREIGN KEY (world_id) REFERENCES worlds(world_id) ON DELETE CASCADE,
|
|
||||||
UNIQUE (world_id, key)
|
|
||||||
);
|
|
||||||
\
|
|
||||||
CREATE TRIGGER IF NOT EXISTS data_update_trigger
|
|
||||||
AFTER UPDATE ON data
|
|
||||||
FOR EACH ROW
|
|
||||||
BEGIN
|
|
||||||
UPDATE data SET last_modified = (strftime('%s', 'now')) WHERE id = OLD.id;
|
|
||||||
END;
|
|
||||||
\
|
|
||||||
CREATE TRIGGER IF NOT EXISTS data_insert_trigger
|
|
||||||
AFTER INSERT ON data
|
|
||||||
FOR EACH ROW
|
|
||||||
BEGIN
|
|
||||||
UPDATE data SET last_accessed = (strftime('%s', 'now')), last_modified = (strftime('%s', 'now')) WHERE id = NEW.id;
|
|
||||||
END;";
|
|
||||||
public WorldDatabase(string databaseLocation)
|
|
||||||
{
|
|
||||||
var options = new SQLiteConnectionString(databaseLocation, true);
|
|
||||||
sqlite = new SQLiteConnection(options);
|
|
||||||
sqlite.Execute(dbInitQuery);
|
|
||||||
|
|
||||||
// TODO: Split these init queries into their own functions so we can call/update them individually.
|
|
||||||
var queries = dbInitQuery.Split('\\');
|
|
||||||
sqlite.BeginTransaction();
|
|
||||||
foreach (var query in queries)
|
|
||||||
{
|
|
||||||
sqlite.Execute(query);
|
|
||||||
}
|
|
||||||
sqlite.Commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if a world with the specified ID exists in the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to check for.</param>
|
|
||||||
/// <returns>True if the world exists in the database, false otherwise.</returns>
|
|
||||||
public bool DoesWorldExist(string worldId)
|
|
||||||
{
|
|
||||||
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId).Select(w => w.WorldId);
|
|
||||||
|
|
||||||
return query.Any();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the ID of the world with the specified connection key from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="connectionKey">The connection key of the world to get the ID for.</param>
|
|
||||||
/// <returns>The ID of the world with the specified connection key, or null if no such world exists in the database.</returns>
|
|
||||||
public string GetWorldByConnectionKey(string connectionKey)
|
|
||||||
{
|
|
||||||
var query = sqlite.Table<World>().Where(w => w.ConnectionKey == connectionKey).Select(w => w.WorldId);
|
|
||||||
|
|
||||||
return query.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the connection key for a world from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to get the connection key for.</param>
|
|
||||||
/// <returns>The connection key for the specified world, or null if the world does not exist in the database.</returns>
|
|
||||||
public string GetWorldConnectionKey(string worldId)
|
|
||||||
{
|
|
||||||
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId).Select(w => w.ConnectionKey);
|
|
||||||
|
|
||||||
return query.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the connection key for a world in the database. If the world already exists in the database, the connection key is updated. Otherwise, a new world is added to the database with the specified connection key.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to set the connection key for.</param>
|
|
||||||
/// <param name="connectionKey">The connection key to set for the world.</param>
|
|
||||||
/// <returns>The connection key that was set.</returns>
|
|
||||||
public string SetWorldConnectionKey(string worldId, string connectionKey)
|
|
||||||
{
|
|
||||||
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId).Select(w => w.ConnectionKey);
|
|
||||||
|
|
||||||
if (query.Any())
|
|
||||||
{
|
|
||||||
sqlite.Execute("UPDATE worlds SET connection_key = ? WHERE world_id = ?", connectionKey, worldId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sqlite.Insert(new World() { WorldId = worldId, ConnectionKey = connectionKey });
|
|
||||||
}
|
|
||||||
|
|
||||||
return connectionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the value of the allow_external_read field for the world with the specified ID in the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to set the allow_external_read field for.</param>
|
|
||||||
/// <param name="allowExternalRead">The value to set for the allow_external_read field.</param>
|
|
||||||
public void SetWorldAllowExternalRead(string worldId, bool allowExternalRead)
|
|
||||||
{
|
|
||||||
sqlite.Execute("UPDATE worlds SET allow_external_read = ? WHERE world_id = ?", allowExternalRead, worldId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the value of the allow_external_read field for the world with the specified ID from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to get the allow_external_read field for.</param>
|
|
||||||
/// <returns>The value of the allow_external_read field for the specified world.</returns>
|
|
||||||
public bool GetWorldAllowExternalRead(string worldId)
|
|
||||||
{
|
|
||||||
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId).Select(w => w.AllowExternalRead);
|
|
||||||
|
|
||||||
return query.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a new world to the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to add.</param>
|
|
||||||
/// <param name="connectionKey">The connection key of the world to add.</param>
|
|
||||||
/// <exception cref="SQLiteException">Thrown if a world with the specified ID already exists in the database.</exception>
|
|
||||||
public void AddWorld(string worldId, string connectionKey)
|
|
||||||
{
|
|
||||||
// * This will throw an error if the world already exists.. so don't do that
|
|
||||||
sqlite.Insert(new World() { WorldId = worldId, ConnectionKey = connectionKey });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the world with the specified ID from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to get.</param>
|
|
||||||
/// <returns>The world with the specified ID, or null if no such world exists in the database.</returns>
|
|
||||||
public World GetWorld(string worldId)
|
|
||||||
{
|
|
||||||
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId);
|
|
||||||
return query.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the total data size shared across all rows, in bytes, for the world with the specified ID from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to get the total data size for.</param>
|
|
||||||
/// <returns>The total data size for the world, in bytes.</returns>
|
|
||||||
public int GetWorldDataSize(string worldId)
|
|
||||||
{
|
|
||||||
var query = sqlite.Table<World>().Where(w => w.WorldId == worldId).Select(w => w.TotalDataSize);
|
|
||||||
|
|
||||||
return query.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the total data size, in bytes for the world with the specified ID in the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to update the total data size for.</param>
|
|
||||||
/// <param name="size">The new total data size for the world, in bytes.</param>
|
|
||||||
public void UpdateWorldDataSize(string worldId, int size)
|
|
||||||
{
|
|
||||||
sqlite.Execute("UPDATE worlds SET total_data_size = ? WHERE world_id = ?", size, worldId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds or updates a data entry in the database with the specified world ID, key, and value.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to add the data entry for.</param>
|
|
||||||
/// <param name="key">The key of the data entry to add or replace.</param>
|
|
||||||
/// <param name="value">The value of the data entry to add or replace.</param>
|
|
||||||
/// <param name="dataSize">The size of the data entry to add or replace, in bytes. If null, the size is calculated from the value automatically.</param>
|
|
||||||
public void AddDataEntry(string worldId, string key, string value, int? dataSize = null)
|
|
||||||
{
|
|
||||||
int byteSize = dataSize ?? Encoding.UTF8.GetByteCount(value);
|
|
||||||
|
|
||||||
// check if entry already exists;
|
|
||||||
// INSERT OR REPLACE(InsertOrReplace method) deletes the old row and creates a new one, incrementing the id, which I don't want
|
|
||||||
var query = sqlite.Table<WorldData>().Where(w => w.WorldId == worldId && w.Key == key);
|
|
||||||
if (query.Any())
|
|
||||||
{
|
|
||||||
sqlite.Execute("UPDATE data SET value = ?, value_size = ? WHERE world_id = ? AND key = ?", value, byteSize, worldId, key);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sqlite.Insert(new WorldData() { WorldId = worldId, Key = key, Value = value, ValueSize = byteSize });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data entry with the specified world ID and key from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to get the data entry for.</param>
|
|
||||||
/// <param name="key">The key of the data entry to get.</param>
|
|
||||||
/// <returns>The data entry with the specified world ID and key, or null if no such data entry exists in the database.</returns>
|
|
||||||
public WorldData GetDataEntry(string worldId, string key)
|
|
||||||
{
|
|
||||||
var query = sqlite.Table<WorldData>().Where(w => w.WorldId == worldId && w.Key == key);
|
|
||||||
|
|
||||||
return query.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data entries with the specified world ID and keys from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to get the data entries for.</param>
|
|
||||||
/// <param name="keys">The keys of the data entries to get.</param>
|
|
||||||
/// <returns>An enumerable collection of the data entries with the specified world ID and keys.</returns>
|
|
||||||
public IEnumerable<WorldData> GetDataEntries(string worldId, string[] keys)
|
|
||||||
{
|
|
||||||
var query = sqlite.Table<WorldData>().Where(w => w.WorldId == worldId && keys.Contains(w.Key));
|
|
||||||
|
|
||||||
return query.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all data entries for the world with the specified ID from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to get the data entries for.</param>
|
|
||||||
/// <returns>An enumerable collection of all data entries for the world with the specified ID.</returns>
|
|
||||||
public IEnumerable<WorldData> GetAllDataEntries(string worldId)
|
|
||||||
{
|
|
||||||
var query = sqlite.Table<WorldData>().Where(w => w.WorldId == worldId).Take(10000);
|
|
||||||
return query.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the size of the data entry, in bytes, with the specified world ID and key from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to get the data entry size for.</param>
|
|
||||||
/// <param name="key">The key of the data entry to get the size for.</param>
|
|
||||||
/// <returns>The size of the data entry with the specified world ID and key, or 0 if no such data entry exists in the database.</returns>
|
|
||||||
public int GetDataEntrySize(string worldId, string key)
|
|
||||||
{
|
|
||||||
var query = sqlite.Table<WorldData>().Where(w => w.WorldId == worldId && w.Key == key).Select(w => w.ValueSize);
|
|
||||||
|
|
||||||
return query.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deletes the data entry with the specified world ID and key from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to delete the data entry from.</param>
|
|
||||||
/// <param name="key">The key of the data entry to delete.</param>
|
|
||||||
public void DeleteDataEntry(string worldId, string key)
|
|
||||||
{
|
|
||||||
sqlite.Execute("DELETE FROM data WHERE world_id = ? AND key = ?", worldId, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deletes all data entries for the world with the specified ID from the database.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldId">The ID of the world to delete all data entries for.</param>
|
|
||||||
public void DeleteAllDataEntriesForWorld(string worldId)
|
|
||||||
{
|
|
||||||
sqlite.Execute("DELETE FROM data WHERE world_id = ?", worldId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Close()
|
|
||||||
{
|
|
||||||
sqlite.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ using NLog;
|
|||||||
using NLog.Targets;
|
using NLog.Targets;
|
||||||
using System;
|
using System;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -99,14 +100,12 @@ namespace VRCX
|
|||||||
// Layout with padding between the level/logger and message so that the message always starts at the same column
|
// Layout with padding between the level/logger and message so that the message always starts at the same column
|
||||||
Layout =
|
Layout =
|
||||||
"${longdate} [${level:uppercase=true:padding=-5}] ${logger:padding=-20} - ${message} ${exception:format=tostring}",
|
"${longdate} [${level:uppercase=true:padding=-5}] ${logger:padding=-20} - ${message} ${exception:format=tostring}",
|
||||||
ArchiveFileName = Path.Join(AppDataDirectory, "logs", "VRCX.{#}.log"),
|
ArchiveSuffixFormat = "{0:000}",
|
||||||
ArchiveNumbering = ArchiveNumberingMode.DateAndSequence,
|
|
||||||
ArchiveEvery = FileArchivePeriod.Day,
|
ArchiveEvery = FileArchivePeriod.Day,
|
||||||
MaxArchiveFiles = 4,
|
MaxArchiveFiles = 4,
|
||||||
MaxArchiveDays = 7,
|
MaxArchiveDays = 7,
|
||||||
ArchiveAboveSize = 10000000,
|
ArchiveAboveSize = 10000000,
|
||||||
ArchiveOldFileOnStartup = true,
|
ArchiveOldFileOnStartup = true,
|
||||||
ConcurrentWrites = true,
|
|
||||||
KeepFileOpen = true,
|
KeepFileOpen = true,
|
||||||
AutoFlush = true,
|
AutoFlush = true,
|
||||||
Encoding = System.Text.Encoding.UTF8
|
Encoding = System.Text.Encoding.UTF8
|
||||||
@@ -118,12 +117,13 @@ namespace VRCX
|
|||||||
Layout = "${longdate} [${level:uppercase=true:padding=-5}] ${logger:padding=-20} - ${message} ${exception:format=tostring}",
|
Layout = "${longdate} [${level:uppercase=true:padding=-5}] ${logger:padding=-20} - ${message} ${exception:format=tostring}",
|
||||||
DetectConsoleAvailable = true
|
DetectConsoleAvailable = true
|
||||||
};
|
};
|
||||||
builder.ForLogger("VRCX").FilterMinLevel(LogLevel.Info).WriteTo(consoleTarget);
|
builder.ForLogger().FilterMinLevel(LogLevel.Debug).WriteTo(consoleTarget);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !LINUX
|
#if !LINUX
|
||||||
[STAThread]
|
[STAThread]
|
||||||
|
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
|
||||||
private static void Main()
|
private static void Main()
|
||||||
{
|
{
|
||||||
if (Wine.GetIfWine())
|
if (Wine.GetIfWine())
|
||||||
@@ -150,7 +150,7 @@ namespace VRCX
|
|||||||
{
|
{
|
||||||
case DialogResult.Yes:
|
case DialogResult.Yes:
|
||||||
logger.Fatal("Handled Exception, user selected auto install of vc_redist.");
|
logger.Fatal("Handled Exception, user selected auto install of vc_redist.");
|
||||||
Update.DownloadInstallRedist();
|
Update.DownloadInstallRedist().GetAwaiter().GetResult();
|
||||||
MessageBox.Show(
|
MessageBox.Show(
|
||||||
"vc_redist has finished installing, if the issue persists upon next restart, please reinstall VRCX From GitHub,\nVRCX Will now restart.",
|
"vc_redist has finished installing, if the issue persists upon next restart, please reinstall VRCX From GitHub,\nVRCX Will now restart.",
|
||||||
"vc_redist installation complete", MessageBoxButtons.OK);
|
"vc_redist installation complete", MessageBoxButtons.OK);
|
||||||
@@ -210,6 +210,7 @@ namespace VRCX
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
|
||||||
private static void Run()
|
private static void Run()
|
||||||
{
|
{
|
||||||
var args = Environment.GetCommandLineArgs();
|
var args = Environment.GetCommandLineArgs();
|
||||||
@@ -236,7 +237,6 @@ namespace VRCX
|
|||||||
AppApiVr.Instance.Init();
|
AppApiVr.Instance.Init();
|
||||||
ProcessMonitor.Instance.Init();
|
ProcessMonitor.Instance.Init();
|
||||||
Discord.Instance.Init();
|
Discord.Instance.Init();
|
||||||
WorldDBManager.Instance.Init();
|
|
||||||
WebApi.Instance.Init();
|
WebApi.Instance.Init();
|
||||||
LogWatcher.Instance.Init();
|
LogWatcher.Instance.Init();
|
||||||
AutoAppLaunchManager.Instance.Init();
|
AutoAppLaunchManager.Instance.Init();
|
||||||
@@ -258,7 +258,6 @@ namespace VRCX
|
|||||||
AutoAppLaunchManager.Instance.Exit();
|
AutoAppLaunchManager.Instance.Exit();
|
||||||
LogWatcher.Instance.Exit();
|
LogWatcher.Instance.Exit();
|
||||||
WebApi.Instance.Exit();
|
WebApi.Instance.Exit();
|
||||||
WorldDBManager.Instance.Stop();
|
|
||||||
|
|
||||||
Discord.Instance.Exit();
|
Discord.Instance.Exit();
|
||||||
SystemMonitor.Instance.Exit();
|
SystemMonitor.Instance.Exit();
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
// Copyright(c) 2019-2025 pypy, Natsumi and individual contributors.
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// This work is licensed under the terms of the MIT license.
|
|
||||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace VRCX
|
|
||||||
{
|
|
||||||
public class SharedVariable
|
|
||||||
{
|
|
||||||
public static readonly SharedVariable Instance;
|
|
||||||
private readonly ReaderWriterLockSlim m_MapLock;
|
|
||||||
private readonly Dictionary<string, string> m_Map;
|
|
||||||
|
|
||||||
static SharedVariable()
|
|
||||||
{
|
|
||||||
Instance = new SharedVariable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SharedVariable()
|
|
||||||
{
|
|
||||||
m_MapLock = new ReaderWriterLockSlim();
|
|
||||||
m_Map = new Dictionary<string, string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
m_MapLock.EnterWriteLock();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
m_Map.Clear();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
m_MapLock.ExitWriteLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Get(string key)
|
|
||||||
{
|
|
||||||
m_MapLock.EnterReadLock();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (m_Map.TryGetValue(key, out string value) == true)
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
m_MapLock.ExitReadLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Set(string key, string value)
|
|
||||||
{
|
|
||||||
m_MapLock.EnterWriteLock();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
m_Map[key] = value;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
m_MapLock.ExitWriteLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(string key)
|
|
||||||
{
|
|
||||||
m_MapLock.EnterWriteLock();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return m_Map.Remove(key);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
m_MapLock.ExitWriteLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -91,12 +91,12 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CefSharp.OffScreen.NETCore" Version="137.0.100" />
|
<PackageReference Include="CefSharp.OffScreen.NETCore" Version="138.0.170" />
|
||||||
<PackageReference Include="CefSharp.WinForms.NETCore" Version="137.0.100" />
|
<PackageReference Include="CefSharp.WinForms.NETCore" Version="138.0.170" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.3.0.28" />
|
<PackageReference Include="DiscordRichPresence" Version="1.3.0.28" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="NLog" Version="5.5.0" />
|
<PackageReference Include="NLog" Version="6.0.1" />
|
||||||
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
|
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
|
||||||
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
|
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
|
||||||
@@ -104,10 +104,10 @@
|
|||||||
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
||||||
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />
|
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />
|
||||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
|
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
|
||||||
<PackageReference Include="System.Management" Version="9.0.6" />
|
<PackageReference Include="System.Management" Version="9.0.7" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||||
<PackageReference Include="System.Text.Json" Version="9.0.6" />
|
<PackageReference Include="System.Text.Json" Version="9.0.7" />
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||||
<PackageReference Include="Websocket.Client" Version="5.2.0" />
|
<PackageReference Include="Websocket.Client" Version="5.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -93,12 +93,12 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.JavaScript.NodeApi" Version="0.9.11" />
|
<PackageReference Include="Microsoft.JavaScript.NodeApi" Version="0.9.12" />
|
||||||
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.9.11" />
|
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.9.12" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.3.0.28" />
|
<PackageReference Include="DiscordRichPresence" Version="1.3.0.28" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="NLog" Version="5.5.0" />
|
<PackageReference Include="NLog" Version="6.0.1" />
|
||||||
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
|
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
|
||||||
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
|
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
|
||||||
@@ -106,10 +106,10 @@
|
|||||||
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
||||||
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />
|
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />
|
||||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
|
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
|
||||||
<PackageReference Include="System.Management" Version="9.0.6" />
|
<PackageReference Include="System.Management" Version="9.0.7" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||||
<PackageReference Include="System.Text.Json" Version="9.0.6" />
|
<PackageReference Include="System.Text.Json" Version="9.0.7" />
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||||
<PackageReference Include="Websocket.Client" Version="5.2.0" />
|
<PackageReference Include="Websocket.Client" Version="5.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -121,8 +121,6 @@
|
|||||||
<Compile Remove="Cef\**" />
|
<Compile Remove="Cef\**" />
|
||||||
<Content Remove="AppApi\Cef\**" />
|
<Content Remove="AppApi\Cef\**" />
|
||||||
<Compile Remove="AppApi\Cef\**" />
|
<Compile Remove="AppApi\Cef\**" />
|
||||||
<Content Remove="PWI\**" />
|
|
||||||
<Compile Remove="PWI\**" />
|
|
||||||
<Content Remove="Overlay\**" />
|
<Content Remove="Overlay\**" />
|
||||||
<Compile Remove="Overlay\**" />
|
<Compile Remove="Overlay\**" />
|
||||||
<Content Remove="DBMerger\**" />
|
<Content Remove="DBMerger\**" />
|
||||||
|
|||||||
@@ -33,10 +33,6 @@ Versión beta/nocturna disponible [aquí](https://vrcx.app/github/nightly) o en
|
|||||||
- :electric_plug: Inicia automáticamente aplicaciones cuando inicias VRChat
|
- :electric_plug: Inicia automáticamente aplicaciones cuando inicias VRChat
|
||||||
- Puedes configurar VRCX para iniciar otras aplicaciones cuando inicias VRChat.
|
- Puedes configurar VRCX para iniciar otras aplicaciones cuando inicias VRChat.
|
||||||
- Por ejemplo, podrías hacer que VRCX inicie una aplicación OSC o una aplicación de cambio de voz cuando se abra VRChat.
|
- Por ejemplo, podrías hacer que VRCX inicie una aplicación OSC o una aplicación de cambio de voz cuando se abra VRChat.
|
||||||
- :floppy_disk: Persistencia de mundos
|
|
||||||
- Para los mundos que soportan la función, VRCX puede guardar configuraciones de mundos, estados guardados, inventarios y otros datos.
|
|
||||||
- **Nota**: Para usar esta función, debes tener "Permitir URLs no confiables" habilitado en la configuración de VRChat.
|
|
||||||
- Para desarrolladores: [Página Wiki - Persistencia de Mundos (PWI)](<https://github.com/vrcx-team/VRCX/wiki/World-Persistence-(PWI)>)
|
|
||||||
- :mag: Busca avatares, usuarios, mundos y grupos
|
- :mag: Busca avatares, usuarios, mundos y grupos
|
||||||
- :earth_americas: Crea una lista de favoritos de mundos local y sin restricciones
|
- :earth_americas: Crea una lista de favoritos de mundos local y sin restricciones
|
||||||
- :camera: Almacena datos de mundos en las fotos que tomas en el juego, para que puedas recordar ese mundo donde tomaste esas fotos geniales hace como... 6 meses.
|
- :camera: Almacena datos de mundos en las fotos que tomas en el juego, para que puedas recordar ese mundo donde tomaste esas fotos geniales hace como... 6 meses.
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ Téléchargez et exécutez le dernier programme d'installation (`VRCX_Setup.exe`
|
|||||||
- :electric_plug: Lancement automatique d'applications lorsque vous démarrez VRChat
|
- :electric_plug: Lancement automatique d'applications lorsque vous démarrez VRChat
|
||||||
- Vous pouvez configurer VRCX pour lancer d'autres applications lorsque vous démarrez VRChat.
|
- Vous pouvez configurer VRCX pour lancer d'autres applications lorsque vous démarrez VRChat.
|
||||||
- Par exemple, vous pourriez avoir VRCX qui lance une application OSC ou une application de modification de voix lorsque VRChat s'ouvre.
|
- Par exemple, vous pourriez avoir VRCX qui lance une application OSC ou une application de modification de voix lorsque VRChat s'ouvre.
|
||||||
- :floppy_disk: Persistance de Monde
|
|
||||||
- Pour les mondes qui prennent en charge cette fonctionnalité, VRCX peut sauvegarder les paramètres de monde, les états sauvegardés, les inventaires et d'autres données !
|
|
||||||
- **Note** : Pour utiliser cette fonctionnalité, vous devez avoir activé "Allow Untrusted URLs" dans vos paramètres VRChat.
|
|
||||||
- Pour les Développeurs : [Page Wiki - World Persistence (PWI)](<https://github.com/vrcx-team/VRCX/wiki/World-Persistence-(PWI)>)
|
|
||||||
- :mag: Recherche d'avatars, d'utilisateurs, de mondes et de groupes
|
- :mag: Recherche d'avatars, d'utilisateurs, de mondes et de groupes
|
||||||
- :earth_americas: Créez une liste de favoris de mondes locale et sans restrictions
|
- :earth_americas: Créez une liste de favoris de mondes locale et sans restrictions
|
||||||
- :camera: Stockez les données du monde dans les images que vous prenez en jeu, afin de vous souvenir de ce monde où vous avez pris ces superbes photos il y a... 6 mois !
|
- :camera: Stockez les données du monde dans les images que vous prenez en jeu, afin de vous souvenir de ce monde où vous avez pris ces superbes photos il y a... 6 mois !
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ Scarica e installa l'ultimo installer (`VRCX_Setup.exe`) da [qui](https://github
|
|||||||
- :electric_plug: Avvio automatico delle applicazioni all'avvio di VRChat
|
- :electric_plug: Avvio automatico delle applicazioni all'avvio di VRChat
|
||||||
- È possibile configurare VRCX per avviare altre applicazioni quando si avvia VRChat.
|
- È possibile configurare VRCX per avviare altre applicazioni quando si avvia VRChat.
|
||||||
- Ad esempio, si può fare in modo che VRCX lanci un'applicazione OSC o un cambiavoce all'apertura di VRChat.
|
- Ad esempio, si può fare in modo che VRCX lanci un'applicazione OSC o un cambiavoce all'apertura di VRChat.
|
||||||
- :floppy_disk: Persistenza del mondo
|
|
||||||
- Per i mondi che supportano questa funzione, VRCX può salvare le impostazioni del mondo, gli stati, gli inventari e altri dati!
|
|
||||||
- **Nota**: Per utilizzare questa funzione, è necessario che nelle impostazioni di VRChat sia abilitato "Consenti URL non attendibili".
|
|
||||||
- Per gli sviluppatori: [Pagina Wiki - World Persistence (PWI)](<https://github.com/vrcx-team/VRCX/wiki/World-Persistence-(PWI)>)
|
|
||||||
- :mag: Ricerca di avatar, utenti, mondi e gruppi
|
- :mag: Ricerca di avatar, utenti, mondi e gruppi
|
||||||
- :earth_americas: Costruire un elenco di preferiti locali e non limitati del mondo
|
- :earth_americas: Costruire un elenco di preferiti locali e non limitati del mondo
|
||||||
- :camera: Memorizza i dati del mondo nelle foto scattate nel gioco, in modo da poter ricordare quel mondo in cui hai scattato quelle belle foto come... 6 mesi fa!
|
- :camera: Memorizza i dati del mondo nelle foto scattate nel gioco, in modo da poter ricordare quel mondo in cui hai scattato quelle belle foto come... 6 mesi fa!
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ VRCX は VRChat クライアント (デスクトップ & VR) や Web サイト
|
|||||||
- :electric_plug: VRChat 起動時に一緒にアプリを起動
|
- :electric_plug: VRChat 起動時に一緒にアプリを起動
|
||||||
- VRChat の起動時に他のアプリを同時起動できるよう設定できます。
|
- VRChat の起動時に他のアプリを同時起動できるよう設定できます。
|
||||||
- 例えば、VRChat を起動したら同時に OSC アプリやボイスチェンジャーを起動するようにできます。
|
- 例えば、VRChat を起動したら同時に OSC アプリやボイスチェンジャーを起動するようにできます。
|
||||||
- :floppy_disk: ワールドの永続化
|
|
||||||
- ワールドが対応している場合、設定やセーブデータ、インベントリなどのデータを保存することができます。
|
|
||||||
- **注意**: この機能を使うには、VRChat の設定で「信頼されていない URL を許可」を有効化しておく必要があります。
|
|
||||||
- 開発者向け: [Wiki Page - World Persistence (PWI)](<https://github.com/vrcx-team/VRCX/wiki/World-Persistence-(PWI)>)
|
|
||||||
- :mag: アバター、ユーザー、ワールド、グループの検索
|
- :mag: アバター、ユーザー、ワールド、グループの検索
|
||||||
- :earth_americas: 無制限!ローカル保存のワールドお気に入りリスト
|
- :earth_americas: 無制限!ローカル保存のワールドお気に入りリスト
|
||||||
- :camera: ゲーム内で撮った写真にワールドデータを保存することで、半年前に撮影した綺麗なワールドをいつでも振り返ることができます。
|
- :camera: ゲーム内で撮った写真にワールドデータを保存することで、半年前に撮影した綺麗なワールドをいつでも振り返ることができます。
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
[](https://github.com/vrcx-team/VRCX/releases/latest)
|
[](https://github.com/vrcx-team/VRCX/releases/latest)
|
||||||
[](https://github.com/vrcx-team/VRCX/releases/latest)
|
[](https://github.com/vrcx-team/VRCX/releases/latest)
|
||||||
[](https://github.com/vrcx-team/VRCX/actions/workflows/github_actions.yml)
|
[](https://github.com/vrcx-team/VRCX/actions/workflows/github_actions.yml)
|
||||||
[](https://crowdin.com/project/vrcx)
|
|
||||||
[](https://vrcx.app/discord)
|
[](https://vrcx.app/discord)
|
||||||
|
|
||||||
| **English** | [Français](./README.fr.md) | [日本語](./README.jp.md) | [简体中文](./README.zh_CN.md) | [Italiano](./README.it.md) | [Русский](./README.ru_RU.md) | [Español](./README.es.md) | [Polski](./README.pl.md)
|
| **English** | [Français](./README.fr.md) | [日本語](./README.jp.md) | [简体中文](./README.zh_CN.md) | [Italiano](./README.it.md) | [Русский](./README.ru_RU.md) | [Español](./README.es.md) | [Polski](./README.pl.md)
|
||||||
@@ -34,10 +33,6 @@ Beta/nightly build available [here](https://vrcx.app/github/nightly) or in-app `
|
|||||||
- :electric_plug: Automatically launch apps when you start VRChat
|
- :electric_plug: Automatically launch apps when you start VRChat
|
||||||
- You can configure VRCX to launch other apps when you start VRChat.
|
- You can configure VRCX to launch other apps when you start VRChat.
|
||||||
- For example, you could have VRCX launch an OSC app or a voice changer app when VRChat opens up.
|
- For example, you could have VRCX launch an OSC app or a voice changer app when VRChat opens up.
|
||||||
- :floppy_disk: World Persistence
|
|
||||||
- For worlds that support the feature, VRCX can save world settings, save states, inventories, and other data!
|
|
||||||
- **Note**: To use this feature, you must have "Allow Untrusted URLs" enabled in your VRChat settings.
|
|
||||||
- For Developers: [Wiki Page - World Persistence (PWI)](<https://github.com/vrcx-team/VRCX/wiki/World-Persistence-(PWI)>)
|
|
||||||
- :mag: Search for avatars, users, worlds, and groups
|
- :mag: Search for avatars, users, worlds, and groups
|
||||||
- :earth_americas: Build a local, unrestricted world favorites list
|
- :earth_americas: Build a local, unrestricted world favorites list
|
||||||
- :camera: Store world data in the pictures you take in-game, so you can remember that one world you took those cool pictures in like... 6 months ago!
|
- :camera: Store world data in the pictures you take in-game, so you can remember that one world you took those cool pictures in like... 6 months ago!
|
||||||
@@ -47,7 +42,7 @@ Beta/nightly build available [here](https://vrcx.app/github/nightly) or in-app `
|
|||||||
- :tv: See the links to videos and that are playing in the world you're in, as well as various other logged data.
|
- :tv: See the links to videos and that are playing in the world you're in, as well as various other logged data.
|
||||||
- :bar_chart: Improved Discord Rich Presence
|
- :bar_chart: Improved Discord Rich Presence
|
||||||
- You can optionally display more information about your current instance in Discord.
|
- You can optionally display more information about your current instance in Discord.
|
||||||
- World integration for popular worlds like PyPyDance, LSMedia, Movies&Chill and VRDancing.
|
- World integration for popular worlds like Popcorn Palace, PyPyDance, VRDancing and LSMedia.
|
||||||
- This includes the world thumbnail, name, instance ID, and player count, depending on your settings and whether the lobby is private. You can also add a join button for public lobbies!
|
- This includes the world thumbnail, name, instance ID, and player count, depending on your settings and whether the lobby is private. You can also add a join button for public lobbies!
|
||||||
- :crystal_ball: VR Overlay with configurable live feed of all supported events/notifications
|
- :crystal_ball: VR Overlay with configurable live feed of all supported events/notifications
|
||||||
- :outbox_tray: Upload avatar/world images without Unity
|
- :outbox_tray: Upload avatar/world images without Unity
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ Pobierz i uruchom najnowszy instalator (`VRCX_Setup.exe`) [stąd](https://github
|
|||||||
- :electric_plug: Automatycznie uruchamiaj aplikacje podczas startu VRChat
|
- :electric_plug: Automatycznie uruchamiaj aplikacje podczas startu VRChat
|
||||||
- Ustaw, aby VRCX uruchamiał inne aplikacje, gdy włączysz VRChat.
|
- Ustaw, aby VRCX uruchamiał inne aplikacje, gdy włączysz VRChat.
|
||||||
- Na przykład, możesz ustawić, aby VRCX uruchamiał aplikację OSC lub modulacji głosu, gdy włączysz VRChat.
|
- Na przykład, możesz ustawić, aby VRCX uruchamiał aplikację OSC lub modulacji głosu, gdy włączysz VRChat.
|
||||||
- :floppy_disk: Persystencja światów
|
|
||||||
- VRCX może zapisywać ustawienia, stany, ekwipunek czy inne dane światów, jeżeli wspierają one tę funkcję.
|
|
||||||
- **Notatka**: Aby używać tej funkcji, musisz zezwolić na niezaufane linki URL w ustawieniach VRChatu.
|
|
||||||
- Dla programistów: [Strona wiki o persystencji światów](<https://github.com/vrcx-team/VRCX/wiki/World-Persistence-(PWI)>)
|
|
||||||
- :mag: Wyszukuj awatary, użytkwoników, światy i grupy
|
- :mag: Wyszukuj awatary, użytkwoników, światy i grupy
|
||||||
- :earth_americas: Twórz lokalną i nieograniczoną listę ulubionych światów
|
- :earth_americas: Twórz lokalną i nieograniczoną listę ulubionych światów
|
||||||
- :camera: Przechowuj dane światów w zdjęciach robionych w grze, aby pamiętać o światach odwiedzanych dawno temu!
|
- :camera: Przechowuj dane światów w zdjęciach robionych w grze, aby pamiętać o światach odwiedzanych dawno temu!
|
||||||
|
|||||||
@@ -33,10 +33,6 @@ VRCX является ассистентом/компаньоном прилож
|
|||||||
- :electric_plug: Автоматически запускает приложения при запуске VRChat
|
- :electric_plug: Автоматически запускает приложения при запуске VRChat
|
||||||
- Вы можете настроить VRCX на запуск других приложений при запуске VRChat.
|
- Вы можете настроить VRCX на запуск других приложений при запуске VRChat.
|
||||||
- Например, при открытии VRChat вы можете запустить приложение OSC или программу для изменения голоса.
|
- Например, при открытии VRChat вы можете запустить приложение OSC или программу для изменения голоса.
|
||||||
- :floppy_disk: Сохранение мира
|
|
||||||
- В мирах, поддерживающих эту функцию, VRCX может сохранять настройки мира, сохранять состояния, инвентарь и другие данные!
|
|
||||||
- **Примечание**: Для использования этой функции в настройках VRChat должна быть включена опция "Разрешить недоверенные URL'ы" ("Allow Untrusted URLs").
|
|
||||||
- Для разработчиков: [Вики страница - Сохранение мира (PWI)](<https://github.com/vrcx-team/VRCX/wiki/World-Persistence-(PWI)>)
|
|
||||||
- :mag: Поиск аватаров, пользователей, миров и групп
|
- :mag: Поиск аватаров, пользователей, миров и групп
|
||||||
- :earth_americas: Создает локальный, неограниченный список избранных миров
|
- :earth_americas: Создает локальный, неограниченный список избранных миров
|
||||||
- :camera: Храните данные о мире в фотографиях, которые вы делаете в игре, чтобы вы могли вспомнить тот мир, в котором вы сделали те классные фотографии, например... 6 месяцев назад!
|
- :camera: Храните данные о мире в фотографиях, которые вы делаете в игре, чтобы вы могли вспомнить тот мир, в котором вы сделали те классные фотографии, например... 6 месяцев назад!
|
||||||
|
|||||||
@@ -33,10 +33,6 @@ VRCX 是一款用于 VRChat 的外部辅助小工具,可以比 VRChat 游戏
|
|||||||
- :electric_plug: 当你启动 VRChat 时自动启动其他程序
|
- :electric_plug: 当你启动 VRChat 时自动启动其他程序
|
||||||
- 你可以配置 VRCX,让其在启动 VRChat 时自动启动你指定的程序。
|
- 你可以配置 VRCX,让其在启动 VRChat 时自动启动你指定的程序。
|
||||||
- 例如,你可以在启动时让 VRCX 打开一个 OSC 应用或变声器。
|
- 例如,你可以在启动时让 VRCX 打开一个 OSC 应用或变声器。
|
||||||
- :floppy_disk: 世界数据持久化保存 (World Persistence)
|
|
||||||
- 对于支持此功能的世界,VRCX 将能够保存世界设置、存档、各种清单以及其他可以保存的数据!
|
|
||||||
- **注意**:要使用此功能,必须在 VRChat 设置中启用 “Allow Untrusted URLs”。
|
|
||||||
- 给世界开发者的指南: [Wiki Page - World Persistence (PWI)](<https://github.com/vrcx-team/VRCX/wiki/World-Persistence-(PWI)>)
|
|
||||||
- :mag: 以更加方便的形式搜索模型、房间、世界以及群组。
|
- :mag: 以更加方便的形式搜索模型、房间、世界以及群组。
|
||||||
- :earth_americas: 创建本地的、没有任何限制的世界收藏夹
|
- :earth_americas: 创建本地的、没有任何限制的世界收藏夹
|
||||||
- :camera: 将世界数据存储在你在游戏内拍摄的照片中,这样即使几个月后也能知道当时是在什么世界拍的照片
|
- :camera: 将世界数据存储在你在游戏内拍摄的照片中,这样即使几个月后也能知道当时是在什么世界拍的照片
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@echo off
|
@echo off
|
||||||
cd ..
|
cd ../../
|
||||||
dotnet build Dotnet\VRCX-Cef.csproj -p:Configuration=Release -p:Platform=x64 -p:RestorePackagesConfig=true -t:"Restore;Clean;Build" -m --self-contained
|
dotnet build Dotnet\VRCX-Cef.csproj -p:Configuration=Release -p:Platform=x64 -p:RestorePackagesConfig=true -t:"Restore;Clean;Build" -m --self-contained
|
||||||
mklink /J "%~dp0\..\build\Cef\html" "%~dp0\..\build\html"
|
|
||||||
pause
|
pause
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
@echo off
|
@echo off
|
||||||
setlocal enabledelayedexpansion
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
if not exist "%~dp0\..\build\Cef" (
|
||||||
|
mkdir "%~dp0\..\build\Cef"
|
||||||
|
)
|
||||||
|
if not exist "%~dp0\..\build\html" (
|
||||||
|
mkdir "%~dp0\..\build\html"
|
||||||
|
)
|
||||||
if not exist "%~dp0\..\build\Cef\html" (
|
if not exist "%~dp0\..\build\Cef\html" (
|
||||||
mklink /J "%~dp0\..\build\Cef\html" "%~dp0\..\build\html"
|
mklink /J "%~dp0\..\build\Cef\html" "%~dp0\..\build\html"
|
||||||
)
|
)
|
||||||
3
build-scripts/node/build-node-debug.cmd
Normal file
3
build-scripts/node/build-node-debug.cmd
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
cd ../../
|
||||||
|
call npm run dev
|
||||||
|
pause
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
cd ../
|
cd ../../
|
||||||
call npm ci
|
|
||||||
call npm run prod
|
call npm run prod
|
||||||
pause
|
pause
|
||||||
3
build-scripts/node/restore-node-dependencies.cmd
Normal file
3
build-scripts/node/restore-node-dependencies.cmd
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
cd ../../
|
||||||
|
call npm ci
|
||||||
|
pause
|
||||||
60
eslint.config.mjs
Normal file
60
eslint.config.mjs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import pluginVue from 'eslint-plugin-vue';
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,mjs,cjs,vue}'],
|
||||||
|
plugins: { js },
|
||||||
|
extends: ['js/recommended']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,mjs,cjs,vue}'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
CefSharp: 'readonly',
|
||||||
|
VRCX: 'readonly',
|
||||||
|
VRCXStorage: 'readonly',
|
||||||
|
SQLite: 'readonly',
|
||||||
|
LogWatcher: 'readonly',
|
||||||
|
Discord: 'readonly',
|
||||||
|
AppApi: 'readonly',
|
||||||
|
AppApiVr: 'readonly',
|
||||||
|
WebApi: 'readonly',
|
||||||
|
AssetBundleManager: 'readonly',
|
||||||
|
WINDOWS: 'readonly',
|
||||||
|
LINUX: 'readonly',
|
||||||
|
webApiService: 'readonly',
|
||||||
|
process: 'readonly'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
'**/webpack.*.js',
|
||||||
|
'src-electron/*.js',
|
||||||
|
'src/localization/*.js'
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
sourceType: 'commonjs',
|
||||||
|
globals: {
|
||||||
|
...globals.node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pluginVue.configs['flat/vue2-essential'],
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'no-unused-vars': 'warn',
|
||||||
|
'no-case-declarations': 'off',
|
||||||
|
'no-control-regex': 'warn',
|
||||||
|
|
||||||
|
'vue/no-mutating-props': 'warn',
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
|
'vue/no-v-text-v-html-on-component': 'off',
|
||||||
|
'vue/no-use-v-if-with-v-for': 'warn'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
17
jsconfig.json
Normal file
17
jsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "ESNext",
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"lib": ["esnext", "dom", "dom.iterable"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*", "src-electron/**/*"],
|
||||||
|
"exclude": ["node_modules", "build"]
|
||||||
|
}
|
||||||
6439
package-lock.json
generated
6439
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@@ -4,6 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"main": "src-electron/main.js",
|
"main": "src-electron/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"dev": "cross-env PLATFORM=windows webpack serve --config webpack.config.js --mode development",
|
||||||
"watch": "cross-env PLATFORM=windows webpack --config webpack.config.js --mode development --watch",
|
"watch": "cross-env PLATFORM=windows webpack --config webpack.config.js --mode development --watch",
|
||||||
"watch-linux": "cross-env PLATFORM=linux webpack --config webpack.config.js --mode development --watch",
|
"watch-linux": "cross-env PLATFORM=linux webpack --config webpack.config.js --mode development --watch",
|
||||||
"localization": "node ./src/localization/localizationHelperCLI.js",
|
"localization": "node ./src/localization/localizationHelperCLI.js",
|
||||||
@@ -27,50 +28,52 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/vrcx-team/VRCX#readme",
|
"homepage": "https://github.com/vrcx-team/VRCX#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.27.5",
|
|
||||||
"@electron/rebuild": "^4.0.1",
|
"@electron/rebuild": "^4.0.1",
|
||||||
"@fontsource/noto-sans-jp": "^5.2.5",
|
"@eslint/js": "^9.31.0",
|
||||||
"@fontsource/noto-sans-kr": "^5.2.5",
|
"@fontsource/noto-sans-jp": "^5.2.6",
|
||||||
|
"@fontsource/noto-sans-kr": "^5.2.6",
|
||||||
"@fontsource/noto-sans-sc": "^5.2.6",
|
"@fontsource/noto-sans-sc": "^5.2.6",
|
||||||
"@fontsource/noto-sans-tc": "^5.2.6",
|
"@fontsource/noto-sans-tc": "^5.2.6",
|
||||||
"@infolektuell/noto-color-emoji": "^0.2.0",
|
"@infolektuell/noto-color-emoji": "^0.2.0",
|
||||||
"@prettier/plugin-pug": "^3.4.0",
|
"@prettier/plugin-pug": "^3.4.0",
|
||||||
|
"@types/node": "^24.0.13",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"copy-webpack-plugin": "^13.0.0",
|
"copy-webpack-plugin": "^13.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.2",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"default-passive-events": "^4.0.0",
|
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
"electron": "^36.4.0",
|
"electron": "^37.2.1",
|
||||||
"electron-builder": "^26.0.12",
|
"electron-builder": "^26.0.12",
|
||||||
"element-ui": "^2.15.14",
|
"element-ui": "^2.15.14",
|
||||||
"esbuild-loader": "^4.3.0",
|
"esbuild-loader": "^4.3.0",
|
||||||
"eslint": "^9.28.0",
|
"eslint": "^9.31.0",
|
||||||
"eslint-config-prettier": "^10.1.5",
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"eslint-plugin-vue": "^9.32.0",
|
"eslint-plugin-vue": "^9.33.0",
|
||||||
|
"globals": "^16.3.0",
|
||||||
"html-webpack-plugin": "^5.6.3",
|
"html-webpack-plugin": "^5.6.3",
|
||||||
"mini-css-extract-plugin": "^2.9.2",
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
"normalize.css": "^8.0.1",
|
|
||||||
"noty": "^3.2.0-beta-deprecated",
|
"noty": "^3.2.0-beta-deprecated",
|
||||||
"prettier": "^3.5.3",
|
"pinia": "^2.3.1",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"pug-plain-loader": "^1.1.0",
|
"pug-plain-loader": "^1.1.0",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
"sass": "^1.89.1",
|
"sass": "^1.89.2",
|
||||||
"sass-loader": "^16.0.5",
|
"sass-loader": "^16.0.5",
|
||||||
"vue": "^2.7.16",
|
"vue": "^2.7.16",
|
||||||
"vue-data-tables": "^3.4.5",
|
"vue-data-tables": "^3.4.5",
|
||||||
"vue-demi": "^0.14.10",
|
"vue-demi": "^0.14.10",
|
||||||
"vue-i18n": "^8.28.2",
|
"vue-i18n": "^8.28.2",
|
||||||
"vue-i18n-bridge": "^9.14.1",
|
"vue-i18n-bridge": "^9.14.1",
|
||||||
"vue-lazyload": "^1.3.4",
|
"vue-lazyload": "^1.3.5",
|
||||||
"vue-loader": "^15.11.1",
|
"vue-loader": "^15.11.1",
|
||||||
"vue-markdown": "^2.2.4",
|
"vue-markdown": "^2.2.4",
|
||||||
"vue-marquee-text-component": "^1.2.0",
|
"vue-marquee-text-component": "^1.2.0",
|
||||||
"webpack": "^5.99.9",
|
"webpack": "^5.100.1",
|
||||||
"webpack-cli": "^6.0.1",
|
"webpack-cli": "^6.0.1",
|
||||||
"worker-timers": "^8.0.21",
|
"webpack-dev-server": "^5.2.2",
|
||||||
|
"worker-timers": "^8.0.23",
|
||||||
"yargs": "^18.0.0"
|
"yargs": "^18.0.0"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
@@ -135,6 +138,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hazardous": "^0.3.0",
|
"hazardous": "^0.3.0",
|
||||||
"node-api-dotnet": "^0.9.11"
|
"node-api-dotnet": "^0.9.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
109
src/App.vue
Normal file
109
src/App.vue
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<script>
|
||||||
|
import Vue, { onMounted } from 'vue';
|
||||||
|
import template from './app.pug';
|
||||||
|
import { createGlobalStores } from './stores';
|
||||||
|
import { watchState } from './service/watchState';
|
||||||
|
|
||||||
|
import Login from './views/Login/Login.vue';
|
||||||
|
import NavMenu from './components/NavMenu.vue';
|
||||||
|
import Sidebar from './views/Sidebar/Sidebar.vue';
|
||||||
|
import Feed from './views/Feed/Feed.vue';
|
||||||
|
import GameLog from './views/GameLog/GameLog.vue';
|
||||||
|
import PlayerList from './views/PlayerList/PlayerList.vue';
|
||||||
|
import Search from './views/Search/Search.vue';
|
||||||
|
import Favorites from './views/Favorites/Favorites.vue';
|
||||||
|
import FriendLog from './views/FriendLog/FriendLog.vue';
|
||||||
|
import Moderation from './views/Moderation/Moderation.vue';
|
||||||
|
import Notification from './views/Notifications/Notification.vue';
|
||||||
|
import FriendList from './views/FriendList/FriendList.vue';
|
||||||
|
import Charts from './views/Charts/Charts.vue';
|
||||||
|
import Profile from './views/Profile/Profile.vue';
|
||||||
|
import Settings from './views/Settings/Settings.vue';
|
||||||
|
|
||||||
|
import UserDialog from './components/dialogs/UserDialog/UserDialog.vue';
|
||||||
|
import WorldDialog from './components/dialogs/WorldDialog/WorldDialog.vue';
|
||||||
|
import AvatarDialog from './components/dialogs/AvatarDialog/AvatarDialog.vue';
|
||||||
|
import GroupDialog from './components/dialogs/GroupDialog/GroupDialog.vue';
|
||||||
|
import GalleryDialog from './components/dialogs/GalleryDialog.vue';
|
||||||
|
import FullscreenImageDialog from './components/dialogs/FullscreenImageDialog.vue';
|
||||||
|
import PreviousInstancesInfoDialog from './components/dialogs/PreviousInstancesDialog/PreviousInstancesInfoDialog.vue';
|
||||||
|
import LaunchDialog from './components/dialogs/LaunchDialog.vue';
|
||||||
|
import LaunchOptionsDialog from './views/Settings/dialogs/LaunchOptionsDialog.vue';
|
||||||
|
import FriendImportDialog from './views/Favorites/dialogs/FriendImportDialog.vue';
|
||||||
|
import WorldImportDialog from './views/Favorites/dialogs/WorldImportDialog.vue';
|
||||||
|
import AvatarImportDialog from './views/Favorites/dialogs/AvatarImportDialog.vue';
|
||||||
|
import ChooseFavoriteGroupDialog from './components/dialogs/ChooseFavoriteGroupDialog.vue';
|
||||||
|
import EditInviteMessageDialog from './views/Profile/dialogs/EditInviteMessageDialog.vue';
|
||||||
|
import VRCXUpdateDialog from './components/dialogs/VRCXUpdateDialog.vue';
|
||||||
|
import VRChatConfigDialog from './views/Settings/dialogs/VRChatConfigDialog.vue';
|
||||||
|
import PrimaryPasswordDialog from './views/Settings/dialogs/PrimaryPasswordDialog.vue';
|
||||||
|
|
||||||
|
import { utils } from './shared/utils/_utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
template,
|
||||||
|
components: {
|
||||||
|
Login,
|
||||||
|
NavMenu,
|
||||||
|
Sidebar,
|
||||||
|
Feed,
|
||||||
|
GameLog,
|
||||||
|
PlayerList,
|
||||||
|
Search,
|
||||||
|
Favorites,
|
||||||
|
FriendLog,
|
||||||
|
Moderation,
|
||||||
|
Notification,
|
||||||
|
FriendList,
|
||||||
|
Charts,
|
||||||
|
Profile,
|
||||||
|
Settings,
|
||||||
|
|
||||||
|
UserDialog,
|
||||||
|
WorldDialog,
|
||||||
|
AvatarDialog,
|
||||||
|
GroupDialog,
|
||||||
|
GalleryDialog,
|
||||||
|
FullscreenImageDialog,
|
||||||
|
PreviousInstancesInfoDialog,
|
||||||
|
LaunchDialog,
|
||||||
|
LaunchOptionsDialog,
|
||||||
|
FriendImportDialog,
|
||||||
|
WorldImportDialog,
|
||||||
|
AvatarImportDialog,
|
||||||
|
ChooseFavoriteGroupDialog,
|
||||||
|
EditInviteMessageDialog,
|
||||||
|
VRCXUpdateDialog,
|
||||||
|
VRChatConfigDialog,
|
||||||
|
PrimaryPasswordDialog
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const store = createGlobalStores();
|
||||||
|
Vue.prototype.store = store;
|
||||||
|
Vue.prototype.utils = utils;
|
||||||
|
|
||||||
|
store.updateLoop.updateLoop();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
store.gameLog.getGameLogTable();
|
||||||
|
await store.auth.migrateStoredUsers();
|
||||||
|
store.auth.autoLoginAfterMounted();
|
||||||
|
store.vrcx.checkAutoBackupRestoreVrcRegistry();
|
||||||
|
store.game.checkVRChatDebugLogging();
|
||||||
|
try {
|
||||||
|
if (await AppApi.IsRunningUnderWine()) {
|
||||||
|
store.vrcx.isRunningUnderWine = true;
|
||||||
|
store.vrcx.applyWineEmojis();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err, 'Failed to check if running under Wine');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
store,
|
||||||
|
watchState
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
69
src/api/auth.js
Normal file
69
src/api/auth.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { request } from '../service/request';
|
||||||
|
import { useUserStore } from '../stores';
|
||||||
|
|
||||||
|
const loginReq = {
|
||||||
|
/**
|
||||||
|
* @param {{ code: string }} params One-time password
|
||||||
|
* @returns {Promise<{json: any, params}>}
|
||||||
|
*/
|
||||||
|
verifyOTP(params) {
|
||||||
|
return request('auth/twofactorauth/otp/verify', {
|
||||||
|
method: 'POST',
|
||||||
|
params
|
||||||
|
}).then((json) => {
|
||||||
|
const args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ code: string }} params One-time token
|
||||||
|
* @returns {Promise<{json: any, params}>}
|
||||||
|
*/
|
||||||
|
verifyTOTP(params) {
|
||||||
|
return request('auth/twofactorauth/totp/verify', {
|
||||||
|
method: 'POST',
|
||||||
|
params
|
||||||
|
}).then((json) => {
|
||||||
|
const args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ code: string }} params One-time token
|
||||||
|
* @returns {Promise<{json: any, params}>}
|
||||||
|
*/
|
||||||
|
verifyEmailOTP(params) {
|
||||||
|
return request('auth/twofactorauth/emailotp/verify', {
|
||||||
|
method: 'POST',
|
||||||
|
params
|
||||||
|
}).then((json) => {
|
||||||
|
const args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getConfig() {
|
||||||
|
return request('config', {
|
||||||
|
method: 'GET'
|
||||||
|
}).then((json) => {
|
||||||
|
const args = {
|
||||||
|
json
|
||||||
|
};
|
||||||
|
useUserStore().handleConfig(args);
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default loginReq;
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
// #region | API: Avatar
|
import { request } from '../service/request';
|
||||||
|
import { useUserStore } from '../stores';
|
||||||
|
|
||||||
const avatarReq = {
|
const avatarReq = {
|
||||||
/**
|
/**
|
||||||
* @param {{ avatarId: string }} params
|
* @type {import('../types/avatar').getAvatar}
|
||||||
* @returns {Promise<{json: any, params}>}
|
|
||||||
*/
|
*/
|
||||||
getAvatar(params) {
|
getAvatar(params) {
|
||||||
return window.API.call(`avatars/${params.avatarId}`, {
|
return request(`avatars/${params.avatarId}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATAR', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -37,7 +36,7 @@ const avatarReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getAvatars(params) {
|
getAvatars(params) {
|
||||||
return window.API.call('avatars', {
|
return request('avatars', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -45,16 +44,16 @@ const avatarReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATAR:LIST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @param {{ id: string, releaseStatus: 'public' | 'private' }} params
|
* @param {{ id: string, releaseStatus?: 'public' | 'private', name?: string, description?: string }} params
|
||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
saveAvatar(params) {
|
saveAvatar(params) {
|
||||||
return window.API.call(`avatars/${params.id}`, {
|
return request(`avatars/${params.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -62,7 +61,6 @@ const avatarReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATAR:SAVE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -72,7 +70,8 @@ const avatarReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
selectAvatar(params) {
|
selectAvatar(params) {
|
||||||
return window.API.call(`avatars/${params.avatarId}/select`, {
|
const userStore = useUserStore();
|
||||||
|
return request(`avatars/${params.avatarId}/select`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -80,7 +79,7 @@ const avatarReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATAR:SELECT', args);
|
userStore.applyCurrentUser(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -90,7 +89,8 @@ const avatarReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
selectFallbackAvatar(params) {
|
selectFallbackAvatar(params) {
|
||||||
return window.API.call(`avatars/${params.avatarId}/selectfallback`, {
|
const userStore = useUserStore();
|
||||||
|
return request(`avatars/${params.avatarId}/selectfallback`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -98,7 +98,7 @@ const avatarReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATAR:SELECT', args);
|
userStore.applyCurrentUser(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -108,14 +108,13 @@ const avatarReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
deleteAvatar(params) {
|
deleteAvatar(params) {
|
||||||
return window.API.call(`avatars/${params.avatarId}`, {
|
return request(`avatars/${params.avatarId}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATAR:DELETE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -125,14 +124,13 @@ const avatarReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
createImposter(params) {
|
createImposter(params) {
|
||||||
return window.API.call(`avatars/${params.avatarId}/impostor/enqueue`, {
|
return request(`avatars/${params.avatarId}/impostor/enqueue`, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATAR:IMPOSTER:CREATE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -142,35 +140,33 @@ const avatarReq = {
|
|||||||
* @returns {Promise<{json: T, params}>}
|
* @returns {Promise<{json: T, params}>}
|
||||||
*/
|
*/
|
||||||
deleteImposter(params) {
|
deleteImposter(params) {
|
||||||
return window.API.call(`avatars/${params.avatarId}/impostor`, {
|
return request(`avatars/${params.avatarId}/impostor`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATAR:IMPOSTER:DELETE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any}>}
|
||||||
*/
|
*/
|
||||||
getAvailableAvatarStyles() {
|
getAvailableAvatarStyles() {
|
||||||
return window.API.call('avatarStyles', {
|
return request('avatarStyles', {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json
|
json
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATAR:STYLES', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{ avatarId: string }} params
|
* @param {{ avatarId: string }} avatarId
|
||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getAvatarGallery(avatarId) {
|
getAvatarGallery(avatarId) {
|
||||||
@@ -180,7 +176,7 @@ const avatarReq = {
|
|||||||
n: 100,
|
n: 100,
|
||||||
offset: 0
|
offset: 0
|
||||||
};
|
};
|
||||||
return window.API.call(`files`, {
|
return request(`files`, {
|
||||||
params,
|
params,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -188,7 +184,6 @@ const avatarReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATAR:GALLERY', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -202,7 +197,7 @@ const avatarReq = {
|
|||||||
tag: 'avatargallery',
|
tag: 'avatargallery',
|
||||||
galleryId: avatarId
|
galleryId: avatarId
|
||||||
};
|
};
|
||||||
return window.API.call('file/image', {
|
return request('file/image', {
|
||||||
uploadImage: true,
|
uploadImage: true,
|
||||||
matchingDimensions: false,
|
matchingDimensions: false,
|
||||||
postData: JSON.stringify(params),
|
postData: JSON.stringify(params),
|
||||||
@@ -212,7 +207,6 @@ const avatarReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATARGALLERYIMAGE:ADD', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -225,7 +219,7 @@ const avatarReq = {
|
|||||||
const params = {
|
const params = {
|
||||||
ids: order
|
ids: order
|
||||||
};
|
};
|
||||||
return window.API.call('files/order', {
|
return request('files/order', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -233,11 +227,9 @@ const avatarReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATARGALLERYIMAGE:ORDER', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// #endregion
|
|
||||||
|
|
||||||
export default avatarReq;
|
export default avatarReq;
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
// #region | API: AvatarModeration
|
import { request } from '../service/request';
|
||||||
|
|
||||||
const avatarModerationReq = {
|
const avatarModerationReq = {
|
||||||
getAvatarModerations() {
|
getAvatarModerations() {
|
||||||
return window.API.call('auth/user/avatarmoderations', {
|
return request('auth/user/avatarmoderations', {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json
|
json
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATAR-MODERATION:LIST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -18,7 +17,7 @@ const avatarModerationReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
sendAvatarModeration(params) {
|
sendAvatarModeration(params) {
|
||||||
return window.API.call('auth/user/avatarmoderations', {
|
return request('auth/user/avatarmoderations', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -26,7 +25,6 @@ const avatarModerationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATAR-MODERATION', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -36,7 +34,7 @@ const avatarModerationReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
deleteAvatarModeration(params) {
|
deleteAvatarModeration(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`auth/user/avatarmoderations?targetAvatarId=${encodeURIComponent(
|
`auth/user/avatarmoderations?targetAvatarId=${encodeURIComponent(
|
||||||
params.targetAvatarId
|
params.targetAvatarId
|
||||||
)}&avatarModerationType=${encodeURIComponent(
|
)}&avatarModerationType=${encodeURIComponent(
|
||||||
@@ -50,11 +48,9 @@ const avatarModerationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATAR-MODERATION:DELETE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// #endregion
|
|
||||||
export default avatarModerationReq;
|
export default avatarModerationReq;
|
||||||
|
|||||||
@@ -1,29 +1,27 @@
|
|||||||
// #region | API: Favorite
|
import { request } from '../service/request';
|
||||||
|
import { useFavoriteStore, useUserStore } from '../stores';
|
||||||
|
|
||||||
|
function getCurrentUserId() {
|
||||||
|
return useUserStore().currentUser.id;
|
||||||
|
}
|
||||||
|
|
||||||
const favoriteReq = {
|
const favoriteReq = {
|
||||||
getFavoriteLimits() {
|
getFavoriteLimits() {
|
||||||
return window.API.call('auth/user/favoritelimits', {
|
return request('auth/user/favoritelimits', {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json
|
json
|
||||||
};
|
};
|
||||||
window.API.$emit('FAVORITE:LIMITS', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{
|
* @type {import('../types/favorite').getFavorites}
|
||||||
* n: number,
|
|
||||||
* offset: number,
|
|
||||||
* type: string,
|
|
||||||
* tag: string
|
|
||||||
* }} params
|
|
||||||
* @return { Promise<{json: any, params}> }
|
|
||||||
*/
|
*/
|
||||||
getFavorites(params) {
|
getFavorites(params) {
|
||||||
return window.API.call('favorites', {
|
return request('favorites', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -31,7 +29,6 @@ const favoriteReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FAVORITE:LIST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -45,7 +42,7 @@ const favoriteReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
addFavorite(params) {
|
addFavorite(params) {
|
||||||
return window.API.call('favorites', {
|
return request('favorites', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -53,7 +50,7 @@ const favoriteReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FAVORITE:ADD', args);
|
useFavoriteStore().handleFavoriteAdd(args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -63,14 +60,14 @@ const favoriteReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
deleteFavorite(params) {
|
deleteFavorite(params) {
|
||||||
return window.API.call(`favorites/${params.objectId}`, {
|
return request(`favorites/${params.objectId}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FAVORITE:DELETE', args);
|
useFavoriteStore().handleFavoriteDelete(args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -80,7 +77,7 @@ const favoriteReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getFavoriteGroups(params) {
|
getFavoriteGroups(params) {
|
||||||
return window.API.call('favorite/groups', {
|
return request('favorite/groups', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -88,7 +85,6 @@ const favoriteReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FAVORITE:GROUP:LIST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -99,8 +95,8 @@ const favoriteReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
saveFavoriteGroup(params) {
|
saveFavoriteGroup(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`favorite/group/${params.type}/${params.group}/${window.API.currentUser.id}`,
|
`favorite/group/${params.type}/${params.group}/${getCurrentUserId()}`,
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
@@ -110,7 +106,6 @@ const favoriteReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FAVORITE:GROUP:SAVE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -123,8 +118,8 @@ const favoriteReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
clearFavoriteGroup(params) {
|
clearFavoriteGroup(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`favorite/group/${params.type}/${params.group}/${window.API.currentUser.id}`,
|
`favorite/group/${params.type}/${params.group}/${getCurrentUserId()}`,
|
||||||
{
|
{
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
params
|
params
|
||||||
@@ -134,20 +129,16 @@ const favoriteReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FAVORITE:GROUP:CLEAR', args);
|
useFavoriteStore().handleFavoriteGroupClear(args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{
|
* @type {import('../types/favorite').getFavoriteWorlds}
|
||||||
* n: number,
|
|
||||||
* offset: number
|
|
||||||
* }} params
|
|
||||||
* @return { Promise<{json: any, params}> }
|
|
||||||
*/
|
*/
|
||||||
getFavoriteWorlds(params) {
|
getFavoriteWorlds(params) {
|
||||||
return window.API.call('worlds/favorites', {
|
return request('worlds/favorites', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -155,20 +146,15 @@ const favoriteReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FAVORITE:WORLD:LIST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{
|
* @type {import('../types/favorite').getFavoriteAvatars}
|
||||||
* n: number,
|
|
||||||
* offset: number
|
|
||||||
* }} params
|
|
||||||
* @return { Promise<{json: any, params}> }
|
|
||||||
*/
|
*/
|
||||||
getFavoriteAvatars(params) {
|
getFavoriteAvatars(params) {
|
||||||
return window.API.call('avatars/favorites', {
|
return request('avatars/favorites', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -176,12 +162,9 @@ const favoriteReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FAVORITE:AVATAR:LIST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
export default favoriteReq;
|
export default favoriteReq;
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
// #region | API: Friend
|
import { request } from '../service/request';
|
||||||
|
import { useUserStore } from '../stores/user';
|
||||||
|
|
||||||
const friendReq = {
|
const friendReq = {
|
||||||
/**
|
/**
|
||||||
* Fetch friends of current user.
|
* Fetch friends of current user.
|
||||||
* @param {{ n: number, offset: number, offline: boolean }} params
|
* @type {import('../types/friend').getFriends}
|
||||||
* @returns {Promise<{json: any, params}>}
|
|
||||||
*/
|
*/
|
||||||
getFriends(params) {
|
getFriends(params) {
|
||||||
return window.API.call('auth/user/friends', {
|
const userStore = useUserStore();
|
||||||
|
return request('auth/user/friends', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -15,7 +16,13 @@ const friendReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FRIEND:LIST', args);
|
for (const user of args.json) {
|
||||||
|
if (!user.displayName) {
|
||||||
|
console.error('/friends gave us garbage', user);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
userStore.applyUser(user);
|
||||||
|
}
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -25,14 +32,13 @@ const friendReq = {
|
|||||||
* @returns {Promise<{json: T, params}>}
|
* @returns {Promise<{json: T, params}>}
|
||||||
*/
|
*/
|
||||||
sendFriendRequest(params) {
|
sendFriendRequest(params) {
|
||||||
return window.API.call(`user/${params.userId}/friendRequest`, {
|
return request(`user/${params.userId}/friendRequest`, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FRIEND:REQUEST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -42,14 +48,13 @@ const friendReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
cancelFriendRequest(params) {
|
cancelFriendRequest(params) {
|
||||||
return window.API.call(`user/${params.userId}/friendRequest`, {
|
return request(`user/${params.userId}/friendRequest`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('FRIEND:REQUEST:CANCEL', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -59,14 +64,13 @@ const friendReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
deleteFriend(params) {
|
deleteFriend(params) {
|
||||||
return window.API.call(`auth/user/friends/${params.userId}`, {
|
return request(`auth/user/friends/${params.userId}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FRIEND:DELETE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -76,7 +80,7 @@ const friendReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getFriendStatus(params) {
|
getFriendStatus(params) {
|
||||||
return window.API.call(`user/${params.userId}/friendStatus`, {
|
return request(`user/${params.userId}/friendStatus`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
console.log('getFriendStatus', json);
|
console.log('getFriendStatus', json);
|
||||||
@@ -84,15 +88,12 @@ const friendReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FRIEND:STATUS', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// ------------------- need to test -------------------
|
|
||||||
|
|
||||||
deleteHiddenFriendRequest(params, userId) {
|
deleteHiddenFriendRequest(params, userId) {
|
||||||
return window.API.call(`user/${userId}/friendRequest`, {
|
return request(`user/${userId}/friendRequest`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -101,11 +102,9 @@ const friendReq = {
|
|||||||
params,
|
params,
|
||||||
userId
|
userId
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTIFICATION:HIDE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// #endregion
|
|
||||||
|
|
||||||
export default friendReq;
|
export default friendReq;
|
||||||
|
|||||||
262
src/api/group.js
262
src/api/group.js
@@ -1,3 +1,9 @@
|
|||||||
|
import { request } from '../service/request';
|
||||||
|
import { useUserStore, useGroupStore } from '../stores';
|
||||||
|
|
||||||
|
function getCurrentUserId() {
|
||||||
|
return useUserStore().currentUser.id;
|
||||||
|
}
|
||||||
const groupReq = {
|
const groupReq = {
|
||||||
/**
|
/**
|
||||||
* @param {string} groupId
|
* @param {string} groupId
|
||||||
@@ -5,7 +11,7 @@ const groupReq = {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
setGroupRepresentation(groupId, params) {
|
setGroupRepresentation(groupId, params) {
|
||||||
return window.API.call(`groups/${groupId}/representation`, {
|
return request(`groups/${groupId}/representation`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -23,7 +29,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
cancelGroupRequest(params) {
|
cancelGroupRequest(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/requests`, {
|
return request(`groups/${params.groupId}/requests`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
@@ -39,12 +45,9 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
deleteGroupPost(params) {
|
deleteGroupPost(params) {
|
||||||
return window.API.call(
|
return request(`groups/${params.groupId}/posts/${params.postId}`, {
|
||||||
`groups/${params.groupId}/posts/${params.postId}`,
|
method: 'DELETE'
|
||||||
{
|
}).then((json) => {
|
||||||
method: 'DELETE'
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
@@ -53,10 +56,10 @@ const groupReq = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @param {{ groupId: string }} params
|
* @type {import('../types/group').getGroup}
|
||||||
*/
|
*/
|
||||||
getGroup(params) {
|
getGroup(params) {
|
||||||
return window.API.call(`groups/${params.groupId}`, {
|
return request(`groups/${params.groupId}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
includeRoles: params.includeRoles || false
|
includeRoles: params.includeRoles || false
|
||||||
@@ -66,23 +69,48 @@ const groupReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {{ groupId: string }} params
|
||||||
|
* @return { Promise<{json: any, params}> }
|
||||||
|
*/
|
||||||
|
getCachedGroup(params) {
|
||||||
|
const groupStore = useGroupStore();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const ref = groupStore.cachedGroups.get(params.groupId);
|
||||||
|
if (typeof ref === 'undefined') {
|
||||||
|
groupReq
|
||||||
|
.getGroup(params)
|
||||||
|
.catch(reject)
|
||||||
|
.then((args) => {
|
||||||
|
args.ref = groupStore.applyGroup(args.json);
|
||||||
|
resolve(args);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve({
|
||||||
|
cache: true,
|
||||||
|
json: ref,
|
||||||
|
params,
|
||||||
|
ref
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* @param {{ userId: string }} params
|
* @param {{ userId: string }} params
|
||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getRepresentedGroup(params) {
|
getRepresentedGroup(params) {
|
||||||
return window.API.call(`users/${params.userId}/groups/represented`, {
|
return request(`users/${params.userId}/groups/represented`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP:REPRESENTED', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -91,14 +119,13 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroups(params) {
|
getGroups(params) {
|
||||||
return window.API.call(`users/${params.userId}/groups`, {
|
return request(`users/${params.userId}/groups`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP:LIST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -107,14 +134,13 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
joinGroup(params) {
|
joinGroup(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/join`, {
|
return request(`groups/${params.groupId}/join`, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP:JOIN', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -123,7 +149,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
leaveGroup(params) {
|
leaveGroup(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/leave`, {
|
return request(`groups/${params.groupId}/leave`, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
@@ -138,7 +164,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
groupStrictsearch(params) {
|
groupStrictsearch(params) {
|
||||||
return window.API.call(`groups/strictsearch`, {
|
return request(`groups/strictsearch`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -159,7 +185,7 @@ const groupReq = {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
setGroupMemberProps(userId, groupId, params) {
|
setGroupMemberProps(userId, groupId, params) {
|
||||||
return window.API.call(`groups/${groupId}/members/${userId}`, {
|
return request(`groups/${groupId}/members/${userId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -169,7 +195,6 @@ const groupReq = {
|
|||||||
groupId,
|
groupId,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP:MEMBER:PROPS', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -182,7 +207,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
addGroupMemberRole(params) {
|
addGroupMemberRole(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`groups/${params.groupId}/members/${params.userId}/roles/${params.roleId}`,
|
`groups/${params.groupId}/members/${params.userId}/roles/${params.roleId}`,
|
||||||
{
|
{
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
@@ -192,7 +217,6 @@ const groupReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('GROUP:MEMBER:ROLE:CHANGE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -205,7 +229,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
removeGroupMemberRole(params) {
|
removeGroupMemberRole(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`groups/${params.groupId}/members/${params.userId}/roles/${params.roleId}`,
|
`groups/${params.groupId}/members/${params.userId}/roles/${params.roleId}`,
|
||||||
{
|
{
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
@@ -215,19 +239,17 @@ const groupReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('GROUP:MEMBER:ROLE:CHANGE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getGroupPermissions(params) {
|
getGroupPermissions(params) {
|
||||||
return window.API.call(`users/${params.userId}/groups/permissions`, {
|
return request(`users/${params.userId}/groups/permissions`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP:PERMISSIONS', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -240,7 +262,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroupPosts(params) {
|
getGroupPosts(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/posts`, {
|
return request(`groups/${params.groupId}/posts`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -248,28 +270,23 @@ const groupReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP:POSTS', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
editGroupPost(params) {
|
editGroupPost(params) {
|
||||||
return window.API.call(
|
return request(`groups/${params.groupId}/posts/${params.postId}`, {
|
||||||
`groups/${params.groupId}/posts/${params.postId}`,
|
method: 'PUT',
|
||||||
{
|
params
|
||||||
method: 'PUT',
|
}).then((json) => {
|
||||||
params
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP:POST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
createGroupPost(params) {
|
createGroupPost(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/posts`, {
|
return request(`groups/${params.groupId}/posts`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -277,7 +294,6 @@ const groupReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP:POST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -289,17 +305,13 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroupMember(params) {
|
getGroupMember(params) {
|
||||||
return window.API.call(
|
return request(`groups/${params.groupId}/members/${params.userId}`, {
|
||||||
`groups/${params.groupId}/members/${params.userId}`,
|
method: 'GET'
|
||||||
{
|
}).then((json) => {
|
||||||
method: 'GET'
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('GROUP:MEMBER', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -312,7 +324,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroupMembers(params) {
|
getGroupMembers(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/members`, {
|
return request(`groups/${params.groupId}/members`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -320,7 +332,6 @@ const groupReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP:MEMBERS', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -334,7 +345,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroupMembersSearch(params) {
|
getGroupMembersSearch(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/members/search`, {
|
return request(`groups/${params.groupId}/members/search`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -352,7 +363,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
blockGroup(params) {
|
blockGroup(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/block`, {
|
return request(`groups/${params.groupId}/block`, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
@@ -370,12 +381,9 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
unblockGroup(params) {
|
unblockGroup(params) {
|
||||||
return window.API.call(
|
return request(`groups/${params.groupId}/members/${params.userId}`, {
|
||||||
`groups/${params.groupId}/members/${params.userId}`,
|
method: 'DELETE'
|
||||||
{
|
}).then((json) => {
|
||||||
method: 'DELETE'
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
@@ -391,7 +399,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
sendGroupInvite(params) {
|
sendGroupInvite(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/invites`, {
|
return request(`groups/${params.groupId}/invites`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params: {
|
params: {
|
||||||
userId: params.userId
|
userId: params.userId
|
||||||
@@ -401,7 +409,6 @@ const groupReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP:INVITE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -413,18 +420,13 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
kickGroupMember(params) {
|
kickGroupMember(params) {
|
||||||
return window.API.call(
|
return request(`groups/${params.groupId}/members/${params.userId}`, {
|
||||||
`groups/${params.groupId}/members/${params.userId}`,
|
method: 'DELETE'
|
||||||
{
|
}).then((json) => {
|
||||||
method: 'DELETE'
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// useless code
|
|
||||||
// window.API.$emit('GROUP:MEMBER:KICK', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -433,7 +435,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
banGroupMember(params) {
|
banGroupMember(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/bans`, {
|
return request(`groups/${params.groupId}/bans`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params: {
|
params: {
|
||||||
userId: params.userId
|
userId: params.userId
|
||||||
@@ -443,24 +445,17 @@ const groupReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// useless code
|
|
||||||
// window.API.$emit('GROUP:MEMBER:BAN', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
unbanGroupMember(params) {
|
unbanGroupMember(params) {
|
||||||
return window.API.call(
|
return request(`groups/${params.groupId}/bans/${params.userId}`, {
|
||||||
`groups/${params.groupId}/bans/${params.userId}`,
|
method: 'DELETE'
|
||||||
{
|
}).then((json) => {
|
||||||
method: 'DELETE'
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// useless code
|
|
||||||
// window.API.$emit('GROUP:MEMBER:UNBAN', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -469,97 +464,73 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
deleteSentGroupInvite(params) {
|
deleteSentGroupInvite(params) {
|
||||||
return window.API.call(
|
return request(`groups/${params.groupId}/invites/${params.userId}`, {
|
||||||
`groups/${params.groupId}/invites/${params.userId}`,
|
method: 'DELETE'
|
||||||
{
|
}).then((json) => {
|
||||||
method: 'DELETE'
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// useless code
|
|
||||||
// window.API.$emit('GROUP:INVITE:DELETE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteBlockedGroupRequest(params) {
|
deleteBlockedGroupRequest(params) {
|
||||||
return window.API.call(
|
return request(`groups/${params.groupId}/members/${params.userId}`, {
|
||||||
`groups/${params.groupId}/members/${params.userId}`,
|
method: 'DELETE'
|
||||||
{
|
}).then((json) => {
|
||||||
method: 'DELETE'
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// useless code
|
|
||||||
// window.API.$emit('GROUP:BLOCKED:DELETE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
acceptGroupInviteRequest(params) {
|
acceptGroupInviteRequest(params) {
|
||||||
return window.API.call(
|
return request(`groups/${params.groupId}/requests/${params.userId}`, {
|
||||||
`groups/${params.groupId}/requests/${params.userId}`,
|
method: 'PUT',
|
||||||
{
|
params: {
|
||||||
method: 'PUT',
|
action: 'accept'
|
||||||
params: {
|
|
||||||
action: 'accept'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// useless code
|
|
||||||
// window.API.$emit('GROUP:INVITE:ACCEPT', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
rejectGroupInviteRequest(params) {
|
rejectGroupInviteRequest(params) {
|
||||||
return window.API.call(
|
return request(`groups/${params.groupId}/requests/${params.userId}`, {
|
||||||
`groups/${params.groupId}/requests/${params.userId}`,
|
method: 'PUT',
|
||||||
{
|
params: {
|
||||||
method: 'PUT',
|
action: 'reject'
|
||||||
params: {
|
|
||||||
action: 'reject'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// useless code
|
|
||||||
// window.API.$emit('GROUP:INVITE:REJECT', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
blockGroupInviteRequest(params) {
|
blockGroupInviteRequest(params) {
|
||||||
return window.API.call(
|
return request(`groups/${params.groupId}/requests/${params.userId}`, {
|
||||||
`groups/${params.groupId}/requests/${params.userId}`,
|
method: 'PUT',
|
||||||
{
|
params: {
|
||||||
method: 'PUT',
|
action: 'reject',
|
||||||
params: {
|
block: true
|
||||||
action: 'reject',
|
|
||||||
block: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// useless code
|
|
||||||
// window.API.$emit('GROUP:INVITE:BLOCK', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getGroupBans(params) {
|
getGroupBans(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/bans`, {
|
return request(`groups/${params.groupId}/bans`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -575,7 +546,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroupAuditLogTypes(params) {
|
getGroupAuditLogTypes(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/auditLogTypes`, {
|
return request(`groups/${params.groupId}/auditLogTypes`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
@@ -590,7 +561,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroupLogs(params) {
|
getGroupLogs(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/auditLogs`, {
|
return request(`groups/${params.groupId}/auditLogs`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -606,7 +577,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroupInvites(params) {
|
getGroupInvites(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/invites`, {
|
return request(`groups/${params.groupId}/invites`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -622,7 +593,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroupJoinRequests(params) {
|
getGroupJoinRequests(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/requests`, {
|
return request(`groups/${params.groupId}/requests`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -630,7 +601,6 @@ const groupReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('GROUP:JOINREQUESTS', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -639,8 +609,8 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroupInstances(params) {
|
getGroupInstances(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`users/${window.API.currentUser.id}/instances/groups/${params.groupId}`,
|
`users/${getCurrentUserId()}/instances/groups/${params.groupId}`,
|
||||||
{
|
{
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}
|
}
|
||||||
@@ -657,7 +627,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroupRoles(params) {
|
getGroupRoles(params) {
|
||||||
return window.API.call(`groups/${params.groupId}/roles`, {
|
return request(`groups/${params.groupId}/roles`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -665,22 +635,16 @@ const groupReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// useless code
|
|
||||||
// this.$emit('GROUP:ROLES', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getUsersGroupInstances() {
|
getUsersGroupInstances() {
|
||||||
return window.API.call(
|
return request(`users/${getCurrentUserId()}/instances/groups`, {
|
||||||
`users/${window.API.currentUser.id}/instances/groups`,
|
method: 'GET'
|
||||||
{
|
}).then((json) => {
|
||||||
method: 'GET'
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json
|
json
|
||||||
};
|
};
|
||||||
window.API.$emit('GROUP:USER:INSTANCES', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -696,7 +660,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
groupSearch(params) {
|
groupSearch(params) {
|
||||||
return window.API.call(`groups`, {
|
return request(`groups`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -717,7 +681,7 @@ const groupReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getGroupGallery(params) {
|
getGroupGallery(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`groups/${params.groupId}/galleries/${params.galleryId}`,
|
`groups/${params.groupId}/galleries/${params.galleryId}`,
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -735,10 +699,9 @@ const groupReq = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// no place to use this
|
|
||||||
// getRequestedGroups() {
|
// getRequestedGroups() {
|
||||||
// return window.API.call(
|
// return request(
|
||||||
// `users/${window.API.currentUser.id}/groups/requested`,
|
// `users/${API.currentUser.id}/groups/requested`,
|
||||||
// {
|
// {
|
||||||
// method: 'GET'
|
// method: 'GET'
|
||||||
// }
|
// }
|
||||||
@@ -746,18 +709,17 @@ const groupReq = {
|
|||||||
// const args = {
|
// const args = {
|
||||||
// json
|
// json
|
||||||
// };
|
// };
|
||||||
// window.API.$emit('GROUP:REQUESTED', args);
|
// API.$emit('GROUP:REQUESTED', args);
|
||||||
// return args;
|
// return args;
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// ----------------- left over code -----------------
|
|
||||||
// /**
|
// /**
|
||||||
// * @param {{ groupId: string }} params
|
// * @param {{ groupId: string }} params
|
||||||
// * @return { Promise<{json: any, params}> }
|
// * @return { Promise<{json: any, params}> }
|
||||||
// */
|
// */
|
||||||
// API.getGroupAnnouncement = function (params) {
|
// API.getGroupAnnouncement = function (params) {
|
||||||
// return this.call(`groups/${params.groupId}/announcement`, {
|
// return request(`groups/${params.groupId}/announcement`, {
|
||||||
// method: 'GET'
|
// method: 'GET'
|
||||||
// }).then((json) => {
|
// }).then((json) => {
|
||||||
// var args = {
|
// var args = {
|
||||||
|
|||||||
@@ -1,25 +1,26 @@
|
|||||||
|
import { request } from '../service/request';
|
||||||
|
import { useAvatarStore, useWorldStore } from '../stores';
|
||||||
|
|
||||||
const imageReq = {
|
const imageReq = {
|
||||||
// use in uploadAvatarImage
|
|
||||||
// need to test
|
|
||||||
async uploadAvatarFailCleanup(id) {
|
async uploadAvatarFailCleanup(id) {
|
||||||
const json = await window.API.call(`file/${id}`, {
|
const avatarStore = useAvatarStore();
|
||||||
|
const json = await request(`file/${id}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
});
|
});
|
||||||
const fileId = json.id;
|
const fileId = json.id;
|
||||||
const fileVersion = json.versions[json.versions.length - 1].version;
|
const fileVersion = json.versions[json.versions.length - 1].version;
|
||||||
window.API.call(`file/${fileId}/${fileVersion}/signature/finish`, {
|
request(`file/${fileId}/${fileVersion}/signature/finish`, {
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
});
|
});
|
||||||
window.API.call(`file/${fileId}/${fileVersion}/file/finish`, {
|
request(`file/${fileId}/${fileVersion}/file/finish`, {
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
});
|
});
|
||||||
window.$app.avatarDialog.loading = false;
|
avatarStore.avatarDialog.loading = false;
|
||||||
// window.$app.changeAvatarImageDialogLoading = false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadAvatarImage(params, fileId) {
|
async uploadAvatarImage(params, fileId) {
|
||||||
try {
|
try {
|
||||||
return await window.API.call(`file/${fileId}`, {
|
return await request(`file/${fileId}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -28,7 +29,6 @@ const imageReq = {
|
|||||||
params,
|
params,
|
||||||
fileId
|
fileId
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATARIMAGE:INIT', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -36,12 +36,11 @@ const imageReq = {
|
|||||||
imageReq.uploadAvatarFailCleanup(fileId);
|
imageReq.uploadAvatarFailCleanup(fileId);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
// return void 0;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadAvatarImageFileStart(params) {
|
async uploadAvatarImageFileStart(params) {
|
||||||
try {
|
try {
|
||||||
return await window.API.call(
|
return await request(
|
||||||
`file/${params.fileId}/${params.fileVersion}/file/start`,
|
`file/${params.fileId}/${params.fileVersion}/file/start`,
|
||||||
{
|
{
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
@@ -51,18 +50,16 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATARIMAGE:FILESTART', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
imageReq.uploadAvatarFailCleanup(params.fileId);
|
imageReq.uploadAvatarFailCleanup(params.fileId);
|
||||||
}
|
}
|
||||||
return void 0;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadAvatarImageFileFinish(params) {
|
uploadAvatarImageFileFinish(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`file/${params.fileId}/${params.fileVersion}/file/finish`,
|
`file/${params.fileId}/${params.fileVersion}/file/finish`,
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
@@ -76,14 +73,13 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATARIMAGE:FILEFINISH', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadAvatarImageSigStart(params) {
|
async uploadAvatarImageSigStart(params) {
|
||||||
try {
|
try {
|
||||||
return await window.API.call(
|
return await request(
|
||||||
`file/${params.fileId}/${params.fileVersion}/signature/start`,
|
`file/${params.fileId}/${params.fileVersion}/signature/start`,
|
||||||
{
|
{
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
@@ -93,18 +89,16 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATARIMAGE:SIGSTART', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
imageReq.uploadAvatarFailCleanup(params.fileId);
|
imageReq.uploadAvatarFailCleanup(params.fileId);
|
||||||
}
|
}
|
||||||
return void 0;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadAvatarImageSigFinish(params) {
|
uploadAvatarImageSigFinish(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`file/${params.fileId}/${params.fileVersion}/signature/finish`,
|
`file/${params.fileId}/${params.fileVersion}/signature/finish`,
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
@@ -118,13 +112,12 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATARIMAGE:SIGFINISH', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setAvatarImage(params) {
|
setAvatarImage(params) {
|
||||||
return window.API.call(`avatars/${params.id}`, {
|
return request(`avatars/${params.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -132,33 +125,29 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATARIMAGE:SET', args);
|
|
||||||
window.API.$emit('AVATAR', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// use in uploadWorldImage
|
|
||||||
// need to test
|
|
||||||
async uploadWorldFailCleanup(id) {
|
async uploadWorldFailCleanup(id) {
|
||||||
const json = await window.API.call(`file/${id}`, {
|
const worldStore = useWorldStore();
|
||||||
|
const json = await request(`file/${id}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
});
|
});
|
||||||
const fileId = json.id;
|
const fileId = json.id;
|
||||||
const fileVersion = json.versions[json.versions.length - 1].version;
|
const fileVersion = json.versions[json.versions.length - 1].version;
|
||||||
window.API.call(`file/${fileId}/${fileVersion}/signature/finish`, {
|
request(`file/${fileId}/${fileVersion}/signature/finish`, {
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
});
|
});
|
||||||
window.API.call(`file/${fileId}/${fileVersion}/file/finish`, {
|
request(`file/${fileId}/${fileVersion}/file/finish`, {
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
});
|
});
|
||||||
window.$app.worldDialog.loading = false;
|
worldStore.worldDialog.loading = false;
|
||||||
// window.$app.changeWorldImageDialogLoading = false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadWorldImage(params, fileId) {
|
async uploadWorldImage(params, fileId) {
|
||||||
try {
|
try {
|
||||||
return await window.API.call(`file/${fileId}`, {
|
return await request(`file/${fileId}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -167,7 +156,6 @@ const imageReq = {
|
|||||||
params,
|
params,
|
||||||
fileId
|
fileId
|
||||||
};
|
};
|
||||||
// window.API.$emit('WORLDIMAGE:INIT', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -179,7 +167,7 @@ const imageReq = {
|
|||||||
|
|
||||||
async uploadWorldImageFileStart(params) {
|
async uploadWorldImageFileStart(params) {
|
||||||
try {
|
try {
|
||||||
return await window.API.call(
|
return await request(
|
||||||
`file/${params.fileId}/${params.fileVersion}/file/start`,
|
`file/${params.fileId}/${params.fileVersion}/file/start`,
|
||||||
{
|
{
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
@@ -189,7 +177,6 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('WORLDIMAGE:FILESTART', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -200,7 +187,7 @@ const imageReq = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
uploadWorldImageFileFinish(params) {
|
uploadWorldImageFileFinish(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`file/${params.fileId}/${params.fileVersion}/file/finish`,
|
`file/${params.fileId}/${params.fileVersion}/file/finish`,
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
@@ -214,14 +201,13 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('WORLDIMAGE:FILEFINISH', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadWorldImageSigStart(params) {
|
async uploadWorldImageSigStart(params) {
|
||||||
try {
|
try {
|
||||||
return await window.API.call(
|
return await request(
|
||||||
`file/${params.fileId}/${params.fileVersion}/signature/start`,
|
`file/${params.fileId}/${params.fileVersion}/signature/start`,
|
||||||
{
|
{
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
@@ -231,7 +217,6 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('WORLDIMAGE:SIGSTART', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -242,7 +227,7 @@ const imageReq = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
uploadWorldImageSigFinish(params) {
|
uploadWorldImageSigFinish(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`file/${params.fileId}/${params.fileVersion}/signature/finish`,
|
`file/${params.fileId}/${params.fileVersion}/signature/finish`,
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
@@ -256,13 +241,13 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('WORLDIMAGE:SIGFINISH', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setWorldImage(params) {
|
setWorldImage(params) {
|
||||||
return window.API.call(`worlds/${params.id}`, {
|
const worldStore = useWorldStore();
|
||||||
|
return request(`worlds/${params.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -270,27 +255,25 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('WORLDIMAGE:SET', args);
|
args.ref = worldStore.applyWorld(json);
|
||||||
window.API.$emit('WORLD', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getAvatarImages(params) {
|
getAvatarImages(params) {
|
||||||
return window.API.call(`file/${params.fileId}`, {
|
return request(`file/${params.fileId}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('AVATARIMAGE:GET', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getWorldImages(params) {
|
getWorldImages(params) {
|
||||||
return window.API.call(`file/${params.fileId}`, {
|
return request(`file/${params.fileId}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -298,7 +281,6 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('WORLDIMAGE:GET', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* API requests
|
* API requests
|
||||||
* Export all API requests from here
|
* Export all API requests from here
|
||||||
*
|
|
||||||
* "window.API" is used as app.js is a large IIFE, preventing direct API export. No current issues
|
|
||||||
* Refactoring may be required
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import userRequest from './user';
|
import userRequest from './user';
|
||||||
@@ -21,6 +18,7 @@ import inviteMessagesRequest from './inviteMessages';
|
|||||||
import imageRequest from './image';
|
import imageRequest from './image';
|
||||||
import miscRequest from './misc';
|
import miscRequest from './misc';
|
||||||
import groupRequest from './group';
|
import groupRequest from './group';
|
||||||
|
import authRequest from './auth';
|
||||||
import inventoryRequest from './inventory';
|
import inventoryRequest from './inventory';
|
||||||
import propRequest from './prop';
|
import propRequest from './prop';
|
||||||
|
|
||||||
@@ -39,6 +37,7 @@ window.request = {
|
|||||||
inviteMessagesRequest,
|
inviteMessagesRequest,
|
||||||
imageRequest,
|
imageRequest,
|
||||||
miscRequest,
|
miscRequest,
|
||||||
|
authRequest,
|
||||||
groupRequest,
|
groupRequest,
|
||||||
inventoryRequest,
|
inventoryRequest,
|
||||||
propRequest
|
propRequest
|
||||||
@@ -59,6 +58,7 @@ export {
|
|||||||
inviteMessagesRequest,
|
inviteMessagesRequest,
|
||||||
imageRequest,
|
imageRequest,
|
||||||
miscRequest,
|
miscRequest,
|
||||||
|
authRequest,
|
||||||
groupRequest,
|
groupRequest,
|
||||||
inventoryRequest,
|
inventoryRequest,
|
||||||
propRequest
|
propRequest
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
// #region | API: Instance
|
import { $app } from '../app';
|
||||||
|
import { t } from '../plugin';
|
||||||
|
import { request } from '../service/request';
|
||||||
|
import { useInstanceStore } from '../stores';
|
||||||
|
|
||||||
const instanceReq = {
|
const instanceReq = {
|
||||||
/**
|
/**
|
||||||
* @param {{worldId: string, instanceId: string}} params
|
* @type {import('../types/instance').getInstance}
|
||||||
* @returns {Promise<{json: any, params}>}
|
|
||||||
*/
|
*/
|
||||||
getInstance(params) {
|
getInstance(params) {
|
||||||
return window.API.call(
|
const instanceStore = useInstanceStore();
|
||||||
`instances/${params.worldId}:${params.instanceId}`,
|
return request(`instances/${params.worldId}:${params.instanceId}`, {
|
||||||
{
|
method: 'GET'
|
||||||
method: 'GET'
|
}).then((json) => {
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('INSTANCE', args);
|
args.ref = instanceStore.applyInstance(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -37,7 +37,8 @@ const instanceReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
createInstance(params) {
|
createInstance(params) {
|
||||||
return window.API.call('instances', {
|
const instanceStore = useInstanceStore();
|
||||||
|
return request('instances', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -45,7 +46,7 @@ const instanceReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('INSTANCE', args);
|
args.ref = instanceStore.applyInstance(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -59,7 +60,7 @@ const instanceReq = {
|
|||||||
if (instance.shortName) {
|
if (instance.shortName) {
|
||||||
params.shortName = instance.shortName;
|
params.shortName = instance.shortName;
|
||||||
}
|
}
|
||||||
return window.API.call(
|
return request(
|
||||||
`instances/${instance.worldId}:${instance.instanceId}/shortName`,
|
`instances/${instance.worldId}:${instance.instanceId}/shortName`,
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -80,14 +81,15 @@ const instanceReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getInstanceFromShortName(params) {
|
getInstanceFromShortName(params) {
|
||||||
return window.API.call(`instances/s/${params.shortName}`, {
|
const instanceStore = useInstanceStore();
|
||||||
|
return request(`instances/s/${params.shortName}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('INSTANCE', args);
|
args.ref = instanceStore.applyInstance(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -105,7 +107,7 @@ const instanceReq = {
|
|||||||
if (instance.shortName) {
|
if (instance.shortName) {
|
||||||
params.shortName = instance.shortName;
|
params.shortName = instance.shortName;
|
||||||
}
|
}
|
||||||
return window.API.call(
|
return request(
|
||||||
`invite/myself/to/${instance.worldId}:${instance.instanceId}`,
|
`invite/myself/to/${instance.worldId}:${instance.instanceId}`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -121,20 +123,19 @@ const instanceReq = {
|
|||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err?.error?.message) {
|
if (err?.error?.message) {
|
||||||
window.$app.$message({
|
$app.$message({
|
||||||
message: err.error.message,
|
message: err.error.message,
|
||||||
type: 'error'
|
type: 'error'
|
||||||
});
|
});
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
window.$app.$message({
|
$app.$message({
|
||||||
message: window.$t('message.instance.not_allowed'),
|
message: t('message.instance.not_allowed'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
});
|
});
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// #endregion
|
|
||||||
|
|
||||||
export default instanceReq;
|
export default instanceReq;
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import { request } from '../service/request';
|
||||||
|
|
||||||
const inventoryReq = {
|
const inventoryReq = {
|
||||||
/**
|
/**
|
||||||
* @param {{ inventoryId: string, userId: string }} params
|
* @param {{ inventoryId: string, userId: string }} params
|
||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getUserInventoryItem(params) {
|
getUserInventoryItem(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`user/${params.userId}/inventory/${params.inventoryId}`,
|
`user/${params.userId}/inventory/${params.inventoryId}`,
|
||||||
{
|
{
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
@@ -23,7 +25,7 @@ const inventoryReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getInventoryItem(params) {
|
getInventoryItem(params) {
|
||||||
return window.API.call(`inventory/${params.inventoryId}`, {
|
return request(`inventory/${params.inventoryId}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -40,7 +42,7 @@ const inventoryReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getInventoryItems(params) {
|
getInventoryItems(params) {
|
||||||
return window.API.call('inventory', {
|
return request('inventory', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -57,7 +59,7 @@ const inventoryReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
consumeInventoryBundle(params) {
|
consumeInventoryBundle(params) {
|
||||||
return window.API.call(`inventory/${params.inventoryId}/consume`, {
|
return request(`inventory/${params.inventoryId}/consume`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -74,13 +76,10 @@ const inventoryReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getInventoryTemplate(params) {
|
getInventoryTemplate(params) {
|
||||||
return window.API.call(
|
return request(`inventory/template/${params.inventoryTemplateId}`, {
|
||||||
`inventory/template/${params.inventoryTemplateId}`,
|
method: 'GET',
|
||||||
{
|
params
|
||||||
method: 'GET',
|
}).then((json) => {
|
||||||
params
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
|
|||||||
@@ -1,30 +1,28 @@
|
|||||||
// #region | App: Invite Messages
|
import { request } from '../service/request';
|
||||||
|
import { useUserStore } from '../stores';
|
||||||
|
|
||||||
|
function getCurrentUserId() {
|
||||||
|
return useUserStore().currentUser.id;
|
||||||
|
}
|
||||||
|
|
||||||
const inviteMessagesReq = {
|
const inviteMessagesReq = {
|
||||||
refreshInviteMessageTableData(messageType) {
|
refreshInviteMessageTableData(messageType) {
|
||||||
return window.API.call(
|
return request(`message/${getCurrentUserId()}/${messageType}`, {
|
||||||
`message/${window.API.currentUser.id}/${messageType}`,
|
method: 'GET'
|
||||||
{
|
}).then((json) => {
|
||||||
method: 'GET'
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
messageType
|
messageType
|
||||||
};
|
};
|
||||||
window.API.$emit(`INVITE:${messageType.toUpperCase()}`, args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
editInviteMessage(params, messageType, slot) {
|
editInviteMessage(params, messageType, slot) {
|
||||||
return window.API.call(
|
return request(`message/${getCurrentUserId()}/${messageType}/${slot}`, {
|
||||||
`message/${window.API.currentUser.id}/${messageType}/${slot}`,
|
method: 'PUT',
|
||||||
{
|
params
|
||||||
method: 'PUT',
|
}).then((json) => {
|
||||||
params
|
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params,
|
params,
|
||||||
@@ -36,6 +34,4 @@ const inviteMessagesReq = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
export default inviteMessagesReq;
|
export default inviteMessagesReq;
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
|
import { request } from '../service/request';
|
||||||
|
import { useUserStore } from '../stores';
|
||||||
|
|
||||||
|
function getCurrentUserId() {
|
||||||
|
return useUserStore().currentUser.id;
|
||||||
|
}
|
||||||
|
|
||||||
const miscReq = {
|
const miscReq = {
|
||||||
getBundles(fileId) {
|
getBundles(fileId) {
|
||||||
return window.API.call(`file/${fileId}`, {
|
return request(`file/${fileId}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
@@ -11,7 +18,7 @@ const miscReq = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
saveNote(params) {
|
saveNote(params) {
|
||||||
return window.API.call('userNotes', {
|
return request('userNotes', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -19,7 +26,6 @@ const miscReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('NOTE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -34,7 +40,7 @@ const miscReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
reportUser(params) {
|
reportUser(params) {
|
||||||
return window.API.call(`feedback/${params.userId}/user`, {
|
return request(`feedback/${params.userId}/user`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params: {
|
params: {
|
||||||
contentType: params.contentType,
|
contentType: params.contentType,
|
||||||
@@ -46,7 +52,6 @@ const miscReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('FEEDBACK:REPORT:USER', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -59,7 +64,7 @@ const miscReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
getFileAnalysis(params) {
|
getFileAnalysis(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`analysis/${params.fileId}/${params.version}/${params.variant}`,
|
`analysis/${params.fileId}/${params.version}/${params.variant}`,
|
||||||
{
|
{
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
@@ -69,19 +74,17 @@ const miscReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('FILE:ANALYSIS', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getVRChatCredits() {
|
getVRChatCredits() {
|
||||||
return window.API.call(`user/${window.API.currentUser.id}/balance`, {
|
return request(`user/${getCurrentUserId()}/balance`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json
|
json
|
||||||
};
|
};
|
||||||
// window.API.$emit('VRCCREDITS', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -94,7 +97,7 @@ const miscReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
closeInstance(params) {
|
closeInstance(params) {
|
||||||
return window.API.call(`instances/${params.location}`, {
|
return request(`instances/${params.location}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
params: {
|
params: {
|
||||||
hardClose: params.hardClose ?? false
|
hardClose: params.hardClose ?? false
|
||||||
@@ -104,7 +107,6 @@ const miscReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('INSTANCE:CLOSE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -116,8 +118,8 @@ const miscReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
deleteWorldPersistData(params) {
|
deleteWorldPersistData(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`users/${window.API.currentUser.id}/${params.worldId}/persist`,
|
`users/${getCurrentUserId()}/${params.worldId}/persist`,
|
||||||
{
|
{
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}
|
}
|
||||||
@@ -126,7 +128,6 @@ const miscReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLD:PERSIST:DELETE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -138,8 +139,8 @@ const miscReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
hasWorldPersistData(params) {
|
hasWorldPersistData(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`users/${window.API.currentUser.id}/${params.worldId}/persist/exists`,
|
`users/${getCurrentUserId()}/${params.worldId}/persist/exists`,
|
||||||
{
|
{
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}
|
}
|
||||||
@@ -148,47 +149,41 @@ const miscReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLD:PERSIST:HAS', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateBadge(params) {
|
updateBadge(params) {
|
||||||
return window.API.call(
|
return request(`users/${getCurrentUserId()}/badges/${params.badgeId}`, {
|
||||||
`users/${window.API.currentUser.id}/badges/${params.badgeId}`,
|
method: 'PUT',
|
||||||
{
|
params: {
|
||||||
method: 'PUT',
|
userId: getCurrentUserId(),
|
||||||
params: {
|
badgeId: params.badgeId,
|
||||||
userId: window.API.currentUser.id,
|
hidden: params.hidden,
|
||||||
badgeId: params.badgeId,
|
showcased: params.showcased
|
||||||
hidden: params.hidden,
|
|
||||||
showcased: params.showcased
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('BADGE:UPDATE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getVisits() {
|
getVisits() {
|
||||||
return window.API.call('visits', {
|
return request('visits', {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json
|
json
|
||||||
};
|
};
|
||||||
// window.API.$emit('VISITS', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteFile(fileId) {
|
deleteFile(fileId) {
|
||||||
return window.API.call(`file/${fileId}`, {
|
return request(`file/${fileId}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
@@ -207,7 +202,7 @@ const miscReq = {
|
|||||||
// * @returns {Promise<{json: any, params}>}
|
// * @returns {Promise<{json: any, params}>}
|
||||||
// */
|
// */
|
||||||
// sendBoop(params) {
|
// sendBoop(params) {
|
||||||
// return window.API.call(`users/${params.userId}/boop`, {
|
// return request(`users/${params.userId}/boop`, {
|
||||||
// method: 'POST',
|
// method: 'POST',
|
||||||
// params
|
// params
|
||||||
// }).then((json) => {
|
// }).then((json) => {
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
// #region | API: Notification
|
import { request } from '../service/request';
|
||||||
|
import { useGroupStore, useNotificationStore } from '../stores';
|
||||||
|
|
||||||
|
function getGalleryStore() {
|
||||||
|
return useGroupStore();
|
||||||
|
}
|
||||||
|
|
||||||
const notificationReq = {
|
const notificationReq = {
|
||||||
/** @typedef {{
|
/** @typedef {{
|
||||||
@@ -17,7 +22,7 @@ const notificationReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getNotifications(params) {
|
getNotifications(params) {
|
||||||
return window.API.call('auth/user/notifications', {
|
return request('auth/user/notifications', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -25,13 +30,13 @@ const notificationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTIFICATION:LIST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getHiddenFriendRequests(params) {
|
getHiddenFriendRequests(params) {
|
||||||
return window.API.call('auth/user/notifications', {
|
return request('auth/user/notifications', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
type: 'friendRequest',
|
type: 'friendRequest',
|
||||||
@@ -43,13 +48,12 @@ const notificationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTIFICATION:LIST:HIDDEN', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getNotificationsV2(params) {
|
getNotificationsV2(params) {
|
||||||
return window.API.call('notifications', {
|
return request('notifications', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -57,7 +61,6 @@ const notificationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTIFICATION:V2:LIST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -80,7 +83,7 @@ const notificationReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
sendInvite(params, receiverUserId) {
|
sendInvite(params, receiverUserId) {
|
||||||
return window.API.call(`invite/${receiverUserId}`, {
|
return request(`invite/${receiverUserId}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -89,28 +92,26 @@ const notificationReq = {
|
|||||||
params,
|
params,
|
||||||
receiverUserId
|
receiverUserId
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTIFICATION:INVITE:SEND', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
sendInvitePhoto(params, receiverUserId) {
|
sendInvitePhoto(params, receiverUserId) {
|
||||||
return window.API.call(`invite/${receiverUserId}/photo`, {
|
return request(`invite/${receiverUserId}/photo`, {
|
||||||
uploadImageLegacy: true,
|
uploadImageLegacy: true,
|
||||||
postData: JSON.stringify(params),
|
postData: JSON.stringify(params),
|
||||||
imageData: window.$app.uploadImage
|
imageData: getGalleryStore().uploadImage
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params,
|
params,
|
||||||
receiverUserId
|
receiverUserId
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTIFICATION:INVITE:PHOTO:SEND', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
sendRequestInvite(params, receiverUserId) {
|
sendRequestInvite(params, receiverUserId) {
|
||||||
return window.API.call(`requestInvite/${receiverUserId}`, {
|
return request(`requestInvite/${receiverUserId}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -119,29 +120,27 @@ const notificationReq = {
|
|||||||
params,
|
params,
|
||||||
receiverUserId
|
receiverUserId
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTIFICATION:REQUESTINVITE:SEND', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
sendRequestInvitePhoto(params, receiverUserId) {
|
sendRequestInvitePhoto(params, receiverUserId) {
|
||||||
return window.API.call(`requestInvite/${receiverUserId}/photo`, {
|
return request(`requestInvite/${receiverUserId}/photo`, {
|
||||||
uploadImageLegacy: true,
|
uploadImageLegacy: true,
|
||||||
postData: JSON.stringify(params),
|
postData: JSON.stringify(params),
|
||||||
imageData: window.$app.uploadImage
|
imageData: getGalleryStore().uploadImage
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params,
|
params,
|
||||||
receiverUserId
|
receiverUserId
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTIFICATION:REQUESTINVITE:PHOTO:SEND', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
sendInviteResponse(params, inviteId) {
|
sendInviteResponse(params, inviteId) {
|
||||||
return window.API.call(`invite/${inviteId}/response`, {
|
return request(`invite/${inviteId}/response`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params,
|
params,
|
||||||
inviteId
|
inviteId
|
||||||
@@ -151,16 +150,15 @@ const notificationReq = {
|
|||||||
params,
|
params,
|
||||||
inviteId
|
inviteId
|
||||||
};
|
};
|
||||||
window.API.$emit('INVITE:RESPONSE:SEND', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
sendInviteResponsePhoto(params, inviteId) {
|
sendInviteResponsePhoto(params, inviteId) {
|
||||||
return window.API.call(`invite/${inviteId}/response/photo`, {
|
return request(`invite/${inviteId}/response/photo`, {
|
||||||
uploadImageLegacy: true,
|
uploadImageLegacy: true,
|
||||||
postData: JSON.stringify(params),
|
postData: JSON.stringify(params),
|
||||||
imageData: window.$app.uploadImage,
|
imageData: getGalleryStore().uploadImage,
|
||||||
inviteId
|
inviteId
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
@@ -168,7 +166,6 @@ const notificationReq = {
|
|||||||
params,
|
params,
|
||||||
inviteId
|
inviteId
|
||||||
};
|
};
|
||||||
window.API.$emit('INVITE:RESPONSE:PHOTO:SEND', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -178,7 +175,7 @@ const notificationReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
acceptFriendRequestNotification(params) {
|
acceptFriendRequestNotification(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`auth/user/notifications/${params.notificationId}/accept`,
|
`auth/user/notifications/${params.notificationId}/accept`,
|
||||||
{
|
{
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
@@ -189,13 +186,13 @@ const notificationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTIFICATION:ACCEPT', args);
|
useNotificationStore().handleNotificationAccept(args);
|
||||||
return args;
|
return args;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
// if friend request could not be found, delete it
|
// if friend request could not be found, delete it
|
||||||
if (err && err.message && err.message.includes('404')) {
|
if (err && err.message && err.message.includes('404')) {
|
||||||
window.API.$emit('NOTIFICATION:HIDE', { params });
|
useNotificationStore().handleNotificationHide({ params });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -205,7 +202,7 @@ const notificationReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
hideNotification(params) {
|
hideNotification(params) {
|
||||||
return window.API.call(
|
return request(
|
||||||
`auth/user/notifications/${params.notificationId}/hide`,
|
`auth/user/notifications/${params.notificationId}/hide`,
|
||||||
{
|
{
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
@@ -215,13 +212,11 @@ const notificationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTIFICATION:HIDE', args);
|
useNotificationStore().handleNotificationHide(args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// ------------------- need to test -------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{
|
* @param {{
|
||||||
* notificationId: string,
|
* notificationId: string,
|
||||||
@@ -231,32 +226,14 @@ const notificationReq = {
|
|||||||
* @return { Promise<{json: any, params}> }
|
* @return { Promise<{json: any, params}> }
|
||||||
*/
|
*/
|
||||||
sendNotificationResponse(params) {
|
sendNotificationResponse(params) {
|
||||||
return window.API.call(
|
return request(`notifications/${params.notificationId}/respond`, {
|
||||||
`notifications/${params.notificationId}/respond`,
|
method: 'POST',
|
||||||
{
|
params
|
||||||
method: 'POST',
|
});
|
||||||
params
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then((json) => {
|
|
||||||
const args = {
|
|
||||||
json,
|
|
||||||
params
|
|
||||||
};
|
|
||||||
window.API.$emit('NOTIFICATION:RESPONSE', args);
|
|
||||||
return args;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
// TODO: need to test
|
|
||||||
// something went wrong, lets assume it's already expired
|
|
||||||
window.API.$emit('NOTIFICATION:HIDE', { params });
|
|
||||||
notificationReq.hideNotificationV2(params.notificationId);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
// use in sendNotificationResponse
|
|
||||||
hideNotificationV2(notificationId) {
|
hideNotificationV2(notificationId) {
|
||||||
return window.API.call(`notifications/${notificationId}`, {
|
return request(`notifications/${notificationId}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
@@ -265,16 +242,12 @@ const notificationReq = {
|
|||||||
notificationId
|
notificationId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// useless
|
|
||||||
// window.API.$emit('NOTIFICATION:V2:HIDE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------ look like no place use these requests ------------------
|
|
||||||
|
|
||||||
// sendInviteGalleryPhoto(params, receiverUserId) {
|
// sendInviteGalleryPhoto(params, receiverUserId) {
|
||||||
// return window.API.call(`invite/${receiverUserId}/photo`, {
|
// return request(`invite/${receiverUserId}/photo`, {
|
||||||
// method: 'POST',
|
// method: 'POST',
|
||||||
// params
|
// params
|
||||||
// }).then((json) => {
|
// }).then((json) => {
|
||||||
@@ -283,13 +256,13 @@ const notificationReq = {
|
|||||||
// params,
|
// params,
|
||||||
// receiverUserId
|
// receiverUserId
|
||||||
// };
|
// };
|
||||||
// window.API.$emit('NOTIFICATION:INVITE:GALLERYPHOTO:SEND', args);
|
// API.$emit('NOTIFICATION:INVITE:GALLERYPHOTO:SEND', args);
|
||||||
// return args;
|
// return args;
|
||||||
// });
|
// });
|
||||||
// },
|
// },
|
||||||
|
|
||||||
// API.clearNotifications = function () {
|
// API.clearNotifications = function () {
|
||||||
// return this.call('auth/user/notifications/clear', {
|
// return request('auth/user/notifications/clear', {
|
||||||
// method: 'PUT'
|
// method: 'PUT'
|
||||||
// }).then((json) => {
|
// }).then((json) => {
|
||||||
// var args = {
|
// var args = {
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
// #region | API: PlayerModeration
|
import { request } from '../service/request';
|
||||||
|
|
||||||
const playerModerationReq = {
|
const playerModerationReq = {
|
||||||
getPlayerModerations() {
|
getPlayerModerations() {
|
||||||
return window.API.call('auth/user/playermoderations', {
|
return request('auth/user/playermoderations', {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json
|
json
|
||||||
};
|
};
|
||||||
window.API.$emit('PLAYER-MODERATION:LIST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -19,7 +18,7 @@ const playerModerationReq = {
|
|||||||
*/
|
*/
|
||||||
// old-way: POST auth/user/blocks {blocked:userId}
|
// old-way: POST auth/user/blocks {blocked:userId}
|
||||||
sendPlayerModeration(params) {
|
sendPlayerModeration(params) {
|
||||||
return window.API.call('auth/user/playermoderations', {
|
return request('auth/user/playermoderations', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -27,7 +26,6 @@ const playerModerationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('PLAYER-MODERATION:SEND', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -38,7 +36,7 @@ const playerModerationReq = {
|
|||||||
*/
|
*/
|
||||||
// old-way: PUT auth/user/unblocks {blocked:userId}
|
// old-way: PUT auth/user/unblocks {blocked:userId}
|
||||||
deletePlayerModeration(params) {
|
deletePlayerModeration(params) {
|
||||||
return window.API.call('auth/user/unplayermoderate', {
|
return request('auth/user/unplayermoderate', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -46,11 +44,9 @@ const playerModerationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('PLAYER-MODERATION:DELETE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// #endregion
|
|
||||||
export default playerModerationReq;
|
export default playerModerationReq;
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import { request } from '../service/request';
|
||||||
|
|
||||||
const propReq = {
|
const propReq = {
|
||||||
/**
|
/**
|
||||||
* @param {{ propId: string }} params
|
* @param {{ propId: string }} params
|
||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getProp(params) {
|
getProp(params) {
|
||||||
return window.API.call(`props/${params.propId}`, {
|
return request(`props/${params.propId}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
// #region | API: User
|
import { request } from '../service/request';
|
||||||
|
import { useUserStore } from '../stores';
|
||||||
|
|
||||||
|
function getCurrentUserId() {
|
||||||
|
return useUserStore().currentUser.id;
|
||||||
|
}
|
||||||
|
|
||||||
const userReq = {
|
const userReq = {
|
||||||
/**
|
/**
|
||||||
* Fetch user from API.
|
* Fetch user from API.
|
||||||
* @param {{ userId: string }} params identifier of registered user
|
* identifier of registered user
|
||||||
* @returns {Promise<{json: any, params}>}
|
* @type {import('../types/user').getUser}
|
||||||
*/
|
*/
|
||||||
getUser(params) {
|
getUser(params) {
|
||||||
return window.API.call(`users/${params.userId}`, {
|
const userStore = useUserStore();
|
||||||
|
return request(`users/${params.userId}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
if (!json) {
|
if (!json) {
|
||||||
@@ -19,7 +25,7 @@ const userReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('USER', args);
|
args.ref = userStore.applyUser(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -30,10 +36,17 @@ const userReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getCachedUser(params) {
|
getCachedUser(params) {
|
||||||
|
const userStore = useUserStore();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const ref = window.API.cachedUsers.get(params.userId);
|
const ref = userStore.cachedUsers.get(params.userId);
|
||||||
if (typeof ref === 'undefined') {
|
if (typeof ref === 'undefined') {
|
||||||
userReq.getUser(params).catch(reject).then(resolve);
|
userReq
|
||||||
|
.getUser(params)
|
||||||
|
.catch(reject)
|
||||||
|
.then((args) => {
|
||||||
|
args.ref = userStore.applyUser(args.json);
|
||||||
|
resolve(args);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve({
|
resolve({
|
||||||
cache: true,
|
cache: true,
|
||||||
@@ -59,7 +72,7 @@ const userReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getUsers(params) {
|
getUsers(params) {
|
||||||
return window.API.call('users', {
|
return request('users', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -67,7 +80,6 @@ const userReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('USER:LIST', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -77,7 +89,8 @@ const userReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
addUserTags(params) {
|
addUserTags(params) {
|
||||||
return window.API.call(`users/${window.API.currentUser.id}/addTags`, {
|
const userStore = useUserStore();
|
||||||
|
return request(`users/${getCurrentUserId()}/addTags`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -85,7 +98,7 @@ const userReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('USER:CURRENT:SAVE', args);
|
userStore.applyCurrentUser(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -95,18 +108,16 @@ const userReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
removeUserTags(params) {
|
removeUserTags(params) {
|
||||||
return window.API.call(
|
const userStore = useUserStore();
|
||||||
`users/${window.API.currentUser.id}/removeTags`,
|
return request(`users/${getCurrentUserId()}/removeTags`, {
|
||||||
{
|
method: 'POST',
|
||||||
method: 'POST',
|
params
|
||||||
params
|
}).then((json) => {
|
||||||
}
|
|
||||||
).then((json) => {
|
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('USER:CURRENT:SAVE', args);
|
userStore.applyCurrentUser(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -116,7 +127,7 @@ const userReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getUserFeedback(params) {
|
getUserFeedback(params) {
|
||||||
return window.API.call(`users/${params.userId}/feedback`, {
|
return request(`users/${params.userId}/feedback`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
n: 100
|
n: 100
|
||||||
@@ -126,7 +137,6 @@ const userReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('USER:FEEDBACK', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -144,15 +154,16 @@ const userReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
saveCurrentUser(params) {
|
saveCurrentUser(params) {
|
||||||
return window.API.call(`users/${window.API.currentUser.id}`, {
|
const userStore = useUserStore();
|
||||||
|
return request(`users/${getCurrentUserId()}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
var args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('USER:CURRENT:SAVE', args);
|
userStore.applyCurrentUser(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -162,7 +173,7 @@ const userReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getUserNotes(params) {
|
getUserNotes(params) {
|
||||||
return window.API.call(`userNotes`, {
|
return request(`userNotes`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -170,11 +181,9 @@ const userReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
// window.API.$emit('USER:NOTES', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// #endregion
|
|
||||||
|
|
||||||
export default userReq;
|
export default userReq;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// #region | App: VRCPlus Icons
|
import { request } from '../service/request';
|
||||||
|
|
||||||
const VRCPlusIconsReq = {
|
const VRCPlusIconsReq = {
|
||||||
getFileList(params) {
|
getFileList(params) {
|
||||||
return window.API.call('files', {
|
return request('files', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -10,7 +10,18 @@ const VRCPlusIconsReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FILES:LIST', args);
|
return args;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteFile(fileId) {
|
||||||
|
return request(`file/${fileId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
}).then((json) => {
|
||||||
|
const args = {
|
||||||
|
json,
|
||||||
|
fileId
|
||||||
|
};
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -19,7 +30,7 @@ const VRCPlusIconsReq = {
|
|||||||
const params = {
|
const params = {
|
||||||
tag: 'icon'
|
tag: 'icon'
|
||||||
};
|
};
|
||||||
return window.API.call('file/image', {
|
return request('file/image', {
|
||||||
uploadImage: true,
|
uploadImage: true,
|
||||||
matchingDimensions: true,
|
matchingDimensions: true,
|
||||||
postData: JSON.stringify(params),
|
postData: JSON.stringify(params),
|
||||||
@@ -29,15 +40,12 @@ const VRCPlusIconsReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('VRCPLUSICON:ADD', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// unused
|
|
||||||
// images.pug line 63
|
|
||||||
// deleteFileVersion(params) {
|
// deleteFileVersion(params) {
|
||||||
// return window.API.call(`file/${params.fileId}/${params.version}`, {
|
// return request(`file/${params.fileId}/${params.version}`, {
|
||||||
// method: 'DELETE'
|
// method: 'DELETE'
|
||||||
// }).then((json) => {
|
// }).then((json) => {
|
||||||
// const args = {
|
// const args = {
|
||||||
@@ -49,6 +57,4 @@ const VRCPlusIconsReq = {
|
|||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
export default VRCPlusIconsReq;
|
export default VRCPlusIconsReq;
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
|
import { request } from '../service/request';
|
||||||
|
import { useUserStore } from '../stores';
|
||||||
|
|
||||||
|
function getCurrentUserId() {
|
||||||
|
return useUserStore().currentUser.id;
|
||||||
|
}
|
||||||
const vrcPlusImageReq = {
|
const vrcPlusImageReq = {
|
||||||
uploadGalleryImage(imageData) {
|
uploadGalleryImage(imageData) {
|
||||||
const params = {
|
const params = {
|
||||||
tag: 'gallery'
|
tag: 'gallery'
|
||||||
};
|
};
|
||||||
return window.API.call('file/image', {
|
return request('file/image', {
|
||||||
uploadImage: true,
|
uploadImage: true,
|
||||||
matchingDimensions: false,
|
matchingDimensions: false,
|
||||||
postData: JSON.stringify(params),
|
postData: JSON.stringify(params),
|
||||||
@@ -13,13 +19,12 @@ const vrcPlusImageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('GALLERYIMAGE:ADD', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadSticker(imageData, params) {
|
uploadSticker(imageData, params) {
|
||||||
return window.API.call('file/image', {
|
return request('file/image', {
|
||||||
uploadImage: true,
|
uploadImage: true,
|
||||||
matchingDimensions: true,
|
matchingDimensions: true,
|
||||||
postData: JSON.stringify(params),
|
postData: JSON.stringify(params),
|
||||||
@@ -29,13 +34,12 @@ const vrcPlusImageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('STICKER:ADD', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getPrints(params) {
|
getPrints(params) {
|
||||||
return window.API.call(`prints/user/${window.API.currentUser.id}`, {
|
return request(`prints/user/${getCurrentUserId()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -48,20 +52,19 @@ const vrcPlusImageReq = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
deletePrint(printId) {
|
deletePrint(printId) {
|
||||||
return window.API.call(`prints/${printId}`, {
|
return request(`prints/${printId}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
printId
|
printId
|
||||||
};
|
};
|
||||||
// window.API.$emit('PRINT:DELETE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadPrint(imageData, cropWhiteBorder, params) {
|
uploadPrint(imageData, cropWhiteBorder, params) {
|
||||||
return window.API.call('prints', {
|
return request('prints', {
|
||||||
uploadImagePrint: true,
|
uploadImagePrint: true,
|
||||||
cropWhiteBorder,
|
cropWhiteBorder,
|
||||||
postData: JSON.stringify(params),
|
postData: JSON.stringify(params),
|
||||||
@@ -71,26 +74,24 @@ const vrcPlusImageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('PRINT:ADD', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getPrint(params) {
|
getPrint(params) {
|
||||||
return window.API.call(`prints/${params.printId}`, {
|
return request(`prints/${params.printId}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('PRINT', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadEmoji(imageData, params) {
|
uploadEmoji(imageData, params) {
|
||||||
return window.API.call('file/image', {
|
return request('file/image', {
|
||||||
uploadImage: true,
|
uploadImage: true,
|
||||||
matchingDimensions: true,
|
matchingDimensions: true,
|
||||||
postData: JSON.stringify(params),
|
postData: JSON.stringify(params),
|
||||||
@@ -100,15 +101,12 @@ const vrcPlusImageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('EMOJI:ADD', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------- no place uses this function ------------
|
|
||||||
|
|
||||||
// editPrint(params) {
|
// editPrint(params) {
|
||||||
// return window.API.call(`prints/${params.printId}`, {
|
// return request(`prints/${params.printId}`, {
|
||||||
// method: 'POST',
|
// method: 'POST',
|
||||||
// params
|
// params
|
||||||
// }).then((json) => {
|
// }).then((json) => {
|
||||||
@@ -116,7 +114,7 @@ const vrcPlusImageReq = {
|
|||||||
// json,
|
// json,
|
||||||
// params
|
// params
|
||||||
// };
|
// };
|
||||||
// window.API.$emit('PRINT:EDIT', args);
|
// API.$emit('PRINT:EDIT', args);
|
||||||
// return args;
|
// return args;
|
||||||
// });
|
// });
|
||||||
// },
|
// },
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// #region | API: World
|
import { request } from '../service/request';
|
||||||
|
import { useWorldStore } from '../stores';
|
||||||
|
|
||||||
const worldReq = {
|
const worldReq = {
|
||||||
/**
|
/**
|
||||||
@@ -6,14 +7,15 @@ const worldReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getWorld(params) {
|
getWorld(params) {
|
||||||
return window.API.call(`worlds/${params.worldId}`, {
|
const worldStore = useWorldStore();
|
||||||
|
return request(`worlds/${params.worldId}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLD', args);
|
args.ref = worldStore.applyWorld(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -23,10 +25,17 @@ const worldReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
getCachedWorld(params) {
|
getCachedWorld(params) {
|
||||||
|
const worldStore = useWorldStore();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const ref = window.API.cachedWorlds.get(params.worldId);
|
const ref = worldStore.cachedWorlds.get(params.worldId);
|
||||||
if (typeof ref === 'undefined') {
|
if (typeof ref === 'undefined') {
|
||||||
worldReq.getWorld(params).catch(reject).then(resolve);
|
worldReq
|
||||||
|
.getWorld(params)
|
||||||
|
.catch(reject)
|
||||||
|
.then((args) => {
|
||||||
|
args.ref = worldStore.applyWorld(args.json);
|
||||||
|
resolve(args);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve({
|
resolve({
|
||||||
cache: true,
|
cache: true,
|
||||||
@@ -57,11 +66,12 @@ const worldReq = {
|
|||||||
* @returns {Promise<{json: any, params, option}>}
|
* @returns {Promise<{json: any, params, option}>}
|
||||||
*/
|
*/
|
||||||
getWorlds(params, option) {
|
getWorlds(params, option) {
|
||||||
|
const worldStore = useWorldStore();
|
||||||
let endpoint = 'worlds';
|
let endpoint = 'worlds';
|
||||||
if (typeof option !== 'undefined') {
|
if (typeof option !== 'undefined') {
|
||||||
endpoint = `worlds/${option}`;
|
endpoint = `worlds/${option}`;
|
||||||
}
|
}
|
||||||
return window.API.call(endpoint, {
|
return request(endpoint, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -70,7 +80,9 @@ const worldReq = {
|
|||||||
params,
|
params,
|
||||||
option
|
option
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLD:LIST', args);
|
for (const json of args.json) {
|
||||||
|
worldStore.applyWorld(json);
|
||||||
|
}
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -79,14 +91,13 @@ const worldReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
deleteWorld(params) {
|
deleteWorld(params) {
|
||||||
return window.API.call(`worlds/${params.worldId}`, {
|
return request(`worlds/${params.worldId}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
const args = {
|
const args = {
|
||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLD:DELETE', args);
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -96,7 +107,8 @@ const worldReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
saveWorld(params) {
|
saveWorld(params) {
|
||||||
return window.API.call(`worlds/${params.id}`, {
|
const worldStore = useWorldStore();
|
||||||
|
return request(`worlds/${params.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -104,7 +116,7 @@ const worldReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLD:SAVE', args);
|
args.ref = worldStore.applyWorld(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -114,7 +126,8 @@ const worldReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
publishWorld(params) {
|
publishWorld(params) {
|
||||||
return window.API.call(`worlds/${params.worldId}/publish`, {
|
const worldStore = useWorldStore();
|
||||||
|
return request(`worlds/${params.worldId}/publish`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -122,7 +135,7 @@ const worldReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLD:SAVE', args);
|
args.ref = worldStore.applyWorld(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -132,7 +145,8 @@ const worldReq = {
|
|||||||
* @returns {Promise<{json: any, params}>}
|
* @returns {Promise<{json: any, params}>}
|
||||||
*/
|
*/
|
||||||
unpublishWorld(params) {
|
unpublishWorld(params) {
|
||||||
return window.API.call(`worlds/${params.worldId}/publish`, {
|
const worldStore = useWorldStore();
|
||||||
|
return request(`worlds/${params.worldId}/publish`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
params
|
params
|
||||||
}).then((json) => {
|
}).then((json) => {
|
||||||
@@ -140,12 +154,10 @@ const worldReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLD:SAVE', args);
|
args.ref = worldStore.applyWorld(json);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
export default worldReq;
|
export default worldReq;
|
||||||
|
|||||||
14159
src/app.js
14159
src/app.js
File diff suppressed because it is too large
Load Diff
94
src/app.pug
94
src/app.pug
@@ -1,63 +1,71 @@
|
|||||||
doctype html
|
doctype html
|
||||||
#x-app.x-app(@dragenter.prevent @dragover.prevent @drop.prevent)
|
#x-app.x-app(@dragenter.prevent @dragover.prevent @drop.prevent)
|
||||||
LoginPage(v-if="!API.isLoggedIn" v-bind="loginPageBind" v-on="loginPageEvent")
|
//- ### Login ###
|
||||||
|
Login(v-if='!watchState.isLoggedIn')
|
||||||
|
|
||||||
VRCXUpdateDialog(v-bind="vrcxUpdateDialogBind" v-on="vrcxUpdateDialogEvent")
|
VRCXUpdateDialog
|
||||||
|
|
||||||
//- menu
|
template(v-if='watchState.isLoggedIn')
|
||||||
.x-menu-container
|
//- ### Menu ###
|
||||||
//- download progress, update pending
|
NavMenu
|
||||||
.pending-update(v-if='updateInProgress' @click='showVRCXUpdateDialog')
|
|
||||||
el-progress(
|
|
||||||
type='circle'
|
|
||||||
width='50'
|
|
||||||
stroke-width='3'
|
|
||||||
:percentage='updateProgress'
|
|
||||||
:format='updateProgressText')
|
|
||||||
.pending-update(v-else-if='pendingVRCXUpdate || pendingVRCXInstall')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='showVRCXUpdateDialog'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-download'
|
|
||||||
circle
|
|
||||||
style='font-size: 14px; height: 50px; width: 50px')
|
|
||||||
|
|
||||||
NavMenu(ref='menu' @select='selectMenu' :menu-active-index='menuActiveIndex')
|
//- ### Sidebar ###
|
||||||
|
Sidebar
|
||||||
|
|
||||||
//- ### Tabs ###
|
//- ### Tabs ###
|
||||||
template(v-if='API.isLoggedIn')
|
Feed
|
||||||
FeedTab(v-bind='feedTabBind' v-on='feedTabEvent')
|
|
||||||
|
|
||||||
GameLogTab(v-bind='gameLogTabBind' v-on='gameLogTabEvent')
|
GameLog
|
||||||
|
|
||||||
PlayerListTab(v-bind='playerListTabBind' v-on='playerListTabEvent')
|
PlayerList
|
||||||
|
|
||||||
SearchTab(v-bind='searchTabBind' v-on='searchTabEvent')
|
Search
|
||||||
|
|
||||||
FavoritesTab(v-bind='favoritesTabBind' v-on='favoritesTabEvent')
|
Favorites
|
||||||
|
|
||||||
FriendLogTab(v-bind='friendLogTabBind')
|
FriendLog
|
||||||
|
|
||||||
ModerationTab(v-bind='moderationTabBind')
|
Moderation
|
||||||
|
|
||||||
NotificationTab(v-bind='notificationTabBind' v-on='notificationTabEvent')
|
Notification
|
||||||
|
|
||||||
ProfileTab(v-bind='profileTabBind' v-on='profileTabEvent')
|
FriendList
|
||||||
|
|
||||||
FriendListTab(v-bind='friendsListTabBind' v-on='friendsListTabEvent')
|
Charts
|
||||||
|
|
||||||
KeepAlive
|
Profile
|
||||||
ChartsTab(v-if='menuActiveIndex === "charts"' v-bind='chartsTabBind' v-on='chartsTabEvent')
|
|
||||||
|
|
||||||
//- settings
|
Settings
|
||||||
include ./mixins/tabs/settings.pug
|
|
||||||
+settingsTab
|
|
||||||
|
|
||||||
SideBar(v-bind='sideBarTabBind' v-on='sideBarTabEvent')
|
|
||||||
|
|
||||||
//- ## Dialogs ## -\\
|
//- ## Dialogs ## -\\
|
||||||
include ./mixins/dialogs/dialogs.pug
|
UserDialog
|
||||||
+dialogs
|
|
||||||
|
|
||||||
//- el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="templateDialog" :visible.sync="templateDialog.visible" :title="$t('dialog.template_dialog.header')" width="450px")
|
WorldDialog
|
||||||
|
|
||||||
|
AvatarDialog
|
||||||
|
|
||||||
|
GroupDialog
|
||||||
|
|
||||||
|
GalleryDialog
|
||||||
|
|
||||||
|
FullscreenImageDialog
|
||||||
|
|
||||||
|
PreviousInstancesInfoDialog
|
||||||
|
|
||||||
|
LaunchDialog
|
||||||
|
|
||||||
|
LaunchOptionsDialog
|
||||||
|
|
||||||
|
FriendImportDialog
|
||||||
|
|
||||||
|
WorldImportDialog
|
||||||
|
|
||||||
|
AvatarImportDialog
|
||||||
|
|
||||||
|
ChooseFavoriteGroupDialog
|
||||||
|
|
||||||
|
EditInviteMessageDialog
|
||||||
|
|
||||||
|
VRChatConfigDialog
|
||||||
|
|
||||||
|
PrimaryPasswordDialog
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
@use "./assets/scss/flags.scss";
|
||||||
|
@use "./assets/scss/animated-emoji.scss";
|
||||||
|
|
||||||
@import '~animate.css/animate.min.css';
|
@import '~animate.css/animate.min.css';
|
||||||
@import '~noty/lib/noty.css';
|
@import '~noty/lib/noty.css';
|
||||||
@import '~element-ui/lib/theme-chalk/index.css';
|
@import '~element-ui/lib/theme-chalk/index.css';
|
||||||
@@ -299,6 +302,7 @@ hr.x-vertical-divider {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #f8f8f8;
|
background: #f8f8f8;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
order: 99;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-popper.x-quick-search {
|
.el-popper.x-quick-search {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* VRCX Dark-Vanilla theme by MintLily
|
* VRCX Dark-Vanilla theme by MintLily
|
||||||
* https://github.com/MintLily/Dark-Vanilla
|
* https://github.com/MintLily/Dark-Vanilla
|
||||||
*/
|
*/
|
||||||
@import 'theme.dark';
|
@use 'theme.dark';
|
||||||
:root {
|
:root {
|
||||||
--ThemeName: 'Dark Vanilla';
|
--ThemeName: 'Dark Vanilla';
|
||||||
--ThemeVersion: 'v1.7';
|
--ThemeVersion: 'v1.7';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* VRCX Material-You-like theme by Kamiya
|
* VRCX Material-You-like theme by Kamiya
|
||||||
* https://github.com/kamiya10/VRCX-theme
|
* https://github.com/kamiya10/VRCX-theme
|
||||||
*/
|
*/
|
||||||
@import 'theme.dark';
|
@use 'theme.dark';
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;600&family=Noto+Sans+TC:wght@300;400;500&family=Noto+Sans+SC:wght@300;400;500&family=Noto+Sans+JP:wght@300;400;500&family=Roboto&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;600&family=Noto+Sans+TC:wght@300;400;500&family=Noto+Sans+SC:wght@300;400;500&family=Noto+Sans+JP:wght@300;400;500&family=Roboto&display=swap');
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200');
|
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200');
|
||||||
|
|
||||||
@@ -653,6 +653,8 @@ input[type='number'],
|
|||||||
border-left: 1px solid rgb(var(--md-sys-color-surface-variant));
|
border-left: 1px solid rgb(var(--md-sys-color-surface-variant));
|
||||||
*/
|
*/
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
/* ---------- Switch ---------- */
|
/* ---------- Switch ---------- */
|
||||||
|
|
||||||
@@ -1083,8 +1085,8 @@ input[type='number'],
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
margin-top: 8px !important;
|
// margin-top: 8px !important;
|
||||||
margin-right: 8px !important;
|
// margin-right: 8px !important;
|
||||||
color: rgb(var(--md-sys-color-on-surface-variant));
|
color: rgb(var(--md-sys-color-on-surface-variant));
|
||||||
font-family: var(--md-sys-typescale-label-large-font);
|
font-family: var(--md-sys-typescale-label-large-font);
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
@@ -1380,7 +1382,7 @@ img.x-link.el-popover__reference {
|
|||||||
*:not(.x-user-dialog, .x-world-dialog, .x-avatar-dialog, .x-group-dialog)
|
*:not(.x-user-dialog, .x-world-dialog, .x-avatar-dialog, .x-group-dialog)
|
||||||
> .el-dialog:not([aria-label*='Notification Position']):not(
|
> .el-dialog:not([aria-label*='Notification Position']):not(
|
||||||
[aria-label*='Launch']
|
[aria-label*='Launch']
|
||||||
):not([aria-label*='VRCX Updater']) {
|
):not([aria-label*='VRCX Updater']):not([aria-label*='Social Status']) {
|
||||||
max-width: 1125px !important;
|
max-width: 1125px !important;
|
||||||
width: 1125px !important;
|
width: 1125px !important;
|
||||||
}
|
}
|
||||||
@@ -2008,6 +2010,7 @@ i.x-user-status {
|
|||||||
.el-collapse-item .el-tag--mini {
|
.el-collapse-item .el-tag--mini {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: transparent;
|
border: transparent;
|
||||||
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
.simple-switch {
|
.simple-switch {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -2072,3 +2075,11 @@ i.x-user-status {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.el-dialog[aria-label='Launch'] .el-form .el-form-item__content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.el-dialog[aria-label='Launch'] .el-form > .el-form-item:nth-child(2) .el-form-item__label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* VRCX Pink theme by Kamiya
|
* VRCX Pink theme by Kamiya
|
||||||
* https://github.com/kamiya10/VRCX-theme
|
* https://github.com/kamiya10/VRCX-theme
|
||||||
*/
|
*/
|
||||||
@import 'theme.dark';
|
@use 'theme.dark';
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
|
||||||
:root {
|
:root {
|
||||||
--theme: #dfa2a2;
|
--theme: #dfa2a2;
|
||||||
@@ -70,7 +70,7 @@ textarea {
|
|||||||
.el-textarea .el-input__count,
|
.el-textarea .el-input__count,
|
||||||
.el-textarea__inner,
|
.el-textarea__inner,
|
||||||
.x-menu-container {
|
.x-menu-container {
|
||||||
background-color: var(--lighter-bg);
|
background-color: var(--lighter-bg) !important;
|
||||||
}
|
}
|
||||||
.el-color-picker__panel {
|
.el-color-picker__panel {
|
||||||
border-color: var(--lighter-bg);
|
border-color: var(--lighter-bg);
|
||||||
|
|||||||
76
src/bootstrap.js
vendored
Normal file
76
src/bootstrap.js
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import '@fontsource/noto-sans-kr';
|
||||||
|
import '@fontsource/noto-sans-jp';
|
||||||
|
import '@fontsource/noto-sans-sc';
|
||||||
|
import '@fontsource/noto-sans-tc';
|
||||||
|
import '@infolektuell/noto-color-emoji';
|
||||||
|
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { PiniaVuePlugin } from 'pinia';
|
||||||
|
import { DataTables } from 'vue-data-tables';
|
||||||
|
import VueLazyload from 'vue-lazyload';
|
||||||
|
import configRepository from './service/config';
|
||||||
|
import vrcxJsonStorage from './service/jsonStorage';
|
||||||
|
import {
|
||||||
|
changeAppDarkStyle,
|
||||||
|
changeAppThemeStyle,
|
||||||
|
refreshCustomCss,
|
||||||
|
refreshCustomScript,
|
||||||
|
systemIsDarkMode
|
||||||
|
} from './shared/utils';
|
||||||
|
import { i18n } from './plugin';
|
||||||
|
|
||||||
|
configRepository.init();
|
||||||
|
i18n.locale = await configRepository.getString('VRCX_appLanguage', 'en');
|
||||||
|
|
||||||
|
AppApi.SetUserAgent();
|
||||||
|
|
||||||
|
const initThemeMode = await configRepository.getString(
|
||||||
|
'VRCX_ThemeMode',
|
||||||
|
'system'
|
||||||
|
);
|
||||||
|
|
||||||
|
let isDarkMode;
|
||||||
|
|
||||||
|
if (initThemeMode === 'light') {
|
||||||
|
isDarkMode = false;
|
||||||
|
} else if (initThemeMode === 'system') {
|
||||||
|
isDarkMode = systemIsDarkMode();
|
||||||
|
} else {
|
||||||
|
isDarkMode = true;
|
||||||
|
}
|
||||||
|
changeAppDarkStyle(isDarkMode);
|
||||||
|
changeAppThemeStyle(initThemeMode);
|
||||||
|
|
||||||
|
refreshCustomCss();
|
||||||
|
refreshCustomScript();
|
||||||
|
|
||||||
|
Vue.use(PiniaVuePlugin);
|
||||||
|
Vue.use(DataTables);
|
||||||
|
|
||||||
|
Vue.use(VueLazyload, {
|
||||||
|
preLoad: 1,
|
||||||
|
observer: true,
|
||||||
|
observerOptions: {
|
||||||
|
rootMargin: '0px',
|
||||||
|
threshold: 0
|
||||||
|
},
|
||||||
|
attempt: 3
|
||||||
|
});
|
||||||
|
|
||||||
|
new vrcxJsonStorage(VRCXStorage);
|
||||||
|
|
||||||
|
// some workaround for failing to get voice list first run
|
||||||
|
speechSynthesis.getVoices();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
Vue.config.errorHandler = function (err, vm, info) {
|
||||||
|
console.error('Vue Error:', err);
|
||||||
|
console.error('Component:', vm);
|
||||||
|
console.error('Error Info:', info);
|
||||||
|
};
|
||||||
|
Vue.config.warnHandler = function (msg, vm, trace) {
|
||||||
|
console.warn('Vue Warning:', msg);
|
||||||
|
console.warn('Component:', vm);
|
||||||
|
console.warn('Trace:', trace);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { baseClass, $app, API, $t, $utils } from '../baseClass.js';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
API.getConfig = function () {
|
|
||||||
return this.call('config', {
|
|
||||||
method: 'GET'
|
|
||||||
}).then((json) => {
|
|
||||||
var args = {
|
|
||||||
json
|
|
||||||
};
|
|
||||||
this.$emit('CONFIG', args);
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
API.$on('CONFIG', function (args) {
|
|
||||||
args.ref = this.applyConfig(args.json);
|
|
||||||
});
|
|
||||||
|
|
||||||
API.$on('CONFIG', function (args) {
|
|
||||||
if (typeof args.ref?.whiteListedAssetUrls !== 'object') {
|
|
||||||
console.error('Invalid config whiteListedAssetUrls');
|
|
||||||
}
|
|
||||||
AppApi.PopulateImageHosts(
|
|
||||||
JSON.stringify(args.ref.whiteListedAssetUrls)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
API.applyConfig = function (json) {
|
|
||||||
var ref = {
|
|
||||||
...json
|
|
||||||
};
|
|
||||||
this.cachedConfig = ref;
|
|
||||||
return ref;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {};
|
|
||||||
|
|
||||||
_methods = {};
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import * as workerTimers from 'worker-timers';
|
|
||||||
import configRepository from '../service/config.js';
|
|
||||||
import database from '../service/database.js';
|
|
||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
_data = {};
|
|
||||||
|
|
||||||
_methods = {};
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { baseClass, $app, API, $t } from './baseClass.js';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app) {
|
|
||||||
super(_app);
|
|
||||||
}
|
|
||||||
|
|
||||||
eventHandlers = new Map();
|
|
||||||
|
|
||||||
$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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$on = function (name, handler) {
|
|
||||||
var handlers = this.eventHandlers.get(name);
|
|
||||||
if (typeof handlers === 'undefined') {
|
|
||||||
handlers = [];
|
|
||||||
this.eventHandlers.set(name, handlers);
|
|
||||||
}
|
|
||||||
handlers.push(handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
$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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,421 +0,0 @@
|
|||||||
import Noty from 'noty';
|
|
||||||
import security from '../service/security.js';
|
|
||||||
import configRepository from '../service/config.js';
|
|
||||||
import { baseClass, $app, API, $t } from './baseClass.js';
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
let webApiService = {};
|
|
||||||
/* eslint-enable no-unused-vars */
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t, _webApiService) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
webApiService = _webApiService;
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
API.isLoggedIn = false;
|
|
||||||
API.attemptingAutoLogin = false;
|
|
||||||
API.autoLoginAttempts = new Set();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {{ username: string, password: string }} params credential to login
|
|
||||||
* @returns {Promise<{origin: boolean, json: any, params}>}
|
|
||||||
*/
|
|
||||||
API.login = function (params) {
|
|
||||||
var { username, password, saveCredentials, cipher } = params;
|
|
||||||
username = encodeURIComponent(username);
|
|
||||||
password = encodeURIComponent(password);
|
|
||||||
var auth = btoa(`${username}:${password}`);
|
|
||||||
if (saveCredentials) {
|
|
||||||
delete params.saveCredentials;
|
|
||||||
if (cipher) {
|
|
||||||
params.password = cipher;
|
|
||||||
delete params.cipher;
|
|
||||||
}
|
|
||||||
$app.saveCredentials = params;
|
|
||||||
}
|
|
||||||
return this.call('auth/user', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Basic ${auth}`
|
|
||||||
}
|
|
||||||
}).then((json) => {
|
|
||||||
var args = {
|
|
||||||
json,
|
|
||||||
params,
|
|
||||||
origin: true
|
|
||||||
};
|
|
||||||
if (
|
|
||||||
json.requiresTwoFactorAuth &&
|
|
||||||
json.requiresTwoFactorAuth.includes('emailOtp')
|
|
||||||
) {
|
|
||||||
this.$emit('USER:EMAILOTP', args);
|
|
||||||
} else if (json.requiresTwoFactorAuth) {
|
|
||||||
this.$emit('USER:2FA', args);
|
|
||||||
} else {
|
|
||||||
this.$emit('USER:CURRENT', args);
|
|
||||||
}
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {{ code: string }} params One-time password
|
|
||||||
* @returns {Promise<{json: any, params}>}
|
|
||||||
*/
|
|
||||||
API.verifyOTP = function (params) {
|
|
||||||
return this.call('auth/twofactorauth/otp/verify', {
|
|
||||||
method: 'POST',
|
|
||||||
params
|
|
||||||
}).then((json) => {
|
|
||||||
var args = {
|
|
||||||
json,
|
|
||||||
params
|
|
||||||
};
|
|
||||||
this.$emit('OTP', args);
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {{ code: string }} params One-time token
|
|
||||||
* @returns {Promise<{json: any, params}>}
|
|
||||||
*/
|
|
||||||
API.verifyTOTP = function (params) {
|
|
||||||
return this.call('auth/twofactorauth/totp/verify', {
|
|
||||||
method: 'POST',
|
|
||||||
params
|
|
||||||
}).then((json) => {
|
|
||||||
var args = {
|
|
||||||
json,
|
|
||||||
params
|
|
||||||
};
|
|
||||||
this.$emit('TOTP', args);
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {{ code: string }} params One-time token
|
|
||||||
* @returns {Promise<{json: any, params}>}
|
|
||||||
*/
|
|
||||||
API.verifyEmailOTP = function (params) {
|
|
||||||
return this.call('auth/twofactorauth/emailotp/verify', {
|
|
||||||
method: 'POST',
|
|
||||||
params
|
|
||||||
}).then((json) => {
|
|
||||||
var args = {
|
|
||||||
json,
|
|
||||||
params
|
|
||||||
};
|
|
||||||
this.$emit('EMAILOTP', args);
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
API.$on('AUTOLOGIN', function () {
|
|
||||||
if (this.attemptingAutoLogin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.attemptingAutoLogin = true;
|
|
||||||
var user =
|
|
||||||
$app.loginForm.savedCredentials[
|
|
||||||
$app.loginForm.lastUserLoggedIn
|
|
||||||
];
|
|
||||||
if (typeof user === 'undefined') {
|
|
||||||
this.attemptingAutoLogin = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($app.enablePrimaryPassword) {
|
|
||||||
console.error(
|
|
||||||
'Primary password is enabled, this disables auto login.'
|
|
||||||
);
|
|
||||||
this.attemptingAutoLogin = false;
|
|
||||||
this.logout();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var attemptsInLastHour = Array.from(this.autoLoginAttempts).filter(
|
|
||||||
(timestamp) => timestamp > new Date().getTime() - 3600000
|
|
||||||
).length;
|
|
||||||
if (attemptsInLastHour >= 3) {
|
|
||||||
console.error(
|
|
||||||
'More than 3 auto login attempts within the past hour, logging out instead of attempting auto login.'
|
|
||||||
);
|
|
||||||
this.attemptingAutoLogin = false;
|
|
||||||
this.logout();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.autoLoginAttempts.add(new Date().getTime());
|
|
||||||
$app.relogin(user)
|
|
||||||
.then(() => {
|
|
||||||
if (this.errorNoty) {
|
|
||||||
this.errorNoty.close();
|
|
||||||
}
|
|
||||||
this.errorNoty = new Noty({
|
|
||||||
type: 'success',
|
|
||||||
text: 'Automatically logged in.'
|
|
||||||
}).show();
|
|
||||||
console.log('Automatically logged in.');
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
if (this.errorNoty) {
|
|
||||||
this.errorNoty.close();
|
|
||||||
}
|
|
||||||
this.errorNoty = new Noty({
|
|
||||||
type: 'error',
|
|
||||||
text: 'Failed to login automatically.'
|
|
||||||
}).show();
|
|
||||||
console.error('Failed to login automatically.', err);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
if (!navigator.onLine) {
|
|
||||||
this.errorNoty = new Noty({
|
|
||||||
type: 'error',
|
|
||||||
text: `You're offline.`
|
|
||||||
}).show();
|
|
||||||
console.error(`You're offline.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
API.$on('USER:CURRENT', function () {
|
|
||||||
this.attemptingAutoLogin = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
API.$on('LOGOUT', function () {
|
|
||||||
this.attemptingAutoLogin = false;
|
|
||||||
this.autoLoginAttempts.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
API.logout = function () {
|
|
||||||
this.$emit('LOGOUT');
|
|
||||||
// return this.call('logout', {
|
|
||||||
// method: 'PUT'
|
|
||||||
// }).finally(() => {
|
|
||||||
// this.$emit('LOGOUT');
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {
|
|
||||||
loginForm: {
|
|
||||||
loading: true,
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
endpoint: '',
|
|
||||||
websocket: '',
|
|
||||||
saveCredentials: false,
|
|
||||||
savedCredentials: {},
|
|
||||||
lastUserLoggedIn: '',
|
|
||||||
rules: {
|
|
||||||
username: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
trigger: 'blur'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
password: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
trigger: 'blur'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
async relogin(user) {
|
|
||||||
var { loginParmas } = user;
|
|
||||||
if (user.cookies) {
|
|
||||||
await webApiService.setCookies(user.cookies);
|
|
||||||
}
|
|
||||||
this.loginForm.lastUserLoggedIn = user.user.id; // for resend email 2fa
|
|
||||||
if (loginParmas.endpoint) {
|
|
||||||
API.endpointDomain = loginParmas.endpoint;
|
|
||||||
API.websocketDomain = loginParmas.websocket;
|
|
||||||
} else {
|
|
||||||
API.endpointDomain = API.endpointDomainVrchat;
|
|
||||||
API.websocketDomain = API.websocketDomainVrchat;
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.loginForm.loading = true;
|
|
||||||
if (this.enablePrimaryPassword) {
|
|
||||||
this.checkPrimaryPassword(loginParmas)
|
|
||||||
.then((pwd) => {
|
|
||||||
return API.getConfig()
|
|
||||||
.catch((err) => {
|
|
||||||
reject(err);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
API.login({
|
|
||||||
username: loginParmas.username,
|
|
||||||
password: pwd,
|
|
||||||
cipher: loginParmas.password,
|
|
||||||
endpoint: loginParmas.endpoint,
|
|
||||||
websocket: loginParmas.websocket
|
|
||||||
})
|
|
||||||
.catch((err2) => {
|
|
||||||
// API.logout();
|
|
||||||
reject(err2);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((_) => {
|
|
||||||
this.$message({
|
|
||||||
message: 'Incorrect primary password',
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
reject(_);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
API.getConfig()
|
|
||||||
.catch((err) => {
|
|
||||||
reject(err);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
API.login({
|
|
||||||
username: loginParmas.username,
|
|
||||||
password: loginParmas.password,
|
|
||||||
endpoint: loginParmas.endpoint,
|
|
||||||
websocket: loginParmas.websocket
|
|
||||||
})
|
|
||||||
.catch((err2) => {
|
|
||||||
API.logout();
|
|
||||||
reject(err2);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).finally(() => (this.loginForm.loading = false));
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteSavedLogin(userId) {
|
|
||||||
var savedCredentials = JSON.parse(
|
|
||||||
await configRepository.getString('savedCredentials')
|
|
||||||
);
|
|
||||||
delete savedCredentials[userId];
|
|
||||||
// Disable primary password when no account is available.
|
|
||||||
if (Object.keys(savedCredentials).length === 0) {
|
|
||||||
this.enablePrimaryPassword = false;
|
|
||||||
await configRepository.setBool('enablePrimaryPassword', false);
|
|
||||||
}
|
|
||||||
this.loginForm.savedCredentials = savedCredentials;
|
|
||||||
var jsonCredentials = JSON.stringify(savedCredentials);
|
|
||||||
await configRepository.setString(
|
|
||||||
'savedCredentials',
|
|
||||||
jsonCredentials
|
|
||||||
);
|
|
||||||
new Noty({
|
|
||||||
type: 'success',
|
|
||||||
text: 'Account removed.'
|
|
||||||
}).show();
|
|
||||||
},
|
|
||||||
|
|
||||||
async login() {
|
|
||||||
await webApiService.clearCookies();
|
|
||||||
if (!this.loginForm.loading) {
|
|
||||||
this.loginForm.loading = true;
|
|
||||||
if (this.loginForm.endpoint) {
|
|
||||||
API.endpointDomain = this.loginForm.endpoint;
|
|
||||||
API.websocketDomain = this.loginForm.websocket;
|
|
||||||
} else {
|
|
||||||
API.endpointDomain = API.endpointDomainVrchat;
|
|
||||||
API.websocketDomain = API.websocketDomainVrchat;
|
|
||||||
}
|
|
||||||
API.getConfig()
|
|
||||||
.catch((err) => {
|
|
||||||
this.loginForm.loading = false;
|
|
||||||
throw err;
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
if (
|
|
||||||
this.loginForm.saveCredentials &&
|
|
||||||
this.enablePrimaryPassword
|
|
||||||
) {
|
|
||||||
$app.$prompt(
|
|
||||||
$t('prompt.primary_password.description'),
|
|
||||||
$t('prompt.primary_password.header'),
|
|
||||||
{
|
|
||||||
inputType: 'password',
|
|
||||||
inputPattern: /[\s\S]{1,32}/
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(({ value }) => {
|
|
||||||
let saveCredential =
|
|
||||||
this.loginForm.savedCredentials[
|
|
||||||
Object.keys(
|
|
||||||
this.loginForm.savedCredentials
|
|
||||||
)[0]
|
|
||||||
];
|
|
||||||
security
|
|
||||||
.decrypt(
|
|
||||||
saveCredential.loginParmas.password,
|
|
||||||
value
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
security
|
|
||||||
.encrypt(
|
|
||||||
this.loginForm.password,
|
|
||||||
value
|
|
||||||
)
|
|
||||||
.then((pwd) => {
|
|
||||||
API.login({
|
|
||||||
username:
|
|
||||||
this.loginForm
|
|
||||||
.username,
|
|
||||||
password:
|
|
||||||
this.loginForm
|
|
||||||
.password,
|
|
||||||
endpoint:
|
|
||||||
this.loginForm
|
|
||||||
.endpoint,
|
|
||||||
websocket:
|
|
||||||
this.loginForm
|
|
||||||
.websocket,
|
|
||||||
saveCredentials:
|
|
||||||
this.loginForm
|
|
||||||
.saveCredentials,
|
|
||||||
cipher: pwd
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.loginForm.loading = false;
|
|
||||||
});
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
API.login({
|
|
||||||
username: this.loginForm.username,
|
|
||||||
password: this.loginForm.password,
|
|
||||||
endpoint: this.loginForm.endpoint,
|
|
||||||
websocket: this.loginForm.websocket,
|
|
||||||
saveCredentials: this.loginForm.saveCredentials
|
|
||||||
}).finally(() => {
|
|
||||||
this.loginForm.loading = false;
|
|
||||||
});
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
logout() {
|
|
||||||
this.$confirm('Continue? Logout', 'Confirm', {
|
|
||||||
confirmButtonText: 'Confirm',
|
|
||||||
cancelButtonText: 'Cancel',
|
|
||||||
type: 'info',
|
|
||||||
callback: (action) => {
|
|
||||||
if (action === 'confirm') {
|
|
||||||
API.logout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,402 +0,0 @@
|
|||||||
import Noty from 'noty';
|
|
||||||
import { baseClass, $app, API, $t } from './baseClass.js';
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
let webApiService = {};
|
|
||||||
/* eslint-enable no-unused-vars */
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t, _webApiService) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
webApiService = _webApiService;
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
API.cachedConfig = {};
|
|
||||||
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';
|
|
||||||
|
|
||||||
API.call = function (endpoint, options) {
|
|
||||||
var init = {
|
|
||||||
url: `${API.endpointDomain}/${endpoint}`,
|
|
||||||
method: 'GET',
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
var { params } = init;
|
|
||||||
if (init.method === 'GET') {
|
|
||||||
// don't retry recent 404/403
|
|
||||||
if (this.failedGetRequests.has(endpoint)) {
|
|
||||||
var lastRun = this.failedGetRequests.get(endpoint);
|
|
||||||
if (lastRun >= Date.now() - 900000) {
|
|
||||||
// 15mins
|
|
||||||
throw new Error(
|
|
||||||
`${$t('api.error.message.403_404_bailing_request')}, ${endpoint}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.failedGetRequests.delete(endpoint);
|
|
||||||
}
|
|
||||||
// transform body to url
|
|
||||||
if (params === Object(params)) {
|
|
||||||
var url = new URL(init.url);
|
|
||||||
var { searchParams } = url;
|
|
||||||
for (var key in params) {
|
|
||||||
searchParams.set(key, params[key]);
|
|
||||||
}
|
|
||||||
init.url = url.toString();
|
|
||||||
}
|
|
||||||
// merge requests
|
|
||||||
var req = this.pendingGetRequests.get(init.url);
|
|
||||||
if (typeof req !== 'undefined') {
|
|
||||||
if (req.time >= Date.now() - 10000) {
|
|
||||||
// 10s
|
|
||||||
return req.req;
|
|
||||||
}
|
|
||||||
this.pendingGetRequests.delete(init.url);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
init.uploadImage ||
|
|
||||||
init.uploadFilePUT ||
|
|
||||||
init.uploadImageLegacy
|
|
||||||
) {
|
|
||||||
// nothing
|
|
||||||
} else {
|
|
||||||
init.headers = {
|
|
||||||
'Content-Type': 'application/json;charset=utf-8',
|
|
||||||
...init.headers
|
|
||||||
};
|
|
||||||
init.body =
|
|
||||||
params === Object(params) ? JSON.stringify(params) : '{}';
|
|
||||||
}
|
|
||||||
var req = webApiService
|
|
||||||
.execute(init)
|
|
||||||
.catch((err) => {
|
|
||||||
this.$throw(0, err, endpoint);
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (!response.data) {
|
|
||||||
if ($app.debugWebRequests) {
|
|
||||||
console.log(init, response);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
response.data = JSON.parse(response.data);
|
|
||||||
if ($app.debugWebRequests) {
|
|
||||||
console.log(init, response.data);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
} catch (e) {}
|
|
||||||
if (response.status === 200) {
|
|
||||||
this.$throw(
|
|
||||||
0,
|
|
||||||
$t('api.error.message.invalid_json_response'),
|
|
||||||
endpoint
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
response.status === 429 &&
|
|
||||||
init.url.endsWith('/instances/groups')
|
|
||||||
) {
|
|
||||||
$app.nextGroupInstanceRefresh = 120; // 1min
|
|
||||||
throw new Error(
|
|
||||||
`${response.status}: rate limited ${endpoint}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (response.status === 504 || response.status === 502) {
|
|
||||||
// ignore expected API errors
|
|
||||||
throw new Error(
|
|
||||||
`${response.status}: ${response.data} ${endpoint}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.$throw(response.status, endpoint);
|
|
||||||
return {};
|
|
||||||
})
|
|
||||||
.then(({ data, status }) => {
|
|
||||||
if (status === 200) {
|
|
||||||
if (!data) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
var text = '';
|
|
||||||
if (data.success === Object(data.success)) {
|
|
||||||
text = data.success.message;
|
|
||||||
} else if (data.OK === String(data.OK)) {
|
|
||||||
text = data.OK;
|
|
||||||
}
|
|
||||||
if (text) {
|
|
||||||
new Noty({
|
|
||||||
type: 'success',
|
|
||||||
text: $app.escapeTag(text)
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
status === 401 &&
|
|
||||||
data.error.message === '"Missing Credentials"'
|
|
||||||
) {
|
|
||||||
this.$emit('AUTOLOGIN');
|
|
||||||
throw new Error(
|
|
||||||
`401 ${$t('api.error.message.missing_credentials')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
status === 401 &&
|
|
||||||
data.error.message === '"Unauthorized"' &&
|
|
||||||
endpoint !== 'auth/user'
|
|
||||||
) {
|
|
||||||
// trigger 2FA dialog
|
|
||||||
if (!$app.twoFactorAuthDialogVisible) {
|
|
||||||
$app.API.getCurrentUser();
|
|
||||||
}
|
|
||||||
throw new Error(`401 ${$t('api.status_code.401')}`);
|
|
||||||
}
|
|
||||||
if (status === 403 && endpoint === 'config') {
|
|
||||||
$app.$alert(
|
|
||||||
$t('api.error.message.vpn_in_use'),
|
|
||||||
`403 ${$t('api.error.message.login_error')}`
|
|
||||||
);
|
|
||||||
this.logout();
|
|
||||||
throw new Error(`403 ${endpoint}`);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
init.method === 'GET' &&
|
|
||||||
status === 404 &&
|
|
||||||
endpoint.startsWith('avatars/')
|
|
||||||
) {
|
|
||||||
$app.$message({
|
|
||||||
message: $t(
|
|
||||||
'message.api_handler.avatar_private_or_deleted'
|
|
||||||
),
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
$app.avatarDialog.visible = false;
|
|
||||||
throw new Error(
|
|
||||||
`404: ${data.error.message} ${endpoint}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
status === 404 &&
|
|
||||||
endpoint.endsWith('/persist/exists')
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
init.method === 'GET' &&
|
|
||||||
(status === 404 || status === 403) &&
|
|
||||||
!endpoint.startsWith('auth/user')
|
|
||||||
) {
|
|
||||||
this.failedGetRequests.set(endpoint, Date.now());
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
init.method === 'GET' &&
|
|
||||||
status === 404 &&
|
|
||||||
endpoint.startsWith('users/') &&
|
|
||||||
endpoint.split('/').length - 1 === 1
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
`404: ${data.error.message} ${endpoint}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
status === 404 &&
|
|
||||||
endpoint.startsWith('invite/') &&
|
|
||||||
init.inviteId
|
|
||||||
) {
|
|
||||||
this.expireNotification(init.inviteId);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
status === 403 &&
|
|
||||||
endpoint.startsWith('invite/myself/to/')
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
`403: ${data.error.message} ${endpoint}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (data && data.error === Object(data.error)) {
|
|
||||||
this.$throw(
|
|
||||||
data.error.status_code || status,
|
|
||||||
data.error.message,
|
|
||||||
endpoint
|
|
||||||
);
|
|
||||||
} else if (data && typeof data.error === 'string') {
|
|
||||||
this.$throw(
|
|
||||||
data.status_code || status,
|
|
||||||
data.error,
|
|
||||||
endpoint
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.$throw(status, data, endpoint);
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
if (init.method === 'GET') {
|
|
||||||
req.finally(() => {
|
|
||||||
this.pendingGetRequests.delete(init.url);
|
|
||||||
});
|
|
||||||
this.pendingGetRequests.set(init.url, {
|
|
||||||
req,
|
|
||||||
time: Date.now()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return req;
|
|
||||||
};
|
|
||||||
|
|
||||||
// FIXME : extra를 없애줘
|
|
||||||
API.$throw = function (code, error, endpoint) {
|
|
||||||
var text = [];
|
|
||||||
if (code > 0) {
|
|
||||||
const status = this.statusCodes[code];
|
|
||||||
if (typeof status === 'undefined') {
|
|
||||||
text.push(`${code}`);
|
|
||||||
} else {
|
|
||||||
const codeText = $t(`api.status_code.${code}`);
|
|
||||||
text.push(`${code} ${codeText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof error !== 'undefined') {
|
|
||||||
text.push(
|
|
||||||
`${$t('api.error.message.error_message')}: ${typeof error === 'string' ? error : JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (typeof endpoint !== 'undefined') {
|
|
||||||
text.push(
|
|
||||||
`${$t('api.error.message.endpoint')}: "${typeof endpoint === 'string' ? endpoint : JSON.stringify(endpoint)}"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
text = text.map((s) => $app.escapeTag(s)).join('<br>');
|
|
||||||
if (text.length) {
|
|
||||||
if (this.errorNoty) {
|
|
||||||
this.errorNoty.close();
|
|
||||||
}
|
|
||||||
this.errorNoty = new Noty({
|
|
||||||
type: 'error',
|
|
||||||
text
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
throw new Error(text);
|
|
||||||
};
|
|
||||||
|
|
||||||
API.$bulk = function (options, args) {
|
|
||||||
if ('handle' in options) {
|
|
||||||
options.handle.call(this, args, options);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
args.json.length > 0 &&
|
|
||||||
((options.params.offset += args.json.length),
|
|
||||||
// eslint-disable-next-line no-nested-ternary
|
|
||||||
options.N > 0
|
|
||||||
? options.N > options.params.offset
|
|
||||||
: options.N < 0
|
|
||||||
? args.json.length
|
|
||||||
: options.params.n === args.json.length)
|
|
||||||
) {
|
|
||||||
this.bulk(options);
|
|
||||||
} else if ('done' in options) {
|
|
||||||
options.done.call(this, true, options);
|
|
||||||
}
|
|
||||||
return args;
|
|
||||||
};
|
|
||||||
|
|
||||||
API.bulk = function (options) {
|
|
||||||
// it's stupid, but I won't waste time on the 'this' context
|
|
||||||
// works, that's enough.
|
|
||||||
if (typeof options.fn === 'function') {
|
|
||||||
options
|
|
||||||
.fn(options.params)
|
|
||||||
.catch((err) => {
|
|
||||||
if ('done' in options) {
|
|
||||||
options.done.call(this, false, options);
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
})
|
|
||||||
.then((args) => this.$bulk(options, args));
|
|
||||||
} else {
|
|
||||||
this[options.fn](options.params)
|
|
||||||
.catch((err) => {
|
|
||||||
if ('done' in options) {
|
|
||||||
options.done.call(this, false, options);
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
})
|
|
||||||
.then((args) => this.$bulk(options, args));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
API.statusCodes = {
|
|
||||||
100: 'Continue',
|
|
||||||
101: 'Switching Protocols',
|
|
||||||
102: 'Processing',
|
|
||||||
103: 'Early Hints',
|
|
||||||
200: 'OK',
|
|
||||||
201: 'Created',
|
|
||||||
202: 'Accepted',
|
|
||||||
203: 'Non-Authoritative Information',
|
|
||||||
204: 'No Content',
|
|
||||||
205: 'Reset Content',
|
|
||||||
206: 'Partial Content',
|
|
||||||
207: 'Multi-Status',
|
|
||||||
208: 'Already Reported',
|
|
||||||
226: 'IM Used',
|
|
||||||
300: 'Multiple Choices',
|
|
||||||
301: 'Moved Permanently',
|
|
||||||
302: 'Found',
|
|
||||||
303: 'See Other',
|
|
||||||
304: 'Not Modified',
|
|
||||||
305: 'Use Proxy',
|
|
||||||
306: 'Switch Proxy',
|
|
||||||
307: 'Temporary Redirect',
|
|
||||||
308: 'Permanent Redirect',
|
|
||||||
400: 'Bad Request',
|
|
||||||
401: 'Unauthorized',
|
|
||||||
402: 'Payment Required',
|
|
||||||
403: 'Forbidden',
|
|
||||||
404: 'Not Found',
|
|
||||||
405: 'Method Not Allowed',
|
|
||||||
406: 'Not Acceptable',
|
|
||||||
407: 'Proxy Authentication Required',
|
|
||||||
408: 'Request Timeout',
|
|
||||||
409: 'Conflict',
|
|
||||||
410: 'Gone',
|
|
||||||
411: 'Length Required',
|
|
||||||
412: 'Precondition Failed',
|
|
||||||
413: 'Payload Too Large',
|
|
||||||
414: 'URI Too Long',
|
|
||||||
415: 'Unsupported Media Type',
|
|
||||||
416: 'Range Not Satisfiable',
|
|
||||||
417: 'Expectation Failed',
|
|
||||||
418: "I'm a teapot",
|
|
||||||
421: 'Misdirected Request',
|
|
||||||
422: 'Unprocessable Entity',
|
|
||||||
423: 'Locked',
|
|
||||||
424: 'Failed Dependency',
|
|
||||||
425: 'Too Early',
|
|
||||||
426: 'Upgrade Required',
|
|
||||||
428: 'Precondition Required',
|
|
||||||
429: 'Too Many Requests',
|
|
||||||
431: 'Request Header Fields Too Large',
|
|
||||||
451: 'Unavailable For Legal Reasons',
|
|
||||||
500: 'Internal Server Error',
|
|
||||||
501: 'Not Implemented',
|
|
||||||
502: 'Bad Gateway',
|
|
||||||
503: 'Service Unavailable',
|
|
||||||
504: 'Gateway Timeout',
|
|
||||||
505: 'HTTP Version Not Supported',
|
|
||||||
506: 'Variant Also Negotiates',
|
|
||||||
507: 'Insufficient Storage',
|
|
||||||
508: 'Loop Detected',
|
|
||||||
510: 'Not Extended',
|
|
||||||
511: 'Network Authentication Required',
|
|
||||||
// CloudFlare Error
|
|
||||||
520: 'Web server returns an unknown error',
|
|
||||||
521: 'Web server is down',
|
|
||||||
522: 'Connection timed out',
|
|
||||||
523: 'Origin is unreachable',
|
|
||||||
524: 'A timeout occurred',
|
|
||||||
525: 'SSL handshake failed',
|
|
||||||
526: 'Invalid SSL certificate',
|
|
||||||
527: 'Railgun Listener to origin error'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import $utils from './utils';
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
let $app = {};
|
|
||||||
let API = {};
|
|
||||||
let $t = {};
|
|
||||||
/* eslint-enable no-unused-vars */
|
|
||||||
|
|
||||||
class baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
$app = _app;
|
|
||||||
API = _API;
|
|
||||||
$t = _t;
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRef(_app) {
|
|
||||||
$app = _app;
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
_data = {};
|
|
||||||
|
|
||||||
_methods = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
export { baseClass, $app, API, $t, $utils };
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
import { isRealInstance, parseLocation } from '../composables/instance/utils';
|
|
||||||
import { $app, API, baseClass } from './baseClass.js';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
API.currentUser = {
|
|
||||||
$userColour: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
API.getCurrentUser = function () {
|
|
||||||
return this.call('auth/user', {
|
|
||||||
method: 'GET'
|
|
||||||
}).then((json) => {
|
|
||||||
var args = {
|
|
||||||
json,
|
|
||||||
fromGetCurrentUser: true
|
|
||||||
};
|
|
||||||
if (
|
|
||||||
json.requiresTwoFactorAuth &&
|
|
||||||
json.requiresTwoFactorAuth.includes('emailOtp')
|
|
||||||
) {
|
|
||||||
this.$emit('USER:EMAILOTP', args);
|
|
||||||
} else if (json.requiresTwoFactorAuth) {
|
|
||||||
this.$emit('USER:2FA', args);
|
|
||||||
} else {
|
|
||||||
if ($app.debugCurrentUserDiff) {
|
|
||||||
var ref = args.json;
|
|
||||||
var $ref = this.currentUser;
|
|
||||||
var props = {};
|
|
||||||
for (var prop in $ref) {
|
|
||||||
if ($ref[prop] !== Object($ref[prop])) {
|
|
||||||
props[prop] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var prop in ref) {
|
|
||||||
if (
|
|
||||||
Array.isArray(ref[prop]) &&
|
|
||||||
Array.isArray($ref[prop])
|
|
||||||
) {
|
|
||||||
if (!$app.arraysMatch(ref[prop], $ref[prop])) {
|
|
||||||
props[prop] = true;
|
|
||||||
}
|
|
||||||
} else if (ref[prop] !== Object(ref[prop])) {
|
|
||||||
props[prop] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var has = false;
|
|
||||||
for (var prop in props) {
|
|
||||||
var asis = $ref[prop];
|
|
||||||
var tobe = ref[prop];
|
|
||||||
if (asis === tobe) {
|
|
||||||
delete props[prop];
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
prop.startsWith('$') ||
|
|
||||||
prop === 'offlineFriends' ||
|
|
||||||
prop === 'onlineFriends' ||
|
|
||||||
prop === 'activeFriends'
|
|
||||||
) {
|
|
||||||
delete props[prop];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
props[prop] = [tobe, asis];
|
|
||||||
has = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (has) {
|
|
||||||
console.log('API.getCurrentUser diff', props);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$app.nextCurrentUserRefresh = 420; // 7mins
|
|
||||||
this.$emit('USER:CURRENT', args);
|
|
||||||
}
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
API.$on('USER:CURRENT', function (args) {
|
|
||||||
var { json } = args;
|
|
||||||
args.ref = this.applyCurrentUser(json);
|
|
||||||
|
|
||||||
// when isGameRunning use gameLog instead of API
|
|
||||||
var $location = parseLocation($app.lastLocation.location);
|
|
||||||
var $travelingLocation = parseLocation(
|
|
||||||
$app.lastLocationDestination
|
|
||||||
);
|
|
||||||
var location = $app.lastLocation.location;
|
|
||||||
var instanceId = $location.instanceId;
|
|
||||||
var worldId = $location.worldId;
|
|
||||||
var travelingToLocation = $app.lastLocationDestination;
|
|
||||||
var travelingToWorld = $travelingLocation.worldId;
|
|
||||||
var travelingToInstance = $travelingLocation.instanceId;
|
|
||||||
if (!$app.isGameRunning && json.presence) {
|
|
||||||
if (isRealInstance(json.presence.world)) {
|
|
||||||
location = `${json.presence.world}:${json.presence.instance}`;
|
|
||||||
} else {
|
|
||||||
location = json.presence.world;
|
|
||||||
}
|
|
||||||
if (isRealInstance(json.presence.travelingToWorld)) {
|
|
||||||
travelingToLocation = `${json.presence.travelingToWorld}:${json.presence.travelingToInstance}`;
|
|
||||||
} else {
|
|
||||||
travelingToLocation = json.presence.travelingToWorld;
|
|
||||||
}
|
|
||||||
instanceId = json.presence.instance;
|
|
||||||
worldId = json.presence.world;
|
|
||||||
travelingToInstance = json.presence.travelingToInstance;
|
|
||||||
travelingToWorld = json.presence.travelingToWorld;
|
|
||||||
}
|
|
||||||
this.applyUser({
|
|
||||||
allowAvatarCopying: json.allowAvatarCopying,
|
|
||||||
badges: json.badges,
|
|
||||||
bio: json.bio,
|
|
||||||
bioLinks: json.bioLinks,
|
|
||||||
currentAvatarImageUrl: json.currentAvatarImageUrl,
|
|
||||||
currentAvatarTags: json.currentAvatarTags,
|
|
||||||
currentAvatarThumbnailImageUrl:
|
|
||||||
json.currentAvatarThumbnailImageUrl,
|
|
||||||
date_joined: json.date_joined,
|
|
||||||
developerType: json.developerType,
|
|
||||||
displayName: json.displayName,
|
|
||||||
friendKey: json.friendKey,
|
|
||||||
// json.friendRequestStatus - missing from currentUser
|
|
||||||
id: json.id,
|
|
||||||
// instanceId - missing from currentUser
|
|
||||||
isFriend: json.isFriend,
|
|
||||||
last_activity: json.last_activity,
|
|
||||||
last_login: json.last_login,
|
|
||||||
last_mobile: json.last_mobile,
|
|
||||||
last_platform: json.last_platform,
|
|
||||||
// location - missing from currentUser
|
|
||||||
// platform - missing from currentUser
|
|
||||||
// note - missing from currentUser
|
|
||||||
profilePicOverride: json.profilePicOverride,
|
|
||||||
// profilePicOverrideThumbnail - missing from currentUser
|
|
||||||
pronouns: json.pronouns,
|
|
||||||
state: json.state,
|
|
||||||
status: json.status,
|
|
||||||
statusDescription: json.statusDescription,
|
|
||||||
tags: json.tags,
|
|
||||||
// travelingToInstance - missing from currentUser
|
|
||||||
// travelingToLocation - missing from currentUser
|
|
||||||
// travelingToWorld - missing from currentUser
|
|
||||||
userIcon: json.userIcon,
|
|
||||||
// worldId - missing from currentUser
|
|
||||||
fallbackAvatar: json.fallbackAvatar,
|
|
||||||
|
|
||||||
// Location from gameLog/presence
|
|
||||||
location,
|
|
||||||
instanceId,
|
|
||||||
worldId,
|
|
||||||
travelingToLocation,
|
|
||||||
travelingToInstance,
|
|
||||||
travelingToWorld,
|
|
||||||
|
|
||||||
// set VRCX online/offline timers
|
|
||||||
$online_for: this.currentUser.$online_for,
|
|
||||||
$offline_for: this.currentUser.$offline_for,
|
|
||||||
$location_at: this.currentUser.$location_at,
|
|
||||||
$travelingToTime: this.currentUser.$travelingToTime
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
API.applyCurrentUser = function (json) {
|
|
||||||
var ref = this.currentUser;
|
|
||||||
if (this.isLoggedIn) {
|
|
||||||
if (json.currentAvatar !== ref.currentAvatar) {
|
|
||||||
$app.addAvatarToHistory(json.currentAvatar);
|
|
||||||
if ($app.isGameRunning) {
|
|
||||||
$app.addAvatarWearTime(ref.currentAvatar);
|
|
||||||
ref.$previousAvatarSwapTime = Date.now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Object.assign(ref, json);
|
|
||||||
if (ref.homeLocation !== ref.$homeLocation.tag) {
|
|
||||||
ref.$homeLocation = parseLocation(ref.homeLocation);
|
|
||||||
// apply home location name to user dialog
|
|
||||||
if (
|
|
||||||
$app.userDialog.visible &&
|
|
||||||
$app.userDialog.id === ref.id
|
|
||||||
) {
|
|
||||||
$app.getWorldName(API.currentUser.homeLocation).then(
|
|
||||||
(worldName) => {
|
|
||||||
$app.userDialog.$homeLocationName = worldName;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ref.$isVRCPlus = ref.tags.includes('system_supporter');
|
|
||||||
this.applyUserTrustLevel(ref);
|
|
||||||
this.applyUserLanguage(ref);
|
|
||||||
this.applyPresenceLocation(ref);
|
|
||||||
this.applyQueuedInstance(ref.queuedInstance);
|
|
||||||
this.applyPresenceGroups(ref);
|
|
||||||
} else {
|
|
||||||
ref = {
|
|
||||||
acceptedPrivacyVersion: 0,
|
|
||||||
acceptedTOSVersion: 0,
|
|
||||||
accountDeletionDate: null,
|
|
||||||
accountDeletionLog: null,
|
|
||||||
activeFriends: [],
|
|
||||||
ageVerificationStatus: '',
|
|
||||||
ageVerified: false,
|
|
||||||
allowAvatarCopying: false,
|
|
||||||
badges: [],
|
|
||||||
bio: '',
|
|
||||||
bioLinks: [],
|
|
||||||
currentAvatar: '',
|
|
||||||
currentAvatarImageUrl: '',
|
|
||||||
currentAvatarTags: [],
|
|
||||||
currentAvatarThumbnailImageUrl: '',
|
|
||||||
date_joined: '',
|
|
||||||
developerType: '',
|
|
||||||
displayName: '',
|
|
||||||
emailVerified: false,
|
|
||||||
fallbackAvatar: '',
|
|
||||||
friendGroupNames: [],
|
|
||||||
friendKey: '',
|
|
||||||
friends: [],
|
|
||||||
googleId: '',
|
|
||||||
hasBirthday: false,
|
|
||||||
hasEmail: false,
|
|
||||||
hasLoggedInFromClient: false,
|
|
||||||
hasPendingEmail: false,
|
|
||||||
hideContentFilterSettings: false,
|
|
||||||
homeLocation: '',
|
|
||||||
id: '',
|
|
||||||
isAdult: true,
|
|
||||||
isBoopingEnabled: false,
|
|
||||||
isFriend: false,
|
|
||||||
last_activity: '',
|
|
||||||
last_login: '',
|
|
||||||
last_mobile: null,
|
|
||||||
last_platform: '',
|
|
||||||
obfuscatedEmail: '',
|
|
||||||
obfuscatedPendingEmail: '',
|
|
||||||
oculusId: '',
|
|
||||||
offlineFriends: [],
|
|
||||||
onlineFriends: [],
|
|
||||||
pastDisplayNames: [],
|
|
||||||
picoId: '',
|
|
||||||
presence: {
|
|
||||||
avatarThumbnail: '',
|
|
||||||
currentAvatarTags: '',
|
|
||||||
displayName: '',
|
|
||||||
groups: [],
|
|
||||||
id: '',
|
|
||||||
instance: '',
|
|
||||||
instanceType: '',
|
|
||||||
platform: '',
|
|
||||||
profilePicOverride: '',
|
|
||||||
status: '',
|
|
||||||
travelingToInstance: '',
|
|
||||||
travelingToWorld: '',
|
|
||||||
userIcon: '',
|
|
||||||
world: '',
|
|
||||||
...json.presence
|
|
||||||
},
|
|
||||||
profilePicOverride: '',
|
|
||||||
pronouns: '',
|
|
||||||
queuedInstance: '',
|
|
||||||
state: '',
|
|
||||||
status: '',
|
|
||||||
statusDescription: '',
|
|
||||||
statusFirstTime: false,
|
|
||||||
statusHistory: [],
|
|
||||||
steamDetails: {},
|
|
||||||
steamId: '',
|
|
||||||
tags: [],
|
|
||||||
twoFactorAuthEnabled: false,
|
|
||||||
twoFactorAuthEnabledDate: null,
|
|
||||||
unsubscribe: false,
|
|
||||||
updated_at: '',
|
|
||||||
userIcon: '',
|
|
||||||
userLanguage: '',
|
|
||||||
userLanguageCode: '',
|
|
||||||
username: '',
|
|
||||||
viveId: '',
|
|
||||||
// VRCX
|
|
||||||
$online_for: Date.now(),
|
|
||||||
$offline_for: '',
|
|
||||||
$location_at: Date.now(),
|
|
||||||
$travelingToTime: Date.now(),
|
|
||||||
$previousAvatarSwapTime: '',
|
|
||||||
$homeLocation: {},
|
|
||||||
$isVRCPlus: false,
|
|
||||||
$isModerator: false,
|
|
||||||
$isTroll: false,
|
|
||||||
$isProbableTroll: false,
|
|
||||||
$trustLevel: 'Visitor',
|
|
||||||
$trustClass: 'x-tag-untrusted',
|
|
||||||
$userColour: '',
|
|
||||||
$trustSortNum: 1,
|
|
||||||
$languages: [],
|
|
||||||
$locationTag: '',
|
|
||||||
$travelingToLocation: '',
|
|
||||||
...json
|
|
||||||
};
|
|
||||||
if ($app.isGameRunning) {
|
|
||||||
ref.$previousAvatarSwapTime = Date.now();
|
|
||||||
}
|
|
||||||
ref.$homeLocation = parseLocation(ref.homeLocation);
|
|
||||||
ref.$isVRCPlus = ref.tags.includes('system_supporter');
|
|
||||||
this.applyUserTrustLevel(ref);
|
|
||||||
this.applyUserLanguage(ref);
|
|
||||||
this.applyPresenceLocation(ref);
|
|
||||||
this.applyPresenceGroups(ref);
|
|
||||||
this.currentUser = ref;
|
|
||||||
this.isLoggedIn = true;
|
|
||||||
this.$emit('LOGIN', {
|
|
||||||
json,
|
|
||||||
ref
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return ref;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {};
|
|
||||||
|
|
||||||
_methods = {};
|
|
||||||
}
|
|
||||||
@@ -1,291 +0,0 @@
|
|||||||
import { worldRequest } from '../api';
|
|
||||||
import { parseLocation } from '../composables/instance/utils';
|
|
||||||
import { getLaunchURL } from '../composables/shared/utils';
|
|
||||||
import configRepository from '../service/config.js';
|
|
||||||
import { API, baseClass } from './baseClass.js';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {
|
|
||||||
isDiscordActive: false,
|
|
||||||
discordActive: false,
|
|
||||||
discordInstance: true,
|
|
||||||
discordJoinButton: false,
|
|
||||||
discordHideInvite: true,
|
|
||||||
discordHideImage: false
|
|
||||||
};
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
updateDiscord() {
|
|
||||||
var currentLocation = this.lastLocation.location;
|
|
||||||
var timeStamp = this.lastLocation.date;
|
|
||||||
if (this.lastLocation.location === 'traveling') {
|
|
||||||
currentLocation = this.lastLocationDestination;
|
|
||||||
timeStamp = this.lastLocationDestinationTime;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!this.discordActive ||
|
|
||||||
(!this.isGameRunning && !this.gameLogDisabled) ||
|
|
||||||
(!currentLocation && !this.lastLocation$.tag)
|
|
||||||
) {
|
|
||||||
this.setDiscordActive(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setDiscordActive(true);
|
|
||||||
var L = this.lastLocation$;
|
|
||||||
if (currentLocation !== this.lastLocation$.tag) {
|
|
||||||
Discord.SetTimestamps(timeStamp, 0);
|
|
||||||
L = parseLocation(currentLocation);
|
|
||||||
L.worldName = '';
|
|
||||||
L.thumbnailImageUrl = '';
|
|
||||||
L.worldCapacity = 0;
|
|
||||||
L.joinUrl = '';
|
|
||||||
L.accessName = '';
|
|
||||||
if (L.worldId) {
|
|
||||||
var ref = API.cachedWorlds.get(L.worldId);
|
|
||||||
if (ref) {
|
|
||||||
L.worldName = ref.name;
|
|
||||||
L.thumbnailImageUrl = ref.thumbnailImageUrl;
|
|
||||||
L.worldCapacity = ref.capacity;
|
|
||||||
} else {
|
|
||||||
worldRequest
|
|
||||||
.getWorld({
|
|
||||||
worldId: L.worldId
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
L.worldName = args.ref.name;
|
|
||||||
L.thumbnailImageUrl =
|
|
||||||
args.ref.thumbnailImageUrl;
|
|
||||||
L.worldCapacity = args.ref.capacity;
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.isGameNoVR) {
|
|
||||||
var platform = 'Desktop';
|
|
||||||
} else {
|
|
||||||
var platform = 'VR';
|
|
||||||
}
|
|
||||||
var groupAccessType = '';
|
|
||||||
if (L.groupAccessType) {
|
|
||||||
if (L.groupAccessType === 'public') {
|
|
||||||
groupAccessType = 'Public';
|
|
||||||
} else if (L.groupAccessType === 'plus') {
|
|
||||||
groupAccessType = 'Plus';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (L.accessType) {
|
|
||||||
case 'public':
|
|
||||||
L.joinUrl = getLaunchURL(L);
|
|
||||||
L.accessName = `Public #${L.instanceName} (${platform})`;
|
|
||||||
break;
|
|
||||||
case 'invite+':
|
|
||||||
L.accessName = `Invite+ #${L.instanceName} (${platform})`;
|
|
||||||
break;
|
|
||||||
case 'invite':
|
|
||||||
L.accessName = `Invite #${L.instanceName} (${platform})`;
|
|
||||||
break;
|
|
||||||
case 'friends':
|
|
||||||
L.accessName = `Friends #${L.instanceName} (${platform})`;
|
|
||||||
break;
|
|
||||||
case 'friends+':
|
|
||||||
L.accessName = `Friends+ #${L.instanceName} (${platform})`;
|
|
||||||
break;
|
|
||||||
case 'group':
|
|
||||||
L.accessName = `Group #${L.instanceName} (${platform})`;
|
|
||||||
this.getGroupName(L.groupId).then((groupName) => {
|
|
||||||
if (groupName) {
|
|
||||||
L.accessName = `Group${groupAccessType}(${groupName}) #${L.instanceName} (${platform})`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.lastLocation$ = L;
|
|
||||||
}
|
|
||||||
var hidePrivate = false;
|
|
||||||
if (
|
|
||||||
this.discordHideInvite &&
|
|
||||||
(L.accessType === 'invite' ||
|
|
||||||
L.accessType === 'invite+' ||
|
|
||||||
L.groupAccessType === 'members')
|
|
||||||
) {
|
|
||||||
hidePrivate = true;
|
|
||||||
}
|
|
||||||
switch (API.currentUser.status) {
|
|
||||||
case 'active':
|
|
||||||
L.statusName = 'Online';
|
|
||||||
L.statusImage = 'active';
|
|
||||||
break;
|
|
||||||
case 'join me':
|
|
||||||
L.statusName = 'Join Me';
|
|
||||||
L.statusImage = 'joinme';
|
|
||||||
break;
|
|
||||||
case 'ask me':
|
|
||||||
L.statusName = 'Ask Me';
|
|
||||||
L.statusImage = 'askme';
|
|
||||||
if (this.discordHideInvite) {
|
|
||||||
hidePrivate = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'busy':
|
|
||||||
L.statusName = 'Do Not Disturb';
|
|
||||||
L.statusImage = 'busy';
|
|
||||||
hidePrivate = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
var appId = '883308884863901717';
|
|
||||||
var bigIcon = 'vrchat';
|
|
||||||
var partyId = `${L.worldId}:${L.instanceName}`;
|
|
||||||
var partySize = this.lastLocation.playerList.size;
|
|
||||||
var partyMaxSize = L.worldCapacity;
|
|
||||||
if (partySize > partyMaxSize) {
|
|
||||||
partyMaxSize = partySize;
|
|
||||||
}
|
|
||||||
var buttonText = 'Join';
|
|
||||||
var buttonUrl = L.joinUrl;
|
|
||||||
if (!this.discordJoinButton) {
|
|
||||||
buttonText = '';
|
|
||||||
buttonUrl = '';
|
|
||||||
}
|
|
||||||
if (!this.discordInstance) {
|
|
||||||
partySize = 0;
|
|
||||||
partyMaxSize = 0;
|
|
||||||
}
|
|
||||||
if (hidePrivate) {
|
|
||||||
partyId = '';
|
|
||||||
partySize = 0;
|
|
||||||
partyMaxSize = 0;
|
|
||||||
buttonText = '';
|
|
||||||
buttonUrl = '';
|
|
||||||
} else if (this.isRpcWorld(L.tag)) {
|
|
||||||
// custom world rpc
|
|
||||||
if (
|
|
||||||
L.worldId === 'wrld_f20326da-f1ac-45fc-a062-609723b097b1' ||
|
|
||||||
L.worldId === 'wrld_10e5e467-fc65-42ed-8957-f02cace1398c' ||
|
|
||||||
L.worldId === 'wrld_04899f23-e182-4a8d-b2c7-2c74c7c15534'
|
|
||||||
) {
|
|
||||||
appId = '784094509008551956';
|
|
||||||
bigIcon = 'pypy';
|
|
||||||
} else if (
|
|
||||||
L.worldId === 'wrld_42377cf1-c54f-45ed-8996-5875b0573a83' ||
|
|
||||||
L.worldId === 'wrld_dd6d2888-dbdc-47c2-bc98-3d631b2acd7c'
|
|
||||||
) {
|
|
||||||
appId = '846232616054030376';
|
|
||||||
bigIcon = 'vr_dancing';
|
|
||||||
} else if (
|
|
||||||
L.worldId === 'wrld_52bdcdab-11cd-4325-9655-0fb120846945' ||
|
|
||||||
L.worldId === 'wrld_2d40da63-8f1f-4011-8a9e-414eb8530acd'
|
|
||||||
) {
|
|
||||||
appId = '939473404808007731';
|
|
||||||
bigIcon = 'zuwa_zuwa_dance';
|
|
||||||
} else if (
|
|
||||||
L.worldId === 'wrld_74970324-58e8-4239-a17b-2c59dfdf00db' ||
|
|
||||||
L.worldId === 'wrld_db9d878f-6e76-4776-8bf2-15bcdd7fc445' ||
|
|
||||||
L.worldId === 'wrld_435bbf25-f34f-4b8b-82c6-cd809057eb8e' ||
|
|
||||||
L.worldId === 'wrld_f767d1c8-b249-4ecc-a56f-614e433682c8'
|
|
||||||
) {
|
|
||||||
appId = '968292722391785512';
|
|
||||||
bigIcon = 'ls_media';
|
|
||||||
} else if (
|
|
||||||
L.worldId === 'wrld_266523e8-9161-40da-acd0-6bd82e075833'
|
|
||||||
) {
|
|
||||||
appId = '1095440531821170820';
|
|
||||||
bigIcon = 'movie_and_chill';
|
|
||||||
}
|
|
||||||
if (this.nowPlaying.name) {
|
|
||||||
L.worldName = this.nowPlaying.name;
|
|
||||||
}
|
|
||||||
if (this.nowPlaying.playing) {
|
|
||||||
Discord.SetTimestamps(
|
|
||||||
Date.now(),
|
|
||||||
(this.nowPlaying.startTime -
|
|
||||||
this.nowPlaying.offset +
|
|
||||||
this.nowPlaying.length) *
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (!this.discordHideImage && L.thumbnailImageUrl) {
|
|
||||||
bigIcon = L.thumbnailImageUrl;
|
|
||||||
}
|
|
||||||
Discord.SetAssets(
|
|
||||||
bigIcon, // big icon
|
|
||||||
'Powered by VRCX', // big icon hover text
|
|
||||||
L.statusImage, // small icon
|
|
||||||
L.statusName, // small icon hover text
|
|
||||||
partyId, // party id
|
|
||||||
partySize, // party size
|
|
||||||
partyMaxSize, // party max size
|
|
||||||
buttonText, // button text
|
|
||||||
buttonUrl, // button url
|
|
||||||
appId // app id
|
|
||||||
);
|
|
||||||
// NOTE
|
|
||||||
// 글자 수가 짧으면 업데이트가 안된다..
|
|
||||||
if (L.worldName.length < 2) {
|
|
||||||
L.worldName += '\uFFA0'.repeat(2 - L.worldName.length);
|
|
||||||
}
|
|
||||||
if (hidePrivate) {
|
|
||||||
Discord.SetText('Private', '');
|
|
||||||
Discord.SetTimestamps(0, 0);
|
|
||||||
} else if (this.discordInstance) {
|
|
||||||
Discord.SetText(L.worldName, L.accessName);
|
|
||||||
} else {
|
|
||||||
Discord.SetText(L.worldName, '');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async setDiscordActive(active) {
|
|
||||||
if (active !== this.isDiscordActive) {
|
|
||||||
this.isDiscordActive = await Discord.SetActive(active);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveDiscordOption(configLabel = '') {
|
|
||||||
if (configLabel === 'discordActive') {
|
|
||||||
this.discordActive = !this.discordActive;
|
|
||||||
await configRepository.setBool(
|
|
||||||
'discordActive',
|
|
||||||
this.discordActive
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configLabel === 'discordInstance') {
|
|
||||||
this.discordInstance = !this.discordInstance;
|
|
||||||
await configRepository.setBool(
|
|
||||||
'discordInstance',
|
|
||||||
this.discordInstance
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configLabel === 'discordJoinButton') {
|
|
||||||
this.discordJoinButton = !this.discordJoinButton;
|
|
||||||
await configRepository.setBool(
|
|
||||||
'discordJoinButton',
|
|
||||||
this.discordJoinButton
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configLabel === 'discordHideInvite') {
|
|
||||||
this.discordHideInvite = !this.discordHideInvite;
|
|
||||||
await configRepository.setBool(
|
|
||||||
'discordHideInvite',
|
|
||||||
this.discordHideInvite
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (configLabel === 'discordHideImage') {
|
|
||||||
this.discordHideImage = !this.discordHideImage;
|
|
||||||
await configRepository.setBool(
|
|
||||||
'discordHideImage',
|
|
||||||
this.discordHideImage
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastLocation$.tag = '';
|
|
||||||
this.nextDiscordUpdate = 3;
|
|
||||||
this.updateDiscord();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
import configRepository from '../service/config.js';
|
|
||||||
import database from '../service/database.js';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {
|
|
||||||
feedTable: {
|
|
||||||
data: [],
|
|
||||||
search: '',
|
|
||||||
vip: false,
|
|
||||||
loading: false,
|
|
||||||
filter: [],
|
|
||||||
tableProps: {
|
|
||||||
stripe: true,
|
|
||||||
size: 'mini',
|
|
||||||
defaultSort: {
|
|
||||||
prop: 'created_at',
|
|
||||||
order: 'descending'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pageSize: 15,
|
|
||||||
paginationProps: {
|
|
||||||
small: true,
|
|
||||||
layout: 'sizes,prev,pager,next,total',
|
|
||||||
pageSizes: [10, 15, 20, 25, 50, 100]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
feedSessionTable: []
|
|
||||||
};
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
feedSearch(row) {
|
|
||||||
var value = this.feedTable.search.toUpperCase();
|
|
||||||
if (!value) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(value.startsWith('wrld_') || value.startsWith('grp_')) &&
|
|
||||||
String(row.location).toUpperCase().includes(value)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
switch (row.type) {
|
|
||||||
case 'GPS':
|
|
||||||
if (String(row.displayName).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (String(row.worldName).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
case 'Online':
|
|
||||||
if (String(row.displayName).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (String(row.worldName).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
case 'Offline':
|
|
||||||
if (String(row.displayName).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (String(row.worldName).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
case 'Status':
|
|
||||||
if (String(row.displayName).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (String(row.status).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
String(row.statusDescription)
|
|
||||||
.toUpperCase()
|
|
||||||
.includes(value)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
case 'Avatar':
|
|
||||||
if (String(row.displayName).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (String(row.avatarName).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
case 'Bio':
|
|
||||||
if (String(row.displayName).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (String(row.bio).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (String(row.previousBio).toUpperCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async feedTableLookup() {
|
|
||||||
await configRepository.setString(
|
|
||||||
'VRCX_feedTableFilters',
|
|
||||||
JSON.stringify(this.feedTable.filter)
|
|
||||||
);
|
|
||||||
await configRepository.setBool(
|
|
||||||
'VRCX_feedTableVIPFilter',
|
|
||||||
this.feedTable.vip
|
|
||||||
);
|
|
||||||
this.feedTable.loading = true;
|
|
||||||
var vipList = [];
|
|
||||||
if (this.feedTable.vip) {
|
|
||||||
vipList = Array.from(this.localFavoriteFriends.values());
|
|
||||||
}
|
|
||||||
this.feedTable.data = await database.lookupFeedDatabase(
|
|
||||||
this.feedTable.search,
|
|
||||||
this.feedTable.filter,
|
|
||||||
vipList
|
|
||||||
);
|
|
||||||
this.feedTable.loading = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
addFeed(feed) {
|
|
||||||
this.queueFeedNoty(feed);
|
|
||||||
this.feedSessionTable.push(feed);
|
|
||||||
this.updateSharedFeed(false);
|
|
||||||
if (
|
|
||||||
this.feedTable.filter.length > 0 &&
|
|
||||||
!this.feedTable.filter.includes(feed.type)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
this.feedTable.vip &&
|
|
||||||
!this.localFavoriteFriends.has(feed.userId)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.feedSearch(feed)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.feedTable.data.push(feed);
|
|
||||||
this.sweepFeed();
|
|
||||||
this.notifyMenu('feed');
|
|
||||||
},
|
|
||||||
|
|
||||||
sweepFeed() {
|
|
||||||
var { data } = this.feedTable;
|
|
||||||
var j = data.length;
|
|
||||||
if (j > this.maxTableSize) {
|
|
||||||
data.splice(0, j - this.maxTableSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
var date = new Date();
|
|
||||||
date.setDate(date.getDate() - 1); // 24 hour limit
|
|
||||||
var limit = date.toJSON();
|
|
||||||
var i = 0;
|
|
||||||
var k = this.feedSessionTable.length;
|
|
||||||
while (i < k && this.feedSessionTable[i].created_at < limit) {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
if (i === k) {
|
|
||||||
this.feedSessionTable = [];
|
|
||||||
} else if (i) {
|
|
||||||
this.feedSessionTable.splice(0, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,55 +0,0 @@
|
|||||||
import * as workerTimers from 'worker-timers';
|
|
||||||
import configRepository from '../service/config.js';
|
|
||||||
import database from '../service/database.js';
|
|
||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
import { inventoryRequest } from '../api';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
API.currentUserInventory = new Map();
|
|
||||||
API.$on('LOGIN', function () {
|
|
||||||
API.currentUserInventory.clear();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {
|
|
||||||
inventoryTable: []
|
|
||||||
};
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
async getInventory() {
|
|
||||||
this.inventoryTable = [];
|
|
||||||
API.currentUserInventory.clear();
|
|
||||||
var params = {
|
|
||||||
n: 100,
|
|
||||||
offset: 0,
|
|
||||||
order: 'newest'
|
|
||||||
};
|
|
||||||
this.galleryDialogInventoryLoading = true;
|
|
||||||
try {
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
params.offset = i * params.n;
|
|
||||||
const args =
|
|
||||||
await inventoryRequest.getInventoryItems(params);
|
|
||||||
for (const item of args.json.data) {
|
|
||||||
API.currentUserInventory.set(item.id, item);
|
|
||||||
if (!item.flags.includes('ugc')) {
|
|
||||||
this.inventoryTable.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (args.json.data.length === 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching inventory items:', error);
|
|
||||||
} finally {
|
|
||||||
this.galleryDialogInventoryLoading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
import { userRequest } from '../api';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
API.$on('CONFIG', function (args) {
|
|
||||||
var languages =
|
|
||||||
args.ref?.constants?.LANGUAGE?.SPOKEN_LANGUAGE_OPTIONS;
|
|
||||||
if (!languages) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$app.subsetOfLanguages = languages;
|
|
||||||
var data = [];
|
|
||||||
for (var key in languages) {
|
|
||||||
var value = languages[key];
|
|
||||||
data.push({
|
|
||||||
key,
|
|
||||||
value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$app.languageDialog.languages = data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {
|
|
||||||
subsetOfLanguages: [],
|
|
||||||
|
|
||||||
languageDialog: {
|
|
||||||
visible: false,
|
|
||||||
loading: false,
|
|
||||||
languageChoice: false,
|
|
||||||
languages: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_methods = {};
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
import database from '../service/database.js';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
_data = {
|
|
||||||
hideUserMemos: false
|
|
||||||
};
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
async migrateMemos() {
|
|
||||||
var json = JSON.parse(await VRCXStorage.GetAll());
|
|
||||||
for (var line in json) {
|
|
||||||
if (line.substring(0, 8) === 'memo_usr') {
|
|
||||||
var userId = line.substring(5);
|
|
||||||
var memo = json[line];
|
|
||||||
if (memo) {
|
|
||||||
await this.saveUserMemo(userId, memo);
|
|
||||||
VRCXStorage.Remove(`memo_${userId}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getUserMemo(userId) {
|
|
||||||
try {
|
|
||||||
return await database.getUserMemo(userId);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
return {
|
|
||||||
userId: '',
|
|
||||||
editedAt: '',
|
|
||||||
memo: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveUserMemo(id, memo) {
|
|
||||||
if (memo) {
|
|
||||||
await database.setUserMemo({
|
|
||||||
userId: id,
|
|
||||||
editedAt: new Date().toJSON(),
|
|
||||||
memo
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await database.deleteUserMemo(id);
|
|
||||||
}
|
|
||||||
var ref = this.friends.get(id);
|
|
||||||
if (ref) {
|
|
||||||
ref.memo = String(memo || '');
|
|
||||||
if (memo) {
|
|
||||||
var array = memo.split('\n');
|
|
||||||
ref.$nickName = array[0];
|
|
||||||
} else {
|
|
||||||
ref.$nickName = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getAllUserMemos() {
|
|
||||||
var memos = await database.getAllUserMemos();
|
|
||||||
memos.forEach((memo) => {
|
|
||||||
var ref = $app.friends.get(memo.userId);
|
|
||||||
if (typeof ref !== 'undefined') {
|
|
||||||
ref.memo = memo.memo;
|
|
||||||
ref.$nickName = '';
|
|
||||||
if (memo.memo) {
|
|
||||||
var array = memo.memo.split('\n');
|
|
||||||
ref.$nickName = array[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async getWorldMemo(worldId) {
|
|
||||||
try {
|
|
||||||
return await database.getWorldMemo(worldId);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
return {
|
|
||||||
worldId: '',
|
|
||||||
editedAt: '',
|
|
||||||
memo: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getAvatarMemo(avatarId) {
|
|
||||||
try {
|
|
||||||
return await database.getAvatarMemoDB(avatarId);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
return {
|
|
||||||
avatarId: '',
|
|
||||||
editedAt: '',
|
|
||||||
memo: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,574 +0,0 @@
|
|||||||
import * as workerTimers from 'worker-timers';
|
|
||||||
import configRepository from '../service/config.js';
|
|
||||||
import database from '../service/database.js';
|
|
||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
import { avatarRequest, favoriteRequest, worldRequest } from '../api';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
promptTOTP() {
|
|
||||||
if (this.twoFactorAuthDialogVisible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AppApi.FlashWindow();
|
|
||||||
this.twoFactorAuthDialogVisible = true;
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.totp.description'),
|
|
||||||
$t('prompt.totp.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
cancelButtonText: $t('prompt.totp.use_otp'),
|
|
||||||
confirmButtonText: $t('prompt.totp.verify'),
|
|
||||||
inputPlaceholder: $t('prompt.totp.input_placeholder'),
|
|
||||||
inputPattern: /^[0-9]{6}$/,
|
|
||||||
inputErrorMessage: $t('prompt.totp.input_error'),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (action === 'confirm') {
|
|
||||||
API.verifyTOTP({
|
|
||||||
code: instance.inputValue.trim()
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
$app.clearCookiesTryLogin();
|
|
||||||
throw err;
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
API.getCurrentUser();
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
} else if (action === 'cancel') {
|
|
||||||
this.promptOTP();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeClose: (action, instance, done) => {
|
|
||||||
this.twoFactorAuthDialogVisible = false;
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptOTP() {
|
|
||||||
if (this.twoFactorAuthDialogVisible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.twoFactorAuthDialogVisible = true;
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.otp.description'),
|
|
||||||
$t('prompt.otp.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
cancelButtonText: $t('prompt.otp.use_totp'),
|
|
||||||
confirmButtonText: $t('prompt.otp.verify'),
|
|
||||||
inputPlaceholder: $t('prompt.otp.input_placeholder'),
|
|
||||||
inputPattern: /^[a-z0-9]{4}-[a-z0-9]{4}$/,
|
|
||||||
inputErrorMessage: $t('prompt.otp.input_error'),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (action === 'confirm') {
|
|
||||||
API.verifyOTP({
|
|
||||||
code: instance.inputValue.trim()
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
$app.clearCookiesTryLogin();
|
|
||||||
throw err;
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
API.getCurrentUser();
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
} else if (action === 'cancel') {
|
|
||||||
this.promptTOTP();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeClose: (action, instance, done) => {
|
|
||||||
this.twoFactorAuthDialogVisible = false;
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptEmailOTP() {
|
|
||||||
if (this.twoFactorAuthDialogVisible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AppApi.FlashWindow();
|
|
||||||
this.twoFactorAuthDialogVisible = true;
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.email_otp.description'),
|
|
||||||
$t('prompt.email_otp.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
cancelButtonText: $t('prompt.email_otp.resend'),
|
|
||||||
confirmButtonText: $t('prompt.email_otp.verify'),
|
|
||||||
inputPlaceholder: $t('prompt.email_otp.input_placeholder'),
|
|
||||||
inputPattern: /^[0-9]{6}$/,
|
|
||||||
inputErrorMessage: $t('prompt.email_otp.input_error'),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (action === 'confirm') {
|
|
||||||
API.verifyEmailOTP({
|
|
||||||
code: instance.inputValue.trim()
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.promptEmailOTP();
|
|
||||||
throw err;
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
API.getCurrentUser();
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
} else if (action === 'cancel') {
|
|
||||||
this.resendEmail2fa();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeClose: (action, instance, done) => {
|
|
||||||
this.twoFactorAuthDialogVisible = false;
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptOmniDirectDialog() {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.direct_access_omni.description'),
|
|
||||||
$t('prompt.direct_access_omni.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.direct_access_omni.ok'),
|
|
||||||
cancelButtonText: $t('prompt.direct_access_omni.cancel'),
|
|
||||||
inputPattern: /\S+/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.direct_access_omni.input_error'
|
|
||||||
),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (action === 'confirm' && instance.inputValue) {
|
|
||||||
var input = instance.inputValue.trim();
|
|
||||||
if (!this.directAccessParse(input)) {
|
|
||||||
this.$message({
|
|
||||||
message: $t(
|
|
||||||
'prompt.direct_access_omni.message.error'
|
|
||||||
),
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptNotificationTimeout() {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.notification_timeout.description'),
|
|
||||||
$t('prompt.notification_timeout.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.notification_timeout.ok'),
|
|
||||||
cancelButtonText: $t('prompt.notification_timeout.cancel'),
|
|
||||||
inputValue: this.notificationTimeout / 1000,
|
|
||||||
inputPattern: /\d+$/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.notification_timeout.input_error'
|
|
||||||
),
|
|
||||||
callback: async (action, instance) => {
|
|
||||||
if (
|
|
||||||
action === 'confirm' &&
|
|
||||||
instance.inputValue &&
|
|
||||||
!isNaN(instance.inputValue)
|
|
||||||
) {
|
|
||||||
this.notificationTimeout = Math.trunc(
|
|
||||||
Number(instance.inputValue) * 1000
|
|
||||||
);
|
|
||||||
await configRepository.setString(
|
|
||||||
'VRCX_notificationTimeout',
|
|
||||||
this.notificationTimeout
|
|
||||||
);
|
|
||||||
this.updateVRConfigVars();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptPhotonOverlayMessageTimeout() {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.overlay_message_timeout.description'),
|
|
||||||
$t('prompt.overlay_message_timeout.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.overlay_message_timeout.ok'),
|
|
||||||
cancelButtonText: $t(
|
|
||||||
'prompt.overlay_message_timeout.cancel'
|
|
||||||
),
|
|
||||||
inputValue: this.photonOverlayMessageTimeout / 1000,
|
|
||||||
inputPattern: /\d+$/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.overlay_message_timeout.input_error'
|
|
||||||
),
|
|
||||||
callback: async (action, instance) => {
|
|
||||||
if (
|
|
||||||
action === 'confirm' &&
|
|
||||||
instance.inputValue &&
|
|
||||||
!isNaN(instance.inputValue)
|
|
||||||
) {
|
|
||||||
this.photonOverlayMessageTimeout = Math.trunc(
|
|
||||||
Number(instance.inputValue) * 1000
|
|
||||||
);
|
|
||||||
await configRepository.setString(
|
|
||||||
'VRCX_photonOverlayMessageTimeout',
|
|
||||||
this.photonOverlayMessageTimeout
|
|
||||||
);
|
|
||||||
this.updateVRConfigVars();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptRenameWorld(world) {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.rename_world.description'),
|
|
||||||
$t('prompt.rename_world.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.rename_world.ok'),
|
|
||||||
cancelButtonText: $t('prompt.rename_world.cancel'),
|
|
||||||
inputValue: world.ref.name,
|
|
||||||
inputErrorMessage: $t('prompt.rename_world.input_error'),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (
|
|
||||||
action === 'confirm' &&
|
|
||||||
instance.inputValue !== world.ref.name
|
|
||||||
) {
|
|
||||||
worldRequest
|
|
||||||
.saveWorld({
|
|
||||||
id: world.id,
|
|
||||||
name: instance.inputValue
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
this.$message({
|
|
||||||
message: $t(
|
|
||||||
'prompt.rename_world.message.success'
|
|
||||||
),
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptChangeWorldDescription(world) {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.change_world_description.description'),
|
|
||||||
$t('prompt.change_world_description.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.change_world_description.ok'),
|
|
||||||
cancelButtonText: $t(
|
|
||||||
'prompt.change_world_description.cancel'
|
|
||||||
),
|
|
||||||
inputValue: world.ref.description,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.change_world_description.input_error'
|
|
||||||
),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (
|
|
||||||
action === 'confirm' &&
|
|
||||||
instance.inputValue !== world.ref.description
|
|
||||||
) {
|
|
||||||
worldRequest
|
|
||||||
.saveWorld({
|
|
||||||
id: world.id,
|
|
||||||
description: instance.inputValue
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
this.$message({
|
|
||||||
message: $t(
|
|
||||||
'prompt.change_world_description.message.success'
|
|
||||||
),
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptChangeWorldCapacity(world) {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.change_world_capacity.description'),
|
|
||||||
$t('prompt.change_world_capacity.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.change_world_capacity.ok'),
|
|
||||||
cancelButtonText: $t('prompt.change_world_capacity.cancel'),
|
|
||||||
inputValue: world.ref.capacity,
|
|
||||||
inputPattern: /\d+$/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.change_world_capacity.input_error'
|
|
||||||
),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (
|
|
||||||
action === 'confirm' &&
|
|
||||||
instance.inputValue !== world.ref.capacity
|
|
||||||
) {
|
|
||||||
worldRequest
|
|
||||||
.saveWorld({
|
|
||||||
id: world.id,
|
|
||||||
capacity: instance.inputValue
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
this.$message({
|
|
||||||
message: $t(
|
|
||||||
'prompt.change_world_capacity.message.success'
|
|
||||||
),
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptChangeWorldRecommendedCapacity(world) {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.change_world_recommended_capacity.description'),
|
|
||||||
$t('prompt.change_world_recommended_capacity.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.change_world_capacity.ok'),
|
|
||||||
cancelButtonText: $t('prompt.change_world_capacity.cancel'),
|
|
||||||
inputValue: world.ref.recommendedCapacity,
|
|
||||||
inputPattern: /\d+$/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.change_world_recommended_capacity.input_error'
|
|
||||||
),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (
|
|
||||||
action === 'confirm' &&
|
|
||||||
instance.inputValue !==
|
|
||||||
world.ref.recommendedCapacity
|
|
||||||
) {
|
|
||||||
worldRequest
|
|
||||||
.saveWorld({
|
|
||||||
id: world.id,
|
|
||||||
recommendedCapacity: instance.inputValue
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
this.$message({
|
|
||||||
message: $t(
|
|
||||||
'prompt.change_world_recommended_capacity.message.success'
|
|
||||||
),
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptChangeWorldYouTubePreview(world) {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.change_world_preview.description'),
|
|
||||||
$t('prompt.change_world_preview.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.change_world_preview.ok'),
|
|
||||||
cancelButtonText: $t('prompt.change_world_preview.cancel'),
|
|
||||||
inputValue: world.ref.previewYoutubeId,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.change_world_preview.input_error'
|
|
||||||
),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (
|
|
||||||
action === 'confirm' &&
|
|
||||||
instance.inputValue !== world.ref.previewYoutubeId
|
|
||||||
) {
|
|
||||||
if (instance.inputValue.length > 11) {
|
|
||||||
try {
|
|
||||||
var url = new URL(instance.inputValue);
|
|
||||||
var id1 = url.pathname;
|
|
||||||
var id2 = url.searchParams.get('v');
|
|
||||||
if (id1 && id1.length === 12) {
|
|
||||||
instance.inputValue = id1.substring(
|
|
||||||
1,
|
|
||||||
12
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (id2 && id2.length === 11) {
|
|
||||||
instance.inputValue = id2;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
this.$message({
|
|
||||||
message: $t(
|
|
||||||
'prompt.change_world_preview.message.error'
|
|
||||||
),
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
instance.inputValue !==
|
|
||||||
world.ref.previewYoutubeId
|
|
||||||
) {
|
|
||||||
worldRequest
|
|
||||||
.saveWorld({
|
|
||||||
id: world.id,
|
|
||||||
previewYoutubeId: instance.inputValue
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
this.$message({
|
|
||||||
message: $t(
|
|
||||||
'prompt.change_world_preview.message.success'
|
|
||||||
),
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptMaxTableSizeDialog() {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.change_table_size.description'),
|
|
||||||
$t('prompt.change_table_size.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.change_table_size.save'),
|
|
||||||
cancelButtonText: $t('prompt.change_table_size.cancel'),
|
|
||||||
inputValue: this.maxTableSize,
|
|
||||||
inputPattern: /\d+$/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.change_table_size.input_error'
|
|
||||||
),
|
|
||||||
callback: async (action, instance) => {
|
|
||||||
if (action === 'confirm' && instance.inputValue) {
|
|
||||||
if (instance.inputValue > 10000) {
|
|
||||||
instance.inputValue = 10000;
|
|
||||||
}
|
|
||||||
this.maxTableSize = instance.inputValue;
|
|
||||||
await configRepository.setString(
|
|
||||||
'VRCX_maxTableSize',
|
|
||||||
this.maxTableSize
|
|
||||||
);
|
|
||||||
database.setmaxTableSize(this.maxTableSize);
|
|
||||||
this.feedTableLookup();
|
|
||||||
this.gameLogTableLookup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptProxySettings() {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.proxy_settings.description'),
|
|
||||||
$t('prompt.proxy_settings.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.proxy_settings.restart'),
|
|
||||||
cancelButtonText: $t('prompt.proxy_settings.close'),
|
|
||||||
inputValue: this.proxyServer,
|
|
||||||
inputPlaceholder: $t('prompt.proxy_settings.placeholder'),
|
|
||||||
callback: async (action, instance) => {
|
|
||||||
this.proxyServer = instance.inputValue;
|
|
||||||
await VRCXStorage.Set(
|
|
||||||
'VRCX_ProxyServer',
|
|
||||||
this.proxyServer
|
|
||||||
);
|
|
||||||
await VRCXStorage.Flush();
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
workerTimers.setTimeout(resolve, 100);
|
|
||||||
});
|
|
||||||
if (action === 'confirm') {
|
|
||||||
var isUpgrade = false;
|
|
||||||
this.restartVRCX(isUpgrade);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptPhotonLobbyTimeoutThreshold() {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.photon_lobby_timeout.description'),
|
|
||||||
$t('prompt.photon_lobby_timeout.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.photon_lobby_timeout.ok'),
|
|
||||||
cancelButtonText: $t('prompt.photon_lobby_timeout.cancel'),
|
|
||||||
inputValue: this.photonLobbyTimeoutThreshold / 1000,
|
|
||||||
inputPattern: /\d+$/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.photon_lobby_timeout.input_error'
|
|
||||||
),
|
|
||||||
callback: async (action, instance) => {
|
|
||||||
if (
|
|
||||||
action === 'confirm' &&
|
|
||||||
instance.inputValue &&
|
|
||||||
!isNaN(instance.inputValue)
|
|
||||||
) {
|
|
||||||
this.photonLobbyTimeoutThreshold = Math.trunc(
|
|
||||||
Number(instance.inputValue) * 1000
|
|
||||||
);
|
|
||||||
await configRepository.setString(
|
|
||||||
'VRCX_photonLobbyTimeoutThreshold',
|
|
||||||
this.photonLobbyTimeoutThreshold
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptAutoClearVRCXCacheFrequency() {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.auto_clear_cache.description'),
|
|
||||||
$t('prompt.auto_clear_cache.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.auto_clear_cache.ok'),
|
|
||||||
cancelButtonText: $t('prompt.auto_clear_cache.cancel'),
|
|
||||||
inputValue: this.clearVRCXCacheFrequency / 3600 / 2,
|
|
||||||
inputPattern: /\d+$/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.auto_clear_cache.input_error'
|
|
||||||
),
|
|
||||||
callback: async (action, instance) => {
|
|
||||||
if (
|
|
||||||
action === 'confirm' &&
|
|
||||||
instance.inputValue &&
|
|
||||||
!isNaN(instance.inputValue)
|
|
||||||
) {
|
|
||||||
this.clearVRCXCacheFrequency = Math.trunc(
|
|
||||||
Number(instance.inputValue) * 3600 * 2
|
|
||||||
);
|
|
||||||
await configRepository.setString(
|
|
||||||
'VRCX_clearVRCXCacheFrequency',
|
|
||||||
this.clearVRCXCacheFrequency
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
import * as workerTimers from 'worker-timers';
|
|
||||||
import configRepository from '../service/config.js';
|
|
||||||
import database from '../service/database.js';
|
|
||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
_data = {};
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
async tryRestoreFriendNumber() {
|
|
||||||
var lastUpdate = await configRepository.getString(
|
|
||||||
`VRCX_lastStoreTime_${API.currentUser.id}`
|
|
||||||
);
|
|
||||||
if (lastUpdate == -4) {
|
|
||||||
// this means the backup was already applied
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var status = false;
|
|
||||||
this.friendNumber = 0;
|
|
||||||
for (var ref of this.friendLog.values()) {
|
|
||||||
ref.friendNumber = 0;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (lastUpdate) {
|
|
||||||
// backup ready to try apply
|
|
||||||
status = await this.restoreFriendNumber();
|
|
||||||
}
|
|
||||||
// needs to be in reverse because we don't know the starting number
|
|
||||||
this.applyFriendLogFriendOrderInReverse();
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
// if (status) {
|
|
||||||
// this.$message({
|
|
||||||
// message: 'Friend order restored from backup',
|
|
||||||
// type: 'success',
|
|
||||||
// duration: 0,
|
|
||||||
// showClose: true
|
|
||||||
// });
|
|
||||||
// } else if (this.friendLogTable.data.length > 0) {
|
|
||||||
// this.$message({
|
|
||||||
// message:
|
|
||||||
// 'No backup found, friend order partially restored from friendLog',
|
|
||||||
// type: 'success',
|
|
||||||
// duration: 0,
|
|
||||||
// showClose: true
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
await configRepository.setString(
|
|
||||||
`VRCX_lastStoreTime_${API.currentUser.id}`,
|
|
||||||
-4
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
async restoreFriendNumber() {
|
|
||||||
var storedData = null;
|
|
||||||
try {
|
|
||||||
var data = await configRepository.getString(
|
|
||||||
`VRCX_friendOrder_${API.currentUser.id}`
|
|
||||||
);
|
|
||||||
if (data) {
|
|
||||||
var storedData = JSON.parse(data);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
if (!storedData || storedData.length === 0) {
|
|
||||||
var message = 'whomp whomp, no friend order backup found';
|
|
||||||
console.error(message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var friendLogTable = this.getFriendLogFriendOrder();
|
|
||||||
|
|
||||||
// for storedData
|
|
||||||
var machList = [];
|
|
||||||
for (var i = 0; i < Object.keys(storedData).length; i++) {
|
|
||||||
var key = Object.keys(storedData)[i];
|
|
||||||
var value = storedData[key];
|
|
||||||
var item = this.parseFriendOrderBackup(
|
|
||||||
friendLogTable,
|
|
||||||
key,
|
|
||||||
value
|
|
||||||
);
|
|
||||||
machList.push(item);
|
|
||||||
}
|
|
||||||
machList.sort((a, b) => b.matches - a.matches);
|
|
||||||
console.log(
|
|
||||||
`friendLog: ${friendLogTable.length} friendOrderBackups:`,
|
|
||||||
machList
|
|
||||||
);
|
|
||||||
|
|
||||||
var bestBackup = machList[0];
|
|
||||||
if (!bestBackup?.isValid) {
|
|
||||||
var message = 'whomp whomp, no valid backup found';
|
|
||||||
console.error(message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.applyFriendOrderBackup(bestBackup.table);
|
|
||||||
this.applyFriendLogFriendOrder();
|
|
||||||
await configRepository.setInt(
|
|
||||||
`VRCX_friendNumber_${API.currentUser.id}`,
|
|
||||||
this.friendNumber
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
getFriendLogFriendOrder() {
|
|
||||||
var friendLogTable = [];
|
|
||||||
for (var i = 0; i < this.friendLogTable.data.length; i++) {
|
|
||||||
var ref = this.friendLogTable.data[i];
|
|
||||||
if (ref.type !== 'Friend') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
friendLogTable.findIndex((x) => x.id === ref.userId) !== -1
|
|
||||||
) {
|
|
||||||
// console.log(
|
|
||||||
// 'ignoring duplicate friend',
|
|
||||||
// ref.displayName,
|
|
||||||
// ref.created_at
|
|
||||||
// );
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
friendLogTable.push({
|
|
||||||
id: ref.userId,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
created_at: ref.created_at
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var compareByCreatedAt = function (a, b) {
|
|
||||||
var A = a.created_at;
|
|
||||||
var B = b.created_at;
|
|
||||||
if (A < B) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (A > B) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
friendLogTable.sort(compareByCreatedAt);
|
|
||||||
return friendLogTable;
|
|
||||||
},
|
|
||||||
|
|
||||||
applyFriendLogFriendOrder() {
|
|
||||||
var friendLogTable = this.getFriendLogFriendOrder();
|
|
||||||
if (this.friendNumber === 0) {
|
|
||||||
console.log(
|
|
||||||
'No backup applied, applying friend log in reverse'
|
|
||||||
);
|
|
||||||
// this means no FriendOrderBackup was applied
|
|
||||||
// will need to apply in reverse order instead
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (var friendLog of friendLogTable) {
|
|
||||||
var ref = this.friendLog.get(friendLog.id);
|
|
||||||
if (!ref || ref.friendNumber) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ref.friendNumber = ++this.friendNumber;
|
|
||||||
this.friendLog.set(ref.userId, ref);
|
|
||||||
database.setFriendLogCurrent(ref);
|
|
||||||
var friendRef = this.friends.get(friendLog.id);
|
|
||||||
if (friendRef?.ref) {
|
|
||||||
friendRef.ref.$friendNumber = ref.friendNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
applyFriendLogFriendOrderInReverse() {
|
|
||||||
this.friendNumber = this.friends.size + 1;
|
|
||||||
var friendLogTable = this.getFriendLogFriendOrder();
|
|
||||||
for (var i = friendLogTable.length - 1; i > -1; i--) {
|
|
||||||
var friendLog = friendLogTable[i];
|
|
||||||
var ref = this.friendLog.get(friendLog.id);
|
|
||||||
if (!ref) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ref.friendNumber) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ref.friendNumber = --this.friendNumber;
|
|
||||||
this.friendLog.set(ref.userId, ref);
|
|
||||||
database.setFriendLogCurrent(ref);
|
|
||||||
var friendRef = this.friends.get(friendLog.id);
|
|
||||||
if (friendRef?.ref) {
|
|
||||||
friendRef.ref.$friendNumber = ref.friendNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.friendNumber = this.friends.size;
|
|
||||||
console.log('Applied friend order from friendLog');
|
|
||||||
},
|
|
||||||
|
|
||||||
parseFriendOrderBackup(friendLogTable, created_at, backupUserIds) {
|
|
||||||
var backupTable = [];
|
|
||||||
for (var i = 0; i < backupUserIds.length; i++) {
|
|
||||||
var userId = backupUserIds[i];
|
|
||||||
var ctx = this.friends.get(userId);
|
|
||||||
if (ctx) {
|
|
||||||
backupTable.push({
|
|
||||||
id: ctx.id,
|
|
||||||
displayName: ctx.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// var compareTable = [];
|
|
||||||
// compare 2 tables, find max amount of id's in same order
|
|
||||||
var maxMatches = 0;
|
|
||||||
var currentMatches = 0;
|
|
||||||
var backupIndex = 0;
|
|
||||||
for (var i = 0; i < friendLogTable.length; i++) {
|
|
||||||
var isMatch = false;
|
|
||||||
var ref = friendLogTable[i];
|
|
||||||
if (backupIndex <= 0) {
|
|
||||||
backupIndex = backupTable.findIndex((x) => x.id === ref.id);
|
|
||||||
if (backupIndex !== -1) {
|
|
||||||
currentMatches = 1;
|
|
||||||
}
|
|
||||||
} else if (backupTable[backupIndex].id === ref.id) {
|
|
||||||
currentMatches++;
|
|
||||||
isMatch = true;
|
|
||||||
} else {
|
|
||||||
var backupIndex = backupTable.findIndex(
|
|
||||||
(x) => x.id === ref.id
|
|
||||||
);
|
|
||||||
if (backupIndex !== -1) {
|
|
||||||
currentMatches = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (backupIndex === backupTable.length - 1) {
|
|
||||||
backupIndex = 0;
|
|
||||||
} else {
|
|
||||||
backupIndex++;
|
|
||||||
}
|
|
||||||
if (currentMatches > maxMatches) {
|
|
||||||
maxMatches = currentMatches;
|
|
||||||
}
|
|
||||||
// compareTable.push({
|
|
||||||
// id: ref.id,
|
|
||||||
// displayName: ref.displayName,
|
|
||||||
// match: isMatch
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
var lerp = (a, b, alpha) => {
|
|
||||||
return a + alpha * (b - a);
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
matches: parseFloat(`${maxMatches}.${created_at}`),
|
|
||||||
table: backupUserIds,
|
|
||||||
isValid: maxMatches > lerp(4, 10, backupTable.length / 1000) // pls no collisions
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
applyFriendOrderBackup(userIdOrder) {
|
|
||||||
for (var i = 0; i < userIdOrder.length; i++) {
|
|
||||||
var userId = userIdOrder[i];
|
|
||||||
var ctx = this.friends.get(userId);
|
|
||||||
var ref = ctx?.ref;
|
|
||||||
if (!ref || ref.$friendNumber) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var friendLogCurrent = {
|
|
||||||
userId,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
trustLevel: ref.$trustLevel,
|
|
||||||
friendNumber: i + 1
|
|
||||||
};
|
|
||||||
this.friendLog.set(userId, friendLogCurrent);
|
|
||||||
database.setFriendLogCurrent(friendLogCurrent);
|
|
||||||
this.friendNumber = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,577 +0,0 @@
|
|||||||
import * as workerTimers from 'worker-timers';
|
|
||||||
import { baseClass, $app, API } from './baseClass.js';
|
|
||||||
import { worldRequest, groupRequest } from '../api';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {
|
|
||||||
sharedFeed: {
|
|
||||||
gameLog: {
|
|
||||||
wrist: [],
|
|
||||||
lastEntryDate: ''
|
|
||||||
},
|
|
||||||
feedTable: {
|
|
||||||
wrist: [],
|
|
||||||
lastEntryDate: ''
|
|
||||||
},
|
|
||||||
notificationTable: {
|
|
||||||
wrist: [],
|
|
||||||
lastEntryDate: ''
|
|
||||||
},
|
|
||||||
friendLogTable: {
|
|
||||||
wrist: [],
|
|
||||||
lastEntryDate: ''
|
|
||||||
},
|
|
||||||
moderationAgainstTable: {
|
|
||||||
wrist: [],
|
|
||||||
lastEntryDate: ''
|
|
||||||
},
|
|
||||||
pendingUpdate: false
|
|
||||||
},
|
|
||||||
updateSharedFeedTimer: null,
|
|
||||||
updateSharedFeedPending: false,
|
|
||||||
updateSharedFeedPendingForceUpdate: false
|
|
||||||
};
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
updateSharedFeed(forceUpdate) {
|
|
||||||
if (!this.friendLogInitStatus) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.updateSharedFeedTimer) {
|
|
||||||
if (forceUpdate) {
|
|
||||||
this.updateSharedFeedPendingForceUpdate = true;
|
|
||||||
}
|
|
||||||
this.updateSharedFeedPending = true;
|
|
||||||
} else {
|
|
||||||
this.updateSharedExecute(forceUpdate);
|
|
||||||
this.updateSharedFeedTimer = setTimeout(() => {
|
|
||||||
if (this.updateSharedFeedPending) {
|
|
||||||
this.updateSharedExecute(
|
|
||||||
this.updateSharedFeedPendingForceUpdate
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.updateSharedFeedTimer = null;
|
|
||||||
}, 150);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSharedExecute(forceUpdate) {
|
|
||||||
try {
|
|
||||||
this.updateSharedFeedDebounce(forceUpdate);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
this.updateSharedFeedTimer = null;
|
|
||||||
this.updateSharedFeedPending = false;
|
|
||||||
this.updateSharedFeedPendingForceUpdate = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSharedFeedDebounce(forceUpdate) {
|
|
||||||
this.updateSharedFeedGameLog(forceUpdate);
|
|
||||||
this.updateSharedFeedFeedTable(forceUpdate);
|
|
||||||
this.updateSharedFeedNotificationTable(forceUpdate);
|
|
||||||
this.updateSharedFeedFriendLogTable(forceUpdate);
|
|
||||||
this.updateSharedFeedModerationAgainstTable(forceUpdate);
|
|
||||||
var feeds = this.sharedFeed;
|
|
||||||
if (!feeds.pendingUpdate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var wristFeed = [];
|
|
||||||
wristFeed = wristFeed.concat(
|
|
||||||
feeds.gameLog.wrist,
|
|
||||||
feeds.feedTable.wrist,
|
|
||||||
feeds.notificationTable.wrist,
|
|
||||||
feeds.friendLogTable.wrist,
|
|
||||||
feeds.moderationAgainstTable.wrist
|
|
||||||
);
|
|
||||||
// OnPlayerJoining/Traveling
|
|
||||||
API.currentTravelers.forEach((ref) => {
|
|
||||||
var isFavorite = this.localFavoriteFriends.has(ref.id);
|
|
||||||
if (
|
|
||||||
(this.sharedFeedFilters.wrist.OnPlayerJoining ===
|
|
||||||
'Friends' ||
|
|
||||||
(this.sharedFeedFilters.wrist.OnPlayerJoining ===
|
|
||||||
'VIP' &&
|
|
||||||
isFavorite)) &&
|
|
||||||
!$app.lastLocation.playerList.has(ref.id)
|
|
||||||
) {
|
|
||||||
if (ref.$location.tag === $app.lastLocation.location) {
|
|
||||||
var feedEntry = {
|
|
||||||
...ref,
|
|
||||||
isFavorite,
|
|
||||||
isFriend: true,
|
|
||||||
type: 'OnPlayerJoining'
|
|
||||||
};
|
|
||||||
wristFeed.unshift(feedEntry);
|
|
||||||
} else {
|
|
||||||
var worldRef = API.cachedWorlds.get(
|
|
||||||
ref.$location.worldId
|
|
||||||
);
|
|
||||||
var groupName = '';
|
|
||||||
if (ref.$location.groupId) {
|
|
||||||
var groupRef = API.cachedGroups.get(
|
|
||||||
ref.$location.groupId
|
|
||||||
);
|
|
||||||
if (typeof groupRef !== 'undefined') {
|
|
||||||
groupName = groupRef.name;
|
|
||||||
} else {
|
|
||||||
// no group cache, fetch group and try again
|
|
||||||
groupRequest
|
|
||||||
.getGroup({
|
|
||||||
groupId: ref.$location.groupId
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
workerTimers.setTimeout(() => {
|
|
||||||
// delay to allow for group cache to update
|
|
||||||
$app.sharedFeed.pendingUpdate =
|
|
||||||
true;
|
|
||||||
$app.updateSharedFeed(false);
|
|
||||||
}, 100);
|
|
||||||
return args;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof worldRef !== 'undefined') {
|
|
||||||
var feedEntry = {
|
|
||||||
created_at: ref.created_at,
|
|
||||||
type: 'GPS',
|
|
||||||
userId: ref.id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
location: ref.$location.tag,
|
|
||||||
worldName: worldRef.name,
|
|
||||||
groupName,
|
|
||||||
previousLocation: '',
|
|
||||||
isFavorite,
|
|
||||||
time: 0,
|
|
||||||
isFriend: true,
|
|
||||||
isTraveling: true
|
|
||||||
};
|
|
||||||
wristFeed.unshift(feedEntry);
|
|
||||||
} else {
|
|
||||||
// no world cache, fetch world and try again
|
|
||||||
worldRequest
|
|
||||||
.getWorld({
|
|
||||||
worldId: ref.$location.worldId
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
workerTimers.setTimeout(() => {
|
|
||||||
// delay to allow for world cache to update
|
|
||||||
$app.sharedFeed.pendingUpdate = true;
|
|
||||||
$app.updateSharedFeed(false);
|
|
||||||
}, 100);
|
|
||||||
return args;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
wristFeed.sort(function (a, b) {
|
|
||||||
if (a.created_at < b.created_at) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (a.created_at > b.created_at) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
wristFeed.splice(16);
|
|
||||||
AppApi.ExecuteVrFeedFunction(
|
|
||||||
'wristFeedUpdate',
|
|
||||||
JSON.stringify(wristFeed)
|
|
||||||
);
|
|
||||||
this.applyUserDialogLocation();
|
|
||||||
this.applyWorldDialogInstances();
|
|
||||||
this.applyGroupDialogInstances();
|
|
||||||
feeds.pendingUpdate = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSharedFeedGameLog(forceUpdate) {
|
|
||||||
// Location, OnPlayerJoined, OnPlayerLeft
|
|
||||||
var sessionTable = this.gameLogSessionTable;
|
|
||||||
var i = sessionTable.length;
|
|
||||||
if (i > 0) {
|
|
||||||
if (
|
|
||||||
sessionTable[i - 1].created_at ===
|
|
||||||
this.sharedFeed.gameLog.lastEntryDate &&
|
|
||||||
forceUpdate === false
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.sharedFeed.gameLog.lastEntryDate =
|
|
||||||
sessionTable[i - 1].created_at;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours
|
|
||||||
var wristArr = [];
|
|
||||||
var w = 0;
|
|
||||||
var wristFilter = this.sharedFeedFilters.wrist;
|
|
||||||
var currentUserLeaveTime = 0;
|
|
||||||
var locationJoinTime = 0;
|
|
||||||
for (var i = sessionTable.length - 1; i > -1; i--) {
|
|
||||||
var ctx = sessionTable[i];
|
|
||||||
if (ctx.created_at < bias) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ctx.type === 'Notification') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// on Location change remove OnPlayerLeft
|
|
||||||
if (ctx.type === 'LocationDestination') {
|
|
||||||
currentUserLeaveTime = Date.parse(ctx.created_at);
|
|
||||||
var currentUserLeaveTimeOffset =
|
|
||||||
currentUserLeaveTime + 5 * 1000;
|
|
||||||
for (var k = w - 1; k > -1; k--) {
|
|
||||||
var feedItem = wristArr[k];
|
|
||||||
if (
|
|
||||||
(feedItem.type === 'OnPlayerLeft' ||
|
|
||||||
feedItem.type === 'BlockedOnPlayerLeft' ||
|
|
||||||
feedItem.type === 'MutedOnPlayerLeft') &&
|
|
||||||
Date.parse(feedItem.created_at) >=
|
|
||||||
currentUserLeaveTime &&
|
|
||||||
Date.parse(feedItem.created_at) <=
|
|
||||||
currentUserLeaveTimeOffset
|
|
||||||
) {
|
|
||||||
wristArr.splice(k, 1);
|
|
||||||
w--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// on Location change remove OnPlayerJoined
|
|
||||||
if (ctx.type === 'Location') {
|
|
||||||
locationJoinTime = Date.parse(ctx.created_at);
|
|
||||||
var locationJoinTimeOffset = locationJoinTime + 20 * 1000;
|
|
||||||
for (var k = w - 1; k > -1; k--) {
|
|
||||||
var feedItem = wristArr[k];
|
|
||||||
if (
|
|
||||||
(feedItem.type === 'OnPlayerJoined' ||
|
|
||||||
feedItem.type === 'BlockedOnPlayerJoined' ||
|
|
||||||
feedItem.type === 'MutedOnPlayerJoined') &&
|
|
||||||
Date.parse(feedItem.created_at) >=
|
|
||||||
locationJoinTime &&
|
|
||||||
Date.parse(feedItem.created_at) <=
|
|
||||||
locationJoinTimeOffset
|
|
||||||
) {
|
|
||||||
wristArr.splice(k, 1);
|
|
||||||
w--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove current user
|
|
||||||
if (
|
|
||||||
(ctx.type === 'OnPlayerJoined' ||
|
|
||||||
ctx.type === 'OnPlayerLeft' ||
|
|
||||||
ctx.type === 'PortalSpawn') &&
|
|
||||||
ctx.displayName === API.currentUser.displayName
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var isFriend = false;
|
|
||||||
var isFavorite = false;
|
|
||||||
if (ctx.userId) {
|
|
||||||
isFriend = this.friends.has(ctx.userId);
|
|
||||||
isFavorite = this.localFavoriteFriends.has(ctx.userId);
|
|
||||||
} else if (ctx.displayName) {
|
|
||||||
for (var ref of API.cachedUsers.values()) {
|
|
||||||
if (ref.displayName === ctx.displayName) {
|
|
||||||
isFriend = this.friends.has(ref.id);
|
|
||||||
isFavorite = this.localFavoriteFriends.has(ref.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// add tag colour
|
|
||||||
var tagColour = '';
|
|
||||||
if (ctx.userId) {
|
|
||||||
var tagRef = this.customUserTags.get(ctx.userId);
|
|
||||||
if (typeof tagRef !== 'undefined') {
|
|
||||||
tagColour = tagRef.colour;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// BlockedOnPlayerJoined, BlockedOnPlayerLeft, MutedOnPlayerJoined, MutedOnPlayerLeft
|
|
||||||
if (
|
|
||||||
ctx.type === 'OnPlayerJoined' ||
|
|
||||||
ctx.type === 'OnPlayerLeft'
|
|
||||||
) {
|
|
||||||
for (var ref of API.cachedPlayerModerations.values()) {
|
|
||||||
if (
|
|
||||||
ref.targetDisplayName !== ctx.displayName &&
|
|
||||||
ref.sourceUserId !== ctx.userId
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ref.type === 'block') {
|
|
||||||
var type = `Blocked${ctx.type}`;
|
|
||||||
} else if (ref.type === 'mute') {
|
|
||||||
var type = `Muted${ctx.type}`;
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = {
|
|
||||||
created_at: ctx.created_at,
|
|
||||||
type,
|
|
||||||
displayName: ref.targetDisplayName,
|
|
||||||
userId: ref.targetUserId,
|
|
||||||
isFriend,
|
|
||||||
isFavorite
|
|
||||||
};
|
|
||||||
if (
|
|
||||||
wristFilter[type] &&
|
|
||||||
(wristFilter[type] === 'Everyone' ||
|
|
||||||
(wristFilter[type] === 'Friends' && isFriend) ||
|
|
||||||
(wristFilter[type] === 'VIP' && isFavorite))
|
|
||||||
) {
|
|
||||||
wristArr.unshift(entry);
|
|
||||||
}
|
|
||||||
this.queueGameLogNoty(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// when too many user joins happen at once when switching instances
|
|
||||||
// the "w" counter maxes out and wont add any more entries
|
|
||||||
// until the onJoins are cleared by "Location"
|
|
||||||
// e.g. if a "VideoPlay" occurs between "OnPlayerJoined" and "Location" it wont be added
|
|
||||||
if (
|
|
||||||
w < 50 &&
|
|
||||||
wristFilter[ctx.type] &&
|
|
||||||
(wristFilter[ctx.type] === 'On' ||
|
|
||||||
wristFilter[ctx.type] === 'Everyone' ||
|
|
||||||
(wristFilter[ctx.type] === 'Friends' && isFriend) ||
|
|
||||||
(wristFilter[ctx.type] === 'VIP' && isFavorite))
|
|
||||||
) {
|
|
||||||
wristArr.push({
|
|
||||||
...ctx,
|
|
||||||
tagColour,
|
|
||||||
isFriend,
|
|
||||||
isFavorite
|
|
||||||
});
|
|
||||||
++w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.sharedFeed.gameLog.wrist = wristArr;
|
|
||||||
this.sharedFeed.pendingUpdate = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSharedFeedFeedTable(forceUpdate) {
|
|
||||||
// GPS, Online, Offline, Status, Avatar
|
|
||||||
var feedSession = this.feedSessionTable;
|
|
||||||
var i = feedSession.length;
|
|
||||||
if (i > 0) {
|
|
||||||
if (
|
|
||||||
feedSession[i - 1].created_at ===
|
|
||||||
this.sharedFeed.feedTable.lastEntryDate &&
|
|
||||||
forceUpdate === false
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.sharedFeed.feedTable.lastEntryDate =
|
|
||||||
feedSession[i - 1].created_at;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours
|
|
||||||
var wristArr = [];
|
|
||||||
var w = 0;
|
|
||||||
var wristFilter = this.sharedFeedFilters.wrist;
|
|
||||||
for (var i = feedSession.length - 1; i > -1; i--) {
|
|
||||||
var ctx = feedSession[i];
|
|
||||||
if (ctx.created_at < bias) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ctx.type === 'Avatar') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// hide private worlds from feed
|
|
||||||
if (
|
|
||||||
this.hidePrivateFromFeed &&
|
|
||||||
ctx.type === 'GPS' &&
|
|
||||||
ctx.location === 'private'
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var isFriend = this.friends.has(ctx.userId);
|
|
||||||
var isFavorite = this.localFavoriteFriends.has(ctx.userId);
|
|
||||||
if (
|
|
||||||
w < 20 &&
|
|
||||||
wristFilter[ctx.type] &&
|
|
||||||
(wristFilter[ctx.type] === 'Friends' ||
|
|
||||||
(wristFilter[ctx.type] === 'VIP' && isFavorite))
|
|
||||||
) {
|
|
||||||
wristArr.push({
|
|
||||||
...ctx,
|
|
||||||
isFriend,
|
|
||||||
isFavorite
|
|
||||||
});
|
|
||||||
++w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.sharedFeed.feedTable.wrist = wristArr;
|
|
||||||
this.sharedFeed.pendingUpdate = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSharedFeedNotificationTable(forceUpdate) {
|
|
||||||
// invite, requestInvite, requestInviteResponse, inviteResponse, friendRequest
|
|
||||||
var notificationTable = this.notificationTable.data;
|
|
||||||
var i = notificationTable.length;
|
|
||||||
if (i > 0) {
|
|
||||||
if (
|
|
||||||
notificationTable[i - 1].created_at ===
|
|
||||||
this.sharedFeed.notificationTable.lastEntryDate &&
|
|
||||||
forceUpdate === false
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.sharedFeed.notificationTable.lastEntryDate =
|
|
||||||
notificationTable[i - 1].created_at;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours
|
|
||||||
var wristArr = [];
|
|
||||||
var w = 0;
|
|
||||||
var wristFilter = this.sharedFeedFilters.wrist;
|
|
||||||
for (var i = notificationTable.length - 1; i > -1; i--) {
|
|
||||||
var ctx = notificationTable[i];
|
|
||||||
if (ctx.created_at < bias) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ctx.senderUserId === API.currentUser.id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var isFriend = this.friends.has(ctx.senderUserId);
|
|
||||||
var isFavorite = this.localFavoriteFriends.has(
|
|
||||||
ctx.senderUserId
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
w < 20 &&
|
|
||||||
wristFilter[ctx.type] &&
|
|
||||||
(wristFilter[ctx.type] === 'On' ||
|
|
||||||
wristFilter[ctx.type] === 'Friends' ||
|
|
||||||
(wristFilter[ctx.type] === 'VIP' && isFavorite))
|
|
||||||
) {
|
|
||||||
wristArr.push({
|
|
||||||
...ctx,
|
|
||||||
isFriend,
|
|
||||||
isFavorite
|
|
||||||
});
|
|
||||||
++w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.sharedFeed.notificationTable.wrist = wristArr;
|
|
||||||
this.sharedFeed.pendingUpdate = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSharedFeedFriendLogTable(forceUpdate) {
|
|
||||||
// TrustLevel, Friend, FriendRequest, Unfriend, DisplayName
|
|
||||||
var friendLog = this.friendLogTable.data;
|
|
||||||
var i = friendLog.length;
|
|
||||||
if (i > 0) {
|
|
||||||
if (
|
|
||||||
friendLog[i - 1].created_at ===
|
|
||||||
this.sharedFeed.friendLogTable.lastEntryDate &&
|
|
||||||
forceUpdate === false
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.sharedFeed.friendLogTable.lastEntryDate =
|
|
||||||
friendLog[i - 1].created_at;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours
|
|
||||||
var wristArr = [];
|
|
||||||
var w = 0;
|
|
||||||
var wristFilter = this.sharedFeedFilters.wrist;
|
|
||||||
for (var i = friendLog.length - 1; i > -1; i--) {
|
|
||||||
var ctx = friendLog[i];
|
|
||||||
if (ctx.created_at < bias) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ctx.type === 'FriendRequest') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var isFriend = this.friends.has(ctx.userId);
|
|
||||||
var isFavorite = this.localFavoriteFriends.has(ctx.userId);
|
|
||||||
if (
|
|
||||||
w < 20 &&
|
|
||||||
wristFilter[ctx.type] &&
|
|
||||||
(wristFilter[ctx.type] === 'On' ||
|
|
||||||
wristFilter[ctx.type] === 'Friends' ||
|
|
||||||
(wristFilter[ctx.type] === 'VIP' && isFavorite))
|
|
||||||
) {
|
|
||||||
wristArr.push({
|
|
||||||
...ctx,
|
|
||||||
isFriend,
|
|
||||||
isFavorite
|
|
||||||
});
|
|
||||||
++w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.sharedFeed.friendLogTable.wrist = wristArr;
|
|
||||||
this.sharedFeed.pendingUpdate = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSharedFeedModerationAgainstTable(forceUpdate) {
|
|
||||||
// Unblocked, Blocked, Muted, Unmuted
|
|
||||||
var moderationAgainst = this.moderationAgainstTable;
|
|
||||||
var i = moderationAgainst.length;
|
|
||||||
if (i > 0) {
|
|
||||||
if (
|
|
||||||
moderationAgainst[i - 1].created_at ===
|
|
||||||
this.sharedFeed.moderationAgainstTable.lastEntryDate &&
|
|
||||||
forceUpdate === false
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.sharedFeed.moderationAgainstTable.lastEntryDate =
|
|
||||||
moderationAgainst[i - 1].created_at;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var bias = new Date(Date.now() - 86400000).toJSON(); // 24 hours
|
|
||||||
var wristArr = [];
|
|
||||||
var w = 0;
|
|
||||||
var wristFilter = this.sharedFeedFilters.wrist;
|
|
||||||
for (var i = moderationAgainst.length - 1; i > -1; i--) {
|
|
||||||
var ctx = moderationAgainst[i];
|
|
||||||
if (ctx.created_at < bias) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
var isFriend = this.friends.has(ctx.userId);
|
|
||||||
var isFavorite = this.localFavoriteFriends.has(ctx.userId);
|
|
||||||
// add tag colour
|
|
||||||
var tagColour = '';
|
|
||||||
var tagRef = this.customUserTags.get(ctx.userId);
|
|
||||||
if (typeof tagRef !== 'undefined') {
|
|
||||||
tagColour = tagRef.colour;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
w < 20 &&
|
|
||||||
wristFilter[ctx.type] &&
|
|
||||||
wristFilter[ctx.type] === 'On'
|
|
||||||
) {
|
|
||||||
wristArr.push({
|
|
||||||
...ctx,
|
|
||||||
isFriend,
|
|
||||||
isFavorite,
|
|
||||||
tagColour
|
|
||||||
});
|
|
||||||
++w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.sharedFeed.moderationAgainstTable.wrist = wristArr;
|
|
||||||
this.sharedFeed.pendingUpdate = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,512 +0,0 @@
|
|||||||
import Vue from 'vue';
|
|
||||||
import VueMarkdown from 'vue-markdown';
|
|
||||||
import { instanceRequest, userRequest } from '../api';
|
|
||||||
import { hasGroupPermission } from '../composables/group/utils';
|
|
||||||
import { parseLocation } from '../composables/instance/utils';
|
|
||||||
import { $app, $t, API, baseClass } from './baseClass.js';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
Vue.component('vue-markdown', VueMarkdown);
|
|
||||||
|
|
||||||
Vue.component('launch', {
|
|
||||||
template:
|
|
||||||
'<el-tooltip placement="top" :content="$t(`dialog.user.info.launch_invite_tooltip`)" :disabled="hideTooltips"><el-button @click="confirm" size="mini" icon="el-icon-switch-button" circle></el-button></el-tooltip>',
|
|
||||||
props: {
|
|
||||||
location: String,
|
|
||||||
hideTooltips: Boolean
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
parse() {
|
|
||||||
this.$el.style.display = $app.checkCanInviteSelf(
|
|
||||||
this.location
|
|
||||||
)
|
|
||||||
? ''
|
|
||||||
: 'none';
|
|
||||||
},
|
|
||||||
confirm() {
|
|
||||||
this.$emit('show-launch-dialog', this.location);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
location() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Vue.component('invite-yourself', {
|
|
||||||
template:
|
|
||||||
'<el-button @click="confirm" size="mini" icon="el-icon-message" circle></el-button>',
|
|
||||||
props: {
|
|
||||||
location: String,
|
|
||||||
shortname: String
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
parse() {
|
|
||||||
this.$el.style.display = $app.checkCanInviteSelf(
|
|
||||||
this.location
|
|
||||||
)
|
|
||||||
? ''
|
|
||||||
: 'none';
|
|
||||||
},
|
|
||||||
confirm() {
|
|
||||||
this.selfInvite(this.location, this.shortname);
|
|
||||||
},
|
|
||||||
selfInvite(location, shortName) {
|
|
||||||
const L = parseLocation(location);
|
|
||||||
if (!L.isRealInstance) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
instanceRequest
|
|
||||||
.selfInvite({
|
|
||||||
instanceId: L.instanceId,
|
|
||||||
worldId: L.worldId,
|
|
||||||
shortName
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
this.$message({
|
|
||||||
message: 'Self invite sent',
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
location() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Vue.component('location-world', {
|
|
||||||
template:
|
|
||||||
'<span><span @click="showLaunchDialog" class="x-link">' +
|
|
||||||
'<i v-if="isUnlocked" class="el-icon el-icon-unlock" style="display:inline-block;margin-right:5px"></i>' +
|
|
||||||
'<span>#{{ instanceName }} {{ accessTypeName }}</span></span>' +
|
|
||||||
'<span v-if="groupName" @click="showGroupDialog" class="x-link">({{ groupName }})</span>' +
|
|
||||||
'<span class="flags" :class="region" style="display:inline-block;margin-left:5px"></span>' +
|
|
||||||
'<i v-if="strict" class="el-icon el-icon-lock" style="display:inline-block;margin-left:5px"></i></span>',
|
|
||||||
props: {
|
|
||||||
locationobject: Object,
|
|
||||||
currentuserid: String,
|
|
||||||
worlddialogshortname: String,
|
|
||||||
grouphint: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
location: this.location,
|
|
||||||
instanceName: this.instanceName,
|
|
||||||
accessTypeName: this.accessTypeName,
|
|
||||||
region: this.region,
|
|
||||||
shortName: this.shortName,
|
|
||||||
isUnlocked: this.isUnlocked,
|
|
||||||
strict: this.strict,
|
|
||||||
groupName: this.groupName
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
parse() {
|
|
||||||
this.location = this.locationobject.tag;
|
|
||||||
this.instanceName = this.locationobject.instanceName;
|
|
||||||
this.accessTypeName = this.locationobject.accessTypeName;
|
|
||||||
this.strict = this.locationobject.strict;
|
|
||||||
this.shortName = this.locationobject.shortName;
|
|
||||||
|
|
||||||
this.isUnlocked = false;
|
|
||||||
if (
|
|
||||||
(this.worlddialogshortname &&
|
|
||||||
this.locationobject.shortName &&
|
|
||||||
this.worlddialogshortname ===
|
|
||||||
this.locationobject.shortName) ||
|
|
||||||
this.currentuserid === this.locationobject.userId
|
|
||||||
) {
|
|
||||||
this.isUnlocked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.region = this.locationobject.region;
|
|
||||||
if (!this.region) {
|
|
||||||
this.region = 'us';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.groupName = '';
|
|
||||||
if (this.grouphint) {
|
|
||||||
this.groupName = this.grouphint;
|
|
||||||
} else if (this.locationobject.groupId) {
|
|
||||||
this.groupName = this.locationobject.groupId;
|
|
||||||
$app.getGroupName(this.locationobject.groupId).then(
|
|
||||||
(groupName) => {
|
|
||||||
this.groupName = groupName;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showLaunchDialog() {
|
|
||||||
this.$emit(
|
|
||||||
'show-launch-dialog',
|
|
||||||
this.location,
|
|
||||||
this.shortName
|
|
||||||
);
|
|
||||||
},
|
|
||||||
showGroupDialog() {
|
|
||||||
if (!this.location) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var L = parseLocation(this.location);
|
|
||||||
if (!L.groupId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$app.showGroupDialog(L.groupId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
locationobject() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Vue.component('last-join', {
|
|
||||||
template:
|
|
||||||
'<span v-if="lastJoin">' +
|
|
||||||
'<el-tooltip placement="top" style="margin-left:5px" >' +
|
|
||||||
'<div slot="content">' +
|
|
||||||
'<span>{{ $t("dialog.user.info.last_join") }} <timer :epoch="lastJoin"></timer></span>' +
|
|
||||||
'</div>' +
|
|
||||||
'<i class="el-icon el-icon-location-outline" style="display:inline-block"></i>' +
|
|
||||||
'</el-tooltip>' +
|
|
||||||
'</span>',
|
|
||||||
props: {
|
|
||||||
location: String,
|
|
||||||
currentlocation: String
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
lastJoin: this.lastJoin
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
parse() {
|
|
||||||
this.lastJoin = $app.instanceJoinHistory.get(this.location);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
location() {
|
|
||||||
this.parse();
|
|
||||||
},
|
|
||||||
currentlocation() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Vue.component('instance-info', {
|
|
||||||
template:
|
|
||||||
'<div style="display:inline-block;margin-left:5px">' +
|
|
||||||
'<el-tooltip v-if="isValidInstance" placement="bottom">' +
|
|
||||||
'<div slot="content">' +
|
|
||||||
'<template v-if="isClosed"><span>Closed At: {{ closedAt | formatDate(\'long\') }}</span></br></template>' +
|
|
||||||
'<template v-if="canCloseInstance"><el-button :disabled="isClosed" size="mini" type="primary" @click="$root.closeInstance(location)">{{ $t("dialog.user.info.close_instance") }}</el-button></br></br></template>' +
|
|
||||||
'<span><span style="color:#409eff">PC: </span>{{ platforms.standalonewindows }}</span></br>' +
|
|
||||||
'<span><span style="color:#67c23a">Android: </span>{{ platforms.android }}</span></br>' +
|
|
||||||
'<span>{{ $t("dialog.user.info.instance_game_version") }} {{ gameServerVersion }}</span></br>' +
|
|
||||||
'<span v-if="queueEnabled">{{ $t("dialog.user.info.instance_queuing_enabled") }}</br></span>' +
|
|
||||||
'<span v-if="disabledContentSettings">{{ $t("dialog.user.info.instance_disabled_content") }} {{ disabledContentSettings }}</br></span>' +
|
|
||||||
'<span v-if="userList.length">{{ $t("dialog.user.info.instance_users") }}</br></span>' +
|
|
||||||
'<template v-for="user in userList"><span style="cursor:pointer;margin-right:5px" @click="showUserDialog(user.id)" v-text="user.displayName"></span></template>' +
|
|
||||||
'</div>' +
|
|
||||||
'<i class="el-icon-caret-bottom"></i>' +
|
|
||||||
'</el-tooltip>' +
|
|
||||||
'<span v-if="occupants" style="margin-left:5px">{{ occupants }}/{{ capacity }}</span>' +
|
|
||||||
'<span v-if="friendcount" style="margin-left:5px">({{ friendcount }})</span>' +
|
|
||||||
'<span v-if="isFull" style="margin-left:5px;color:lightcoral">{{ $t("dialog.user.info.instance_full") }}</span>' +
|
|
||||||
'<span v-if="isHardClosed" style="margin-left:5px;color:lightcoral">{{ $t("dialog.user.info.instance_hard_closed") }}</span>' +
|
|
||||||
'<span v-else-if="isClosed" style="margin-left:5px;color:lightcoral">{{ $t("dialog.user.info.instance_closed") }}</span>' +
|
|
||||||
'<span v-if="queueSize" style="margin-left:5px">{{ $t("dialog.user.info.instance_queue") }} {{ queueSize }}</span>' +
|
|
||||||
'<span v-if="isAgeGated" style="margin-left:5px;color:lightcoral">{{ $t("dialog.user.info.instance_age_gated") }}</span>' +
|
|
||||||
'</div>',
|
|
||||||
props: {
|
|
||||||
location: String,
|
|
||||||
instance: Object,
|
|
||||||
friendcount: Number,
|
|
||||||
updateelement: Number
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isValidInstance: this.isValidInstance,
|
|
||||||
isFull: this.isFull,
|
|
||||||
isClosed: this.isClosed,
|
|
||||||
isHardClosed: this.isHardClosed,
|
|
||||||
closedAt: this.closedAt,
|
|
||||||
occupants: this.occupants,
|
|
||||||
capacity: this.capacity,
|
|
||||||
queueSize: this.queueSize,
|
|
||||||
queueEnabled: this.queueEnabled,
|
|
||||||
platforms: this.platforms,
|
|
||||||
userList: this.userList,
|
|
||||||
gameServerVersion: this.gameServerVersion,
|
|
||||||
canCloseInstance: this.canCloseInstance
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
parse() {
|
|
||||||
this.isValidInstance = false;
|
|
||||||
this.isFull = false;
|
|
||||||
this.isClosed = false;
|
|
||||||
this.isHardClosed = false;
|
|
||||||
this.closedAt = '';
|
|
||||||
this.occupants = 0;
|
|
||||||
this.capacity = 0;
|
|
||||||
this.queueSize = 0;
|
|
||||||
this.queueEnabled = false;
|
|
||||||
this.platforms = [];
|
|
||||||
this.userList = [];
|
|
||||||
this.gameServerVersion = '';
|
|
||||||
this.canCloseInstance = false;
|
|
||||||
this.isAgeGated = false;
|
|
||||||
this.disabledContentSettings = '';
|
|
||||||
if (
|
|
||||||
!this.location ||
|
|
||||||
!this.instance ||
|
|
||||||
Object.keys(this.instance).length === 0
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.isValidInstance = true;
|
|
||||||
this.isFull =
|
|
||||||
typeof this.instance.hasCapacityForYou !==
|
|
||||||
'undefined' && !this.instance.hasCapacityForYou;
|
|
||||||
if (this.instance.closedAt) {
|
|
||||||
this.isClosed = true;
|
|
||||||
this.closedAt = this.instance.closedAt;
|
|
||||||
}
|
|
||||||
this.isHardClosed = this.instance.hardClose === true;
|
|
||||||
this.occupants = this.instance.userCount;
|
|
||||||
if (this.location === $app.lastLocation.location) {
|
|
||||||
// use gameLog for occupants when in same location
|
|
||||||
this.occupants = $app.lastLocation.playerList.size;
|
|
||||||
}
|
|
||||||
this.capacity = this.instance.capacity;
|
|
||||||
this.gameServerVersion = this.instance.gameServerVersion;
|
|
||||||
this.queueSize = this.instance.queueSize;
|
|
||||||
if (this.instance.platforms) {
|
|
||||||
this.platforms = this.instance.platforms;
|
|
||||||
}
|
|
||||||
if (this.instance.users) {
|
|
||||||
this.userList = this.instance.users;
|
|
||||||
}
|
|
||||||
if (this.instance.ownerId === API.currentUser.id) {
|
|
||||||
this.canCloseInstance = true;
|
|
||||||
} else if (this.instance?.ownerId?.startsWith('grp_')) {
|
|
||||||
// check group perms
|
|
||||||
var groupId = this.instance.ownerId;
|
|
||||||
var group = API.cachedGroups.get(groupId);
|
|
||||||
this.canCloseInstance = hasGroupPermission(
|
|
||||||
group,
|
|
||||||
'group-instance-moderate'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.isAgeGated = this.instance.ageGate === true;
|
|
||||||
if (this.location && this.location.includes('~ageGate')) {
|
|
||||||
// dumb workaround for API not returning `ageGate`
|
|
||||||
this.isAgeGated = true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
this.instance.$disabledContentSettings &&
|
|
||||||
this.instance.$disabledContentSettings.length
|
|
||||||
) {
|
|
||||||
this.disabledContentSettings =
|
|
||||||
this.instance.$disabledContentSettings.join(', ');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showUserDialog(userId) {
|
|
||||||
this.showUserDialog(userId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
updateelement() {
|
|
||||||
this.parse();
|
|
||||||
},
|
|
||||||
location() {
|
|
||||||
this.parse();
|
|
||||||
},
|
|
||||||
friendcount() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Vue.component('avatar-info', {
|
|
||||||
template:
|
|
||||||
'<div @click="confirm" class="avatar-info">' +
|
|
||||||
'<span style="margin-right:5px">{{ avatarName }}</span>' +
|
|
||||||
'<span v-if="avatarType" style="margin-right:5px" :class="color">{{ avatarType }}</span>' +
|
|
||||||
'<span v-if="avatarTags" style="color:#909399;font-family:monospace;font-size:12px;">{{ avatarTags }}</span>' +
|
|
||||||
'</div>',
|
|
||||||
props: {
|
|
||||||
imageurl: String,
|
|
||||||
userid: String,
|
|
||||||
hintownerid: String,
|
|
||||||
hintavatarname: String,
|
|
||||||
avatartags: Array
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
avatarName: this.avatarName,
|
|
||||||
avatarType: this.avatarType,
|
|
||||||
avatarTags: this.avatarTags,
|
|
||||||
color: this.color
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async parse() {
|
|
||||||
this.ownerId = '';
|
|
||||||
this.avatarName = '';
|
|
||||||
this.avatarType = '';
|
|
||||||
this.color = '';
|
|
||||||
this.avatarTags = '';
|
|
||||||
if (!this.imageurl) {
|
|
||||||
this.avatarName = '-';
|
|
||||||
} else if (this.hintownerid) {
|
|
||||||
this.avatarName = this.hintavatarname;
|
|
||||||
this.ownerId = this.hintownerid;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
var avatarInfo = await $app.getAvatarName(
|
|
||||||
this.imageurl
|
|
||||||
);
|
|
||||||
this.avatarName = avatarInfo.avatarName;
|
|
||||||
this.ownerId = avatarInfo.ownerId;
|
|
||||||
} catch (err) {}
|
|
||||||
}
|
|
||||||
if (typeof this.userid === 'undefined' || !this.ownerId) {
|
|
||||||
this.color = '';
|
|
||||||
this.avatarType = '';
|
|
||||||
} else if (this.ownerId === this.userid) {
|
|
||||||
this.color = 'avatar-info-own';
|
|
||||||
this.avatarType = '(own)';
|
|
||||||
} else {
|
|
||||||
this.color = 'avatar-info-public';
|
|
||||||
this.avatarType = '(public)';
|
|
||||||
}
|
|
||||||
if (typeof this.avatartags === 'object') {
|
|
||||||
var tagString = '';
|
|
||||||
for (var i = 0; i < this.avatartags.length; i++) {
|
|
||||||
var tagName = this.avatartags[i].replace(
|
|
||||||
'content_',
|
|
||||||
''
|
|
||||||
);
|
|
||||||
tagString += tagName;
|
|
||||||
if (i < this.avatartags.length - 1) {
|
|
||||||
tagString += ', ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.avatarTags = tagString;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirm() {
|
|
||||||
if (!this.imageurl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$app.showAvatarAuthorDialog(
|
|
||||||
this.userid,
|
|
||||||
this.ownerId,
|
|
||||||
this.imageurl
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
imageurl() {
|
|
||||||
this.parse();
|
|
||||||
},
|
|
||||||
userid() {
|
|
||||||
this.parse();
|
|
||||||
},
|
|
||||||
avatartags() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Vue.component('display-name', {
|
|
||||||
template:
|
|
||||||
'<span @click="showUserDialog" class="x-link">{{ username }}</span>',
|
|
||||||
props: {
|
|
||||||
userid: String,
|
|
||||||
location: String,
|
|
||||||
forceUpdateKey: Number,
|
|
||||||
hint: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
username: this.username
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async parse() {
|
|
||||||
this.username = this.userid;
|
|
||||||
if (this.hint) {
|
|
||||||
this.username = this.hint;
|
|
||||||
} else if (this.userid) {
|
|
||||||
var args = await userRequest.getCachedUser({
|
|
||||||
userId: this.userid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof args !== 'undefined' &&
|
|
||||||
typeof args.json !== 'undefined' &&
|
|
||||||
typeof args.json.displayName !== 'undefined'
|
|
||||||
) {
|
|
||||||
this.username = args.json.displayName;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showUserDialog() {
|
|
||||||
$app.showUserDialog(this.userid);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
location() {
|
|
||||||
this.parse();
|
|
||||||
},
|
|
||||||
forceUpdateKey() {
|
|
||||||
this.parse();
|
|
||||||
},
|
|
||||||
userid() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.parse();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
import * as workerTimers from 'worker-timers';
|
|
||||||
import { baseClass, $app, API } from './baseClass.js';
|
|
||||||
import { groupRequest } from '../api/index.js';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
API.$on('LOGIN', function () {
|
|
||||||
$app.nextCurrentUserRefresh = 300;
|
|
||||||
$app.nextFriendsRefresh = 3600;
|
|
||||||
$app.nextGroupInstanceRefresh = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {
|
|
||||||
nextCurrentUserRefresh: 300,
|
|
||||||
nextFriendsRefresh: 3600,
|
|
||||||
nextGroupInstanceRefresh: 0,
|
|
||||||
nextAppUpdateCheck: 3600,
|
|
||||||
ipcTimeout: 0,
|
|
||||||
nextClearVRCXCacheCheck: 0,
|
|
||||||
nextDiscordUpdate: 0,
|
|
||||||
nextAutoStateChange: 0,
|
|
||||||
nextGetLogCheck: 0,
|
|
||||||
nextGameRunningCheck: 0,
|
|
||||||
nextDatabaseOptimize: 3600
|
|
||||||
};
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
async updateLoop() {
|
|
||||||
try {
|
|
||||||
if (API.isLoggedIn === true) {
|
|
||||||
if (--this.nextCurrentUserRefresh <= 0) {
|
|
||||||
this.nextCurrentUserRefresh = 300; // 5min
|
|
||||||
API.getCurrentUser();
|
|
||||||
}
|
|
||||||
if (--this.nextFriendsRefresh <= 0) {
|
|
||||||
this.nextFriendsRefresh = 3600; // 1hour
|
|
||||||
this.refreshFriendsList();
|
|
||||||
this.updateStoredUser(API.currentUser);
|
|
||||||
if (this.isGameRunning) {
|
|
||||||
API.refreshPlayerModerations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (--this.nextGroupInstanceRefresh <= 0) {
|
|
||||||
if (this.friendLogInitStatus) {
|
|
||||||
this.nextGroupInstanceRefresh = 300; // 5min
|
|
||||||
groupRequest.getUsersGroupInstances();
|
|
||||||
}
|
|
||||||
AppApi.CheckGameRunning();
|
|
||||||
}
|
|
||||||
if (--this.nextAppUpdateCheck <= 0) {
|
|
||||||
this.nextAppUpdateCheck = 3600; // 1hour
|
|
||||||
if (this.autoUpdateVRCX !== 'Off') {
|
|
||||||
this.checkForVRCXUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (--this.ipcTimeout <= 0) {
|
|
||||||
this.ipcEnabled = false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
--this.nextClearVRCXCacheCheck <= 0 &&
|
|
||||||
this.clearVRCXCacheFrequency > 0
|
|
||||||
) {
|
|
||||||
this.nextClearVRCXCacheCheck =
|
|
||||||
this.clearVRCXCacheFrequency / 2;
|
|
||||||
this.clearVRCXCache();
|
|
||||||
}
|
|
||||||
if (--this.nextDiscordUpdate <= 0) {
|
|
||||||
this.nextDiscordUpdate = 3;
|
|
||||||
if (this.discordActive) {
|
|
||||||
this.updateDiscord();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (--this.nextAutoStateChange <= 0) {
|
|
||||||
this.nextAutoStateChange = 3;
|
|
||||||
this.updateAutoStateChange();
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(this.isRunningUnderWine || LINUX) &&
|
|
||||||
--this.nextGetLogCheck <= 0
|
|
||||||
) {
|
|
||||||
this.nextGetLogCheck = 0.5;
|
|
||||||
const logLines = await LogWatcher.GetLogLines();
|
|
||||||
if (logLines) {
|
|
||||||
logLines.forEach((logLine) => {
|
|
||||||
$app.addGameLogEvent(logLine);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(this.isRunningUnderWine || LINUX) &&
|
|
||||||
--this.nextGameRunningCheck <= 0
|
|
||||||
) {
|
|
||||||
if (LINUX) {
|
|
||||||
this.nextGameRunningCheck = 1;
|
|
||||||
$app.updateIsGameRunning(
|
|
||||||
await AppApi.IsGameRunning(),
|
|
||||||
await AppApi.IsSteamVRRunning(),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.nextGameRunningCheck = 3;
|
|
||||||
AppApi.CheckGameRunning();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (--this.nextDatabaseOptimize <= 0) {
|
|
||||||
this.nextDatabaseOptimize = 86400; // 1 day
|
|
||||||
database.optimize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
API.isRefreshFriendsLoading = false;
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
workerTimers.setTimeout(() => this.updateLoop(), 1000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
import { userRequest } from '../api';
|
|
||||||
import database from '../service/database.js';
|
|
||||||
import utils from '../classes/utils';
|
|
||||||
import * as workerTimers from 'worker-timers';
|
|
||||||
|
|
||||||
const userNotes = {
|
|
||||||
lastNoteCheck: null,
|
|
||||||
lastDbNoteDate: null,
|
|
||||||
notes: new Map(),
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
this.lastNoteCheck = new Date();
|
|
||||||
this.lastDbNoteDate = null;
|
|
||||||
this.notes.clear();
|
|
||||||
try {
|
|
||||||
// todo: get users from store
|
|
||||||
const users = window.API.cachedUsers;
|
|
||||||
const dbNotes = await database.getAllUserNotes();
|
|
||||||
for (const note of dbNotes) {
|
|
||||||
this.notes.set(note.userId, note.note);
|
|
||||||
const user = users.get(note.userId);
|
|
||||||
if (user) {
|
|
||||||
user.note = note.note;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!this.lastDbNoteDate ||
|
|
||||||
this.lastDbNoteDate < note.createdAt
|
|
||||||
) {
|
|
||||||
this.lastDbNoteDate = note.createdAt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await this.getLatestUserNotes();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error initializing user notes:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getLatestUserNotes() {
|
|
||||||
this.lastNoteCheck = new Date();
|
|
||||||
const params = {
|
|
||||||
offset: 0,
|
|
||||||
n: 10 // start light
|
|
||||||
};
|
|
||||||
const newNotes = new Map();
|
|
||||||
let done = false;
|
|
||||||
try {
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
params.offset = i * params.n;
|
|
||||||
const args = await userRequest.getUserNotes(params);
|
|
||||||
for (const note of args.json) {
|
|
||||||
if (
|
|
||||||
this.lastDbNoteDate &&
|
|
||||||
this.lastDbNoteDate > note.createdAt
|
|
||||||
) {
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!this.lastDbNoteDate ||
|
|
||||||
this.lastDbNoteDate < note.createdAt
|
|
||||||
) {
|
|
||||||
this.lastDbNoteDate = note.createdAt;
|
|
||||||
}
|
|
||||||
note.note = utils.replaceBioSymbols(note.note);
|
|
||||||
newNotes.set(note.targetUserId, note);
|
|
||||||
}
|
|
||||||
if (done || args.json.length === 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
params.n = 100; // crank it after first run
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
workerTimers.setTimeout(resolve, 1000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching user notes:', error);
|
|
||||||
}
|
|
||||||
// todo: get users from store
|
|
||||||
const users = window.API.cachedUsers;
|
|
||||||
|
|
||||||
for (const note of newNotes.values()) {
|
|
||||||
const newNote = {
|
|
||||||
userId: note.targetUserId,
|
|
||||||
displayName: note.targetUser?.displayName || note.targetUserId,
|
|
||||||
note: note.note,
|
|
||||||
createdAt: note.createdAt
|
|
||||||
};
|
|
||||||
await database.addUserNote(newNote);
|
|
||||||
this.notes.set(note.targetUserId, note.note);
|
|
||||||
const user = users.get(note.targetUserId);
|
|
||||||
if (user) {
|
|
||||||
user.note = note.note;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async checkNote(userId, newNote) {
|
|
||||||
// last check was more than than 5 minutes ago
|
|
||||||
if (
|
|
||||||
!this.lastNoteCheck ||
|
|
||||||
this.lastNoteCheck.getTime() + 5 * 60 * 1000 > Date.now()
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const existingNote = this.notes.get(userId);
|
|
||||||
if (typeof existingNote !== 'undefined' && !newNote) {
|
|
||||||
console.log('deleting note', userId);
|
|
||||||
this.notes.delete(userId);
|
|
||||||
await database.deleteUserNote(userId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof existingNote === 'undefined' || existingNote !== newNote) {
|
|
||||||
console.log('detected note change', userId, newNote);
|
|
||||||
await this.getLatestUserNotes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { userNotes };
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
let echarts = null;
|
|
||||||
|
|
||||||
const _utils = {
|
|
||||||
removeFromArray(array, item) {
|
|
||||||
var { length } = array;
|
|
||||||
for (var i = 0; i < length; ++i) {
|
|
||||||
if (array[i] === item) {
|
|
||||||
array.splice(i, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
arraysMatch(a, b) {
|
|
||||||
if (!Array.isArray(a) || !Array.isArray(b)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
a.length === b.length &&
|
|
||||||
a.every(
|
|
||||||
(element, index) =>
|
|
||||||
JSON.stringify(element) === JSON.stringify(b[index])
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
moveArrayItem(array, fromIndex, toIndex) {
|
|
||||||
if (!Array.isArray(array) || fromIndex === toIndex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fromIndex < 0 || fromIndex >= array.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (toIndex < 0 || toIndex >= array.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const item = array[fromIndex];
|
|
||||||
array.splice(fromIndex, 1);
|
|
||||||
array.splice(toIndex, 0, item);
|
|
||||||
},
|
|
||||||
escapeTag(tag) {
|
|
||||||
var s = String(tag);
|
|
||||||
return s.replace(/["&'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
||||||
},
|
|
||||||
escapeTagRecursive(obj) {
|
|
||||||
if (typeof obj === 'string') {
|
|
||||||
return this.escapeTag(obj);
|
|
||||||
}
|
|
||||||
if (typeof obj === 'object') {
|
|
||||||
for (var key in obj) {
|
|
||||||
obj[key] = this.escapeTagRecursive(obj[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
timeToText(sec, isNeedSeconds = false) {
|
|
||||||
let n = Number(sec);
|
|
||||||
if (isNaN(n)) {
|
|
||||||
return this.escapeTag(sec);
|
|
||||||
}
|
|
||||||
n = Math.floor(n / 1000);
|
|
||||||
const arr = [];
|
|
||||||
if (n < 0) {
|
|
||||||
n = -n;
|
|
||||||
}
|
|
||||||
if (n >= 86400) {
|
|
||||||
arr.push(`${Math.floor(n / 86400)}d`);
|
|
||||||
n %= 86400;
|
|
||||||
}
|
|
||||||
if (n >= 3600) {
|
|
||||||
arr.push(`${Math.floor(n / 3600)}h`);
|
|
||||||
n %= 3600;
|
|
||||||
}
|
|
||||||
if (n >= 60) {
|
|
||||||
arr.push(`${Math.floor(n / 60)}m`);
|
|
||||||
n %= 60;
|
|
||||||
}
|
|
||||||
if (isNeedSeconds || (arr.length === 0 && n < 60)) {
|
|
||||||
arr.push(`${n}s`);
|
|
||||||
}
|
|
||||||
return arr.join(' ');
|
|
||||||
},
|
|
||||||
textToHex(text) {
|
|
||||||
var s = String(text);
|
|
||||||
return s
|
|
||||||
.split('')
|
|
||||||
.map((c) => c.charCodeAt(0).toString(16))
|
|
||||||
.join(' ');
|
|
||||||
},
|
|
||||||
commaNumber(num) {
|
|
||||||
if (!num) {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
var s = String(Number(num));
|
|
||||||
return s.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
|
||||||
},
|
|
||||||
buildTreeData(json) {
|
|
||||||
var node = [];
|
|
||||||
for (var key in json) {
|
|
||||||
if (key[0] === '$') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var value = json[key];
|
|
||||||
if (Array.isArray(value) && value.length === 0) {
|
|
||||||
node.push({
|
|
||||||
key,
|
|
||||||
value: '[]'
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
value === Object(value) &&
|
|
||||||
Object.keys(value).length === 0
|
|
||||||
) {
|
|
||||||
node.push({
|
|
||||||
key,
|
|
||||||
value: '{}'
|
|
||||||
});
|
|
||||||
} else if (Array.isArray(value)) {
|
|
||||||
node.push({
|
|
||||||
children: value.map((val, idx) => {
|
|
||||||
if (val === Object(val)) {
|
|
||||||
return {
|
|
||||||
children: this.buildTreeData(val),
|
|
||||||
key: idx
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
key: idx,
|
|
||||||
value: val
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
key
|
|
||||||
});
|
|
||||||
} else if (value === Object(value)) {
|
|
||||||
node.push({
|
|
||||||
children: this.buildTreeData(value),
|
|
||||||
key
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
node.push({
|
|
||||||
key,
|
|
||||||
value: String(value)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.sort(function (a, b) {
|
|
||||||
var A = String(a.key).toUpperCase();
|
|
||||||
var B = String(b.key).toUpperCase();
|
|
||||||
if (A < B) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (A > B) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
return node;
|
|
||||||
},
|
|
||||||
// descending
|
|
||||||
compareByCreatedAt(a, b) {
|
|
||||||
if (
|
|
||||||
typeof a.created_at !== 'string' ||
|
|
||||||
typeof b.created_at !== 'string'
|
|
||||||
) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
var A = a.created_at.toUpperCase();
|
|
||||||
var B = b.created_at.toUpperCase();
|
|
||||||
if (A < B) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (A > B) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
},
|
|
||||||
// lazy load echarts
|
|
||||||
loadEcharts() {
|
|
||||||
if (echarts) {
|
|
||||||
return Promise.resolve(echarts);
|
|
||||||
}
|
|
||||||
return import('echarts').then((module) => {
|
|
||||||
echarts = module;
|
|
||||||
return echarts;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// CJK character in Japanese, Korean, Chinese are different
|
|
||||||
// so change font-family order when users change language to display CJK character correctly
|
|
||||||
changeCJKorder(lang) {
|
|
||||||
const otherFonts = window
|
|
||||||
.getComputedStyle(document.body)
|
|
||||||
.fontFamily.split(',')
|
|
||||||
.filter((item) => !item.includes('Noto Sans'))
|
|
||||||
.join(', ');
|
|
||||||
const notoSans = 'Noto Sans';
|
|
||||||
|
|
||||||
const fontFamilies = {
|
|
||||||
ja_JP: ['JP', 'KR', 'TC', 'SC'],
|
|
||||||
ko: ['KR', 'JP', 'TC', 'SC'],
|
|
||||||
zh_TW: ['TC', 'JP', 'KR', 'SC'],
|
|
||||||
zh_CN: ['SC', 'JP', 'KR', 'TC']
|
|
||||||
};
|
|
||||||
|
|
||||||
if (fontFamilies[lang]) {
|
|
||||||
const CJKFamily = fontFamilies[lang]
|
|
||||||
.map((item) => `${notoSans} ${item}`)
|
|
||||||
.join(', ');
|
|
||||||
document.body.style.fontFamily = `${CJKFamily}, ${otherFonts}`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
localeIncludes(str, search, comparer) {
|
|
||||||
// These checks are stolen from https://stackoverflow.com/a/69623589/11030436
|
|
||||||
if (search === '') {
|
|
||||||
return true;
|
|
||||||
} else if (!str || !search) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const strObj = String(str);
|
|
||||||
const searchObj = String(search);
|
|
||||||
|
|
||||||
if (strObj.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchObj.length > strObj.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now simply loop through each substring and compare them
|
|
||||||
for (let i = 0; i < str.length - searchObj.length + 1; i++) {
|
|
||||||
const substr = strObj.substring(i, i + searchObj.length);
|
|
||||||
if (comparer.compare(substr, searchObj) === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
compareByName(a, b) {
|
|
||||||
if (typeof a.name !== 'string' || typeof b.name !== 'string') {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
},
|
|
||||||
replaceBioSymbols(text) {
|
|
||||||
if (!text) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
var symbolList = {
|
|
||||||
'@': '@',
|
|
||||||
'#': '#',
|
|
||||||
$: '$',
|
|
||||||
'%': '%',
|
|
||||||
'&': '&',
|
|
||||||
'=': '=',
|
|
||||||
'+': '+',
|
|
||||||
'/': '⁄',
|
|
||||||
'\\': '\',
|
|
||||||
';': ';',
|
|
||||||
':': '˸',
|
|
||||||
',': '‚',
|
|
||||||
'?': '?',
|
|
||||||
'!': 'ǃ',
|
|
||||||
'"': '"',
|
|
||||||
'<': '≺',
|
|
||||||
'>': '≻',
|
|
||||||
'.': '․',
|
|
||||||
'^': '^',
|
|
||||||
'{': '{',
|
|
||||||
'}': '}',
|
|
||||||
'[': '[',
|
|
||||||
']': ']',
|
|
||||||
'(': '(',
|
|
||||||
')': ')',
|
|
||||||
'|': '|',
|
|
||||||
'*': '∗'
|
|
||||||
};
|
|
||||||
var newText = text;
|
|
||||||
for (var key in symbolList) {
|
|
||||||
var regex = new RegExp(symbolList[key], 'g');
|
|
||||||
newText = newText.replace(regex, key);
|
|
||||||
}
|
|
||||||
return newText.replace(/ {1,}/g, ' ').trimRight();
|
|
||||||
},
|
|
||||||
// descending
|
|
||||||
compareByUpdatedAt(a, b) {
|
|
||||||
if (
|
|
||||||
typeof a.updated_at !== 'string' ||
|
|
||||||
typeof b.updated_at !== 'string'
|
|
||||||
) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
var A = a.updated_at.toUpperCase();
|
|
||||||
var B = b.updated_at.toUpperCase();
|
|
||||||
if (A < B) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (A > B) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default _utils;
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
import configRepository from '../service/config.js';
|
|
||||||
import { baseClass, $t, $utils } from './baseClass.js';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
_data = {};
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
async backupVrcRegistry(name) {
|
|
||||||
var regJson;
|
|
||||||
if (LINUX) {
|
|
||||||
regJson = await AppApi.GetVRChatRegistryJson();
|
|
||||||
regJson = JSON.parse(regJson);
|
|
||||||
} else {
|
|
||||||
regJson = await AppApi.GetVRChatRegistry();
|
|
||||||
}
|
|
||||||
var newBackup = {
|
|
||||||
name,
|
|
||||||
date: new Date().toJSON(),
|
|
||||||
data: regJson
|
|
||||||
};
|
|
||||||
var backupsJson = await configRepository.getString(
|
|
||||||
'VRCX_VRChatRegistryBackups'
|
|
||||||
);
|
|
||||||
if (!backupsJson) {
|
|
||||||
backupsJson = JSON.stringify([]);
|
|
||||||
}
|
|
||||||
var backups = JSON.parse(backupsJson);
|
|
||||||
backups.push(newBackup);
|
|
||||||
await configRepository.setString(
|
|
||||||
'VRCX_VRChatRegistryBackups',
|
|
||||||
JSON.stringify(backups)
|
|
||||||
);
|
|
||||||
// await this.updateRegistryBackupDialog();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Because it is a startup func, it is not integrated into RegistryBackupDialog.vue now
|
|
||||||
// func backupVrcRegistry is also split up
|
|
||||||
async checkAutoBackupRestoreVrcRegistry() {
|
|
||||||
if (!this.vrcRegistryAutoBackup) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for auto restore
|
|
||||||
var hasVRChatRegistryFolder =
|
|
||||||
await AppApi.HasVRChatRegistryFolder();
|
|
||||||
if (!hasVRChatRegistryFolder) {
|
|
||||||
var lastBackupDate = await configRepository.getString(
|
|
||||||
'VRCX_VRChatRegistryLastBackupDate'
|
|
||||||
);
|
|
||||||
var lastRestoreCheck = await configRepository.getString(
|
|
||||||
'VRCX_VRChatRegistryLastRestoreCheck'
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
!lastBackupDate ||
|
|
||||||
(lastRestoreCheck &&
|
|
||||||
lastBackupDate &&
|
|
||||||
lastRestoreCheck === lastBackupDate)
|
|
||||||
) {
|
|
||||||
// only ask to restore once and when backup is present
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// popup message about auto restore
|
|
||||||
this.$alert(
|
|
||||||
$t('dialog.registry_backup.restore_prompt'),
|
|
||||||
$t('dialog.registry_backup.header')
|
|
||||||
);
|
|
||||||
this.showRegistryBackupDialog();
|
|
||||||
await AppApi.FocusWindow();
|
|
||||||
await configRepository.setString(
|
|
||||||
'VRCX_VRChatRegistryLastRestoreCheck',
|
|
||||||
lastBackupDate
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await this.autoBackupVrcRegistry();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async autoBackupVrcRegistry() {
|
|
||||||
var date = new Date();
|
|
||||||
var lastBackupDate = await configRepository.getString(
|
|
||||||
'VRCX_VRChatRegistryLastBackupDate'
|
|
||||||
);
|
|
||||||
if (lastBackupDate) {
|
|
||||||
var lastBackup = new Date(lastBackupDate);
|
|
||||||
var diff = date.getTime() - lastBackup.getTime();
|
|
||||||
var diffDays = Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
||||||
if (diffDays < 7) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var backupsJson = await configRepository.getString(
|
|
||||||
'VRCX_VRChatRegistryBackups'
|
|
||||||
);
|
|
||||||
if (!backupsJson) {
|
|
||||||
backupsJson = JSON.stringify([]);
|
|
||||||
}
|
|
||||||
var backups = JSON.parse(backupsJson);
|
|
||||||
backups.forEach((backup) => {
|
|
||||||
if (backup.name === 'Auto Backup') {
|
|
||||||
// remove old auto backup
|
|
||||||
$utils.removeFromArray(backups, backup);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await configRepository.setString(
|
|
||||||
'VRCX_VRChatRegistryBackups',
|
|
||||||
JSON.stringify(backups)
|
|
||||||
);
|
|
||||||
this.backupVrcRegistry('Auto Backup');
|
|
||||||
await configRepository.setString(
|
|
||||||
'VRCX_VRChatRegistryLastBackupDate',
|
|
||||||
date.toJSON()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,351 +0,0 @@
|
|||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
import * as workerTimers from 'worker-timers';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {
|
|
||||||
VRCXUpdateDialog: {
|
|
||||||
visible: false,
|
|
||||||
updatePending: false,
|
|
||||||
updatePendingIsLatest: false,
|
|
||||||
release: '',
|
|
||||||
releases: [],
|
|
||||||
json: {}
|
|
||||||
},
|
|
||||||
branch: 'Stable',
|
|
||||||
autoUpdateVRCX: 'Auto Download',
|
|
||||||
checkingForVRCXUpdate: false,
|
|
||||||
pendingVRCXInstall: '',
|
|
||||||
pendingVRCXUpdate: false,
|
|
||||||
branches: {
|
|
||||||
Stable: {
|
|
||||||
name: 'Stable',
|
|
||||||
urlReleases: 'https://api0.vrcx.app/releases/stable',
|
|
||||||
urlLatest: 'https://api0.vrcx.app/releases/stable/latest'
|
|
||||||
},
|
|
||||||
Nightly: {
|
|
||||||
name: 'Nightly',
|
|
||||||
urlReleases: 'https://api0.vrcx.app/releases/nightly',
|
|
||||||
urlLatest: 'https://api0.vrcx.app/releases/nightly/latest'
|
|
||||||
}
|
|
||||||
// LinuxTest: {
|
|
||||||
// name: 'LinuxTest',
|
|
||||||
// urlReleases: 'https://api.github.com/repos/rs189/VRCX/releases',
|
|
||||||
// urlLatest:
|
|
||||||
// 'https://api.github.com/repos/rs189/VRCX/releases/latest'
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
updateProgress: 0,
|
|
||||||
updateInProgress: false
|
|
||||||
};
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
async showVRCXUpdateDialog() {
|
|
||||||
var D = this.VRCXUpdateDialog;
|
|
||||||
D.visible = true;
|
|
||||||
D.updatePendingIsLatest = false;
|
|
||||||
D.updatePending = await AppApi.CheckForUpdateExe();
|
|
||||||
this.loadBranchVersions();
|
|
||||||
},
|
|
||||||
|
|
||||||
async downloadVRCXUpdate(
|
|
||||||
downloadUrl,
|
|
||||||
downloadName,
|
|
||||||
hashUrl,
|
|
||||||
size,
|
|
||||||
releaseName,
|
|
||||||
type
|
|
||||||
) {
|
|
||||||
if (this.updateInProgress) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.updateInProgress = true;
|
|
||||||
this.downloadFileProgress();
|
|
||||||
await AppApi.DownloadUpdate(
|
|
||||||
downloadUrl,
|
|
||||||
downloadName,
|
|
||||||
hashUrl,
|
|
||||||
size
|
|
||||||
);
|
|
||||||
this.pendingVRCXInstall = releaseName;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
this.$message({
|
|
||||||
message: `${$t('message.vrcx_updater.failed_install')} ${err}`,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
this.updateInProgress = false;
|
|
||||||
this.updateProgress = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async cancelUpdate() {
|
|
||||||
await AppApi.CancelUpdate();
|
|
||||||
this.updateInProgress = false;
|
|
||||||
this.updateProgress = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
async downloadFileProgress() {
|
|
||||||
this.updateProgress = await AppApi.CheckUpdateProgress();
|
|
||||||
if (this.updateInProgress) {
|
|
||||||
workerTimers.setTimeout(() => this.downloadFileProgress(), 150);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateProgressText() {
|
|
||||||
if (this.updateProgress === 100) {
|
|
||||||
return $t('message.vrcx_updater.checking_hash');
|
|
||||||
}
|
|
||||||
return `${this.updateProgress}%`;
|
|
||||||
},
|
|
||||||
|
|
||||||
installVRCXUpdate() {
|
|
||||||
for (var release of this.VRCXUpdateDialog.releases) {
|
|
||||||
if (release.name !== this.VRCXUpdateDialog.release) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var downloadUrl = '';
|
|
||||||
var downloadName = '';
|
|
||||||
var hashUrl = '';
|
|
||||||
var size = 0;
|
|
||||||
for (var asset of release.assets) {
|
|
||||||
if (asset.state !== 'uploaded') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
WINDOWS &&
|
|
||||||
(asset.content_type === 'application/x-msdownload' ||
|
|
||||||
asset.content_type ===
|
|
||||||
'application/x-msdos-program')
|
|
||||||
) {
|
|
||||||
downloadUrl = asset.browser_download_url;
|
|
||||||
downloadName = asset.name;
|
|
||||||
size = asset.size;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
LINUX &&
|
|
||||||
asset.content_type === 'application/octet-stream'
|
|
||||||
) {
|
|
||||||
downloadUrl = asset.browser_download_url;
|
|
||||||
downloadName = asset.name;
|
|
||||||
size = asset.size;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
asset.name === 'SHA256SUMS.txt' &&
|
|
||||||
asset.content_type === 'text/plain'
|
|
||||||
) {
|
|
||||||
hashUrl = asset.browser_download_url;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!downloadUrl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var releaseName = release.name;
|
|
||||||
var type = 'Manual';
|
|
||||||
this.downloadVRCXUpdate(
|
|
||||||
downloadUrl,
|
|
||||||
downloadName,
|
|
||||||
hashUrl,
|
|
||||||
size,
|
|
||||||
releaseName,
|
|
||||||
type
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async loadBranchVersions() {
|
|
||||||
var D = this.VRCXUpdateDialog;
|
|
||||||
var url = this.branches[this.branch].urlReleases;
|
|
||||||
this.checkingForVRCXUpdate = true;
|
|
||||||
try {
|
|
||||||
var response = await webApiService.execute({
|
|
||||||
url,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'VRCX-ID': this.vrcxId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
this.checkingForVRCXUpdate = false;
|
|
||||||
}
|
|
||||||
var json = JSON.parse(response.data);
|
|
||||||
if (this.debugWebRequests) {
|
|
||||||
console.log(json, response);
|
|
||||||
}
|
|
||||||
var releases = [];
|
|
||||||
if (typeof json !== 'object' || json.message) {
|
|
||||||
$app.$message({
|
|
||||||
message: $t('message.vrcx_updater.failed', {
|
|
||||||
message: json.message
|
|
||||||
}),
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (var release of json) {
|
|
||||||
for (var asset of release.assets) {
|
|
||||||
if (
|
|
||||||
(asset.content_type === 'application/x-msdownload' ||
|
|
||||||
asset.content_type ===
|
|
||||||
'application/x-msdos-program') &&
|
|
||||||
asset.state === 'uploaded'
|
|
||||||
) {
|
|
||||||
releases.push(release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
D.releases = releases;
|
|
||||||
D.release = json[0].name;
|
|
||||||
this.VRCXUpdateDialog.updatePendingIsLatest = false;
|
|
||||||
if (D.release === this.pendingVRCXInstall) {
|
|
||||||
// update already downloaded and latest version
|
|
||||||
this.VRCXUpdateDialog.updatePendingIsLatest = true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(await configRepository.getString('VRCX_branch')) !==
|
|
||||||
this.branch
|
|
||||||
) {
|
|
||||||
await configRepository.setString('VRCX_branch', this.branch);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async checkForVRCXUpdate() {
|
|
||||||
var currentVersion = this.appVersion.replace(' (Linux)', '');
|
|
||||||
if (
|
|
||||||
!currentVersion ||
|
|
||||||
currentVersion === 'VRCX Nightly Build' ||
|
|
||||||
currentVersion === 'VRCX Build'
|
|
||||||
) {
|
|
||||||
// ignore custom builds
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.branch === 'Beta') {
|
|
||||||
// move Beta users to stable
|
|
||||||
this.branch = 'Stable';
|
|
||||||
await configRepository.setString('VRCX_branch', this.branch);
|
|
||||||
}
|
|
||||||
if (typeof this.branches[this.branch] === 'undefined') {
|
|
||||||
// handle invalid branch
|
|
||||||
this.branch = 'Stable';
|
|
||||||
await configRepository.setString('VRCX_branch', this.branch);
|
|
||||||
}
|
|
||||||
var url = this.branches[this.branch].urlLatest;
|
|
||||||
this.checkingForVRCXUpdate = true;
|
|
||||||
try {
|
|
||||||
var response = await webApiService.execute({
|
|
||||||
url,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'VRCX-ID': this.vrcxId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
this.checkingForVRCXUpdate = false;
|
|
||||||
}
|
|
||||||
this.pendingVRCXUpdate = false;
|
|
||||||
var json = JSON.parse(response.data);
|
|
||||||
if (this.debugWebRequests) {
|
|
||||||
console.log(json, response);
|
|
||||||
}
|
|
||||||
if (json === Object(json) && json.name && json.published_at) {
|
|
||||||
this.VRCXUpdateDialog.updateJson = json;
|
|
||||||
this.changeLogDialog.buildName = json.name;
|
|
||||||
this.changeLogDialog.changeLog = this.changeLogRemoveLinks(
|
|
||||||
json.body
|
|
||||||
);
|
|
||||||
var releaseName = json.name;
|
|
||||||
this.latestAppVersion = releaseName;
|
|
||||||
this.VRCXUpdateDialog.updatePendingIsLatest = false;
|
|
||||||
if (releaseName === this.pendingVRCXInstall) {
|
|
||||||
// update already downloaded
|
|
||||||
this.VRCXUpdateDialog.updatePendingIsLatest = true;
|
|
||||||
} else if (releaseName > currentVersion) {
|
|
||||||
var downloadUrl = '';
|
|
||||||
var downloadName = '';
|
|
||||||
var hashUrl = '';
|
|
||||||
var size = 0;
|
|
||||||
for (var asset of json.assets) {
|
|
||||||
if (asset.state !== 'uploaded') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!LINUX &&
|
|
||||||
(asset.content_type ===
|
|
||||||
'application/x-msdownload' ||
|
|
||||||
asset.content_type ===
|
|
||||||
'application/x-msdos-program')
|
|
||||||
) {
|
|
||||||
downloadUrl = asset.browser_download_url;
|
|
||||||
downloadName = asset.name;
|
|
||||||
size = asset.size;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
LINUX &&
|
|
||||||
asset.content_type === 'application/octet-stream'
|
|
||||||
) {
|
|
||||||
downloadUrl = asset.browser_download_url;
|
|
||||||
downloadName = asset.name;
|
|
||||||
size = asset.size;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
asset.name === 'SHA256SUMS.txt' &&
|
|
||||||
asset.content_type === 'text/plain'
|
|
||||||
) {
|
|
||||||
hashUrl = asset.browser_download_url;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!downloadUrl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.pendingVRCXUpdate = true;
|
|
||||||
this.notifyMenu('settings');
|
|
||||||
var type = 'Auto';
|
|
||||||
if (!API.isLoggedIn) {
|
|
||||||
this.showVRCXUpdateDialog();
|
|
||||||
} else if (this.autoUpdateVRCX === 'Notify') {
|
|
||||||
// this.showVRCXUpdateDialog();
|
|
||||||
} else if (this.autoUpdateVRCX === 'Auto Download') {
|
|
||||||
this.downloadVRCXUpdate(
|
|
||||||
downloadUrl,
|
|
||||||
downloadName,
|
|
||||||
hashUrl,
|
|
||||||
size,
|
|
||||||
releaseName,
|
|
||||||
type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
restartVRCX(isUpgrade) {
|
|
||||||
if (!LINUX) {
|
|
||||||
AppApi.RestartApplication(isUpgrade);
|
|
||||||
} else {
|
|
||||||
window.electron.restartApp();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveAutoUpdateVRCX() {
|
|
||||||
if (this.autoUpdateVRCX === 'Off') {
|
|
||||||
this.pendingVRCXUpdate = false;
|
|
||||||
}
|
|
||||||
await configRepository.setString(
|
|
||||||
'VRCX_autoUpdateVRCX',
|
|
||||||
this.autoUpdateVRCX
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,620 +0,0 @@
|
|||||||
import * as workerTimers from 'worker-timers';
|
|
||||||
import Noty from 'noty';
|
|
||||||
import { parseLocation } from '../composables/instance/utils';
|
|
||||||
import { baseClass, $app, API, $utils } from './baseClass.js';
|
|
||||||
import { groupRequest } from '../api';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
API.webSocket = null;
|
|
||||||
API.lastWebSocketMessage = '';
|
|
||||||
|
|
||||||
API.$on('USER:CURRENT', function () {
|
|
||||||
if ($app.friendLogInitStatus && this.webSocket === null) {
|
|
||||||
this.getAuth();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
API.getAuth = function () {
|
|
||||||
return this.call('auth', {
|
|
||||||
method: 'GET'
|
|
||||||
}).then((json) => {
|
|
||||||
var args = {
|
|
||||||
json
|
|
||||||
};
|
|
||||||
this.$emit('AUTH', args);
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
API.$on('AUTH', function (args) {
|
|
||||||
if (args.json.ok) {
|
|
||||||
this.connectWebSocket(args.json.token);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
API.connectWebSocket = function (token) {
|
|
||||||
if (this.webSocket !== null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var socket = new WebSocket(`${API.websocketDomain}/?auth=${token}`);
|
|
||||||
socket.onopen = () => {
|
|
||||||
if ($app.debugWebSocket) {
|
|
||||||
console.log('WebSocket connected');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
socket.onclose = () => {
|
|
||||||
if (this.webSocket === socket) {
|
|
||||||
this.webSocket = null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
socket.close();
|
|
||||||
} catch (err) {}
|
|
||||||
if ($app.debugWebSocket) {
|
|
||||||
console.log('WebSocket closed');
|
|
||||||
}
|
|
||||||
workerTimers.setTimeout(() => {
|
|
||||||
if (
|
|
||||||
this.isLoggedIn &&
|
|
||||||
$app.friendLogInitStatus &&
|
|
||||||
this.webSocket === null
|
|
||||||
) {
|
|
||||||
this.getAuth();
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
};
|
|
||||||
socket.onerror = () => {
|
|
||||||
if (this.errorNoty) {
|
|
||||||
this.errorNoty.close();
|
|
||||||
}
|
|
||||||
this.errorNoty = new Noty({
|
|
||||||
type: 'error',
|
|
||||||
text: 'WebSocket Error'
|
|
||||||
}).show();
|
|
||||||
socket.onclose();
|
|
||||||
};
|
|
||||||
socket.onmessage = ({ data }) => {
|
|
||||||
try {
|
|
||||||
if (this.lastWebSocketMessage === data) {
|
|
||||||
// pls no spam
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.lastWebSocketMessage = data;
|
|
||||||
var json = JSON.parse(data);
|
|
||||||
try {
|
|
||||||
json.content = JSON.parse(json.content);
|
|
||||||
} catch (err) {}
|
|
||||||
this.$emit('PIPELINE', {
|
|
||||||
json
|
|
||||||
});
|
|
||||||
if ($app.debugWebSocket && json.content) {
|
|
||||||
var displayName = '';
|
|
||||||
var user = this.cachedUsers.get(json.content.userId);
|
|
||||||
if (user) {
|
|
||||||
displayName = user.displayName;
|
|
||||||
}
|
|
||||||
console.log(
|
|
||||||
'WebSocket',
|
|
||||||
json.type,
|
|
||||||
displayName,
|
|
||||||
json.content
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.webSocket = socket;
|
|
||||||
};
|
|
||||||
|
|
||||||
API.$on('LOGOUT', function () {
|
|
||||||
this.closeWebSocket();
|
|
||||||
});
|
|
||||||
|
|
||||||
API.closeWebSocket = function () {
|
|
||||||
var socket = this.webSocket;
|
|
||||||
if (socket === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.webSocket = null;
|
|
||||||
try {
|
|
||||||
socket.close();
|
|
||||||
} catch (err) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
API.reconnectWebSocket = function () {
|
|
||||||
if (!this.isLoggedIn || !$app.friendLogInitStatus) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.closeWebSocket();
|
|
||||||
this.getAuth();
|
|
||||||
};
|
|
||||||
|
|
||||||
API.$on('PIPELINE', function (args) {
|
|
||||||
var { type, content, err } = args.json;
|
|
||||||
if (typeof err !== 'undefined') {
|
|
||||||
console.error('PIPELINE: error', args);
|
|
||||||
if (this.errorNoty) {
|
|
||||||
this.errorNoty.close();
|
|
||||||
}
|
|
||||||
this.errorNoty = new Noty({
|
|
||||||
type: 'error',
|
|
||||||
text: $app.escapeTag(`WebSocket Error: ${err}`)
|
|
||||||
}).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof content === 'undefined') {
|
|
||||||
console.error('PIPELINE: missing content', args);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof content.user !== 'undefined') {
|
|
||||||
// I forgot about this...
|
|
||||||
delete content.user.state;
|
|
||||||
}
|
|
||||||
switch (type) {
|
|
||||||
case 'notification':
|
|
||||||
this.$emit('NOTIFICATION', {
|
|
||||||
json: content,
|
|
||||||
params: {
|
|
||||||
notificationId: content.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.$emit('PIPELINE:NOTIFICATION', {
|
|
||||||
json: content,
|
|
||||||
params: {
|
|
||||||
notificationId: content.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'notification-v2':
|
|
||||||
console.log('notification-v2', content);
|
|
||||||
this.$emit('NOTIFICATION:V2', {
|
|
||||||
json: content,
|
|
||||||
params: {
|
|
||||||
notificationId: content.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'notification-v2-delete':
|
|
||||||
console.log('notification-v2-delete', content);
|
|
||||||
for (var id of content.ids) {
|
|
||||||
this.$emit('NOTIFICATION:HIDE', {
|
|
||||||
params: {
|
|
||||||
notificationId: id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.$emit('NOTIFICATION:SEE', {
|
|
||||||
params: {
|
|
||||||
notificationId: id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'notification-v2-update':
|
|
||||||
console.log('notification-v2-update', content);
|
|
||||||
this.$emit('NOTIFICATION:V2:UPDATE', {
|
|
||||||
json: content.updates,
|
|
||||||
params: {
|
|
||||||
notificationId: content.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'see-notification':
|
|
||||||
this.$emit('NOTIFICATION:SEE', {
|
|
||||||
params: {
|
|
||||||
notificationId: content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'hide-notification':
|
|
||||||
this.$emit('NOTIFICATION:HIDE', {
|
|
||||||
params: {
|
|
||||||
notificationId: content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.$emit('NOTIFICATION:SEE', {
|
|
||||||
params: {
|
|
||||||
notificationId: content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'response-notification':
|
|
||||||
this.$emit('NOTIFICATION:HIDE', {
|
|
||||||
params: {
|
|
||||||
notificationId: content.notificationId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.$emit('NOTIFICATION:SEE', {
|
|
||||||
params: {
|
|
||||||
notificationId: content.notificationId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'friend-add':
|
|
||||||
this.$emit('USER', {
|
|
||||||
json: content.user,
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.$emit('FRIEND:ADD', {
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'friend-delete':
|
|
||||||
this.$emit('FRIEND:DELETE', {
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'friend-online':
|
|
||||||
// Where is instanceId, travelingToWorld, travelingToInstance?
|
|
||||||
// More JANK, what a mess
|
|
||||||
var $location = parseLocation(content.location);
|
|
||||||
var $travelingToLocation = parseLocation(
|
|
||||||
content.travelingToLocation
|
|
||||||
);
|
|
||||||
if (content?.user?.id) {
|
|
||||||
this.$emit('USER', {
|
|
||||||
json: {
|
|
||||||
id: content.userId,
|
|
||||||
platform: content.platform,
|
|
||||||
state: 'online',
|
|
||||||
|
|
||||||
location: content.location,
|
|
||||||
worldId: content.worldId,
|
|
||||||
instanceId: $location.instanceId,
|
|
||||||
travelingToLocation:
|
|
||||||
content.travelingToLocation,
|
|
||||||
travelingToWorld: $travelingToLocation.worldId,
|
|
||||||
travelingToInstance:
|
|
||||||
$travelingToLocation.instanceId,
|
|
||||||
|
|
||||||
...content.user
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$emit('FRIEND:STATE', {
|
|
||||||
json: {
|
|
||||||
state: 'online'
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'friend-active':
|
|
||||||
if (content?.user?.id) {
|
|
||||||
this.$emit('USER', {
|
|
||||||
json: {
|
|
||||||
id: content.userId,
|
|
||||||
platform: content.platform,
|
|
||||||
state: 'active',
|
|
||||||
|
|
||||||
location: 'offline',
|
|
||||||
worldId: 'offline',
|
|
||||||
instanceId: 'offline',
|
|
||||||
travelingToLocation: 'offline',
|
|
||||||
travelingToWorld: 'offline',
|
|
||||||
travelingToInstance: 'offline',
|
|
||||||
|
|
||||||
...content.user
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$emit('FRIEND:STATE', {
|
|
||||||
json: {
|
|
||||||
state: 'active'
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'friend-offline':
|
|
||||||
// more JANK, hell yeah
|
|
||||||
this.$emit('USER', {
|
|
||||||
json: {
|
|
||||||
id: content.userId,
|
|
||||||
platform: content.platform,
|
|
||||||
state: 'offline',
|
|
||||||
|
|
||||||
location: 'offline',
|
|
||||||
worldId: 'offline',
|
|
||||||
instanceId: 'offline',
|
|
||||||
travelingToLocation: 'offline',
|
|
||||||
travelingToWorld: 'offline',
|
|
||||||
travelingToInstance: 'offline'
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'friend-update':
|
|
||||||
this.$emit('USER', {
|
|
||||||
json: content.user,
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'friend-location':
|
|
||||||
var $location = parseLocation(content.location);
|
|
||||||
var $travelingToLocation = parseLocation(
|
|
||||||
content.travelingToLocation
|
|
||||||
);
|
|
||||||
if (!content?.user?.id) {
|
|
||||||
var ref = this.cachedUsers.get(content.userId);
|
|
||||||
if (typeof ref !== 'undefined') {
|
|
||||||
this.$emit('USER', {
|
|
||||||
json: {
|
|
||||||
...ref,
|
|
||||||
location: content.location,
|
|
||||||
worldId: content.worldId,
|
|
||||||
instanceId: $location.instanceId,
|
|
||||||
travelingToLocation:
|
|
||||||
content.travelingToLocation,
|
|
||||||
travelingToWorld:
|
|
||||||
$travelingToLocation.worldId,
|
|
||||||
travelingToInstance:
|
|
||||||
$travelingToLocation.instanceId
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.$emit('USER', {
|
|
||||||
json: {
|
|
||||||
location: content.location,
|
|
||||||
worldId: content.worldId,
|
|
||||||
instanceId: $location.instanceId,
|
|
||||||
travelingToLocation: content.travelingToLocation,
|
|
||||||
travelingToWorld: $travelingToLocation.worldId,
|
|
||||||
travelingToInstance:
|
|
||||||
$travelingToLocation.instanceId,
|
|
||||||
...content.user,
|
|
||||||
state: 'online' // JANK
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'user-update':
|
|
||||||
this.$emit('USER:CURRENT', {
|
|
||||||
json: content.user,
|
|
||||||
params: {
|
|
||||||
userId: content.userId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'user-location':
|
|
||||||
// update current user location
|
|
||||||
if (content.userId !== this.currentUser.id) {
|
|
||||||
console.error('user-location wrong userId', content);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// content.user: {} // we don't trust this
|
|
||||||
// content.world: {} // this is long gone
|
|
||||||
// content.worldId // where did worldId go?
|
|
||||||
// content.instance // without worldId, this is useless
|
|
||||||
|
|
||||||
$app.setCurrentUserLocation(
|
|
||||||
content.location,
|
|
||||||
content.travelingToLocation
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'group-joined':
|
|
||||||
// var groupId = content.groupId;
|
|
||||||
// $app.onGroupJoined(groupId);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'group-left':
|
|
||||||
// var groupId = content.groupId;
|
|
||||||
// $app.onGroupLeft(groupId);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'group-role-updated':
|
|
||||||
var groupId = content.role.groupId;
|
|
||||||
groupRequest.getGroup({ groupId, includeRoles: true });
|
|
||||||
console.log('group-role-updated', content);
|
|
||||||
|
|
||||||
// content {
|
|
||||||
// role: {
|
|
||||||
// createdAt: string,
|
|
||||||
// description: string,
|
|
||||||
// groupId: string,
|
|
||||||
// id: string,
|
|
||||||
// isManagementRole: boolean,
|
|
||||||
// isSelfAssignable: boolean,
|
|
||||||
// name: string,
|
|
||||||
// order: number,
|
|
||||||
// permissions: string[],
|
|
||||||
// requiresPurchase: boolean,
|
|
||||||
// requiresTwoFactor: boolean
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'group-member-updated':
|
|
||||||
var member = content.member;
|
|
||||||
if (!member) {
|
|
||||||
console.error(
|
|
||||||
'group-member-updated missing member',
|
|
||||||
content
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
var groupId = member.groupId;
|
|
||||||
if (
|
|
||||||
$app.groupDialog.visible &&
|
|
||||||
$app.groupDialog.id === groupId
|
|
||||||
) {
|
|
||||||
$app.getGroupDialogGroup(groupId);
|
|
||||||
}
|
|
||||||
this.$emit('GROUP:MEMBER', {
|
|
||||||
json: member,
|
|
||||||
params: {
|
|
||||||
groupId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log('group-member-updated', member);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'instance-queue-joined':
|
|
||||||
case 'instance-queue-position':
|
|
||||||
var instanceId = content.instanceLocation;
|
|
||||||
var position = content.position ?? 0;
|
|
||||||
var queueSize = content.queueSize ?? 0;
|
|
||||||
$app.instanceQueueUpdate(instanceId, position, queueSize);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'instance-queue-ready':
|
|
||||||
var instanceId = content.instanceLocation;
|
|
||||||
// var expiry = Date.parse(content.expiry);
|
|
||||||
$app.instanceQueueReady(instanceId);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'instance-queue-left':
|
|
||||||
var instanceId = content.instanceLocation;
|
|
||||||
$app.removeQueuedInstance(instanceId);
|
|
||||||
// $app.instanceQueueClear();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'content-refresh':
|
|
||||||
var contentType = content.contentType;
|
|
||||||
console.log('content-refresh', content);
|
|
||||||
if (contentType === 'icon') {
|
|
||||||
if (
|
|
||||||
$app.galleryDialogVisible &&
|
|
||||||
!$app.galleryDialogIconsLoading
|
|
||||||
) {
|
|
||||||
$app.refreshVRCPlusIconsTable();
|
|
||||||
}
|
|
||||||
} else if (contentType === 'gallery') {
|
|
||||||
if (
|
|
||||||
$app.galleryDialogVisible &&
|
|
||||||
!$app.galleryDialogGalleryLoading
|
|
||||||
) {
|
|
||||||
$app.refreshGalleryTable();
|
|
||||||
}
|
|
||||||
} else if (contentType === 'emoji') {
|
|
||||||
if (
|
|
||||||
$app.galleryDialogVisible &&
|
|
||||||
!$app.galleryDialogEmojisLoading
|
|
||||||
) {
|
|
||||||
$app.refreshEmojiTable();
|
|
||||||
}
|
|
||||||
} else if (contentType === 'sticker') {
|
|
||||||
// on sticker upload
|
|
||||||
} else if (contentType === 'print') {
|
|
||||||
if (
|
|
||||||
$app.autoDeleteOldPrints &&
|
|
||||||
content.actionType === 'created'
|
|
||||||
) {
|
|
||||||
$app.tryDeleteOldPrints();
|
|
||||||
} else if (
|
|
||||||
$app.galleryDialogVisible &&
|
|
||||||
!$app.galleryDialogPrintsLoading
|
|
||||||
) {
|
|
||||||
$app.refreshPrintTable();
|
|
||||||
}
|
|
||||||
} else if (contentType === 'prints') {
|
|
||||||
// lol
|
|
||||||
} else if (contentType === 'avatar') {
|
|
||||||
// hmm, utilizing this might be too spamy and cause UI to move around
|
|
||||||
} else if (contentType === 'world') {
|
|
||||||
// hmm
|
|
||||||
} else if (contentType === 'created') {
|
|
||||||
// on avatar upload, might be gone now
|
|
||||||
} else if (contentType === 'avatargallery') {
|
|
||||||
// on avatar gallery image upload
|
|
||||||
} else if (contentType === 'invitePhoto') {
|
|
||||||
// on uploading invite photo
|
|
||||||
} else if (contentType === 'inventory') {
|
|
||||||
if (
|
|
||||||
$app.galleryDialogVisible &&
|
|
||||||
!$app.galleryDialogInventoryLoading
|
|
||||||
) {
|
|
||||||
$app.getInventory();
|
|
||||||
}
|
|
||||||
// on consuming a bundle
|
|
||||||
// {contentType: 'inventory', itemId: 'inv_', itemType: 'prop', actionType: 'add'}
|
|
||||||
} else if (!contentType) {
|
|
||||||
console.log(
|
|
||||||
'content-refresh without contentType',
|
|
||||||
content
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
'Unknown content-refresh type',
|
|
||||||
content.contentType
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'instance-closed':
|
|
||||||
// TODO: get worldName, groupName, hardClose
|
|
||||||
var noty = {
|
|
||||||
type: 'instance.closed',
|
|
||||||
location: content.instanceLocation,
|
|
||||||
message: 'Instance Closed',
|
|
||||||
created_at: new Date().toJSON()
|
|
||||||
};
|
|
||||||
if (
|
|
||||||
$app.notificationTable.filters[0].value.length === 0 ||
|
|
||||||
$app.notificationTable.filters[0].value.includes(
|
|
||||||
noty.type
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
$app.notifyMenu('notification');
|
|
||||||
}
|
|
||||||
$app.queueNotificationNoty(noty);
|
|
||||||
$app.notificationTable.data.push(noty);
|
|
||||||
$app.updateSharedFeed(true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.log('Unknown pipeline type', args.json);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {};
|
|
||||||
|
|
||||||
_methods = {};
|
|
||||||
}
|
|
||||||
73
src/components/AvatarInfo.vue
Normal file
73
src/components/AvatarInfo.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<div @click="confirm" class="avatar-info">
|
||||||
|
<span style="margin-right: 5px">{{ avatarName }}</span>
|
||||||
|
<span v-if="avatarType" :class="color" style="margin-right: 5px">{{ avatarType }}</span>
|
||||||
|
<span v-if="avatarTags" style="color: #909399; font-family: monospace; font-size: 12px">{{ avatarTags }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { useAvatarStore } from '../stores';
|
||||||
|
|
||||||
|
const avatarStore = useAvatarStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
imageurl: String,
|
||||||
|
userid: String,
|
||||||
|
hintownerid: String,
|
||||||
|
hintavatarname: String,
|
||||||
|
avatartags: Array
|
||||||
|
});
|
||||||
|
|
||||||
|
const avatarName = ref('');
|
||||||
|
const avatarType = ref('');
|
||||||
|
const avatarTags = ref('');
|
||||||
|
const color = ref('');
|
||||||
|
let ownerId = '';
|
||||||
|
|
||||||
|
const parse = async () => {
|
||||||
|
ownerId = '';
|
||||||
|
avatarName.value = '';
|
||||||
|
avatarType.value = '';
|
||||||
|
color.value = '';
|
||||||
|
avatarTags.value = '';
|
||||||
|
|
||||||
|
if (!props.imageurl) {
|
||||||
|
avatarName.value = '-';
|
||||||
|
} else if (props.hintownerid) {
|
||||||
|
avatarName.value = props.hintavatarname;
|
||||||
|
ownerId = props.hintownerid;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const info = await avatarStore.getAvatarName(props.imageurl);
|
||||||
|
avatarName.value = info.avatarName;
|
||||||
|
ownerId = info.ownerId;
|
||||||
|
} catch {
|
||||||
|
console.error('Failed to fetch avatar name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof props.userid === 'undefined' || !ownerId) {
|
||||||
|
color.value = '';
|
||||||
|
avatarType.value = '';
|
||||||
|
} else if (ownerId === props.userid) {
|
||||||
|
color.value = 'avatar-info-own';
|
||||||
|
avatarType.value = '(own)';
|
||||||
|
} else {
|
||||||
|
color.value = 'avatar-info-public';
|
||||||
|
avatarType.value = '(public)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(props.avatartags)) {
|
||||||
|
avatarTags.value = props.avatartags.map((tag) => tag.replace('content_', '')).join(', ');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirm = () => {
|
||||||
|
if (!props.imageurl) return;
|
||||||
|
avatarStore.showAvatarAuthorDialog(props.userid, ownerId, props.imageurl);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch([() => props.imageurl, () => props.userid, () => props.avatartags], parse, { immediate: true });
|
||||||
|
</script>
|
||||||
33
src/components/CountdownTimer.vue
Normal file
33
src/components/CountdownTimer.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<span>{{ text }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||||
|
import * as workerTimers from 'worker-timers';
|
||||||
|
import { timeToText } from '../shared/utils';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
datetime: { type: String, default: '' },
|
||||||
|
hours: { type: Number, default: 1 }
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = ref('');
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
const epoch = new Date(props.datetime).getTime() + 1000 * 60 * 60 * props.hours - Date.now();
|
||||||
|
text.value = epoch >= 0 ? timeToText(epoch) : '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.datetime, update);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
|
const timer = workerTimers.setInterval(update, 5000);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
workerTimers.clearInterval(timer);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
41
src/components/DisplayName.vue
Normal file
41
src/components/DisplayName.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<span @click="showUserDialog" class="x-link">{{ username }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { userRequest } from '../api';
|
||||||
|
import { useUserStore } from '../stores';
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
userid: String,
|
||||||
|
location: String,
|
||||||
|
forceUpdateKey: Number,
|
||||||
|
hint: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const username = ref(props.userid);
|
||||||
|
|
||||||
|
async function parse() {
|
||||||
|
username.value = props.userid;
|
||||||
|
if (props.hint) {
|
||||||
|
username.value = props.hint;
|
||||||
|
} else if (props.userid) {
|
||||||
|
const args = await userRequest.getCachedUser({ userId: props.userid });
|
||||||
|
if (args?.json?.displayName) {
|
||||||
|
username.value = args.json.displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUserDialog() {
|
||||||
|
userStore.showUserDialog(props.userid);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch([() => props.userid, () => props.location, () => props.forceUpdateKey], parse, { immediate: true });
|
||||||
|
</script>
|
||||||
@@ -21,14 +21,12 @@
|
|||||||
</span>
|
</span>
|
||||||
<template v-else-if="isGroupByInstance">
|
<template v-else-if="isGroupByInstance">
|
||||||
<i v-if="isFriendTraveling" class="el-icon el-icon-loading"></i>
|
<i v-if="isFriendTraveling" class="el-icon el-icon-loading"></i>
|
||||||
<timer
|
<Timer
|
||||||
class="extra"
|
class="extra"
|
||||||
:epoch="epoch"
|
:epoch="epoch"
|
||||||
:style="
|
:style="isFriendTraveling ? { display: 'inline-block', overflow: 'unset' } : undefined" />
|
||||||
isFriendTraveling ? { display: 'inline-block', overflow: 'unset' } : undefined
|
|
||||||
"></timer>
|
|
||||||
</template>
|
</template>
|
||||||
<location
|
<Location
|
||||||
v-else
|
v-else
|
||||||
class="extra"
|
class="extra"
|
||||||
:location="friend.ref.location"
|
:location="friend.ref.location"
|
||||||
@@ -37,7 +35,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!friend.ref && !API.isRefreshFriendsLoading">
|
<template v-else-if="!friend.ref && !isRefreshFriendsLoading">
|
||||||
<span>{{ friend.name || friend.id }}</span>
|
<span>{{ friend.name || friend.id }}</span>
|
||||||
<el-button
|
<el-button
|
||||||
ttype="text"
|
ttype="text"
|
||||||
@@ -62,38 +60,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import Location from './Location.vue';
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { userImage, userStatusClass } from '../shared/utils';
|
||||||
|
import { useAppearanceSettingsStore, useFriendStore } from '../stores';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'FriendItem',
|
friend: { type: Object, required: true },
|
||||||
components: {
|
isGroupByInstance: Boolean
|
||||||
Location
|
});
|
||||||
},
|
|
||||||
inject: ['API', 'userImage', 'userStatusClass'],
|
const { hideNicknames } = storeToRefs(useAppearanceSettingsStore());
|
||||||
props: {
|
const { isRefreshFriendsLoading } = storeToRefs(useFriendStore());
|
||||||
friend: {
|
|
||||||
type: Object,
|
const isFriendTraveling = computed(() => props.friend.ref.location === 'traveling');
|
||||||
required: true
|
const isFriendActiveOrOffline = computed(() => props.friend.state === 'active' || props.friend.state === 'offline');
|
||||||
},
|
const epoch = computed(() =>
|
||||||
hideNicknames: {
|
isFriendTraveling.value ? props.friend.ref.$travelingToTime : props.friend.ref.$location_at
|
||||||
type: Boolean,
|
);
|
||||||
default: false
|
|
||||||
},
|
|
||||||
isGroupByInstance: Boolean
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isFriendTraveling() {
|
|
||||||
return this.friend.ref.location === 'traveling';
|
|
||||||
},
|
|
||||||
isFriendActiveOrOffline() {
|
|
||||||
return this.friend.state === 'active' || this.friend.state === 'offline';
|
|
||||||
},
|
|
||||||
epoch() {
|
|
||||||
return this.isFriendTraveling ? this.friend.ref.$travelingToTime : this.friend.ref.$location_at;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
170
src/components/InstanceInfo.vue
Normal file
170
src/components/InstanceInfo.vue
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<template>
|
||||||
|
<div style="display: inline-block; margin-left: 5px">
|
||||||
|
<el-tooltip v-if="state.isValidInstance" placement="bottom">
|
||||||
|
<template #content>
|
||||||
|
<div>
|
||||||
|
<span v-if="state.isClosed">Closed At: {{ formatDateFilter(state.closedAt, 'long') }}<br /></span>
|
||||||
|
<template v-if="state.canCloseInstance">
|
||||||
|
<el-button
|
||||||
|
:disabled="state.isClosed"
|
||||||
|
size="mini"
|
||||||
|
type="primary"
|
||||||
|
@click="closeInstance(props.location)">
|
||||||
|
{{ t('dialog.user.info.close_instance') }} </el-button
|
||||||
|
><br /><br />
|
||||||
|
</template>
|
||||||
|
<span><span style="color: #409eff">PC: </span>{{ state.platforms.standalonewindows }}</span
|
||||||
|
><br />
|
||||||
|
<span><span style="color: #67c23a">Android: </span>{{ state.platforms.android }}</span
|
||||||
|
><br />
|
||||||
|
<span>{{ t('dialog.user.info.instance_game_version') }} {{ state.gameServerVersion }}</span
|
||||||
|
><br />
|
||||||
|
<span v-if="state.queueEnabled">{{ t('dialog.user.info.instance_queuing_enabled') }}<br /></span>
|
||||||
|
<span v-if="state.disabledContentSettings"
|
||||||
|
>{{ t('dialog.user.info.instance_disabled_content') }} {{ state.disabledContentSettings }}<br
|
||||||
|
/></span>
|
||||||
|
<span v-if="state.userList.length">{{ t('dialog.user.info.instance_users') }}<br /></span>
|
||||||
|
<template v-for="user in state.userList">
|
||||||
|
<span
|
||||||
|
style="cursor: pointer; margin-right: 5px"
|
||||||
|
@click="showUserDialog(user.id)"
|
||||||
|
:key="user.id"
|
||||||
|
>{{ user.displayName }}</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<i class="el-icon-caret-bottom"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-if="state.occupants" style="margin-left: 5px">{{ state.occupants }}/{{ state.capacity }}</span>
|
||||||
|
<span v-if="props.friendcount" style="margin-left: 5px">({{ props.friendcount }})</span>
|
||||||
|
<span v-if="state.isFull" style="margin-left: 5px; color: lightcoral">{{
|
||||||
|
t('dialog.user.info.instance_full')
|
||||||
|
}}</span>
|
||||||
|
<span v-if="state.isHardClosed" style="margin-left: 5px; color: lightcoral">{{
|
||||||
|
t('dialog.user.info.instance_hard_closed')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="state.isClosed" style="margin-left: 5px; color: lightcoral">{{
|
||||||
|
t('dialog.user.info.instance_closed')
|
||||||
|
}}</span>
|
||||||
|
<span v-if="state.queueSize" style="margin-left: 5px"
|
||||||
|
>{{ t('dialog.user.info.instance_queue') }} {{ state.queueSize }}</span
|
||||||
|
>
|
||||||
|
<span v-if="state.isAgeGated" style="margin-left: 5px; color: lightcoral">{{
|
||||||
|
t('dialog.user.info.instance_age_gated')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getCurrentInstance, reactive, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { miscRequest } from '../api';
|
||||||
|
import { formatDateFilter, hasGroupPermission } from '../shared/utils';
|
||||||
|
import { useGroupStore, useInstanceStore, useLocationStore, useUserStore } from '../stores';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const groupStore = useGroupStore();
|
||||||
|
const instanceStore = useInstanceStore();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
location: String,
|
||||||
|
instance: Object,
|
||||||
|
friendcount: Number
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
isValidInstance: false,
|
||||||
|
isFull: false,
|
||||||
|
isClosed: false,
|
||||||
|
isHardClosed: false,
|
||||||
|
closedAt: '',
|
||||||
|
occupants: 0,
|
||||||
|
capacity: 0,
|
||||||
|
queueSize: 0,
|
||||||
|
queueEnabled: false,
|
||||||
|
platforms: [],
|
||||||
|
userList: [],
|
||||||
|
gameServerVersion: '',
|
||||||
|
canCloseInstance: false,
|
||||||
|
isAgeGated: false,
|
||||||
|
disabledContentSettings: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
|
function parse() {
|
||||||
|
Object.assign(state, {
|
||||||
|
isValidInstance: false,
|
||||||
|
isFull: false,
|
||||||
|
isClosed: false,
|
||||||
|
isHardClosed: false,
|
||||||
|
closedAt: '',
|
||||||
|
occupants: 0,
|
||||||
|
capacity: 0,
|
||||||
|
queueSize: 0,
|
||||||
|
queueEnabled: false,
|
||||||
|
platforms: [],
|
||||||
|
userList: [],
|
||||||
|
gameServerVersion: '',
|
||||||
|
canCloseInstance: false,
|
||||||
|
isAgeGated: false,
|
||||||
|
disabledContentSettings: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!props.location || !props.instance || Object.keys(props.instance).length === 0) return;
|
||||||
|
|
||||||
|
state.isValidInstance = true;
|
||||||
|
state.isFull = props.instance.hasCapacityForYou === false;
|
||||||
|
if (props.instance.closedAt) {
|
||||||
|
state.isClosed = true;
|
||||||
|
state.closedAt = props.instance.closedAt;
|
||||||
|
}
|
||||||
|
state.isHardClosed = props.instance.hardClose === true;
|
||||||
|
state.occupants = props.instance.userCount;
|
||||||
|
if (props.location === locationStore.lastLocation.location) {
|
||||||
|
state.occupants = locationStore.lastLocation.playerList.size;
|
||||||
|
}
|
||||||
|
state.capacity = props.instance.capacity;
|
||||||
|
state.gameServerVersion = props.instance.gameServerVersion;
|
||||||
|
state.queueSize = props.instance.queueSize;
|
||||||
|
if (props.instance.platforms) state.platforms = props.instance.platforms;
|
||||||
|
if (props.instance.users) state.userList = props.instance.users;
|
||||||
|
if (props.instance.ownerId === userStore.currentUser.id) {
|
||||||
|
state.canCloseInstance = true;
|
||||||
|
} else if (props.instance.ownerId?.startsWith('grp_')) {
|
||||||
|
const group = groupStore.cachedGroups.get(props.instance.ownerId);
|
||||||
|
state.canCloseInstance = hasGroupPermission(group, 'group-instance-moderate');
|
||||||
|
}
|
||||||
|
state.isAgeGated = props.instance.ageGate === true;
|
||||||
|
if (props.location?.includes('~ageGate')) state.isAgeGated = true;
|
||||||
|
if (props.instance.$disabledContentSettings?.length) {
|
||||||
|
state.disabledContentSettings = props.instance.$disabledContentSettings.join(', ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch([() => props.location, () => props.instance, () => props.friendcount], parse, { immediate: true });
|
||||||
|
|
||||||
|
function showUserDialog(userId) {
|
||||||
|
userStore.showUserDialog(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeInstance(location) {
|
||||||
|
proxy.$confirm('Continue? Close Instance, nobody will be able to join', 'Confirm', {
|
||||||
|
confirmButtonText: 'Confirm',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
type: 'warning',
|
||||||
|
callback: async (action) => {
|
||||||
|
if (action !== 'confirm') return;
|
||||||
|
const args = await miscRequest.closeInstance({ location, hardClose: false });
|
||||||
|
if (args.json) {
|
||||||
|
proxy.$message({ message: t('message.instance.closed'), type: 'success' });
|
||||||
|
instanceStore.applyInstance(args.json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user