mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-19 06:43:51 +02:00
Upgrade to Vue3 and Element Plus (#1374)
* Update Vue devtools
* upgrade vue pinia element-plus vue-i18n, add vite
* fix: i18n
* global components
* change v-deep
* upgrade vue-lazyload
* data table
* update enlint and safe-dialog
* package.json and vite.config.js
* el-icon
* el-message
* vue 2 -> vue3 migration changes
* $pinia
* dialog
* el-popover slot
* lint
* chore
* slot
* scss
* remote state access
* misc
* jsconfig
* el-button size mini -> small
* :model-value
* ElMessageBox
* datatable
* remove v-lazyload
* template #dropdown
* mini -> small
* css
* byebye hideTooltips
* use sass-embedded
* Update SQLite, remove unneeded libraries
* Fix shift remove local avatar favorites
* Electron arm64
* arm64 support
* bye pug
* f-word vite hah
* misc
* remove safe dialog component
* Add self invite to launch dialog
* Fix errors
* Icons 1
* improve localfavorite loading performance
* improve favorites world item performance
* dialog visibility changes for Element Plus
* clear element plus error
* import performance
* revert App.vue hah
* hah
* Revert "Add self invite to launch dialog"
This reverts commit 4801cfad58.
* Toggle self invite/open in-game
* Self invite on launch dialog
* el-button icon
* el-icon
* fix user dialog tab switching logic
* fix PlayerList
* Formatting changes
* More icons
* Fix friend log table
* loading margin
* fix markdown
* fix world dialog tab switching issue
* Fixes and formatting
* fix: global i18n.t export
* fix favorites world tab not working
* Create instance, displayName
* Remove group members sort by userId
* Fix loading dialog tabs on swtich
* Star
* charts console.warn
* wip: fix charts
* wip: fix charts
* wip: charts composables
* fix favorite item tooltip warning
* Fixes and formatting
* Clean up image dialogs
* Remove unused method
* Fix platform/size border
* Fix platform/size border
* $vr
* fix friendExportDialogVisible binding
* ElMessageBox and Settings
* Login formatting
* Rename VR overlay query
* Fix image popover and userdialog badges
* Formatting
* Big buttons
* Fixes, update Cef
* Fix gameLog table nav buttons jumping around while using nav buttons
* Fix z-index
* vr overlay
* vite input add theme
* defineAsyncComponent
* ISO 639-1
* fix i18n
* clean t
* Formatting, fix calendar, rotate arrows
* Show user status when user is offline
* Fix VR overlay
* fix theme and clean up
* split InstanceActivity
* tweak
* Fix VR overlay formatting
* fix scss var
* AppDebug hahahaha
* Years
* remove reactive
* improve perf
* state hah…
* fix user rendering poblems when user object is not yet loaded
* improve perf
* Update avatar/world image uploader, licenses, remove previous images dialog (old images are now deleted)
* improve perf 1
* Suppress stray errors
* fix traveling location display issue
* Fix empty instance creator
* improve friend list refresh performance
* fix main charts
* fix chart
* Fix darkmode
* Fix avatar dialog tags
---------
Co-authored-by: pa <maplenagisa@gmail.com>
This commit is contained in:
@@ -8,7 +8,6 @@
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "auto",
|
||||
"plugins": ["@prettier/plugin-pug"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.js",
|
||||
@@ -16,14 +15,6 @@
|
||||
"parser": "meriyah"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.pug",
|
||||
"options": {
|
||||
"pugPrintWidth": 120,
|
||||
"pugAttributeSeparator": "none",
|
||||
"pugBracketSameLine": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.vue",
|
||||
"options": {
|
||||
|
||||
@@ -17,7 +17,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using librsync.net;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using Microsoft.Win32;
|
||||
using NLog;
|
||||
@@ -123,12 +122,6 @@ namespace VRCX
|
||||
return File.Exists(Path.Join(Program.AppDataDirectory, "update.exe"));
|
||||
}
|
||||
|
||||
public override void ExecuteAppFunction(string function, string json)
|
||||
{
|
||||
if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync($"$app.{function}", json);
|
||||
}
|
||||
|
||||
public override void ExecuteVrFeedFunction(string function, string json)
|
||||
{
|
||||
Program.VRCXVRInstance.ExecuteVrFeedFunction(function, json);
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace VRCX
|
||||
public override void VrInit()
|
||||
{
|
||||
if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync("$app.store.vr.vrInit", "");
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync("$pinia.vr.vrInit", "");
|
||||
}
|
||||
|
||||
public override void ToggleSystemMonitor(bool enabled)
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace VRCX
|
||||
|
||||
// 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)
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync("$app.store.game.updateIsGameRunning", isGameRunning, isSteamVRRunning, isHmdAfk);
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync("$pinia.game.updateIsGameRunning", isGameRunning, isSteamVRRunning, isHmdAfk);
|
||||
}
|
||||
|
||||
public override bool IsGameRunning()
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using librsync.net;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
|
||||
@@ -20,10 +19,10 @@ namespace VRCX
|
||||
public void Init()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
Error = delegate(object _, Newtonsoft.Json.Serialization.ErrorEventArgs args)
|
||||
Error = delegate (object _, Newtonsoft.Json.Serialization.ErrorEventArgs args)
|
||||
{
|
||||
args.ErrorContext.Handled = true;
|
||||
}
|
||||
@@ -43,22 +42,6 @@ namespace VRCX
|
||||
return (hash[3] << 8) | hash[4];
|
||||
}
|
||||
|
||||
public string SignFile(string blob)
|
||||
{
|
||||
var fileData = Convert.FromBase64String(blob);
|
||||
using var sig = Librsync.ComputeSignature(new MemoryStream(fileData));
|
||||
using var memoryStream = new MemoryStream();
|
||||
sig.CopyTo(memoryStream);
|
||||
var sigBytes = memoryStream.ToArray();
|
||||
return Convert.ToBase64String(sigBytes);
|
||||
}
|
||||
|
||||
public string FileLength(string blob)
|
||||
{
|
||||
var fileData = Convert.FromBase64String(blob);
|
||||
return fileData.Length.ToString();
|
||||
}
|
||||
|
||||
public void OpenLink(string url)
|
||||
{
|
||||
if (url.StartsWith("http://") ||
|
||||
@@ -70,7 +53,7 @@ namespace VRCX
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string GetLaunchCommand()
|
||||
{
|
||||
var command = StartupArgs.LaunchArguments.LaunchCommand;
|
||||
@@ -102,7 +85,7 @@ namespace VRCX
|
||||
var filePath = Path.Join(Program.AppDataDirectory, "custom.css");
|
||||
if (File.Exists(filePath))
|
||||
return File.ReadAllText(filePath);
|
||||
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
@@ -111,7 +94,7 @@ namespace VRCX
|
||||
var filePath = Path.Join(Program.AppDataDirectory, "custom.js");
|
||||
if (File.Exists(filePath))
|
||||
return File.ReadAllText(filePath);
|
||||
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace VRCX
|
||||
|
||||
public abstract void RestartApplication(bool isUpgrade);
|
||||
public abstract bool CheckForUpdateExe();
|
||||
public abstract void ExecuteAppFunction(string function, string json);
|
||||
public abstract void ExecuteVrFeedFunction(string function, string json);
|
||||
public abstract void ExecuteVrOverlayFunction(string function, string json);
|
||||
public abstract void FocusWindow();
|
||||
|
||||
@@ -51,10 +51,6 @@ namespace VRCX
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void ExecuteAppFunction(string function, string json)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ExecuteVrFeedFunction(string function, string json)
|
||||
{
|
||||
Program.VRCXVRInstance.ExecuteVrFeedFunction(function, json);
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace VRCX
|
||||
}
|
||||
|
||||
if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync("$app.store.vrcx.dragEnterCef", file);
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync("$pinia.vrcx.dragEnterCef", file);
|
||||
|
||||
dragData.Dispose();
|
||||
return false;
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace VRCX
|
||||
|
||||
#if !LINUX
|
||||
if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame)
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync("$app.store.vrcx.ipcEvent", packet);
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync("$pinia.vrcx.ipcEvent", packet);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace VRCX
|
||||
m_LogQueue.Enqueue(logLine);
|
||||
#else
|
||||
if (MainForm.Instance != null && MainForm.Instance.Browser != null)
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync("$app.store.gameLog.addGameLogEvent", logLine);
|
||||
MainForm.Instance.Browser.ExecuteScriptAsync("$pinia.gameLog.addGameLogEvent", logLine);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -190,13 +190,13 @@ namespace VRCX
|
||||
var dashboardHandle = 0UL;
|
||||
|
||||
_wristOverlay = new OffScreenBrowser(
|
||||
Program.LaunchDebug ? "http://localhost:9000/vr.html?1": "file://vrcx/vr.html?1",
|
||||
Program.LaunchDebug ? "http://localhost:9000/vr.html?wrist" : "file://vrcx/vr.html?wrist",
|
||||
512,
|
||||
512
|
||||
);
|
||||
|
||||
_hmdOverlay = new OffScreenBrowser(
|
||||
Program.LaunchDebug ? "http://localhost:9000/vr.html?2": "file://vrcx/vr.html?2",
|
||||
Program.LaunchDebug ? "http://localhost:9000/vr.html?hmd" : "file://vrcx/vr.html?hmd",
|
||||
1024,
|
||||
1024
|
||||
);
|
||||
@@ -846,7 +846,7 @@ namespace VRCX
|
||||
if (_wristOverlay == null) return;
|
||||
// if (_wristOverlay.IsLoading)
|
||||
// Restart();
|
||||
_wristOverlay.ExecuteScriptAsync($"$app.{function}", json);
|
||||
_wristOverlay.ExecuteScriptAsync($"$vr.{function}", json);
|
||||
}
|
||||
|
||||
public override ConcurrentQueue<KeyValuePair<string, string>> GetExecuteVrOverlayFunctionQueue()
|
||||
@@ -859,7 +859,7 @@ namespace VRCX
|
||||
if (_hmdOverlay == null) return;
|
||||
// if (_hmdOverlay.IsLoading)
|
||||
// Restart();
|
||||
_hmdOverlay.ExecuteScriptAsync($"$app.{function}", json);
|
||||
_hmdOverlay.ExecuteScriptAsync($"$vr.{function}", json);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,13 +141,13 @@ namespace VRCX
|
||||
var overlayHandle2 = 0UL;
|
||||
|
||||
_wristOverlay = new OffScreenBrowserLegacy(
|
||||
"file://vrcx/vr.html?1",
|
||||
Program.LaunchDebug ? "http://localhost:9000/vr.html?wrist" : "file://vrcx/vr.html?wrist",
|
||||
512,
|
||||
512
|
||||
);
|
||||
|
||||
_hmdOverlay = new OffScreenBrowserLegacy(
|
||||
"file://vrcx/vr.html?2",
|
||||
Program.LaunchDebug ? "http://localhost:9000/vr.html?hmd" : "file://vrcx/vr.html?hmd",
|
||||
1024,
|
||||
1024
|
||||
);
|
||||
@@ -763,7 +763,7 @@ namespace VRCX
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
public override ConcurrentQueue<KeyValuePair<string, string>> GetExecuteVrFeedFunctionQueue()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@@ -774,9 +774,9 @@ namespace VRCX
|
||||
if (_wristOverlay == null) return;
|
||||
if (_wristOverlay.IsLoading)
|
||||
Restart();
|
||||
_wristOverlay.ExecuteScriptAsync($"$app.{function}", json);
|
||||
_wristOverlay.ExecuteScriptAsync($"$vr.{function}", json);
|
||||
}
|
||||
|
||||
|
||||
public override ConcurrentQueue<KeyValuePair<string, string>> GetExecuteVrOverlayFunctionQueue()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@@ -787,7 +787,7 @@ namespace VRCX
|
||||
if (_hmdOverlay == null) return;
|
||||
if (_hmdOverlay.IsLoading)
|
||||
Restart();
|
||||
_hmdOverlay.ExecuteScriptAsync($"$app.{function}", json);
|
||||
_hmdOverlay.ExecuteScriptAsync($"$vr.{function}", json);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace VRCX
|
||||
InitializeComponent();
|
||||
|
||||
_browser1 = new ChromiumWebBrowser(
|
||||
Path.Join(Program.BaseDirectory, "html/vr.html?1")
|
||||
Path.Join(Program.BaseDirectory, "html/vr.html?wrist")
|
||||
)
|
||||
{
|
||||
DragHandler = new CefNoopDragHandler(),
|
||||
@@ -35,7 +35,7 @@ namespace VRCX
|
||||
};
|
||||
|
||||
_browser2 = new ChromiumWebBrowser(
|
||||
Path.Join(Program.BaseDirectory, "html/vr.html?2")
|
||||
Path.Join(Program.BaseDirectory, "html/vr.html?hmd")
|
||||
)
|
||||
{
|
||||
DragHandler = new CefNoopDragHandler(),
|
||||
|
||||
@@ -61,14 +61,6 @@
|
||||
<EmbeddedResource Remove="DBMerger\**" />
|
||||
<None Remove="DBMerger\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Blake2Sharp">
|
||||
<HintPath>libs\Blake2Sharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="librsync.net">
|
||||
<HintPath>libs\librsync.net.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.manifest" />
|
||||
<None Include="..\Version">
|
||||
@@ -88,8 +80,8 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CefSharp.OffScreen.NETCore" Version="138.0.340" />
|
||||
<PackageReference Include="CefSharp.WinForms.NETCore" Version="138.0.340" />
|
||||
<PackageReference Include="CefSharp.OffScreen.NETCore" Version="139.0.280" />
|
||||
<PackageReference Include="CefSharp.WinForms.NETCore" Version="139.0.280" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.6.1.70" />
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
||||
@@ -49,14 +49,6 @@
|
||||
<ApplicationIcon>..\VRCX.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Blake2Sharp">
|
||||
<HintPath>libs\Blake2Sharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="librsync.net">
|
||||
<HintPath>libs\librsync.net.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.manifest" />
|
||||
<None Include="..\Version">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<BaseIntermediateOutputPath>obj1\</BaseIntermediateOutputPath>
|
||||
<OutputPath>..\build\Electron\</OutputPath>
|
||||
@@ -54,14 +54,6 @@
|
||||
<ApplicationIcon>..\VRCX.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Blake2Sharp">
|
||||
<HintPath>libs\Blake2Sharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="librsync.net">
|
||||
<HintPath>libs\librsync.net.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.manifest" />
|
||||
<None Include="..\Version">
|
||||
|
||||
Binary file not shown.
@@ -1,11 +1,3 @@
|
||||
### librsync.net.dll
|
||||
- [https://github.com/braddodson/librsync.net](https://github.com/braddodson/librsync.net)
|
||||
|
||||
### Blake2Sharp.dll
|
||||
- [https://github.com/BLAKE2/BLAKE2/tree/master/csharp](https://github.com/BLAKE2/BLAKE2/tree/master/csharp)
|
||||
|
||||
### openvr_api.dll
|
||||
- [https://github.com/ValveSoftware/openvr](https://github.com/ValveSoftware/openvr)
|
||||
|
||||
### sqlite3.dll
|
||||
- [https://www.sqlite.org/index.html](https://www.sqlite.org/index.html)
|
||||
- [https://github.com/ValveSoftware/openvr](https://github.com/ValveSoftware/openvr)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -57,7 +57,7 @@ export default defineConfig([
|
||||
}
|
||||
}
|
||||
},
|
||||
pluginVue.configs['flat/vue2-essential'],
|
||||
pluginVue.configs['flat/essential'],
|
||||
{
|
||||
rules: {
|
||||
'no-unused-vars': 'warn',
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, user-scalable=no"
|
||||
/>
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<meta http-equiv="Cache-Control" content="no-cache" />
|
||||
|
||||
<title>VRCX</title>
|
||||
|
||||
@@ -15,5 +18,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -10,7 +10,8 @@
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"lib": ["esnext", "dom", "dom.iterable"]
|
||||
"lib": ["esnext", "dom", "dom.iterable"],
|
||||
"types": ["vite/client", "element-plus/global", "node"]
|
||||
},
|
||||
"include": ["src/**/*", "src-electron/**/*"],
|
||||
"exclude": ["node_modules", "build"]
|
||||
|
||||
11727
package-lock.json
generated
11727
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
61
package.json
61
package.json
@@ -4,14 +4,14 @@
|
||||
"private": true,
|
||||
"main": "src-electron/main.js",
|
||||
"scripts": {
|
||||
"dev": "cross-env PLATFORM=windows webpack serve --config webpack.config.js --mode development",
|
||||
"dev": "cross-env PLATFORM=windows vite serve src",
|
||||
"dev:test": "concurrently \"npm run dev\" \"jest --watchAll\"",
|
||||
"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",
|
||||
"localization": "node ./src/localization/localizationHelperCLI.js",
|
||||
"test": "jest",
|
||||
"test:coverage": "jest --coverage",
|
||||
"prod": "cross-env PLATFORM=windows webpack --config webpack.config.js --mode production",
|
||||
"prod": "cross-env PLATFORM=windows vite build src",
|
||||
"prod-linux": "cross-env PLATFORM=linux webpack --config webpack.config.js --mode production",
|
||||
"build-electron": "node ./src-electron/download-dotnet-runtime.js --arch=x64 && node ./src-electron/patch-package-version.js && electron-builder --x64 --publish never",
|
||||
"build-electron-arm64": "node ./src-electron/download-dotnet-runtime.js --arch=arm64 && node ./src-electron/patch-package-version.js && electron-builder --arm64 --publish never",
|
||||
@@ -34,54 +34,39 @@
|
||||
"homepage": "https://github.com/vrcx-team/VRCX#readme",
|
||||
"devDependencies": {
|
||||
"@electron/rebuild": "^4.0.1",
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@fontsource/noto-sans-jp": "^5.2.6",
|
||||
"@fontsource/noto-sans-kr": "^5.2.6",
|
||||
"@eslint/js": "^9.35.0",
|
||||
"@fontsource/noto-sans-jp": "^5.2.7",
|
||||
"@fontsource/noto-sans-kr": "^5.2.7",
|
||||
"@fontsource/noto-sans-sc": "^5.2.6",
|
||||
"@fontsource/noto-sans-tc": "^5.2.6",
|
||||
"@prettier/plugin-pug": "^3.4.2",
|
||||
"@fontsource/noto-sans-tc": "^5.2.7",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/node": "^24.3.1",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"animate.css": "^4.1.1",
|
||||
"concurrently": "^9.2.0",
|
||||
"copy-webpack-plugin": "^13.0.1",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"cross-env": "^10.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"dayjs": "^1.11.18",
|
||||
"echarts": "^6.0.0",
|
||||
"electron": "^37.3.0",
|
||||
"electron": "^37.4.0",
|
||||
"electron-builder": "^26.0.12",
|
||||
"element-ui": "^2.15.14",
|
||||
"esbuild-jest": "^0.5.0",
|
||||
"esbuild-loader": "^4.3.0",
|
||||
"eslint": "^9.33.0",
|
||||
"element-plus": "^2.11.2",
|
||||
"esbuild-jest": "^0.4.0",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-vue": "^9.33.0",
|
||||
"globals": "^16.3.0",
|
||||
"html-webpack-plugin": "^5.6.4",
|
||||
"jest": "^30.0.5",
|
||||
"mini-css-extract-plugin": "^2.9.4",
|
||||
"jest": "^30.1.3",
|
||||
"noty": "^3.2.0-beta-deprecated",
|
||||
"pinia": "^2.3.1",
|
||||
"pinia": "^3.0.3",
|
||||
"prettier": "^3.6.2",
|
||||
"pug": "^3.0.3",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"remixicon": "^4.6.0",
|
||||
"sass": "^1.90.0",
|
||||
"sass-loader": "^16.0.5",
|
||||
"vue": "^2.7.16",
|
||||
"vue-data-tables": "^3.4.5",
|
||||
"vue-demi": "^0.14.10",
|
||||
"vue-i18n": "^8.28.2",
|
||||
"vue-i18n-bridge": "^9.14.1",
|
||||
"vue-lazyload": "^1.3.5",
|
||||
"vue-loader": "^15.11.1",
|
||||
"vue-markdown": "^2.2.4",
|
||||
"vue-marquee-text-component": "^1.2.0",
|
||||
"webpack": "^5.101.2",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.2",
|
||||
"sass-embedded": "^1.92.1",
|
||||
"vite": "^7.1.4",
|
||||
"vue": "^3.5.21",
|
||||
"vue-i18n": "^11.1.12",
|
||||
"vue-marquee-text-component": "^2.0.1",
|
||||
"vue-showdown": "^4.2.0",
|
||||
"worker-timers": "^8.0.24",
|
||||
"yargs": "^18.0.0"
|
||||
},
|
||||
|
||||
@@ -126,6 +126,7 @@ const gotTheLock = app.requestSingleInstanceLock();
|
||||
const strip_vrcx_prefix_regex = new RegExp('^' + VRCX_URI_PREFIX + '://');
|
||||
|
||||
if (!gotTheLock) {
|
||||
console.log('Another instance is already running. Exiting.');
|
||||
app.quit();
|
||||
} else {
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
@@ -427,7 +428,7 @@ function createWristOverlayWindowOffscreen() {
|
||||
wristOverlayWindow.webContents.setFrameRate(2);
|
||||
|
||||
const indexPath = path.join(rootDir, 'build/html/vr.html');
|
||||
const fileUrl = `file://${indexPath}?1`;
|
||||
const fileUrl = `file://${indexPath}?wrist`;
|
||||
wristOverlayWindow.loadURL(fileUrl, { userAgent: version });
|
||||
|
||||
// Use paint event for offscreen rendering
|
||||
@@ -490,7 +491,7 @@ function createHmdOverlayWindowOffscreen() {
|
||||
hmdOverlayWindow.webContents.setFrameRate(48);
|
||||
|
||||
const indexPath = path.join(rootDir, 'build/html/vr.html');
|
||||
const fileUrl = `file://${indexPath}?2`;
|
||||
const fileUrl = `file://${indexPath}?hmd`;
|
||||
hmdOverlayWindow.loadURL(fileUrl, { userAgent: version });
|
||||
|
||||
// Use paint event for offscreen rendering
|
||||
|
||||
208
src/App.vue
208
src/App.vue
@@ -1,11 +1,99 @@
|
||||
<script>
|
||||
// @ts-ignore
|
||||
import template from './app.pug';
|
||||
import Vue, { onMounted } from 'vue';
|
||||
<template>
|
||||
<!DOCTYPE html>
|
||||
<el-config-provider :locale="currentLocale">
|
||||
<div
|
||||
id="x-app"
|
||||
class="x-app"
|
||||
ondragenter="event.preventDefault()"
|
||||
ondragover="event.preventDefault()"
|
||||
ondrop="event.preventDefault()">
|
||||
<!-- ### Login ### -->
|
||||
<Login v-if="!watchState.isLoggedIn"></Login>
|
||||
|
||||
<VRCXUpdateDialog></VRCXUpdateDialog>
|
||||
|
||||
<template v-if="watchState.isLoggedIn">
|
||||
<!-- ### Menu ### -->
|
||||
<NavMenu></NavMenu>
|
||||
|
||||
<!-- ### Sidebar ### -->
|
||||
<Sidebar></Sidebar>
|
||||
|
||||
<!-- ### Tabs ### -->
|
||||
<Feed></Feed>
|
||||
|
||||
<GameLog></GameLog>
|
||||
|
||||
<PlayerList></PlayerList>
|
||||
|
||||
<Search></Search>
|
||||
|
||||
<Favorites></Favorites>
|
||||
|
||||
<FriendLog></FriendLog>
|
||||
|
||||
<Moderation></Moderation>
|
||||
|
||||
<Notification></Notification>
|
||||
|
||||
<FriendList></FriendList>
|
||||
|
||||
<Charts></Charts>
|
||||
|
||||
<Tools></Tools>
|
||||
|
||||
<Profile></Profile>
|
||||
|
||||
<Settings></Settings>
|
||||
|
||||
<!-- ## Dialogs ## -->
|
||||
<UserDialog></UserDialog>
|
||||
|
||||
<WorldDialog></WorldDialog>
|
||||
|
||||
<AvatarDialog></AvatarDialog>
|
||||
|
||||
<GroupDialog></GroupDialog>
|
||||
|
||||
<GroupMemberModerationDialog></GroupMemberModerationDialog>
|
||||
|
||||
<FullscreenImageDialog></FullscreenImageDialog>
|
||||
|
||||
<PreviousInstancesInfoDialog></PreviousInstancesInfoDialog>
|
||||
|
||||
<LaunchDialog></LaunchDialog>
|
||||
|
||||
<LaunchOptionsDialog></LaunchOptionsDialog>
|
||||
|
||||
<FriendImportDialog></FriendImportDialog>
|
||||
|
||||
<WorldImportDialog></WorldImportDialog>
|
||||
|
||||
<AvatarImportDialog></AvatarImportDialog>
|
||||
|
||||
<ChooseFavoriteGroupDialog></ChooseFavoriteGroupDialog>
|
||||
|
||||
<EditInviteMessageDialog></EditInviteMessageDialog>
|
||||
|
||||
<VRChatConfigDialog></VRChatConfigDialog>
|
||||
|
||||
<PrimaryPasswordDialog></PrimaryPasswordDialog>
|
||||
</template>
|
||||
</div>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { createGlobalStores } from './stores';
|
||||
import { watchState } from './service/watchState';
|
||||
|
||||
import '@fontsource/noto-sans-kr';
|
||||
import '@fontsource/noto-sans-jp';
|
||||
import '@fontsource/noto-sans-sc';
|
||||
import '@fontsource/noto-sans-tc';
|
||||
|
||||
import Login from './views/Login/Login.vue';
|
||||
import NavMenu from './components/NavMenu.vue';
|
||||
import Sidebar from './views/Sidebar/Sidebar.vue';
|
||||
@@ -28,7 +116,6 @@
|
||||
import AvatarDialog from './components/dialogs/AvatarDialog/AvatarDialog.vue';
|
||||
import GroupDialog from './components/dialogs/GroupDialog/GroupDialog.vue';
|
||||
import GroupMemberModerationDialog from './components/dialogs/GroupDialog/GroupMemberModerationDialog.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';
|
||||
@@ -42,66 +129,57 @@
|
||||
import VRChatConfigDialog from './views/Settings/dialogs/VRChatConfigDialog.vue';
|
||||
import PrimaryPasswordDialog from './views/Settings/dialogs/PrimaryPasswordDialog.vue';
|
||||
|
||||
import { utils } from './shared/utils/_utils';
|
||||
import en from 'element-plus/es/locale/lang/en';
|
||||
import es from 'element-plus/es/locale/lang/es';
|
||||
import fr from 'element-plus/es/locale/lang/fr';
|
||||
import hu from 'element-plus/es/locale/lang/hu';
|
||||
import ja from 'element-plus/es/locale/lang/ja';
|
||||
import ko from 'element-plus/es/locale/lang/ko';
|
||||
import pl from 'element-plus/es/locale/lang/pl';
|
||||
import pt from 'element-plus/es/locale/lang/pt';
|
||||
import cs from 'element-plus/es/locale/lang/cs';
|
||||
import ru from 'element-plus/es/locale/lang/ru';
|
||||
import vi from 'element-plus/es/locale/lang/vi';
|
||||
import zhCN from 'element-plus/es/locale/lang/zh-CN';
|
||||
import zhTW from 'element-plus/es/locale/lang/zh-TW';
|
||||
import th from 'element-plus/es/locale/lang/th';
|
||||
|
||||
export default {
|
||||
template,
|
||||
components: {
|
||||
Login,
|
||||
NavMenu,
|
||||
Sidebar,
|
||||
Feed,
|
||||
GameLog,
|
||||
PlayerList,
|
||||
Search,
|
||||
Favorites,
|
||||
FriendLog,
|
||||
Moderation,
|
||||
Notification,
|
||||
FriendList,
|
||||
Charts,
|
||||
Tools,
|
||||
Profile,
|
||||
Settings,
|
||||
|
||||
UserDialog,
|
||||
WorldDialog,
|
||||
AvatarDialog,
|
||||
GroupDialog,
|
||||
GroupMemberModerationDialog,
|
||||
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();
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
watchState
|
||||
};
|
||||
}
|
||||
const langMap = {
|
||||
en: en,
|
||||
es: es,
|
||||
fr: fr,
|
||||
hu: hu,
|
||||
ja: ja,
|
||||
ko: ko,
|
||||
pl: pl,
|
||||
pt: pt,
|
||||
cs: cs,
|
||||
ru: ru,
|
||||
vi: vi,
|
||||
'zh-CN': zhCN,
|
||||
'zh-TW': zhTW,
|
||||
th: th
|
||||
};
|
||||
|
||||
const currentLocale = computed(() => {
|
||||
return langMap[locale.value] || en;
|
||||
});
|
||||
|
||||
const { locale } = useI18n();
|
||||
|
||||
const store = createGlobalStores();
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.$pinia = store;
|
||||
}
|
||||
|
||||
store.updateLoop.updateLoop();
|
||||
|
||||
onMounted(async () => {
|
||||
store.gameLog.getGameLogTable();
|
||||
await store.auth.migrateStoredUsers();
|
||||
store.auth.autoLoginAfterMounted();
|
||||
store.vrcx.checkAutoBackupRestoreVrcRegistry();
|
||||
store.game.checkVRChatDebugLogging();
|
||||
});
|
||||
</script>
|
||||
|
||||
681
src/Vr.vue
681
src/Vr.vue
@@ -1,681 +0,0 @@
|
||||
<script>
|
||||
import '@fontsource/noto-sans-kr';
|
||||
import '@fontsource/noto-sans-jp';
|
||||
import '@fontsource/noto-sans-sc';
|
||||
import '@fontsource/noto-sans-tc';
|
||||
// @ts-ignore
|
||||
import pugTemplate from './vr.pug';
|
||||
import Vue, { onMounted, reactive, toRefs, nextTick } from 'vue';
|
||||
import Noty from 'noty';
|
||||
import * as workerTimers from 'worker-timers';
|
||||
import MarqueeText from 'vue-marquee-text-component';
|
||||
import VrLocation from './components/VrLocation.vue';
|
||||
import { displayLocation } from './shared/utils/location';
|
||||
import { escapeTag, escapeTagRecursive } from './shared/utils/base/string';
|
||||
import { removeFromArray } from './shared/utils/base/array';
|
||||
import { timeToText } from './shared/utils/base/format';
|
||||
|
||||
import { i18n, t as $t } from './plugin/i18n.js';
|
||||
|
||||
export default {
|
||||
name: 'vr',
|
||||
template: pugTemplate,
|
||||
components: {
|
||||
'marquee-text': MarqueeText,
|
||||
location: VrLocation
|
||||
},
|
||||
setup() {
|
||||
const vrState = reactive({
|
||||
appType: location.href.substr(-1),
|
||||
appLanguage: 'en',
|
||||
currentCulture: 'en-nz',
|
||||
isRunningUnderWine: false,
|
||||
currentTime: new Date().toJSON(),
|
||||
cpuUsageEnabled: false,
|
||||
cpuUsage: '0',
|
||||
pcUptimeEnabled: false,
|
||||
pcUptime: '',
|
||||
customInfo: '',
|
||||
config: {},
|
||||
onlineFriendCount: 0,
|
||||
nowPlaying: {
|
||||
url: '',
|
||||
name: '',
|
||||
length: 0,
|
||||
startTime: 0,
|
||||
elapsed: 0,
|
||||
percentage: 0,
|
||||
remainingText: '',
|
||||
playing: false
|
||||
},
|
||||
lastLocation: {
|
||||
date: 0,
|
||||
location: '',
|
||||
name: '',
|
||||
playerList: [],
|
||||
friendList: [],
|
||||
progressPie: false,
|
||||
onlineFor: 0
|
||||
},
|
||||
lastLocationTimer: '',
|
||||
onlineForTimer: '',
|
||||
wristFeed: [],
|
||||
devices: [],
|
||||
deviceCount: 0,
|
||||
notificationOpacity: 100,
|
||||
hudFeed: [],
|
||||
hudTimeout: [],
|
||||
cleanHudFeedLoopStatus: false
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if (LINUX) {
|
||||
updateVrElectronLoop();
|
||||
}
|
||||
if (vrState.appType === '1') {
|
||||
refreshCustomScript();
|
||||
updateStatsLoop();
|
||||
}
|
||||
setDatetimeFormat();
|
||||
|
||||
nextTick(() => {
|
||||
window.$app.configUpdate = configUpdate;
|
||||
window.$app.updateOnlineFriendCount = updateOnlineFriendCount;
|
||||
window.$app.nowPlayingUpdate = nowPlayingUpdate;
|
||||
window.$app.lastLocationUpdate = lastLocationUpdate;
|
||||
window.$app.wristFeedUpdate = wristFeedUpdate;
|
||||
window.$app.refreshCustomScript = refreshCustomScript;
|
||||
window.$app.playNoty = playNoty;
|
||||
window.$app.statusClass = statusClass;
|
||||
window.$app.notyClear = notyClear;
|
||||
window.$app.addEntryHudFeed = addEntryHudFeed;
|
||||
window.$app.updateHudFeedTag = updateHudFeedTag;
|
||||
window.$app.updateHudTimeout = updateHudTimeout;
|
||||
window.$app.setDatetimeFormat = setDatetimeFormat;
|
||||
window.$app.setAppLanguage = setAppLanguage;
|
||||
window.$app.trackingResultToClass = trackingResultToClass;
|
||||
window.$app.updateFeedLength = updateFeedLength;
|
||||
window.$app.updateStatsLoop = updateStatsLoop;
|
||||
window.$app.updateVrElectronLoop = updateVrElectronLoop;
|
||||
window.$app.cleanHudFeedLoop = cleanHudFeedLoop;
|
||||
window.$app.cleanHudFeed = cleanHudFeed;
|
||||
|
||||
window.$app.vrState = vrState;
|
||||
|
||||
AppApiVr.VrInit();
|
||||
});
|
||||
});
|
||||
|
||||
function configUpdate(json) {
|
||||
vrState.config = JSON.parse(json);
|
||||
vrState.hudFeed = [];
|
||||
vrState.hudTimeout = [];
|
||||
setDatetimeFormat();
|
||||
setAppLanguage(vrState.config.appLanguage);
|
||||
updateFeedLength();
|
||||
if (
|
||||
vrState.config.vrOverlayCpuUsage !== vrState.cpuUsageEnabled ||
|
||||
vrState.config.pcUptimeOnFeed !== vrState.pcUptimeEnabled
|
||||
) {
|
||||
vrState.cpuUsageEnabled = vrState.config.vrOverlayCpuUsage;
|
||||
vrState.pcUptimeEnabled = vrState.config.pcUptimeOnFeed;
|
||||
AppApiVr.ToggleSystemMonitor(vrState.cpuUsageEnabled || vrState.pcUptimeEnabled);
|
||||
}
|
||||
if (vrState.config.notificationOpacity !== vrState.notificationOpacity) {
|
||||
vrState.notificationOpacity = vrState.config.notificationOpacity;
|
||||
setNotyOpacity(vrState.notificationOpacity);
|
||||
}
|
||||
}
|
||||
|
||||
function updateOnlineFriendCount(count) {
|
||||
vrState.onlineFriendCount = parseInt(count, 10);
|
||||
}
|
||||
|
||||
function nowPlayingUpdate(json) {
|
||||
vrState.nowPlaying = JSON.parse(json);
|
||||
if (vrState.appType === '2') {
|
||||
const circle = /** @type {SVGCircleElement} */ (
|
||||
document.querySelector('.np-progress-circle-stroke')
|
||||
);
|
||||
|
||||
if (vrState.lastLocation.progressPie && vrState.nowPlaying.percentage !== 0) {
|
||||
circle.style.opacity = (0.5).toString();
|
||||
const circumference = circle.getTotalLength();
|
||||
circle.style.strokeDashoffset = (
|
||||
circumference -
|
||||
(vrState.nowPlaying.percentage / 100) * circumference
|
||||
).toString();
|
||||
} else {
|
||||
circle.style.opacity = '0';
|
||||
}
|
||||
}
|
||||
updateFeedLength();
|
||||
}
|
||||
|
||||
function lastLocationUpdate(json) {
|
||||
vrState.lastLocation = JSON.parse(json);
|
||||
}
|
||||
|
||||
function wristFeedUpdate(json) {
|
||||
vrState.wristFeed = JSON.parse(json);
|
||||
updateFeedLength();
|
||||
}
|
||||
|
||||
function updateFeedLength() {
|
||||
if (vrState.appType === '2' || vrState.wristFeed.length === 0) {
|
||||
return;
|
||||
}
|
||||
let length = 16;
|
||||
if (!vrState.config.hideDevicesFromFeed) {
|
||||
length -= 2;
|
||||
if (vrState.deviceCount > 8) {
|
||||
length -= 1;
|
||||
}
|
||||
}
|
||||
if (vrState.nowPlaying.playing) {
|
||||
length -= 1;
|
||||
}
|
||||
if (length < vrState.wristFeed.length) {
|
||||
vrState.wristFeed.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshCustomScript() {
|
||||
if (document.contains(document.getElementById('vr-custom-script'))) {
|
||||
document.getElementById('vr-custom-script').remove();
|
||||
}
|
||||
const customScript = await AppApiVr.CustomVrScript();
|
||||
if (customScript) {
|
||||
const head = document.head;
|
||||
const $vrCustomScript = document.createElement('script');
|
||||
$vrCustomScript.setAttribute('id', 'vr-custom-script');
|
||||
$vrCustomScript.type = 'text/javascript';
|
||||
$vrCustomScript.textContent = customScript;
|
||||
head.appendChild($vrCustomScript);
|
||||
}
|
||||
}
|
||||
|
||||
function setNotyOpacity(value) {
|
||||
const opacity = (value / 100).toFixed(2);
|
||||
let element = document.getElementById('noty-opacity');
|
||||
if (!element) {
|
||||
document.body.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
`<style id="noty-opacity">.noty_layout { opacity: ${opacity}; }</style>`
|
||||
);
|
||||
element = document.getElementById('noty-opacity');
|
||||
}
|
||||
element.innerHTML = `.noty_layout { opacity: ${opacity}; }`;
|
||||
}
|
||||
|
||||
async function updateStatsLoop() {
|
||||
try {
|
||||
vrState.currentTime = new Date()
|
||||
.toLocaleDateString(vrState.currentCulture, {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
hourCycle: vrState.config.dtHour12 ? 'h12' : 'h23'
|
||||
})
|
||||
.replace(' AM', ' am')
|
||||
.replace(' PM', ' pm')
|
||||
.replace(',', '');
|
||||
|
||||
if (vrState.cpuUsageEnabled) {
|
||||
const cpuUsage = await AppApiVr.CpuUsage();
|
||||
vrState.cpuUsage = cpuUsage.toFixed(0);
|
||||
}
|
||||
if (vrState.lastLocation.date !== 0) {
|
||||
vrState.lastLocationTimer = timeToText(Date.now() - vrState.lastLocation.date);
|
||||
} else {
|
||||
vrState.lastLocationTimer = '';
|
||||
}
|
||||
if (vrState.lastLocation.onlineFor) {
|
||||
vrState.onlineForTimer = timeToText(Date.now() - vrState.lastLocation.onlineFor);
|
||||
} else {
|
||||
vrState.onlineForTimer = '';
|
||||
}
|
||||
|
||||
if (!vrState.config.hideDevicesFromFeed) {
|
||||
AppApiVr.GetVRDevices().then((devices) => {
|
||||
let deviceList = [];
|
||||
let baseStations = 0;
|
||||
devices.forEach((device) => {
|
||||
device[3] = parseInt(device[3], 10).toString();
|
||||
if (device[0] === 'base' && device[1] === 'connected') {
|
||||
baseStations++;
|
||||
} else {
|
||||
deviceList.push(device);
|
||||
}
|
||||
});
|
||||
vrState.deviceCount = deviceList.length;
|
||||
const deviceValue = (dev) => {
|
||||
if (dev[0] === 'headset') return 0;
|
||||
if (dev[0] === 'leftController') return 1;
|
||||
if (dev[0] === 'rightController') return 2;
|
||||
if (dev[0].toLowerCase().includes('controller')) return 3;
|
||||
if (dev[0] === 'tracker' || dev[0] === 'base') return 4;
|
||||
return 5;
|
||||
};
|
||||
deviceList.sort((a, b) => deviceValue(a) - deviceValue(b));
|
||||
deviceList.sort((a, b) => {
|
||||
if (a[1] === b[1]) {
|
||||
return 0;
|
||||
}
|
||||
if (a[1] === 'connected') {
|
||||
return -1;
|
||||
}
|
||||
if (a[1] === 'disconnected') {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
if (baseStations > 0) {
|
||||
deviceList.push(['base', 'connected', '', baseStations]);
|
||||
vrState.deviceCount += 1;
|
||||
}
|
||||
vrState.devices = deviceList;
|
||||
});
|
||||
} else {
|
||||
vrState.devices = [];
|
||||
}
|
||||
if (vrState.config.pcUptimeOnFeed) {
|
||||
AppApiVr.GetUptime().then((uptime) => {
|
||||
if (uptime) {
|
||||
vrState.pcUptime = timeToText(uptime);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
vrState.pcUptime = '';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
workerTimers.setTimeout(() => updateStatsLoop(), 500);
|
||||
}
|
||||
|
||||
async function updateVrElectronLoop() {
|
||||
try {
|
||||
if (vrState.appType === '1') {
|
||||
const wristOverlayQueue = await AppApiVr.GetExecuteVrFeedFunctionQueue();
|
||||
if (wristOverlayQueue) {
|
||||
wristOverlayQueue.forEach((item) => {
|
||||
// item[0] is the function name, item[1] is already an object
|
||||
const fullFunctionName = item[0];
|
||||
const jsonArg = item[1];
|
||||
|
||||
if (
|
||||
typeof window.$app === 'object' &&
|
||||
typeof window.$app[fullFunctionName] === 'function'
|
||||
) {
|
||||
window.$app[fullFunctionName](jsonArg);
|
||||
} else {
|
||||
console.error(`$app.${fullFunctionName} is not defined or is not a function`);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const hmdOverlayQueue = await AppApiVr.GetExecuteVrOverlayFunctionQueue();
|
||||
if (hmdOverlayQueue) {
|
||||
hmdOverlayQueue.forEach((item) => {
|
||||
// item[0] is the function name, item[1] is already an object
|
||||
const fullFunctionName = item[0];
|
||||
const jsonArg = item[1];
|
||||
|
||||
if (
|
||||
typeof window.$app === 'object' &&
|
||||
typeof window.$app[fullFunctionName] === 'function'
|
||||
) {
|
||||
window.$app[fullFunctionName](jsonArg);
|
||||
} else {
|
||||
console.error(`$app.${fullFunctionName} is not defined or is not a function`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
workerTimers.setTimeout(() => updateVrElectronLoop(), 500);
|
||||
}
|
||||
|
||||
function playNoty(json) {
|
||||
let { noty, message, image } = JSON.parse(json);
|
||||
if (typeof noty === 'undefined') {
|
||||
console.error('noty is undefined');
|
||||
return;
|
||||
}
|
||||
noty = escapeTagRecursive(noty);
|
||||
message = escapeTag(message) || '';
|
||||
let text = '';
|
||||
let img = '';
|
||||
if (image) {
|
||||
img = `<img class="noty-img" src="${image}"></img>`;
|
||||
}
|
||||
switch (noty.type) {
|
||||
case 'OnPlayerJoined':
|
||||
text = `<strong>${noty.displayName}</strong> has joined`;
|
||||
break;
|
||||
case 'OnPlayerLeft':
|
||||
text = `<strong>${noty.displayName}</strong> has left`;
|
||||
break;
|
||||
case 'OnPlayerJoining':
|
||||
text = `<strong>${noty.displayName}</strong> is joining`;
|
||||
break;
|
||||
case 'GPS':
|
||||
text = `<strong>${noty.displayName}</strong> is in ${displayLocation(
|
||||
noty.location,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
)}`;
|
||||
break;
|
||||
case 'Online':
|
||||
let locationName = '';
|
||||
if (noty.worldName) {
|
||||
locationName = ` to ${displayLocation(noty.location, noty.worldName, noty.groupName)}`;
|
||||
}
|
||||
text = `<strong>${noty.displayName}</strong> has logged in${locationName}`;
|
||||
break;
|
||||
case 'Offline':
|
||||
text = `<strong>${noty.displayName}</strong> has logged out`;
|
||||
break;
|
||||
case 'Status':
|
||||
text = `<strong>${noty.displayName}</strong> status is now <i>${noty.status}</i> ${noty.statusDescription}`;
|
||||
break;
|
||||
case 'invite':
|
||||
text = `<strong>${noty.senderUsername}</strong> has invited you to ${displayLocation(
|
||||
noty.details.worldId,
|
||||
noty.details.worldName,
|
||||
''
|
||||
)}${message}`;
|
||||
break;
|
||||
case 'requestInvite':
|
||||
text = `<strong>${noty.senderUsername}</strong> has requested an invite ${message}`;
|
||||
break;
|
||||
case 'inviteResponse':
|
||||
text = `<strong>${noty.senderUsername}</strong> has responded to your invite ${message}`;
|
||||
break;
|
||||
case 'requestInviteResponse':
|
||||
text = `<strong>${noty.senderUsername}</strong> has responded to your invite request ${message}`;
|
||||
break;
|
||||
case 'friendRequest':
|
||||
text = `<strong>${noty.senderUsername}</strong> has sent you a friend request`;
|
||||
break;
|
||||
case 'Friend':
|
||||
text = `<strong>${noty.displayName}</strong> is now your friend`;
|
||||
break;
|
||||
case 'Unfriend':
|
||||
text = `<strong>${noty.displayName}</strong> is no longer your friend`;
|
||||
break;
|
||||
case 'TrustLevel':
|
||||
text = `<strong>${noty.displayName}</strong> trust level is now ${noty.trustLevel}`;
|
||||
break;
|
||||
case 'DisplayName':
|
||||
text = `<strong>${noty.previousDisplayName}</strong> changed their name to ${noty.displayName}`;
|
||||
break;
|
||||
case 'boop':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'groupChange':
|
||||
text = `<strong>${noty.senderUsername}</strong> ${noty.message}`;
|
||||
break;
|
||||
case 'group.announcement':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'group.informative':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'group.invite':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'group.joinRequest':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'group.transfer':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'group.queueReady':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'instance.closed':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'PortalSpawn':
|
||||
if (noty.displayName) {
|
||||
text = `<strong>${noty.displayName}</strong> has spawned a portal to ${displayLocation(
|
||||
noty.instanceId,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
)}`;
|
||||
} else {
|
||||
text = 'User has spawned a portal';
|
||||
}
|
||||
break;
|
||||
case 'AvatarChange':
|
||||
text = `<strong>${noty.displayName}</strong> changed into avatar ${noty.name}`;
|
||||
break;
|
||||
case 'ChatBoxMessage':
|
||||
text = `<strong>${noty.displayName}</strong> said ${noty.text}`;
|
||||
break;
|
||||
case 'Event':
|
||||
text = noty.data;
|
||||
break;
|
||||
case 'External':
|
||||
text = noty.message;
|
||||
break;
|
||||
case 'VideoPlay':
|
||||
text = `<strong>Now playing:</strong> ${noty.notyName}`;
|
||||
break;
|
||||
case 'BlockedOnPlayerJoined':
|
||||
text = `Blocked user <strong>${noty.displayName}</strong> has joined`;
|
||||
break;
|
||||
case 'BlockedOnPlayerLeft':
|
||||
text = `Blocked user <strong>${noty.displayName}</strong> has left`;
|
||||
break;
|
||||
case 'MutedOnPlayerJoined':
|
||||
text = `Muted user <strong>${noty.displayName}</strong> has joined`;
|
||||
break;
|
||||
case 'MutedOnPlayerLeft':
|
||||
text = `Muted user <strong>${noty.displayName}</strong> has left`;
|
||||
break;
|
||||
case 'Blocked':
|
||||
text = `<strong>${noty.displayName}</strong> has blocked you`;
|
||||
break;
|
||||
case 'Unblocked':
|
||||
text = `<strong>${noty.displayName}</strong> has unblocked you`;
|
||||
break;
|
||||
case 'Muted':
|
||||
text = `<strong>${noty.displayName}</strong> has muted you`;
|
||||
break;
|
||||
case 'Unmuted':
|
||||
text = `<strong>${noty.displayName}</strong> has unmuted you`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (text) {
|
||||
new Noty({
|
||||
type: 'alert',
|
||||
theme: vrState.config.notificationTheme,
|
||||
timeout: vrState.config.notificationTimeout,
|
||||
layout: vrState.config.notificationPosition,
|
||||
text: `${img}<div class="noty-text">${text}</div>`
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
|
||||
function statusClass(status) {
|
||||
let style = {};
|
||||
if (typeof status === 'undefined') {
|
||||
return style;
|
||||
}
|
||||
if (status === 'active') {
|
||||
// Online
|
||||
style.online = true;
|
||||
} else if (status === 'join me') {
|
||||
// Join Me
|
||||
style.joinme = true;
|
||||
} else if (status === 'ask me') {
|
||||
// Ask Me
|
||||
style.askme = true;
|
||||
} else if (status === 'busy') {
|
||||
// Do Not Disturb
|
||||
style.busy = true;
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
function notyClear() {
|
||||
Noty.closeAll();
|
||||
}
|
||||
|
||||
function cleanHudFeedLoop() {
|
||||
if (!vrState.cleanHudFeedLoopStatus) {
|
||||
return;
|
||||
}
|
||||
cleanHudFeed();
|
||||
if (vrState.hudFeed.length === 0) {
|
||||
vrState.cleanHudFeedLoopStatus = false;
|
||||
return;
|
||||
}
|
||||
workerTimers.setTimeout(() => cleanHudFeedLoop(), 500);
|
||||
}
|
||||
|
||||
function cleanHudFeed() {
|
||||
const dt = Date.now();
|
||||
vrState.hudFeed.forEach((item) => {
|
||||
if (item.time + vrState.config.photonOverlayMessageTimeout < dt) {
|
||||
removeFromArray(vrState.hudFeed, item);
|
||||
}
|
||||
});
|
||||
if (vrState.hudFeed.length > 10) {
|
||||
vrState.hudFeed.length = 10;
|
||||
}
|
||||
if (!vrState.cleanHudFeedLoopStatus) {
|
||||
vrState.cleanHudFeedLoopStatus = true;
|
||||
cleanHudFeedLoop();
|
||||
}
|
||||
}
|
||||
|
||||
function addEntryHudFeed(json) {
|
||||
const data = JSON.parse(json);
|
||||
let combo = 1;
|
||||
vrState.hudFeed.forEach((item) => {
|
||||
if (item.displayName === data.displayName && item.text === data.text) {
|
||||
combo = item.combo + 1;
|
||||
removeFromArray(vrState.hudFeed, item);
|
||||
}
|
||||
});
|
||||
vrState.hudFeed.unshift({
|
||||
time: Date.now(),
|
||||
combo,
|
||||
...data
|
||||
});
|
||||
cleanHudFeed();
|
||||
}
|
||||
|
||||
function updateHudFeedTag(json) {
|
||||
const ref = JSON.parse(json);
|
||||
vrState.hudFeed.forEach((item) => {
|
||||
if (item.userId === ref.userId) {
|
||||
item.colour = ref.colour;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateHudTimeout(json) {
|
||||
vrState.hudTimeout = JSON.parse(json);
|
||||
}
|
||||
|
||||
async function setDatetimeFormat() {
|
||||
vrState.currentCulture = await AppApiVr.CurrentCulture();
|
||||
const formatDate = (date) => {
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
const dt = new Date(date);
|
||||
return dt
|
||||
.toLocaleTimeString(vrState.currentCulture, {
|
||||
hour: '2-digit',
|
||||
minute: 'numeric',
|
||||
hourCycle: vrState.config.dtHour12 ? 'h12' : 'h23'
|
||||
})
|
||||
.replace(' am', '')
|
||||
.replace(' pm', '');
|
||||
};
|
||||
Vue.filter('formatDate', formatDate);
|
||||
}
|
||||
|
||||
function setAppLanguage(appLanguage) {
|
||||
if (!appLanguage) {
|
||||
return;
|
||||
}
|
||||
if (appLanguage !== vrState.appLanguage) {
|
||||
vrState.appLanguage = appLanguage;
|
||||
// @ts-ignore
|
||||
i18n.locale = vrState.appLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
function trackingResultToClass(deviceStatus) {
|
||||
switch (deviceStatus) {
|
||||
case 'Uninitialized':
|
||||
case 'Calibrating_OutOfRange':
|
||||
case 'Fallback_RotationOnly':
|
||||
return 'tracker-error';
|
||||
|
||||
case 'Calibrating_InProgress':
|
||||
case 'Running_OutOfRange':
|
||||
return 'tracker-warning';
|
||||
|
||||
case 'Running_OK':
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
const {
|
||||
appType,
|
||||
config,
|
||||
wristFeed,
|
||||
devices,
|
||||
nowPlaying,
|
||||
lastLocation,
|
||||
lastLocationTimer,
|
||||
onlineForTimer,
|
||||
pcUptime,
|
||||
currentTime,
|
||||
cpuUsageEnabled,
|
||||
cpuUsage,
|
||||
onlineFriendCount,
|
||||
customInfo,
|
||||
hudFeed,
|
||||
hudTimeout
|
||||
} = toRefs(vrState);
|
||||
|
||||
return {
|
||||
appType,
|
||||
config,
|
||||
wristFeed,
|
||||
devices,
|
||||
nowPlaying,
|
||||
lastLocation,
|
||||
lastLocationTimer,
|
||||
onlineForTimer,
|
||||
pcUptime,
|
||||
currentTime,
|
||||
cpuUsageEnabled,
|
||||
cpuUsage,
|
||||
onlineFriendCount,
|
||||
customInfo,
|
||||
hudFeed,
|
||||
hudTimeout,
|
||||
statusClass,
|
||||
trackingResultToClass,
|
||||
$t
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -33,9 +33,9 @@ const avatarReq = {
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{ id: string, releaseStatus?: 'public' | 'private', name?: string, description?: string,tags?: string[] }} params
|
||||
* @returns {Promise<{json: any, params}>}
|
||||
* @type {import('../types/api/avatar').SaveAvatar}
|
||||
*/
|
||||
saveAvatar(params) {
|
||||
return request(`avatars/${params.id}`, {
|
||||
@@ -173,6 +173,24 @@ const avatarReq = {
|
||||
});
|
||||
},
|
||||
|
||||
uploadAvatarImage(imageData) {
|
||||
const params = {
|
||||
tag: 'avatarimage'
|
||||
};
|
||||
return request('file/image', {
|
||||
uploadImage: true,
|
||||
matchingDimensions: false,
|
||||
postData: JSON.stringify(params),
|
||||
imageData
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{ imageData: string, avatarId: string }}
|
||||
* @returns {Promise<{json: any, params}>}
|
||||
|
||||
297
src/api/image.js
297
src/api/image.js
@@ -1,297 +0,0 @@
|
||||
import { request } from '../service/request';
|
||||
import { useAvatarStore, useWorldStore } from '../stores';
|
||||
|
||||
const imageReq = {
|
||||
async uploadAvatarFailCleanup(id) {
|
||||
const avatarStore = useAvatarStore();
|
||||
try {
|
||||
const json = await request(`file/${id}`, {
|
||||
method: 'GET'
|
||||
});
|
||||
const fileId = json.id;
|
||||
const fileVersion = json.versions[json.versions.length - 1].version;
|
||||
request(`file/${fileId}/${fileVersion}/signature/finish`, {
|
||||
method: 'PUT'
|
||||
}).catch(err => console.error('Failed to finish signature:', err));
|
||||
request(`file/${fileId}/${fileVersion}/file/finish`, {
|
||||
method: 'PUT'
|
||||
}).catch(err => console.error('Failed to finish file:', err));
|
||||
} catch (error) {
|
||||
console.error('Failed to cleanup avatar upload:', error);
|
||||
}
|
||||
avatarStore.avatarDialog.loading = false;
|
||||
},
|
||||
|
||||
async uploadAvatarImage(params, fileId) {
|
||||
try {
|
||||
return await request(`file/${fileId}`, {
|
||||
method: 'POST',
|
||||
params
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params,
|
||||
fileId
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadAvatarFailCleanup(fileId);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
async uploadAvatarImageFileStart(params) {
|
||||
try {
|
||||
return await request(
|
||||
`file/${params.fileId}/${params.fileVersion}/file/start`,
|
||||
{
|
||||
method: 'PUT'
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadAvatarFailCleanup(params.fileId);
|
||||
}
|
||||
},
|
||||
|
||||
uploadAvatarImageFileFinish(params) {
|
||||
return request(
|
||||
`file/${params.fileId}/${params.fileVersion}/file/finish`,
|
||||
{
|
||||
method: 'PUT',
|
||||
params: {
|
||||
maxParts: 0,
|
||||
nextPartNumber: 0
|
||||
}
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
async uploadAvatarImageSigStart(params) {
|
||||
try {
|
||||
return await request(
|
||||
`file/${params.fileId}/${params.fileVersion}/signature/start`,
|
||||
{
|
||||
method: 'PUT'
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadAvatarFailCleanup(params.fileId);
|
||||
}
|
||||
},
|
||||
|
||||
uploadAvatarImageSigFinish(params) {
|
||||
return request(
|
||||
`file/${params.fileId}/${params.fileVersion}/signature/finish`,
|
||||
{
|
||||
method: 'PUT',
|
||||
params: {
|
||||
maxParts: 0,
|
||||
nextPartNumber: 0
|
||||
}
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
setAvatarImage(params) {
|
||||
return request(`avatars/${params.id}`, {
|
||||
method: 'PUT',
|
||||
params
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
async uploadWorldFailCleanup(id) {
|
||||
const worldStore = useWorldStore();
|
||||
try {
|
||||
const json = await request(`file/${id}`, {
|
||||
method: 'GET'
|
||||
});
|
||||
const fileId = json.id;
|
||||
const fileVersion = json.versions[json.versions.length - 1].version;
|
||||
request(`file/${fileId}/${fileVersion}/signature/finish`, {
|
||||
method: 'PUT'
|
||||
}).catch(err => console.error('Failed to finish signature:', err));
|
||||
request(`file/${fileId}/${fileVersion}/file/finish`, {
|
||||
method: 'PUT'
|
||||
}).catch(err => console.error('Failed to finish file:', err));
|
||||
} catch (error) {
|
||||
console.error('Failed to cleanup world upload:', error);
|
||||
}
|
||||
worldStore.worldDialog.loading = false;
|
||||
},
|
||||
|
||||
async uploadWorldImage(params, fileId) {
|
||||
try {
|
||||
return await request(`file/${fileId}`, {
|
||||
method: 'POST',
|
||||
params
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params,
|
||||
fileId
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadWorldFailCleanup(fileId);
|
||||
}
|
||||
return void 0;
|
||||
},
|
||||
|
||||
async uploadWorldImageFileStart(params) {
|
||||
try {
|
||||
return await request(
|
||||
`file/${params.fileId}/${params.fileVersion}/file/start`,
|
||||
{
|
||||
method: 'PUT'
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadWorldFailCleanup(params.fileId);
|
||||
}
|
||||
return void 0;
|
||||
},
|
||||
|
||||
uploadWorldImageFileFinish(params) {
|
||||
return request(
|
||||
`file/${params.fileId}/${params.fileVersion}/file/finish`,
|
||||
{
|
||||
method: 'PUT',
|
||||
params: {
|
||||
maxParts: 0,
|
||||
nextPartNumber: 0
|
||||
}
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
async uploadWorldImageSigStart(params) {
|
||||
try {
|
||||
return await request(
|
||||
`file/${params.fileId}/${params.fileVersion}/signature/start`,
|
||||
{
|
||||
method: 'PUT'
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
imageReq.uploadWorldFailCleanup(params.fileId);
|
||||
}
|
||||
return void 0;
|
||||
},
|
||||
|
||||
uploadWorldImageSigFinish(params) {
|
||||
return request(
|
||||
`file/${params.fileId}/${params.fileVersion}/signature/finish`,
|
||||
{
|
||||
method: 'PUT',
|
||||
params: {
|
||||
maxParts: 0,
|
||||
nextPartNumber: 0
|
||||
}
|
||||
}
|
||||
).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
setWorldImage(params) {
|
||||
const worldStore = useWorldStore();
|
||||
return request(`worlds/${params.id}`, {
|
||||
method: 'PUT',
|
||||
params
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
args.ref = worldStore.applyWorld(json);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
getAvatarImages(params) {
|
||||
return request(`file/${params.fileId}`, {
|
||||
method: 'GET'
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
getWorldImages(params) {
|
||||
return request(`file/${params.fileId}`, {
|
||||
method: 'GET',
|
||||
params
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default imageReq;
|
||||
@@ -16,7 +16,6 @@ import favoriteRequest from './favorite';
|
||||
import vrcPlusIconRequest from './vrcPlusIcon';
|
||||
import vrcPlusImageRequest from './vrcPlusImage';
|
||||
import inviteMessagesRequest from './inviteMessages';
|
||||
import imageRequest from './image';
|
||||
import miscRequest from './misc';
|
||||
import groupRequest from './group';
|
||||
import authRequest from './auth';
|
||||
@@ -37,7 +36,6 @@ window.request = {
|
||||
vrcPlusIconRequest,
|
||||
vrcPlusImageRequest,
|
||||
inviteMessagesRequest,
|
||||
imageRequest,
|
||||
miscRequest,
|
||||
authRequest,
|
||||
groupRequest,
|
||||
@@ -59,7 +57,6 @@ export {
|
||||
vrcPlusIconRequest,
|
||||
vrcPlusImageRequest,
|
||||
inviteMessagesRequest,
|
||||
imageRequest,
|
||||
miscRequest,
|
||||
authRequest,
|
||||
groupRequest,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { $app } from '../app';
|
||||
import { t } from '../plugin';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { i18n } from '../plugin/i18n';
|
||||
import { request } from '../service/request';
|
||||
import { useInstanceStore } from '../stores';
|
||||
|
||||
@@ -110,14 +110,14 @@ const instanceReq = {
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err?.error?.message) {
|
||||
$app.$message({
|
||||
ElMessage({
|
||||
message: err.error.message,
|
||||
type: 'error'
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
$app.$message({
|
||||
message: t('message.instance.not_allowed'),
|
||||
ElMessage({
|
||||
message: i18n.global.t('message.instance.not_allowed'),
|
||||
type: 'error'
|
||||
});
|
||||
throw err;
|
||||
|
||||
@@ -6,12 +6,13 @@ function getCurrentUserId() {
|
||||
}
|
||||
|
||||
const miscReq = {
|
||||
getBundles(fileId) {
|
||||
return request(`file/${fileId}`, {
|
||||
getFile(params) {
|
||||
return request(`file/${params.fileId}`, {
|
||||
method: 'GET'
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
|
||||
@@ -140,6 +140,24 @@ const worldReq = {
|
||||
args.ref = worldStore.applyWorld(json);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
uploadWorldImage(imageData) {
|
||||
const params = {
|
||||
tag: 'worldimage'
|
||||
};
|
||||
return request('file/image', {
|
||||
uploadImage: true,
|
||||
matchingDimensions: false,
|
||||
postData: JSON.stringify(params),
|
||||
imageData
|
||||
}).then((json) => {
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return args;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
19
src/app.js
19
src/app.js
@@ -4,20 +4,29 @@
|
||||
// This work is licensed under the terms of the MIT license.
|
||||
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||||
|
||||
import Vue from 'vue';
|
||||
import { createApp } from 'vue';
|
||||
import './bootstrap';
|
||||
import { i18n } from './plugin/i18n';
|
||||
import App from './App.vue';
|
||||
import { pinia } from './stores';
|
||||
import ElementPlus from 'element-plus';
|
||||
|
||||
import './app.scss';
|
||||
import registerComponents from './plugin/components';
|
||||
|
||||
console.log(`isLinux: ${LINUX}`);
|
||||
|
||||
// #region | Hey look it's most of VRCX!
|
||||
// prompt: 'Please clean up and refactor the VRCX codebase.'
|
||||
|
||||
const $app = new Vue({
|
||||
pinia,
|
||||
render: (h) => h(App)
|
||||
}).$mount('#root');
|
||||
const $app = createApp(App);
|
||||
$app.use(pinia);
|
||||
$app.use(i18n);
|
||||
$app.use(ElementPlus);
|
||||
|
||||
registerComponents($app);
|
||||
|
||||
$app.mount('#root');
|
||||
|
||||
window.$app = $app;
|
||||
export { $app };
|
||||
|
||||
75
src/app.pug
75
src/app.pug
@@ -1,75 +0,0 @@
|
||||
doctype html
|
||||
#x-app.x-app(@dragenter.prevent @dragover.prevent @drop.prevent)
|
||||
//- ### Login ###
|
||||
Login(v-if='!watchState.isLoggedIn')
|
||||
|
||||
VRCXUpdateDialog
|
||||
|
||||
template(v-if='watchState.isLoggedIn')
|
||||
//- ### Menu ###
|
||||
NavMenu
|
||||
|
||||
//- ### Sidebar ###
|
||||
Sidebar
|
||||
|
||||
//- ### Tabs ###
|
||||
Feed
|
||||
|
||||
GameLog
|
||||
|
||||
PlayerList
|
||||
|
||||
Search
|
||||
|
||||
Favorites
|
||||
|
||||
FriendLog
|
||||
|
||||
Moderation
|
||||
|
||||
Notification
|
||||
|
||||
FriendList
|
||||
|
||||
Charts
|
||||
|
||||
Tools
|
||||
|
||||
Profile
|
||||
|
||||
Settings
|
||||
|
||||
//- ## Dialogs ## -\\
|
||||
UserDialog
|
||||
|
||||
WorldDialog
|
||||
|
||||
AvatarDialog
|
||||
|
||||
GroupDialog
|
||||
|
||||
GroupMemberModerationDialog
|
||||
|
||||
GalleryDialog
|
||||
|
||||
FullscreenImageDialog
|
||||
|
||||
PreviousInstancesInfoDialog
|
||||
|
||||
LaunchDialog
|
||||
|
||||
LaunchOptionsDialog
|
||||
|
||||
FriendImportDialog
|
||||
|
||||
WorldImportDialog
|
||||
|
||||
AvatarImportDialog
|
||||
|
||||
ChooseFavoriteGroupDialog
|
||||
|
||||
EditInviteMessageDialog
|
||||
|
||||
VRChatConfigDialog
|
||||
|
||||
PrimaryPasswordDialog
|
||||
406
src/app.scss
406
src/app.scss
@@ -10,10 +10,12 @@
|
||||
|
||||
@use './assets/scss/flags.scss';
|
||||
@use './assets/scss/animated-emoji.scss';
|
||||
@use 'element-plus/theme-chalk/src/index.scss' as *;
|
||||
|
||||
@import '~animate.css/animate.min.css';
|
||||
@import '~noty/lib/noty.css';
|
||||
@import '~element-ui/lib/theme-chalk/index.css';
|
||||
@import 'element-plus/theme-chalk/src/dark/css-vars.scss';
|
||||
@import 'animate.css/animate.min.css';
|
||||
@import 'noty/lib/noty.css';
|
||||
@import 'remixicon/fonts/remixicon.css';
|
||||
|
||||
.color-palettes {
|
||||
background: #409eff;
|
||||
@@ -26,6 +28,10 @@
|
||||
background: #c0c4cc;
|
||||
}
|
||||
|
||||
.el-icon.is-loading {
|
||||
animation: rotating 2s linear infinite;
|
||||
}
|
||||
|
||||
.noty_layout {
|
||||
word-break: break-all;
|
||||
}
|
||||
@@ -78,42 +84,46 @@
|
||||
border-bottom: 1px solid #a0b55c;
|
||||
}
|
||||
|
||||
.el-table + .pagination-bar {
|
||||
margin-top: 15px;
|
||||
// .el-table + .pagination-bar {
|
||||
// margin-top: 15px;
|
||||
// }
|
||||
|
||||
.el-table__expanded-cell[class*='cell'] {
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
.el-table--mini .el-table__expanded-cell[class*='cell'] {
|
||||
padding: 20px 50px;
|
||||
.el-table--small .cell {
|
||||
// fix expand arrow position
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.el-table--mini .el-table__cell {
|
||||
padding: 5px 0;
|
||||
}
|
||||
// .el-table--mini .el-table__cell {
|
||||
// padding: 5px 0;
|
||||
// }
|
||||
|
||||
// expand table cell on hover, TODO: replace with a better solution
|
||||
.el-table .cell {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
// .el-table .cell {
|
||||
// display: -webkit-box;
|
||||
// -webkit-box-orient: vertical;
|
||||
// -webkit-line-clamp: 1;
|
||||
// }
|
||||
|
||||
.el-table__row:hover .el-table__cell .cell {
|
||||
display: revert;
|
||||
-webkit-line-clamp: unset;
|
||||
}
|
||||
// .el-table__row:hover .el-table__cell .cell {
|
||||
// display: revert;
|
||||
// -webkit-line-clamp: unset;
|
||||
// }
|
||||
|
||||
.el-table th.is-sortable .cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
// .el-table th.is-sortable .cell {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// }
|
||||
|
||||
.el-table .caret-wrapper {
|
||||
margin-top: 4.5px;
|
||||
}
|
||||
// .el-table .caret-wrapper {
|
||||
// margin-top: 4.5px;
|
||||
// }
|
||||
|
||||
.notification-table .el-table .cell {
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
// .notification-table .el-table .cell {
|
||||
// -webkit-line-clamp: 2;
|
||||
// }
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 20px;
|
||||
@@ -146,6 +156,10 @@
|
||||
unicode-range: U+2026;
|
||||
}
|
||||
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
'ellipsis-font', 'Noto Sans KR', 'Noto Sans JP', 'Noto Sans TC',
|
||||
@@ -313,11 +327,11 @@ hr.x-vertical-divider {
|
||||
order: 99;
|
||||
}
|
||||
|
||||
.el-popper.x-quick-search {
|
||||
width: 225px;
|
||||
min-width: 0 !important;
|
||||
border: none;
|
||||
}
|
||||
// .el-popper.x-quick-search {
|
||||
// width: 225px;
|
||||
// min-width: 0 !important;
|
||||
// border: none;
|
||||
// }
|
||||
|
||||
.el-popper.x-quick-search .el-select-dropdown__item {
|
||||
width: 100%;
|
||||
@@ -383,13 +397,13 @@ hr.x-vertical-divider {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item .x-friend-item:hover {
|
||||
background: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
// .el-select-dropdown__item .x-friend-item:hover {
|
||||
// background: none;
|
||||
// border-radius: 0;
|
||||
// }
|
||||
|
||||
.x-dialog .x-friend-item {
|
||||
width: 175px;
|
||||
width: 167px;
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar {
|
||||
@@ -438,7 +452,7 @@ img.friends-list-avatar {
|
||||
.x-friend-item > .avatar.joinme.mobile > img,
|
||||
.x-friend-item > .avatar.askme.mobile > img,
|
||||
.x-friend-item > .avatar.busy.mobile > img {
|
||||
mask-image: url(assets/images/masks/usercutoutmobile.svg);
|
||||
mask-image: url(./assets/images/masks/usercutoutmobile.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.online.mobile::after,
|
||||
@@ -452,7 +466,7 @@ img.friends-list-avatar {
|
||||
height: 14px;
|
||||
content: '';
|
||||
border-radius: 0px;
|
||||
mask-image: url(assets/images/masks/phone.svg);
|
||||
mask-image: url(./assets/images/masks/phone.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.active > img,
|
||||
@@ -461,7 +475,7 @@ img.friends-list-avatar {
|
||||
.x-friend-item > .avatar.askme > img,
|
||||
.x-friend-item > .avatar.busy > img,
|
||||
.x-friend-item > .avatar.offline > img {
|
||||
mask-image: url(assets/images/masks/usercutout.svg);
|
||||
mask-image: url(./assets/images/masks/usercutout.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.active::after,
|
||||
@@ -490,17 +504,17 @@ img.friends-list-avatar {
|
||||
|
||||
.x-friend-item > .avatar.joinme::after {
|
||||
background: #409eff;
|
||||
mask-image: url(assets/images/masks/joinme.svg);
|
||||
mask-image: url(./assets/images/masks/joinme.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.askme::after {
|
||||
background: #ff9500;
|
||||
mask-image: url(assets/images/masks/askme.svg);
|
||||
mask-image: url(./assets/images/masks/askme.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.busy::after {
|
||||
background: #ff2c2c;
|
||||
mask-image: url(assets/images/masks/busy.svg);
|
||||
mask-image: url(./assets/images/masks/busy.svg);
|
||||
}
|
||||
|
||||
.x-friend-item > .avatar.offline::after {
|
||||
@@ -565,16 +579,11 @@ img.friends-list-avatar {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.x-change-image-item > img,
|
||||
.x-change-image-item > .el-popover__reference-wrapper > img {
|
||||
width: 240px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.current-image {
|
||||
border: 2px solid #67c23a;
|
||||
padding: 2px 2px 0 2px;
|
||||
}
|
||||
// .x-change-image-item > img,
|
||||
// .x-change-image-item > .el-popover__reference-wrapper > img {
|
||||
// width: 240px;
|
||||
// height: 180px;
|
||||
// }
|
||||
|
||||
.x-dialog > .el-dialog {
|
||||
max-width: 100%;
|
||||
@@ -595,12 +604,12 @@ img.friends-list-avatar {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.el-popper.hex {
|
||||
min-width: auto;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
text-align: center;
|
||||
}
|
||||
// .el-popper.hex {
|
||||
// min-width: auto;
|
||||
// padding: 10px;
|
||||
// font-family: monospace;
|
||||
// text-align: center;
|
||||
// }
|
||||
|
||||
i.x-user-status,
|
||||
i.x-status-icon {
|
||||
@@ -621,17 +630,17 @@ i.x-user-status.online {
|
||||
|
||||
i.x-user-status.joinme {
|
||||
background: #409eff;
|
||||
mask-image: url(assets/images/masks/joinme.svg);
|
||||
mask-image: url(./assets/images/masks/joinme.svg);
|
||||
}
|
||||
|
||||
i.x-user-status.askme {
|
||||
background: #ff9500;
|
||||
mask-image: url(assets/images/masks/askme.svg);
|
||||
mask-image: url(./assets/images/masks/askme.svg);
|
||||
}
|
||||
|
||||
i.x-user-status.busy {
|
||||
background: #ff2c2c;
|
||||
mask-image: url(assets/images/masks/busy.svg);
|
||||
mask-image: url(./assets/images/masks/busy.svg);
|
||||
}
|
||||
|
||||
i.x-status-icon.green {
|
||||
@@ -651,67 +660,79 @@ i.x-status-icon.red {
|
||||
}
|
||||
|
||||
.x-tag-friend {
|
||||
color: rgb(255, 208, 0) !important;
|
||||
color: rgb(255, 208, 0);
|
||||
border-color: rgb(255, 208, 0) !important;
|
||||
}
|
||||
|
||||
.x-tag-vrcplus {
|
||||
color: rgb(255, 208, 0) !important;
|
||||
color: rgb(255, 208, 0);
|
||||
border-color: rgb(255, 208, 0) !important;
|
||||
}
|
||||
|
||||
.x-tag-platform-pc {
|
||||
color: #409eff !important;
|
||||
color: #409eff;
|
||||
border-color: #409eff !important;
|
||||
}
|
||||
|
||||
.x-tag-platform-quest {
|
||||
color: #67c23a !important;
|
||||
color: #67c23a;
|
||||
border-color: #67c23a !important;
|
||||
}
|
||||
|
||||
.x-tag-platform-ios {
|
||||
color: #c7c7ce !important;
|
||||
color: #c7c7ce;
|
||||
border-color: #c7c7ce !important;
|
||||
}
|
||||
|
||||
.x-tag-platform-other {
|
||||
color: #ff4177 !important;
|
||||
color: #ff4177;
|
||||
border-color: #ff4177 !important;
|
||||
}
|
||||
|
||||
.x-tag-age-verification {
|
||||
color: #5c70ec !important;
|
||||
color: #5c70ec;
|
||||
border-color: #5c70ec !important;
|
||||
}
|
||||
|
||||
.x-tag-border-left {
|
||||
border-left: 0.8px solid;
|
||||
margin-left: 5px;
|
||||
padding-left: 5px;
|
||||
padding-bottom: 0.5px;
|
||||
}
|
||||
|
||||
.x-grey {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.el-tree-node {
|
||||
white-space: normal;
|
||||
.x-popover-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.el-tree-node__content {
|
||||
height: auto;
|
||||
}
|
||||
// .el-tree-node {
|
||||
// white-space: normal;
|
||||
// }
|
||||
|
||||
.el-progress-bar {
|
||||
padding-right: 80px;
|
||||
margin-right: -85px;
|
||||
}
|
||||
// .el-tree-node__content {
|
||||
// height: auto;
|
||||
// }
|
||||
|
||||
.el-progress__text {
|
||||
color: #c8c8c8;
|
||||
}
|
||||
// .el-progress-bar {
|
||||
// padding-right: 80px;
|
||||
// margin-right: -85px;
|
||||
// }
|
||||
|
||||
.x-user-dialog .el-textarea__inner {
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
// .el-progress__text {
|
||||
// color: #c8c8c8;
|
||||
// }
|
||||
|
||||
// .x-user-dialog .el-textarea__inner {
|
||||
// padding: 0;
|
||||
// background: none;
|
||||
// border: 0;
|
||||
// border-radius: 2px;
|
||||
// }
|
||||
|
||||
.options-container {
|
||||
margin-top: 30px;
|
||||
@@ -767,28 +788,28 @@ i.x-status-icon.red {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.el-color-picker__trigger {
|
||||
border: unset;
|
||||
}
|
||||
// .el-color-picker__trigger {
|
||||
// border: unset;
|
||||
// }
|
||||
|
||||
.el-color-picker__color {
|
||||
border: 0.5px solid #999;
|
||||
}
|
||||
// .el-color-picker__color {
|
||||
// border: 0.5px solid #999;
|
||||
// }
|
||||
|
||||
.el-button--success {
|
||||
background-color: #67c23a !important;
|
||||
border-color: #67c23a !important;
|
||||
}
|
||||
// .el-button--success {
|
||||
// background-color: #67c23a !important;
|
||||
// border-color: #67c23a !important;
|
||||
// }
|
||||
|
||||
.x-dialog .el-button--danger {
|
||||
background-color: #f56c6c !important;
|
||||
border-color: #f56c6c !important;
|
||||
}
|
||||
// .x-dialog .el-button--danger {
|
||||
// background-color: #f56c6c !important;
|
||||
// border-color: #f56c6c !important;
|
||||
// }
|
||||
|
||||
.el-button--warning {
|
||||
background-color: #e6a23c !important;
|
||||
border-color: #e6a23c !important;
|
||||
}
|
||||
// .el-button--warning {
|
||||
// background-color: #e6a23c !important;
|
||||
// border-color: #e6a23c !important;
|
||||
// }
|
||||
|
||||
.avatar-info {
|
||||
margin-top: 2px;
|
||||
@@ -812,14 +833,14 @@ i.x-status-icon.red {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
// .el-form-item {
|
||||
// margin-bottom: 4px;
|
||||
// }
|
||||
|
||||
.photon-event-table .el-table--mini .el-table__cell,
|
||||
.current-instance-table .el-table--mini .el-table__cell {
|
||||
padding: 0;
|
||||
}
|
||||
// .photon-event-table .el-table--mini .el-table__cell,
|
||||
// .current-instance-table .el-table--mini .el-table__cell {
|
||||
// padding: 0;
|
||||
// }
|
||||
|
||||
.photon-event-table {
|
||||
margin-top: 20px;
|
||||
@@ -835,14 +856,14 @@ i.x-status-icon.red {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.el-pagination .el-select .el-input .el-input__inner,
|
||||
.el-input--mini .el-input__icon {
|
||||
height: 22px;
|
||||
}
|
||||
// .el-pagination .el-select .el-input .el-input__inner,
|
||||
// .el-input--mini .el-input__icon {
|
||||
// height: 22px;
|
||||
// }
|
||||
|
||||
.el-pagination .btn-next {
|
||||
margin-right: 10px;
|
||||
}
|
||||
// .el-pagination .btn-next {
|
||||
// margin-right: 10px;
|
||||
// }
|
||||
|
||||
.el-dialog,
|
||||
.el-message-box {
|
||||
@@ -850,35 +871,31 @@ i.x-status-icon.red {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.el-tabs__nav-wrap::after {
|
||||
background-color: #e4e7ed14;
|
||||
}
|
||||
// .el-tabs__nav-wrap::after {
|
||||
// background-color: #e4e7ed14;
|
||||
// }
|
||||
|
||||
.dialog-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.group-banner-image {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vrc-instance-queue-message {
|
||||
padding: 3px;
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
.vrc-instance-queue-message .el-message__content {
|
||||
margin-right: 20px;
|
||||
}
|
||||
// .vrc-instance-queue-message .el-message__content {
|
||||
// margin-right: 20px;
|
||||
// }
|
||||
|
||||
.el-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
// .el-tabs {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// }
|
||||
|
||||
.el-tabs__content {
|
||||
flex: 1;
|
||||
max-height: 100%;
|
||||
// flex: 1;
|
||||
// max-height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -889,60 +906,56 @@ i.x-status-icon.red {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.el-tabs__header {
|
||||
padding: 0 1px;
|
||||
}
|
||||
// .el-tabs__header {
|
||||
// padding: 0 1px;
|
||||
// }
|
||||
|
||||
.zero-margin-tabs .el-tabs__header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
// .zero-margin-tabs .el-tabs__header {
|
||||
// margin-bottom: 0;
|
||||
// }
|
||||
|
||||
.x-friend-item .el-checkbox__inner,
|
||||
.el-table__row .el-checkbox__inner {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
// .x-friend-item .el-checkbox__inner,
|
||||
// .el-table__row .el-checkbox__inner {
|
||||
// width: 28px;
|
||||
// height: 28px;
|
||||
// border-radius: 4px;
|
||||
// }
|
||||
|
||||
.x-friend-item .el-checkbox__inner::after,
|
||||
.el-table__row .el-checkbox__inner::after {
|
||||
width: 8px;
|
||||
height: 14px;
|
||||
left: 8px;
|
||||
top: 2px;
|
||||
}
|
||||
// .x-friend-item .el-checkbox__inner::after,
|
||||
// .el-table__row .el-checkbox__inner::after {
|
||||
// width: 8px;
|
||||
// height: 14px;
|
||||
// left: 8px;
|
||||
// top: 2px;
|
||||
// }
|
||||
|
||||
.max-height-el-select .el-select-dropdown__wrap {
|
||||
max-height: 83vh;
|
||||
}
|
||||
// .max-height-el-select .el-select-dropdown__wrap {
|
||||
// max-height: 83vh;
|
||||
// }
|
||||
|
||||
.el-pagination .el-input .el-input__icon {
|
||||
line-height: 22px;
|
||||
}
|
||||
// .el-pagination .el-input .el-input__icon {
|
||||
// line-height: 22px;
|
||||
// }
|
||||
|
||||
// User dialog memo: tag line-height
|
||||
.el-dialog__body .el-tag--mini {
|
||||
line-height: 17px;
|
||||
}
|
||||
// .el-dialog__body .el-tag--mini {
|
||||
// line-height: 17px;
|
||||
// }
|
||||
|
||||
// feed table detail time tag line-height
|
||||
.el-table__expanded-cell .el-tag--mini {
|
||||
line-height: 18px;
|
||||
}
|
||||
// .el-table__expanded-cell .el-tag--mini {
|
||||
// line-height: 18px;
|
||||
// }
|
||||
|
||||
// User dialog memo: input count background color
|
||||
.x-friend-item:hover .el-input__count {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
// .x-friend-item:hover .el-input__count {
|
||||
// background: #f0f0f0;
|
||||
// }
|
||||
|
||||
// Align the left page with the right friend bar
|
||||
.x-app > .x-container {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
line-height: 17px;
|
||||
}
|
||||
// .el-collapse-item .el-tag--mini {
|
||||
// line-height: 17px;
|
||||
// }
|
||||
|
||||
.x-text-removed {
|
||||
text-decoration: line-through;
|
||||
@@ -959,21 +972,38 @@ i.x-status-icon.red {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.el-carousel__mask {
|
||||
// looks bad
|
||||
display: none;
|
||||
// expand table cell on hover, TODO: replace with a better solution
|
||||
.el-table .cell {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
|
||||
.el-table__row:hover .el-table__cell .cell {
|
||||
display: revert;
|
||||
-webkit-line-clamp: unset;
|
||||
}
|
||||
|
||||
// .el-carousel__mask {
|
||||
// // looks bad
|
||||
// display: none;
|
||||
// }
|
||||
|
||||
// FIXME
|
||||
// Something changed the CSS of element-ui
|
||||
// The other parts are the same
|
||||
// It feels like can't fundamentally modify it
|
||||
// And can only fix it bit by bit by overriding
|
||||
.el-switch__core:after {
|
||||
top: 1.5px;
|
||||
}
|
||||
// .el-switch__core:after {
|
||||
// top: 1.5px;
|
||||
// }
|
||||
|
||||
.el-popover {
|
||||
text-align: left;
|
||||
word-break: break-word;
|
||||
// .el-popover {
|
||||
// text-align: left;
|
||||
// word-break: break-word;
|
||||
// }
|
||||
|
||||
.el-dropdown-menu__item:not(.is-disabled):focus {
|
||||
// dropdown item focus blue background looks bad
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
@@ -3,7 +3,7 @@
|
||||
--offy: calc(var(--offx) / 72 * 52);
|
||||
}
|
||||
.flags {
|
||||
background: url('/images/flags.png') no-repeat;
|
||||
background: url('../images/flags.png') no-repeat;
|
||||
background-size: calc(var(--offx) * 6);
|
||||
width: var(--offx);
|
||||
height: var(--offy);
|
||||
|
||||
@@ -145,7 +145,7 @@ button {
|
||||
div.x-friend-list
|
||||
> div:nth-child(1)
|
||||
> div
|
||||
> div.el-textarea.el-input--mini
|
||||
> div.el-textarea.el-input--small
|
||||
> span.el-input__count {
|
||||
background-color: $--theme-bg-4;
|
||||
}
|
||||
@@ -168,7 +168,7 @@ div.x-friend-list
|
||||
background-color: $--theme-bg-5;
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
.el-collapse-item .el-tag--small {
|
||||
background-color: $--theme-bg-5;
|
||||
border: transparent;
|
||||
}
|
||||
@@ -220,11 +220,11 @@ div.x-friend-list
|
||||
background-color: hsl($--theme-hue, $--theme-saturation, 27%);
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::after {
|
||||
border-bottom-color: $--theme-bg-5;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
|
||||
border-bottom-color: $--theme-border-2;
|
||||
}
|
||||
|
||||
@@ -303,11 +303,11 @@ div.x-friend-list
|
||||
border-color: hsl($--theme-hue, $--theme-saturation, 37%);
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow::after {
|
||||
border-right-color: hsl($--theme-hue, $--theme-saturation, 37%);
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow {
|
||||
border-right-color: hsl($--theme-hue, $--theme-saturation, 37%);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,104 @@ $--theme-warning: #e6a23c;
|
||||
$--theme-danger: #f56c6c;
|
||||
$--theme-info: #909399;
|
||||
|
||||
// Mapping
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
|
||||
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||
$colors: (
|
||||
'white': $--theme-text-1,
|
||||
'black': $--theme-bg-1,
|
||||
'primary': (
|
||||
'base': $--theme-primary
|
||||
),
|
||||
'success': (
|
||||
'base': $--theme-success
|
||||
),
|
||||
'warning': (
|
||||
'base': $--theme-warning
|
||||
),
|
||||
'danger': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'error': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'info': (
|
||||
'base': $--theme-info
|
||||
)
|
||||
),
|
||||
$text-color: (
|
||||
'primary': $--theme-text-1,
|
||||
'regular': $--theme-text-2,
|
||||
'secondary': $--theme-info,
|
||||
'placeholder': $--theme-text-4,
|
||||
'disabled': $--theme-text-4
|
||||
),
|
||||
$border-color: (
|
||||
'': $--theme-border-1,
|
||||
'light': $--theme-border-2,
|
||||
'lighter': $--border-color-lighter,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-border-1,
|
||||
'darker': $--theme-border-2
|
||||
),
|
||||
$fill-color: (
|
||||
'': $--background-color-base,
|
||||
'light': $--theme-bg-4,
|
||||
'lighter': $--theme-bg-5,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-bg-2,
|
||||
'darker': $--theme-bg-1,
|
||||
'blank': $--theme-bg-4
|
||||
),
|
||||
$bg-color: (
|
||||
'': $--theme-bg-2,
|
||||
'page': $--theme-bg-1,
|
||||
'overlay': $--theme-bg-4
|
||||
),
|
||||
$box-shadow: (
|
||||
'': (
|
||||
0px 12px 32px 4px rgba(0, 0, 0, 0.36),
|
||||
0px 8px 20px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'light': (
|
||||
0px 0px 12px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'lighter': (
|
||||
0px 0px 6px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'dark': (
|
||||
0px 16px 48px 16px rgba(0, 0, 0, 0.72),
|
||||
0px 12px 32px rgba(0, 0, 0, 0.72),
|
||||
0px 8px 16px -8px rgba(0, 0, 0, 0.96)
|
||||
)
|
||||
),
|
||||
$disabled: (
|
||||
'bg-color': $--theme-bg-5,
|
||||
'text-color': $--theme-text-4,
|
||||
'border-color': $--theme-border-2
|
||||
)
|
||||
);
|
||||
|
||||
$--color-primary: $--theme-primary;
|
||||
$--color-success: $--theme-success;
|
||||
@@ -52,10 +149,6 @@ $--color-text-placeholder: $--theme-text-4;
|
||||
|
||||
$--border-color-base: $--theme-border-1;
|
||||
$--border-color-light: $--theme-bg-5;
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
|
||||
$--button-default-background-color: $--theme-bg-5;
|
||||
$--button-default-border-color: $--theme-border-2;
|
||||
@@ -64,20 +157,13 @@ $--button-default-font-color: $--theme-text-2;
|
||||
$--input-background-color: $--theme-bg-5;
|
||||
$--input-border-color: $--theme-border-1;
|
||||
$--input-font-color: $--theme-text-1;
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-input-focus-border-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--select-input-focus-border-color: $--input-focus-border;
|
||||
|
||||
$--dialog-background-color: $--theme-bg-4;
|
||||
$--popover-background-color: $--theme-bg-4;
|
||||
|
||||
$--table-border-color: $--theme-border-2;
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
|
||||
$--pagination-font-color: $--theme-text-2;
|
||||
$--pagination-background-color: $--theme-bg-4;
|
||||
@@ -95,21 +181,11 @@ $--datepicker-header-font-color: $--theme-text-1;
|
||||
$--datepicker-icon-color: $--theme-text-1;
|
||||
|
||||
$--skeleton-color: $--theme-bg-5;
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-dropdown-background: $--theme-bg-4;
|
||||
$--select-dropdown-border: 1px solid $--theme-border-1;
|
||||
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
|
||||
$--tree-font-color: $--theme-text-2;
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
|
||||
$--menu-item-font-color: $--theme-text-2;
|
||||
$--menu-background-color: $--theme-bg-2;
|
||||
@@ -118,10 +194,8 @@ $--collapse-header-background-color: $--theme-bg-5;
|
||||
$--collapse-content-background-color: $--theme-bg-4;
|
||||
$--collapse-border-color: $--theme-border-2;
|
||||
$--collapse-header-font-color: $--theme-text-2;
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
|
||||
$--message-background-color: $--theme-bg-5;
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--message-close-hover-color: $--theme-text-2;
|
||||
$--message-success-font-color: #52c41a;
|
||||
$--message-info-font-color: #1890ff;
|
||||
@@ -130,13 +204,8 @@ $--message-danger-font-color: #ff4d4f;
|
||||
|
||||
$--pagination-hover-color: $--theme-text-4;
|
||||
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
$--card-background-color: $--theme-bg-4;
|
||||
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
|
||||
@import '~element-ui/packages/theme-chalk/src/index';
|
||||
|
||||
:root {
|
||||
--group-calendar-event-bg: #{$--calendar-selected-background-color};
|
||||
--group-calendar-badge-following: #{darken($--theme-primary, 20%)};
|
||||
@@ -218,35 +287,35 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
background-color: #{darken($--theme-primary, 20%)} !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow::after {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::after {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow::after {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow::after {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,105 @@ $--theme-warning: #e6a23c;
|
||||
$--theme-danger: #f56c6c;
|
||||
$--theme-info: #909399;
|
||||
|
||||
// Mapping
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px
|
||||
hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
|
||||
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||
$colors: (
|
||||
'white': $--theme-text-1,
|
||||
'black': $--theme-bg-1,
|
||||
'primary': (
|
||||
'base': $--theme-primary
|
||||
),
|
||||
'success': (
|
||||
'base': $--theme-success
|
||||
),
|
||||
'warning': (
|
||||
'base': $--theme-warning
|
||||
),
|
||||
'danger': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'error': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'info': (
|
||||
'base': $--theme-info
|
||||
)
|
||||
),
|
||||
$text-color: (
|
||||
'primary': $--theme-text-1,
|
||||
'regular': $--theme-text-2,
|
||||
'secondary': $--theme-info,
|
||||
'placeholder': $--theme-text-4,
|
||||
'disabled': $--theme-text-4
|
||||
),
|
||||
$border-color: (
|
||||
'': $--theme-border-1,
|
||||
'light': $--theme-border-2,
|
||||
'lighter': $--border-color-lighter,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-border-1,
|
||||
'darker': $--theme-border-2
|
||||
),
|
||||
$fill-color: (
|
||||
'': $--background-color-base,
|
||||
'light': $--theme-bg-4,
|
||||
'lighter': $--theme-bg-5,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-bg-2,
|
||||
'darker': $--theme-bg-1,
|
||||
'blank': $--theme-bg-4
|
||||
),
|
||||
$bg-color: (
|
||||
'': $--theme-bg-2,
|
||||
'page': $--theme-bg-1,
|
||||
'overlay': $--theme-bg-4
|
||||
),
|
||||
$box-shadow: (
|
||||
'': (
|
||||
0px 12px 32px 4px rgba(0, 0, 0, 0.36),
|
||||
0px 8px 20px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'light': (
|
||||
0px 0px 12px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'lighter': (
|
||||
0px 0px 6px rgba(0, 0, 0, 0.72)
|
||||
),
|
||||
'dark': (
|
||||
0px 16px 48px 16px rgba(0, 0, 0, 0.72),
|
||||
0px 12px 32px rgba(0, 0, 0, 0.72),
|
||||
0px 8px 16px -8px rgba(0, 0, 0, 0.96)
|
||||
)
|
||||
),
|
||||
$disabled: (
|
||||
'bg-color': $--theme-bg-5,
|
||||
'text-color': $--theme-text-4,
|
||||
'border-color': $--theme-border-2
|
||||
)
|
||||
);
|
||||
|
||||
$--color-primary: $--theme-primary;
|
||||
$--color-success: $--theme-success;
|
||||
@@ -50,10 +148,6 @@ $--color-text-placeholder: $--theme-text-4;
|
||||
|
||||
$--border-color-base: $--theme-border-1;
|
||||
$--border-color-light: $--theme-bg-5;
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
|
||||
$--button-default-background-color: $--theme-bg-5;
|
||||
$--button-default-border-color: $--theme-border-2;
|
||||
@@ -62,20 +156,13 @@ $--button-default-font-color: $--theme-text-2;
|
||||
$--input-background-color: $--theme-bg-5;
|
||||
$--input-border-color: $--theme-border-1;
|
||||
$--input-font-color: $--theme-text-1;
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-input-focus-border-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--select-input-focus-border-color: $--input-focus-border;
|
||||
|
||||
$--dialog-background-color: $--theme-bg-4;
|
||||
$--popover-background-color: $--theme-bg-4;
|
||||
|
||||
$--table-border-color: $--theme-border-2;
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
|
||||
$--pagination-font-color: $--theme-text-2;
|
||||
$--pagination-background-color: $--theme-bg-4;
|
||||
@@ -93,22 +180,11 @@ $--datepicker-header-font-color: $--theme-text-1;
|
||||
$--datepicker-icon-color: $--theme-text-1;
|
||||
|
||||
$--skeleton-color: $--theme-bg-5;
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-dropdown-background: $--theme-bg-4;
|
||||
$--select-dropdown-border: 1px solid $--theme-border-1;
|
||||
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px
|
||||
hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
|
||||
$--tree-font-color: $--theme-text-2;
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
|
||||
$--menu-item-font-color: $--theme-text-2;
|
||||
$--menu-background-color: $--theme-bg-2;
|
||||
@@ -117,10 +193,8 @@ $--collapse-header-background-color: $--theme-bg-5;
|
||||
$--collapse-content-background-color: $--theme-bg-4;
|
||||
$--collapse-border-color: $--theme-border-2;
|
||||
$--collapse-header-font-color: $--theme-text-2;
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
|
||||
$--message-background-color: $--theme-bg-5;
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--message-close-hover-color: $--theme-text-2;
|
||||
$--message-success-font-color: #52c41a;
|
||||
$--message-info-font-color: #1890ff;
|
||||
@@ -129,13 +203,8 @@ $--message-danger-font-color: #ff4d4f;
|
||||
|
||||
$--pagination-hover-color: $--theme-text-4;
|
||||
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
$--card-background-color: $--theme-bg-4;
|
||||
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
|
||||
@import '~element-ui/packages/theme-chalk/src/index';
|
||||
|
||||
:root {
|
||||
--group-calendar-event-bg: #{$--calendar-selected-background-color};
|
||||
--group-calendar-badge-following: #{$--color-success};
|
||||
@@ -170,35 +239,35 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
background-color: $--theme-border-2 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow::after {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::after {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow::after {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow::after {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,105 @@ $--theme-warning: #e6a23c;
|
||||
$--theme-danger: #f56c6c;
|
||||
$--theme-info: #909399;
|
||||
|
||||
// Mapping
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px
|
||||
hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
|
||||
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||
$colors: (
|
||||
'white': $--theme-text-1,
|
||||
'black': $--theme-bg-1,
|
||||
'primary': (
|
||||
'base': $--theme-primary
|
||||
),
|
||||
'success': (
|
||||
'base': $--theme-success
|
||||
),
|
||||
'warning': (
|
||||
'base': $--theme-warning
|
||||
),
|
||||
'danger': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'error': (
|
||||
'base': $--theme-danger
|
||||
),
|
||||
'info': (
|
||||
'base': $--theme-info
|
||||
)
|
||||
),
|
||||
$text-color: (
|
||||
'primary': $--theme-text-1,
|
||||
'regular': $--theme-text-2,
|
||||
'secondary': $--theme-info,
|
||||
'placeholder': $--theme-text-4,
|
||||
'disabled': $--theme-text-4
|
||||
),
|
||||
$border-color: (
|
||||
'': $--theme-border-1,
|
||||
'light': $--theme-border-2,
|
||||
'lighter': $--border-color-lighter,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-border-1,
|
||||
'darker': $--theme-border-2
|
||||
),
|
||||
$fill-color: (
|
||||
'': $--background-color-base,
|
||||
'light': $--theme-bg-4,
|
||||
'lighter': $--theme-bg-5,
|
||||
'extra-light': $--border-color-extra-light,
|
||||
'dark': $--theme-bg-2,
|
||||
'darker': $--theme-bg-1,
|
||||
'blank': $--theme-bg-4
|
||||
),
|
||||
$bg-color: (
|
||||
'': $--theme-bg-2,
|
||||
'page': $--theme-bg-1,
|
||||
'overlay': $--theme-bg-4
|
||||
),
|
||||
$box-shadow: (
|
||||
'': (
|
||||
0px 12px 32px 4px hsla($--theme-hue, $--theme-saturation, 0%, 0.36),
|
||||
0px 8px 20px hsla($--theme-hue, $--theme-saturation, 0%, 0.72)
|
||||
),
|
||||
'light': (
|
||||
0px 0px 12px hsla($--theme-hue, $--theme-saturation, 0%, 0.72)
|
||||
),
|
||||
'lighter': (
|
||||
0px 0px 6px hsla($--theme-hue, $--theme-saturation, 0%, 0.72)
|
||||
),
|
||||
'dark': (
|
||||
0px 16px 48px 16px hsla($--theme-hue, $--theme-saturation, 0%, 0.72),
|
||||
0px 12px 32px hsla($--theme-hue, $--theme-saturation, 0%, 0.72),
|
||||
0px 8px 16px -8px hsla($--theme-hue, $--theme-saturation, 0%, 0.96)
|
||||
)
|
||||
),
|
||||
$disabled: (
|
||||
'bg-color': $--theme-bg-5,
|
||||
'text-color': $--theme-text-4,
|
||||
'border-color': $--theme-border-2
|
||||
)
|
||||
);
|
||||
|
||||
$--color-primary: $--theme-primary;
|
||||
$--color-success: $--theme-success;
|
||||
@@ -50,10 +148,6 @@ $--color-text-placeholder: $--theme-text-4;
|
||||
|
||||
$--border-color-base: $--theme-border-1;
|
||||
$--border-color-light: $--theme-bg-5;
|
||||
$--border-color-lighter: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
$--border-color-extra-light: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
|
||||
$--background-color-base: hsl($--theme-hue, $--theme-saturation, 16%);
|
||||
|
||||
$--button-default-background-color: $--theme-bg-5;
|
||||
$--button-default-border-color: $--theme-border-2;
|
||||
@@ -62,20 +156,13 @@ $--button-default-font-color: $--theme-text-2;
|
||||
$--input-background-color: $--theme-bg-5;
|
||||
$--input-border-color: $--theme-border-1;
|
||||
$--input-font-color: $--theme-text-1;
|
||||
$--input-focus-border: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-input-focus-border-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
$--select-input-focus-border-color: $--input-focus-border;
|
||||
|
||||
$--dialog-background-color: $--theme-bg-4;
|
||||
$--popover-background-color: $--theme-bg-4;
|
||||
|
||||
$--table-border-color: $--theme-border-2;
|
||||
$--table-header-background-color: hsl($--theme-hue, $--theme-saturation, 15%);
|
||||
$--table-row-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
18%
|
||||
);
|
||||
|
||||
$--pagination-font-color: $--theme-text-2;
|
||||
$--pagination-background-color: $--theme-bg-4;
|
||||
@@ -93,22 +180,11 @@ $--datepicker-header-font-color: $--theme-text-1;
|
||||
$--datepicker-icon-color: $--theme-text-1;
|
||||
|
||||
$--skeleton-color: $--theme-bg-5;
|
||||
$--skeleton-to-color: hsl($--theme-hue, $--theme-saturation, 33%);
|
||||
|
||||
$--select-dropdown-background: $--theme-bg-4;
|
||||
$--select-dropdown-border: 1px solid $--theme-border-1;
|
||||
|
||||
$--dropdown-menu-box-shadow: 0 2px 12px
|
||||
hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
$--box-shadow-base: 0 1px 2px hsla($--theme-hue, $--theme-saturation, 0%, 0.1);
|
||||
$--box-shadow-dark: 0 1px 3px hsla($--theme-hue, $--theme-saturation, 0%, 0.15);
|
||||
|
||||
$--tree-font-color: $--theme-text-2;
|
||||
$--tree-node-hover-background-color: hsl(
|
||||
$--theme-hue,
|
||||
$--theme-saturation,
|
||||
16%
|
||||
);
|
||||
|
||||
$--menu-item-font-color: $--theme-text-2;
|
||||
$--menu-background-color: $--theme-bg-2;
|
||||
@@ -117,10 +193,8 @@ $--collapse-header-background-color: $--theme-bg-5;
|
||||
$--collapse-content-background-color: $--theme-bg-4;
|
||||
$--collapse-border-color: $--theme-border-2;
|
||||
$--collapse-header-font-color: $--theme-text-2;
|
||||
$--collapse-content-font-color: hsl($--theme-hue, $--theme-saturation, 66%);
|
||||
|
||||
$--message-background-color: $--theme-bg-5;
|
||||
$--message-close-icon-color: hsl($--theme-hue, $--theme-saturation, 60%);
|
||||
$--message-close-hover-color: $--theme-text-2;
|
||||
$--message-success-font-color: #52c41a;
|
||||
$--message-info-font-color: #1890ff;
|
||||
@@ -129,13 +203,8 @@ $--message-danger-font-color: #ff4d4f;
|
||||
|
||||
$--pagination-hover-color: $--theme-text-4;
|
||||
|
||||
$--calendar-selected-background-color: rgba($--theme-primary, 0.1);
|
||||
$--card-background-color: $--theme-bg-4;
|
||||
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
|
||||
@import '~element-ui/packages/theme-chalk/src/index';
|
||||
|
||||
:root {
|
||||
--group-calendar-event-bg: #{$--calendar-selected-background-color};
|
||||
--group-calendar-badge-following: #{$--color-success};
|
||||
@@ -170,35 +239,35 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
background-color: $--theme-border-2 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow::after {
|
||||
border-top-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::after {
|
||||
border-bottom-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow::after {
|
||||
border-left-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow::after {
|
||||
border-right-color: $--theme-bg-4 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -598,7 +598,7 @@ img.friends-list-avatar {
|
||||
border-radius: var(--dv_lg-rounded) !important;
|
||||
}
|
||||
|
||||
.el-tag--mini {
|
||||
.el-tag--small {
|
||||
height: 30px;
|
||||
padding: 5px 15px;
|
||||
font-size: 10pt;
|
||||
@@ -627,7 +627,7 @@ img.friends-list-avatar {
|
||||
background-color: var(--dv_muted);
|
||||
}
|
||||
|
||||
.el-input--mini .el-textarea__inner:hover {
|
||||
.el-input--small .el-textarea__inner:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
@@ -730,7 +730,7 @@ i[class='el-icon-star-off']:not(.el-menu-item div.el-tooltip i) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
.el-collapse-item .el-tag--small {
|
||||
border: transparent;
|
||||
background-color: var(--dv_bg-bot);
|
||||
}
|
||||
@@ -786,7 +786,7 @@ i[class='el-icon-star-off']:not(.el-menu-item div.el-tooltip i) {
|
||||
div.x-friend-list
|
||||
> div:nth-child(1)
|
||||
> div
|
||||
> div.el-textarea.el-input--mini
|
||||
> div.el-textarea.el-input--small
|
||||
> span.el-input__count {
|
||||
background-color: var(--mid) !important;
|
||||
}
|
||||
@@ -831,3 +831,27 @@ div.x-friend-list
|
||||
.group-header {
|
||||
border-bottom: 2px solid var(--dv_muted) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper {
|
||||
background-color: var(--dv_bg-top) !important;
|
||||
border: 1px solid var(--dv_muted) !important;
|
||||
color: var(--dv_bright);
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-focused {
|
||||
border-color: var(--dv_bright) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-disabled {
|
||||
background-color: var(--dv_bg-bot) !important;
|
||||
color: var(--dv_muted) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: var(--dv_bg-top) !important;
|
||||
border: 1px solid var(--dv_muted) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper.is-focus {
|
||||
border-color: var(--dv_bright) !important;
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ path[stroke='#e5e9f2'] {
|
||||
stroke: var(--farback) !important;
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
.el-collapse-item .el-tag--small {
|
||||
border: transparent;
|
||||
background-color: #333 !important;
|
||||
}
|
||||
@@ -372,7 +372,7 @@ path[stroke='#e5e9f2'] {
|
||||
div.x-friend-list
|
||||
> div:nth-child(1)
|
||||
> div
|
||||
> div.el-textarea.el-input--mini
|
||||
> div.el-textarea.el-input--small
|
||||
> span.el-input__count {
|
||||
background-color: var(--mid) !important;
|
||||
}
|
||||
@@ -402,3 +402,27 @@ div.x-friend-list
|
||||
.el-timeline-item__node {
|
||||
background-color: var(--theme-text-muted) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper {
|
||||
background-color: var(--top) !important;
|
||||
border: 1px solid var(--top-border) !important;
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-focused {
|
||||
border-color: var(--theme-text) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-disabled {
|
||||
background-color: var(--farback) !important;
|
||||
color: var(--theme-text-muted) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: var(--top) !important;
|
||||
border: 1px solid var(--top-border) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper.is-focus {
|
||||
border-color: var(--theme-text) !important;
|
||||
}
|
||||
|
||||
@@ -1651,35 +1651,35 @@ img.x-link.el-popover__reference {
|
||||
border-color: rgb(var(--md-sys-color-outline-variant)) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow {
|
||||
border-top-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='top'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='top'] .el-popper__arrow::after {
|
||||
border-top-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow {
|
||||
border-bottom-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='bottom'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='bottom'] .el-popper__arrow::after {
|
||||
border-bottom-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow {
|
||||
border-left-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='left'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='left'] .el-popper__arrow::after {
|
||||
border-left-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow {
|
||||
border-right-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
.el-popper[x-placement^='right'] .popper__arrow::after {
|
||||
.el-popper[data-popper-placement^='right'] .el-popper__arrow::after {
|
||||
border-right-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
|
||||
@@ -2065,7 +2065,7 @@ i.x-user-status {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
.el-collapse-item .el-tag--small {
|
||||
background-color: transparent;
|
||||
border: transparent;
|
||||
padding-top: 6px;
|
||||
@@ -2074,7 +2074,7 @@ i.x-user-status {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.el-dialog__body .el-tag--mini {
|
||||
.el-dialog__body .el-tag--small {
|
||||
line-height: 28px;
|
||||
}
|
||||
.el-divider {
|
||||
@@ -2149,7 +2149,7 @@ i.x-user-status {
|
||||
div.x-friend-list
|
||||
> div:nth-child(1)
|
||||
> div
|
||||
> div.el-textarea.el-input--mini
|
||||
> div.el-textarea.el-input--small
|
||||
> span.el-input__count {
|
||||
background-color: var(--md-sys-color-surface-3) !important;
|
||||
}
|
||||
@@ -2192,3 +2192,44 @@ div.x-friend-list
|
||||
.group-header {
|
||||
border-bottom: 2px solid rgba(var(--md-sys-color-outline), 0.5) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper {
|
||||
background-color: rgb(var(--md-sys-color-surface-variant)) !important;
|
||||
border: 1px solid rgb(var(--md-sys-color-outline-variant)) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-focused {
|
||||
border-color: rgb(var(--md-sys-color-primary)) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-disabled {
|
||||
background-color: rgba(var(--md-sys-color-on-surface), 0.12) !important;
|
||||
color: rgba(var(--md-sys-color-on-surface), 0.38) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: rgb(var(--md-sys-color-surface-variant)) !important;
|
||||
border: 1px solid rgb(var(--md-sys-color-outline-variant)) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper.is-focus {
|
||||
border-color: rgb(var(--md-sys-color-primary)) !important;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
background-color: rgb(var(--md-sys-color-surface)) !important;
|
||||
}
|
||||
|
||||
.el-table tr,
|
||||
.el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell {
|
||||
background-color: rgba(var(--md-sys-color-primary), 0.05) !important;
|
||||
}
|
||||
|
||||
.el-table__body-wrapper .el-table__row:hover > .el-table__cell {
|
||||
background-color: rgba(var(--md-sys-color-primary), 0.08) !important;
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ input[type='checkbox']:checked + .el-switch__core {
|
||||
background-color: var(--lighter-lighter-bg);
|
||||
}
|
||||
|
||||
.el-collapse-item .el-tag--mini {
|
||||
.el-collapse-item .el-tag--small {
|
||||
border: transparent;
|
||||
background-color: var(--lighter-lighter-bg);
|
||||
}
|
||||
@@ -506,3 +506,45 @@ input[type='checkbox']:checked + .el-switch__core {
|
||||
.group-header {
|
||||
border-bottom: 2px solid var(--lighter-bg) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper {
|
||||
background-color: var(--light-bg) !important;
|
||||
border: 1px solid var(--lighter-bg) !important;
|
||||
color: var(--theme);
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-focused {
|
||||
border-color: var(--theme) !important;
|
||||
}
|
||||
|
||||
.el-select__wrapper.is-disabled {
|
||||
background-color: var(--bg) !important;
|
||||
color: var(--lighter-border) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: var(--light-bg) !important;
|
||||
border: 1px solid var(--lighter-bg) !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper.is-focus {
|
||||
border-color: var(--theme) !important;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
background-color: var(--bg) !important;
|
||||
}
|
||||
|
||||
.el-table tr,
|
||||
.el-table td.el-table__cell,
|
||||
.el-table th.el-table__cell {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell {
|
||||
background-color: var(--light-bg) !important;
|
||||
}
|
||||
|
||||
.el-table__body-wrapper .el-table__row:hover > .el-table__cell {
|
||||
background-color: var(--lighter-bg) !important;
|
||||
}
|
||||
|
||||
44
src/bootstrap.js
vendored
44
src/bootstrap.js
vendored
@@ -1,16 +1,6 @@
|
||||
import '@fontsource/noto-sans-kr';
|
||||
import '@fontsource/noto-sans-jp';
|
||||
import '@fontsource/noto-sans-sc';
|
||||
import '@fontsource/noto-sans-tc';
|
||||
|
||||
import 'remixicon/fonts/remixicon.css';
|
||||
|
||||
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,
|
||||
@@ -18,8 +8,10 @@ import {
|
||||
refreshCustomScript,
|
||||
systemIsDarkMode
|
||||
} from './shared/utils';
|
||||
import { i18n } from './plugin';
|
||||
import { i18n } from './plugin/i18n';
|
||||
import { initNoty } from './plugin/noty';
|
||||
import './plugin/ipc';
|
||||
import './plugin/dayjs';
|
||||
|
||||
initNoty(false);
|
||||
|
||||
@@ -42,7 +34,7 @@ function setLoginContainerStyle(isDarkMode) {
|
||||
background-color: ${backgroundColor} !important;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
.x-login-container .el-input__inner {
|
||||
background-color: ${inputBackgroundColor} !important;
|
||||
border: ${inputBorder} !important;
|
||||
@@ -88,33 +80,7 @@ try {
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
244
src/components/DataTable.vue
Normal file
244
src/components/DataTable.vue
Normal file
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<div class="data-table-wrapper">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="paginatedData"
|
||||
v-bind="mergedTableProps"
|
||||
default-sort-prop="created_at"
|
||||
default-sort-order="descending"
|
||||
lazy
|
||||
@sort-change="handleSortChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-click="handleRowClick">
|
||||
<slot></slot>
|
||||
</el-table>
|
||||
|
||||
<div v-if="showPagination" class="pagination-wrapper">
|
||||
<el-pagination
|
||||
:current-page="internalCurrentPage"
|
||||
:page-size="internalPageSize"
|
||||
:total="filteredData.length"
|
||||
v-bind="mergedPaginationProps"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, ref, watch, toRefs } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'DataTable',
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
tableProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
paginationProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
filters: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'table, pagination'
|
||||
},
|
||||
defaultSort: {
|
||||
type: Object,
|
||||
default: () => ({ prop: 'created_at', order: 'descending' })
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'update:currentPage',
|
||||
'update:pageSize',
|
||||
'size-change',
|
||||
'current-change',
|
||||
'selection-change',
|
||||
'row-click',
|
||||
'filtered-data'
|
||||
],
|
||||
setup(props, { emit }) {
|
||||
const { data, currentPage, pageSize, tableProps, paginationProps, filters } = toRefs(props);
|
||||
|
||||
const internalCurrentPage = ref(currentPage.value);
|
||||
const internalPageSize = ref(pageSize.value);
|
||||
const sortData = ref({
|
||||
prop: props.defaultSort?.prop || null,
|
||||
order: props.defaultSort?.order || null
|
||||
});
|
||||
|
||||
const showPagination = computed(() => {
|
||||
return props.layout.includes('pagination');
|
||||
});
|
||||
|
||||
const mergedTableProps = computed(() => ({
|
||||
stripe: true,
|
||||
...tableProps.value
|
||||
}));
|
||||
|
||||
const mergedPaginationProps = computed(() => ({
|
||||
layout: 'sizes, prev, pager, next, total',
|
||||
pageSizes: [20, 50, 100, 200],
|
||||
small: true,
|
||||
...paginationProps.value
|
||||
}));
|
||||
|
||||
const applyFilter = function (row, filter) {
|
||||
if (Array.isArray(filter.prop)) {
|
||||
return filter.prop.some((propItem) => applyFilter(row, { prop: propItem, value: filter.value }));
|
||||
}
|
||||
|
||||
const cellValue = row[filter.prop];
|
||||
if (cellValue === undefined || cellValue === null) return false;
|
||||
|
||||
if (Array.isArray(filter.value)) {
|
||||
return filter.value.some((val) =>
|
||||
String(cellValue).toLowerCase().includes(String(val).toLowerCase())
|
||||
);
|
||||
} else {
|
||||
return String(cellValue).toLowerCase().includes(String(filter.value).toLowerCase());
|
||||
}
|
||||
};
|
||||
|
||||
const filteredData = computed(() => {
|
||||
let result = [...data.value];
|
||||
|
||||
if (filters.value && Array.isArray(filters.value) && filters.value.length > 0) {
|
||||
filters.value.forEach((filter) => {
|
||||
if (filter.value && (!Array.isArray(filter.value) || filter.value.length > 0)) {
|
||||
result = result.filter((row) => applyFilter(row, filter));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (sortData.value.prop && sortData.value.order) {
|
||||
const { prop, order } = sortData.value;
|
||||
result.sort((a, b) => {
|
||||
const aVal = a[prop];
|
||||
const bVal = b[prop];
|
||||
let comparison = 0;
|
||||
|
||||
if (aVal == null && bVal == null) return 0;
|
||||
if (aVal == null) return 1;
|
||||
if (bVal == null) return -1;
|
||||
|
||||
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
||||
comparison = aVal - bVal;
|
||||
} else if (aVal instanceof Date && bVal instanceof Date) {
|
||||
comparison = aVal.getTime() - bVal.getTime();
|
||||
} else {
|
||||
const aStr = String(aVal).toLowerCase();
|
||||
const bStr = String(bVal).toLowerCase();
|
||||
if (aStr > bStr) comparison = 1;
|
||||
else if (aStr < bStr) comparison = -1;
|
||||
}
|
||||
|
||||
return order === 'descending' ? -comparison : comparison;
|
||||
});
|
||||
}
|
||||
|
||||
emit('filtered-data', result);
|
||||
return result;
|
||||
});
|
||||
|
||||
const paginatedData = computed(() => {
|
||||
if (!showPagination.value) {
|
||||
return filteredData.value;
|
||||
}
|
||||
|
||||
const start = (internalCurrentPage.value - 1) * internalPageSize.value;
|
||||
const end = start + internalPageSize.value;
|
||||
return filteredData.value.slice(start, end);
|
||||
});
|
||||
|
||||
const handleSortChange = ({ prop, order }) => {
|
||||
sortData.value = { prop, order };
|
||||
};
|
||||
|
||||
const handleSelectionChange = (selection) => {
|
||||
emit('selection-change', selection);
|
||||
};
|
||||
|
||||
const handleRowClick = (row, column, event) => {
|
||||
emit('row-click', row, column, event);
|
||||
};
|
||||
|
||||
const handleSizeChange = (size) => {
|
||||
internalPageSize.value = size;
|
||||
};
|
||||
|
||||
const handleCurrentChange = (page) => {
|
||||
internalCurrentPage.value = page;
|
||||
};
|
||||
|
||||
watch(currentPage, (newVal) => {
|
||||
internalCurrentPage.value = newVal;
|
||||
});
|
||||
|
||||
watch(pageSize, (newVal) => {
|
||||
internalPageSize.value = newVal;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.defaultSort,
|
||||
(newSort) => {
|
||||
if (newSort) {
|
||||
sortData.value = {
|
||||
prop: newSort.prop,
|
||||
order: newSort.order
|
||||
};
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return {
|
||||
internalCurrentPage,
|
||||
internalPageSize,
|
||||
showPagination,
|
||||
mergedTableProps,
|
||||
mergedPaginationProps,
|
||||
filteredData,
|
||||
paginatedData,
|
||||
handleSortChange,
|
||||
handleSelectionChange,
|
||||
handleRowClick,
|
||||
handleSizeChange,
|
||||
handleCurrentChange
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.data-table-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@
|
||||
<div
|
||||
class="avatar"
|
||||
:class="isFriendActiveOrOffline ? undefined : userStatusClass(friend.ref, friend.pendingOffline)">
|
||||
<img v-lazy="userImage(friend.ref, true)" />
|
||||
<img :src="userImage(friend.ref, true)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span v-if="!hideNicknames && friend.$nickName" class="name" :style="{ color: friend.ref.$userColour }">
|
||||
@@ -17,21 +17,18 @@
|
||||
<span v-if="isFriendActiveOrOffline" class="extra">{{ friend.ref.statusDescription }}</span>
|
||||
<template v-else>
|
||||
<span v-if="friend.pendingOffline" class="extra">
|
||||
<i class="el-icon-warning-outline" /> {{ $t('side_panel.pending_offline') }}
|
||||
<el-icon><WarningFilled /></el-icon> {{ t('side_panel.pending_offline') }}
|
||||
</span>
|
||||
<template v-else-if="isGroupByInstance">
|
||||
<i v-if="isFriendTraveling" class="el-icon el-icon-loading"></i>
|
||||
<el-icon v-if="isFriendTraveling" class="is-loading" style="margin-right: 3px"
|
||||
><Loading
|
||||
/></el-icon>
|
||||
<Timer
|
||||
class="extra"
|
||||
:epoch="epoch"
|
||||
:style="isFriendTraveling ? { display: 'inline-block', overflow: 'unset' } : undefined" />
|
||||
</template>
|
||||
<Location
|
||||
v-else
|
||||
class="extra"
|
||||
:location="friend.ref.location"
|
||||
:traveling="friend.ref.travelingToLocation"
|
||||
:link="false" />
|
||||
<Location v-else class="extra" :location="locationProp" :traveling="travelingProp" :link="false" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -39,15 +36,15 @@
|
||||
<span>{{ friend.name || friend.id }}</span>
|
||||
<el-button
|
||||
ttype="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
style="margin-left: 5px"
|
||||
@click.stop="$emit('confirm-delete-friend', friend.id)">
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<el-skeleton v-else animated class="skeleton" :throttle="100">
|
||||
<template slot="template">
|
||||
<template #template>
|
||||
<div>
|
||||
<el-skeleton-item variant="circle" />
|
||||
<div>
|
||||
@@ -61,8 +58,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { WarningFilled, Close, Loading } from '@element-plus/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { userImage, userStatusClass } from '../shared/utils';
|
||||
import { useAppearanceSettingsStore, useFriendStore } from '../stores';
|
||||
|
||||
@@ -71,21 +70,27 @@
|
||||
isGroupByInstance: Boolean
|
||||
});
|
||||
|
||||
defineEmits(['click', 'confirm-delete-friend']);
|
||||
|
||||
const { hideNicknames } = storeToRefs(useAppearanceSettingsStore());
|
||||
const { isRefreshFriendsLoading } = storeToRefs(useFriendStore());
|
||||
const { t } = useI18n();
|
||||
|
||||
const isFriendTraveling = computed(() => props.friend.ref?.location === 'traveling');
|
||||
const isFriendActiveOrOffline = computed(() => props.friend.state === 'active' || props.friend.state === 'offline');
|
||||
const epoch = computed(() =>
|
||||
isFriendTraveling.value ? props.friend.ref?.$travelingToTime : props.friend.ref?.$location_at
|
||||
);
|
||||
|
||||
const locationProp = computed(() => props.friend.ref?.location || '');
|
||||
const travelingProp = computed(() => props.friend.ref?.travelingToLocation || '');
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.skeleton {
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
::v-deep .el-skeleton {
|
||||
:deep(.el-skeleton) {
|
||||
height: 100%;
|
||||
> div {
|
||||
height: 100%;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<template v-if="state.canCloseInstance">
|
||||
<el-button
|
||||
:disabled="state.isClosed"
|
||||
size="mini"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="closeInstance(props.location)">
|
||||
{{ t('dialog.user.info.close_instance') }} </el-button
|
||||
@@ -24,17 +24,14 @@
|
||||
>{{ 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 v-for="user in state.userList" :key="user.id">
|
||||
<span style="cursor: pointer; margin-right: 5px" @click="showUserDialog(user.id)">{{
|
||||
user.displayName
|
||||
}}</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<i class="el-icon-caret-bottom"></i>
|
||||
<el-icon><CaretBottom /></el-icon>
|
||||
</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>
|
||||
@@ -57,8 +54,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance, reactive, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { CaretBottom } from '@element-plus/icons-vue';
|
||||
import { reactive, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { miscRequest } from '../api';
|
||||
import { formatDateFilter, hasGroupPermission } from '../shared/utils';
|
||||
import { useGroupStore, useInstanceStore, useLocationStore, useUserStore } from '../stores';
|
||||
@@ -94,8 +93,6 @@
|
||||
disabledContentSettings: ''
|
||||
});
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
function parse() {
|
||||
Object.assign(state, {
|
||||
isValidInstance: false,
|
||||
@@ -153,18 +150,19 @@
|
||||
}
|
||||
|
||||
function closeInstance(location) {
|
||||
proxy.$confirm('Continue? Close Instance, nobody will be able to join', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Close Instance, nobody will be able to join', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning',
|
||||
callback: async (action) => {
|
||||
type: 'warning'
|
||||
})
|
||||
.then(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' });
|
||||
ElMessage({ message: t('message.instance.closed'), type: 'success' });
|
||||
instanceStore.applyInstance(args.json);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<el-tooltip
|
||||
v-if="!canOpenInstanceInGame()"
|
||||
placement="top"
|
||||
:content="t('dialog.user.info.self_invite_tooltip')"
|
||||
:disabled="hideTooltips">
|
||||
<el-button v-show="isVisible" @click="confirmInvite" size="mini" icon="el-icon-message" circle />
|
||||
</el-tooltip>
|
||||
<el-tooltip v-else placement="top" :content="t('dialog.user.info.open_in_vrchat_tooltip')" :disabled="hideTooltips">
|
||||
<el-button @click="openInstance" size="mini" icon="el-icon-message" circle />
|
||||
</el-tooltip>
|
||||
<div v-if="isVisible" :class="['inline-block']">
|
||||
<el-tooltip
|
||||
v-if="!canOpenInstanceInGame()"
|
||||
placement="top"
|
||||
:content="t('dialog.user.info.self_invite_tooltip')">
|
||||
<el-button v-show="isVisible" @click="confirmInvite" size="small" :icon="Message" circle />
|
||||
</el-tooltip>
|
||||
<el-tooltip v-else placement="top" :content="t('dialog.user.info.open_in_vrchat_tooltip')">
|
||||
<el-button @click="openInstance" size="small" :icon="Message" circle />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Message } from '@element-plus/icons-vue';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { instanceRequest } from '../api';
|
||||
import { checkCanInviteSelf, parseLocation } from '../shared/utils';
|
||||
import { useAppearanceSettingsStore } from '../stores/settings/appearance';
|
||||
import { useLaunchStore } from '../stores/launch';
|
||||
import { useInviteStore } from '../stores/invite';
|
||||
import { useInviteStore, useLaunchStore } from '../stores';
|
||||
|
||||
const props = defineProps({
|
||||
location: String,
|
||||
@@ -28,13 +28,9 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { hideTooltips } = storeToRefs(useAppearanceSettingsStore());
|
||||
|
||||
const { canOpenInstanceInGame } = useInviteStore();
|
||||
const { tryOpenInstanceInVrc } = useLaunchStore();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const isVisible = computed(() => checkCanInviteSelf(props.location));
|
||||
|
||||
function confirmInvite() {
|
||||
@@ -50,7 +46,7 @@
|
||||
shortName: props.shortname
|
||||
})
|
||||
.then((args) => {
|
||||
proxy.$message({ message: 'Self invite sent', type: 'success' });
|
||||
ElMessage({ message: 'Self invite sent', type: 'success' });
|
||||
return args;
|
||||
});
|
||||
}
|
||||
@@ -64,3 +60,9 @@
|
||||
tryOpenInstanceInVrc(L.tag, props.shortname);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
<template>
|
||||
<span v-if="lastJoin">
|
||||
<el-tooltip placement="top" style="margin-left: 5px">
|
||||
<span v-if="lastJoin" :class="['inline-block', 'ml-5']">
|
||||
<el-tooltip placement="top" class="ml-5">
|
||||
<template #content>
|
||||
<span>{{ $t('dialog.user.info.last_join') }} <Timer :epoch="lastJoin" /></span>
|
||||
<span>{{ t('dialog.user.info.last_join') }} <Timer :epoch="lastJoin" /></span>
|
||||
</template>
|
||||
<i class="el-icon el-icon-location-outline" style="display: inline-block" />
|
||||
<el-icon style="display: inline-block"><Location /></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Location } from '@element-plus/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useInstanceStore } from '../stores';
|
||||
|
||||
const { instanceJoinHistory } = storeToRefs(useInstanceStore());
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
location: String,
|
||||
@@ -30,3 +33,12 @@
|
||||
watch(() => props.location, parse, { immediate: true });
|
||||
watch(() => props.currentlocation, parse);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ml-5 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
<template>
|
||||
<el-tooltip
|
||||
v-show="isVisible"
|
||||
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-tooltip>
|
||||
<div v-if="isVisible" class="inline-block">
|
||||
<el-tooltip placement="top" :content="t('dialog.user.info.launch_invite_tooltip')"
|
||||
><el-button @click="confirm" size="small" :icon="SwitchButton" circle />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { SwitchButton } from '@element-plus/icons-vue';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { checkCanInviteSelf } from '../shared/utils';
|
||||
import { useLaunchStore } from '../stores';
|
||||
|
||||
@@ -18,8 +17,7 @@
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
location: String,
|
||||
hideTooltips: Boolean
|
||||
location: String
|
||||
});
|
||||
|
||||
const isVisible = computed(() => {
|
||||
@@ -30,3 +28,9 @@
|
||||
launchStore.showLaunchDialog(props.location);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<span v-if="!text" style="color: transparent">-</span>
|
||||
<span v-if="!text" class="transparent">-</span>
|
||||
<span v-show="text">
|
||||
<span
|
||||
:class="{ 'x-link': link && location !== 'private' && location !== 'offline' }"
|
||||
@click="handleShowWorldDialog">
|
||||
<i v-if="isTraveling" class="el-icon el-icon-loading" style="display: inline-block"></i>
|
||||
<el-icon :class="['is-loading', 'inline-block']" style="margin-right: 3px" v-if="isTraveling"
|
||||
><Loading
|
||||
/></el-icon>
|
||||
<span>{{ text }}</span>
|
||||
</span>
|
||||
<span v-if="groupName" :class="{ 'x-link': link }" @click="handleShowGroupDialog">({{ groupName }})</span>
|
||||
<span v-if="region" 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 v-if="region" :class="['flags', 'inline-block', 'ml-5', region]"></span>
|
||||
<el-icon v-if="strict" :class="['inline-block', 'ml-5']"><Lock /></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watch } from 'vue';
|
||||
import { Loading, Lock } from '@element-plus/icons-vue';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { getGroupName, getWorldName, parseLocation } from '../shared/utils';
|
||||
import { useGroupStore, useInstanceStore, useSearchStore, useWorldStore } from '../stores';
|
||||
|
||||
const { cachedWorlds } = storeToRefs(useWorldStore());
|
||||
const { showWorldDialog } = useWorldStore();
|
||||
const { cachedWorlds, showWorldDialog } = useWorldStore();
|
||||
const { showGroupDialog } = useGroupStore();
|
||||
const { showPreviousInstancesInfoDialog } = useInstanceStore();
|
||||
const { verifyShortName } = useSearchStore();
|
||||
@@ -51,14 +52,9 @@
|
||||
const isTraveling = ref(false);
|
||||
const groupName = ref('');
|
||||
|
||||
watch(
|
||||
() => props.location,
|
||||
() => {
|
||||
parse();
|
||||
}
|
||||
);
|
||||
|
||||
parse();
|
||||
watchEffect(() => {
|
||||
parse();
|
||||
});
|
||||
|
||||
function parse() {
|
||||
isTraveling.value = false;
|
||||
@@ -82,7 +78,7 @@
|
||||
text.value = props.hint;
|
||||
}
|
||||
} else if (L.worldId) {
|
||||
const ref = cachedWorlds.value.get(L.worldId);
|
||||
const ref = cachedWorlds.get(L.worldId);
|
||||
if (typeof ref === 'undefined') {
|
||||
getWorldName(L.worldId).then((worldName) => {
|
||||
if (L.tag === instanceId) {
|
||||
@@ -152,3 +148,17 @@
|
||||
showGroupDialog(L.groupId);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ml-5 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.transparent {
|
||||
color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<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>
|
||||
<el-icon v-if="isUnlocked" style="display: inline-block; margin-right: 5px"><Unlock /></el-icon>
|
||||
<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>
|
||||
<el-icon v-if="strict" style="display: inline-block; margin-left: 5px"><Lock /></el-icon>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Lock, Unlock } from '@element-plus/icons-vue';
|
||||
import { ref, watch } from 'vue';
|
||||
import { getGroupName, parseLocation } from '../shared/utils';
|
||||
import { useGroupStore, useLaunchStore } from '../stores';
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<div v-else-if="pendingVRCXUpdate || pendingVRCXInstall" class="pending-update">
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-download"
|
||||
size="small"
|
||||
:icon="Download"
|
||||
circle
|
||||
style="font-size: 14px; height: 50px; width: 50px"
|
||||
@click="showVRCXUpdateDialog" />
|
||||
@@ -30,7 +30,7 @@
|
||||
:class="{ notify: notifiedMenus.includes(item.index) }">
|
||||
<i :class="item.icon"></i>
|
||||
<template #title>
|
||||
<span>{{ $t(item.tooltip) }}</span>
|
||||
<span>{{ t(item.tooltip) }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
@@ -38,9 +38,13 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Download } from '@element-plus/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useUiStore, useVRCXUpdaterStore } from '../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const navItems = [
|
||||
{ index: 'feed', icon: 'ri-rss-line', tooltip: 'nav_tooltip.feed' },
|
||||
{ index: 'gameLog', icon: 'ri-history-line', tooltip: 'nav_tooltip.game_log' },
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
<div class="name" :style="{ width: longLabel ? '300px' : undefined }">
|
||||
{{ label }}
|
||||
<el-tooltip v-if="tooltip" placement="top" class="tooltip" :content="tooltip"
|
||||
><i class="el-icon-info"
|
||||
/></el-tooltip>
|
||||
><el-icon><InfoFilled /></el-icon
|
||||
></el-tooltip>
|
||||
</div>
|
||||
|
||||
<el-switch class="switch" :value="value" @change="change" :disabled="disabled"></el-switch>
|
||||
<el-switch class="switch" :model-value="value" @change="change" :disabled="disabled"></el-switch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
defineProps({
|
||||
label: String,
|
||||
value: Boolean,
|
||||
@@ -30,13 +31,13 @@
|
||||
<style scoped>
|
||||
.simple-switch {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
}
|
||||
.simple-switch > .name {
|
||||
width: 225px;
|
||||
min-width: 225px;
|
||||
word-wrap: break-word;
|
||||
padding-top: 7px;
|
||||
}
|
||||
.simple-switch > .switch {
|
||||
margin-left: 10px;
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
<template>
|
||||
<span>
|
||||
<span>{{ text }}</span>
|
||||
<span v-if="groupName">({{ groupName }})</span>
|
||||
<span
|
||||
v-if="region"
|
||||
class="flags"
|
||||
:class="region"
|
||||
style="display:inline-block;margin-bottom:2px;margin-left:5px">
|
||||
</span>
|
||||
<i
|
||||
v-if="strict"
|
||||
class="el-icon el-icon-lock"
|
||||
style="display:inline-block;margin-left:5px">
|
||||
</i>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { parseLocation } from '../shared/utils/location';
|
||||
|
||||
const props = defineProps({
|
||||
location: String,
|
||||
hint: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
grouphint: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const text = ref('');
|
||||
const region = ref('');
|
||||
const strict = ref(false);
|
||||
const groupName = ref('');
|
||||
|
||||
function parse() {
|
||||
text.value = props.location;
|
||||
const L = parseLocation(props.location);
|
||||
|
||||
if (L.isOffline) {
|
||||
text.value = 'Offline';
|
||||
} else if (L.isPrivate) {
|
||||
text.value = 'Private';
|
||||
} else if (L.isTraveling) {
|
||||
text.value = 'Traveling';
|
||||
} else if (typeof props.hint === 'string' && props.hint !== '') {
|
||||
if (L.instanceId) {
|
||||
text.value = `${props.hint} #${L.instanceName} ${L.accessTypeName}`;
|
||||
} else {
|
||||
text.value = props.hint;
|
||||
}
|
||||
} else if (L.worldId) {
|
||||
if (L.instanceId) {
|
||||
text.value = ` #${L.instanceName} ${L.accessTypeName}`;
|
||||
} else {
|
||||
text.value = props.location;
|
||||
}
|
||||
}
|
||||
|
||||
region.value = '';
|
||||
if (
|
||||
props.location !== '' &&
|
||||
L.instanceId &&
|
||||
!L.isOffline &&
|
||||
!L.isPrivate
|
||||
) {
|
||||
region.value = L.region;
|
||||
if (!L.region) {
|
||||
region.value = 'us';
|
||||
}
|
||||
}
|
||||
|
||||
strict.value = L.strict;
|
||||
groupName.value = props.grouphint;
|
||||
}
|
||||
|
||||
watch(() => props.location, parse);
|
||||
|
||||
onMounted(parse);
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="changeAvatarImageDialogVisible"
|
||||
:model-value="changeAvatarImageDialogVisible"
|
||||
:title="t('dialog.change_content_image.avatar')"
|
||||
width="850px"
|
||||
append-to-body
|
||||
@@ -16,73 +16,47 @@
|
||||
<span>{{ t('dialog.change_content_image.description') }}</span>
|
||||
<br />
|
||||
<el-button-group style="padding-bottom: 10px; padding-top: 10px">
|
||||
<el-button type="default" size="small" icon="el-icon-refresh" @click="refresh">
|
||||
{{ t('dialog.change_content_image.refresh') }}
|
||||
</el-button>
|
||||
<el-button type="default" size="small" icon="el-icon-upload2" @click="uploadAvatarImage">
|
||||
<el-button type="default" size="small" :icon="Upload" @click="uploadAvatarImage">
|
||||
{{ t('dialog.change_content_image.upload') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<br />
|
||||
<div v-for="image in previousImagesTable" :key="image.version" style="display: inline-block">
|
||||
<div
|
||||
v-if="image.file"
|
||||
class="x-change-image-item"
|
||||
style="cursor: pointer"
|
||||
:class="{ 'current-image': compareCurrentImage(image) }"
|
||||
@click="setAvatarImage(image)">
|
||||
<img v-lazy="image.file.url" class="image" />
|
||||
</div>
|
||||
<div class="x-change-image-item">
|
||||
<img :src="currentImageUrl" class="img-size" loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Upload } from '@element-plus/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { imageRequest } from '../../../api';
|
||||
import { AppGlobal } from '../../../service/appConfig';
|
||||
import { $throw } from '../../../service/request';
|
||||
import { extractFileId } from '../../../shared/utils';
|
||||
import { useAvatarStore, useGalleryStore } from '../../../stores';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { avatarRequest } from '../../../api';
|
||||
import { useAvatarStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const { avatarDialog } = storeToRefs(useAvatarStore());
|
||||
const { previousImagesTable } = storeToRefs(useGalleryStore());
|
||||
const { applyAvatar } = useAvatarStore();
|
||||
|
||||
const props = defineProps({
|
||||
changeAvatarImageDialogVisible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
required: true
|
||||
},
|
||||
previousImagesFileId: {
|
||||
previousImageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const changeAvatarImageDialogLoading = ref(false);
|
||||
const avatarImage = ref({
|
||||
base64File: '',
|
||||
fileMd5: '',
|
||||
base64SignatureFile: '',
|
||||
signatureMd5: '',
|
||||
fileId: '',
|
||||
avatarId: ''
|
||||
});
|
||||
const currentImageUrl = computed(() => props.previousImageUrl);
|
||||
|
||||
const emit = defineEmits(['update:changeAvatarImageDialogVisible', 'refresh']);
|
||||
|
||||
function refresh() {
|
||||
emit('refresh', 'Change');
|
||||
}
|
||||
const emit = defineEmits(['update:changeAvatarImageDialogVisible', 'update:previousImageUrl']);
|
||||
|
||||
function closeDialog() {
|
||||
emit('update:changeAvatarImageDialogVisible', false);
|
||||
@@ -93,23 +67,9 @@
|
||||
return response;
|
||||
}
|
||||
|
||||
async function genMd5(file) {
|
||||
const response = await AppApi.MD5File(file);
|
||||
return response;
|
||||
}
|
||||
|
||||
async function genSig(file) {
|
||||
const response = await AppApi.SignFile(file);
|
||||
return response;
|
||||
}
|
||||
|
||||
async function genLength(file) {
|
||||
const response = await AppApi.FileLength(file);
|
||||
return response;
|
||||
}
|
||||
|
||||
function onFileChangeAvatarImage(e) {
|
||||
const clearFile = function () {
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
const fileInput = /** @type{HTMLInputElement} */ (document.querySelector('#AvatarImageUploadButton'));
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
@@ -124,7 +84,7 @@
|
||||
// validate file
|
||||
if (files[0].size >= 100000000) {
|
||||
// 100MB
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.too_large'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -132,7 +92,7 @@
|
||||
return;
|
||||
}
|
||||
if (!files[0].type.match(/image.*/)) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.not_image'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -141,50 +101,14 @@
|
||||
}
|
||||
|
||||
const r = new FileReader();
|
||||
r.onload = async function (file) {
|
||||
r.onload = async function () {
|
||||
try {
|
||||
const base64File = await resizeImageToFitLimits(btoa(r.result.toString()));
|
||||
// 10MB
|
||||
const fileMd5 = await genMd5(base64File);
|
||||
const fileSizeInBytes = parseInt(file.total.toString(), 10);
|
||||
const base64SignatureFile = await genSig(base64File);
|
||||
const signatureMd5 = await genMd5(base64SignatureFile);
|
||||
const signatureSizeInBytes = parseInt(await genLength(base64SignatureFile), 10);
|
||||
|
||||
const avatarId = avatarDialog.value.id;
|
||||
const { imageUrl } = avatarDialog.value.ref;
|
||||
|
||||
const fileId = extractFileId(imageUrl);
|
||||
if (!fileId) {
|
||||
$message({
|
||||
message: t('message.avatar.image_invalid'),
|
||||
type: 'error'
|
||||
});
|
||||
clearFile();
|
||||
return;
|
||||
}
|
||||
|
||||
avatarImage.value = {
|
||||
base64File,
|
||||
fileMd5,
|
||||
base64SignatureFile,
|
||||
signatureMd5,
|
||||
fileId,
|
||||
avatarId
|
||||
};
|
||||
const params = {
|
||||
fileMd5,
|
||||
fileSizeInBytes,
|
||||
signatureMd5,
|
||||
signatureSizeInBytes
|
||||
};
|
||||
|
||||
// Upload chaining
|
||||
await initiateUpload(params, fileId);
|
||||
await initiateUpload(base64File);
|
||||
} catch (error) {
|
||||
console.error('Avatar image upload process failed:', error);
|
||||
} finally {
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
clearFile();
|
||||
}
|
||||
};
|
||||
@@ -193,169 +117,32 @@
|
||||
r.readAsBinaryString(files[0]);
|
||||
}
|
||||
|
||||
// ------------ Upload Process Start ------------
|
||||
|
||||
async function initiateUpload(params, fileId) {
|
||||
const res = await imageRequest.uploadAvatarImage(params, fileId);
|
||||
return avatarImageInit(res);
|
||||
}
|
||||
|
||||
async function avatarImageInit(args) {
|
||||
const fileId = args.json.id;
|
||||
const fileVersion = args.json.versions[args.json.versions.length - 1].version;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadAvatarImageFileStart(params);
|
||||
return avatarImageFileStart(res);
|
||||
}
|
||||
|
||||
async function avatarImageFileStart(args) {
|
||||
const { url } = args.json;
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
url,
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
return uploadAvatarImageFileAWS(params);
|
||||
}
|
||||
|
||||
async function uploadAvatarImageFileAWS(params) {
|
||||
const json = await webApiService.execute({
|
||||
url: params.url,
|
||||
uploadFilePUT: true,
|
||||
fileData: avatarImage.value.base64File,
|
||||
fileMIME: 'image/png',
|
||||
headers: {
|
||||
'Content-MD5': avatarImage.value.fileMd5
|
||||
}
|
||||
async function initiateUpload(base64File) {
|
||||
const args = await avatarRequest.uploadAvatarImage(base64File);
|
||||
const fileUrl = args.json.versions[args.json.versions.length - 1].file.url;
|
||||
const avatarArgs = await avatarRequest.saveAvatar({
|
||||
id: avatarDialog.value.id,
|
||||
imageUrl: fileUrl
|
||||
});
|
||||
|
||||
if (json.status !== 200) {
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
$throw(json.status, 'Avatar image upload failed', params.url);
|
||||
}
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return avatarImageFileAWS(args);
|
||||
}
|
||||
|
||||
async function avatarImageFileAWS(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadAvatarImageFileFinish(params);
|
||||
return avatarImageFileFinish(res);
|
||||
}
|
||||
|
||||
async function avatarImageFileFinish(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadAvatarImageSigStart(params);
|
||||
return avatarImageSigStart(res);
|
||||
}
|
||||
|
||||
async function avatarImageSigStart(args) {
|
||||
const { url } = args.json;
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
url,
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
return uploadAvatarImageSigAWS(params);
|
||||
}
|
||||
|
||||
async function uploadAvatarImageSigAWS(params) {
|
||||
const json = await webApiService.execute({
|
||||
url: params.url,
|
||||
uploadFilePUT: true,
|
||||
fileData: avatarImage.value.base64SignatureFile,
|
||||
fileMIME: 'application/x-rsync-signature',
|
||||
headers: {
|
||||
'Content-MD5': avatarImage.value.signatureMd5
|
||||
}
|
||||
});
|
||||
|
||||
if (json.status !== 200) {
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
$throw(json.status, 'Avatar image upload failed', params.url);
|
||||
}
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return avatarImageSigAWS(args);
|
||||
}
|
||||
|
||||
async function avatarImageSigAWS(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadAvatarImageSigFinish(params);
|
||||
return avatarImageSigFinish(res);
|
||||
}
|
||||
|
||||
async function avatarImageSigFinish(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const parmas = {
|
||||
id: avatarImage.value.avatarId,
|
||||
imageUrl: `${AppGlobal.endpointDomain}/file/${fileId}/${fileVersion}/file`
|
||||
};
|
||||
const res = await imageRequest.setAvatarImage(parmas);
|
||||
return avatarImageSet(res);
|
||||
}
|
||||
|
||||
async function avatarImageSet(args) {
|
||||
applyAvatar(args.json);
|
||||
const ref = applyAvatar(avatarArgs.json);
|
||||
emit('update:previousImageUrl', ref.imageUrl);
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
if (args.json.imageUrl === args.params.imageUrl) {
|
||||
$message({
|
||||
message: t('message.avatar.image_changed'),
|
||||
type: 'success'
|
||||
});
|
||||
refresh();
|
||||
} else {
|
||||
$throw(0, 'Avatar image change failed', args.params.imageUrl);
|
||||
}
|
||||
}
|
||||
ElMessage({
|
||||
message: t('message.avatar.image_changed'),
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
// ------------ Upload Process End ------------
|
||||
// closeDialog();
|
||||
}
|
||||
|
||||
function uploadAvatarImage() {
|
||||
document.getElementById('AvatarImageUploadButton').click();
|
||||
}
|
||||
|
||||
function setAvatarImage(image) {
|
||||
changeAvatarImageDialogLoading.value = true;
|
||||
const parmas = {
|
||||
id: avatarDialog.value.id,
|
||||
imageUrl: `${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file`
|
||||
};
|
||||
imageRequest
|
||||
.setAvatarImage(parmas)
|
||||
.then((args) => applyAvatar(args.json))
|
||||
.finally(() => {
|
||||
changeAvatarImageDialogLoading.value = false;
|
||||
closeDialog();
|
||||
});
|
||||
}
|
||||
|
||||
function compareCurrentImage(image) {
|
||||
return (
|
||||
`${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file` ===
|
||||
avatarDialog.value.ref.imageUrl
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.img-size {
|
||||
width: 500px;
|
||||
height: 375px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
ref="setAvatarStylesDialog"
|
||||
class="x-dialog"
|
||||
:visible.sync="setAvatarStylesDialog.visible"
|
||||
v-model="setAvatarStylesDialog.visible"
|
||||
:title="t('dialog.set_avatar_styles.header')"
|
||||
width="400px"
|
||||
append-to-body>
|
||||
@@ -39,11 +39,11 @@
|
||||
</el-select>
|
||||
</div>
|
||||
<br />
|
||||
<div style="font-size: 12px; margin-top: 10px">{{ $t('dialog.set_world_tags.author_tags') }}<br /></div>
|
||||
<div style="font-size: 12px; margin-top: 10px">{{ t('dialog.set_world_tags.author_tags') }}<br /></div>
|
||||
<el-input
|
||||
v-model="setAvatarStylesDialog.authorTags"
|
||||
type="textarea"
|
||||
size="mini"
|
||||
size="small"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
placeholder=""
|
||||
@@ -57,21 +57,18 @@
|
||||
t('dialog.set_avatar_styles.save')
|
||||
}}</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { watch, getCurrentInstance } from 'vue';
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { arraysMatch } from '../../../shared/utils';
|
||||
import { avatarRequest } from '../../../api';
|
||||
import { useAvatarStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const { applyAvatar } = useAvatarStore();
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
ref="setAvatarTagsDialog"
|
||||
class="x-dialog"
|
||||
:visible.sync="setAvatarTagsDialog.visible"
|
||||
v-model="setAvatarTagsDialog.visible"
|
||||
:title="t('dialog.set_avatar_tags.header')"
|
||||
width="770px"
|
||||
append-to-body>
|
||||
@@ -29,7 +29,7 @@
|
||||
<br />
|
||||
<el-input
|
||||
v-model="setAvatarTagsDialog.selectedTagsCsv"
|
||||
size="mini"
|
||||
size="small"
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
:placeholder="t('dialog.set_avatar_tags.custom_tags_placeholder')"
|
||||
style="margin-top: 10px"
|
||||
@@ -47,19 +47,18 @@
|
||||
<span style="margin-left: 5px"
|
||||
>{{ setAvatarTagsDialog.selectedCount }} / {{ setAvatarTagsDialog.ownAvatars.length }}</span
|
||||
>
|
||||
<span v-if="setAvatarTagsDialog.loading" style="margin-left: 5px">
|
||||
<i class="el-icon-loading"></i>
|
||||
</span>
|
||||
<el-icon v-if="setAvatarTagsDialog.loading" class="is-loading" style="margin-left: 5px"
|
||||
><Loading
|
||||
/></el-icon>
|
||||
<br />
|
||||
<div class="x-friend-list" style="margin-top: 10px; min-height: 60px; max-height: 280px">
|
||||
<div
|
||||
v-for="avatar in setAvatarTagsDialog.ownAvatars"
|
||||
:key="avatar.id"
|
||||
class="x-friend-item x-friend-item-border"
|
||||
style="width: 350px"
|
||||
:class="['item-width', 'x-friend-item', 'x-friend-item-border']"
|
||||
@click="showAvatarDialog(avatar.id)">
|
||||
<div class="avatar">
|
||||
<img v-if="avatar.thumbnailImageUrl" v-lazy="avatar.thumbnailImageUrl" />
|
||||
<img v-if="avatar.thumbnailImageUrl" :src="avatar.thumbnailImageUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="avatar.name"></span>
|
||||
@@ -76,7 +75,7 @@
|
||||
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
|
||||
<span class="extra" v-text="avatar.$tagString"></span>
|
||||
</div>
|
||||
<el-button type="text" size="mini" style="margin-left: 5px" @click.stop>
|
||||
<el-button type="text" size="small" style="margin-left: 5px" @click.stop>
|
||||
<el-checkbox v-model="avatar.$selected" @change="updateAvatarTagsSelection"></el-checkbox>
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -90,21 +89,21 @@
|
||||
t('dialog.set_avatar_tags.save')
|
||||
}}</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { Loading } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { avatarRequest } from '../../../api';
|
||||
import { useAvatarStore } from '../../../stores';
|
||||
|
||||
const { showAvatarDialog, applyAvatar } = useAvatarStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const props = defineProps({
|
||||
setAvatarTagsDialog: {
|
||||
type: Object,
|
||||
@@ -229,7 +228,7 @@
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Error saving avatar tags',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -273,4 +272,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.item-width {
|
||||
width: 335px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<safe-dialog ref="favoriteDialogRef" :visible.sync="isVisible" :title="t('dialog.favorite.header')" width="300px">
|
||||
<el-dialog :z-index="favoriteDialogIndex" v-model="isVisible" :title="t('dialog.favorite.header')" width="300px">
|
||||
<div v-loading="loading">
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.vrchat_favorites') }}</span>
|
||||
<template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key">
|
||||
<el-button
|
||||
style="display: block; width: 100%; margin: 10px 0"
|
||||
@click="deleteFavoriteNoConfirm(favoriteDialog.objectId)">
|
||||
<i class="el-icon-check"></i>
|
||||
<el-icon><Check /></el-icon>
|
||||
{{ favoriteDialog.currentGroup.displayName }} ({{ favoriteDialog.currentGroup.count }} /
|
||||
{{ favoriteDialog.currentGroup.capacity }})
|
||||
</el-button>
|
||||
@@ -23,18 +23,16 @@
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'world'" style="margin-top: 20px">
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_favorites') }}</span>
|
||||
<template v-for="group in localWorldFavoriteGroups">
|
||||
<template v-for="group in localWorldFavoriteGroups" :key="group">
|
||||
<el-button
|
||||
v-if="hasLocalWorldFavorite(favoriteDialog.objectId, group)"
|
||||
:key="group"
|
||||
style="display: block; width: 100%; margin: 10px 0"
|
||||
@click="removeLocalWorldFavorite(favoriteDialog.objectId, group)">
|
||||
<i class="el-icon-check"></i>
|
||||
<el-icon><Check /></el-icon>
|
||||
{{ group }} ({{ getLocalWorldFavoriteGroupLength(group) }})
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
:key="group"
|
||||
style="display: block; width: 100%; margin: 10px 0"
|
||||
@click="addLocalWorldFavorite(favoriteDialog.objectId, group)">
|
||||
{{ group }} ({{ getLocalWorldFavoriteGroupLength(group) }})
|
||||
@@ -43,18 +41,16 @@
|
||||
</div>
|
||||
<div v-if="favoriteDialog.type === 'avatar'" style="margin-top: 20px">
|
||||
<span style="display: block; text-align: center">{{ t('dialog.favorite.local_avatar_favorites') }}</span>
|
||||
<template v-for="group in localAvatarFavoriteGroups">
|
||||
<template v-for="group in localAvatarFavoriteGroups" :key="group">
|
||||
<el-button
|
||||
v-if="hasLocalAvatarFavorite(favoriteDialog.objectId, group)"
|
||||
:key="group"
|
||||
style="display: block; width: 100%; margin: 10px 0"
|
||||
@click="removeLocalAvatarFavorite(favoriteDialog.objectId, group)">
|
||||
<i class="el-icon-check"></i>
|
||||
<el-icon><Check /></el-icon>
|
||||
{{ group }} ({{ getLocalAvatarFavoriteGroupLength(group) }})
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
:key="group"
|
||||
style="display: block; width: 100%; margin: 10px 0"
|
||||
:disabled="!isLocalUserVrcplusSupporter"
|
||||
@click="addLocalAvatarFavorite(favoriteDialog.objectId, group)">
|
||||
@@ -62,16 +58,18 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Check } from '@element-plus/icons-vue';
|
||||
|
||||
import Noty from 'noty';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { favoriteRequest } from '../../api';
|
||||
import { adjustDialogZ } from '../../shared/utils';
|
||||
import { getNextDialogIndex } from '../../shared/utils';
|
||||
import { useFavoriteStore, useUserStore } from '../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -98,7 +96,7 @@
|
||||
} = favoriteStore;
|
||||
const { currentUser } = storeToRefs(useUserStore());
|
||||
|
||||
const favoriteDialogRef = ref(null);
|
||||
const favoriteDialogIndex = ref(2000);
|
||||
const groups = ref([]);
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -113,11 +111,12 @@
|
||||
|
||||
watch(
|
||||
() => favoriteDialog.value.visible,
|
||||
async (value) => {
|
||||
(value) => {
|
||||
if (value) {
|
||||
initFavoriteDialog();
|
||||
await nextTick();
|
||||
adjustDialogZ(favoriteDialogRef.value.$el);
|
||||
nextTick(() => {
|
||||
favoriteDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,49 +1,90 @@
|
||||
<template>
|
||||
<safe-dialog class="x-dialog" :visible.sync="fullscreenImageDialog.visible" append-to-body width="97vw">
|
||||
<el-dialog class="x-dialog" v-model="fullscreenImageDialog.visible" append-to-body width="97vw">
|
||||
<div>
|
||||
<div style="margin: 0 0 5px 5px">
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-s-order"
|
||||
circle
|
||||
@click="copyImageUrl(fullscreenImageDialog.imageUrl)"></el-button>
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-download"
|
||||
circle
|
||||
style="margin-left: 5px"
|
||||
:disabled="!fullscreenImageDialog.imageUrl.startsWith('http')"
|
||||
@click="
|
||||
downloadAndSaveImage(fullscreenImageDialog.imageUrl, fullscreenImageDialog.fileName)
|
||||
"></el-button>
|
||||
<el-tooltip :content="t('dialog.fullscreen_image.copy_image_to_clipboard')" placement="top">
|
||||
<el-button
|
||||
size="small"
|
||||
:icon="CopyDocument"
|
||||
circle
|
||||
@click="copyImageToClipboard(fullscreenImageDialog.imageUrl)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="t('dialog.fullscreen_image.download_and_save_image')" placement="top">
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
:icon="Download"
|
||||
circle
|
||||
style="margin-left: 5px"
|
||||
:disabled="!fullscreenImageDialog.imageUrl.startsWith('http')"
|
||||
@click="
|
||||
downloadAndSaveImage(fullscreenImageDialog.imageUrl, fullscreenImageDialog.fileName)
|
||||
"></el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<img v-lazy="fullscreenImageDialog.imageUrl" style="width: 100%; height: 85vh; object-fit: contain" />
|
||||
<img
|
||||
:src="fullscreenImageDialog.imageUrl"
|
||||
style="width: 100%; height: 85vh; object-fit: contain"
|
||||
loading="lazy" />
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Download, CopyDocument } from '@element-plus/icons-vue';
|
||||
|
||||
import Noty from 'noty';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { copyToClipboard, escapeTag, extractFileId } from '../../shared/utils';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { escapeTag, extractFileId } from '../../shared/utils';
|
||||
import { useGalleryStore } from '../../stores';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { $message } = proxy;
|
||||
const { t } = useI18n();
|
||||
|
||||
const { fullscreenImageDialog } = storeToRefs(useGalleryStore());
|
||||
|
||||
function copyImageUrl(imageUrl) {
|
||||
copyToClipboard(imageUrl, 'ImageUrl copied to clipboard');
|
||||
async function copyImageToClipboard(url) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
const msg = ElMessage({
|
||||
message: 'Downloading image...',
|
||||
type: 'info'
|
||||
});
|
||||
try {
|
||||
const response = await webApiService.execute({
|
||||
url,
|
||||
method: 'GET'
|
||||
});
|
||||
if (response.status !== 200 || !response.data.startsWith('data:image/png')) {
|
||||
throw new Error(`Error: ${response.data}`);
|
||||
}
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
'image/png': await (await fetch(response.data)).blob()
|
||||
})
|
||||
]);
|
||||
ElMessage({
|
||||
message: 'Image copied to clipboard',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error downloading image:', error);
|
||||
new Noty({
|
||||
type: 'error',
|
||||
text: escapeTag(`Failed to download image. ${url}`)
|
||||
}).show();
|
||||
} finally {
|
||||
msg.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadAndSaveImage(url, fileName) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
$message({
|
||||
const msg = ElMessage({
|
||||
message: 'Downloading image...',
|
||||
type: 'info'
|
||||
});
|
||||
@@ -77,6 +118,8 @@
|
||||
type: 'error',
|
||||
text: escapeTag(`Failed to download image. ${url}`)
|
||||
}).show();
|
||||
} finally {
|
||||
msg.close();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="gallerySelectDialog.visible"
|
||||
v-model="gallerySelectDialog.visible"
|
||||
:title="t('dialog.gallery_select.header')"
|
||||
width="100%"
|
||||
append-to-body>
|
||||
@@ -16,16 +16,16 @@
|
||||
style="display: none"
|
||||
@change="onFileChangeGallery" />
|
||||
<el-button-group>
|
||||
<el-button type="default" size="small" icon="el-icon-close" @click="selectImageGallerySelect('', '')">{{
|
||||
<el-button type="default" size="small" :icon="Close" @click="selectImageGallerySelect('', '')">{{
|
||||
t('dialog.gallery_select.none')
|
||||
}}</el-button>
|
||||
<el-button type="default" size="small" icon="el-icon-refresh" @click="refreshGalleryTable">{{
|
||||
<el-button type="default" size="small" :icon="Refresh" @click="refreshGalleryTable">{{
|
||||
t('dialog.gallery_select.refresh')
|
||||
}}</el-button>
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
icon="el-icon-upload2"
|
||||
:icon="Upload"
|
||||
:disabled="!currentUser.$isVRCPlus"
|
||||
@click="displayGalleryUpload"
|
||||
>{{ t('dialog.gallery_select.upload') }}</el-button
|
||||
@@ -42,24 +42,28 @@
|
||||
v-if="image.versions[image.versions.length - 1].file.url"
|
||||
class="vrcplus-icon"
|
||||
@click="selectImageGallerySelect(image.versions[image.versions.length - 1].file.url, image.id)">
|
||||
<img v-lazy="image.versions[image.versions.length - 1].file.url" class="avatar" /></div
|
||||
<img
|
||||
:src="image.versions[image.versions.length - 1].file.url"
|
||||
class="avatar"
|
||||
loading="lazy" /></div
|
||||
></template>
|
||||
</div>
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { Close, Refresh, Upload } from '@element-plus/icons-vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { vrcPlusImageRequest } from '../../../api';
|
||||
import { useGalleryStore, useUserStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { $message } = proxy;
|
||||
const { galleryTable } = storeToRefs(useGalleryStore());
|
||||
const { refreshGalleryTable, handleGalleryImageAdd } = useGalleryStore();
|
||||
const { currentUser } = storeToRefs(useUserStore());
|
||||
@@ -95,7 +99,7 @@
|
||||
}
|
||||
if (files[0].size >= 100000000) {
|
||||
// 100MB
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.too_large'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -103,7 +107,7 @@
|
||||
return;
|
||||
}
|
||||
if (!files[0].type.match(/image.*/)) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.not_image'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -115,7 +119,7 @@
|
||||
const base64Body = btoa(r.result.toString());
|
||||
vrcPlusImageRequest.uploadGalleryImage(base64Body).then((args) => {
|
||||
handleGalleryImageAdd(args);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.gallery.uploaded'),
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="groupMemberModeration.visible"
|
||||
v-model="groupMemberModeration.visible"
|
||||
:title="t('dialog.group_member_moderation.header')"
|
||||
append-to-body
|
||||
width="90vw">
|
||||
@@ -12,8 +12,8 @@
|
||||
<div style="margin-top: 10px">
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
size="small"
|
||||
:icon="Refresh"
|
||||
:loading="isGroupMembersLoading"
|
||||
circle
|
||||
@click="loadAllGroupMembers" />
|
||||
@@ -34,21 +34,23 @@
|
||||
memberSearch.length ||
|
||||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')
|
||||
)
|
||||
"
|
||||
@click.native.stop>
|
||||
<el-button size="mini">
|
||||
">
|
||||
<el-button size="small" @click.stop>
|
||||
<span
|
||||
>{{ t(memberSortOrder.name) }} <i class="el-icon-arrow-down el-icon--right"></i
|
||||
></span>
|
||||
>{{ t(memberSortOrder.name) }}
|
||||
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon>
|
||||
</span>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="item in groupDialogSortingOptions"
|
||||
:key="item.name"
|
||||
@click.native="setGroupMemberSortOrder(item)">
|
||||
{{ t(item.name) }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in groupDialogSortingOptions"
|
||||
:key="item.name"
|
||||
@click="setGroupMemberSortOrder(item)">
|
||||
{{ t(item.name) }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<span style="margin-right: 5px">{{ t('dialog.group.members.filter') }}</span>
|
||||
<el-dropdown
|
||||
@@ -61,33 +63,36 @@
|
||||
memberSearch.length ||
|
||||
!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')
|
||||
)
|
||||
"
|
||||
@click.native.stop>
|
||||
<el-button size="mini">
|
||||
">
|
||||
<el-button size="small" @click.stop>
|
||||
<span
|
||||
>{{ t(memberFilter.name) }} <i class="el-icon-arrow-down el-icon--right"></i
|
||||
>{{ t(memberFilter.name) }}
|
||||
<el-icon style="margin-left: 5px"><ArrowDown /></el-icon
|
||||
></span>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="item in groupDialogFilterOptions"
|
||||
:key="item.name"
|
||||
@click.native="setGroupMemberFilter(item)"
|
||||
v-text="t(item.name)"></el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-for="item in groupMemberModeration.groupRef.roles"
|
||||
:key="item.name"
|
||||
@click.native="setGroupMemberFilter(item)"
|
||||
><span v-if="!item.defaultRole">{{ t(item.name) }}</span></el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in groupDialogFilterOptions"
|
||||
:key="item.name"
|
||||
@click="setGroupMemberFilter(item)"
|
||||
v-text="t(item.name)"></el-dropdown-item>
|
||||
<template v-for="role in groupMemberModeration.groupRef.roles" :key="role.name">
|
||||
<el-dropdown-item
|
||||
v-if="!role.defaultRole"
|
||||
@click="setGroupMemberFilter(role)">
|
||||
{{ t(role.name) }}
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="memberSearch"
|
||||
:disabled="!hasGroupPermission(groupMemberModeration.groupRef, 'group-bans-manage')"
|
||||
clearable
|
||||
size="mini"
|
||||
size="small"
|
||||
:placeholder="t('dialog.group.members.search')"
|
||||
style="margin-top: 10px; margin-bottom: 10px"
|
||||
@input="groupMembersSearch"></el-input>
|
||||
@@ -95,13 +100,13 @@
|
||||
<el-button size="small" @click="selectAllGroupMembers">{{
|
||||
t('dialog.group_member_moderation.select_all')
|
||||
}}</el-button>
|
||||
<data-tables
|
||||
<DataTable
|
||||
v-if="groupMemberModerationTable.data.length"
|
||||
v-bind="groupMemberModerationTable"
|
||||
style="margin-top: 10px">
|
||||
<el-table-column width="55" prop="$selected">
|
||||
<template #default="scope">
|
||||
<el-button type="text" size="mini" @click.stop>
|
||||
<el-button type="text" size="small" @click.stop>
|
||||
<el-checkbox
|
||||
v-model="scope.row.$selected"
|
||||
@change="
|
||||
@@ -115,16 +120,19 @@
|
||||
width="70"
|
||||
prop="photo">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" height="500px" trigger="hover">
|
||||
<el-popover placement="right" :width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="userImage(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="userImage(scope.row.user)"
|
||||
class="friends-list-avatar" />
|
||||
<img
|
||||
v-lazy="userImageFull(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
style="height: 500px; cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
||||
:src="userImageFull(scope.row.user)"
|
||||
:class="['friends-list-avatar', 'x-popover-image']"
|
||||
style="cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -145,9 +153,11 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('dialog.group_member_moderation.roles')" prop="roleIds" sortable>
|
||||
<template #default="scope">
|
||||
<template v-for="(roleId, index) in scope.row.roleIds">
|
||||
<template v-for="(role, rIndex) in groupMemberModeration.groupRef.roles">
|
||||
<span v-if="role?.id === roleId" :key="roleId + rIndex"
|
||||
<template v-for="(roleId, index) in scope.row.roleIds" :key="roleId">
|
||||
<template
|
||||
v-for="(role, rIndex) in groupMemberModeration.groupRef.roles"
|
||||
:key="roleId + rIndex">
|
||||
<span v-if="role?.id === roleId"
|
||||
>{{ role.name
|
||||
}}<span v-if="index < scope.row.roleIds.length - 1">, </span></span
|
||||
></template
|
||||
@@ -181,7 +191,7 @@
|
||||
<span v-text="scope.row.visibility"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -191,8 +201,8 @@
|
||||
<div style="margin-top: 10px">
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
size="small"
|
||||
:icon="Refresh"
|
||||
:loading="isGroupMembersLoading"
|
||||
circle
|
||||
@click="getAllGroupBans(groupMemberModeration.id)"></el-button>
|
||||
@@ -203,17 +213,17 @@
|
||||
<el-input
|
||||
v-model="groupBansModerationTable.filters[0].value"
|
||||
clearable
|
||||
size="mini"
|
||||
size="small"
|
||||
:placeholder="t('dialog.group.members.search')"
|
||||
style="margin-top: 10px; margin-bottom: 10px"></el-input>
|
||||
<br />
|
||||
<el-button size="small" @click="selectAllGroupBans">{{
|
||||
t('dialog.group_member_moderation.select_all')
|
||||
}}</el-button>
|
||||
<data-tables v-bind="groupBansModerationTable" style="margin-top: 10px">
|
||||
<DataTable v-bind="groupBansModerationTable" style="margin-top: 10px">
|
||||
<el-table-column width="55" prop="$selected">
|
||||
<template #default="scope">
|
||||
<el-button type="text" size="mini" @click.stop>
|
||||
<el-button type="text" size="small" @click.stop>
|
||||
<el-checkbox
|
||||
v-model="scope.row.$selected"
|
||||
@change="
|
||||
@@ -227,16 +237,19 @@
|
||||
width="70"
|
||||
prop="photo">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" height="500px" trigger="hover">
|
||||
<el-popover placement="right" :width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="userImage(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="userImage(scope.row.user)"
|
||||
class="friends-list-avatar" />
|
||||
<img
|
||||
v-lazy="userImageFull(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
style="height: 500px; cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
||||
:src="userImageFull(scope.row.user)"
|
||||
:class="['friends-list-avatar', 'x-popover-image']"
|
||||
style="cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -257,16 +270,14 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('dialog.group_member_moderation.roles')" prop="roleIds" sortable>
|
||||
<template #default="scope">
|
||||
<template v-for="(roleId, index) in scope.row.roleIds">
|
||||
<template v-for="(roleId, index) in scope.row.roleIds" :key="roleId">
|
||||
<span
|
||||
v-for="(role, rIndex) in groupMemberModeration.groupRef.roles"
|
||||
v-if="role.id === roleId"
|
||||
:key="rIndex + roleId"
|
||||
>{{ role.name }}</span
|
||||
>
|
||||
<span v-if="index < scope.row.roleIds.length - 1" :key="index + roleId"
|
||||
>,
|
||||
</span>
|
||||
<span v-if="index < scope.row.roleIds.length - 1">, </span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -296,7 +307,7 @@
|
||||
<span>{{ formatDateFilter(scope.row.bannedAt, 'long') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -306,29 +317,29 @@
|
||||
<div style="margin-top: 10px">
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
size="small"
|
||||
:icon="Refresh"
|
||||
:loading="isGroupMembersLoading"
|
||||
circle
|
||||
@click="getAllGroupInvitesAndJoinRequests(groupMemberModeration.id)"></el-button>
|
||||
<br />
|
||||
<el-tabs>
|
||||
<el-tab-pane>
|
||||
<span slot="label">
|
||||
<template #label>
|
||||
<span style="font-weight: bold; font-size: 16px">{{
|
||||
t('dialog.group_member_moderation.sent_invites')
|
||||
}}</span>
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
|
||||
groupInvitesModerationTable.data.length
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
<el-button size="small" @click="selectAllGroupInvites">{{
|
||||
t('dialog.group_member_moderation.select_all')
|
||||
}}</el-button>
|
||||
<data-tables v-bind="groupInvitesModerationTable" style="margin-top: 10px">
|
||||
<DataTable v-bind="groupInvitesModerationTable" style="margin-top: 10px">
|
||||
<el-table-column width="55" prop="$selected">
|
||||
<template #default="scope">
|
||||
<el-button type="text" size="mini" @click.stop>
|
||||
<el-button type="text" size="small" @click.stop>
|
||||
<el-checkbox
|
||||
v-model="scope.row.$selected"
|
||||
@change="
|
||||
@@ -342,16 +353,19 @@
|
||||
width="70"
|
||||
prop="photo">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" height="500px" trigger="hover">
|
||||
<el-popover placement="right" :width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="userImage(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="userImage(scope.row.user)"
|
||||
class="friends-list-avatar" />
|
||||
<img
|
||||
v-lazy="userImageFull(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
style="height: 500px; cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
||||
:src="userImageFull(scope.row.user)"
|
||||
:class="['friends-list-avatar', 'x-popover-image']"
|
||||
style="cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -378,7 +392,7 @@
|
||||
<span @click.stop v-text="scope.row.managerNotes"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
<br />
|
||||
<el-button
|
||||
:disabled="
|
||||
@@ -396,21 +410,21 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane>
|
||||
<span slot="label">
|
||||
<template #label>
|
||||
<span style="font-weight: bold; font-size: 16px">{{
|
||||
t('dialog.group_member_moderation.join_requests')
|
||||
}}</span>
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
|
||||
groupJoinRequestsModerationTable.data.length
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
<el-button size="small" @click="selectAllGroupJoinRequests">{{
|
||||
t('dialog.group_member_moderation.select_all')
|
||||
}}</el-button>
|
||||
<data-tables v-bind="groupJoinRequestsModerationTable" style="margin-top: 10px">
|
||||
<DataTable v-bind="groupJoinRequestsModerationTable" style="margin-top: 10px">
|
||||
<el-table-column width="55" prop="$selected">
|
||||
<template #default="scope">
|
||||
<el-button type="text" size="mini" @click.stop>
|
||||
<el-button type="text" size="small" @click.stop>
|
||||
<el-checkbox
|
||||
v-model="scope.row.$selected"
|
||||
@change="
|
||||
@@ -424,16 +438,19 @@
|
||||
width="70"
|
||||
prop="photo">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" height="500px" trigger="hover">
|
||||
<el-popover placement="right" :width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="userImage(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="userImage(scope.row.user)"
|
||||
class="friends-list-avatar" />
|
||||
<img
|
||||
v-lazy="userImageFull(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
style="height: 500px; cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
||||
:src="userImageFull(scope.row.user)"
|
||||
:class="['friends-list-avatar', 'x-popover-image']"
|
||||
style="cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -460,7 +477,7 @@
|
||||
<span @click.stop v-text="scope.row.managerNotes"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
<br />
|
||||
<el-button
|
||||
:disabled="
|
||||
@@ -504,21 +521,21 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane>
|
||||
<span slot="label">
|
||||
<template #label>
|
||||
<span style="font-weight: bold; font-size: 16px">{{
|
||||
t('dialog.group_member_moderation.blocked_requests')
|
||||
}}</span>
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
|
||||
groupBlockedModerationTable.data.length
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
<el-button size="small" @click="selectAllGroupBlocked">{{
|
||||
t('dialog.group_member_moderation.select_all')
|
||||
}}</el-button>
|
||||
<data-tables v-bind="groupBlockedModerationTable" style="margin-top: 10px">
|
||||
<DataTable v-bind="groupBlockedModerationTable" style="margin-top: 10px">
|
||||
<el-table-column width="55" prop="$selected">
|
||||
<template #default="scope">
|
||||
<el-button type="text" size="mini" @click.stop>
|
||||
<el-button type="text" size="small" @click.stop>
|
||||
<el-checkbox
|
||||
v-model="scope.row.$selected"
|
||||
@change="
|
||||
@@ -532,16 +549,19 @@
|
||||
width="70"
|
||||
prop="photo">
|
||||
<template #default="scope">
|
||||
<el-popover placement="right" height="500px" trigger="hover">
|
||||
<el-popover placement="right" :width="500" trigger="hover">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="userImage(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="userImage(scope.row.user)"
|
||||
class="friends-list-avatar" />
|
||||
<img
|
||||
v-lazy="userImageFull(scope.row.user)"
|
||||
class="friends-list-avatar"
|
||||
style="height: 500px; cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
||||
:src="userImageFull(scope.row.user)"
|
||||
:class="['friends-list-avatar', 'x-popover-image']"
|
||||
style="cursor: pointer"
|
||||
@click="showFullscreenImageDialog(userImageFull(scope.row.user))"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -568,7 +588,7 @@
|
||||
<span @click.stop v-text="scope.row.managerNotes"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
<br />
|
||||
<el-button
|
||||
:disabled="
|
||||
@@ -594,8 +614,8 @@
|
||||
<div style="margin-top: 10px">
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
size="small"
|
||||
:icon="Refresh"
|
||||
:loading="isGroupMembersLoading"
|
||||
circle
|
||||
@click="getAllGroupLogs(groupMemberModeration.id)"></el-button>
|
||||
@@ -637,7 +657,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<data-tables v-bind="groupLogsModerationTable" style="margin-top: 10px">
|
||||
<DataTable v-bind="groupLogsModerationTable" style="margin-top: 10px">
|
||||
<el-table-column
|
||||
:label="t('dialog.group_member_moderation.created_at')"
|
||||
width="170"
|
||||
@@ -684,7 +704,7 @@
|
||||
v-text="JSON.stringify(scope.row.data)"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
@@ -695,7 +715,7 @@
|
||||
<br />
|
||||
<el-input
|
||||
v-model="selectUserId"
|
||||
size="mini"
|
||||
size="small"
|
||||
style="margin-top: 5px; width: 340px"
|
||||
:placeholder="t('dialog.group_member_moderation.user_id_placeholder')"
|
||||
clearable></el-input>
|
||||
@@ -707,8 +727,8 @@
|
||||
<span class="name">{{ t('dialog.group_member_moderation.selected_users') }}</span>
|
||||
<el-button
|
||||
type="default"
|
||||
size="mini"
|
||||
icon="el-icon-delete"
|
||||
size="small"
|
||||
:icon="Delete"
|
||||
circle
|
||||
style="margin-left: 5px"
|
||||
@click="clearSelectedGroupMembers"></el-button>
|
||||
@@ -725,7 +745,7 @@
|
||||
<template #content>
|
||||
<span>{{ t('dialog.group_member_moderation.user_isnt_in_group') }}</span>
|
||||
</template>
|
||||
<i class="el-icon el-icon-warning" style="display: inline-block" />
|
||||
<el-icon style="margin-left: 3px; display: inline-block"><Warning /></el-icon>
|
||||
</el-tooltip>
|
||||
<span v-text="user.user?.displayName || user.userId" style="font-weight: bold; margin-left: 5px"></span>
|
||||
</el-tag>
|
||||
@@ -739,7 +759,7 @@
|
||||
:rows="2"
|
||||
:autosize="{ minRows: 1, maxRows: 20 }"
|
||||
:placeholder="t('dialog.group_member_moderation.note_placeholder')"
|
||||
size="mini"
|
||||
size="small"
|
||||
resize="none"
|
||||
style="margin-top: 5px"></el-input>
|
||||
<br />
|
||||
@@ -826,7 +846,7 @@
|
||||
>{{ t('dialog.group_member_moderation.unban') }}</el-button
|
||||
>
|
||||
<span v-if="progressCurrent" style="margin-top: 10px">
|
||||
<i class="el-icon-loading" style="margin-left: 5px; margin-right: 5px"></i>
|
||||
<el-icon class="is-loading" style="margin-left: 5px; margin-right: 5px"><Loading /></el-icon>
|
||||
{{ t('dialog.group_member_moderation.progress') }} {{ progressCurrent }}/{{ progressTotal }}
|
||||
</span>
|
||||
<el-button v-if="progressCurrent" style="margin-left: 5px" @click="progressTotal = 0">{{
|
||||
@@ -834,15 +854,18 @@
|
||||
}}</el-button>
|
||||
</div>
|
||||
<group-member-moderation-export-dialog
|
||||
:is-group-logs-export-dialog-visible.sync="isGroupLogsExportDialogVisible"
|
||||
:is-group-logs-export-dialog-visible="isGroupLogsExportDialogVisible"
|
||||
:group-logs-moderation-table="groupLogsModerationTable" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Refresh, Delete, ArrowDown, Warning, Loading } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import * as workerTimers from 'worker-timers';
|
||||
import { groupRequest, userRequest } from '../../../api';
|
||||
import { groupDialogFilterOptions, groupDialogSortingOptions } from '../../../shared/constants';
|
||||
@@ -857,9 +880,6 @@
|
||||
const { applyGroupMember, handleGroupMember, handleGroupMemberProps } = useGroupStore();
|
||||
const { showFullscreenImageDialog } = useGalleryStore();
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const selectedUsers = reactive({});
|
||||
const selectedUsersArray = ref([]);
|
||||
const isGroupMembersLoading = ref(false);
|
||||
@@ -880,7 +900,7 @@
|
||||
n: 100,
|
||||
offset: 0,
|
||||
groupId: '',
|
||||
sort: '',
|
||||
sort: 'joinedAt:desc',
|
||||
roleId: ''
|
||||
});
|
||||
|
||||
@@ -917,7 +937,7 @@
|
||||
|
||||
const groupInvitesModerationTable = reactive({
|
||||
data: [],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -927,7 +947,7 @@
|
||||
});
|
||||
const groupJoinRequestsModerationTable = reactive({
|
||||
data: [],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -937,7 +957,7 @@
|
||||
});
|
||||
const groupBlockedModerationTable = reactive({
|
||||
data: [],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -948,7 +968,7 @@
|
||||
const groupLogsModerationTable = reactive({
|
||||
data: [],
|
||||
filters: [{ prop: ['description'], value: '' }],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -959,7 +979,7 @@
|
||||
const groupBansModerationTable = reactive({
|
||||
data: [],
|
||||
filters: [{ prop: ['$displayName'], value: '' }],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -969,7 +989,7 @@
|
||||
});
|
||||
const groupMemberModerationTable = reactive({
|
||||
data: [],
|
||||
tableProps: { stripe: true, size: 'mini' },
|
||||
tableProps: { stripe: true, size: 'small' },
|
||||
pageSize: 15,
|
||||
paginationProps: {
|
||||
small: true,
|
||||
@@ -1084,7 +1104,7 @@
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to delete group invites: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1092,7 +1112,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Deleted ${memberCount} group invites`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1136,7 +1156,7 @@
|
||||
}
|
||||
groupBansModerationTable.data = fetchedBans;
|
||||
} catch {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Failed to get group bans',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1167,13 +1187,13 @@
|
||||
await groupRequest.banGroupMember({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to ban group member: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
$message({ message: `Banned ${memberCount} group members`, type: 'success' });
|
||||
ElMessage({ message: `Banned ${memberCount} group members`, type: 'success' });
|
||||
progressCurrent.value = 0;
|
||||
progressTotal.value = 0;
|
||||
getAllGroupBans(D.id);
|
||||
@@ -1196,7 +1216,7 @@
|
||||
await groupRequest.unbanGroupMember({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to unban group member: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1205,7 +1225,7 @@
|
||||
}
|
||||
|
||||
if (allSuccess) {
|
||||
$message({ message: `Unbanned ${memberCount} group members`, type: 'success' });
|
||||
ElMessage({ message: `Unbanned ${memberCount} group members`, type: 'success' });
|
||||
}
|
||||
progressCurrent.value = 0;
|
||||
progressTotal.value = 0;
|
||||
@@ -1230,7 +1250,7 @@
|
||||
await groupRequest.kickGroupMember({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to kick group member: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1238,7 +1258,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({ message: `Kicked ${memberCount} group members`, type: 'success' });
|
||||
ElMessage({ message: `Kicked ${memberCount} group members`, type: 'success' });
|
||||
}
|
||||
progressCurrent.value = 0;
|
||||
progressTotal.value = 0;
|
||||
@@ -1266,7 +1286,7 @@
|
||||
handleGroupMemberProps(args);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to set group member note for ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1274,7 +1294,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({ message: `Saved notes for ${memberCount} group members`, type: 'success' });
|
||||
ElMessage({ message: `Saved notes for ${memberCount} group members`, type: 'success' });
|
||||
}
|
||||
progressCurrent.value = 0;
|
||||
progressTotal.value = 0;
|
||||
@@ -1314,7 +1334,7 @@
|
||||
handleGroupMemberRoleChange(args);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to remove group member roles: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1323,7 +1343,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Roles removed`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1362,7 +1382,7 @@
|
||||
handleGroupMemberRoleChange(args);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to add group member roles: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1371,7 +1391,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Added group member roles`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1466,7 +1486,7 @@
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Failed to get group logs',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1496,7 +1516,7 @@
|
||||
await groupRequest.deleteBlockedGroupRequest({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to delete blocked group requests: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1504,7 +1524,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Deleted ${memberCount} blocked group requests`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1532,7 +1552,7 @@
|
||||
await groupRequest.blockGroupInviteRequest({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to block group join requests: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1540,7 +1560,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Blocked ${memberCount} group join requests`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1569,7 +1589,7 @@
|
||||
await groupRequest.rejectGroupInviteRequest({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to reject group join requests: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1577,7 +1597,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Rejected ${memberCount} group join requests`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1605,7 +1625,7 @@
|
||||
await groupRequest.acceptGroupInviteRequest({ groupId: D.id, userId: user.userId });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Failed to accept group join requests: ${err}`,
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1613,7 +1633,7 @@
|
||||
}
|
||||
}
|
||||
if (allSuccess) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: `Accepted ${memberCount} group join requests`,
|
||||
type: 'success'
|
||||
});
|
||||
@@ -1661,7 +1681,7 @@
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Failed to get group join requests',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1694,7 +1714,7 @@
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Failed to get group join requests',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1731,7 +1751,7 @@
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Failed to get group invites',
|
||||
type: 'error'
|
||||
});
|
||||
@@ -1823,7 +1843,7 @@
|
||||
members.value = [];
|
||||
isGroupMembersDone.value = false;
|
||||
loadMoreGroupMembersParams.value = {
|
||||
sort: '',
|
||||
sort: 'joinedAt:desc',
|
||||
roleId: '',
|
||||
n: 100,
|
||||
offset: 0,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="isGroupLogsExportDialogVisible"
|
||||
:model-value="isGroupLogsExportDialogVisible"
|
||||
:title="t('dialog.group_member_moderation.export_logs')"
|
||||
width="650px"
|
||||
append-to-body
|
||||
@@ -10,8 +10,8 @@
|
||||
v-model="checkedGroupLogsExportLogsOptions"
|
||||
style="margin-bottom: 10px"
|
||||
@change="updateGroupLogsExportContent">
|
||||
<template v-for="option in checkGroupsLogsExportLogsOptions">
|
||||
<el-checkbox :key="option.label" :label="option.label">
|
||||
<template v-for="option in checkGroupsLogsExportLogsOptions" :key="option.label">
|
||||
<el-checkbox :label="option.label">
|
||||
{{ t(option.text) }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
@@ -20,18 +20,18 @@
|
||||
<el-input
|
||||
v-model="groupLogsExportContent"
|
||||
type="textarea"
|
||||
size="mini"
|
||||
rows="15"
|
||||
size="small"
|
||||
:rows="15"
|
||||
resize="none"
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
@click.native="handleCopyGroupLogsExportContent" />
|
||||
</safe-dialog>
|
||||
@click="handleCopyGroupLogsExportContent" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { copyToClipboard } from '../../../shared/utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
:visible.sync="groupPostEditDialog.visible"
|
||||
<el-dialog
|
||||
v-model="groupPostEditDialog.visible"
|
||||
:title="t('dialog.group_post_edit.header')"
|
||||
width="650px"
|
||||
append-to-body>
|
||||
@@ -8,7 +8,7 @@
|
||||
<h3 v-text="groupPostEditDialog.groupRef.name"></h3>
|
||||
<el-form :model="groupPostEditDialog" label-width="150px">
|
||||
<el-form-item :label="t('dialog.group_post_edit.title')">
|
||||
<el-input v-model="groupPostEditDialog.title" size="mini"></el-input>
|
||||
<el-input v-model="groupPostEditDialog.title" size="small"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.group_post_edit.message')">
|
||||
<el-input
|
||||
@@ -61,29 +61,32 @@
|
||||
<el-form-item :label="t('dialog.group_post_edit.image')">
|
||||
<template v-if="gallerySelectDialog.selectedFileId">
|
||||
<div style="display: inline-block; flex: none; margin-right: 5px">
|
||||
<el-popover placement="right" width="500px" trigger="click">
|
||||
<el-popover placement="right" :width="500" trigger="click">
|
||||
<template #reference>
|
||||
<img
|
||||
:src="gallerySelectDialog.selectedImageUrl"
|
||||
style="
|
||||
flex: none;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
"
|
||||
loading="lazy" />
|
||||
</template>
|
||||
<img
|
||||
slot="reference"
|
||||
v-lazy="gallerySelectDialog.selectedImageUrl"
|
||||
style="
|
||||
flex: none;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
" />
|
||||
<img
|
||||
v-lazy="gallerySelectDialog.selectedImageUrl"
|
||||
style="height: 500px"
|
||||
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" />
|
||||
:src="gallerySelectDialog.selectedImageUrl"
|
||||
:class="['x-link', 'x-popover-image']"
|
||||
@click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)"
|
||||
loading="lazy" />
|
||||
</el-popover>
|
||||
<el-button size="mini" style="vertical-align: top" @click="clearImageGallerySelect">
|
||||
<el-button size="small" style="vertical-align: top" @click="clearImageGallerySelect">
|
||||
{{ t('dialog.invite_message.clear_selected_image') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button size="mini" style="margin-right: 5px" @click="showGallerySelectDialog">
|
||||
<el-button size="small" style="margin-right: 5px" @click="showGallerySelectDialog">
|
||||
{{ t('dialog.invite_message.select_image') }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -105,12 +108,13 @@
|
||||
:gallery-select-dialog="gallerySelectDialog"
|
||||
:gallery-table="galleryTable"
|
||||
@refresh-gallery-table="refreshGalleryTable" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { groupRequest, vrcPlusIconRequest } from '../../../api';
|
||||
import { useGalleryStore, useGroupStore } from '../../../stores';
|
||||
import GallerySelectDialog from './GallerySelectDialog.vue';
|
||||
@@ -125,7 +129,6 @@
|
||||
|
||||
const emit = defineEmits(['update:dialogData']);
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { showFullscreenImageDialog, handleFilesList } = useGalleryStore();
|
||||
@@ -169,7 +172,7 @@
|
||||
return;
|
||||
}
|
||||
if (!D.title || !D.text) {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Title and text are required',
|
||||
type: 'warning'
|
||||
});
|
||||
@@ -188,8 +191,8 @@
|
||||
params.imageId = gallerySelectDialog.value.selectedFileId;
|
||||
}
|
||||
groupRequest.editGroupPost(params).then((args) => {
|
||||
handleGroupPost();
|
||||
proxy.$message({
|
||||
handleGroupPost(args);
|
||||
ElMessage({
|
||||
message: 'Group post edited',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -200,7 +203,7 @@
|
||||
function createGroupPost() {
|
||||
const D = groupPostEditDialog.value;
|
||||
if (!D.title || !D.text) {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Title and text are required',
|
||||
type: 'warning'
|
||||
});
|
||||
@@ -220,7 +223,7 @@
|
||||
}
|
||||
groupRequest.createGroupPost(params).then((args) => {
|
||||
handleGroupPost();
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Group post created',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="editAndSendInviteDialog.visible"
|
||||
:model-value="editAndSendInviteDialog.visible"
|
||||
:title="t('dialog.edit_send_invite_message.header')"
|
||||
width="400px"
|
||||
append-to-body
|
||||
@@ -13,7 +13,7 @@
|
||||
<el-input
|
||||
v-model="editAndSendInviteDialog.newMessage"
|
||||
type="textarea"
|
||||
size="mini"
|
||||
size="small"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
@@ -21,28 +21,25 @@
|
||||
style="margin-top: 10px"></el-input>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="small" @click="cancelEditAndSendInvite">
|
||||
<el-button @click="cancelEditAndSendInvite">
|
||||
{{ t('dialog.edit_send_invite_message.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" @click="saveEditAndSendInvite">
|
||||
{{ t('dialog.edit_send_invite_message.send') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { instanceRequest, inviteMessagesRequest, notificationRequest } from '../../../api';
|
||||
import { parseLocation } from '../../../shared/utils';
|
||||
import { useGalleryStore, useUserStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const { uploadImage } = storeToRefs(useGalleryStore());
|
||||
const { clearInviteImageUpload } = useGalleryStore();
|
||||
const { currentUser } = storeToRefs(useUserStore());
|
||||
@@ -86,13 +83,13 @@
|
||||
})
|
||||
.then((args) => {
|
||||
if (args.json[slot].message === I.messageSlot.message) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: "VRChat API didn't update message, try again",
|
||||
type: 'error'
|
||||
});
|
||||
throw new Error("VRChat API didn't update message, try again");
|
||||
} else {
|
||||
$message('Invite message updated');
|
||||
ElMessage('Invite message updated');
|
||||
}
|
||||
return args;
|
||||
});
|
||||
@@ -139,7 +136,7 @@
|
||||
} else {
|
||||
J.loading = false;
|
||||
J.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -155,7 +152,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite photo message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -168,7 +165,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -185,7 +182,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Request invite photo message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -198,7 +195,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Request invite message sent',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="inviteDialog.visible"
|
||||
v-model="inviteDialog.visible"
|
||||
:title="t('dialog.invite.header')"
|
||||
width="500px"
|
||||
append-to-body>
|
||||
<div v-if="inviteDialog.visible" v-loading="inviteDialog.loading">
|
||||
<Location :location="inviteDialog.worldId" :link="false" />
|
||||
<br />
|
||||
<el-button size="mini" style="margin-top: 10px" @click="addSelfToInvite">{{
|
||||
<el-button size="small" style="margin-top: 10px" @click="addSelfToInvite">{{
|
||||
t('dialog.invite.add_self')
|
||||
}}</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
size="small"
|
||||
:disabled="inviteDialog.friendsInInstance.length === 0"
|
||||
style="margin-top: 10px"
|
||||
@click="addFriendsInInstanceToInvite"
|
||||
>{{ t('dialog.invite.add_friends_in_instance') }}</el-button
|
||||
>
|
||||
<el-button
|
||||
size="mini"
|
||||
size="small"
|
||||
:disabled="vipFriends.length === 0"
|
||||
style="margin-top: 10px"
|
||||
@click="addFavoriteFriendsToInvite"
|
||||
@@ -34,108 +34,116 @@
|
||||
filterable
|
||||
:disabled="inviteDialog.loading"
|
||||
style="width: 100%; margin-top: 15px">
|
||||
<el-option-group v-if="currentUser" :label="t('side_panel.me')">
|
||||
<el-option
|
||||
class="x-friend-item"
|
||||
:label="currentUser.displayName"
|
||||
:value="currentUser.id"
|
||||
style="height: auto">
|
||||
<div :class="['avatar', userStatusClass(currentUser)]">
|
||||
<img v-lazy="userImage(currentUser)" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name">{{ currentUser.displayName }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
|
||||
<el-option-group
|
||||
v-if="inviteDialog.friendsInInstance.length"
|
||||
:label="t('dialog.invite.friends_in_instance')">
|
||||
<el-option
|
||||
v-for="friend in inviteDialog.friendsInInstance"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<template v-if="currentUser">
|
||||
<el-option-group :label="t('side_panel.me')">
|
||||
<el-option
|
||||
class="x-friend-item"
|
||||
:label="currentUser.displayName"
|
||||
:value="currentUser.id"
|
||||
style="height: auto">
|
||||
<div :class="['avatar', userStatusClass(currentUser)]">
|
||||
<img :src="userImage(currentUser)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
<span class="name">{{ currentUser.displayName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</template>
|
||||
|
||||
<el-option-group v-if="vipFriends.length" :label="t('side_panel.favorite')">
|
||||
<el-option
|
||||
v-for="friend in vipFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<template v-if="inviteDialog.friendsInInstance.length">
|
||||
<el-option-group :label="t('dialog.invite.friends_in_instance')">
|
||||
<el-option
|
||||
v-for="friend in inviteDialog.friendsInInstance"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</template>
|
||||
|
||||
<el-option-group v-if="onlineFriends.length" :label="t('side_panel.online')">
|
||||
<el-option
|
||||
v-for="friend in onlineFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<template v-if="vipFriends.length">
|
||||
<el-option-group :label="t('side_panel.favorite')">
|
||||
<el-option
|
||||
v-for="friend in vipFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</template>
|
||||
|
||||
<el-option-group v-if="activeFriends.length" :label="t('side_panel.active')">
|
||||
<el-option
|
||||
v-for="friend in activeFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar"><img v-lazy="userImage(friend.ref)" /></div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<template v-if="onlineFriends.length">
|
||||
<el-option-group :label="t('side_panel.online')">
|
||||
<el-option
|
||||
v-for="friend in onlineFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</template>
|
||||
|
||||
<template v-if="activeFriends.length">
|
||||
<el-option-group :label="t('side_panel.active')">
|
||||
<el-option
|
||||
v-for="friend in activeFriends"
|
||||
:key="friend.id"
|
||||
class="x-friend-item"
|
||||
:label="friend.name"
|
||||
:value="friend.id"
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar"><img :src="userImage(friend.ref)" loading="lazy" /></div>
|
||||
<div class="detail">
|
||||
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||
friend.ref.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>{{ friend.id }}</span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</template>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
@@ -155,17 +163,19 @@
|
||||
>
|
||||
</template>
|
||||
<SendInviteDialog
|
||||
:send-invite-dialog-visible.sync="sendInviteDialogVisible"
|
||||
:send-invite-dialog-visible="sendInviteDialogVisible"
|
||||
:send-invite-dialog="sendInviteDialog"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { instanceRequest, notificationRequest } from '../../../api';
|
||||
import { parseLocation, userImage, userStatusClass } from '../../../shared/utils';
|
||||
import { useFriendStore, useGalleryStore, useInviteStore, useUserStore } from '../../../stores';
|
||||
@@ -177,10 +187,6 @@
|
||||
const { clearInviteImageUpload } = useGalleryStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
const $confirm = instance.proxy.$confirm;
|
||||
|
||||
const props = defineProps({
|
||||
inviteDialog: {
|
||||
type: Object,
|
||||
@@ -238,11 +244,12 @@
|
||||
}
|
||||
|
||||
function sendInvite() {
|
||||
$confirm('Continue? Invite', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Invite', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
const D = props.inviteDialog;
|
||||
if (action !== 'confirm' || D.loading === true) {
|
||||
return;
|
||||
@@ -275,14 +282,14 @@
|
||||
} else {
|
||||
D.loading = false;
|
||||
D.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite sent',
|
||||
type: 'success'
|
||||
});
|
||||
}
|
||||
};
|
||||
inviteLoop();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="visible"
|
||||
:model-value="visible"
|
||||
:title="t('dialog.invite_message.header')"
|
||||
width="400px"
|
||||
append-to-body
|
||||
@@ -18,22 +18,19 @@
|
||||
{{ t('dialog.invite_message.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { instanceRequest, notificationRequest } from '../../../api';
|
||||
import { parseLocation } from '../../../shared/utils';
|
||||
import { useGalleryStore, useUserStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const { uploadImage } = storeToRefs(useGalleryStore());
|
||||
const { clearInviteImageUpload } = useGalleryStore();
|
||||
const { currentUser } = storeToRefs(useUserStore());
|
||||
@@ -54,10 +51,10 @@
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:visible', 'closeInviteDialog']);
|
||||
const emit = defineEmits(['update:model-value', 'closeInviteDialog']);
|
||||
|
||||
function cancelInviteConfirm() {
|
||||
emit('update:visible', false);
|
||||
emit('update:model-value', false);
|
||||
}
|
||||
|
||||
function sendInviteConfirm() {
|
||||
@@ -106,7 +103,7 @@
|
||||
} else {
|
||||
J.loading = false;
|
||||
J.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -122,7 +119,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite photo message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -135,7 +132,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Invite message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -152,7 +149,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Request invite photo message sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -165,7 +162,7 @@
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Request invite message sent',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="sendInviteDialogVisible"
|
||||
:model-value="sendInviteDialogVisible"
|
||||
:title="t('dialog.invite_message.header')"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@@ -9,46 +9,46 @@
|
||||
<template v-if="currentUser.$isVRCPlus">
|
||||
<!-- <template v-if="gallerySelectDialog.selectedFileId">-->
|
||||
<!-- <div style="display: inline-block; flex: none; margin-right: 5px">-->
|
||||
<!-- <el-popover placement="right" width="500px" trigger="click">-->
|
||||
<!-- <el-popover placement="right" :width="500px" trigger="click">-->
|
||||
<!-- <template #reference>-->
|
||||
<!-- <img-->
|
||||
<!-- class="x-link"-->
|
||||
<!-- v-lazy="gallerySelectDialog.selectedImageUrl"-->
|
||||
<!-- :src="gallerySelectDialog.selectedImageUrl"-->
|
||||
<!-- style="flex: none; width: 60px; height: 60px; border-radius: 4px; object-fit: cover" />-->
|
||||
<!-- </template>-->
|
||||
<!-- <img-->
|
||||
<!-- class="x-link"-->
|
||||
<!-- v-lazy="gallerySelectDialog.selectedImageUrl"-->
|
||||
<!-- :src="gallerySelectDialog.selectedImageUrl"-->
|
||||
<!-- style="height: 500px"-->
|
||||
<!-- @click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" />-->
|
||||
<!-- </el-popover>-->
|
||||
<!-- </div>-->
|
||||
<!-- <el-button size="mini" @click="clearImageGallerySelect" style="vertical-align: top">-->
|
||||
<!-- <el-button size="small" @click="clearImageGallerySelect" style="vertical-align: top">-->
|
||||
<!-- {{ t('dialog.invite_message.clear_selected_image') }}-->
|
||||
<!-- </el-button>-->
|
||||
<!-- </template>-->
|
||||
<!-- <template v-else>-->
|
||||
<!-- <el-button size="mini" @click="showGallerySelectDialog" style="margin-right: 5px">-->
|
||||
<!-- <el-button size="small" @click="showGallerySelectDialog" style="margin-right: 5px">-->
|
||||
<!-- {{ t('dialog.invite_message.select_image') }}-->
|
||||
<!-- </el-button>-->
|
||||
<!-- </template>-->
|
||||
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||
</template>
|
||||
|
||||
<data-tables
|
||||
<DataTable
|
||||
v-bind="inviteMessageTable"
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
@row-click="showSendInviteConfirmDialog">
|
||||
<el-table-column
|
||||
:label="t('table.profile.invite_messages.slot')"
|
||||
prop="slot"
|
||||
sortable="custom"
|
||||
:sortable="true"
|
||||
width="70"></el-table-column>
|
||||
<el-table-column :label="t('table.profile.invite_messages.message')" prop="message"></el-table-column>
|
||||
<el-table-column
|
||||
:label="t('table.profile.invite_messages.cool_down')"
|
||||
prop="updatedAt"
|
||||
sortable="custom"
|
||||
:sortable="true"
|
||||
width="110"
|
||||
align="right">
|
||||
<template #default="scope">
|
||||
@@ -59,38 +59,40 @@
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:icon="Edit"
|
||||
size="small"
|
||||
@click.stop="showEditAndSendInviteDialog(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="small" @click="cancelSendInvite">
|
||||
<el-button @click="cancelSendInvite">
|
||||
{{ t('dialog.invite_message.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="small" @click="refreshInviteMessageTableData('message')">
|
||||
<el-button @click="refreshInviteMessageTableData('message')">
|
||||
{{ t('dialog.invite_message.refresh') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<SendInviteConfirmDialog
|
||||
:visible.sync="isSendInviteConfirmDialogVisible"
|
||||
v-model="isSendInviteConfirmDialogVisible"
|
||||
:send-invite-dialog="sendInviteDialog"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
<EditAndSendInviteDialog
|
||||
:edit-and-send-invite-dialog.sync="editAndSendInviteDialog"
|
||||
:edit-and-send-invite-dialog="editAndSendInviteDialog"
|
||||
:send-invite-dialog="sendInviteDialog"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Edit } from '@element-plus/icons-vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useGalleryStore, useInviteStore, useUserStore } from '../../../stores';
|
||||
import EditAndSendInviteDialog from './EditAndSendInviteDialog.vue';
|
||||
import SendInviteConfirmDialog from './SendInviteConfirmDialog.vue';
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="inviteGroupDialogRef"
|
||||
:visible.sync="inviteGroupDialog.visible"
|
||||
:title="$t('dialog.invite_to_group.header')"
|
||||
<el-dialog
|
||||
:z-index="inviteGroupDialogIndex"
|
||||
v-model="inviteGroupDialog.visible"
|
||||
:title="t('dialog.invite_to_group.header')"
|
||||
width="450px"
|
||||
append-to-body>
|
||||
<div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading">
|
||||
<span>{{ $t('dialog.invite_to_group.description') }}</span>
|
||||
<span>{{ t('dialog.invite_to_group.description') }}</span>
|
||||
<br />
|
||||
<el-select
|
||||
v-model="inviteGroupDialog.groupId"
|
||||
clearable
|
||||
:placeholder="$t('dialog.invite_to_group.choose_group_placeholder')"
|
||||
:placeholder="t('dialog.invite_to_group.choose_group_placeholder')"
|
||||
filterable
|
||||
:disabled="inviteGroupDialog.loading"
|
||||
style="margin-top: 15px; width: 100%">
|
||||
<el-option-group
|
||||
:label="$t('dialog.invite_to_group.groups_with_invite_permission')"
|
||||
:label="t('dialog.invite_to_group.groups_with_invite_permission')"
|
||||
style="width: 410px">
|
||||
<el-option
|
||||
v-for="group in groupsWithInvitePermission"
|
||||
@@ -26,7 +26,7 @@
|
||||
style="height: auto"
|
||||
class="x-friend-item">
|
||||
<div class="avatar">
|
||||
<img v-lazy="group.iconUrl" />
|
||||
<img :src="group.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="group.name"></span>
|
||||
@@ -38,11 +38,11 @@
|
||||
v-model="inviteGroupDialog.userIds"
|
||||
multiple
|
||||
clearable
|
||||
:placeholder="$t('dialog.invite_to_group.choose_friends_placeholder')"
|
||||
:placeholder="t('dialog.invite_to_group.choose_friends_placeholder')"
|
||||
filterable
|
||||
:disabled="inviteGroupDialog.loading"
|
||||
style="width: 100%; margin-top: 15px">
|
||||
<el-option-group v-if="inviteGroupDialog.userId" :label="$t('dialog.invite_to_group.selected_users')">
|
||||
<el-option-group v-if="inviteGroupDialog.userId" :label="t('dialog.invite_to_group.selected_users')">
|
||||
<el-option
|
||||
:key="inviteGroupDialog.userObject.id"
|
||||
:label="inviteGroupDialog.userObject.displayName"
|
||||
@@ -50,7 +50,7 @@
|
||||
class="x-friend-item">
|
||||
<template v-if="inviteGroupDialog.userObject.id">
|
||||
<div class="avatar" :class="userStatusClass(inviteGroupDialog.userObject)">
|
||||
<img v-lazy="userImage(inviteGroupDialog.userObject)" />
|
||||
<img :src="userImage(inviteGroupDialog.userObject)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -62,7 +62,7 @@
|
||||
<span v-else v-text="inviteGroupDialog.userId"></span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<el-option-group v-if="vipFriends.length" :label="$t('side_panel.favorite')">
|
||||
<el-option-group v-if="vipFriends.length" :label="t('side_panel.favorite')">
|
||||
<el-option
|
||||
v-for="friend in vipFriends"
|
||||
:key="friend.id"
|
||||
@@ -72,7 +72,7 @@
|
||||
class="x-friend-item">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar" :class="userStatusClass(friend.ref)">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -84,7 +84,7 @@
|
||||
<span v-else v-text="friend.id"></span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<el-option-group v-if="onlineFriends.length" :label="$t('side_panel.online')">
|
||||
<el-option-group v-if="onlineFriends.length" :label="t('side_panel.online')">
|
||||
<el-option
|
||||
v-for="friend in onlineFriends"
|
||||
:key="friend.id"
|
||||
@@ -94,7 +94,7 @@
|
||||
class="x-friend-item">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar" :class="userStatusClass(friend.ref)">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -106,7 +106,7 @@
|
||||
<span v-else v-text="friend.id"></span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<el-option-group v-if="activeFriends.length" :label="$t('side_panel.active')">
|
||||
<el-option-group v-if="activeFriends.length" :label="t('side_panel.active')">
|
||||
<el-option
|
||||
v-for="friend in activeFriends"
|
||||
:key="friend.id"
|
||||
@@ -116,7 +116,7 @@
|
||||
class="x-friend-item">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -128,7 +128,7 @@
|
||||
<span v-else v-text="friend.id"></span>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
<el-option-group v-if="offlineFriends.length" :label="$t('side_panel.offline')">
|
||||
<el-option-group v-if="offlineFriends.length" :label="t('side_panel.offline')">
|
||||
<el-option
|
||||
v-for="friend in offlineFriends"
|
||||
:key="friend.id"
|
||||
@@ -138,7 +138,7 @@
|
||||
class="x-friend-item">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -158,24 +158,25 @@
|
||||
size="small"
|
||||
:disabled="inviteGroupDialog.loading || !inviteGroupDialog.userIds.length || !inviteGroupDialog.groupId"
|
||||
@click="sendGroupInvite">
|
||||
{{ $t('dialog.invite_to_group.invite') }}
|
||||
{{ t('dialog.invite_to_group.invite') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, getCurrentInstance, nextTick, computed } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { ref, watch, nextTick, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { groupRequest, userRequest } from '../../api';
|
||||
import { adjustDialogZ, hasGroupPermission, userImage, userStatusClass } from '../../shared/utils';
|
||||
import { getNextDialogIndex, hasGroupPermission, userImage, userStatusClass } from '../../shared/utils';
|
||||
import { useFriendStore, useGroupStore } from '../../stores';
|
||||
|
||||
const { vipFriends, onlineFriends, activeFriends, offlineFriends } = storeToRefs(useFriendStore());
|
||||
const { currentUserGroups, inviteGroupDialog } = storeToRefs(useGroupStore());
|
||||
const { applyGroup } = useGroupStore();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { t } = useI18n();
|
||||
|
||||
watch(
|
||||
() => {
|
||||
@@ -188,7 +189,7 @@
|
||||
}
|
||||
);
|
||||
|
||||
const inviteGroupDialogRef = ref(null);
|
||||
const inviteGroupDialogIndex = ref(2000);
|
||||
|
||||
const groupsWithInvitePermission = computed(() => {
|
||||
return Array.from(currentUserGroups.value.values()).filter((group) =>
|
||||
@@ -197,7 +198,9 @@
|
||||
});
|
||||
|
||||
function initDialog() {
|
||||
nextTick(() => adjustDialogZ(inviteGroupDialogRef.value.$el));
|
||||
nextTick(() => {
|
||||
inviteGroupDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = inviteGroupDialog.value;
|
||||
if (D.groupId) {
|
||||
groupRequest
|
||||
@@ -236,7 +239,7 @@
|
||||
}
|
||||
// not allowed to invite
|
||||
inviteGroupDialog.value.groupId = '';
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: 'You are not allowed to invite to this group'
|
||||
});
|
||||
@@ -247,11 +250,12 @@
|
||||
});
|
||||
}
|
||||
function sendGroupInvite() {
|
||||
proxy.$confirm('Continue? Invite User(s) To Group', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Invite User(s) To Group', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
const D = inviteGroupDialog.value;
|
||||
if (action !== 'confirm' || D.loading === true) {
|
||||
return;
|
||||
@@ -274,7 +278,7 @@
|
||||
});
|
||||
};
|
||||
inviteLoop();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
<template>
|
||||
<safe-dialog ref="launchDialogRef" :visible.sync="isVisible" :title="t('dialog.launch.header')" width="450px">
|
||||
<el-dialog :z-index="launchDialogIndex" v-model="isVisible" :title="t('dialog.launch.header')" width="450px">
|
||||
<el-form :model="launchDialog" label-width="100px">
|
||||
<el-form-item :label="t('dialog.launch.url')">
|
||||
<el-input
|
||||
v-model="launchDialog.url"
|
||||
size="mini"
|
||||
style="width: 260px"
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
|
||||
size="small"
|
||||
style="width: 230px"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-s-order"
|
||||
size="small"
|
||||
:icon="CopyDocument"
|
||||
style="margin-left: 5px"
|
||||
circle
|
||||
@click="copyInstanceMessage(launchDialog.url)" />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="launchDialog.shortUrl">
|
||||
<template slot="label">
|
||||
<template #label>
|
||||
<span>{{ t('dialog.launch.short_url') }}</span>
|
||||
<el-tooltip placement="top" style="margin-left: 5px" :content="t('dialog.launch.short_url_notice')">
|
||||
<i class="el-icon-warning" />
|
||||
<el-tooltip placement="top" :content="t('dialog.launch.short_url_notice')">
|
||||
<el-icon style="display: inline-block; margin-left: 5px"><Warning /></el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-input
|
||||
v-model="launchDialog.shortUrl"
|
||||
size="mini"
|
||||
style="width: 260px"
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
|
||||
size="small"
|
||||
style="width: 230px"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-s-order"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
:icon="CopyDocument"
|
||||
style="display: inline-block; margin-left: 5px"
|
||||
circle
|
||||
@click="copyInstanceMessage(launchDialog.shortUrl)" />
|
||||
</el-tooltip>
|
||||
@@ -40,23 +40,26 @@
|
||||
<el-form-item :label="t('dialog.launch.location')">
|
||||
<el-input
|
||||
v-model="launchDialog.location"
|
||||
size="mini"
|
||||
style="width: 260px"
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')" :disabled="hideTooltips">
|
||||
size="small"
|
||||
style="width: 230px"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()" />
|
||||
<el-tooltip placement="right" :content="t('dialog.launch.copy_tooltip')">
|
||||
<el-button
|
||||
size="mini"
|
||||
icon="el-icon-s-order"
|
||||
style="margin-left: 5px"
|
||||
size="small"
|
||||
:icon="CopyDocument"
|
||||
style="display: inline-block; margin-left: 5px"
|
||||
circle
|
||||
@click="copyInstanceMessage(launchDialog.location)" />
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-checkbox v-model="launchDialog.desktop" style="float: left; margin-top: 5px" @change="saveLaunchDialog">
|
||||
<el-checkbox
|
||||
v-model="launchDialog.desktop"
|
||||
style="display: inline-flex; align-items: center; margin-top: 5px"
|
||||
@change="saveLaunchDialog">
|
||||
{{ t('dialog.launch.start_as_desktop') }}
|
||||
</el-checkbox>
|
||||
<template slot="footer">
|
||||
<template #footer>
|
||||
<el-button size="small" @click="showPreviousInstancesInfoDialog(launchDialog.location)">
|
||||
{{ t('dialog.launch.info') }}
|
||||
</el-button>
|
||||
@@ -68,7 +71,6 @@
|
||||
</el-button>
|
||||
<template v-if="canOpenInstanceInGame()">
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
:disabled="!launchDialog.secureOrShortName"
|
||||
@click="handleLaunchGame(launchDialog.location, launchDialog.shortName, launchDialog.desktop)">
|
||||
@@ -84,7 +86,6 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
:disabled="!launchDialog.secureOrShortName"
|
||||
@click="selfInvite(launchDialog.location, launchDialog.shortName)">
|
||||
@@ -100,18 +101,26 @@
|
||||
</template>
|
||||
</template>
|
||||
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, nextTick, watch, getCurrentInstance } from 'vue';
|
||||
import { CopyDocument, Warning } from '@element-plus/icons-vue';
|
||||
|
||||
import { ref, computed, nextTick, watch } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { instanceRequest, worldRequest } from '../../api';
|
||||
import configRepository from '../../service/config';
|
||||
import { adjustDialogZ, checkCanInvite, getLaunchURL, isRealInstance, parseLocation } from '../../shared/utils';
|
||||
import {
|
||||
useAppearanceSettingsStore,
|
||||
getNextDialogIndex,
|
||||
checkCanInvite,
|
||||
getLaunchURL,
|
||||
isRealInstance,
|
||||
parseLocation
|
||||
} from '../../shared/utils';
|
||||
import {
|
||||
useFriendStore,
|
||||
useInviteStore,
|
||||
useInstanceStore,
|
||||
@@ -121,19 +130,18 @@
|
||||
} from '../../stores';
|
||||
import InviteDialog from './InviteDialog/InviteDialog.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { friends } = storeToRefs(useFriendStore());
|
||||
const { hideTooltips } = storeToRefs(useAppearanceSettingsStore());
|
||||
const { lastLocation } = storeToRefs(useLocationStore());
|
||||
const { launchGame, tryOpenInstanceInVrc } = useLaunchStore();
|
||||
const { launchDialogData } = storeToRefs(useLaunchStore());
|
||||
|
||||
const { showPreviousInstancesInfoDialog } = useInstanceStore();
|
||||
const { canOpenInstanceInGame } = useInviteStore();
|
||||
const { isGameRunning } = storeToRefs(useGameStore());
|
||||
|
||||
const launchDialogRef = ref(null);
|
||||
const launchDialogIndex = ref(2000);
|
||||
|
||||
const launchDialog = ref({
|
||||
loading: false,
|
||||
@@ -207,17 +215,18 @@
|
||||
}
|
||||
function handleLaunchGame(location, shortName, desktop) {
|
||||
if (isGameRunning.value) {
|
||||
proxy.$confirm(t('dialog.launch.game_running_warning'), t('dialog.launch.header'), {
|
||||
ElMessageBox.confirm(t('dialog.launch.game_running_warning'), t('dialog.launch.header'), {
|
||||
confirmButtonText: t('dialog.launch.confirm_yes'),
|
||||
cancelButtonText: t('dialog.launch.confirm_no'),
|
||||
type: 'warning',
|
||||
callback: (action) => {
|
||||
type: 'warning'
|
||||
})
|
||||
.then((action) => {
|
||||
if (action === 'confirm') {
|
||||
launchGame(location, shortName, desktop);
|
||||
isVisible.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
return;
|
||||
}
|
||||
launchGame(location, shortName, desktop);
|
||||
@@ -239,7 +248,7 @@
|
||||
shortName
|
||||
})
|
||||
.then((args) => {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Self invite sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -257,7 +266,9 @@
|
||||
if (!isRealInstance(tag)) {
|
||||
return;
|
||||
}
|
||||
nextTick(() => adjustDialogZ(launchDialogRef.value.$el));
|
||||
nextTick(() => {
|
||||
launchDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = launchDialog.value;
|
||||
D.tag = tag;
|
||||
D.secureOrShortName = shortName;
|
||||
@@ -300,12 +311,12 @@
|
||||
async function copyInstanceMessage(input) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(input);
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Instance copied to clipboard',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Instance copied failed',
|
||||
type: 'error'
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="moderateGroupDialogRef"
|
||||
:visible.sync="moderateGroupDialog.visible"
|
||||
:title="$t('dialog.moderate_group.header')"
|
||||
<el-dialog
|
||||
:z-index="moderateGroupDialogIndex"
|
||||
v-model="moderateGroupDialog.visible"
|
||||
:title="t('dialog.moderate_group.header')"
|
||||
width="450px"
|
||||
append-to-body>
|
||||
<div v-if="moderateGroupDialog.visible">
|
||||
<div class="x-friend-item" style="cursor: default">
|
||||
<div class="avatar">
|
||||
<img v-lazy="userImage(moderateGroupDialog.userObject)" />
|
||||
<img :src="userImage(moderateGroupDialog.userObject)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -22,12 +22,12 @@
|
||||
<el-select
|
||||
v-model="moderateGroupDialog.groupId"
|
||||
clearable
|
||||
:placeholder="$t('dialog.moderate_group.choose_group_placeholder')"
|
||||
:placeholder="t('dialog.moderate_group.choose_group_placeholder')"
|
||||
filterable
|
||||
style="margin-top: 15px; width: 100%">
|
||||
<el-option-group
|
||||
v-if="currentUserGroups.size"
|
||||
:label="$t('dialog.moderate_group.groups_with_moderation_permission')">
|
||||
:label="t('dialog.moderate_group.groups_with_moderation_permission')">
|
||||
<el-option
|
||||
v-for="group in groupsWithModerationPermission"
|
||||
:key="group.id"
|
||||
@@ -36,7 +36,7 @@
|
||||
style="height: auto"
|
||||
class="x-friend-item">
|
||||
<div class="avatar">
|
||||
<img v-lazy="group.iconUrl" />
|
||||
<img :src="group.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="group.name"></span>
|
||||
@@ -54,29 +54,23 @@
|
||||
showGroupMemberModerationDialog(moderateGroupDialog.groupId, moderateGroupDialog.userId);
|
||||
moderateGroupDialog.visible = false;
|
||||
">
|
||||
{{ $t('dialog.moderate_group.moderation_tools') }}
|
||||
{{ t('dialog.moderate_group.moderation_tools') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, getCurrentInstance, nextTick, computed } from 'vue';
|
||||
import { ref, watch, nextTick, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { groupRequest, userRequest } from '../../api';
|
||||
import {
|
||||
adjustDialogZ,
|
||||
hasGroupPermission,
|
||||
hasGroupModerationPermission,
|
||||
userImage,
|
||||
userStatusClass
|
||||
} from '../../shared/utils';
|
||||
import { getNextDialogIndex, hasGroupModerationPermission, userImage } from '../../shared/utils';
|
||||
import { useGroupStore } from '../../stores';
|
||||
|
||||
const { currentUserGroups, moderateGroupDialog } = storeToRefs(useGroupStore());
|
||||
const { applyGroup, showGroupMemberModerationDialog } = useGroupStore();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { showGroupMemberModerationDialog } = useGroupStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const groupsWithModerationPermission = computed(() => {
|
||||
return Array.from(currentUserGroups.value.values()).filter((group) => hasGroupModerationPermission(group));
|
||||
@@ -93,10 +87,12 @@
|
||||
}
|
||||
);
|
||||
|
||||
const moderateGroupDialogRef = ref(null);
|
||||
const moderateGroupDialogIndex = ref(2000);
|
||||
|
||||
function initDialog() {
|
||||
nextTick(() => adjustDialogZ(moderateGroupDialogRef.value.$el));
|
||||
nextTick(() => {
|
||||
moderateGroupDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = moderateGroupDialog.value;
|
||||
if (D.groupId) {
|
||||
groupRequest
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="newInstanceDialogRef"
|
||||
:visible.sync="newInstanceDialog.visible"
|
||||
<el-dialog
|
||||
:z-index="newInstanceDialogIndex"
|
||||
v-model="newInstanceDialog.visible"
|
||||
:title="t('dialog.new_instance.header')"
|
||||
width="650px"
|
||||
append-to-body>
|
||||
<el-tabs v-model="newInstanceDialog.selectedTab" type="card" @tab-click="newInstanceTabClick">
|
||||
<el-tab-pane :label="t('dialog.new_instance.normal')">
|
||||
<el-tab-pane name="Normal" :label="t('dialog.new_instance.normal')">
|
||||
<el-form :model="newInstanceDialog" label-width="150px">
|
||||
<el-form-item :label="t('dialog.new_instance.access_type')">
|
||||
<el-radio-group v-model="newInstanceDialog.accessType" size="mini" @change="buildInstance">
|
||||
<el-radio-group v-model="newInstanceDialog.accessType" size="small" @change="buildInstance">
|
||||
<el-radio-button label="public">{{
|
||||
t('dialog.new_instance.access_type_public')
|
||||
}}</el-radio-button>
|
||||
@@ -33,7 +33,10 @@
|
||||
<el-form-item
|
||||
v-if="newInstanceDialog.accessType === 'group'"
|
||||
:label="t('dialog.new_instance.group_access_type')">
|
||||
<el-radio-group v-model="newInstanceDialog.groupAccessType" size="mini" @change="buildInstance">
|
||||
<el-radio-group
|
||||
v-model="newInstanceDialog.groupAccessType"
|
||||
size="small"
|
||||
@change="buildInstance">
|
||||
<el-radio-button
|
||||
label="members"
|
||||
:disabled="
|
||||
@@ -59,7 +62,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.region')">
|
||||
<el-radio-group v-model="newInstanceDialog.region" size="mini" @change="buildInstance">
|
||||
<el-radio-group v-model="newInstanceDialog.region" size="small" @change="buildInstance">
|
||||
<el-radio-button label="US West">{{ t('dialog.new_instance.region_usw') }}</el-radio-button>
|
||||
<el-radio-button label="US East">{{ t('dialog.new_instance.region_use') }}</el-radio-button>
|
||||
<el-radio-button label="Europe">{{ t('dialog.new_instance.region_eu') }}</el-radio-button>
|
||||
@@ -84,8 +87,16 @@
|
||||
<el-form-item :label="t('dialog.new_instance.world_id')">
|
||||
<el-input
|
||||
v-model="newInstanceDialog.worldId"
|
||||
size="mini"
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"
|
||||
size="small"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()"
|
||||
@change="buildInstance"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.display_name')">
|
||||
<el-input
|
||||
:disabled="!isLocalUserVrcplusSupporter"
|
||||
v-model="newInstanceDialog.displayName"
|
||||
size="small"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()"
|
||||
@change="buildInstance"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
@@ -114,7 +125,7 @@
|
||||
hasGroupPermission(group, 'group-instance-open-create'))
|
||||
">
|
||||
<div class="avatar">
|
||||
<img v-lazy="group.iconUrl" />
|
||||
<img :src="group.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="group.name"></span>
|
||||
@@ -155,22 +166,22 @@
|
||||
<el-form-item :label="t('dialog.new_instance.location')">
|
||||
<el-input
|
||||
v-model="newInstanceDialog.location"
|
||||
size="mini"
|
||||
size="small"
|
||||
readonly
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"></el-input>
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.url')">
|
||||
<el-input v-model="newInstanceDialog.url" size="mini" readonly></el-input>
|
||||
<el-input v-model="newInstanceDialog.url" size="small" readonly></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('dialog.new_instance.legacy')">
|
||||
<el-tab-pane name="Legacy" :label="t('dialog.new_instance.legacy')">
|
||||
<el-form :model="newInstanceDialog" label-width="150px">
|
||||
<el-form-item :label="t('dialog.new_instance.access_type')">
|
||||
<el-radio-group
|
||||
v-model="newInstanceDialog.accessType"
|
||||
size="mini"
|
||||
size="small"
|
||||
@change="buildLegacyInstance">
|
||||
<el-radio-button label="public">{{
|
||||
t('dialog.new_instance.access_type_public')
|
||||
@@ -197,7 +208,7 @@
|
||||
:label="t('dialog.new_instance.group_access_type')">
|
||||
<el-radio-group
|
||||
v-model="newInstanceDialog.groupAccessType"
|
||||
size="mini"
|
||||
size="small"
|
||||
@change="buildLegacyInstance">
|
||||
<el-radio-button label="members">{{
|
||||
t('dialog.new_instance.group_access_type_members')
|
||||
@@ -211,7 +222,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.region')">
|
||||
<el-radio-group v-model="newInstanceDialog.region" size="mini" @change="buildLegacyInstance">
|
||||
<el-radio-group v-model="newInstanceDialog.region" size="small" @change="buildLegacyInstance">
|
||||
<el-radio-button label="US West">{{ t('dialog.new_instance.region_usw') }}</el-radio-button>
|
||||
<el-radio-button label="US East">{{ t('dialog.new_instance.region_use') }}</el-radio-button>
|
||||
<el-radio-button label="Europe">{{ t('dialog.new_instance.region_eu') }}</el-radio-button>
|
||||
@@ -226,15 +237,15 @@
|
||||
<el-form-item :label="t('dialog.new_instance.world_id')">
|
||||
<el-input
|
||||
v-model="newInstanceDialog.worldId"
|
||||
size="mini"
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"
|
||||
size="small"
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()"
|
||||
@change="buildLegacyInstance"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.instance_id')">
|
||||
<el-input
|
||||
v-model="newInstanceDialog.instanceName"
|
||||
:placeholder="t('dialog.new_instance.instance_id_placeholder')"
|
||||
size="mini"
|
||||
size="small"
|
||||
@change="buildLegacyInstance"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
@@ -254,7 +265,7 @@
|
||||
:value="currentUser.id"
|
||||
style="height: auto">
|
||||
<div class="avatar" :class="userStatusClass(currentUser)">
|
||||
<img v-lazy="userImage(currentUser)" />
|
||||
<img :src="userImage(currentUser)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="currentUser.displayName"></span>
|
||||
@@ -271,7 +282,7 @@
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar" :class="userStatusClass(friend.ref)">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -293,7 +304,7 @@
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar" :class="userStatusClass(friend.ref)">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -315,7 +326,7 @@
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -337,7 +348,7 @@
|
||||
style="height: auto">
|
||||
<template v-if="friend.ref">
|
||||
<div class="avatar">
|
||||
<img v-lazy="userImage(friend.ref)" />
|
||||
<img :src="userImage(friend.ref)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span
|
||||
@@ -371,7 +382,7 @@
|
||||
style="height: auto; width: 478px">
|
||||
<template v-if="group">
|
||||
<div class="avatar">
|
||||
<img v-lazy="group.iconUrl" />
|
||||
<img :src="group.iconUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="group.name"></span></div
|
||||
@@ -383,17 +394,17 @@
|
||||
<el-form-item :label="t('dialog.new_instance.location')">
|
||||
<el-input
|
||||
v-model="newInstanceDialog.location"
|
||||
size="mini"
|
||||
size="small"
|
||||
readonly
|
||||
@click.native="$event.target.tagName === 'INPUT' && $event.target.select()"></el-input>
|
||||
@click="$event.target.tagName === 'INPUT' && $event.target.select()"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('dialog.new_instance.url')">
|
||||
<el-input v-model="newInstanceDialog.url" size="mini" readonly></el-input>
|
||||
<el-input v-model="newInstanceDialog.url" size="small" readonly></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template v-if="newInstanceDialog.selectedTab === '0'" #footer>
|
||||
<template v-if="newInstanceDialog.selectedTab === 'Normal'" #footer>
|
||||
<template v-if="newInstanceDialog.instanceCreated">
|
||||
<el-button size="small" @click="copyInstanceUrl(newInstanceDialog.location)">{{
|
||||
t('dialog.new_instance.copy_url')
|
||||
@@ -412,13 +423,11 @@
|
||||
>
|
||||
<template v-if="canOpenInstanceInGame()">
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
|
||||
>{{ t('dialog.new_instance.launch') }}</el-button
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleAttachGame(newInstanceDialog.location, newInstanceDialog.shortName)">
|
||||
{{ t('dialog.new_instance.open_ingame') }}
|
||||
@@ -439,7 +448,7 @@
|
||||
}}</el-button>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="newInstanceDialog.selectedTab === '1'" #footer>
|
||||
<template v-else-if="newInstanceDialog.selectedTab === 'Legacy'" #footer>
|
||||
<el-button size="small" @click="copyInstanceUrl(newInstanceDialog.location)">{{
|
||||
t('dialog.new_instance.copy_url')
|
||||
}}</el-button>
|
||||
@@ -457,7 +466,6 @@
|
||||
>
|
||||
<template v-if="canOpenInstanceInGame()">
|
||||
<el-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click="showLaunchDialog(newInstanceDialog.location, newInstanceDialog.shortName)"
|
||||
>{{ t('dialog.new_instance.launch') }}</el-button
|
||||
@@ -479,17 +487,18 @@
|
||||
</template>
|
||||
</template>
|
||||
<InviteDialog :invite-dialog="inviteDialog" @closeInviteDialog="closeInviteDialog" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, nextTick, getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ref, watch, nextTick, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { groupRequest, instanceRequest, worldRequest } from '../../api';
|
||||
import configRepository from '../../service/config';
|
||||
import {
|
||||
adjustDialogZ,
|
||||
getNextDialogIndex,
|
||||
copyToClipboard,
|
||||
getLaunchURL,
|
||||
hasGroupPermission,
|
||||
@@ -518,11 +527,9 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const { friends, vipFriends, onlineFriends, activeFriends, offlineFriends } = storeToRefs(useFriendStore());
|
||||
const { currentUserGroups, cachedGroups } = storeToRefs(useGroupStore());
|
||||
const { handleGroupPermissions } = useGroupStore();
|
||||
const { currentUserGroups } = storeToRefs(useGroupStore());
|
||||
const { cachedGroups, handleGroupPermissions } = useGroupStore();
|
||||
const { lastLocation } = storeToRefs(useLocationStore());
|
||||
const { showLaunchDialog } = useLaunchStore();
|
||||
const { createNewInstance } = useInstanceStore();
|
||||
@@ -530,12 +537,12 @@
|
||||
const { tryOpenInstanceInVrc } = useLaunchStore();
|
||||
const { canOpenInstanceInGame } = useInviteStore();
|
||||
|
||||
const newInstanceDialogRef = ref(null);
|
||||
const newInstanceDialogIndex = ref(2000);
|
||||
|
||||
const newInstanceDialog = ref({
|
||||
visible: false,
|
||||
// loading: false,
|
||||
selectedTab: '0',
|
||||
selectedTab: 'Normal',
|
||||
instanceCreated: false,
|
||||
queueEnabled: false,
|
||||
worldId: '',
|
||||
@@ -551,6 +558,7 @@
|
||||
strict: false,
|
||||
location: '',
|
||||
shortName: '',
|
||||
displayName: '',
|
||||
url: '',
|
||||
secureOrShortName: '',
|
||||
lastSelectedGroupId: '',
|
||||
@@ -575,6 +583,8 @@
|
||||
}
|
||||
);
|
||||
|
||||
const isLocalUserVrcplusSupporter = computed(() => currentUser.value.$isVRCPlus);
|
||||
|
||||
initializeNewInstanceDialog();
|
||||
|
||||
function closeInviteDialog() {
|
||||
@@ -617,7 +627,9 @@
|
||||
if (!isRealInstance(tag)) {
|
||||
return;
|
||||
}
|
||||
nextTick(() => adjustDialogZ(newInstanceDialogRef.value.$el));
|
||||
nextTick(() => {
|
||||
newInstanceDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
const D = newInstanceDialog.value;
|
||||
const L = parseLocation(tag);
|
||||
if (D.worldId === L.worldId) {
|
||||
@@ -634,6 +646,9 @@
|
||||
D.strict = false;
|
||||
D.shortName = '';
|
||||
D.secureOrShortName = '';
|
||||
if (!isLocalUserVrcplusSupporter.value) {
|
||||
D.displayName = '';
|
||||
}
|
||||
const args = await groupRequest.getGroupPermissions({ userId: currentUser.value.id });
|
||||
handleGroupPermissions(args);
|
||||
buildInstance();
|
||||
@@ -673,10 +688,23 @@
|
||||
configRepository
|
||||
.getBool('instanceDialogAgeGate', false)
|
||||
.then((value) => (newInstanceDialog.value.ageGate = value));
|
||||
|
||||
configRepository
|
||||
.getString('instanceDialogDisplayName', '')
|
||||
.then((value) => (newInstanceDialog.value.displayName = value));
|
||||
}
|
||||
function saveNewInstanceDialog() {
|
||||
const { accessType, region, instanceName, userId, groupId, groupAccessType, queueEnabled, ageGate } =
|
||||
newInstanceDialog.value;
|
||||
const {
|
||||
accessType,
|
||||
region,
|
||||
instanceName,
|
||||
userId,
|
||||
groupId,
|
||||
groupAccessType,
|
||||
queueEnabled,
|
||||
ageGate,
|
||||
displayName
|
||||
} = newInstanceDialog.value;
|
||||
|
||||
configRepository.setString('instanceDialogAccessType', accessType);
|
||||
configRepository.setString('instanceRegion', region);
|
||||
@@ -686,9 +714,10 @@
|
||||
configRepository.setString('instanceDialogGroupAccessType', groupAccessType);
|
||||
configRepository.setBool('instanceDialogQueueEnabled', queueEnabled);
|
||||
configRepository.setBool('instanceDialogAgeGate', ageGate);
|
||||
configRepository.setString('instanceDialogDisplayName', displayName);
|
||||
}
|
||||
function newInstanceTabClick(tab) {
|
||||
if (tab === '1') {
|
||||
function newInstanceTabClick(obj) {
|
||||
if (obj.props.name === 'Normal') {
|
||||
buildInstance();
|
||||
} else {
|
||||
buildLegacyInstance();
|
||||
@@ -720,7 +749,7 @@
|
||||
worldId: L.worldId
|
||||
})
|
||||
.then((args) => {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Self invite sent',
|
||||
type: 'success'
|
||||
});
|
||||
@@ -749,7 +778,7 @@
|
||||
}
|
||||
if (D.groupId && D.groupId !== D.lastSelectedGroupId) {
|
||||
D.roleIds = [];
|
||||
const ref = cachedGroups.value.get(D.groupId);
|
||||
const ref = cachedGroups.get(D.groupId);
|
||||
if (typeof ref !== 'undefined') {
|
||||
D.groupRef = ref;
|
||||
D.selectedGroupRoles = ref.roles;
|
||||
@@ -824,7 +853,7 @@
|
||||
}
|
||||
if (D.groupId && D.groupId !== D.lastSelectedGroupId) {
|
||||
D.roleIds = [];
|
||||
const ref = cachedGroups.value.get(D.groupId);
|
||||
const ref = cachedGroups.get(D.groupId);
|
||||
if (typeof ref !== 'undefined') {
|
||||
D.groupRef = ref;
|
||||
D.selectedGroupRoles = ref.roles;
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
class="x-dialog"
|
||||
:visible="previousImagesDialogVisible"
|
||||
:title="t('dialog.previous_images.header')"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@close="closeDialog">
|
||||
<div>
|
||||
<div v-for="image in previousImagesTable" :key="image.version" style="display: inline-block">
|
||||
<el-popover
|
||||
class="x-change-image-item"
|
||||
placement="right"
|
||||
width="500px"
|
||||
trigger="click"
|
||||
v-if="image.file">
|
||||
<img slot="reference" v-lazy="image.file.url" class="x-link" />
|
||||
<img
|
||||
v-lazy="image.file.url"
|
||||
class="x-link"
|
||||
style="width: 500px; height: 375px"
|
||||
@click="showFullscreenImageDialog(image.file.url)" />
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useGalleryStore } from '../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { previousImagesDialogVisible, previousImagesTable } = storeToRefs(useGalleryStore());
|
||||
const { showFullscreenImageDialog } = useGalleryStore();
|
||||
|
||||
function closeDialog() {
|
||||
previousImagesDialogVisible.value = false;
|
||||
}
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="previousInstancesGroupDialogRef"
|
||||
:visible.sync="isVisible"
|
||||
<el-dialog
|
||||
:z-index="previousInstancesGroupDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.previous_instances.header')"
|
||||
width="1000px"
|
||||
append-to-body>
|
||||
@@ -13,7 +13,7 @@
|
||||
style="width: 150px" />
|
||||
</div>
|
||||
|
||||
<data-tables v-loading="loading" v-bind="previousInstancesGroupDialogTable" style="margin-top: 10px">
|
||||
<DataTable v-loading="loading" v-bind="previousInstancesGroupDialogTable" style="margin-top: 10px">
|
||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||
@@ -39,48 +39,50 @@
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-s-data"
|
||||
size="mini"
|
||||
:icon="DataLine"
|
||||
size="small"
|
||||
@click="showPreviousInstancesInfoDialog(scope.row.location)" />
|
||||
<el-button
|
||||
v-if="shiftHeld"
|
||||
style="color: #f56c6c"
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
@click="deleteGameLogGroupInstance(scope.row)" />
|
||||
<el-button
|
||||
v-else
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
@click="deleteGameLogGroupInstancePrompt(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</safe-dialog>
|
||||
</DataTable>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { DataLine, Close } from '@element-plus/icons-vue';
|
||||
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { ref, reactive, computed, watch, nextTick, getCurrentInstance } from 'vue';
|
||||
import {
|
||||
parseLocation,
|
||||
compareByCreatedAt,
|
||||
timeToText,
|
||||
removeFromArray,
|
||||
adjustDialogZ,
|
||||
getNextDialogIndex,
|
||||
formatDateFilter
|
||||
} from '../../../shared/utils';
|
||||
import { database } from '../../../service/database';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useInstanceStore, useUiStore } from '../../../stores';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { showPreviousInstancesInfoDialog } = useInstanceStore();
|
||||
const { shiftHeld } = useUiStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const previousInstancesGroupDialogRef = ref(null);
|
||||
const previousInstancesGroupDialogIndex = ref(2000);
|
||||
const loading = ref(false);
|
||||
|
||||
const previousInstancesGroupDialogTable = reactive({
|
||||
@@ -88,7 +90,7 @@
|
||||
filters: [{ prop: 'groupName', value: '' }],
|
||||
tableProps: {
|
||||
stripe: true,
|
||||
size: 'mini',
|
||||
size: 'small',
|
||||
defaultSort: { prop: 'created_at', order: 'descending' }
|
||||
},
|
||||
pageSize: 10,
|
||||
@@ -115,7 +117,7 @@
|
||||
() => {
|
||||
if (props.previousInstancesGroupDialog.visible) {
|
||||
nextTick(() => {
|
||||
adjustDialogZ(previousInstancesGroupDialogRef.value.$el);
|
||||
previousInstancesGroupDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
refreshPreviousInstancesGroupTable();
|
||||
}
|
||||
@@ -144,15 +146,16 @@
|
||||
}
|
||||
|
||||
function deleteGameLogGroupInstancePrompt(row) {
|
||||
proxy.$confirm('Continue? Delete GameLog Instance', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Delete GameLog Instance', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
if (action === 'confirm') {
|
||||
deleteGameLogGroupInstance(row);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="dialogRef"
|
||||
:visible="previousInstancesInfoDialogVisible"
|
||||
:title="$t('dialog.previous_instances.info')"
|
||||
<el-dialog
|
||||
:z-index="previousInstancesInfoDialogIndex"
|
||||
:model-value="previousInstancesInfoDialogVisible"
|
||||
:title="t('dialog.previous_instances.info')"
|
||||
width="800px"
|
||||
:fullscreen="fullscreen"
|
||||
destroy-on-close
|
||||
@@ -11,12 +11,12 @@
|
||||
<Location :location="location.tag" style="font-size: 14px" />
|
||||
<el-input
|
||||
v-model="dataTable.filters[0].value"
|
||||
:placeholder="$t('dialog.previous_instances.search_placeholder')"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
style="width: 150px"
|
||||
clearable></el-input>
|
||||
</div>
|
||||
<data-tables v-loading="loading" v-bind="dataTable" style="margin-top: 10px">
|
||||
<el-table-column :label="$t('table.previous_instances.date')" prop="created_at" sortable width="110">
|
||||
<DataTable v-loading="loading" v-bind="dataTable" style="margin-top: 10px">
|
||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="110">
|
||||
<template #default="scope">
|
||||
<el-tooltip placement="left">
|
||||
<template #content>
|
||||
@@ -26,7 +26,7 @@
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.gameLog.icon')" prop="isFriend" width="70" align="center">
|
||||
<el-table-column :label="t('table.gameLog.icon')" prop="isFriend" width="70" align="center">
|
||||
<template #default="scope">
|
||||
<template v-if="gameLogIsFriend(scope.row)">
|
||||
<el-tooltip v-if="gameLogIsFavorite(scope.row)" placement="top" content="Favorite">
|
||||
@@ -36,33 +36,35 @@
|
||||
<span>💚</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<span v-else></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.display_name')" prop="displayName" sortable>
|
||||
<el-table-column :label="t('table.previous_instances.display_name')" prop="displayName" sortable>
|
||||
<template #default="scope">
|
||||
<span class="x-link" @click="lookupUser(scope.row)">{{ scope.row.displayName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.time')" prop="time" width="100" sortable>
|
||||
<el-table-column :label="t('table.previous_instances.time')" prop="time" width="100" sortable>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.timer }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.count')" prop="count" width="100" sortable>
|
||||
<el-table-column :label="t('table.previous_instances.count')" prop="count" width="100" sortable>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.count }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</safe-dialog>
|
||||
</DataTable>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { database } from '../../../service/database';
|
||||
import {
|
||||
adjustDialogZ,
|
||||
getNextDialogIndex,
|
||||
compareByCreatedAt,
|
||||
parseLocation,
|
||||
timeToText,
|
||||
@@ -74,8 +76,9 @@
|
||||
const { previousInstancesInfoDialogVisible, previousInstancesInfoDialogInstanceId } =
|
||||
storeToRefs(useInstanceStore());
|
||||
const { gameLogIsFriend, gameLogIsFavorite } = useGameLogStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const dialogRef = ref(null);
|
||||
const previousInstancesInfoDialogIndex = ref(2000);
|
||||
|
||||
const loading = ref(false);
|
||||
const location = ref({
|
||||
@@ -111,7 +114,7 @@
|
||||
],
|
||||
tableProps: {
|
||||
stripe: true,
|
||||
size: 'mini',
|
||||
size: 'small',
|
||||
defaultSort: {
|
||||
prop: 'created_at',
|
||||
order: 'descending'
|
||||
@@ -139,7 +142,7 @@
|
||||
);
|
||||
|
||||
function init() {
|
||||
adjustDialogZ(dialogRef.value.$el);
|
||||
previousInstancesInfoDialogIndex.value = getNextDialogIndex();
|
||||
loading.value = true;
|
||||
location.value = parseLocation(previousInstancesInfoDialogInstanceId.value);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="previousInstancesWorldDialogRef"
|
||||
:visible.sync="isVisible"
|
||||
<el-dialog
|
||||
:z-index="previousInstancesWorldDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.previous_instances.header')"
|
||||
width="1000px"
|
||||
append-to-body>
|
||||
@@ -12,7 +12,7 @@
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
style="display: block; width: 150px"></el-input>
|
||||
</div>
|
||||
<data-tables v-loading="loading" v-bind="previousInstancesWorldDialogTable" style="margin-top: 10px">
|
||||
<DataTable v-loading="loading" v-bind="previousInstancesWorldDialogTable" style="margin-top: 10px">
|
||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||
@@ -43,35 +43,41 @@
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-s-data"
|
||||
size="mini"
|
||||
:icon="DataLine"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="showPreviousInstancesInfoDialog(scope.row.location)"></el-button>
|
||||
<el-button
|
||||
v-if="shiftHeld"
|
||||
style="color: #f56c6c"
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="deleteGameLogWorldInstance(scope.row)"></el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="deleteGameLogWorldInstancePrompt(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</safe-dialog>
|
||||
</DataTable>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { DataLine, Close } from '@element-plus/icons-vue';
|
||||
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, getCurrentInstance, nextTick, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { database } from '../../../service/database';
|
||||
import {
|
||||
adjustDialogZ,
|
||||
getNextDialogIndex,
|
||||
compareByCreatedAt,
|
||||
parseLocation,
|
||||
removeFromArray,
|
||||
@@ -80,7 +86,6 @@
|
||||
} from '../../../shared/utils';
|
||||
import { useInstanceStore, useUiStore, useUserStore } from '../../../stores';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
@@ -100,7 +105,7 @@
|
||||
filters: [{ prop: 'groupName', value: '' }],
|
||||
tableProps: {
|
||||
stripe: true,
|
||||
size: 'mini',
|
||||
size: 'small',
|
||||
defaultSort: { prop: 'created_at', order: 'descending' }
|
||||
},
|
||||
pageSize: 10,
|
||||
@@ -111,7 +116,7 @@
|
||||
}
|
||||
});
|
||||
const loading = ref(false);
|
||||
const previousInstancesWorldDialogRef = ref(null);
|
||||
const previousInstancesWorldDialogIndex = ref(2000);
|
||||
|
||||
const isVisible = computed({
|
||||
get: () => props.previousInstancesWorldDialog.visible,
|
||||
@@ -145,16 +150,17 @@
|
||||
}
|
||||
|
||||
function deleteGameLogWorldInstancePrompt(row) {
|
||||
proxy.$confirm('Continue? Delete GameLog Instance', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Delete GameLog Instance', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
if (action === 'confirm') {
|
||||
deleteGameLogWorldInstance(row);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -162,10 +168,16 @@
|
||||
() => {
|
||||
if (props.previousInstancesWorldDialog.visible) {
|
||||
nextTick(() => {
|
||||
adjustDialogZ(previousInstancesWorldDialogRef.value.$el);
|
||||
previousInstancesWorldDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
refreshPreviousInstancesWorldTable();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.button-pd-0 {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
ref="elDialogRef"
|
||||
:visible="props.visible"
|
||||
v-bind="attrs"
|
||||
:close-on-click-modal="false"
|
||||
@open="handleOpen"
|
||||
@close="handleClose"
|
||||
:top="marginTop">
|
||||
<slot></slot>
|
||||
|
||||
<template v-if="slots.title" #title>
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
|
||||
<template v-if="slots.footer" #footer>
|
||||
<slot name="footer"></slot>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onBeforeUnmount, nextTick, useAttrs, useSlots } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:visible', 'open', 'close']);
|
||||
|
||||
const attrs = useAttrs();
|
||||
const slots = useSlots();
|
||||
|
||||
const elDialogRef = ref(null);
|
||||
const wrapperElement = ref(null);
|
||||
const mouseDownOnWrapper = ref(false);
|
||||
const styleObserver = ref(null);
|
||||
const resizeObserver = ref(null);
|
||||
const marginTop = ref('5px');
|
||||
let handleResize = null;
|
||||
|
||||
const handleOpen = () => {
|
||||
emit('open');
|
||||
|
||||
nextTick(() => {
|
||||
addWrapperListeners();
|
||||
removeTitleAttribute();
|
||||
adjustDialogMarginTop();
|
||||
});
|
||||
};
|
||||
|
||||
const removeTitleAttribute = () => {
|
||||
const wrapper = elDialogRef.value?.$el;
|
||||
if (wrapper && wrapper.nodeType === Node.ELEMENT_NODE) {
|
||||
wrapper.removeAttribute('title');
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close');
|
||||
removeWrapperListeners();
|
||||
|
||||
if (styleObserver.value) {
|
||||
styleObserver.value.disconnect();
|
||||
styleObserver.value = null;
|
||||
}
|
||||
|
||||
if (resizeObserver.value) {
|
||||
resizeObserver.value.disconnect();
|
||||
resizeObserver.value = null;
|
||||
}
|
||||
|
||||
if (handleResize) {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
handleResize = null;
|
||||
}
|
||||
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
const handleWrapperMouseDown = (event) => {
|
||||
if (event.target === wrapperElement.value) {
|
||||
mouseDownOnWrapper.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const handleWrapperMouseUp = (event) => {
|
||||
if (event.target === wrapperElement.value && mouseDownOnWrapper.value) {
|
||||
handleClose();
|
||||
}
|
||||
mouseDownOnWrapper.value = false;
|
||||
};
|
||||
|
||||
const addWrapperListeners = () => {
|
||||
const wrapper = elDialogRef.value?.$el;
|
||||
|
||||
if (
|
||||
wrapper &&
|
||||
wrapper.nodeType === Node.ELEMENT_NODE &&
|
||||
wrapper.classList &&
|
||||
wrapper.classList.contains('el-dialog__wrapper')
|
||||
) {
|
||||
wrapperElement.value = wrapper;
|
||||
wrapperElement.value.addEventListener('mousedown', handleWrapperMouseDown);
|
||||
wrapperElement.value.addEventListener('mouseup', handleWrapperMouseUp);
|
||||
} else {
|
||||
wrapperElement.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const removeWrapperListeners = () => {
|
||||
if (wrapperElement.value) {
|
||||
wrapperElement.value.removeEventListener('mousedown', handleWrapperMouseDown);
|
||||
wrapperElement.value.removeEventListener('mouseup', handleWrapperMouseUp);
|
||||
wrapperElement.value = null;
|
||||
}
|
||||
mouseDownOnWrapper.value = false;
|
||||
};
|
||||
|
||||
const adjustDialogMarginTop = () => {
|
||||
const wrapper = elDialogRef.value?.$el;
|
||||
if (!wrapper) return;
|
||||
|
||||
const dialog = wrapper.querySelector('.el-dialog');
|
||||
if (!dialog) return;
|
||||
|
||||
const applyStyle = () => {
|
||||
const dialogHeight = dialog.offsetHeight;
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
const topOffset = Math.max(0, (viewportHeight - dialogHeight) * 0.2);
|
||||
marginTop.value = `${topOffset}px`;
|
||||
};
|
||||
|
||||
applyStyle();
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
removeWrapperListeners();
|
||||
|
||||
if (styleObserver.value) {
|
||||
styleObserver.value.disconnect();
|
||||
styleObserver.value = null;
|
||||
}
|
||||
|
||||
if (resizeObserver.value) {
|
||||
resizeObserver.value.disconnect();
|
||||
resizeObserver.value = null;
|
||||
}
|
||||
|
||||
if (handleResize) {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
handleResize = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--<template>-->
|
||||
<!-- <safe-dialog-->
|
||||
<!-- <el-dialog-->
|
||||
<!-- class="x-dialog"-->
|
||||
<!-- :visible="sendBoopDialog.visible"-->
|
||||
<!-- :model-value="sendBoopDialog.visible"-->
|
||||
<!-- :title="t('dialog.boop_dialog.header')"-->
|
||||
<!-- width="450px"-->
|
||||
<!-- @close="closeDialog">-->
|
||||
@@ -20,7 +20,7 @@
|
||||
<!-- style="height: auto">-->
|
||||
<!-- <template v-if="friend.ref">-->
|
||||
<!-- <div class="avatar" :class="userStatusClass(friend.ref)">-->
|
||||
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="detail">-->
|
||||
<!-- <span-->
|
||||
@@ -42,7 +42,7 @@
|
||||
<!-- style="height: auto">-->
|
||||
<!-- <template v-if="friend.ref">-->
|
||||
<!-- <div class="avatar" :class="userStatusClass(friend.ref)">-->
|
||||
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="detail">-->
|
||||
<!-- <span-->
|
||||
@@ -64,7 +64,7 @@
|
||||
<!-- style="height: auto">-->
|
||||
<!-- <template v-if="friend.ref">-->
|
||||
<!-- <div class="avatar">-->
|
||||
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="detail">-->
|
||||
<!-- <span-->
|
||||
@@ -86,7 +86,7 @@
|
||||
<!-- style="height: auto">-->
|
||||
<!-- <template v-if="friend.ref">-->
|
||||
<!-- <div class="avatar">-->
|
||||
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||
<!-- <img :src="userImage(friend.ref)" loading="lazy">-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="detail">-->
|
||||
<!-- <span-->
|
||||
@@ -135,7 +135,7 @@
|
||||
<!-- </template>-->
|
||||
<!-- <template v-else>-->
|
||||
<!-- <img-->
|
||||
<!-- v-lazy="image.versions[image.versions.length - 1].file.url"-->
|
||||
<!-- :src="image.versions[image.versions.length - 1].file.url"-->
|
||||
<!-- class="avatar"-->
|
||||
<!-- style="width: 200px; height: 200px" />-->
|
||||
<!-- </template>-->
|
||||
@@ -162,7 +162,7 @@
|
||||
<!-- t('dialog.boop_dialog.send')-->
|
||||
<!-- }}</el-button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </safe-dialog>-->
|
||||
<!-- </el-dialog>-->
|
||||
<!--</template>-->
|
||||
|
||||
<!--<script setup>-->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="bioDialog.visible"
|
||||
v-model="bioDialog.visible"
|
||||
:title="t('dialog.bio.header')"
|
||||
width="600px"
|
||||
append-to-body>
|
||||
@@ -9,7 +9,7 @@
|
||||
<el-input
|
||||
v-model="bioDialog.bio"
|
||||
type="textarea"
|
||||
size="mini"
|
||||
size="small"
|
||||
maxlength="512"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 5, maxRows: 20 }"
|
||||
@@ -23,16 +23,13 @@
|
||||
v-model="bioDialog.bioLinks[index]"
|
||||
size="small"
|
||||
style="margin-top: 5px">
|
||||
<img
|
||||
slot="prepend"
|
||||
:src="getFaviconUrl(link)"
|
||||
style="width: 16px; height: 16px; vertical-align: middle" />
|
||||
<el-button slot="append" icon="el-icon-delete" @click="bioDialog.bioLinks.splice(index, 1)" />
|
||||
<img :src="getFaviconUrl(link)" style="width: 16px; height: 16px; vertical-align: middle" />
|
||||
<el-button :icon="Delete" @click="bioDialog.bioLinks.splice(index, 1)" />
|
||||
</el-input>
|
||||
|
||||
<el-button
|
||||
:disabled="bioDialog.bioLinks.length >= 3"
|
||||
size="mini"
|
||||
size="small"
|
||||
style="margin-top: 5px"
|
||||
@click="bioDialog.bioLinks.push('')">
|
||||
{{ t('dialog.bio.add_link') }}
|
||||
@@ -44,18 +41,19 @@
|
||||
{{ t('dialog.bio.update') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { Delete } from '@element-plus/icons-vue';
|
||||
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { userRequest } from '../../../api';
|
||||
import { getFaviconUrl } from '../../../shared/utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { $message } = getCurrentInstance().proxy;
|
||||
|
||||
const props = defineProps({
|
||||
bioDialog: {
|
||||
type: Object,
|
||||
@@ -79,7 +77,7 @@
|
||||
})
|
||||
.then((args) => {
|
||||
D.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Bio updated',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="languageDialog.visible"
|
||||
v-model="languageDialog.visible"
|
||||
:title="t('dialog.language.header')"
|
||||
width="400px"
|
||||
append-to-body>
|
||||
@@ -22,7 +22,6 @@
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-select
|
||||
value=""
|
||||
:disabled="languageDialog.loading || (currentUser.$languages && currentUser.$languages.length === 3)"
|
||||
:placeholder="t('dialog.language.select_language')"
|
||||
style="margin-top: 14px"
|
||||
@@ -40,12 +39,12 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { userRequest } from '../../../api';
|
||||
import { languageClass } from '../../../shared/utils';
|
||||
import { useUserStore } from '../../../stores';
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="previousInstancesUserDialogRef"
|
||||
:visible.sync="isVisible"
|
||||
:title="$t('dialog.previous_instances.header')"
|
||||
<el-dialog
|
||||
:z-index="previousInstancesUserDialogIndex"
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.previous_instances.header')"
|
||||
width="1000px"
|
||||
append-to-body>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span style="font-size: 14px" v-text="previousInstancesUserDialog.userRef.displayName"></span>
|
||||
<el-input
|
||||
v-model="previousInstancesUserDialogTable.filters[0].value"
|
||||
:placeholder="$t('dialog.previous_instances.search_placeholder')"
|
||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||
style="display: block; width: 150px"></el-input>
|
||||
</div>
|
||||
<data-tables v-loading="loading" v-bind="previousInstancesUserDialogTable" style="margin-top: 10px">
|
||||
<el-table-column :label="$t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
||||
<DataTable v-loading="loading" v-bind="previousInstancesUserDialogTable" style="margin-top: 10px">
|
||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.world')" prop="name" sortable>
|
||||
<el-table-column :label="t('table.previous_instances.world')" prop="name" sortable>
|
||||
<template #default="scope">
|
||||
<Location
|
||||
:location="scope.row.location"
|
||||
@@ -26,53 +26,61 @@
|
||||
:grouphint="scope.row.groupName" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.instance_creator')" prop="location" width="170">
|
||||
<el-table-column :label="t('table.previous_instances.instance_creator')" prop="location" width="170">
|
||||
<template #default="scope">
|
||||
<DisplayName :userid="scope.row.$location.userId" :location="scope.row.$location.tag" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.time')" prop="time" width="100" sortable>
|
||||
<el-table-column :label="t('table.previous_instances.time')" prop="time" width="100" sortable>
|
||||
<template #default="scope">
|
||||
<span v-text="scope.row.timer"></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('table.previous_instances.action')" width="90" align="right">
|
||||
<el-table-column :label="t('table.previous_instances.action')" width="90" align="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-switch-button"
|
||||
size="mini"
|
||||
:icon="SwitchButton"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="showLaunchDialog(scope.row.location)"></el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-s-data"
|
||||
size="mini"
|
||||
:icon="DataLine"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="showPreviousInstancesInfoDialog(scope.row.location)"></el-button>
|
||||
<el-button
|
||||
v-if="shiftHeld"
|
||||
style="color: #f56c6c"
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="deleteGameLogUserInstance(scope.row)"></el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="text"
|
||||
icon="el-icon-close"
|
||||
size="mini"
|
||||
:icon="Close"
|
||||
size="small"
|
||||
class="button-pd-0"
|
||||
@click="deleteGameLogUserInstancePrompt(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</safe-dialog>
|
||||
</DataTable>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { SwitchButton, DataLine, Close } from '@element-plus/icons-vue';
|
||||
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, getCurrentInstance, nextTick, reactive, ref, watch } from 'vue';
|
||||
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { database } from '../../../service/database';
|
||||
import {
|
||||
adjustDialogZ,
|
||||
getNextDialogIndex,
|
||||
compareByCreatedAt,
|
||||
parseLocation,
|
||||
removeFromArray,
|
||||
@@ -93,22 +101,20 @@
|
||||
previousInstancesTable: {
|
||||
data: [],
|
||||
filters: [{ prop: 'displayName', value: '' }],
|
||||
tableProps: { stripe: true, size: 'mini', height: '400px' }
|
||||
tableProps: { stripe: true, size: 'small', height: '400px' }
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:previous-instances-user-dialog']);
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const loading = ref(false);
|
||||
const previousInstancesUserDialogTable = reactive({
|
||||
data: [],
|
||||
filters: [{ prop: 'worldName', value: '' }],
|
||||
tableProps: {
|
||||
stripe: true,
|
||||
size: 'mini',
|
||||
size: 'small',
|
||||
defaultSort: { prop: 'created_at', order: 'descending' }
|
||||
},
|
||||
pageSize: 10,
|
||||
@@ -122,8 +128,9 @@
|
||||
const { showLaunchDialog } = useLaunchStore();
|
||||
const { showPreviousInstancesInfoDialog } = useInstanceStore();
|
||||
const { shiftHeld } = storeToRefs(useUiStore());
|
||||
const { t } = useI18n();
|
||||
|
||||
const previousInstancesUserDialogRef = ref(null);
|
||||
const previousInstancesUserDialogIndex = ref(2000);
|
||||
|
||||
const isVisible = computed({
|
||||
get: () => props.previousInstancesUserDialog.visible,
|
||||
@@ -154,7 +161,7 @@
|
||||
() => {
|
||||
if (props.previousInstancesUserDialog.visible) {
|
||||
nextTick(() => {
|
||||
adjustDialogZ(previousInstancesUserDialogRef.value.$el);
|
||||
previousInstancesUserDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
refreshPreviousInstancesUserTable();
|
||||
}
|
||||
@@ -172,13 +179,20 @@
|
||||
}
|
||||
|
||||
function deleteGameLogUserInstancePrompt(row) {
|
||||
proxy.$confirm('Continue? Delete User From GameLog Instance', 'Confirm', {
|
||||
ElMessageBox.confirm('Continue? Delete User From GameLog Instance', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
type: 'info'
|
||||
})
|
||||
.then((action) => {
|
||||
if (action === 'confirm') deleteGameLogUserInstance(row);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.button-pd-0 {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="pronounsDialog.visible"
|
||||
v-model="pronounsDialog.visible"
|
||||
:title="t('dialog.pronouns.header')"
|
||||
width="600px"
|
||||
append-to-body>
|
||||
@@ -9,7 +9,7 @@
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="pronounsDialog.pronouns"
|
||||
size="mini"
|
||||
size="small"
|
||||
maxlength="32"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
@@ -21,18 +21,15 @@
|
||||
{{ t('dialog.pronouns.update') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { userRequest } from '../../../api';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { $message } = proxy;
|
||||
|
||||
const props = defineProps({
|
||||
pronounsDialog: {
|
||||
type: Object,
|
||||
@@ -55,7 +52,7 @@
|
||||
})
|
||||
.then((args) => {
|
||||
D.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Pronouns updated',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="sendInviteRequestDialogVisible"
|
||||
:model-value="sendInviteRequestDialogVisible"
|
||||
@update:model-value="$emit('update:sendInviteRequestDialogVisible', $event)"
|
||||
:title="t('dialog.invite_request_message.header')"
|
||||
width="800px"
|
||||
append-to-body
|
||||
@@ -10,20 +11,20 @@
|
||||
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||
</template>
|
||||
|
||||
<data-tables
|
||||
<DataTable
|
||||
v-bind="inviteRequestMessageTable"
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
@row-click="showSendInviteConfirmDialog">
|
||||
<el-table-column
|
||||
:label="t('table.profile.invite_messages.slot')"
|
||||
prop="slot"
|
||||
sortable="custom"
|
||||
:sortable="true"
|
||||
width="70"></el-table-column>
|
||||
<el-table-column :label="t('table.profile.invite_messages.message')" prop="message"></el-table-column>
|
||||
<el-table-column
|
||||
:label="t('table.profile.invite_messages.cool_down')"
|
||||
prop="updatedAt"
|
||||
sortable="custom"
|
||||
:sortable="true"
|
||||
width="110"
|
||||
align="right">
|
||||
<template #default="scope">
|
||||
@@ -34,38 +35,38 @@
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:icon="Edit"
|
||||
size="small"
|
||||
@click.stop="showEditAndSendInviteDialog(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="small" @click="cancelSendInviteRequest">{{
|
||||
t('dialog.invite_request_message.cancel')
|
||||
}}</el-button>
|
||||
<el-button type="small" @click="refreshInviteMessageTableData('request')">{{
|
||||
<el-button @click="cancelSendInviteRequest">{{ t('dialog.invite_request_message.cancel') }}</el-button>
|
||||
<el-button @click="refreshInviteMessageTableData('request')">{{
|
||||
t('dialog.invite_request_message.refresh')
|
||||
}}</el-button>
|
||||
</template>
|
||||
<SendInviteConfirmDialog
|
||||
:visible.sync="isSendInviteConfirmDialogVisible"
|
||||
v-model="isSendInviteConfirmDialogVisible"
|
||||
:send-invite-dialog="sendInviteDialog"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
<EditAndSendInviteDialog
|
||||
:edit-and-send-invite-dialog.sync="editAndSendInviteDialog"
|
||||
:edit-and-send-invite-dialog="editAndSendInviteDialog"
|
||||
:send-invite-dialog="sendInviteDialog"
|
||||
:invite-dialog="inviteDialog"
|
||||
@closeInviteDialog="closeInviteDialog" />
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Edit } from '@element-plus/icons-vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useGalleryStore, useInviteStore, useUserStore } from '../../../stores';
|
||||
import EditAndSendInviteDialog from '../InviteDialog/EditAndSendInviteDialog.vue';
|
||||
import SendInviteConfirmDialog from '../InviteDialog/SendInviteConfirmDialog.vue';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible.sync="socialStatusDialog.visible"
|
||||
v-model="socialStatusDialog.visible"
|
||||
:title="t('dialog.social_status.header')"
|
||||
append-to-body
|
||||
width="400px">
|
||||
@@ -11,13 +11,13 @@
|
||||
<template #title>
|
||||
<span style="font-size: 16px">{{ t('dialog.social_status.history') }}</span>
|
||||
</template>
|
||||
<data-tables
|
||||
<DataTable
|
||||
v-bind="socialStatusHistoryTable"
|
||||
style="cursor: pointer"
|
||||
@row-click="setSocialStatusFromHistory">
|
||||
<el-table-column :label="t('table.social_status.no')" prop="no" width="50"></el-table-column>
|
||||
<el-table-column :label="t('table.social_status.status')" prop="status"></el-table-column>
|
||||
</data-tables>
|
||||
</DataTable>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
@@ -53,19 +53,18 @@
|
||||
{{ t('dialog.social_status.update') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { userRequest } from '../../../api';
|
||||
import { useUserStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { $message } = getCurrentInstance().proxy;
|
||||
|
||||
const { currentUser } = storeToRefs(useUserStore());
|
||||
|
||||
const props = defineProps({
|
||||
@@ -103,7 +102,7 @@
|
||||
})
|
||||
.then((args) => {
|
||||
D.visible = false;
|
||||
$message({
|
||||
ElMessage({
|
||||
message: 'Status updated',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
ref="VRCXUpdateDialogRef"
|
||||
<el-dialog
|
||||
:z-index="VRCXUpdateDialogIndex"
|
||||
class="x-dialog"
|
||||
:visible.sync="VRCXUpdateDialog.visible"
|
||||
v-model="VRCXUpdateDialog.visible"
|
||||
:title="t('dialog.vrcx_updater.header')"
|
||||
append-to-body
|
||||
width="400px">
|
||||
@@ -59,15 +59,15 @@
|
||||
{{ t('dialog.vrcx_updater.install') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { branches } from '../../shared/constants';
|
||||
import { adjustDialogZ } from '../../shared/utils';
|
||||
import { getNextDialogIndex } from '../../shared/utils';
|
||||
import { useVRCXUpdaterStore } from '../../stores';
|
||||
|
||||
const VRCXUpdaterStore = useVRCXUpdaterStore();
|
||||
@@ -85,14 +85,14 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const VRCXUpdateDialogRef = ref(null);
|
||||
const VRCXUpdateDialogIndex = ref(2000);
|
||||
|
||||
watch(
|
||||
() => VRCXUpdateDialog,
|
||||
(newVal) => {
|
||||
if (newVal.value.visible) {
|
||||
nextTick(() => {
|
||||
adjustDialogZ(VRCXUpdateDialogRef.value.$el);
|
||||
VRCXUpdateDialogIndex.value = getNextDialogIndex();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
<el-dialog
|
||||
class="x-dialog"
|
||||
:visible="changeWorldImageDialogVisible"
|
||||
:model-value="changeWorldImageDialogVisible"
|
||||
:title="t('dialog.change_content_image.world')"
|
||||
width="850px"
|
||||
append-to-body
|
||||
@@ -16,106 +16,60 @@
|
||||
<span>{{ t('dialog.change_content_image.description') }}</span>
|
||||
<br />
|
||||
<el-button-group style="padding-bottom: 10px; padding-top: 10px">
|
||||
<el-button type="default" size="small" icon="el-icon-refresh" @click="refresh">{{
|
||||
t('dialog.change_content_image.refresh')
|
||||
}}</el-button>
|
||||
<el-button type="default" size="small" icon="el-icon-upload2" @click="uploadWorldImage">{{
|
||||
t('dialog.change_content_image.upload')
|
||||
}}</el-button>
|
||||
<!-- el-button(type="default" size="small" @click="deleteWorldImage" icon="el-icon-delete") Delete Latest Image-->
|
||||
<el-button type="default" size="small" :icon="Upload" @click="uploadWorldImage">
|
||||
{{ t('dialog.change_content_image.upload') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<br />
|
||||
<div v-for="image in previousImagesTable" :key="image.version" style="display: inline-block">
|
||||
<div
|
||||
v-if="image.file"
|
||||
class="x-change-image-item"
|
||||
style="cursor: pointer"
|
||||
:class="{ 'current-image': compareCurrentImage(image) }"
|
||||
@click="setWorldImage(image)">
|
||||
<img v-lazy="image.file.url" class="image" />
|
||||
</div>
|
||||
<div class="x-change-image-item">
|
||||
<img :src="previousImageUrl" class="img-size" loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Upload } from '@element-plus/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCurrentInstance, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { imageRequest } from '../../../api';
|
||||
import { AppGlobal } from '../../../service/appConfig';
|
||||
import { $throw } from '../../../service/request';
|
||||
import { extractFileId } from '../../../shared/utils';
|
||||
import { useGalleryStore, useWorldStore } from '../../../stores';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { worldRequest } from '../../../api';
|
||||
import { useWorldStore } from '../../../stores';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
const { worldDialog } = storeToRefs(useWorldStore());
|
||||
const { previousImagesTable } = storeToRefs(useGalleryStore());
|
||||
const { applyWorld } = useWorldStore();
|
||||
|
||||
const props = defineProps({
|
||||
changeWorldImageDialogVisible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
required: true
|
||||
},
|
||||
previousImagesFileId: {
|
||||
previousImageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:changeWorldImageDialogVisible', 'refresh']);
|
||||
|
||||
const changeWorldImageDialogLoading = ref(false);
|
||||
const worldImage = ref({
|
||||
base64File: '',
|
||||
fileMd5: '',
|
||||
base64SignatureFile: '',
|
||||
signatureMd5: '',
|
||||
fileId: '',
|
||||
avatarId: '',
|
||||
worldId: ''
|
||||
});
|
||||
|
||||
function uploadWorldImage() {
|
||||
document.getElementById('WorldImageUploadButton').click();
|
||||
}
|
||||
const emit = defineEmits(['update:changeWorldImageDialogVisible', 'update:previousImageUrl']);
|
||||
|
||||
function closeDialog() {
|
||||
emit('update:changeWorldImageDialogVisible', false);
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
emit('refresh', 'Change');
|
||||
}
|
||||
|
||||
async function resizeImageToFitLimits(file) {
|
||||
const response = await AppApi.ResizeImageToFitLimits(file);
|
||||
return response;
|
||||
}
|
||||
|
||||
async function genMd5(file) {
|
||||
const response = await AppApi.MD5File(file);
|
||||
return response;
|
||||
}
|
||||
|
||||
async function genSig(file) {
|
||||
const response = await AppApi.SignFile(file);
|
||||
return response;
|
||||
}
|
||||
|
||||
async function genLength(file) {
|
||||
const response = await AppApi.FileLength(file);
|
||||
return response;
|
||||
}
|
||||
|
||||
function onFileChangeWorldImage(e) {
|
||||
const clearFile = function () {
|
||||
const fileInput = /** @type {HTMLInputElement} */ (document.querySelector('#WorldImageUploadButton'));
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
const fileInput = /** @type{HTMLInputElement} */ (document.querySelector('#WorldImageUploadButton'));
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
@@ -125,9 +79,11 @@
|
||||
clearFile();
|
||||
return;
|
||||
}
|
||||
|
||||
// validate file
|
||||
if (files[0].size >= 100000000) {
|
||||
// 100MB
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.too_large'),
|
||||
type: 'error'
|
||||
});
|
||||
@@ -135,223 +91,57 @@
|
||||
return;
|
||||
}
|
||||
if (!files[0].type.match(/image.*/)) {
|
||||
$message({
|
||||
ElMessage({
|
||||
message: t('message.file.not_image'),
|
||||
type: 'error'
|
||||
});
|
||||
clearFile();
|
||||
return;
|
||||
}
|
||||
changeWorldImageDialogLoading.value = true;
|
||||
|
||||
const r = new FileReader();
|
||||
r.onload = async function (file) {
|
||||
r.onload = async function () {
|
||||
try {
|
||||
const base64File = await resizeImageToFitLimits(btoa(r.result.toString()));
|
||||
// 10MB
|
||||
const fileMd5 = await genMd5(base64File);
|
||||
const fileSizeInBytes = parseInt(file.total.toString(), 10);
|
||||
const base64SignatureFile = await genSig(base64File);
|
||||
const signatureMd5 = await genMd5(base64SignatureFile);
|
||||
const signatureSizeInBytes = parseInt(await genLength(base64SignatureFile), 10);
|
||||
const worldId = worldDialog.value.id;
|
||||
const { imageUrl } = worldDialog.value.ref;
|
||||
const fileId = extractFileId(imageUrl);
|
||||
if (!fileId) {
|
||||
$message({
|
||||
message: t('message.world.image_invalid'),
|
||||
type: 'error'
|
||||
});
|
||||
clearFile();
|
||||
return;
|
||||
}
|
||||
worldImage.value = {
|
||||
base64File,
|
||||
fileMd5,
|
||||
base64SignatureFile,
|
||||
signatureMd5,
|
||||
fileId,
|
||||
worldId,
|
||||
...worldImage.value
|
||||
};
|
||||
const params = {
|
||||
fileMd5,
|
||||
fileSizeInBytes,
|
||||
signatureMd5,
|
||||
signatureSizeInBytes
|
||||
};
|
||||
|
||||
// Upload chaining
|
||||
await initiateUpload(params, fileId);
|
||||
await initiateUpload(base64File);
|
||||
} catch (error) {
|
||||
console.error('World image upload process failed:', error);
|
||||
} finally {
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
clearFile();
|
||||
}
|
||||
};
|
||||
|
||||
changeWorldImageDialogLoading.value = true;
|
||||
r.readAsBinaryString(files[0]);
|
||||
}
|
||||
|
||||
// ------------ Upload Process Start ------------
|
||||
|
||||
async function initiateUpload(params, fileId) {
|
||||
const res = await imageRequest.uploadWorldImage(params, fileId);
|
||||
return worldImageInit(res);
|
||||
}
|
||||
|
||||
async function worldImageInit(args) {
|
||||
const fileId = args.json.id;
|
||||
const fileVersion = args.json.versions[args.json.versions.length - 1].version;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadWorldImageFileStart(params);
|
||||
return worldImageFileStart(res);
|
||||
}
|
||||
|
||||
async function worldImageFileStart(args) {
|
||||
const { url } = args.json;
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
url,
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
return uploadWorldImageFileAWS(params);
|
||||
}
|
||||
|
||||
async function uploadWorldImageFileAWS(params) {
|
||||
const json = await webApiService.execute({
|
||||
url: params.url,
|
||||
uploadFilePUT: true,
|
||||
fileData: worldImage.value.base64File,
|
||||
fileMIME: 'image/png',
|
||||
headers: {
|
||||
'Content-MD5': worldImage.value.fileMd5
|
||||
}
|
||||
});
|
||||
|
||||
if (json.status !== 200) {
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
$throw(json.status, 'World image upload failed', params.url);
|
||||
}
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return worldImageFileAWS(args);
|
||||
}
|
||||
|
||||
async function worldImageFileAWS(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadWorldImageFileFinish(params);
|
||||
return worldImageFileFinish(res);
|
||||
}
|
||||
|
||||
async function worldImageFileFinish(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadWorldImageSigStart(params);
|
||||
return worldImageSigStart(res);
|
||||
}
|
||||
|
||||
async function worldImageSigStart(args) {
|
||||
const { url } = args.json;
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
url,
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
return uploadWorldImageSigAWS(params);
|
||||
}
|
||||
|
||||
async function uploadWorldImageSigAWS(params) {
|
||||
const json = await webApiService.execute({
|
||||
url: params.url,
|
||||
uploadFilePUT: true,
|
||||
fileData: worldImage.value.base64SignatureFile,
|
||||
fileMIME: 'application/x-rsync-signature',
|
||||
headers: {
|
||||
'Content-MD5': worldImage.value.signatureMd5
|
||||
}
|
||||
});
|
||||
|
||||
if (json.status !== 200) {
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
$throw(json.status, 'World image upload failed', params.url);
|
||||
}
|
||||
const args = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
return worldImageSigAWS(args);
|
||||
}
|
||||
|
||||
async function worldImageSigAWS(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const params = {
|
||||
fileId,
|
||||
fileVersion
|
||||
};
|
||||
const res = await imageRequest.uploadWorldImageSigFinish(params);
|
||||
return worldImageSigFinish(res);
|
||||
}
|
||||
async function worldImageSigFinish(args) {
|
||||
const { fileId, fileVersion } = args.params;
|
||||
const parmas = {
|
||||
id: worldImage.value.worldId,
|
||||
imageUrl: `${AppGlobal.endpointDomain}/file/${fileId}/${fileVersion}/file`
|
||||
};
|
||||
const res = await imageRequest.setWorldImage(parmas);
|
||||
return worldImageSet(res);
|
||||
}
|
||||
|
||||
function worldImageSet(args) {
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
if (args.json.imageUrl === args.params.imageUrl) {
|
||||
$message({
|
||||
message: t('message.world.image_changed'),
|
||||
type: 'success'
|
||||
});
|
||||
refresh();
|
||||
} else {
|
||||
$throw(0, 'World image change failed', args.params.imageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------ Upload Process End ------------
|
||||
|
||||
function setWorldImage(image) {
|
||||
changeWorldImageDialogLoading.value = true;
|
||||
const parmas = {
|
||||
async function initiateUpload(base64File) {
|
||||
const args = await worldRequest.uploadWorldImage(base64File);
|
||||
const fileUrl = args.json.versions[args.json.versions.length - 1].file.url;
|
||||
const worldArgs = await worldRequest.saveWorld({
|
||||
id: worldDialog.value.id,
|
||||
imageUrl: `${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file`
|
||||
};
|
||||
imageRequest
|
||||
.setWorldImage(parmas)
|
||||
.then((args) => worldImageSet(args))
|
||||
.finally(() => {
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
closeDialog();
|
||||
});
|
||||
imageUrl: fileUrl
|
||||
});
|
||||
const ref = applyWorld(worldArgs.json);
|
||||
changeWorldImageDialogLoading.value = false;
|
||||
emit('update:previousImageUrl', ref.imageUrl);
|
||||
ElMessage({
|
||||
message: t('message.world.image_changed'),
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
// closeDialog();
|
||||
}
|
||||
|
||||
function compareCurrentImage(image) {
|
||||
if (
|
||||
`${AppGlobal.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file` ===
|
||||
worldDialog.value.ref.imageUrl
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
function uploadWorldImage() {
|
||||
document.getElementById('WorldImageUploadButton').click();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.img-size {
|
||||
width: 500px;
|
||||
height: 375px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
:visible.sync="isVisible"
|
||||
<el-dialog
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.set_world_tags.header')"
|
||||
width="400px"
|
||||
destroy-on-close
|
||||
@@ -20,7 +20,7 @@
|
||||
<el-input
|
||||
v-model="setWorldTagsDialog.authorTags"
|
||||
type="textarea"
|
||||
size="mini"
|
||||
size="small"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||
placeholder=""
|
||||
@@ -81,12 +81,13 @@
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { worldRequest } from '../../../api';
|
||||
import { useWorldStore } from '../../../stores';
|
||||
|
||||
@@ -115,8 +116,6 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const setWorldTagsDialog = ref({
|
||||
authorTags: '',
|
||||
contentTags: '',
|
||||
@@ -296,7 +295,7 @@
|
||||
tags
|
||||
})
|
||||
.then((args) => {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Tags updated',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<safe-dialog
|
||||
:visible.sync="isVisible"
|
||||
<el-dialog
|
||||
v-model="isVisible"
|
||||
:title="t('dialog.allowed_video_player_domains.header')"
|
||||
width="600px"
|
||||
destroy-on-close
|
||||
@@ -12,9 +12,9 @@
|
||||
v-model="urlList[index]"
|
||||
size="small"
|
||||
style="margin-top: 5px">
|
||||
<el-button slot="append" icon="el-icon-delete" @click="urlList.splice(index, 1)"></el-button>
|
||||
<el-button :icon="Delete" @click="urlList.splice(index, 1)"></el-button>
|
||||
</el-input>
|
||||
<el-button size="mini" style="margin-top: 5px" @click="urlList.push('')">
|
||||
<el-button size="small" style="margin-top: 5px" @click="urlList.push('')">
|
||||
{{ t('dialog.allowed_video_player_domains.add_domain') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -27,12 +27,16 @@
|
||||
{{ t('dialog.allowed_video_player_domains.save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</safe-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, getCurrentInstance } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { Delete } from '@element-plus/icons-vue';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { worldRequest } from '../../../api';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -44,8 +48,6 @@
|
||||
|
||||
const emit = defineEmits(['update:worldAllowedDomainsDialog']);
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const urlList = ref([]);
|
||||
@@ -79,7 +81,7 @@
|
||||
urlList: urlList.value
|
||||
})
|
||||
.then((args) => {
|
||||
proxy.$message({
|
||||
ElMessage({
|
||||
message: 'Allowed Video Player Domains updated',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
25
src/index.html
Normal file
25
src/index.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, user-scalable=no"
|
||||
/>
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<meta http-equiv="Cache-Control" content="no-cache" />
|
||||
|
||||
<title>VRCX</title>
|
||||
|
||||
<!-- <link rel="stylesheet" href="app.css" /> -->
|
||||
|
||||
<link rel="preconnect" href="https://api.vrchat.cloud" />
|
||||
<link rel="preconnect" href="https://files.vrchat.cloud" />
|
||||
<link rel="preconnect" href="https://d348imysud55la.cloudfront.net" />
|
||||
<link rel="dns-prefetch" href="https://assets.vrchat.com" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"language": "Čeština (CZ)",
|
||||
"language": "Čeština (cs)",
|
||||
"translator": "-",
|
||||
"nav_tooltip": {
|
||||
"feed": "Zdroj",
|
||||
@@ -617,7 +617,7 @@
|
||||
"launch_commands": {
|
||||
"header": "Launch Commands (Deep Links)",
|
||||
"docs": "Launch Command Docs",
|
||||
"show_confirmation_on_switch_avatar_enable": "Show confirmation diaglog before switching avatars",
|
||||
"show_confirmation_on_switch_avatar_enable": "Show confirmation dialog before switching avatars",
|
||||
"show_confirmation_on_switch_avatar_tooltip": "When disabled VRCX will not come to the front when switching avatars and ask for confirmation",
|
||||
"website_userscript": "VRC Website Userscript"
|
||||
},
|
||||
@@ -653,6 +653,7 @@
|
||||
"avatar_name_cache": "Avatar Name cache:",
|
||||
"instance_cache": "Instance cache:",
|
||||
"clear_cache": "Clear Cache",
|
||||
"refresh_cache": "Refresh Cache",
|
||||
"auto_clear_cache": "Auto Clear Cache",
|
||||
"show_console": "Show Console"
|
||||
},
|
||||
@@ -766,7 +767,6 @@
|
||||
"show_avatar_author": "Show Avatar Author",
|
||||
"show_fallback_avatar": "Show Fallback Avatar Details",
|
||||
"show_previous_instances": "Show Previous Instances",
|
||||
"show_previous_images": "Show Previous Images",
|
||||
"moderation_block": "Block",
|
||||
"moderation_unblock": "Unblock",
|
||||
"moderation_mute": "Mute",
|
||||
@@ -915,7 +915,6 @@
|
||||
"make_home": "Make Home",
|
||||
"reset_home": "Reset Home",
|
||||
"show_previous_instances": "Show Previous Instances",
|
||||
"show_previous_images": "Show Previous Images",
|
||||
"rename": "Rename",
|
||||
"change_description": "Change Description",
|
||||
"change_capacity": "Change Capacity",
|
||||
@@ -1002,7 +1001,6 @@
|
||||
"select_fallback": "Select Fallback Avatar",
|
||||
"block": "Block Avatar",
|
||||
"unblock": "Unblock Avatar",
|
||||
"show_previous_images": "Show Previous Images",
|
||||
"make_public": "Make Public",
|
||||
"make_private": "Make Private",
|
||||
"rename": "Rename",
|
||||
@@ -1118,7 +1116,6 @@
|
||||
"load_more": "Load more...",
|
||||
"sort_by": "Sort By:",
|
||||
"sorting": {
|
||||
"user_id": "User ID (Ascending)",
|
||||
"joined_at_asc": "Joined At (Ascending)",
|
||||
"joined_at_desc": "Joined At (Descending)"
|
||||
},
|
||||
@@ -1227,7 +1224,8 @@
|
||||
"group": "Group",
|
||||
"legacy": "Legacy",
|
||||
"roles": "Roles",
|
||||
"open_ingame": "Open in-game"
|
||||
"open_ingame": "Open in-game",
|
||||
"display_name": "Display Name (VRC+)"
|
||||
},
|
||||
"launch_options": {
|
||||
"header": "VRChat Launch Options",
|
||||
@@ -1518,12 +1516,8 @@
|
||||
"avatar": "Change Avatar Image",
|
||||
"world": "Change World Image",
|
||||
"description": "Recommended image size: 1200x900px (4:3)",
|
||||
"refresh": "Refresh",
|
||||
"upload": "Upload Image"
|
||||
},
|
||||
"previous_images": {
|
||||
"header": "Previous Images"
|
||||
},
|
||||
"previous_instances": {
|
||||
"header": "Previous Instances",
|
||||
"info": "Previous Instance Info",
|
||||
@@ -1663,6 +1657,10 @@
|
||||
"choose_group_placeholder": "Choose Group",
|
||||
"groups_with_moderation_permission": "Groups with Moderation Permission",
|
||||
"moderation_tools": "Moderation Tools"
|
||||
},
|
||||
"fullscreen_image": {
|
||||
"download_and_save_image": "Download and save image",
|
||||
"copy_image_to_clipboard": "Copy image to clipboard"
|
||||
}
|
||||
},
|
||||
"confirm": {
|
||||
|
||||
@@ -1,73 +1,44 @@
|
||||
import en from './en/en.json' assert { type: 'JSON' };
|
||||
import elements_en from 'element-ui/lib/locale/lang/en';
|
||||
|
||||
import es from './es/en.json' assert { type: 'JSON' };
|
||||
import elements_es from 'element-ui/lib/locale/lang/es';
|
||||
|
||||
import fr from './fr/en.json' assert { type: 'JSON' };
|
||||
import elements_fr from 'element-ui/lib/locale/lang/fr';
|
||||
|
||||
// import hu from './hu/en.json' assert { type: 'JSON' };
|
||||
// import elements_hu from 'element-ui/lib/locale/lang/hu';
|
||||
import hu from './hu/en.json' assert { type: 'JSON' };
|
||||
|
||||
import ja from './ja/en.json' assert { type: 'JSON' };
|
||||
import elements_ja from 'element-ui/lib/locale/lang/ja';
|
||||
|
||||
import ko from './ko/en.json' assert { type: 'JSON' };
|
||||
import elements_ko from 'element-ui/lib/locale/lang/ko';
|
||||
|
||||
import pl from './pl/en.json' assert { type: 'JSON' };
|
||||
import elements_pl from 'element-ui/lib/locale/lang/pl';
|
||||
|
||||
import pt from './pt/en.json' assert { type: 'JSON' };
|
||||
import elements_pt from 'element-ui/lib/locale/lang/pt';
|
||||
|
||||
import cz from './cz/en.json' assert { type: 'JSON' };
|
||||
import elements_cz from 'element-ui/lib/locale/lang/cs-CZ';
|
||||
import cs from './cs/en.json' assert { type: 'JSON' };
|
||||
|
||||
import ru_RU from './ru/en.json' assert { type: 'JSON' };
|
||||
import elements_ru from 'element-ui/lib/locale/lang/ru-RU';
|
||||
import ru from './ru/en.json' assert { type: 'JSON' };
|
||||
|
||||
import vi from './vi/en.json' assert { type: 'JSON' };
|
||||
import elements_vi from 'element-ui/lib/locale/lang/vi';
|
||||
|
||||
import zh_CN from './zh-CN/en.json' assert { type: 'JSON' };
|
||||
import elements_zh_CN from 'element-ui/lib/locale/lang/zh-CN';
|
||||
|
||||
import zh_TW from './zh-TW/en.json' assert { type: 'JSON' };
|
||||
import elements_zh_TW from 'element-ui/lib/locale/lang/zh-TW';
|
||||
|
||||
import th from './th/en.json' assert { type: 'JSON' };
|
||||
import elements_th from 'element-ui/lib/locale/lang/th';
|
||||
|
||||
const localized_en = { ...en, ...elements_en };
|
||||
const localized_es = { ...es, ...elements_es };
|
||||
const localized_fr = { ...fr, ...elements_fr };
|
||||
// const localized_hu = { ...hu, ...elements_hu };
|
||||
const localized_ja = { ...ja, ...elements_ja };
|
||||
const localized_ko = { ...ko, ...elements_ko };
|
||||
const localized_pl = { ...pl, ...elements_pl };
|
||||
const localized_pt = { ...pt, ...elements_pt };
|
||||
const localized_cz = { ...cz, ...elements_cz };
|
||||
const localized_ru = { ...ru_RU, ...elements_ru };
|
||||
const localized_vi = { ...vi, ...elements_vi };
|
||||
const localized_zh_CN = { ...zh_CN, ...elements_zh_CN };
|
||||
const localized_zh_TW = { ...zh_TW, ...elements_zh_TW };
|
||||
const localized_th = { ...th, ...elements_th };
|
||||
|
||||
export {
|
||||
localized_en as en,
|
||||
localized_es as es,
|
||||
localized_fr as fr,
|
||||
// localized_hu as hu,
|
||||
localized_ja as ja_JP,
|
||||
localized_ko as ko,
|
||||
localized_pl as pl,
|
||||
localized_pt as pt,
|
||||
localized_cz as cz,
|
||||
localized_ru as ru_RU,
|
||||
localized_vi as vi,
|
||||
localized_zh_CN as zh_CN,
|
||||
localized_zh_TW as zh_TW,
|
||||
localized_th as th
|
||||
en,
|
||||
es,
|
||||
fr,
|
||||
hu,
|
||||
ja,
|
||||
ko,
|
||||
pl,
|
||||
pt,
|
||||
cs,
|
||||
ru,
|
||||
vi,
|
||||
zh_CN,
|
||||
zh_TW,
|
||||
th
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user