Compare commits
27 Commits
4688043f6a
...
fr-transla
| Author | SHA1 | Date | |
|---|---|---|---|
|
6d8a8476b4
|
|||
|
0808266887
|
|||
|
a2071b9c05
|
|||
|
612e7c88a2
|
|||
|
1755b5cb54
|
|||
|
4a388dcfa9
|
|||
|
a0957715a5
|
|||
|
04a8ee3186
|
|||
|
b79a51748f
|
|||
|
95d9f65068
|
|||
|
a70968d30c
|
|||
|
6ebb73040b
|
|||
|
46f2443824
|
|||
|
eeab8354b6
|
|||
|
b5d8f288f9
|
|||
| 3c2dab4d21 | |||
| edb49f710a | |||
| 9ff21dc341 | |||
| 17962a37b3 | |||
| 4495177f02 | |||
| 14bb5c14f7 | |||
| 6101686a33 | |||
| bc6cde48de | |||
| 9ce213f949 | |||
| cd5bf3f06f | |||
| 52eaa0cf95 | |||
| 6edb90f4d7 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -2,15 +2,17 @@
|
|||||||
## files generated by popular Visual Studio add-ons.
|
## files generated by popular Visual Studio add-ons.
|
||||||
##
|
##
|
||||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
.idea
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.rsuser
|
*.rsuser
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
*.bak
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
MareSynchronos/.DS_Store
|
||||||
|
*.zip
|
||||||
|
UmbraServer_extracted/
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
*.userprefs
|
*.userprefs
|
||||||
|
|||||||
14
.gitmodules
vendored
14
.gitmodules
vendored
@@ -1,12 +1,12 @@
|
|||||||
[submodule "UmbraAPI"]
|
[submodule "MareAPI"]
|
||||||
path = UmbraAPI
|
path = MareAPI
|
||||||
url = https://git.umbra-sync.net/SirConstance/UmbraAPI.git
|
url = ssh://git@git.umbra-sync.net:1222/Keda/UmbraAPI.git
|
||||||
|
branch = main
|
||||||
[submodule "Penumbra.Api"]
|
[submodule "Penumbra.Api"]
|
||||||
path = Penumbra.Api
|
path = Penumbra.Api
|
||||||
url = https://github.com/Ottermandias/Penumbra.Api.git
|
url = https://github.com/Ottermandias/Penumbra.Api.git
|
||||||
|
branch = main
|
||||||
[submodule "Glamourer.Api"]
|
[submodule "Glamourer.Api"]
|
||||||
path = Glamourer.Api
|
path = Glamourer.Api
|
||||||
url = https://github.com/Ottermandias/Glamourer.Api
|
url = https://github.com/Ottermandias/Glamourer.Api.git
|
||||||
[submodule "Chaos.NaCl"]
|
branch = main
|
||||||
path = Chaos.NaCl
|
|
||||||
url = https://github.com/CodesInChaos/Chaos.NaCl.git
|
|
||||||
|
|||||||
Submodule Chaos.NaCl deleted from 2c861348dc
1
Glamourer.Api
Submodule
1
Glamourer.Api
Submodule
Submodule Glamourer.Api added at 54c1944dc7
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2022 Mare Synchronos
|
Copyright (c) 2022 Penumbra-Sync
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
1
MareAPI
Submodule
1
MareAPI
Submodule
Submodule MareAPI added at fa9b7bce43
@@ -5,7 +5,7 @@ VisualStudioVersion = 17.1.32328.378
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos", "MareSynchronos\MareSynchronos.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos", "MareSynchronos\MareSynchronos.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos.API", "UmbraAPI\MareSynchronosAPI\MareSynchronos.API.csproj", "{5A0B7434-8D89-4E90-B55C-B4A7AE1A6ADE}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos.API", "MareAPI\MareSynchronosAPI\MareSynchronos.API.csproj", "{5A0B7434-8D89-4E90-B55C-B4A7AE1A6ADE}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{585B740D-BA2C-429B-9CF3-B2D223423748}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{585B740D-BA2C-429B-9CF3-B2D223423748}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
|||||||
@@ -122,24 +122,24 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
public bool StorageisNTFS { get; private set; } = false;
|
public bool StorageisNTFS { get; private set; } = false;
|
||||||
|
|
||||||
public void StartMareWatcher(string? marePath)
|
public void StartMareWatcher(string? snowPath)
|
||||||
{
|
{
|
||||||
MareWatcher?.Dispose();
|
MareWatcher?.Dispose();
|
||||||
if (string.IsNullOrEmpty(marePath) || !Directory.Exists(marePath))
|
if (string.IsNullOrEmpty(snowPath) || !Directory.Exists(snowPath))
|
||||||
{
|
{
|
||||||
MareWatcher = null;
|
MareWatcher = null;
|
||||||
Logger.LogWarning("Mare file path is not set, cannot start the FSW for Mare.");
|
Logger.LogWarning("Umbra file path is not set, cannot start the FSW for Umbra.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DriveInfo di = new(new DirectoryInfo(_configService.Current.CacheFolder).Root.FullName);
|
DriveInfo di = new(new DirectoryInfo(_configService.Current.CacheFolder).Root.FullName);
|
||||||
StorageisNTFS = string.Equals("NTFS", di.DriveFormat, StringComparison.OrdinalIgnoreCase);
|
StorageisNTFS = string.Equals("NTFS", di.DriveFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
Logger.LogInformation("Mare Storage is on NTFS drive: {isNtfs}", StorageisNTFS);
|
Logger.LogInformation("Umbra Storage is on NTFS drive: {isNtfs}", StorageisNTFS);
|
||||||
|
|
||||||
Logger.LogDebug("Initializing Mare FSW on {path}", marePath);
|
Logger.LogDebug("Initializing Mare FSW on {path}", snowPath);
|
||||||
MareWatcher = new()
|
MareWatcher = new()
|
||||||
{
|
{
|
||||||
Path = marePath,
|
Path = snowPath,
|
||||||
InternalBufferSize = 8388608,
|
InternalBufferSize = 8388608,
|
||||||
NotifyFilter = NotifyFilters.CreationTime
|
NotifyFilter = NotifyFilters.CreationTime
|
||||||
| NotifyFilters.LastWrite
|
| NotifyFilters.LastWrite
|
||||||
@@ -161,7 +161,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
if (string.IsNullOrEmpty(substPath))
|
if (string.IsNullOrEmpty(substPath))
|
||||||
{
|
{
|
||||||
SubstWatcher = null;
|
SubstWatcher = null;
|
||||||
Logger.LogWarning("Mare file path is not set, cannot start the FSW for Mare.");
|
Logger.LogWarning("Umbra file path is not set, cannot start the FSW for Umbra.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
private void MareWatcher_FileChanged(object sender, FileSystemEventArgs e)
|
private void MareWatcher_FileChanged(object sender, FileSystemEventArgs e)
|
||||||
{
|
{
|
||||||
Logger.LogTrace("Mare FSW: FileChanged: {change} => {path}", e.ChangeType, e.FullPath);
|
Logger.LogTrace("Umbra FSW: FileChanged: {change} => {path}", e.ChangeType, e.FullPath);
|
||||||
|
|
||||||
if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
|
if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
|
||||||
|
|
||||||
@@ -631,7 +631,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
if (string.IsNullOrEmpty(_configService.Current.CacheFolder) || !Directory.Exists(_configService.Current.CacheFolder))
|
if (string.IsNullOrEmpty(_configService.Current.CacheFolder) || !Directory.Exists(_configService.Current.CacheFolder))
|
||||||
{
|
{
|
||||||
cacheDirExists = false;
|
cacheDirExists = false;
|
||||||
Logger.LogWarning("UmbraSync Cache directory is not set or does not exist.");
|
Logger.LogWarning("Umbra Cache directory is not set or does not exist.");
|
||||||
}
|
}
|
||||||
if (!penDirExists || !cacheDirExists)
|
if (!penDirExists || !cacheDirExists)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -236,7 +236,6 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
|
|
||||||
foreach (var entry in cleanedPaths)
|
foreach (var entry in cleanedPaths)
|
||||||
{
|
{
|
||||||
//_logger.LogDebug("Checking {path}", entry.Value);
|
|
||||||
|
|
||||||
if (dict.TryGetValue(entry.Value, out var entity))
|
if (dict.TryGetValue(entry.Value, out var entity))
|
||||||
{
|
{
|
||||||
@@ -366,8 +365,7 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
|
|
||||||
if (!entries.Exists(u => string.Equals(u.PrefixedFilePath, fileCache.PrefixedFilePath, StringComparison.OrdinalIgnoreCase)))
|
if (!entries.Exists(u => string.Equals(u.PrefixedFilePath, fileCache.PrefixedFilePath, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
//_logger.LogTrace("Adding to DB: {hash} => {path}", fileCache.Hash, fileCache.PrefixedFilePath);
|
entries.Add(fileCache);
|
||||||
entries.Add(fileCache);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +387,6 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
private FileCacheEntity? GetValidatedFileCache(FileCacheEntity fileCache)
|
private FileCacheEntity? GetValidatedFileCache(FileCacheEntity fileCache)
|
||||||
{
|
{
|
||||||
var resultingFileCache = ReplacePathPrefixes(fileCache);
|
var resultingFileCache = ReplacePathPrefixes(fileCache);
|
||||||
//_logger.LogTrace("Validating {path}", fileCache.PrefixedFilePath);
|
|
||||||
resultingFileCache = Validate(resultingFileCache);
|
resultingFileCache = Validate(resultingFileCache);
|
||||||
return resultingFileCache;
|
return resultingFileCache;
|
||||||
}
|
}
|
||||||
@@ -465,7 +462,7 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
if (!_ipcManager.Penumbra.APIAvailable || string.IsNullOrEmpty(_ipcManager.Penumbra.ModDirectory))
|
if (!_ipcManager.Penumbra.APIAvailable || string.IsNullOrEmpty(_ipcManager.Penumbra.ModDirectory))
|
||||||
{
|
{
|
||||||
_mareMediator.Publish(new NotificationMessage("Penumbra not connected",
|
_mareMediator.Publish(new NotificationMessage("Penumbra not connected",
|
||||||
"Could not load local file cache data. Penumbra is not connected or not properly set up. Please enable and/or configure Penumbra properly to use UmbraSync. After, reload UmbraSync in the Plugin installer.",
|
"Could not load local file cache data. Penumbra is not connected or not properly set up. Please enable and/or configure Penumbra properly to use Umbra. After, reload Umbra in the Plugin installer.",
|
||||||
MareConfiguration.Models.NotificationType.Error));
|
MareConfiguration.Models.NotificationType.Error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,8 +95,6 @@ public sealed class IpcCallerBrio : IIpcCaller
|
|||||||
if (gameObject == null) return default;
|
if (gameObject == null) return default;
|
||||||
var data = await _dalamudUtilService.RunOnFrameworkThread(() => _brioGetModelTransform.InvokeFunc(gameObject)).ConfigureAwait(false);
|
var data = await _dalamudUtilService.RunOnFrameworkThread(() => _brioGetModelTransform.InvokeFunc(gameObject)).ConfigureAwait(false);
|
||||||
if (data.Item1 == null || data.Item2 == null || data.Item3 == null) return default;
|
if (data.Item1 == null || data.Item2 == null || data.Item3 == null) return default;
|
||||||
//_logger.LogDebug("Getting Transform from Actor {actor}", gameObject.Name.TextValue);
|
|
||||||
|
|
||||||
return new WorldData()
|
return new WorldData()
|
||||||
{
|
{
|
||||||
PositionX = data.Item1.Value.X,
|
PositionX = data.Item1.Value.X,
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcC
|
|||||||
if (!apiAvailable && !_shownGlamourerUnavailable)
|
if (!apiAvailable && !_shownGlamourerUnavailable)
|
||||||
{
|
{
|
||||||
_shownGlamourerUnavailable = true;
|
_shownGlamourerUnavailable = true;
|
||||||
_mareMediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use UmbraSync. If you just updated Glamourer, ignore this message.",
|
_mareMediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Umbra. If you just updated Glamourer, ignore this message.",
|
||||||
NotificationType.Error));
|
NotificationType.Error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ public sealed class IpcCallerMoodles : IIpcCaller
|
|||||||
|
|
||||||
_moodlesApiVersion = pi.GetIpcSubscriber<int>("Moodles.Version");
|
_moodlesApiVersion = pi.GetIpcSubscriber<int>("Moodles.Version");
|
||||||
_moodlesOnChange = pi.GetIpcSubscriber<IPlayerCharacter, object>("Moodles.StatusManagerModified");
|
_moodlesOnChange = pi.GetIpcSubscriber<IPlayerCharacter, object>("Moodles.StatusManagerModified");
|
||||||
_moodlesGetStatus = pi.GetIpcSubscriber<nint, string>("Moodles.GetStatusManagerByPtr");
|
_moodlesGetStatus = pi.GetIpcSubscriber<nint, string>("Moodles.GetStatusManagerByPtrV2");
|
||||||
_moodlesSetStatus = pi.GetIpcSubscriber<nint, string, object>("Moodles.SetStatusManagerByPtr");
|
_moodlesSetStatus = pi.GetIpcSubscriber<nint, string, object>("Moodles.SetStatusManagerByPtrV2");
|
||||||
_moodlesRevertStatus = pi.GetIpcSubscriber<nint, object>("Moodles.ClearStatusManagerByPtr");
|
_moodlesRevertStatus = pi.GetIpcSubscriber<nint, object>("Moodles.ClearStatusManagerByPtrV2");
|
||||||
|
|
||||||
_moodlesOnChange.Subscribe(OnMoodlesChange);
|
_moodlesOnChange.Subscribe(OnMoodlesChange);
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ public sealed class IpcCallerMoodles : IIpcCaller
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
APIAvailable = _moodlesApiVersion.InvokeFunc() == 1;
|
APIAvailable = _moodlesApiVersion.InvokeFunc() == 3;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
|
|||||||
bool penumbraAvailable = false;
|
bool penumbraAvailable = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
penumbraAvailable = _pluginLoaded && _pluginVersion >= new Version(1, 0, 1, 0);
|
penumbraAvailable = _pluginLoaded && _pluginVersion >= new Version(1, 5, 1, 0);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
penumbraAvailable &= _penumbraEnabled.Invoke();
|
penumbraAvailable &= _penumbraEnabled.Invoke();
|
||||||
@@ -136,7 +136,7 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
|
|||||||
{
|
{
|
||||||
_shownPenumbraUnavailable = true;
|
_shownPenumbraUnavailable = true;
|
||||||
_mareMediator.Publish(new NotificationMessage("Penumbra inactive",
|
_mareMediator.Publish(new NotificationMessage("Penumbra inactive",
|
||||||
"Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use UmbraSync. If you just updated Penumbra, ignore this message.",
|
"Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Umbra. If you just updated Penumbra, ignore this message.",
|
||||||
NotificationType.Error));
|
NotificationType.Error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,9 +225,15 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
|
|||||||
|
|
||||||
return await _dalamudUtil.RunOnFrameworkThread(() =>
|
return await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||||
{
|
{
|
||||||
var collName = "UmbraSync_" + uid;
|
Guid collId;
|
||||||
var collId = _penumbraCreateNamedTemporaryCollection.Invoke(collName);
|
var collName = "ElfSync_" + uid;
|
||||||
|
PenumbraApiEc penEC = _penumbraCreateNamedTemporaryCollection.Invoke(uid, collName, out collId);
|
||||||
logger.LogTrace("Creating Temp Collection {collName}, GUID: {collId}", collName, collId);
|
logger.LogTrace("Creating Temp Collection {collName}, GUID: {collId}", collName, collId);
|
||||||
|
if (penEC != PenumbraApiEc.Success)
|
||||||
|
{
|
||||||
|
logger.LogError("Failed to create temporary collection for {collName} with error code {penEC}. Please include this line in any error reports", collName, penEC);
|
||||||
|
return Guid.Empty;
|
||||||
|
}
|
||||||
return collId;
|
return collId;
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ public sealed class IpcCallerPetNames : IIpcCaller
|
|||||||
_dalamudUtil = dalamudUtil;
|
_dalamudUtil = dalamudUtil;
|
||||||
_mareMediator = mareMediator;
|
_mareMediator = mareMediator;
|
||||||
|
|
||||||
_petnamesReady = pi.GetIpcSubscriber<object>("PetRenamer.Ready");
|
_petnamesReady = pi.GetIpcSubscriber<object>("PetRenamer.OnReady");
|
||||||
_petnamesDisposing = pi.GetIpcSubscriber<object>("PetRenamer.Disposing");
|
_petnamesDisposing = pi.GetIpcSubscriber<object>("PetRenamer.OnDisposing");
|
||||||
_apiVersion = pi.GetIpcSubscriber<(uint, uint)>("PetRenamer.ApiVersion");
|
_apiVersion = pi.GetIpcSubscriber<(uint, uint)>("PetRenamer.ApiVersion");
|
||||||
_enabled = pi.GetIpcSubscriber<bool>("PetRenamer.Enabled");
|
_enabled = pi.GetIpcSubscriber<bool>("PetRenamer.IsEnabled");
|
||||||
|
|
||||||
_playerDataChanged = pi.GetIpcSubscriber<string, object>("PetRenamer.PlayerDataChanged");
|
_playerDataChanged = pi.GetIpcSubscriber<string, object>("PetRenamer.OnPlayerDataChanged");
|
||||||
_getPlayerData = pi.GetIpcSubscriber<string>("PetRenamer.GetPlayerData");
|
_getPlayerData = pi.GetIpcSubscriber<string>("PetRenamer.GetPlayerData");
|
||||||
_setPlayerData = pi.GetIpcSubscriber<string, object>("PetRenamer.SetPlayerData");
|
_setPlayerData = pi.GetIpcSubscriber<string, object>("PetRenamer.SetPlayerData");
|
||||||
_clearPlayerData = pi.GetIpcSubscriber<ushort, object>("PetRenamer.ClearPlayerData");
|
_clearPlayerData = pi.GetIpcSubscriber<ushort, object>("PetRenamer.ClearPlayerData");
|
||||||
@@ -56,7 +56,7 @@ public sealed class IpcCallerPetNames : IIpcCaller
|
|||||||
APIAvailable = _enabled?.InvokeFunc() ?? false;
|
APIAvailable = _enabled?.InvokeFunc() ?? false;
|
||||||
if (APIAvailable)
|
if (APIAvailable)
|
||||||
{
|
{
|
||||||
APIAvailable = _apiVersion?.InvokeFunc() is { Item1: 3, Item2: >= 1 };
|
APIAvailable = _apiVersion?.InvokeFunc() is { Item1: 4, Item2: >= 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -66,11 +66,11 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
|||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Starting IpcProvider Service");
|
_logger.LogDebug("Starting IpcProvider Service");
|
||||||
_loadFileProvider = _pi.GetIpcProvider<string, IGameObject, bool>("UmbraSyncSync.LoadMcdf");
|
_loadFileProvider = _pi.GetIpcProvider<string, IGameObject, bool>("ElfSync.LoadMcdf");
|
||||||
_loadFileProvider.RegisterFunc(LoadMcdf);
|
_loadFileProvider.RegisterFunc(LoadMcdf);
|
||||||
_loadFileAsyncProvider = _pi.GetIpcProvider<string, IGameObject, Task<bool>>("UmbraSyncSync.LoadMcdfAsync");
|
_loadFileAsyncProvider = _pi.GetIpcProvider<string, IGameObject, Task<bool>>("UmbraSync.LoadMcdfAsync");
|
||||||
_loadFileAsyncProvider.RegisterFunc(LoadMcdfAsync);
|
_loadFileAsyncProvider.RegisterFunc(LoadMcdfAsync);
|
||||||
_handledGameAddresses = _pi.GetIpcProvider<List<nint>>("UmbraSyncSync.GetHandledAddresses");
|
_handledGameAddresses = _pi.GetIpcProvider<List<nint>>("UmbraSync.GetHandledAddresses");
|
||||||
_handledGameAddresses.RegisterFunc(GetHandledAddresses);
|
_handledGameAddresses.RegisterFunc(GetHandledAddresses);
|
||||||
|
|
||||||
_loadFileProviderMare = _pi.GetIpcProvider<string, IGameObject, bool>("MareSynchronos.LoadMcdf");
|
_loadFileProviderMare = _pi.GetIpcProvider<string, IGameObject, bool>("MareSynchronos.LoadMcdf");
|
||||||
|
|||||||
48
MareSynchronos/Localization/LocalizationExtensions.cs
Normal file
48
MareSynchronos/Localization/LocalizationExtensions.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Localization;
|
||||||
|
|
||||||
|
public static class LocalizationExtensions
|
||||||
|
{
|
||||||
|
public static string Loc(this string fallbackEnglish, params object[] formatArgs)
|
||||||
|
{
|
||||||
|
var service = LocalizationService.Instance;
|
||||||
|
if (service == null) return FormatFallback(fallbackEnglish, formatArgs);
|
||||||
|
return service.GetString(fallbackEnglish, formatArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string LocKey(this string key, string fallbackEnglish, params object[] formatArgs)
|
||||||
|
{
|
||||||
|
var service = LocalizationService.Instance;
|
||||||
|
if (service == null) return FormatFallback(fallbackEnglish, formatArgs);
|
||||||
|
return service.GetString(key, fallbackEnglish, formatArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string LocLabel(this string labelWithId, params object[] formatArgs)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(labelWithId)) return labelWithId;
|
||||||
|
|
||||||
|
var separatorIndex = labelWithId.IndexOf("##", StringComparison.Ordinal);
|
||||||
|
if (separatorIndex < 0)
|
||||||
|
{
|
||||||
|
return labelWithId.Loc(formatArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
var label = labelWithId[..separatorIndex];
|
||||||
|
var id = labelWithId[separatorIndex..];
|
||||||
|
return string.Concat(label.Loc(formatArgs), id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatFallback(string fallback, params object[] args)
|
||||||
|
{
|
||||||
|
if (args == null || args.Length == 0) return fallback;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, fallback, args);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
231
MareSynchronos/Localization/LocalizationService.cs
Normal file
231
MareSynchronos/Localization/LocalizationService.cs
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using MareSynchronos.MareConfiguration;
|
||||||
|
using MareSynchronos.MareConfiguration.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Localization;
|
||||||
|
|
||||||
|
public class LocalizationService
|
||||||
|
{
|
||||||
|
private readonly ILogger<LocalizationService> _logger;
|
||||||
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
|
private readonly MareConfigService _configService;
|
||||||
|
private readonly Dictionary<LocalizationLanguage, Dictionary<string, string>> _translations = new();
|
||||||
|
private readonly HashSet<string> _missingLocalizationsLogged = new(StringComparer.Ordinal);
|
||||||
|
private readonly Lock _missingLocalizationsLock = new();
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
|
{
|
||||||
|
AllowTrailingCommas = true
|
||||||
|
};
|
||||||
|
|
||||||
|
public static LocalizationService? Instance { get; private set; }
|
||||||
|
|
||||||
|
public LocalizationService(ILogger<LocalizationService> logger, IDalamudPluginInterface pluginInterface, MareConfigService configService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
|
_configService = configService;
|
||||||
|
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
foreach (var language in Enum.GetValues<LocalizationLanguage>())
|
||||||
|
{
|
||||||
|
_translations[language] = LoadLanguage(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalizationLanguage CurrentLanguage => _configService.Current.Language;
|
||||||
|
|
||||||
|
public static IEnumerable<LocalizationLanguage> SupportedLanguages => Enum.GetValues<LocalizationLanguage>();
|
||||||
|
|
||||||
|
public string GetString(string fallbackEnglish, params object[] formatArgs)
|
||||||
|
{
|
||||||
|
return GetStringInternal(null, fallbackEnglish, formatArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetString(string key, string fallbackEnglish, params object[] formatArgs)
|
||||||
|
{
|
||||||
|
return GetStringInternal(key, fallbackEnglish, formatArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetLanguageDisplayName(LocalizationLanguage language)
|
||||||
|
{
|
||||||
|
var fallback = language switch
|
||||||
|
{
|
||||||
|
LocalizationLanguage.French => "Français",
|
||||||
|
LocalizationLanguage.English => "English",
|
||||||
|
_ => language.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetRawString($"Language.DisplayName.{language}", fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadLanguage(LocalizationLanguage language)
|
||||||
|
{
|
||||||
|
_translations[language] = LoadLanguage(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadAll()
|
||||||
|
{
|
||||||
|
foreach (var language in Enum.GetValues<LocalizationLanguage>())
|
||||||
|
{
|
||||||
|
ReloadLanguage(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetStringInternal(string? key, string fallbackEnglish, params object[] formatArgs)
|
||||||
|
{
|
||||||
|
var usedKey = string.IsNullOrWhiteSpace(key) ? fallbackEnglish : key;
|
||||||
|
var text = GetRawString(usedKey, fallbackEnglish);
|
||||||
|
|
||||||
|
if (formatArgs != null && formatArgs.Length > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, text, formatArgs);
|
||||||
|
}
|
||||||
|
catch (FormatException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Localization format mismatch for key {Key} with text '{Text}'", usedKey, text);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, fallbackEnglish, formatArgs);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return fallbackEnglish;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetRawString(string key, string fallbackEnglish)
|
||||||
|
{
|
||||||
|
if (TryGetString(CurrentLanguage, key, out var localized))
|
||||||
|
{
|
||||||
|
return localized;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogMissingLocalization(CurrentLanguage, key, fallbackEnglish);
|
||||||
|
|
||||||
|
if (TryGetString(LocalizationLanguage.English, key, out var english))
|
||||||
|
{
|
||||||
|
return english;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentLanguage != LocalizationLanguage.English)
|
||||||
|
{
|
||||||
|
LogMissingLocalization(LocalizationLanguage.English, key, fallbackEnglish);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackEnglish;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetString(LocalizationLanguage language, string key, out string value)
|
||||||
|
{
|
||||||
|
var dictionary = GetDictionary(language);
|
||||||
|
if (dictionary.TryGetValue(key, out var text) && !string.IsNullOrWhiteSpace(text))
|
||||||
|
{
|
||||||
|
value = text;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = string.Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, string> GetDictionary(LocalizationLanguage language)
|
||||||
|
{
|
||||||
|
if (!_translations.TryGetValue(language, out var dictionary))
|
||||||
|
{
|
||||||
|
dictionary = LoadLanguage(language);
|
||||||
|
_translations[language] = dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, string> LoadLanguage(LocalizationLanguage language)
|
||||||
|
{
|
||||||
|
var baseDirectory = _pluginInterface.AssemblyLocation.DirectoryName ?? string.Empty;
|
||||||
|
var localizationDirectory = Path.Combine(baseDirectory, "Localization");
|
||||||
|
var languageCode = GetLanguageCode(language);
|
||||||
|
var filePath = Path.Combine(localizationDirectory, $"{languageCode}.json");
|
||||||
|
|
||||||
|
Dictionary<string, string>? translations = null;
|
||||||
|
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var stream = File.OpenRead(filePath);
|
||||||
|
translations = DeserializeTranslations(stream);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to load localization data from {FilePath}", filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translations == null)
|
||||||
|
{
|
||||||
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
|
var resourceName = $"{assembly.GetName().Name}.Localization.{languageCode}.json";
|
||||||
|
using var resourceStream = assembly.GetManifestResourceStream(resourceName);
|
||||||
|
if (resourceStream != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
translations = DeserializeTranslations(resourceStream);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to load embedded localization resource {Resource}", resourceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translations == null)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Localization data for {Language} not found on disk or embedded, using empty dictionary.", language);
|
||||||
|
translations = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
return translations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogMissingLocalization(LocalizationLanguage language, string key, string fallback)
|
||||||
|
{
|
||||||
|
var marker = $"{language}:{key}";
|
||||||
|
using var scope = _missingLocalizationsLock.EnterScope();
|
||||||
|
if (_missingLocalizationsLogged.Contains(marker)) return;
|
||||||
|
_missingLocalizationsLogged.Add(marker);
|
||||||
|
|
||||||
|
_logger.LogDebug("Missing localization for {Language} ({Key}). Using fallback '{Fallback}'.", language, key, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetLanguageCode(LocalizationLanguage language) => language switch
|
||||||
|
{
|
||||||
|
LocalizationLanguage.French => "fr",
|
||||||
|
LocalizationLanguage.English => "en",
|
||||||
|
_ => language.ToString().ToLowerInvariant(),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Dictionary<string, string>? DeserializeTranslations(Stream stream)
|
||||||
|
{
|
||||||
|
var translations = JsonSerializer.Deserialize<Dictionary<string, string>>(stream, JsonOptions);
|
||||||
|
return translations != null
|
||||||
|
? new Dictionary<string, string>(translations, StringComparer.OrdinalIgnoreCase)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
579
MareSynchronos/Localization/en.json
Normal file
579
MareSynchronos/Localization/en.json
Normal file
@@ -0,0 +1,579 @@
|
|||||||
|
{
|
||||||
|
"Language.DisplayName.French": "French",
|
||||||
|
"Language.DisplayName.English": "English",
|
||||||
|
"Settings.Plugins.MandatoryHeading": "Mandatory Plugins",
|
||||||
|
"Settings.Plugins.MandatoryLabel": "Mandatory Plugins:",
|
||||||
|
"Settings.Plugins.OptionalHeading": "Optional Addons",
|
||||||
|
"Settings.Plugins.OptionalLabel": "Optional Addons:",
|
||||||
|
"Settings.Plugins.OptionalDescription": "These addons are not required for basic operation, but without them you may not see others as intended.",
|
||||||
|
"Settings.Plugins.Tooltip.Available": "{0} is available and up to date.",
|
||||||
|
"Settings.Plugins.Tooltip.Unavailable": "{0} is unavailable or not up to date.",
|
||||||
|
"Settings.Plugins.WarningMandatoryMissing": "You need to install both Penumbra and Glamourer and keep them up to date to use Umbra.",
|
||||||
|
"Settings.General.LocalizationHeading": "Localization",
|
||||||
|
"Settings.General.Language": "Language",
|
||||||
|
"Settings.General.Language.Description": "Select the plugin language. Any missing translations will be shown in English.",
|
||||||
|
"Settings.General.NotesHeading": "Notes",
|
||||||
|
"Settings.General.Notes.Export": "Export all your user notes to clipboard",
|
||||||
|
"Settings.General.Notes.Import": "Import notes from clipboard",
|
||||||
|
"Settings.General.Notes.Overwrite": "Overwrite existing notes",
|
||||||
|
"Settings.General.Notes.Overwrite.Description": "If this option is selected all already existing notes for UIDs will be overwritten by the imported notes.",
|
||||||
|
"Settings.General.Notes.Import.Success": "User Notes successfully imported",
|
||||||
|
"Settings.General.Notes.Import.Failure": "Attempt to import notes from clipboard failed. Check formatting and try again",
|
||||||
|
"Settings.General.Notes.OpenPopup": "Open Notes Popup on user addition",
|
||||||
|
"Settings.General.Notes.OpenPopup.Description": "This will open a popup that allows you to set the notes for a user after successfully adding them to your individual pairs.",
|
||||||
|
"Settings.Transfers.Blocked.Description": "Files that you attempted to upload or download that were forbidden to be transferred by their creators will appear here. If you see file paths from your drive here, then those files were not allowed to be uploaded. If you see hashes, those files were not allowed to be downloaded. Ask your paired friend to send you the mod in question through other means or acquire the mod yourself.",
|
||||||
|
"Settings.Transfers.Blocked.Column.Hash": "Hash/Filename",
|
||||||
|
"Settings.Transfers.Blocked.Column.ForbiddenBy": "Forbidden by",
|
||||||
|
"Settings.Transfers.Blocked.Tab": "Blocked Transfers",
|
||||||
|
"Settings.Transfers.Heading": "Transfer Settings",
|
||||||
|
"Settings.Transfers.GlobalLimit.Label": "Global Download Speed Limit",
|
||||||
|
"Settings.Transfers.GlobalLimit.Unit.BytePerSec": "Byte/s",
|
||||||
|
"Settings.Transfers.GlobalLimit.Unit.KiloBytePerSec": "KB/s",
|
||||||
|
"Settings.Transfers.GlobalLimit.Unit.MegaBytePerSec": "MB/s",
|
||||||
|
"Settings.Transfers.GlobalLimit.Hint": "0 = No limit/infinite",
|
||||||
|
"Settings.Transfers.MaxParallelDownloads": "Maximum Parallel Downloads",
|
||||||
|
"Settings.Transfers.AutoDetect.Heading": "AutoDetect",
|
||||||
|
"Settings.Transfers.AutoDetect.EnableNearby": "Enable Nearby detection (beta)",
|
||||||
|
"Settings.Transfers.AutoDetect.AllowRequests": "Allow pair requests",
|
||||||
|
"Settings.Transfers.AutoDetect.Notification.Title": "Nearby Detection",
|
||||||
|
"Settings.Transfers.AutoDetect.Notification.Enabled": "Pair requests enabled: others can invite you.",
|
||||||
|
"Settings.Transfers.AutoDetect.Notification.Disabled": "Pair requests disabled: others cannot invite you.",
|
||||||
|
"Settings.Transfers.AutoDetect.MaxDistance": "Max distance (meters)",
|
||||||
|
"Settings.Transfers.UI.Heading": "Transfer UI",
|
||||||
|
"Settings.Transfers.UI.ShowWindow": "Show separate transfer window",
|
||||||
|
"Settings.Transfers.UI.ShowWindow.Description": "The download window will show the current progress of outstanding downloads.\n\nWhat do W/Q/P/D stand for?\nW = Waiting for Slot (see Maximum Parallel Downloads)\nQ = Queued on Server, waiting for queue ready signal\nP = Processing download (aka downloading)\nD = Decompressing download",
|
||||||
|
"Settings.Transfers.UI.EditWindowPosition": "Edit Transfer Window position",
|
||||||
|
"Settings.Transfers.UI.ShowTransferBars": "Show transfer bars rendered below players",
|
||||||
|
"Settings.Transfers.UI.ShowTransferBars.Description": "This will render a progress bar during the download at the feet of the player you are downloading from.",
|
||||||
|
"Settings.Transfers.UI.ShowDownloadText": "Show Download Text",
|
||||||
|
"Settings.Transfers.UI.ShowDownloadText.Description": "Shows download text (amount of MiB downloaded) in the transfer bars",
|
||||||
|
"Settings.Transfers.UI.BarWidth": "Transfer Bar Width",
|
||||||
|
"Settings.Transfers.UI.BarWidth.Description": "Width of the displayed transfer bars (will never be less wide than the displayed text)",
|
||||||
|
"Settings.Transfers.UI.BarHeight": "Transfer Bar Height",
|
||||||
|
"Settings.Transfers.UI.BarHeight.Description": "Height of the displayed transfer bars (will never be less tall than the displayed text)",
|
||||||
|
"Settings.Transfers.UI.ShowUploading": "Show 'Uploading' text below players that are currently uploading",
|
||||||
|
"Settings.Transfers.UI.ShowUploading.Description": "This will render an 'Uploading' text at the feet of the player that is in progress of uploading data.",
|
||||||
|
"Settings.Transfers.UI.ShowUploadingBigText": "Large font for 'Uploading' text",
|
||||||
|
"Settings.Transfers.UI.ShowUploadingBigText.Description": "This will render an 'Uploading' text in a larger font.",
|
||||||
|
"Settings.Transfers.Current.Heading": "Current Transfers",
|
||||||
|
"Settings.Transfers.Current.Tab": "Transfers",
|
||||||
|
"Settings.Transfers.Current.Uploads": "Uploads",
|
||||||
|
"Settings.Transfers.Current.Uploads.Column.File": "File",
|
||||||
|
"Settings.Transfers.Current.Uploads.Column.Uploaded": "Uploaded",
|
||||||
|
"Settings.Transfers.Current.Uploads.Column.Size": "Size",
|
||||||
|
"Settings.Transfers.Current.Downloads": "Downloads",
|
||||||
|
"Settings.Transfers.Current.Downloads.Column.User": "User",
|
||||||
|
"Settings.Transfers.Current.Downloads.Column.Server": "Server",
|
||||||
|
"Settings.Transfers.Current.Downloads.Column.Files": "Files",
|
||||||
|
"Settings.Transfers.Current.Downloads.Column.Download": "Download",
|
||||||
|
"Settings.Storage.Heading": "Storage",
|
||||||
|
"Settings.Storage.Description": "Umbra stores downloaded files from paired people permanently. This is to improve loading performance and requiring less downloads. The storage governs itself by clearing data beyond the set storage size. Please set the storage size accordingly. It is not necessary to manually clear the storage.",
|
||||||
|
"Settings.Service.ActionsHeading": "Service Actions",
|
||||||
|
"Settings.Service.Actions.DeleteAccount": "Delete account",
|
||||||
|
"Settings.Service.Actions.DeleteAccountPopup": "Delete your account?",
|
||||||
|
"Settings.Service.Actions.DeleteAccount.Description": "Completely deletes your currently connected account.",
|
||||||
|
"Settings.Service.Actions.DeleteAccount.Popup.Body1": "Your account and all associated files and data on the service will be deleted.",
|
||||||
|
"Settings.Service.Actions.DeleteAccount.Popup.Body2": "Your UID will be removed from all pairing lists.",
|
||||||
|
"Settings.Service.Actions.DeleteAccount.Popup.Confirm": "Are you sure you want to continue?",
|
||||||
|
"Settings.Service.Actions.DeleteAccount.Popup.Cancel": "Cancel",
|
||||||
|
"Settings.Service.SettingsHeading": "Service & Character Settings",
|
||||||
|
"Settings.Service.ReconnectWarning": "For any changes to be applied to the current service you need to reconnect to the service.",
|
||||||
|
"Settings.Service.Tabs.CharacterAssignments": "Character Assignments",
|
||||||
|
"Settings.Service.Tabs.SecretKey": "Secret Key Management",
|
||||||
|
"Settings.Service.Tabs.ServiceSettings": "Service Settings",
|
||||||
|
"Settings.Service.Character.Assignments.Description": "Characters listed here will connect with the specified secret key.",
|
||||||
|
"Settings.Service.Character.Assignments.TooltipCurrent": "Current character",
|
||||||
|
"Settings.Service.Character.Assignments.DeleteTooltip": "Delete character assignment",
|
||||||
|
"Settings.Service.Character.Assignments.AddCurrent": "Add current character",
|
||||||
|
"Settings.Service.Character.Assignments.NoKeys": "You need to add a Secret Key first before adding Characters.",
|
||||||
|
"Settings.Service.SecretKey.DisplayName": "Secret Key Display Name",
|
||||||
|
"Settings.Service.SecretKey.Value": "Secret Key",
|
||||||
|
"Settings.Service.SecretKey.AssignCurrent": "Assign current character",
|
||||||
|
"Settings.Service.SecretKey.AssignTooltip": "Use this secret key for {0} @ {1}",
|
||||||
|
"Settings.Service.SecretKey.Delete": "Delete Secret Key",
|
||||||
|
"Settings.Service.SecretKey.DeleteTooltip": "Hold CTRL to delete this secret key entry",
|
||||||
|
"Settings.Service.SecretKey.InUse": "This key is currently assigned to a character and cannot be edited or deleted.",
|
||||||
|
"Settings.Service.SecretKey.Add": "Add new Secret Key",
|
||||||
|
"Settings.Service.SecretKey.NewFriendlyName": "New Secret Key",
|
||||||
|
"Settings.Service.SecretKey.RegisterAccount": "Register a new Umbra account",
|
||||||
|
"Settings.Service.SecretKey.RegisterFailed": "An unknown error occured. Please try again later.",
|
||||||
|
"Settings.Service.SecretKey.RegisterSuccess": "New account registered.\nPlease keep a copy of your secret key in case you need to reset your plugins, or to use it on another PC.",
|
||||||
|
"Settings.Service.SecretKey.RegisteredFriendlyName": "{0} (registered {1})",
|
||||||
|
"Settings.Service.SecretKey.Registering": "Sending request...",
|
||||||
|
"Settings.Service.ServiceTab.Uri": "Service URI",
|
||||||
|
"Settings.Service.ServiceTab.UriReadOnlyHint": "You cannot edit the URI of the main service.",
|
||||||
|
"Settings.Service.ServiceTab.Name": "Service Name",
|
||||||
|
"Settings.Service.ServiceTab.NameReadOnlyHint": "You cannot edit the name of the main service.",
|
||||||
|
"Settings.Service.ServiceTab.Delete": "Delete Service",
|
||||||
|
"Settings.Service.ServiceTab.DeleteHint": "Hold CTRL to delete this service",
|
||||||
|
"Settings.Advanced.Heading": "Advanced",
|
||||||
|
"Settings.Advanced.Tab": "Advanced",
|
||||||
|
"Settings.Advanced.Api.Enable": "Enable Umbra Sync API",
|
||||||
|
"Settings.Advanced.Api.Description": "Enables handling of the Umbra Sync API. This currently includes:\n\n - MCDF loading support for other plugins\n - Blocking Moodles applications to paired users\n\nIf the Umbra Sync plugin is loaded while this option is enabled, control of its API will be relinquished.",
|
||||||
|
"Settings.Advanced.Api.Status.Active": "Umbra API active!",
|
||||||
|
"Settings.Advanced.Api.Status.Disabled": "Umbra API inactive: Option is disabled",
|
||||||
|
"Settings.Advanced.Api.Status.PluginLoaded": "Umbra API inactive: Umbra plugin is loaded",
|
||||||
|
"Settings.Advanced.Api.Status.Unknown": "Umbra API inactive: Unknown reason",
|
||||||
|
"Settings.Advanced.EventViewer.LogToDisk": "Log Event Viewer data to disk",
|
||||||
|
"Settings.Advanced.EventViewer.Open": "Open Event Viewer",
|
||||||
|
"Settings.Advanced.HoldCombat": "Hold application during combat",
|
||||||
|
"Settings.Advanced.SerializedApplications": "Serialized player applications",
|
||||||
|
"Settings.Advanced.SerializedApplications.Description": "Experimental - May reduce issues in crowded areas",
|
||||||
|
"Settings.Advanced.DebugHeading": "Debug",
|
||||||
|
"Settings.Advanced.Debug.LastCreatedTree": "Last created character data",
|
||||||
|
"Settings.Advanced.Debug.CopyButton": "[DEBUG] Copy Last created Character Data to clipboard",
|
||||||
|
"Settings.Advanced.Debug.CopyError": "ERROR: No created character data, cannot copy.",
|
||||||
|
"Settings.Advanced.Debug.CopyTooltip": "Use this when reporting mods being rejected from the server.",
|
||||||
|
"Settings.Advanced.LogLevel": "Log Level",
|
||||||
|
"Settings.Advanced.Performance.LogCounters": "Log Performance Counters",
|
||||||
|
"Settings.Advanced.Performance.LogCounters.Description": "Enabling this can incur a (slight) performance impact. Enabling this for extended periods of time is not recommended.",
|
||||||
|
"Settings.Advanced.Performance.PrintStats": "Print Performance Stats to /xllog",
|
||||||
|
"Settings.Advanced.Performance.PrintStatsRecent": "Print Performance Stats (last 60s) to /xllog",
|
||||||
|
"Settings.Advanced.ActiveBlocks": "Active Character Blocks",
|
||||||
|
"Settings.UI.Heading": "UI",
|
||||||
|
"Settings.UI.EnableRightClick": "Enable Game Right Click Menu Entries",
|
||||||
|
"Settings.UI.EnableRightClick.Description": "This will add Umbra related right click menu entries in the game UI on paired players.",
|
||||||
|
"Settings.UI.EnableDtrEntry": "Display status and visible pair count in Server Info Bar",
|
||||||
|
"Settings.UI.EnableDtrEntry.Description": "This will add Umbra connection status and visible pair count in the Server Info Bar.\nYou can further configure this through your Dalamud Settings.",
|
||||||
|
"Settings.UI.Dtr.ShowUid": "Show visible character's UID in tooltip",
|
||||||
|
"Settings.UI.Dtr.PreferNotes": "Prefer notes over player names in tooltip",
|
||||||
|
"Settings.UI.Dtr.UseColors": "Color-code the Server Info Bar entry according to status",
|
||||||
|
"Settings.UI.Dtr.ColorDefault": "Default",
|
||||||
|
"Settings.UI.Dtr.ColorNotConnected": "Not Connected",
|
||||||
|
"Settings.UI.Dtr.ColorPairsInRange": "Pairs in Range",
|
||||||
|
"Settings.UI.NameColors.Enable": "Color nameplates of paired players",
|
||||||
|
"Settings.UI.NameColors.Character": "Character Name Color",
|
||||||
|
"Settings.UI.NameColors.Blocked": "Blocked Character Color",
|
||||||
|
"Settings.UI.VisibleGroup": "Show separate Visible group",
|
||||||
|
"Settings.UI.VisibleGroup.Description": "This will show all currently visible users in a special 'Visible' group in the main UI.",
|
||||||
|
"Settings.UI.OfflineGroup": "Show separate Offline group",
|
||||||
|
"Settings.UI.OfflineGroup.Description": "This will show all currently offline users in a special 'Offline' group in the main UI.",
|
||||||
|
"Settings.UI.ShowPlayerNames": "Show player names",
|
||||||
|
"Settings.UI.ShowPlayerNames.Description": "This will show character names instead of UIDs when possible",
|
||||||
|
"Settings.UI.Profiles.Show": "Show Profiles on Hover",
|
||||||
|
"Settings.UI.Profiles.Show.Description": "This will show the configured user profile after a set delay",
|
||||||
|
"Settings.UI.Profiles.PopoutRight": "Popout profiles on the right",
|
||||||
|
"Settings.UI.Profiles.PopoutRight.Description": "Will show profiles on the right side of the main UI",
|
||||||
|
"Settings.UI.Profiles.HoverDelay": "Hover Delay",
|
||||||
|
"Settings.UI.Profiles.HoverDelay.Description": "Delay until the profile should be displayed",
|
||||||
|
"Settings.UI.Profiles.ShowNsfw": "Show profiles marked as NSFW",
|
||||||
|
"Settings.UI.Profiles.ShowNsfw.Description": "Will show profiles that have the NSFW tag enabled",
|
||||||
|
"Settings.Notifications.Heading": "Notifications",
|
||||||
|
"Settings.Notifications.InfoDisplay": "Info Notification Display",
|
||||||
|
"Settings.Notifications.InfoDisplay.Description": "The location where \"Info\" notifications will display.\n'Nowhere' will not show any Info notifications\n'Chat' will print Info notifications in chat\n'Toast' will show Warning toast notifications in the bottom right corner\n'Both' will show chat as well as the toast notification",
|
||||||
|
"Settings.Notifications.WarningDisplay": "Warning Notification Display",
|
||||||
|
"Settings.Notifications.WarningDisplay.Description": "The location where \"Warning\" notifications will display.\n'Nowhere' will not show any Warning notifications\n'Chat' will print Warning notifications in chat\n'Toast' will show Warning toast notifications in the bottom right corner\n'Both' will show chat as well as the toast notification",
|
||||||
|
"Settings.Notifications.ErrorDisplay": "Error Notification Display",
|
||||||
|
"Settings.Notifications.ErrorDisplay.Description": "The location where \"Error\" notifications will display.\n'Nowhere' will not show any Error notifications\n'Chat' will print Error notifications in chat\n'Toast' will show Error toast notifications in the bottom right corner\n'Both' will show chat as well as the toast notification",
|
||||||
|
"Settings.Notifications.Location.Nowhere": "Nowhere",
|
||||||
|
"Settings.Notifications.Location.Chat": "Chat",
|
||||||
|
"Settings.Notifications.Location.Toast": "Toast",
|
||||||
|
"Settings.Notifications.Location.Both": "Both",
|
||||||
|
"Settings.Notifications.DisableOptionalWarnings": "Disable optional plugin warnings",
|
||||||
|
"Settings.Notifications.DisableOptionalWarnings.Description": "Enabling this will not show any \"Warning\" labeled messages for missing optional plugins.",
|
||||||
|
"Settings.Notifications.EnableOnlineNotifications": "Enable online notifications",
|
||||||
|
"Settings.Notifications.EnableOnlineNotifications.Description": "Enabling this will show a small notification (type: Info) in the bottom right corner when pairs go online.",
|
||||||
|
"Settings.Notifications.IndividualPairsOnly": "Notify only for individual pairs",
|
||||||
|
"Settings.Notifications.IndividualPairsOnly.Description": "Enabling this will only show online notifications (type: Info) for individual pairs.",
|
||||||
|
"Settings.Notifications.NamedPairsOnly": "Notify only for named pairs",
|
||||||
|
"Settings.Notifications.NamedPairsOnly.Description": "Enabling this will only show online notifications (type: Info) for pairs where you have set an individual note.",
|
||||||
|
"Compact.Version.UnsupportedTitle": "UNSUPPORTED VERSION",
|
||||||
|
"Compact.Version.Outdated": "Your UmbraSync installation is out of date, the current version is {0}.{1}.{2}. It is highly recommended to keep UmbraSync up to date. Open /xlplugins and update the plugin.",
|
||||||
|
"Compact.Toggle.IndividualPairs": "Individual pairs",
|
||||||
|
"Compact.Toggle.Syncshells": "Syncshells",
|
||||||
|
"Compact.AddUser.ModalTitle": "Set Notes for New User",
|
||||||
|
"Compact.AddUser.Description": "You have successfully added {0}. Set a local note for the user in the field below:",
|
||||||
|
"Compact.AddUser.NoteHint": "Note for {0}",
|
||||||
|
"Compact.AddUser.Save": "Save Note",
|
||||||
|
"Compact.AddCharacter.Button": "Add current character with secret key",
|
||||||
|
"Compact.AddCharacter.SecretKeyLabel": "Secret Key",
|
||||||
|
"Compact.AddCharacter.NoKeys": "No secret keys are configured for the current server.",
|
||||||
|
"Compact.AddPair.Hint": "Other player's UID/Alias",
|
||||||
|
"Compact.AddPair.Tooltip": "Pair with {0}",
|
||||||
|
"Compact.AddPair.Tooltip.DefaultUser": "other user",
|
||||||
|
"Compact.Filter.Hint": "Filter for UID/notes",
|
||||||
|
"Compact.Filter.ToggleTooltip": "Hold Control to {0} pairing with {1} out of {2} displayed users.",
|
||||||
|
"Compact.Filter.ToggleTooltip.Resume": "resume",
|
||||||
|
"Compact.Filter.ToggleTooltip.Pause": "pause",
|
||||||
|
"Compact.Filter.CooldownTooltip": "Next execution is available at {0} seconds",
|
||||||
|
"Compact.Nearby.Title": "Nearby ({0})",
|
||||||
|
"Compact.Nearby.Button": "Nearby",
|
||||||
|
"Compact.Nearby.None": "No nearby players detected.",
|
||||||
|
"Compact.Nearby.Tooltip.AlreadyPaired": "Already paired on Umbra",
|
||||||
|
"Compact.Nearby.Tooltip.RequestsDisabled": "Pair requests are disabled for this player",
|
||||||
|
"Compact.Nearby.Tooltip.SendInvite": "Send Umbra invitation",
|
||||||
|
"Compact.Nearby.Tooltip.CannotInvite": "Unable to invite this player",
|
||||||
|
"Compact.Nearby.Incoming": "Incoming requests",
|
||||||
|
"Compact.Nearby.Incoming.Entry": "{0} [{1}]",
|
||||||
|
"Compact.Nearby.Incoming.Accept": "Accept and add as pair",
|
||||||
|
"Compact.Nearby.Incoming.Dismiss": "Dismiss request",
|
||||||
|
"Compact.Header.SettingsTooltip": "Open the UmbraSync settings",
|
||||||
|
"Compact.Header.CopyUid": "Copy your UID to clipboard",
|
||||||
|
"Compact.ServerStatus.UsersOnline": "Users Online",
|
||||||
|
"Compact.ServerStatus.Shard": "Shard: {0}",
|
||||||
|
"Compact.ServerStatus.NotConnected": "Not connected to any server",
|
||||||
|
"Compact.ServerStatus.EditProfile": "Edit your Profile",
|
||||||
|
"Compact.ServerStatus.Disconnect": "Disconnect from {0}",
|
||||||
|
"Compact.ServerStatus.Connect": "Connect to {0}",
|
||||||
|
"Compact.ServerError.Connecting": "Attempting to connect to the server.",
|
||||||
|
"Compact.ServerError.Reconnecting": "Connection to server interrupted, attempting to reconnect to the server.",
|
||||||
|
"Compact.ServerError.Disconnected": "You are currently disconnected from the sync server.",
|
||||||
|
"Compact.ServerError.Disconnecting": "Disconnecting from the server",
|
||||||
|
"Compact.ServerError.Unauthorized": "Server Response: {0}",
|
||||||
|
"Compact.ServerError.Offline": "Your selected sync server is currently offline.",
|
||||||
|
"Compact.ServerError.VersionMismatch": "Your plugin or the server you are connecting to is out of date. Please update your plugin now. If you already did so, contact the server provider to update their server to the latest version.",
|
||||||
|
"Compact.ServerError.RateLimited": "You are rate limited for (re)connecting too often. Disconnect, wait 10 minutes and try again.",
|
||||||
|
"Compact.ServerError.NoSecretKey": "You have no secret key set for this current character. Use the button below or open the settings and set a secret key for the current character. You can reuse the same secret key for multiple characters.",
|
||||||
|
"Compact.ServerError.MultiChara": "Your Character Configuration has multiple characters configured with same name and world. You will not be able to connect until you fix this issue. Remove the duplicates from the configuration in Settings -> Service Settings -> Character Management and reconnect manually after.",
|
||||||
|
"Compact.Transfers.CharacterAnalysis": "Character Analysis",
|
||||||
|
"Compact.Transfers.CharacterDataHub": "Character Data Hub",
|
||||||
|
"Compact.UidText.Reconnecting": "Reconnecting",
|
||||||
|
"Compact.UidText.Connecting": "Connecting",
|
||||||
|
"Compact.UidText.Disconnected": "Disconnected",
|
||||||
|
"Compact.UidText.Disconnecting": "Disconnecting",
|
||||||
|
"Compact.UidText.Unauthorized": "Unauthorized",
|
||||||
|
"Compact.UidText.VersionMismatch": "Version mismatch",
|
||||||
|
"Compact.UidText.Offline": "Unavailable",
|
||||||
|
"Compact.UidText.RateLimited": "Rate Limited",
|
||||||
|
"Compact.UidText.NoSecretKey": "No Secret Key",
|
||||||
|
"Compact.UidText.MultiChara": "Duplicate Characters",
|
||||||
|
"UserPair.Status.Online": "User is online",
|
||||||
|
"UserPair.Status.Offline": "User is offline",
|
||||||
|
"UserPair.Tooltip.NotAddedBack": "{0} has not added you back",
|
||||||
|
"UserPair.Tooltip.Paused": "Pairing with {0} is paused",
|
||||||
|
"UserPair.Tooltip.Visible": "{0} is visible: {1}\nClick to target this player",
|
||||||
|
"UserPair.Tooltip.Visible.LastPrefix": "(Last) ",
|
||||||
|
"UserPair.Tooltip.Visible.ModsInfo": "Mods Info",
|
||||||
|
"UserPair.Tooltip.Visible.FilesSize": "Files Size: {0}",
|
||||||
|
"UserPair.Tooltip.Visible.Vram": "Approx. VRAM Usage: {0}",
|
||||||
|
"UserPair.Tooltip.Visible.Tris": "Triangle Count (excl. Vanilla): {0}",
|
||||||
|
"UserPair.Tooltip.Pause": "Pause pairing with {0}",
|
||||||
|
"UserPair.Tooltip.Resume": "Resume pairing with {0}",
|
||||||
|
"UserPair.Tooltip.Permission.Header": "Individual user permissions",
|
||||||
|
"UserPair.Tooltip.Permission.Sound": "Sound sync disabled with {0}",
|
||||||
|
"UserPair.Tooltip.Permission.Animation": "Animation sync disabled with {0}",
|
||||||
|
"UserPair.Tooltip.Permission.Vfx": "VFX sync disabled with {0}",
|
||||||
|
"UserPair.Tooltip.Permission.Status": "You: {0}, They: {1}",
|
||||||
|
"UserPair.Tooltip.Permission.State.Disabled": "Disabled",
|
||||||
|
"UserPair.Tooltip.Permission.State.Enabled": "Enabled",
|
||||||
|
"UserPair.Tooltip.SharedData": "This user has shared {0} Character Data Sets with you.",
|
||||||
|
"UserPair.Tooltip.SharedData.OpenHub": "Click to open the Character Data Hub and show the entries.",
|
||||||
|
"UserPair.Menu.Target": "Target player",
|
||||||
|
"UserPair.Menu.OpenProfile": "Open Profile",
|
||||||
|
"UserPair.Menu.OpenProfile.Tooltip": "Opens the profile for this user in a new window",
|
||||||
|
"UserPair.Menu.OpenAnalysis": "Open Analysis",
|
||||||
|
"UserPair.Menu.ReloadData": "Reload last data",
|
||||||
|
"UserPair.Menu.ReloadData.Tooltip": "This reapplies the last received character data to this character",
|
||||||
|
"UserPair.Menu.CyclePause": "Cycle pause state",
|
||||||
|
"UserPair.Menu.PairGroups": "Pair Groups",
|
||||||
|
"UserPair.Menu.PairGroups.Tooltip": "Choose pair groups for {0}",
|
||||||
|
"UserPair.Menu.EnableSoundSync": "Enable sound sync",
|
||||||
|
"UserPair.Menu.DisableSoundSync": "Disable sound sync",
|
||||||
|
"UserPair.Menu.EnableAnimationSync": "Enable animation sync",
|
||||||
|
"UserPair.Menu.DisableAnimationSync": "Disable animation sync",
|
||||||
|
"UserPair.Menu.EnableVfxSync": "Enable VFX sync",
|
||||||
|
"UserPair.Menu.DisableVfxSync": "Disable VFX sync",
|
||||||
|
"UserPair.Menu.Unpair": "Unpair Permanently",
|
||||||
|
"UserPair.Menu.Unpair.Tooltip": "Hold CTRL and click to unpair permanently from {0}",
|
||||||
|
"Popup.Generic.Close": "Close",
|
||||||
|
"Popup.BanUser.Description": "User {0} will be banned and removed from this Syncshell.",
|
||||||
|
"Popup.BanUser.ReasonHint": "Ban Reason",
|
||||||
|
"Popup.BanUser.Button": "Ban User",
|
||||||
|
"Popup.BanUser.ReasonNote": "The reason will be displayed in the banlist. The current server-side alias if present (Vanity ID) will automatically be attached to the reason.",
|
||||||
|
"Popup.Report.Title": "Report {0} Profile",
|
||||||
|
"Popup.Report.Note": "Note: Sending a report will disable the offending profile globally.\nThe report will be sent to the team of your currently connected server.\nDepending on the severity of the offense the users profile or account can be permanently disabled or banned.",
|
||||||
|
"Popup.Report.Warning": "Report spam and wrong reports will not be tolerated and can lead to permanent account suspension.",
|
||||||
|
"Popup.Report.Scope": "This is not for reporting misbehavior but solely for the actual profile. Reports that are not solely for the profile will be ignored.",
|
||||||
|
"Popup.Report.Button": "Send Report",
|
||||||
|
"PairGroups.Popup.Title": "Choose Groups for {0}",
|
||||||
|
"PairGroups.Popup.SelectPrompt": "Select the groups you want {0} to be in.",
|
||||||
|
"PairGroups.Popup.CreatePrompt": "Create a new group for {0}.",
|
||||||
|
"PairGroups.Popup.NewGroupHint": "New Group",
|
||||||
|
"PairGroups.SelectPairs.Title": "Choose Users for Group {0}",
|
||||||
|
"PairGroups.SelectPairs.SelectPrompt": "Select users for group {0}",
|
||||||
|
"PairGroups.SelectPairs.FilterHint": "Filter",
|
||||||
|
"UidDisplay.Tooltip": "Left click to switch between UID display and nick\nRight click to change nick for {0}\nMiddle Mouse Button to open their profile in a separate window",
|
||||||
|
"UidDisplay.EditNotes.Hint": "Nick/Notes",
|
||||||
|
"DataAnalysis.WindowTitle": "Character Data Analysis",
|
||||||
|
"DataAnalysis.Bc7.ModalTitle": "BC7 Conversion in Progress",
|
||||||
|
"DataAnalysis.Bc7.Status": "BC7 Conversion in progress: {0}/{1}",
|
||||||
|
"DataAnalysis.Bc7.CurrentFile": "Current file: {0}",
|
||||||
|
"DataAnalysis.Bc7.Cancel": "Cancel conversion",
|
||||||
|
"DataAnalysis.Description": "This window shows you all files and their sizes that are currently in use through your character and associated entities",
|
||||||
|
"DataAnalysis.Analyzing": "Analyzing {0}/{1}",
|
||||||
|
"DataAnalysis.Button.CancelAnalysis": "Cancel analysis",
|
||||||
|
"DataAnalysis.Analyze.MissingNotice": "Some entries in the analysis have file size not determined yet, press the button below to analyze your current data",
|
||||||
|
"DataAnalysis.Button.StartMissing": "Start analysis (missing entries)",
|
||||||
|
"DataAnalysis.Button.StartAll": "Start analysis (recalculate all entries)",
|
||||||
|
"DataAnalysis.TotalFiles": "Total files:",
|
||||||
|
"DataAnalysis.Tooltip.FileSummary": "{0}: {1} files, size: {2}, compressed: {3}",
|
||||||
|
"DataAnalysis.TotalSizeActual": "Total size (actual):",
|
||||||
|
"DataAnalysis.TotalSizeDownload": "Total size (download size):",
|
||||||
|
"DataAnalysis.Tooltip.CalculateDownloadSize": "Click \"Start analysis\" to calculate download size",
|
||||||
|
"DataAnalysis.TotalTriangles": "Total modded model triangles: {0}",
|
||||||
|
"DataAnalysis.FilesFor": "Files for {0}",
|
||||||
|
"DataAnalysis.Object.SizeActual": "{0} size (actual):",
|
||||||
|
"DataAnalysis.Object.SizeDownload": "{0} size (download size):",
|
||||||
|
"DataAnalysis.Object.Vram": "{0} VRAM usage:",
|
||||||
|
"DataAnalysis.Object.Triangles": "{0} modded model triangles: {1}",
|
||||||
|
"DataAnalysis.FileGroup.Count": "{0} files",
|
||||||
|
"DataAnalysis.FileGroup.SizeActual": "{0} files size (actual):",
|
||||||
|
"DataAnalysis.FileGroup.SizeDownload": "{0} files size (download size):",
|
||||||
|
"DataAnalysis.Bc7.EnableMode": "Enable BC7 Conversion Mode",
|
||||||
|
"DataAnalysis.Bc7.WarningTitle": "WARNING BC7 CONVERSION:",
|
||||||
|
"DataAnalysis.Bc7.WarningIrreversible": "Converting textures to BC7 is irreversible!",
|
||||||
|
"DataAnalysis.Bc7.WarningDetails": "- Converting textures to BC7 will reduce their size (compressed and uncompressed) drastically. It is recommended to be used for large (4k+) textures.\n- Some textures, especially ones utilizing colorsets, might not be suited for BC7 conversion and might produce visual artifacts.\n- Before converting textures, make sure to have the original files of the mod you are converting so you can reimport it in case of issues.\n- Conversion will convert all found texture duplicates (entries with more than 1 file path) automatically.\n- Converting textures to BC7 is a very expensive operation and, depending on the amount of textures to convert, will take a while to complete.",
|
||||||
|
"DataAnalysis.Bc7.StartConversion": "Start conversion of {0} texture(s)",
|
||||||
|
"DataAnalysis.Table.Hash": "Hash",
|
||||||
|
"DataAnalysis.Table.Filepaths": "Filepaths",
|
||||||
|
"DataAnalysis.Table.Gamepaths": "Gamepaths",
|
||||||
|
"DataAnalysis.Table.FileSize": "File Size",
|
||||||
|
"DataAnalysis.Table.DownloadSize": "Download Size",
|
||||||
|
"DataAnalysis.Table.Format": "Format",
|
||||||
|
"DataAnalysis.Table.ConvertToBc7": "Convert to BC7",
|
||||||
|
"DataAnalysis.Table.Triangles": "Triangles",
|
||||||
|
"DataAnalysis.SelectedFile": "Selected file:",
|
||||||
|
"DataAnalysis.LocalFilePath": "Local file path:",
|
||||||
|
"DataAnalysis.MoreCount": "(and {0} more)",
|
||||||
|
"DataAnalysis.GamePath": "Used by game path:",
|
||||||
|
"DownloadUi.WindowTitle": "Umbra Downloads",
|
||||||
|
"DownloadUi.UploadStatus": "Compressing+Uploading {0}/{1}",
|
||||||
|
"DownloadUi.DownloadStatus": "{0} [W:{1}/Q:{2}/P:{3}/D:{4}]",
|
||||||
|
"DownloadUi.UploadingLabel": "Uploading",
|
||||||
|
"EventViewer.WindowTitle": "Event Viewer",
|
||||||
|
"EventViewer.Button.Unfreeze": "Unfreeze View",
|
||||||
|
"EventViewer.Button.Freeze": "Freeze View",
|
||||||
|
"EventViewer.Tooltip.NewEvents": "New events are available. Click to resume updating.",
|
||||||
|
"EventViewer.FilterLabel": "Filter lines",
|
||||||
|
"EventViewer.Button.OpenLog": "Open EventLog folder",
|
||||||
|
"EventViewer.Column.Time": "Time",
|
||||||
|
"EventViewer.Column.Source": "Source",
|
||||||
|
"EventViewer.Column.Uid": "UID",
|
||||||
|
"EventViewer.Column.Character": "Character",
|
||||||
|
"EventViewer.Column.Event": "Event",
|
||||||
|
"EventViewer.Severity.Informational": "Informational",
|
||||||
|
"EventViewer.Severity.Warning": "Warning",
|
||||||
|
"EventViewer.Severity.Error": "Error",
|
||||||
|
"EventViewer.NoValue": "--",
|
||||||
|
"DtrEntry.EntryName": "Umbra",
|
||||||
|
"DtrEntry.Tooltip.Connected": "Umbra: Connected",
|
||||||
|
"DtrEntry.Tooltip.Disconnected": "Umbra: Not Connected",
|
||||||
|
"PermissionWindow.Title": "Permissions for {0}",
|
||||||
|
"PermissionWindow.Pause.Label": "Pause Sync",
|
||||||
|
"PermissionWindow.Pause.HelpMain": "Pausing will completely cease any sync with this user.",
|
||||||
|
"PermissionWindow.Pause.HelpNote": "Note: this is bidirectional, either user pausing will cease sync completely.",
|
||||||
|
"PermissionWindow.OtherPaused.True": "{0} has paused you",
|
||||||
|
"PermissionWindow.OtherPaused.False": "{0} has not paused you",
|
||||||
|
"PermissionWindow.Sounds.Label": "Disable Sounds",
|
||||||
|
"PermissionWindow.Sounds.HelpMain": "Disabling sounds will remove all sounds synced with this user on both sides.",
|
||||||
|
"PermissionWindow.Sounds.HelpNote": "Note: this is bidirectional, either user disabling sound sync will stop sound sync on both sides.",
|
||||||
|
"PermissionWindow.OtherSoundDisabled.True": "{0} has disabled sound sync with you",
|
||||||
|
"PermissionWindow.OtherSoundDisabled.False": "{0} has not disabled sound sync with you",
|
||||||
|
"PermissionWindow.Animations.Label": "Disable Animations",
|
||||||
|
"PermissionWindow.Animations.HelpMain": "Disabling sounds will remove all animations synced with this user on both sides.",
|
||||||
|
"PermissionWindow.Animations.HelpNote": "Note: this is bidirectional, either user disabling animation sync will stop animation sync on both sides.",
|
||||||
|
"PermissionWindow.OtherAnimationDisabled.True": "{0} has disabled animation sync with you",
|
||||||
|
"PermissionWindow.OtherAnimationDisabled.False": "{0} has not disabled animation sync with you",
|
||||||
|
"PermissionWindow.Vfx.Label": "Disable VFX",
|
||||||
|
"PermissionWindow.Vfx.HelpMain": "Disabling sounds will remove all VFX synced with this user on both sides.",
|
||||||
|
"PermissionWindow.Vfx.HelpNote": "Note: this is bidirectional, either user disabling VFX sync will stop VFX sync on both sides.",
|
||||||
|
"PermissionWindow.OtherVfxDisabled.True": "{0} has disabled VFX sync with you",
|
||||||
|
"PermissionWindow.OtherVfxDisabled.False": "{0} has not disabled VFX sync with you",
|
||||||
|
"PermissionWindow.Button.Save": "Save",
|
||||||
|
"PermissionWindow.Tooltip.Save": "Save and apply all changes",
|
||||||
|
"PermissionWindow.Button.Revert": "Revert",
|
||||||
|
"PermissionWindow.Tooltip.Revert": "Revert all changes",
|
||||||
|
"PermissionWindow.Button.Reset": "Reset to Default",
|
||||||
|
"PermissionWindow.Tooltip.Reset": "This will set all permissions to their default setting",
|
||||||
|
"EditProfile.WindowTitle": "Umbra Edit Profile",
|
||||||
|
"EditProfile.CurrentProfile": "Current Profile (as saved on server)",
|
||||||
|
"EditProfile.Button.UploadPicture": "Upload new profile picture",
|
||||||
|
"EditProfile.Dialog.PictureTitle": "Select new Profile picture",
|
||||||
|
"EditProfile.Tooltip.UploadPicture": "Select and upload a new profile picture",
|
||||||
|
"EditProfile.Button.ClearPicture": "Clear uploaded profile picture",
|
||||||
|
"EditProfile.Tooltip.ClearPicture": "Clear your currently uploaded profile picture",
|
||||||
|
"EditProfile.Error.PictureTooLarge": "The profile picture must be a PNG file with a maximum height and width of 256px and 250KiB size",
|
||||||
|
"EditProfile.Checkbox.Nsfw": "Profile is NSFW",
|
||||||
|
"EditProfile.Help.Nsfw": "If your profile description or image can be considered NSFW, toggle this to ON",
|
||||||
|
"EditProfile.DescriptionCounter": "Description {0}/1500",
|
||||||
|
"EditProfile.PreviewLabel": "Preview (approximate)",
|
||||||
|
"EditProfile.Button.SaveDescription": "Save Description",
|
||||||
|
"EditProfile.Tooltip.SaveDescription": "Sets your profile description text",
|
||||||
|
"EditProfile.Button.ClearDescription": "Clear Description",
|
||||||
|
"EditProfile.Tooltip.ClearDescription": "Clears your profile description text",
|
||||||
|
"Intro.Welcome.Title": "Welcome to Umbra",
|
||||||
|
"Intro.Welcome.Paragraph1": "Umbra is a plugin that will replicate your full current character state including all Penumbra mods to other paired users. Note that you will have to have Penumbra as well as Glamourer installed to use this plugin.",
|
||||||
|
"Intro.Welcome.Paragraph2": "We will have to setup a few things first before you can start using this plugin. Click on next to continue.",
|
||||||
|
"Intro.Welcome.Note": "Note: Any modifications you have applied through anything but Penumbra cannot be shared and your character state on other clients might look broken because of this or others players mods might not apply on your end altogether. If you want to use this plugin you will have to move your mods to Penumbra.",
|
||||||
|
"Intro.Welcome.Next": "Next",
|
||||||
|
"Intro.Agreement.Title": "Agreement of Usage of Service",
|
||||||
|
"Intro.Agreement.Callout": "READ THIS CAREFULLY",
|
||||||
|
"Intro.Agreement.Timeout": "'I agree' button will be available in {0}s",
|
||||||
|
"Intro.Agreement.Paragraph1": "To use Umbra, you must be over the age of 18, or 21 in some jurisdictions.",
|
||||||
|
"Intro.Agreement.Paragraph2": "All of the mod files currently active on your character as well as your current character state will be uploaded to the service you registered yourself at automatically. The plugin will exclusively upload the necessary mod files and not the whole mod.",
|
||||||
|
"Intro.Agreement.Paragraph3": "If you are on a data capped internet connection, higher fees due to data usage depending on the amount of downloaded and uploaded mod files might occur. Mod files will be compressed on up- and download to save on bandwidth usage. Due to varying up- and download speeds, changes in characters might not be visible immediately. Files present on the service that already represent your active mod files will not be uploaded again.",
|
||||||
|
"Intro.Agreement.Paragraph4": "The mod files you are uploading are confidential and will not be distributed to parties other than the ones who are requesting the exact same mod files. Please think about who you are going to pair since it is unavoidable that they will receive and locally cache the necessary mod files that you have currently in use. Locally cached mod files will have arbitrary file names to discourage attempts at replicating the original mod.",
|
||||||
|
"Intro.Agreement.Paragraph5": "The plugin creator tried their best to keep you secure. However, there is no guarantee for 100% security. Do not blindly pair your client with everyone.",
|
||||||
|
"Intro.Agreement.Paragraph6": "Mod files that are saved on the service will remain on the service as long as there are requests for the files from clients. After a period of not being used, the mod files will be automatically deleted.",
|
||||||
|
"Intro.Agreement.Paragraph7": "Accounts that are inactive for ninety (90) days will be deleted for privacy reasons.",
|
||||||
|
"Intro.Agreement.Paragraph8": "Umbra is operated from servers located in the European Union. You agree not to upload any content to the service that violates EU law; and more specifically, German law.",
|
||||||
|
"Intro.Agreement.Paragraph9": "You may delete your account at any time from within the Settings panel of the plugin. Any mods unique to you will then be removed from the server within 14 days.",
|
||||||
|
"Intro.Agreement.Paragraph10": "This service is provided as-is.",
|
||||||
|
"Intro.Agreement.Accept": "I agree",
|
||||||
|
"Intro.Storage.Title": "File Storage Setup",
|
||||||
|
"Intro.Storage.Description": "To not unnecessarily download files already present on your computer, Umbra will have to scan your Penumbra mod directory. Additionally, a local storage folder must be set where Umbra will download other character files to. Once the storage folder is set and the scan complete, this page will automatically forward to registration at a service.",
|
||||||
|
"Intro.Storage.ScanNote": "Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed.",
|
||||||
|
"Intro.Storage.Warning.FileCache": "Warning: once past this step you should not delete the FileCache.csv of Umbra in the Plugin Configurations folder of Dalamud. Otherwise on the next launch a full re-scan of the file cache database will be initiated.",
|
||||||
|
"Intro.Storage.Warning.ScanHang": "Warning: if the scan is hanging and does nothing for a long time, chances are high your Penumbra folder is not set up properly.",
|
||||||
|
"Intro.Storage.NoPenumbra": "You do not have a valid Penumbra path set. Open Penumbra and set up a valid path for the mod directory.",
|
||||||
|
"Intro.Storage.StartScan": "Start Scan",
|
||||||
|
"Intro.Storage.UseCompactor": "Use File Compactor",
|
||||||
|
"Intro.Storage.CompactorDescription": "The File Compactor can save a tremendous amount of space on the hard disk for downloads through Umbra. It will incur a minor CPU penalty on download but can speed up loading of other characters. It is recommended to keep it enabled. You can change this setting later anytime in the Umbra settings.",
|
||||||
|
"Intro.Registration.Title": "Service Registration",
|
||||||
|
"Intro.Registration.Description": "To be able to use Umbra you will have to register an account.",
|
||||||
|
"Intro.Registration.Support": "Refer to the instructions at the location you obtained this plugin for more information or support.",
|
||||||
|
"Intro.Registration.NewAccountInfo": "If you have not used Umbra before, click below to register a new account.",
|
||||||
|
"Intro.Registration.RegisterButton": "Register a new Umbra account",
|
||||||
|
"Intro.Registration.SendingRequest": "Sending request...",
|
||||||
|
"Intro.Registration.Success": "New account registered.\nPlease keep a copy of your secret key in case you need to reset your plugins, or to use it on another PC.",
|
||||||
|
"Intro.Registration.UnknownError": "An unknown error occured. Please try again later.",
|
||||||
|
"Intro.Registration.SecretKeyLabel": "Enter Secret Key",
|
||||||
|
"Intro.Registration.SecretKeyLabelRegistered": "Secret Key",
|
||||||
|
"Intro.Registration.SecretKeyInstructions": "If you already have a registered account, you can enter its secret key below to use it instead.",
|
||||||
|
"Intro.Registration.SecretKeyLength": "Your secret key must be exactly 64 characters long.",
|
||||||
|
"Intro.Registration.SecretKeyCharacters": "Your secret key can only contain ABCDEF and the numbers 0-9.",
|
||||||
|
"Intro.Registration.SaveAndConnect": "Save and Connect",
|
||||||
|
"Intro.Registration.SavedKeyRegistered": "(registered {0})",
|
||||||
|
"Intro.Registration.SavedKeySetup": "Secret Key added on Setup ({0})",
|
||||||
|
"Intro.ConnectionStatus.Connected": "Connected",
|
||||||
|
"AutoDetect.Disabled": "Nearby detection is disabled. Enable it in Settings to start detecting nearby Umbra users.",
|
||||||
|
"AutoDetect.MaxDistance": "Max distance (m)",
|
||||||
|
"AutoDetect.Table.Name": "Name",
|
||||||
|
"AutoDetect.Table.World": "World",
|
||||||
|
"AutoDetect.Table.Distance": "Distance",
|
||||||
|
"AutoDetect.Table.Status": "Status",
|
||||||
|
"AutoDetect.Table.Action": "Action",
|
||||||
|
"AutoDetect.World.Unknown": "-",
|
||||||
|
"AutoDetect.Distance.Unknown": "-",
|
||||||
|
"AutoDetect.Distance.Format": "{0:0.0} m",
|
||||||
|
"AutoDetect.Status.Paired": "Paired",
|
||||||
|
"AutoDetect.Status.RequestsDisabled": "Requests disabled",
|
||||||
|
"AutoDetect.Status.OnUmbra": "On Umbra",
|
||||||
|
"AutoDetect.Action.AlreadySynced": "Already sync",
|
||||||
|
"AutoDetect.Action.RequestsDisabled": "Requests disabled",
|
||||||
|
"AutoDetect.Action.SendRequest": "Send request",
|
||||||
|
"PairGroups.ResumeAll": "Resume pairing with all pairs in {0}",
|
||||||
|
"PairGroups.PauseAll": "Pause pairing with all pairs in {0}",
|
||||||
|
"PairGroups.Menu.Title": "Group Flyout Menu",
|
||||||
|
"PairGroups.Menu.AddPeople": "Add people to {0}",
|
||||||
|
"PairGroups.Menu.AddPeople.Tooltip": "Add more users to Group {0}",
|
||||||
|
"PairGroups.Menu.Delete": "Delete {0}",
|
||||||
|
"PairGroups.Menu.Delete.Tooltip": "Delete Group {0} (Will not delete the pairs)\nHold CTRL to delete",
|
||||||
|
"PairGroups.Tag.Unpaired": "Unpaired",
|
||||||
|
"PairGroups.Tag.Offline": "Offline",
|
||||||
|
"PairGroups.Tag.Online": "Online",
|
||||||
|
"PairGroups.Tag.Contacts": "Contacts",
|
||||||
|
"PairGroups.Tag.Visible": "Visible",
|
||||||
|
"PairGroups.Header.WithCounts": "{0} ({1}/{2}/{3} Pairs)",
|
||||||
|
"PairGroups.Header.Special": "{0} ({1} Pairs)",
|
||||||
|
"PairGroups.Tooltip.Title": "Group {0}",
|
||||||
|
"PairGroups.Tooltip.Visible": "{0} Pairs visible",
|
||||||
|
"PairGroups.Tooltip.Online": "{0} Pairs online/paused",
|
||||||
|
"PairGroups.Tooltip.Total": "{0} Pairs total",
|
||||||
|
"GroupPanel.Join.InputHint": "Syncshell GID/Alias (leave empty to create)",
|
||||||
|
"GroupPanel.Join.PasswordPopup": "Enter Syncshell Password",
|
||||||
|
"GroupPanel.Create.PopupTitle": "Create Syncshell",
|
||||||
|
"GroupPanel.Create.Tooltip": "Create Syncshell",
|
||||||
|
"GroupPanel.Create.TooMany": "You cannot create more than {0} Syncshells",
|
||||||
|
"GroupPanel.Join.Tooltip": "Join Syncshell {0}",
|
||||||
|
"GroupPanel.Join.TooMany": "You cannot join more than {0} Syncshells",
|
||||||
|
"GroupPanel.Join.Warning": "Before joining any Syncshells please be aware that you will be automatically paired with everyone in the Syncshell.",
|
||||||
|
"GroupPanel.Join.EnterPassword": "Enter the password for Syncshell {0}:",
|
||||||
|
"GroupPanel.Join.PasswordHint": "{0} Password",
|
||||||
|
"GroupPanel.Join.Error": "An error occured during joining of this Syncshell: you either have joined the maximum amount of Syncshells ({0}), it does not exist, the password you entered is wrong, you already joined the Syncshell, the Syncshell is full ({1} users) or the Syncshell has closed invites.",
|
||||||
|
"GroupPanel.Join.Button": "Join {0}",
|
||||||
|
"GroupPanel.Create.ChooseType": "Choisissez le type de Syncshell \u00e0 cr\u00e9er.",
|
||||||
|
"GroupPanel.Create.Permanent": "Permanente",
|
||||||
|
"GroupPanel.Create.Temporary": "Temporaire",
|
||||||
|
"GroupPanel.Create.AliasPrompt": "Donnez un nom \u00e0 votre Syncshell (optionnel) puis cr\u00e9ez-la.",
|
||||||
|
"GroupPanel.Create.AliasHint": "Nom du Syncshell",
|
||||||
|
"GroupPanel.Create.TempMaxDuration": "Dur\u00e9e maximale d'une Syncshell temporaire : 7 jours.",
|
||||||
|
"GroupPanel.Create.TempExpires": "Expiration le {0:g} (heure locale).",
|
||||||
|
"GroupPanel.Create.Instruction": "Appuyez sur le bouton ci-dessous pour cr\u00e9er une nouvelle Syncshell.",
|
||||||
|
"GroupPanel.Create.Button": "Create Syncshell",
|
||||||
|
"GroupPanel.Create.Error.NameInUse": "Le nom de la Syncshell est d\u00e9j\u00e0 utilis\u00e9.",
|
||||||
|
"GroupPanel.Create.Result.Name": "Syncshell Name: {0}",
|
||||||
|
"GroupPanel.Create.Result.Id": "Syncshell ID: {0}",
|
||||||
|
"GroupPanel.Create.Result.Password": "Syncshell Password: {0}",
|
||||||
|
"GroupPanel.Create.Result.ChangeLater": "You can change the Syncshell password later at any time.",
|
||||||
|
"GroupPanel.Create.Result.TempExpires": "Cette Syncshell expirera le {0:g} (heure locale).",
|
||||||
|
"GroupPanel.Create.Error.Generic": "You are already owner of the maximum amount of Syncshells (3) or joined the maximum amount of Syncshells (6). Relinquish ownership of your own Syncshells to someone else or leave existing Syncshells.",
|
||||||
|
"GroupPanel.CommentHint": "Comment/Notes",
|
||||||
|
"GroupPanel.CommentTooltip": "Hit ENTER to save\\nRight click to cancel",
|
||||||
|
"GroupPanel.Banlist.Title": "Manage Banlist for {0}",
|
||||||
|
"GroupPanel.Banlist.Refresh": "Refresh Banlist from Server",
|
||||||
|
"GroupPanel.Banlist.Column.Uid": "UID",
|
||||||
|
"GroupPanel.Banlist.Column.Alias": "Alias",
|
||||||
|
"GroupPanel.Banlist.Column.By": "By",
|
||||||
|
"GroupPanel.Banlist.Column.Date": "Date",
|
||||||
|
"GroupPanel.Banlist.Column.Reason": "Reason",
|
||||||
|
"GroupPanel.Banlist.Column.Actions": "Actions",
|
||||||
|
"GroupPanel.Banlist.Unban": "Unban",
|
||||||
|
"GroupPanel.Password.Title": "Change Syncshell Password",
|
||||||
|
"GroupPanel.Password.Description": "Enter the new Syncshell password for Syncshell {0} here.",
|
||||||
|
"GroupPanel.Password.Warning": "This action is irreversible",
|
||||||
|
"GroupPanel.Password.Hint": "New password for {0}",
|
||||||
|
"GroupPanel.Password.Button": "Change password",
|
||||||
|
"GroupPanel.Password.Error.TooShort": "The selected password is too short. It must be at least 10 characters.",
|
||||||
|
"GroupPanel.Invites.Title": "Create Bulk One-Time Invites",
|
||||||
|
"GroupPanel.Invites.Description": "This allows you to create up to 100 one-time invites at once for the Syncshell {0}.\\nThe invites are valid for 24h after creation and will automatically expire.",
|
||||||
|
"GroupPanel.Invites.CreateButton": "Create invites",
|
||||||
|
"GroupPanel.Invites.Result": "A total of {0} invites have been created.",
|
||||||
|
"GroupPanel.Invites.Copy": "Copy invites to clipboard",
|
||||||
|
"GroupPanel.List.Visible": "Visible",
|
||||||
|
"GroupPanel.List.Online": "Online",
|
||||||
|
"GroupPanel.List.Offline": "Offline/Unknown",
|
||||||
|
"GroupPanel.List.OfflineOmitted": "{0} offline users omitted from display.",
|
||||||
|
"GroupPanel.Permissions.Header": "Syncshell permissions",
|
||||||
|
"GroupPanel.Permissions.InvitesDisabled": "Syncshell is closed for joining",
|
||||||
|
"GroupPanel.Permissions.SoundDisabledOwner": "Sound sync disabled through owner",
|
||||||
|
"GroupPanel.Permissions.AnimationDisabledOwner": "Animation sync disabled through owner",
|
||||||
|
"GroupPanel.Permissions.VfxDisabledOwner": "VFX sync disabled through owner",
|
||||||
|
"GroupPanel.Permissions.OwnHeader": "Your permissions",
|
||||||
|
"GroupPanel.Permissions.SoundDisabledSelf": "Sound sync disabled through you",
|
||||||
|
"GroupPanel.Permissions.AnimationDisabledSelf": "Animation sync disabled through you",
|
||||||
|
"GroupPanel.Permissions.VfxDisabledSelf": "VFX sync disabled through you",
|
||||||
|
"GroupPanel.Permissions.NotePriority": "Note that syncshell permissions for disabling take precedence over your own set permissions",
|
||||||
|
"GroupPanel.PauseToggle.Tooltip": "{0} pairing with all users in this Syncshell",
|
||||||
|
"GroupPanel.PauseToggle.Resume": "Resume",
|
||||||
|
"GroupPanel.PauseToggle.Pause": "Pause",
|
||||||
|
"GroupPanel.Popup.Leave": "Leave Syncshell",
|
||||||
|
"GroupPanel.Popup.LeaveTooltip": "Hold CTRL and click to leave this Syncshell{0}",
|
||||||
|
"GroupPanel.Popup.LeaveWarning": "WARNING: This action is irreversible\\nLeaving an owned Syncshell will transfer the ownership to a random person in the Syncshell.",
|
||||||
|
"GroupPanel.Popup.CopyId": "Copy ID",
|
||||||
|
"GroupPanel.Popup.CopyIdTooltip": "Copy Syncshell ID to Clipboard",
|
||||||
|
"GroupPanel.Popup.CopyNotes": "Copy Notes",
|
||||||
|
"GroupPanel.Popup.CopyNotesTooltip": "Copies all your notes for all users in this Syncshell to the clipboard.\\nThey can be imported via Settings -> General -> Notes -> Import notes from clipboard",
|
||||||
|
"GroupPanel.Popup.EnableSound": "Enable sound sync",
|
||||||
|
"GroupPanel.Popup.DisableSound": "Disable sound sync",
|
||||||
|
"GroupPanel.Popup.SoundTooltip": "Sets your allowance for sound synchronization for users of this syncshell.\\nDisabling the synchronization will stop applying sound modifications for users of this syncshell.\\nNote: this setting can be forcefully overridden to 'disabled' through the syncshell owner.\\nNote: this setting does not apply to individual pairs that are also in the syncshell.",
|
||||||
|
"GroupPanel.Popup.EnableAnimations": "Enable animations sync",
|
||||||
|
"GroupPanel.Popup.DisableAnimations": "Disable animations sync",
|
||||||
|
"GroupPanel.Popup.AnimTooltip": "Sets your allowance for animations synchronization for users of this syncshell.\\nDisabling the synchronization will stop applying animations modifications for users of this syncshell.\\nNote: this setting might also affect sound synchronization\\nNote: this setting can be forcefully overridden to 'disabled' through the syncshell owner.\\nNote: this setting does not apply to individual pairs that are also in the syncshell.",
|
||||||
|
"GroupPanel.Popup.EnableVfx": "Enable VFX sync",
|
||||||
|
"GroupPanel.Popup.DisableVfx": "Disable VFX sync",
|
||||||
|
"GroupPanel.Popup.VfxTooltip": "Sets your allowance for VFX synchronization for users of this syncshell.\\nDisabling the synchronization will stop applying VFX modifications for users of this syncshell.\\nNote: this setting might also affect animation synchronization to some degree\\nNote: this setting can be forcefully overridden to 'disabled' through the syncshell owner.\\nNote: this setting does not apply to individual pairs that are also in the syncshell.",
|
||||||
|
"GroupPanel.Syncshell.OwnerTooltip": "You are the owner of Syncshell {0}",
|
||||||
|
"GroupPanel.Syncshell.ModeratorTooltip": "You are a moderator of Syncshell {0}",
|
||||||
|
"GroupPanel.Syncshell.MemberCount": "{0}/{1}",
|
||||||
|
"GroupPanel.Syncshell.MemberCountTooltip": "Membres connect\u00e9s / membres totaux\\nCapacit\u00e9 maximale : {0}\\nSyncshell ID: {1}",
|
||||||
|
"GroupPanel.Syncshell.NameTooltip": "Left click to switch between GID display and comment\\nRight click to change comment for {0}\\n\\nUsers: {1}, Owner: {2}",
|
||||||
|
"GroupPanel.Syncshell.TempTag": "(Temp)",
|
||||||
|
"GroupPanel.Syncshell.TempExpires": "Expire le {0:g}",
|
||||||
|
"GroupPanel.Syncshell.TempTooltip": "Syncshell temporaire",
|
||||||
|
"GroupPanel.Create.Duration.SingleDay": "24h",
|
||||||
|
"GroupPanel.Create.Duration.Days": "{0}j",
|
||||||
|
"GroupPanel.Create.Duration.Hours": "{0}h",
|
||||||
|
"GroupPanel.Invites.AmountLabel": "Amount",
|
||||||
|
"GroupPanel.Popup.OpenAdmin": "Open Admin Panel"
|
||||||
|
}
|
||||||
579
MareSynchronos/Localization/fr.json
Normal file
579
MareSynchronos/Localization/fr.json
Normal file
@@ -0,0 +1,579 @@
|
|||||||
|
{
|
||||||
|
"Language.DisplayName.French": "Français",
|
||||||
|
"Language.DisplayName.English": "Anglais",
|
||||||
|
"Settings.Plugins.MandatoryHeading": "",
|
||||||
|
"Settings.Plugins.MandatoryLabel": "",
|
||||||
|
"Settings.Plugins.OptionalHeading": "",
|
||||||
|
"Settings.Plugins.OptionalLabel": "",
|
||||||
|
"Settings.Plugins.OptionalDescription": "",
|
||||||
|
"Settings.Plugins.Tooltip.Available": "",
|
||||||
|
"Settings.Plugins.Tooltip.Unavailable": "",
|
||||||
|
"Settings.Plugins.WarningMandatoryMissing": "",
|
||||||
|
"Settings.General.LocalizationHeading": "Langue du plugin",
|
||||||
|
"Settings.General.Language": "Langue",
|
||||||
|
"Settings.General.Language.Description": "Sélectionnez la langue du plugin. Les traductions manquantes seront affichées en anglais.",
|
||||||
|
"Settings.General.NotesHeading": "",
|
||||||
|
"Settings.General.Notes.Export": "",
|
||||||
|
"Settings.General.Notes.Import": "",
|
||||||
|
"Settings.General.Notes.Overwrite": "",
|
||||||
|
"Settings.General.Notes.Overwrite.Description": "",
|
||||||
|
"Settings.General.Notes.Import.Success": "",
|
||||||
|
"Settings.General.Notes.Import.Failure": "",
|
||||||
|
"Settings.General.Notes.OpenPopup": "",
|
||||||
|
"Settings.General.Notes.OpenPopup.Description": "",
|
||||||
|
"Settings.Transfers.Blocked.Description": "",
|
||||||
|
"Settings.Transfers.Blocked.Column.Hash": "",
|
||||||
|
"Settings.Transfers.Blocked.Column.ForbiddenBy": "",
|
||||||
|
"Settings.Transfers.Blocked.Tab": "",
|
||||||
|
"Settings.Transfers.Heading": "",
|
||||||
|
"Settings.Transfers.GlobalLimit.Label": "",
|
||||||
|
"Settings.Transfers.GlobalLimit.Unit.BytePerSec": "",
|
||||||
|
"Settings.Transfers.GlobalLimit.Unit.KiloBytePerSec": "",
|
||||||
|
"Settings.Transfers.GlobalLimit.Unit.MegaBytePerSec": "",
|
||||||
|
"Settings.Transfers.GlobalLimit.Hint": "",
|
||||||
|
"Settings.Transfers.MaxParallelDownloads": "",
|
||||||
|
"Settings.Transfers.AutoDetect.Heading": "",
|
||||||
|
"Settings.Transfers.AutoDetect.EnableNearby": "",
|
||||||
|
"Settings.Transfers.AutoDetect.AllowRequests": "",
|
||||||
|
"Settings.Transfers.AutoDetect.Notification.Title": "",
|
||||||
|
"Settings.Transfers.AutoDetect.Notification.Enabled": "",
|
||||||
|
"Settings.Transfers.AutoDetect.Notification.Disabled": "",
|
||||||
|
"Settings.Transfers.AutoDetect.MaxDistance": "",
|
||||||
|
"Settings.Transfers.UI.Heading": "",
|
||||||
|
"Settings.Transfers.UI.ShowWindow": "",
|
||||||
|
"Settings.Transfers.UI.ShowWindow.Description": "",
|
||||||
|
"Settings.Transfers.UI.EditWindowPosition": "",
|
||||||
|
"Settings.Transfers.UI.ShowTransferBars": "",
|
||||||
|
"Settings.Transfers.UI.ShowTransferBars.Description": "",
|
||||||
|
"Settings.Transfers.UI.ShowDownloadText": "",
|
||||||
|
"Settings.Transfers.UI.ShowDownloadText.Description": "",
|
||||||
|
"Settings.Transfers.UI.BarWidth": "",
|
||||||
|
"Settings.Transfers.UI.BarWidth.Description": "",
|
||||||
|
"Settings.Transfers.UI.BarHeight": "",
|
||||||
|
"Settings.Transfers.UI.BarHeight.Description": "",
|
||||||
|
"Settings.Transfers.UI.ShowUploading": "",
|
||||||
|
"Settings.Transfers.UI.ShowUploading.Description": "",
|
||||||
|
"Settings.Transfers.UI.ShowUploadingBigText": "",
|
||||||
|
"Settings.Transfers.UI.ShowUploadingBigText.Description": "",
|
||||||
|
"Settings.Transfers.Current.Heading": "",
|
||||||
|
"Settings.Transfers.Current.Tab": "",
|
||||||
|
"Settings.Transfers.Current.Uploads": "",
|
||||||
|
"Settings.Transfers.Current.Uploads.Column.File": "",
|
||||||
|
"Settings.Transfers.Current.Uploads.Column.Uploaded": "",
|
||||||
|
"Settings.Transfers.Current.Uploads.Column.Size": "",
|
||||||
|
"Settings.Transfers.Current.Downloads": "",
|
||||||
|
"Settings.Transfers.Current.Downloads.Column.User": "",
|
||||||
|
"Settings.Transfers.Current.Downloads.Column.Server": "",
|
||||||
|
"Settings.Transfers.Current.Downloads.Column.Files": "",
|
||||||
|
"Settings.Transfers.Current.Downloads.Column.Download": "",
|
||||||
|
"Settings.Storage.Heading": "",
|
||||||
|
"Settings.Storage.Description": "",
|
||||||
|
"Settings.Service.ActionsHeading": "",
|
||||||
|
"Settings.Service.Actions.DeleteAccount": "",
|
||||||
|
"Settings.Service.Actions.DeleteAccountPopup": "",
|
||||||
|
"Settings.Service.Actions.DeleteAccount.Description": "",
|
||||||
|
"Settings.Service.Actions.DeleteAccount.Popup.Body1": "",
|
||||||
|
"Settings.Service.Actions.DeleteAccount.Popup.Body2": "",
|
||||||
|
"Settings.Service.Actions.DeleteAccount.Popup.Confirm": "",
|
||||||
|
"Settings.Service.Actions.DeleteAccount.Popup.Cancel": "",
|
||||||
|
"Settings.Service.SettingsHeading": "",
|
||||||
|
"Settings.Service.ReconnectWarning": "",
|
||||||
|
"Settings.Service.Tabs.CharacterAssignments": "",
|
||||||
|
"Settings.Service.Tabs.SecretKey": "",
|
||||||
|
"Settings.Service.Tabs.ServiceSettings": "",
|
||||||
|
"Settings.Service.Character.Assignments.Description": "",
|
||||||
|
"Settings.Service.Character.Assignments.TooltipCurrent": "",
|
||||||
|
"Settings.Service.Character.Assignments.DeleteTooltip": "",
|
||||||
|
"Settings.Service.Character.Assignments.AddCurrent": "",
|
||||||
|
"Settings.Service.Character.Assignments.NoKeys": "",
|
||||||
|
"Settings.Service.SecretKey.DisplayName": "",
|
||||||
|
"Settings.Service.SecretKey.Value": "",
|
||||||
|
"Settings.Service.SecretKey.AssignCurrent": "",
|
||||||
|
"Settings.Service.SecretKey.AssignTooltip": "",
|
||||||
|
"Settings.Service.SecretKey.Delete": "",
|
||||||
|
"Settings.Service.SecretKey.DeleteTooltip": "",
|
||||||
|
"Settings.Service.SecretKey.InUse": "",
|
||||||
|
"Settings.Service.SecretKey.Add": "",
|
||||||
|
"Settings.Service.SecretKey.NewFriendlyName": "",
|
||||||
|
"Settings.Service.SecretKey.RegisterAccount": "",
|
||||||
|
"Settings.Service.SecretKey.RegisterFailed": "",
|
||||||
|
"Settings.Service.SecretKey.RegisterSuccess": "",
|
||||||
|
"Settings.Service.SecretKey.RegisteredFriendlyName": "",
|
||||||
|
"Settings.Service.SecretKey.Registering": "",
|
||||||
|
"Settings.Service.ServiceTab.Uri": "",
|
||||||
|
"Settings.Service.ServiceTab.UriReadOnlyHint": "",
|
||||||
|
"Settings.Service.ServiceTab.Name": "",
|
||||||
|
"Settings.Service.ServiceTab.NameReadOnlyHint": "",
|
||||||
|
"Settings.Service.ServiceTab.Delete": "",
|
||||||
|
"Settings.Service.ServiceTab.DeleteHint": "",
|
||||||
|
"Settings.Advanced.Heading": "",
|
||||||
|
"Settings.Advanced.Tab": "",
|
||||||
|
"Settings.Advanced.Api.Enable": "",
|
||||||
|
"Settings.Advanced.Api.Description": "",
|
||||||
|
"Settings.Advanced.Api.Status.Active": "",
|
||||||
|
"Settings.Advanced.Api.Status.Disabled": "",
|
||||||
|
"Settings.Advanced.Api.Status.PluginLoaded": "",
|
||||||
|
"Settings.Advanced.Api.Status.Unknown": "",
|
||||||
|
"Settings.Advanced.EventViewer.LogToDisk": "",
|
||||||
|
"Settings.Advanced.EventViewer.Open": "",
|
||||||
|
"Settings.Advanced.HoldCombat": "",
|
||||||
|
"Settings.Advanced.SerializedApplications": "",
|
||||||
|
"Settings.Advanced.SerializedApplications.Description": "",
|
||||||
|
"Settings.Advanced.DebugHeading": "",
|
||||||
|
"Settings.Advanced.Debug.LastCreatedTree": "",
|
||||||
|
"Settings.Advanced.Debug.CopyButton": "",
|
||||||
|
"Settings.Advanced.Debug.CopyError": "",
|
||||||
|
"Settings.Advanced.Debug.CopyTooltip": "",
|
||||||
|
"Settings.Advanced.LogLevel": "",
|
||||||
|
"Settings.Advanced.Performance.LogCounters": "",
|
||||||
|
"Settings.Advanced.Performance.LogCounters.Description": "",
|
||||||
|
"Settings.Advanced.Performance.PrintStats": "",
|
||||||
|
"Settings.Advanced.Performance.PrintStatsRecent": "",
|
||||||
|
"Settings.Advanced.ActiveBlocks": "",
|
||||||
|
"Settings.UI.Heading": "",
|
||||||
|
"Settings.UI.EnableRightClick": "",
|
||||||
|
"Settings.UI.EnableRightClick.Description": "",
|
||||||
|
"Settings.UI.EnableDtrEntry": "",
|
||||||
|
"Settings.UI.EnableDtrEntry.Description": "",
|
||||||
|
"Settings.UI.Dtr.ShowUid": "",
|
||||||
|
"Settings.UI.Dtr.PreferNotes": "",
|
||||||
|
"Settings.UI.Dtr.UseColors": "",
|
||||||
|
"Settings.UI.Dtr.ColorDefault": "",
|
||||||
|
"Settings.UI.Dtr.ColorNotConnected": "",
|
||||||
|
"Settings.UI.Dtr.ColorPairsInRange": "",
|
||||||
|
"Settings.UI.NameColors.Enable": "",
|
||||||
|
"Settings.UI.NameColors.Character": "",
|
||||||
|
"Settings.UI.NameColors.Blocked": "",
|
||||||
|
"Settings.UI.VisibleGroup": "",
|
||||||
|
"Settings.UI.VisibleGroup.Description": "",
|
||||||
|
"Settings.UI.OfflineGroup": "",
|
||||||
|
"Settings.UI.OfflineGroup.Description": "",
|
||||||
|
"Settings.UI.ShowPlayerNames": "",
|
||||||
|
"Settings.UI.ShowPlayerNames.Description": "",
|
||||||
|
"Settings.UI.Profiles.Show": "",
|
||||||
|
"Settings.UI.Profiles.Show.Description": "",
|
||||||
|
"Settings.UI.Profiles.PopoutRight": "",
|
||||||
|
"Settings.UI.Profiles.PopoutRight.Description": "",
|
||||||
|
"Settings.UI.Profiles.HoverDelay": "",
|
||||||
|
"Settings.UI.Profiles.HoverDelay.Description": "",
|
||||||
|
"Settings.UI.Profiles.ShowNsfw": "",
|
||||||
|
"Settings.UI.Profiles.ShowNsfw.Description": "",
|
||||||
|
"Settings.Notifications.Heading": "",
|
||||||
|
"Settings.Notifications.InfoDisplay": "",
|
||||||
|
"Settings.Notifications.InfoDisplay.Description": "",
|
||||||
|
"Settings.Notifications.WarningDisplay": "",
|
||||||
|
"Settings.Notifications.WarningDisplay.Description": "",
|
||||||
|
"Settings.Notifications.ErrorDisplay": "",
|
||||||
|
"Settings.Notifications.ErrorDisplay.Description": "",
|
||||||
|
"Settings.Notifications.Location.Nowhere": "",
|
||||||
|
"Settings.Notifications.Location.Chat": "",
|
||||||
|
"Settings.Notifications.Location.Toast": "",
|
||||||
|
"Settings.Notifications.Location.Both": "",
|
||||||
|
"Settings.Notifications.DisableOptionalWarnings": "",
|
||||||
|
"Settings.Notifications.DisableOptionalWarnings.Description": "",
|
||||||
|
"Settings.Notifications.EnableOnlineNotifications": "",
|
||||||
|
"Settings.Notifications.EnableOnlineNotifications.Description": "",
|
||||||
|
"Settings.Notifications.IndividualPairsOnly": "",
|
||||||
|
"Settings.Notifications.IndividualPairsOnly.Description": "",
|
||||||
|
"Settings.Notifications.NamedPairsOnly": "",
|
||||||
|
"Settings.Notifications.NamedPairsOnly.Description": "",
|
||||||
|
"Compact.Version.UnsupportedTitle": "",
|
||||||
|
"Compact.Version.Outdated": "",
|
||||||
|
"Compact.Toggle.IndividualPairs": "",
|
||||||
|
"Compact.Toggle.Syncshells": "",
|
||||||
|
"Compact.AddUser.ModalTitle": "",
|
||||||
|
"Compact.AddUser.Description": "",
|
||||||
|
"Compact.AddUser.NoteHint": "",
|
||||||
|
"Compact.AddUser.Save": "",
|
||||||
|
"Compact.AddCharacter.Button": "",
|
||||||
|
"Compact.AddCharacter.SecretKeyLabel": "",
|
||||||
|
"Compact.AddCharacter.NoKeys": "",
|
||||||
|
"Compact.AddPair.Hint": "",
|
||||||
|
"Compact.AddPair.Tooltip": "",
|
||||||
|
"Compact.AddPair.Tooltip.DefaultUser": "",
|
||||||
|
"Compact.Filter.Hint": "",
|
||||||
|
"Compact.Filter.ToggleTooltip": "",
|
||||||
|
"Compact.Filter.ToggleTooltip.Resume": "",
|
||||||
|
"Compact.Filter.ToggleTooltip.Pause": "",
|
||||||
|
"Compact.Filter.CooldownTooltip": "",
|
||||||
|
"Compact.Nearby.Title": "",
|
||||||
|
"Compact.Nearby.Button": "",
|
||||||
|
"Compact.Nearby.None": "",
|
||||||
|
"Compact.Nearby.Tooltip.AlreadyPaired": "",
|
||||||
|
"Compact.Nearby.Tooltip.RequestsDisabled": "",
|
||||||
|
"Compact.Nearby.Tooltip.SendInvite": "",
|
||||||
|
"Compact.Nearby.Tooltip.CannotInvite": "",
|
||||||
|
"Compact.Nearby.Incoming": "",
|
||||||
|
"Compact.Nearby.Incoming.Entry": "",
|
||||||
|
"Compact.Nearby.Incoming.Accept": "",
|
||||||
|
"Compact.Nearby.Incoming.Dismiss": "",
|
||||||
|
"Compact.Header.SettingsTooltip": "",
|
||||||
|
"Compact.Header.CopyUid": "",
|
||||||
|
"Compact.ServerStatus.UsersOnline": "",
|
||||||
|
"Compact.ServerStatus.Shard": "",
|
||||||
|
"Compact.ServerStatus.NotConnected": "",
|
||||||
|
"Compact.ServerStatus.EditProfile": "",
|
||||||
|
"Compact.ServerStatus.Disconnect": "",
|
||||||
|
"Compact.ServerStatus.Connect": "",
|
||||||
|
"Compact.ServerError.Connecting": "",
|
||||||
|
"Compact.ServerError.Reconnecting": "",
|
||||||
|
"Compact.ServerError.Disconnected": "",
|
||||||
|
"Compact.ServerError.Disconnecting": "",
|
||||||
|
"Compact.ServerError.Unauthorized": "",
|
||||||
|
"Compact.ServerError.Offline": "",
|
||||||
|
"Compact.ServerError.VersionMismatch": "",
|
||||||
|
"Compact.ServerError.RateLimited": "",
|
||||||
|
"Compact.ServerError.NoSecretKey": "",
|
||||||
|
"Compact.ServerError.MultiChara": "",
|
||||||
|
"Compact.Transfers.CharacterAnalysis": "",
|
||||||
|
"Compact.Transfers.CharacterDataHub": "",
|
||||||
|
"Compact.UidText.Reconnecting": "",
|
||||||
|
"Compact.UidText.Connecting": "",
|
||||||
|
"Compact.UidText.Disconnected": "",
|
||||||
|
"Compact.UidText.Disconnecting": "",
|
||||||
|
"Compact.UidText.Unauthorized": "",
|
||||||
|
"Compact.UidText.VersionMismatch": "",
|
||||||
|
"Compact.UidText.Offline": "",
|
||||||
|
"Compact.UidText.RateLimited": "",
|
||||||
|
"Compact.UidText.NoSecretKey": "",
|
||||||
|
"Compact.UidText.MultiChara": "",
|
||||||
|
"UserPair.Status.Online": "",
|
||||||
|
"UserPair.Status.Offline": "",
|
||||||
|
"UserPair.Tooltip.NotAddedBack": "",
|
||||||
|
"UserPair.Tooltip.Paused": "",
|
||||||
|
"UserPair.Tooltip.Visible": "",
|
||||||
|
"UserPair.Tooltip.Visible.LastPrefix": "",
|
||||||
|
"UserPair.Tooltip.Visible.ModsInfo": "",
|
||||||
|
"UserPair.Tooltip.Visible.FilesSize": "",
|
||||||
|
"UserPair.Tooltip.Visible.Vram": "",
|
||||||
|
"UserPair.Tooltip.Visible.Tris": "",
|
||||||
|
"UserPair.Tooltip.Pause": "",
|
||||||
|
"UserPair.Tooltip.Resume": "",
|
||||||
|
"UserPair.Tooltip.Permission.Header": "",
|
||||||
|
"UserPair.Tooltip.Permission.Sound": "",
|
||||||
|
"UserPair.Tooltip.Permission.Animation": "",
|
||||||
|
"UserPair.Tooltip.Permission.Vfx": "",
|
||||||
|
"UserPair.Tooltip.Permission.Status": "",
|
||||||
|
"UserPair.Tooltip.Permission.State.Disabled": "",
|
||||||
|
"UserPair.Tooltip.Permission.State.Enabled": "",
|
||||||
|
"UserPair.Tooltip.SharedData": "",
|
||||||
|
"UserPair.Tooltip.SharedData.OpenHub": "",
|
||||||
|
"UserPair.Menu.Target": "",
|
||||||
|
"UserPair.Menu.OpenProfile": "",
|
||||||
|
"UserPair.Menu.OpenProfile.Tooltip": "",
|
||||||
|
"UserPair.Menu.OpenAnalysis": "",
|
||||||
|
"UserPair.Menu.ReloadData": "",
|
||||||
|
"UserPair.Menu.ReloadData.Tooltip": "",
|
||||||
|
"UserPair.Menu.CyclePause": "",
|
||||||
|
"UserPair.Menu.PairGroups": "",
|
||||||
|
"UserPair.Menu.PairGroups.Tooltip": "",
|
||||||
|
"UserPair.Menu.EnableSoundSync": "",
|
||||||
|
"UserPair.Menu.DisableSoundSync": "",
|
||||||
|
"UserPair.Menu.EnableAnimationSync": "",
|
||||||
|
"UserPair.Menu.DisableAnimationSync": "",
|
||||||
|
"UserPair.Menu.EnableVfxSync": "",
|
||||||
|
"UserPair.Menu.DisableVfxSync": "",
|
||||||
|
"UserPair.Menu.Unpair": "",
|
||||||
|
"UserPair.Menu.Unpair.Tooltip": "",
|
||||||
|
"Popup.Generic.Close": "",
|
||||||
|
"Popup.BanUser.Description": "",
|
||||||
|
"Popup.BanUser.ReasonHint": "",
|
||||||
|
"Popup.BanUser.Button": "",
|
||||||
|
"Popup.BanUser.ReasonNote": "",
|
||||||
|
"Popup.Report.Title": "",
|
||||||
|
"Popup.Report.Note": "",
|
||||||
|
"Popup.Report.Warning": "",
|
||||||
|
"Popup.Report.Scope": "",
|
||||||
|
"Popup.Report.Button": "",
|
||||||
|
"PairGroups.Popup.Title": "",
|
||||||
|
"PairGroups.Popup.SelectPrompt": "",
|
||||||
|
"PairGroups.Popup.CreatePrompt": "",
|
||||||
|
"PairGroups.Popup.NewGroupHint": "",
|
||||||
|
"PairGroups.SelectPairs.Title": "",
|
||||||
|
"PairGroups.SelectPairs.SelectPrompt": "",
|
||||||
|
"PairGroups.SelectPairs.FilterHint": "",
|
||||||
|
"UidDisplay.Tooltip": "",
|
||||||
|
"UidDisplay.EditNotes.Hint": "",
|
||||||
|
"DataAnalysis.WindowTitle": "",
|
||||||
|
"DataAnalysis.Bc7.ModalTitle": "",
|
||||||
|
"DataAnalysis.Bc7.Status": "",
|
||||||
|
"DataAnalysis.Bc7.CurrentFile": "",
|
||||||
|
"DataAnalysis.Bc7.Cancel": "",
|
||||||
|
"DataAnalysis.Description": "",
|
||||||
|
"DataAnalysis.Analyzing": "",
|
||||||
|
"DataAnalysis.Button.CancelAnalysis": "",
|
||||||
|
"DataAnalysis.Analyze.MissingNotice": "",
|
||||||
|
"DataAnalysis.Button.StartMissing": "",
|
||||||
|
"DataAnalysis.Button.StartAll": "",
|
||||||
|
"DataAnalysis.TotalFiles": "",
|
||||||
|
"DataAnalysis.Tooltip.FileSummary": "",
|
||||||
|
"DataAnalysis.TotalSizeActual": "",
|
||||||
|
"DataAnalysis.TotalSizeDownload": "",
|
||||||
|
"DataAnalysis.Tooltip.CalculateDownloadSize": "",
|
||||||
|
"DataAnalysis.TotalTriangles": "",
|
||||||
|
"DataAnalysis.FilesFor": "",
|
||||||
|
"DataAnalysis.Object.SizeActual": "",
|
||||||
|
"DataAnalysis.Object.SizeDownload": "",
|
||||||
|
"DataAnalysis.Object.Vram": "",
|
||||||
|
"DataAnalysis.Object.Triangles": "",
|
||||||
|
"DataAnalysis.FileGroup.Count": "",
|
||||||
|
"DataAnalysis.FileGroup.SizeActual": "",
|
||||||
|
"DataAnalysis.FileGroup.SizeDownload": "",
|
||||||
|
"DataAnalysis.Bc7.EnableMode": "",
|
||||||
|
"DataAnalysis.Bc7.WarningTitle": "",
|
||||||
|
"DataAnalysis.Bc7.WarningIrreversible": "",
|
||||||
|
"DataAnalysis.Bc7.WarningDetails": "",
|
||||||
|
"DataAnalysis.Bc7.StartConversion": "",
|
||||||
|
"DataAnalysis.Table.Hash": "",
|
||||||
|
"DataAnalysis.Table.Filepaths": "",
|
||||||
|
"DataAnalysis.Table.Gamepaths": "",
|
||||||
|
"DataAnalysis.Table.FileSize": "",
|
||||||
|
"DataAnalysis.Table.DownloadSize": "",
|
||||||
|
"DataAnalysis.Table.Format": "",
|
||||||
|
"DataAnalysis.Table.ConvertToBc7": "",
|
||||||
|
"DataAnalysis.Table.Triangles": "",
|
||||||
|
"DataAnalysis.SelectedFile": "",
|
||||||
|
"DataAnalysis.LocalFilePath": "",
|
||||||
|
"DataAnalysis.MoreCount": "",
|
||||||
|
"DataAnalysis.GamePath": "",
|
||||||
|
"DownloadUi.WindowTitle": "",
|
||||||
|
"DownloadUi.UploadStatus": "",
|
||||||
|
"DownloadUi.DownloadStatus": "",
|
||||||
|
"DownloadUi.UploadingLabel": "",
|
||||||
|
"EventViewer.WindowTitle": "",
|
||||||
|
"EventViewer.Button.Unfreeze": "",
|
||||||
|
"EventViewer.Button.Freeze": "",
|
||||||
|
"EventViewer.Tooltip.NewEvents": "",
|
||||||
|
"EventViewer.FilterLabel": "",
|
||||||
|
"EventViewer.Button.OpenLog": "",
|
||||||
|
"EventViewer.Column.Time": "",
|
||||||
|
"EventViewer.Column.Source": "",
|
||||||
|
"EventViewer.Column.Uid": "",
|
||||||
|
"EventViewer.Column.Character": "",
|
||||||
|
"EventViewer.Column.Event": "",
|
||||||
|
"EventViewer.Severity.Informational": "",
|
||||||
|
"EventViewer.Severity.Warning": "",
|
||||||
|
"EventViewer.Severity.Error": "",
|
||||||
|
"EventViewer.NoValue": "",
|
||||||
|
"DtrEntry.EntryName": "",
|
||||||
|
"DtrEntry.Tooltip.Connected": "",
|
||||||
|
"DtrEntry.Tooltip.Disconnected": "",
|
||||||
|
"PermissionWindow.Title": "",
|
||||||
|
"PermissionWindow.Pause.Label": "",
|
||||||
|
"PermissionWindow.Pause.HelpMain": "",
|
||||||
|
"PermissionWindow.Pause.HelpNote": "",
|
||||||
|
"PermissionWindow.OtherPaused.True": "",
|
||||||
|
"PermissionWindow.OtherPaused.False": "",
|
||||||
|
"PermissionWindow.Sounds.Label": "",
|
||||||
|
"PermissionWindow.Sounds.HelpMain": "",
|
||||||
|
"PermissionWindow.Sounds.HelpNote": "",
|
||||||
|
"PermissionWindow.OtherSoundDisabled.True": "",
|
||||||
|
"PermissionWindow.OtherSoundDisabled.False": "",
|
||||||
|
"PermissionWindow.Animations.Label": "",
|
||||||
|
"PermissionWindow.Animations.HelpMain": "",
|
||||||
|
"PermissionWindow.Animations.HelpNote": "",
|
||||||
|
"PermissionWindow.OtherAnimationDisabled.True": "",
|
||||||
|
"PermissionWindow.OtherAnimationDisabled.False": "",
|
||||||
|
"PermissionWindow.Vfx.Label": "",
|
||||||
|
"PermissionWindow.Vfx.HelpMain": "",
|
||||||
|
"PermissionWindow.Vfx.HelpNote": "",
|
||||||
|
"PermissionWindow.OtherVfxDisabled.True": "",
|
||||||
|
"PermissionWindow.OtherVfxDisabled.False": "",
|
||||||
|
"PermissionWindow.Button.Save": "",
|
||||||
|
"PermissionWindow.Tooltip.Save": "",
|
||||||
|
"PermissionWindow.Button.Revert": "",
|
||||||
|
"PermissionWindow.Tooltip.Revert": "",
|
||||||
|
"PermissionWindow.Button.Reset": "",
|
||||||
|
"PermissionWindow.Tooltip.Reset": "",
|
||||||
|
"EditProfile.WindowTitle": "",
|
||||||
|
"EditProfile.CurrentProfile": "",
|
||||||
|
"EditProfile.Button.UploadPicture": "",
|
||||||
|
"EditProfile.Dialog.PictureTitle": "",
|
||||||
|
"EditProfile.Tooltip.UploadPicture": "",
|
||||||
|
"EditProfile.Button.ClearPicture": "",
|
||||||
|
"EditProfile.Tooltip.ClearPicture": "",
|
||||||
|
"EditProfile.Error.PictureTooLarge": "",
|
||||||
|
"EditProfile.Checkbox.Nsfw": "",
|
||||||
|
"EditProfile.Help.Nsfw": "",
|
||||||
|
"EditProfile.DescriptionCounter": "",
|
||||||
|
"EditProfile.PreviewLabel": "",
|
||||||
|
"EditProfile.Button.SaveDescription": "",
|
||||||
|
"EditProfile.Tooltip.SaveDescription": "",
|
||||||
|
"EditProfile.Button.ClearDescription": "",
|
||||||
|
"EditProfile.Tooltip.ClearDescription": "",
|
||||||
|
"Intro.Welcome.Title": "",
|
||||||
|
"Intro.Welcome.Paragraph1": "",
|
||||||
|
"Intro.Welcome.Paragraph2": "",
|
||||||
|
"Intro.Welcome.Note": "",
|
||||||
|
"Intro.Welcome.Next": "",
|
||||||
|
"Intro.Agreement.Title": "",
|
||||||
|
"Intro.Agreement.Callout": "",
|
||||||
|
"Intro.Agreement.Timeout": "",
|
||||||
|
"Intro.Agreement.Paragraph1": "",
|
||||||
|
"Intro.Agreement.Paragraph2": "",
|
||||||
|
"Intro.Agreement.Paragraph3": "",
|
||||||
|
"Intro.Agreement.Paragraph4": "",
|
||||||
|
"Intro.Agreement.Paragraph5": "",
|
||||||
|
"Intro.Agreement.Paragraph6": "",
|
||||||
|
"Intro.Agreement.Paragraph7": "",
|
||||||
|
"Intro.Agreement.Paragraph8": "",
|
||||||
|
"Intro.Agreement.Paragraph9": "",
|
||||||
|
"Intro.Agreement.Paragraph10": "",
|
||||||
|
"Intro.Agreement.Accept": "",
|
||||||
|
"Intro.Storage.Title": "",
|
||||||
|
"Intro.Storage.Description": "",
|
||||||
|
"Intro.Storage.ScanNote": "",
|
||||||
|
"Intro.Storage.Warning.FileCache": "",
|
||||||
|
"Intro.Storage.Warning.ScanHang": "",
|
||||||
|
"Intro.Storage.NoPenumbra": "",
|
||||||
|
"Intro.Storage.StartScan": "",
|
||||||
|
"Intro.Storage.UseCompactor": "",
|
||||||
|
"Intro.Storage.CompactorDescription": "",
|
||||||
|
"Intro.Registration.Title": "",
|
||||||
|
"Intro.Registration.Description": "",
|
||||||
|
"Intro.Registration.Support": "",
|
||||||
|
"Intro.Registration.NewAccountInfo": "",
|
||||||
|
"Intro.Registration.RegisterButton": "",
|
||||||
|
"Intro.Registration.SendingRequest": "",
|
||||||
|
"Intro.Registration.Success": "",
|
||||||
|
"Intro.Registration.UnknownError": "",
|
||||||
|
"Intro.Registration.SecretKeyLabel": "",
|
||||||
|
"Intro.Registration.SecretKeyLabelRegistered": "",
|
||||||
|
"Intro.Registration.SecretKeyInstructions": "",
|
||||||
|
"Intro.Registration.SecretKeyLength": "",
|
||||||
|
"Intro.Registration.SecretKeyCharacters": "",
|
||||||
|
"Intro.Registration.SaveAndConnect": "",
|
||||||
|
"Intro.Registration.SavedKeyRegistered": "",
|
||||||
|
"Intro.Registration.SavedKeySetup": "",
|
||||||
|
"Intro.ConnectionStatus.Connected": "",
|
||||||
|
"AutoDetect.Disabled": "",
|
||||||
|
"AutoDetect.MaxDistance": "",
|
||||||
|
"AutoDetect.Table.Name": "",
|
||||||
|
"AutoDetect.Table.World": "",
|
||||||
|
"AutoDetect.Table.Distance": "",
|
||||||
|
"AutoDetect.Table.Status": "",
|
||||||
|
"AutoDetect.Table.Action": "",
|
||||||
|
"AutoDetect.World.Unknown": "",
|
||||||
|
"AutoDetect.Distance.Unknown": "",
|
||||||
|
"AutoDetect.Distance.Format": "",
|
||||||
|
"AutoDetect.Status.Paired": "",
|
||||||
|
"AutoDetect.Status.RequestsDisabled": "",
|
||||||
|
"AutoDetect.Status.OnUmbra": "",
|
||||||
|
"AutoDetect.Action.AlreadySynced": "",
|
||||||
|
"AutoDetect.Action.RequestsDisabled": "",
|
||||||
|
"AutoDetect.Action.SendRequest": "",
|
||||||
|
"PairGroups.ResumeAll": "",
|
||||||
|
"PairGroups.PauseAll": "",
|
||||||
|
"PairGroups.Menu.Title": "",
|
||||||
|
"PairGroups.Menu.AddPeople": "",
|
||||||
|
"PairGroups.Menu.AddPeople.Tooltip": "",
|
||||||
|
"PairGroups.Menu.Delete": "",
|
||||||
|
"PairGroups.Menu.Delete.Tooltip": "",
|
||||||
|
"PairGroups.Tag.Unpaired": "",
|
||||||
|
"PairGroups.Tag.Offline": "",
|
||||||
|
"PairGroups.Tag.Online": "",
|
||||||
|
"PairGroups.Tag.Contacts": "",
|
||||||
|
"PairGroups.Tag.Visible": "",
|
||||||
|
"PairGroups.Header.WithCounts": "",
|
||||||
|
"PairGroups.Header.Special": "",
|
||||||
|
"PairGroups.Tooltip.Title": "",
|
||||||
|
"PairGroups.Tooltip.Visible": "",
|
||||||
|
"PairGroups.Tooltip.Online": "",
|
||||||
|
"PairGroups.Tooltip.Total": "",
|
||||||
|
"GroupPanel.Join.InputHint": "",
|
||||||
|
"GroupPanel.Join.PasswordPopup": "",
|
||||||
|
"GroupPanel.Create.PopupTitle": "",
|
||||||
|
"GroupPanel.Create.Tooltip": "",
|
||||||
|
"GroupPanel.Create.TooMany": "",
|
||||||
|
"GroupPanel.Join.Tooltip": "",
|
||||||
|
"GroupPanel.Join.TooMany": "",
|
||||||
|
"GroupPanel.Join.Warning": "",
|
||||||
|
"GroupPanel.Join.EnterPassword": "",
|
||||||
|
"GroupPanel.Join.PasswordHint": "",
|
||||||
|
"GroupPanel.Join.Error": "",
|
||||||
|
"GroupPanel.Join.Button": "",
|
||||||
|
"GroupPanel.Create.ChooseType": "",
|
||||||
|
"GroupPanel.Create.Permanent": "",
|
||||||
|
"GroupPanel.Create.Temporary": "",
|
||||||
|
"GroupPanel.Create.AliasPrompt": "",
|
||||||
|
"GroupPanel.Create.AliasHint": "",
|
||||||
|
"GroupPanel.Create.TempMaxDuration": "",
|
||||||
|
"GroupPanel.Create.TempExpires": "",
|
||||||
|
"GroupPanel.Create.Instruction": "",
|
||||||
|
"GroupPanel.Create.Button": "",
|
||||||
|
"GroupPanel.Create.Error.NameInUse": "",
|
||||||
|
"GroupPanel.Create.Result.Name": "",
|
||||||
|
"GroupPanel.Create.Result.Id": "",
|
||||||
|
"GroupPanel.Create.Result.Password": "",
|
||||||
|
"GroupPanel.Create.Result.ChangeLater": "",
|
||||||
|
"GroupPanel.Create.Result.TempExpires": "",
|
||||||
|
"GroupPanel.Create.Error.Generic": "",
|
||||||
|
"GroupPanel.CommentHint": "",
|
||||||
|
"GroupPanel.CommentTooltip": "",
|
||||||
|
"GroupPanel.Banlist.Title": "",
|
||||||
|
"GroupPanel.Banlist.Refresh": "",
|
||||||
|
"GroupPanel.Banlist.Column.Uid": "",
|
||||||
|
"GroupPanel.Banlist.Column.Alias": "",
|
||||||
|
"GroupPanel.Banlist.Column.By": "",
|
||||||
|
"GroupPanel.Banlist.Column.Date": "",
|
||||||
|
"GroupPanel.Banlist.Column.Reason": "",
|
||||||
|
"GroupPanel.Banlist.Column.Actions": "",
|
||||||
|
"GroupPanel.Banlist.Unban": "",
|
||||||
|
"GroupPanel.Password.Title": "",
|
||||||
|
"GroupPanel.Password.Description": "",
|
||||||
|
"GroupPanel.Password.Warning": "",
|
||||||
|
"GroupPanel.Password.Hint": "",
|
||||||
|
"GroupPanel.Password.Button": "",
|
||||||
|
"GroupPanel.Password.Error.TooShort": "",
|
||||||
|
"GroupPanel.Invites.Title": "",
|
||||||
|
"GroupPanel.Invites.Description": "",
|
||||||
|
"GroupPanel.Invites.CreateButton": "",
|
||||||
|
"GroupPanel.Invites.Result": "",
|
||||||
|
"GroupPanel.Invites.Copy": "",
|
||||||
|
"GroupPanel.List.Visible": "",
|
||||||
|
"GroupPanel.List.Online": "",
|
||||||
|
"GroupPanel.List.Offline": "",
|
||||||
|
"GroupPanel.List.OfflineOmitted": "",
|
||||||
|
"GroupPanel.Permissions.Header": "",
|
||||||
|
"GroupPanel.Permissions.InvitesDisabled": "",
|
||||||
|
"GroupPanel.Permissions.SoundDisabledOwner": "",
|
||||||
|
"GroupPanel.Permissions.AnimationDisabledOwner": "",
|
||||||
|
"GroupPanel.Permissions.VfxDisabledOwner": "",
|
||||||
|
"GroupPanel.Permissions.OwnHeader": "",
|
||||||
|
"GroupPanel.Permissions.SoundDisabledSelf": "",
|
||||||
|
"GroupPanel.Permissions.AnimationDisabledSelf": "",
|
||||||
|
"GroupPanel.Permissions.VfxDisabledSelf": "",
|
||||||
|
"GroupPanel.Permissions.NotePriority": "",
|
||||||
|
"GroupPanel.PauseToggle.Tooltip": "",
|
||||||
|
"GroupPanel.PauseToggle.Resume": "",
|
||||||
|
"GroupPanel.PauseToggle.Pause": "",
|
||||||
|
"GroupPanel.Popup.Leave": "",
|
||||||
|
"GroupPanel.Popup.LeaveTooltip": "",
|
||||||
|
"GroupPanel.Popup.LeaveWarning": "",
|
||||||
|
"GroupPanel.Popup.CopyId": "",
|
||||||
|
"GroupPanel.Popup.CopyIdTooltip": "",
|
||||||
|
"GroupPanel.Popup.CopyNotes": "",
|
||||||
|
"GroupPanel.Popup.CopyNotesTooltip": "",
|
||||||
|
"GroupPanel.Popup.EnableSound": "",
|
||||||
|
"GroupPanel.Popup.DisableSound": "",
|
||||||
|
"GroupPanel.Popup.SoundTooltip": "",
|
||||||
|
"GroupPanel.Popup.EnableAnimations": "",
|
||||||
|
"GroupPanel.Popup.DisableAnimations": "",
|
||||||
|
"GroupPanel.Popup.AnimTooltip": "",
|
||||||
|
"GroupPanel.Popup.EnableVfx": "",
|
||||||
|
"GroupPanel.Popup.DisableVfx": "",
|
||||||
|
"GroupPanel.Popup.VfxTooltip": "",
|
||||||
|
"GroupPanel.Syncshell.OwnerTooltip": "",
|
||||||
|
"GroupPanel.Syncshell.ModeratorTooltip": "",
|
||||||
|
"GroupPanel.Syncshell.MemberCount": "",
|
||||||
|
"GroupPanel.Syncshell.MemberCountTooltip": "",
|
||||||
|
"GroupPanel.Syncshell.NameTooltip": "",
|
||||||
|
"GroupPanel.Syncshell.TempTag": "",
|
||||||
|
"GroupPanel.Syncshell.TempExpires": "",
|
||||||
|
"GroupPanel.Syncshell.TempTooltip": "",
|
||||||
|
"GroupPanel.Create.Duration.SingleDay": "",
|
||||||
|
"GroupPanel.Create.Duration.Days": "",
|
||||||
|
"GroupPanel.Create.Duration.Hours": "",
|
||||||
|
"GroupPanel.Invites.AmountLabel": "",
|
||||||
|
"GroupPanel.Popup.OpenAdmin": ""
|
||||||
|
}
|
||||||
@@ -1,15 +1,72 @@
|
|||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
|
using System.Text.Json;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MareSynchronos.MareConfiguration;
|
namespace MareSynchronos.MareConfiguration;
|
||||||
|
|
||||||
public class ConfigurationMigrator(ILogger<ConfigurationMigrator> logger) : IHostedService
|
public class ConfigurationMigrator(ILogger<ConfigurationMigrator> logger, MareConfigService mareConfig) : IHostedService
|
||||||
{
|
{
|
||||||
private readonly ILogger<ConfigurationMigrator> _logger = logger;
|
private readonly ILogger<ConfigurationMigrator> _logger = logger;
|
||||||
|
private readonly MareConfigService _mareConfig = mareConfig;
|
||||||
|
|
||||||
public void Migrate()
|
public void Migrate()
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var path = _mareConfig.ConfigurationPath;
|
||||||
|
if (!File.Exists(path)) return;
|
||||||
|
|
||||||
|
using var doc = JsonDocument.Parse(File.ReadAllText(path));
|
||||||
|
var root = doc.RootElement;
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
if (root.TryGetProperty("EnableAutoSyncDiscovery", out var enableAutoSync))
|
||||||
|
{
|
||||||
|
var val = enableAutoSync.GetBoolean();
|
||||||
|
if (_mareConfig.Current.EnableAutoDetectDiscovery != val)
|
||||||
|
{
|
||||||
|
_mareConfig.Current.EnableAutoDetectDiscovery = val;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (root.TryGetProperty("AllowAutoSyncPairRequests", out var allowAutoSync))
|
||||||
|
{
|
||||||
|
var val = allowAutoSync.GetBoolean();
|
||||||
|
if (_mareConfig.Current.AllowAutoDetectPairRequests != val)
|
||||||
|
{
|
||||||
|
_mareConfig.Current.AllowAutoDetectPairRequests = val;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (root.TryGetProperty("AutoSyncMaxDistanceMeters", out var maxDistSync) && maxDistSync.TryGetInt32(out var md))
|
||||||
|
{
|
||||||
|
if (_mareConfig.Current.AutoDetectMaxDistanceMeters != md)
|
||||||
|
{
|
||||||
|
_mareConfig.Current.AutoDetectMaxDistanceMeters = md;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (root.TryGetProperty("AutoSyncMuteMinutes", out var muteSync) && muteSync.TryGetInt32(out var mm))
|
||||||
|
{
|
||||||
|
if (_mareConfig.Current.AutoDetectMuteMinutes != mm)
|
||||||
|
{
|
||||||
|
_mareConfig.Current.AutoDetectMuteMinutes = mm;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Migrated config: AutoSync -> AutoDetect fields");
|
||||||
|
_mareConfig.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Configuration migration failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ namespace MareSynchronos.MareConfiguration.Configurations;
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class MareConfig : IMareConfiguration
|
public class MareConfig : IMareConfiguration
|
||||||
{
|
{
|
||||||
|
public int ExpectedTOSVersion = 2;
|
||||||
|
public int AcceptedTOSVersion { get; set; } = 0;
|
||||||
public bool AcceptedAgreement { get; set; } = false;
|
public bool AcceptedAgreement { get; set; } = false;
|
||||||
public string CacheFolder { get; set; } = string.Empty;
|
public string CacheFolder { get; set; } = string.Empty;
|
||||||
public bool DisableOptionalPluginWarnings { get; set; } = false;
|
public bool DisableOptionalPluginWarnings { get; set; } = false;
|
||||||
@@ -31,7 +33,7 @@ public class MareConfig : IMareConfiguration
|
|||||||
public bool LogPerformance { get; set; } = false;
|
public bool LogPerformance { get; set; } = false;
|
||||||
public bool LogEvents { get; set; } = true;
|
public bool LogEvents { get; set; } = true;
|
||||||
public bool HoldCombatApplication { get; set; } = false;
|
public bool HoldCombatApplication { get; set; } = false;
|
||||||
public double MaxLocalCacheInGiB { get; set; } = 20;
|
public double MaxLocalCacheInGiB { get; set; } = 100;
|
||||||
public bool OpenGposeImportOnGposeStart { get; set; } = false;
|
public bool OpenGposeImportOnGposeStart { get; set; } = false;
|
||||||
public bool OpenPopupOnAdd { get; set; } = true;
|
public bool OpenPopupOnAdd { get; set; } = true;
|
||||||
public int ParallelDownloads { get; set; } = 10;
|
public int ParallelDownloads { get; set; } = 10;
|
||||||
@@ -57,6 +59,10 @@ public class MareConfig : IMareConfiguration
|
|||||||
public bool ShowUploading { get; set; } = true;
|
public bool ShowUploading { get; set; } = true;
|
||||||
public bool ShowUploadingBigText { get; set; } = true;
|
public bool ShowUploadingBigText { get; set; } = true;
|
||||||
public bool ShowVisibleUsersSeparately { get; set; } = true;
|
public bool ShowVisibleUsersSeparately { get; set; } = true;
|
||||||
|
public bool EnableAutoDetectDiscovery { get; set; } = false;
|
||||||
|
public bool AllowAutoDetectPairRequests { get; set; } = false;
|
||||||
|
public int AutoDetectMaxDistanceMeters { get; set; } = 40;
|
||||||
|
public int AutoDetectMuteMinutes { get; set; } = 5;
|
||||||
public int TimeSpanBetweenScansInSeconds { get; set; } = 30;
|
public int TimeSpanBetweenScansInSeconds { get; set; } = 30;
|
||||||
public int TransferBarsHeight { get; set; } = 12;
|
public int TransferBarsHeight { get; set; } = 12;
|
||||||
public bool TransferBarsShowText { get; set; } = true;
|
public bool TransferBarsShowText { get; set; } = true;
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ namespace MareSynchronos.MareConfiguration.Configurations;
|
|||||||
public class PlayerPerformanceConfig : IMareConfiguration
|
public class PlayerPerformanceConfig : IMareConfiguration
|
||||||
{
|
{
|
||||||
public int Version { get; set; } = 1;
|
public int Version { get; set; } = 1;
|
||||||
public bool AutoPausePlayersExceedingThresholds { get; set; } = false;
|
public bool AutoPausePlayersExceedingThresholds { get; set; } = true;
|
||||||
public bool NotifyAutoPauseDirectPairs { get; set; } = true;
|
public bool NotifyAutoPauseDirectPairs { get; set; } = true;
|
||||||
public bool NotifyAutoPauseGroupPairs { get; set; } = false;
|
public bool NotifyAutoPauseGroupPairs { get; set; } = true;
|
||||||
public int VRAMSizeAutoPauseThresholdMiB { get; set; } = 550;
|
public int VRAMSizeAutoPauseThresholdMiB { get; set; } = 500;
|
||||||
public int TrisAutoPauseThresholdThousands { get; set; } = 375;
|
public int TrisAutoPauseThresholdThousands { get; set; } = 400;
|
||||||
public bool IgnoreDirectPairs { get; set; } = true;
|
public bool IgnoreDirectPairs { get; set; } = true;
|
||||||
public TextureShrinkMode TextureShrinkMode { get; set; } = TextureShrinkMode.Default;
|
public TextureShrinkMode TextureShrinkMode { get; set; } = TextureShrinkMode.Default;
|
||||||
public bool TextureShrinkDeleteOriginal { get; set; } = false;
|
public bool TextureShrinkDeleteOriginal { get; set; } = false;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class ServerConfig : IMareConfiguration
|
|||||||
|
|
||||||
public List<ServerStorage> ServerStorage { get; set; } = new()
|
public List<ServerStorage> ServerStorage { get; set; } = new()
|
||||||
{
|
{
|
||||||
{ new ServerStorage() { ServerName = ApiController.UmbraSyncServer, ServerUri = ApiController.UmbraSyncServiceUri } },
|
{ new ServerStorage() { ServerName = ApiController.UmbraServer, ServerUri = ApiController.UmbraServiceUri } },
|
||||||
};
|
};
|
||||||
|
|
||||||
public int Version { get; set; } = 1;
|
public int Version { get; set; } = 1;
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ public class MarePlugin : MediatorSubscriberBase, IHostedService
|
|||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var version = Assembly.GetExecutingAssembly().GetName().Version!;
|
var version = Assembly.GetExecutingAssembly().GetName().Version!;
|
||||||
Logger.LogInformation("Launching {name} {major}.{minor}.{build}.{rev}", "UmbraSync", version.Major, version.Minor, version.Build, version.Revision);
|
Logger.LogInformation("Launching {name} {major}.{minor}.{build}.{rev}", "Umbra Sync", version.Major, version.Minor, version.Build, version.Revision);
|
||||||
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(MarePlugin), Services.Events.EventSeverity.Informational,
|
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(MarePlugin), Services.Events.EventSeverity.Informational,
|
||||||
$"Starting UmbraSync {version.Major}.{version.Minor}.{version.Build}.{version.Revision}")));
|
$"Starting Umbra Sync {version.Major}.{version.Minor}.{version.Build}.{version.Revision}")));
|
||||||
|
|
||||||
Mediator.Subscribe<SwitchToMainUiMessage>(this, (msg) => { if (_launchTask == null || _launchTask.IsCompleted) _launchTask = Task.Run(WaitForPlayerAndLaunchCharacterManager); });
|
Mediator.Subscribe<SwitchToMainUiMessage>(this, (msg) => { if (_launchTask == null || _launchTask.IsCompleted) _launchTask = Task.Run(WaitForPlayerAndLaunchCharacterManager); });
|
||||||
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
|
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
|
||||||
@@ -157,7 +157,7 @@ public class MarePlugin : MediatorSubscriberBase, IHostedService
|
|||||||
if (_mareConfigService.Current.LogLevel != LogLevel.Information)
|
if (_mareConfigService.Current.LogLevel != LogLevel.Information)
|
||||||
{
|
{
|
||||||
Mediator.Publish(new NotificationMessage("Abnormal Log Level",
|
Mediator.Publish(new NotificationMessage("Abnormal Log Level",
|
||||||
$"Your log level is set to '{_mareConfigService.Current.LogLevel}' which is not recommended for normal usage. Set it to '{LogLevel.Information}' in \"UmbraSync Settings -> Debug\" unless instructed otherwise.",
|
$"Your log level is set to '{_mareConfigService.Current.LogLevel}' which is not recommended for normal usage. Set it to '{LogLevel.Information}' in \"Umbra Settings -> Debug\" unless instructed otherwise.",
|
||||||
MareConfiguration.Models.NotificationType.Error, TimeSpan.FromSeconds(15000)));
|
MareConfiguration.Models.NotificationType.Error, TimeSpan.FromSeconds(15000)));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project Sdk="Dalamud.NET.Sdk/13.0.0">
|
<Project Sdk="Dalamud.NET.Sdk/13.0.0">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>UmbraSync</AssemblyName>
|
<AssemblyName>UmbraSync</AssemblyName>
|
||||||
<Version>1.0.0</Version>
|
<RootNamespace>UmbraSync</RootNamespace>
|
||||||
<PackageProjectUrl></PackageProjectUrl>
|
<Version>0.1.8.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="PlayerData\Export\**" />
|
<Compile Remove="PlayerData\Export\**" />
|
||||||
@@ -13,37 +13,38 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Chaos.NaCl.Standard" Version="1.0.0" />
|
||||||
<PackageReference Include="Downloader" Version="3.3.4" />
|
<PackageReference Include="Downloader" Version="3.3.4" />
|
||||||
<PackageReference Include="K4os.Compression.LZ4.Legacy" Version="1.3.8" />
|
<PackageReference Include="K4os.Compression.LZ4.Legacy" Version="1.3.8" />
|
||||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
|
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
|
||||||
<PackageReference Include="Meziantou.Analyzer" Version="2.0.189">
|
<PackageReference Include="Meziantou.Analyzer" Version="2.0.212">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.8" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="9.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="9.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
|
||||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.7.0.110445">
|
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.15.0.120848">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.7.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="Exists('..\Penumbra.Api\Penumbra.Api.csproj')">
|
<ItemGroup Condition="Exists('.\Penumbra.Api\Penumbra.Api.csproj')">
|
||||||
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
|
<ProjectReference Include=".\Penumbra.Api\Penumbra.Api.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="!Exists('..\Penumbra.Api\Penumbra.Api.csproj')">
|
<ItemGroup Condition="!Exists('.\Penumbra.Api\Penumbra.Api.csproj')">
|
||||||
<PackageReference Include="Penumbra.Api" Version="5.6.1" />
|
<PackageReference Include="Penumbra.Api" Version="5.12.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="Exists('..\Glamourer.Api\Glamourer.Api.csproj')">
|
<ItemGroup Condition="Exists('.\Glamourer.Api\Glamourer.Api.csproj')">
|
||||||
<ProjectReference Include="..\Glamourer.Api\Glamourer.Api.csproj" />
|
<ProjectReference Include=".\Glamourer.Api\Glamourer.Api.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="!Exists('..\Glamourer.Api\Glamourer.Api.csproj')">
|
<ItemGroup Condition="!Exists('.\Glamourer.Api\Glamourer.Api.csproj')">
|
||||||
<PackageReference Include="Glamourer.Api" Version="2.4.1" />
|
<PackageReference Include="Glamourer.Api" Version="2.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@@ -52,14 +53,21 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\UmbraAPI\MareSynchronosAPI\MareSynchronos.API.csproj" />
|
<ProjectReference Include="..\MareAPI\MareSynchronosAPI\MareSynchronos.API.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="Exists('..\\Chaos.NaCl\\Chaos.NaCl\\Chaos.NaCl.csproj')">
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\\Chaos.NaCl\\Chaos.NaCl\\Chaos.NaCl.csproj" />
|
<EmbeddedResource Include="Localization\\*.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Localization\\*.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -27,14 +27,13 @@ public class PairHandlerFactory
|
|||||||
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
||||||
private readonly PairAnalyzerFactory _pairAnalyzerFactory;
|
private readonly PairAnalyzerFactory _pairAnalyzerFactory;
|
||||||
private readonly VisibilityService _visibilityService;
|
private readonly VisibilityService _visibilityService;
|
||||||
private readonly NoSnapService _noSnapService;
|
|
||||||
|
|
||||||
public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager,
|
public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager,
|
||||||
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
|
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
|
||||||
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
|
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
|
||||||
FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService,
|
FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService,
|
||||||
ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory,
|
ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory,
|
||||||
MareConfigService configService, VisibilityService visibilityService, NoSnapService noSnapService)
|
MareConfigService configService, VisibilityService visibilityService)
|
||||||
{
|
{
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
_gameObjectHandlerFactory = gameObjectHandlerFactory;
|
_gameObjectHandlerFactory = gameObjectHandlerFactory;
|
||||||
@@ -50,13 +49,12 @@ public class PairHandlerFactory
|
|||||||
_pairAnalyzerFactory = pairAnalyzerFactory;
|
_pairAnalyzerFactory = pairAnalyzerFactory;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_visibilityService = visibilityService;
|
_visibilityService = visibilityService;
|
||||||
_noSnapService = noSnapService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PairHandler Create(Pair pair)
|
public PairHandler Create(Pair pair)
|
||||||
{
|
{
|
||||||
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory,
|
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory,
|
||||||
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
|
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
|
||||||
_fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager, _configService, _visibilityService, _noSnapService);
|
_fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager, _configService, _visibilityService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
|||||||
private readonly ServerConfigurationManager _serverConfigManager;
|
private readonly ServerConfigurationManager _serverConfigManager;
|
||||||
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
||||||
private readonly VisibilityService _visibilityService;
|
private readonly VisibilityService _visibilityService;
|
||||||
private readonly NoSnapService _noSnapService;
|
|
||||||
private CancellationTokenSource? _applicationCancellationTokenSource = new();
|
private CancellationTokenSource? _applicationCancellationTokenSource = new();
|
||||||
private Guid _applicationId;
|
private Guid _applicationId;
|
||||||
private Task? _applicationTask;
|
private Task? _applicationTask;
|
||||||
@@ -55,8 +54,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
|||||||
FileCacheManager fileDbManager, MareMediator mediator,
|
FileCacheManager fileDbManager, MareMediator mediator,
|
||||||
PlayerPerformanceService playerPerformanceService,
|
PlayerPerformanceService playerPerformanceService,
|
||||||
ServerConfigurationManager serverConfigManager,
|
ServerConfigurationManager serverConfigManager,
|
||||||
MareConfigService configService, VisibilityService visibilityService,
|
MareConfigService configService, VisibilityService visibilityService) : base(logger, mediator)
|
||||||
NoSnapService noSnapService) : base(logger, mediator)
|
|
||||||
{
|
{
|
||||||
Pair = pair;
|
Pair = pair;
|
||||||
PairAnalyzer = pairAnalyzer;
|
PairAnalyzer = pairAnalyzer;
|
||||||
@@ -70,7 +68,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
|||||||
_serverConfigManager = serverConfigManager;
|
_serverConfigManager = serverConfigManager;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_visibilityService = visibilityService;
|
_visibilityService = visibilityService;
|
||||||
_noSnapService = noSnapService;
|
|
||||||
|
|
||||||
_visibilityService.StartTracking(Pair.Ident);
|
_visibilityService.StartTracking(Pair.Ident);
|
||||||
|
|
||||||
@@ -319,24 +316,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterGposeClones()
|
|
||||||
{
|
|
||||||
var name = PlayerName;
|
|
||||||
if (name == null)
|
|
||||||
return;
|
|
||||||
_ = _dalamudUtil.RunOnFrameworkThread(() =>
|
|
||||||
{
|
|
||||||
foreach (var actor in _dalamudUtil.GetGposeCharactersFromObjectTable())
|
|
||||||
{
|
|
||||||
if (actor == null) continue;
|
|
||||||
var gposeName = actor.Name.TextValue;
|
|
||||||
if (!name.Equals(gposeName, StringComparison.Ordinal))
|
|
||||||
continue;
|
|
||||||
_noSnapService.AddGposer(actor.ObjectIndex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UndoApplicationAsync(Guid applicationId = default)
|
private async Task UndoApplicationAsync(Guid applicationId = default)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"Undoing application of {Pair.UserPair}");
|
Logger.LogDebug($"Undoing application of {Pair.UserPair}");
|
||||||
@@ -353,7 +332,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
await _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).ConfigureAwait(false);
|
await _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).ConfigureAwait(false);
|
||||||
_penumbraCollection = Guid.Empty;
|
_penumbraCollection = Guid.Empty;
|
||||||
RegisterGposeClones();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name))
|
if (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name))
|
||||||
@@ -385,10 +363,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (_dalamudUtil.IsInCutscene && !string.IsNullOrEmpty(name))
|
|
||||||
{
|
|
||||||
_noSnapService.AddGposerNamed(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ public class Pair : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
OnClicked = action,
|
OnClicked = action,
|
||||||
PrefixColor = 559,
|
PrefixColor = 526,
|
||||||
PrefixChar = 'L'
|
PrefixChar = 'S'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,11 +171,6 @@ public class Pair : DisposableMediatorSubscriberBase
|
|||||||
if (_serverConfigurationManager.IsUidBlacklisted(UserData.UID))
|
if (_serverConfigurationManager.IsUidBlacklisted(UserData.UID))
|
||||||
HoldApplication("Blacklist", maxValue: 1);
|
HoldApplication("Blacklist", maxValue: 1);
|
||||||
|
|
||||||
if (NoSnapService.AnyLoaded)
|
|
||||||
HoldApplication("NoSnap", maxValue: 1);
|
|
||||||
else
|
|
||||||
UnholdApplication("NoSnap", skipApplication: true);
|
|
||||||
|
|
||||||
CachedPlayer.ApplyCharacterData(Guid.NewGuid(), RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone())!, forced);
|
CachedPlayer.ApplyCharacterData(Guid.NewGuid(), RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone())!, forced);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Dalamud.Plugin.Services;
|
|||||||
using MareSynchronos.FileCache;
|
using MareSynchronos.FileCache;
|
||||||
using MareSynchronos.Interop;
|
using MareSynchronos.Interop;
|
||||||
using MareSynchronos.Interop.Ipc;
|
using MareSynchronos.Interop.Ipc;
|
||||||
|
using MareSynchronos.Localization;
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.MareConfiguration.Configurations;
|
using MareSynchronos.MareConfiguration.Configurations;
|
||||||
using MareSynchronos.PlayerData.Factories;
|
using MareSynchronos.PlayerData.Factories;
|
||||||
@@ -29,7 +30,7 @@ using MareSynchronos.Services.CharaData;
|
|||||||
|
|
||||||
using MareSynchronos;
|
using MareSynchronos;
|
||||||
|
|
||||||
namespace UmbraSyncSync;
|
namespace Umbra;
|
||||||
|
|
||||||
public sealed class Plugin : IDalamudPlugin
|
public sealed class Plugin : IDalamudPlugin
|
||||||
{
|
{
|
||||||
@@ -39,8 +40,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
public static Plugin Self;
|
public static Plugin Self;
|
||||||
#pragma warning restore CA2211, CS8618, MA0069, S1104, S2223
|
#pragma warning restore CA2211, CS8618, MA0069, S1104, S2223
|
||||||
public Action<IFramework>? RealOnFrameworkUpdate { get; set; }
|
public Action<IFramework>? RealOnFrameworkUpdate { get; set; }
|
||||||
|
|
||||||
// Proxy function in the UmbraSyncSync namespace to avoid confusion in /xlstats
|
|
||||||
public void OnFrameworkUpdate(IFramework framework)
|
public void OnFrameworkUpdate(IFramework framework)
|
||||||
{
|
{
|
||||||
RealOnFrameworkUpdate?.Invoke(framework);
|
RealOnFrameworkUpdate?.Invoke(framework);
|
||||||
@@ -93,11 +92,17 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<MareMediator>();
|
collection.AddSingleton<MareMediator>();
|
||||||
collection.AddSingleton<FileCacheManager>();
|
collection.AddSingleton<FileCacheManager>();
|
||||||
collection.AddSingleton<ServerConfigurationManager>();
|
collection.AddSingleton<ServerConfigurationManager>();
|
||||||
|
collection.AddSingleton<LocalizationService>();
|
||||||
collection.AddSingleton<ApiController>();
|
collection.AddSingleton<ApiController>();
|
||||||
collection.AddSingleton<PerformanceCollectorService>();
|
collection.AddSingleton<PerformanceCollectorService>();
|
||||||
collection.AddSingleton<HubFactory>();
|
collection.AddSingleton<HubFactory>();
|
||||||
collection.AddSingleton<FileUploadManager>();
|
collection.AddSingleton<FileUploadManager>();
|
||||||
collection.AddSingleton<FileTransferOrchestrator>();
|
collection.AddSingleton<FileTransferOrchestrator>();
|
||||||
|
collection.AddSingleton<MareSynchronos.Services.AutoDetect.DiscoveryConfigProvider>();
|
||||||
|
collection.AddSingleton<MareSynchronos.WebAPI.AutoDetect.DiscoveryApiClient>();
|
||||||
|
collection.AddSingleton<MareSynchronos.Services.AutoDetect.AutoDetectRequestService>();
|
||||||
|
collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyDiscoveryService>();
|
||||||
|
collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyPendingService>();
|
||||||
collection.AddSingleton<MarePlugin>();
|
collection.AddSingleton<MarePlugin>();
|
||||||
collection.AddSingleton<MareProfileManager>();
|
collection.AddSingleton<MareProfileManager>();
|
||||||
collection.AddSingleton<GameObjectHandlerFactory>();
|
collection.AddSingleton<GameObjectHandlerFactory>();
|
||||||
@@ -126,7 +131,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<BlockedCharacterHandler>();
|
collection.AddSingleton<BlockedCharacterHandler>();
|
||||||
collection.AddSingleton<IpcProvider>();
|
collection.AddSingleton<IpcProvider>();
|
||||||
collection.AddSingleton<VisibilityService>();
|
collection.AddSingleton<VisibilityService>();
|
||||||
collection.AddSingleton<RepoChangeService>();
|
|
||||||
collection.AddSingleton<EventAggregator>();
|
collection.AddSingleton<EventAggregator>();
|
||||||
collection.AddSingleton<DalamudUtilService>();
|
collection.AddSingleton<DalamudUtilService>();
|
||||||
collection.AddSingleton<DtrEntry>();
|
collection.AddSingleton<DtrEntry>();
|
||||||
@@ -143,7 +147,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<IpcCallerMare>();
|
collection.AddSingleton<IpcCallerMare>();
|
||||||
collection.AddSingleton<IpcManager>();
|
collection.AddSingleton<IpcManager>();
|
||||||
collection.AddSingleton<NotificationService>();
|
collection.AddSingleton<NotificationService>();
|
||||||
collection.AddSingleton<NoSnapService>();
|
collection.AddSingleton<TemporarySyncshellNotificationService>();
|
||||||
|
|
||||||
collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName));
|
collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||||
collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName));
|
collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||||
@@ -169,7 +173,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<RemoteConfigCacheService>());
|
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<RemoteConfigCacheService>());
|
||||||
collection.AddSingleton<ConfigurationMigrator>();
|
collection.AddSingleton<ConfigurationMigrator>();
|
||||||
collection.AddSingleton<ConfigurationSaveService>();
|
collection.AddSingleton<ConfigurationSaveService>();
|
||||||
collection.AddSingleton<RemoteConfigurationService>();
|
|
||||||
|
|
||||||
collection.AddSingleton<HubFactory>();
|
collection.AddSingleton<HubFactory>();
|
||||||
|
|
||||||
@@ -180,6 +183,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddScoped<WindowMediatorSubscriberBase, CompactUi>();
|
collection.AddScoped<WindowMediatorSubscriberBase, CompactUi>();
|
||||||
collection.AddScoped<WindowMediatorSubscriberBase, IntroUi>();
|
collection.AddScoped<WindowMediatorSubscriberBase, IntroUi>();
|
||||||
collection.AddScoped<WindowMediatorSubscriberBase, DownloadUi>();
|
collection.AddScoped<WindowMediatorSubscriberBase, DownloadUi>();
|
||||||
|
collection.AddScoped<WindowMediatorSubscriberBase, AutoDetectUi>();
|
||||||
collection.AddScoped<WindowMediatorSubscriberBase, PopoutProfileUi>();
|
collection.AddScoped<WindowMediatorSubscriberBase, PopoutProfileUi>();
|
||||||
collection.AddScoped<WindowMediatorSubscriberBase, DataAnalysisUi>();
|
collection.AddScoped<WindowMediatorSubscriberBase, DataAnalysisUi>();
|
||||||
collection.AddScoped<WindowMediatorSubscriberBase, EventViewerUI>();
|
collection.AddScoped<WindowMediatorSubscriberBase, EventViewerUI>();
|
||||||
@@ -202,6 +206,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddHostedService(p => p.GetRequiredService<ConfigurationSaveService>());
|
collection.AddHostedService(p => p.GetRequiredService<ConfigurationSaveService>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<MareMediator>());
|
collection.AddHostedService(p => p.GetRequiredService<MareMediator>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<NotificationService>());
|
collection.AddHostedService(p => p.GetRequiredService<NotificationService>());
|
||||||
|
collection.AddHostedService(p => p.GetRequiredService<TemporarySyncshellNotificationService>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<FileCacheManager>());
|
collection.AddHostedService(p => p.GetRequiredService<FileCacheManager>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<ConfigurationMigrator>());
|
collection.AddHostedService(p => p.GetRequiredService<ConfigurationMigrator>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<DalamudUtilService>());
|
collection.AddHostedService(p => p.GetRequiredService<DalamudUtilService>());
|
||||||
@@ -210,8 +215,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddHostedService(p => p.GetRequiredService<EventAggregator>());
|
collection.AddHostedService(p => p.GetRequiredService<EventAggregator>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());
|
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
|
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<RepoChangeService>());
|
collection.AddHostedService(p => p.GetRequiredService<MareSynchronos.Services.AutoDetect.NearbyDiscoveryService>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<NoSnapService>());
|
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MareSynchronos.WebAPI.AutoDetect;
|
||||||
|
using MareSynchronos.MareConfiguration;
|
||||||
|
using MareSynchronos.Services;
|
||||||
|
using MareSynchronos.Services.Mediator;
|
||||||
|
using NotificationType = MareSynchronos.MareConfiguration.Models.NotificationType;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Services.AutoDetect;
|
||||||
|
|
||||||
|
public class AutoDetectRequestService
|
||||||
|
{
|
||||||
|
private readonly ILogger<AutoDetectRequestService> _logger;
|
||||||
|
private readonly DiscoveryConfigProvider _configProvider;
|
||||||
|
private readonly DiscoveryApiClient _client;
|
||||||
|
private readonly MareConfigService _configService;
|
||||||
|
private readonly DalamudUtilService _dalamud;
|
||||||
|
private readonly MareMediator _mediator;
|
||||||
|
|
||||||
|
public AutoDetectRequestService(ILogger<AutoDetectRequestService> logger, DiscoveryConfigProvider configProvider, DiscoveryApiClient client, MareConfigService configService, MareMediator mediator, DalamudUtilService dalamudUtilService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_configProvider = configProvider;
|
||||||
|
_client = client;
|
||||||
|
_configService = configService;
|
||||||
|
_mediator = mediator;
|
||||||
|
_dalamud = dalamudUtilService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SendRequestAsync(string token, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (!_configService.Current.AllowAutoDetectPairRequests)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Nearby request blocked: AllowAutoDetectPairRequests is disabled");
|
||||||
|
_mediator.Publish(new NotificationMessage("Nearby request blocked", "Enable 'Allow pair requests' in Settings to send requests.", NotificationType.Info));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var endpoint = _configProvider.RequestEndpoint;
|
||||||
|
if (string.IsNullOrEmpty(endpoint))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("No request endpoint configured");
|
||||||
|
_mediator.Publish(new NotificationMessage("Nearby request failed", "Server does not expose request endpoint.", NotificationType.Error));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
string? displayName = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
|
||||||
|
displayName = me?.Name.TextValue;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
_logger.LogInformation("Nearby: sending pair request via {endpoint}", endpoint);
|
||||||
|
var ok = await _client.SendRequestAsync(endpoint!, token, displayName, ct).ConfigureAwait(false);
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
_mediator.Publish(new NotificationMessage("Nearby request sent", "The other user will receive a request notification.", NotificationType.Info));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_mediator.Publish(new NotificationMessage("Nearby request failed", "The server rejected the request. Try again soon.", NotificationType.Warning));
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SendAcceptNotifyAsync(string targetUid, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var endpoint = _configProvider.AcceptEndpoint;
|
||||||
|
if (string.IsNullOrEmpty(endpoint))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("No accept endpoint configured");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
string? displayName = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
|
||||||
|
displayName = me?.Name.TextValue;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
_logger.LogInformation("Nearby: sending accept notify via {endpoint}", endpoint);
|
||||||
|
return await _client.SendAcceptAsync(endpoint!, targetUid, displayName, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
173
MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs
Normal file
173
MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MareSynchronos.Services.Mediator;
|
||||||
|
using MareSynchronos.Services.ServerConfiguration;
|
||||||
|
using MareSynchronos.WebAPI.SignalR;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Services.AutoDetect;
|
||||||
|
|
||||||
|
public class DiscoveryConfigProvider
|
||||||
|
{
|
||||||
|
private readonly ILogger<DiscoveryConfigProvider> _logger;
|
||||||
|
private readonly ServerConfigurationManager _serverManager;
|
||||||
|
private readonly TokenProvider _tokenProvider;
|
||||||
|
|
||||||
|
private WellKnownRoot? _config;
|
||||||
|
private DateTimeOffset _lastLoad = DateTimeOffset.MinValue;
|
||||||
|
|
||||||
|
public DiscoveryConfigProvider(ILogger<DiscoveryConfigProvider> logger, ServerConfigurationManager serverManager, TokenProvider tokenProvider)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_serverManager = serverManager;
|
||||||
|
_tokenProvider = tokenProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasConfig => _config != null;
|
||||||
|
public bool NearbyEnabled => _config?.NearbyDiscovery?.Enabled ?? false;
|
||||||
|
public byte[]? Salt => _config?.NearbyDiscovery?.SaltBytes;
|
||||||
|
public string? SaltB64 => _config?.NearbyDiscovery?.SaltB64;
|
||||||
|
public DateTimeOffset? SaltExpiresAt => _config?.NearbyDiscovery?.SaltExpiresAt;
|
||||||
|
public int RefreshSec => _config?.NearbyDiscovery?.RefreshSec ?? 300;
|
||||||
|
public int MinQueryIntervalMs => _config?.NearbyDiscovery?.Policies?.MinQueryIntervalMs ?? 2000;
|
||||||
|
public int MaxQueryBatch => _config?.NearbyDiscovery?.Policies?.MaxQueryBatch ?? 100;
|
||||||
|
public string? PublishEndpoint => _config?.NearbyDiscovery?.Endpoints?.Publish;
|
||||||
|
public string? QueryEndpoint => _config?.NearbyDiscovery?.Endpoints?.Query;
|
||||||
|
public string? RequestEndpoint => _config?.NearbyDiscovery?.Endpoints?.Request;
|
||||||
|
public string? AcceptEndpoint => _config?.NearbyDiscovery?.Endpoints?.Accept;
|
||||||
|
|
||||||
|
public bool TryLoadFromStapled()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = _tokenProvider.GetStapledWellKnown(_serverManager.CurrentApiUrl);
|
||||||
|
if (string.IsNullOrEmpty(json)) return false;
|
||||||
|
|
||||||
|
var root = JsonSerializer.Deserialize<WellKnownRoot>(json!);
|
||||||
|
if (root == null) return false;
|
||||||
|
|
||||||
|
root.NearbyDiscovery?.Hydrate();
|
||||||
|
_config = root;
|
||||||
|
_lastLoad = DateTimeOffset.UtcNow;
|
||||||
|
_logger.LogDebug("Loaded Nearby well-known (stapled), enabled={enabled}, expires={exp}", NearbyEnabled, _config?.NearbyDiscovery?.SaltExpiresAt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to parse stapled well-known");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> TryFetchFromServerAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var baseUrl = _serverManager.CurrentApiUrl
|
||||||
|
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase);
|
||||||
|
// Try likely candidates based on nginx config
|
||||||
|
string[] candidates =
|
||||||
|
[
|
||||||
|
"/.well-known/Umbra/client", // matches provided nginx
|
||||||
|
"/.well-known/umbra", // lowercase variant
|
||||||
|
];
|
||||||
|
|
||||||
|
using var http = new HttpClient();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ver = Assembly.GetExecutingAssembly().GetName().Version!;
|
||||||
|
http.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("MareSynchronos", $"{ver.Major}.{ver.Minor}.{ver.Build}"));
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
foreach (var path in candidates)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var uri = new Uri(new Uri(baseUrl), path);
|
||||||
|
var json = await http.GetStringAsync(uri, ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(json)) continue;
|
||||||
|
|
||||||
|
var root = JsonSerializer.Deserialize<WellKnownRoot>(json);
|
||||||
|
if (root == null) continue;
|
||||||
|
|
||||||
|
root.NearbyDiscovery?.Hydrate();
|
||||||
|
_config = root;
|
||||||
|
_lastLoad = DateTimeOffset.UtcNow;
|
||||||
|
_logger.LogInformation("Loaded Nearby well-known (http {path}), enabled={enabled}", path, NearbyEnabled);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Nearby well-known fetch failed for {path}", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Nearby well-known not found via HTTP candidates");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to fetch Nearby well-known via HTTP");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsExpired()
|
||||||
|
{
|
||||||
|
if (_config?.NearbyDiscovery?.SaltExpiresAt == null) return false;
|
||||||
|
return DateTimeOffset.UtcNow > _config.NearbyDiscovery.SaltExpiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DTOs for well-known JSON
|
||||||
|
private sealed class WellKnownRoot
|
||||||
|
{
|
||||||
|
[JsonPropertyName("features")] public Features? Features { get; set; }
|
||||||
|
[JsonPropertyName("nearby_discovery")] public Nearby? NearbyDiscovery { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Features
|
||||||
|
{
|
||||||
|
[JsonPropertyName("nearby_discovery")] public bool NearbyDiscovery { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Nearby
|
||||||
|
{
|
||||||
|
[JsonPropertyName("enabled")] public bool Enabled { get; set; }
|
||||||
|
[JsonPropertyName("hash_algo")] public string? HashAlgo { get; set; }
|
||||||
|
[JsonPropertyName("salt_b64")] public string? SaltB64 { get; set; }
|
||||||
|
[JsonPropertyName("salt_expires_at")] public string? SaltExpiresAtRaw { get; set; }
|
||||||
|
[JsonPropertyName("refresh_sec")] public int RefreshSec { get; set; } = 300;
|
||||||
|
[JsonPropertyName("endpoints")] public Endpoints? Endpoints { get; set; }
|
||||||
|
[JsonPropertyName("policies")] public Policies? Policies { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore] public byte[]? SaltBytes { get; private set; }
|
||||||
|
[JsonIgnore] public DateTimeOffset? SaltExpiresAt { get; private set; }
|
||||||
|
|
||||||
|
public void Hydrate()
|
||||||
|
{
|
||||||
|
try { SaltBytes = string.IsNullOrEmpty(SaltB64) ? null : Convert.FromBase64String(SaltB64!); } catch { SaltBytes = null; }
|
||||||
|
if (DateTimeOffset.TryParse(SaltExpiresAtRaw, out var dto)) SaltExpiresAt = dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Endpoints
|
||||||
|
{
|
||||||
|
[JsonPropertyName("publish")] public string? Publish { get; set; }
|
||||||
|
[JsonPropertyName("query")] public string? Query { get; set; }
|
||||||
|
[JsonPropertyName("request")] public string? Request { get; set; }
|
||||||
|
[JsonPropertyName("accept")] public string? Accept { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Policies
|
||||||
|
{
|
||||||
|
[JsonPropertyName("max_query_batch")] public int MaxQueryBatch { get; set; } = 100;
|
||||||
|
[JsonPropertyName("min_query_interval_ms")] public int MinQueryIntervalMs { get; set; } = 2000;
|
||||||
|
[JsonPropertyName("rate_limit_per_min")] public int RateLimitPerMin { get; set; } = 30;
|
||||||
|
[JsonPropertyName("token_ttl_sec")] public int TokenTtlSec { get; set; } = 120;
|
||||||
|
}
|
||||||
|
}
|
||||||
477
MareSynchronos/Services/AutoDetect/NearbyDiscoveryService.cs
Normal file
477
MareSynchronos/Services/AutoDetect/NearbyDiscoveryService.cs
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using MareSynchronos.Services.Mediator;
|
||||||
|
using MareSynchronos.MareConfiguration;
|
||||||
|
using MareSynchronos.Services.ServerConfiguration;
|
||||||
|
using MareSynchronos.WebAPI.AutoDetect;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Linq;
|
||||||
|
using MareSynchronos.Utils;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Services.AutoDetect;
|
||||||
|
|
||||||
|
public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
|
||||||
|
{
|
||||||
|
private readonly ILogger<NearbyDiscoveryService> _logger;
|
||||||
|
private readonly MareMediator _mediator;
|
||||||
|
private readonly MareConfigService _config;
|
||||||
|
private readonly DiscoveryConfigProvider _configProvider;
|
||||||
|
private readonly DalamudUtilService _dalamud;
|
||||||
|
private readonly IObjectTable _objectTable;
|
||||||
|
private readonly DiscoveryApiClient _api;
|
||||||
|
private CancellationTokenSource? _loopCts;
|
||||||
|
private string? _lastPublishedSignature;
|
||||||
|
private bool _loggedLocalOnly;
|
||||||
|
private int _lastLocalCount = -1;
|
||||||
|
private int _lastMatchCount = -1;
|
||||||
|
private bool _loggedConfigReady;
|
||||||
|
private string? _lastSnapshotSig;
|
||||||
|
private volatile bool _isConnected;
|
||||||
|
private bool _notifiedDisabled;
|
||||||
|
private bool _notifiedEnabled;
|
||||||
|
private bool _disableSent;
|
||||||
|
private bool _lastAutoDetectState;
|
||||||
|
private DateTime _lastHeartbeat = DateTime.MinValue;
|
||||||
|
private static readonly TimeSpan HeartbeatInterval = TimeSpan.FromSeconds(75);
|
||||||
|
|
||||||
|
public NearbyDiscoveryService(ILogger<NearbyDiscoveryService> logger, MareMediator mediator,
|
||||||
|
MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService,
|
||||||
|
IObjectTable objectTable, DiscoveryApiClient api)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_mediator = mediator;
|
||||||
|
_config = config;
|
||||||
|
_configProvider = configProvider;
|
||||||
|
_dalamud = dalamudUtilService;
|
||||||
|
_objectTable = objectTable;
|
||||||
|
_api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MareMediator Mediator => _mediator;
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_loopCts = new CancellationTokenSource();
|
||||||
|
_mediator.Subscribe<ConnectedMessage>(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); });
|
||||||
|
_mediator.Subscribe<DisconnectedMessage>(this, _ => { _isConnected = false; _lastPublishedSignature = null; });
|
||||||
|
_mediator.Subscribe<AllowPairRequestsToggled>(this, OnAllowPairRequestsToggled);
|
||||||
|
_ = Task.Run(() => Loop(_loopCts.Token));
|
||||||
|
_lastAutoDetectState = _config.Current.EnableAutoDetectDiscovery;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
private async void OnAllowPairRequestsToggled(AllowPairRequestsToggled msg)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_config.Current.EnableAutoDetectDiscovery) return;
|
||||||
|
// Force a publish now so the server immediately reflects the new allow/deny state
|
||||||
|
_lastPublishedSignature = null; // ensure next loop won't skip
|
||||||
|
await PublishSelfOnceAsync(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "OnAllowPairRequestsToggled failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PublishSelfOnceAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_config.Current.EnableAutoDetectDiscovery || !_dalamud.IsLoggedIn || !_isConnected) return;
|
||||||
|
|
||||||
|
if (!_configProvider.HasConfig || _configProvider.IsExpired())
|
||||||
|
{
|
||||||
|
if (!_configProvider.TryLoadFromStapled())
|
||||||
|
{
|
||||||
|
await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ep = _configProvider.PublishEndpoint;
|
||||||
|
var saltBytes = _configProvider.Salt;
|
||||||
|
if (string.IsNullOrEmpty(ep) || saltBytes is not { Length: > 0 }) return;
|
||||||
|
|
||||||
|
var saltHex = Convert.ToHexString(saltBytes);
|
||||||
|
string? displayName = null;
|
||||||
|
ushort meWorld = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
|
||||||
|
if (me != null)
|
||||||
|
{
|
||||||
|
displayName = me.Name.TextValue;
|
||||||
|
if (me is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter mePc)
|
||||||
|
meWorld = (ushort)mePc.HomeWorld.RowId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(displayName)) return;
|
||||||
|
|
||||||
|
var selfHash = (saltHex + displayName + meWorld.ToString()).GetHash256();
|
||||||
|
var ok = await _api.PublishAsync(ep!, new[] { selfHash }, displayName, ct, _config.Current.AllowAutoDetectPairRequests).ConfigureAwait(false);
|
||||||
|
_logger.LogInformation("Nearby publish (manual/immediate): {result}", ok ? "success" : "failed");
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
_lastPublishedSignature = selfHash;
|
||||||
|
_lastHeartbeat = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Immediate publish failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_mediator.UnsubscribeAll(this);
|
||||||
|
try { _loopCts?.Cancel(); } catch { }
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Loop(CancellationToken ct)
|
||||||
|
{
|
||||||
|
_configProvider.TryLoadFromStapled();
|
||||||
|
|
||||||
|
while (!ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool currentState = _config.Current.EnableAutoDetectDiscovery;
|
||||||
|
if (currentState != _lastAutoDetectState)
|
||||||
|
{
|
||||||
|
_lastAutoDetectState = currentState;
|
||||||
|
if (currentState)
|
||||||
|
{
|
||||||
|
// Force immediate publish on toggle ON
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Ensure well-known is present
|
||||||
|
if (!_configProvider.HasConfig || _configProvider.IsExpired())
|
||||||
|
{
|
||||||
|
if (!_configProvider.TryLoadFromStapled())
|
||||||
|
{
|
||||||
|
await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ep = _configProvider.PublishEndpoint;
|
||||||
|
var saltBytes = _configProvider.Salt;
|
||||||
|
if (!string.IsNullOrEmpty(ep) && saltBytes is { Length: > 0 })
|
||||||
|
{
|
||||||
|
var saltHex = Convert.ToHexString(saltBytes);
|
||||||
|
string? displayName = null;
|
||||||
|
ushort meWorld = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
|
||||||
|
if (me != null)
|
||||||
|
{
|
||||||
|
displayName = me.Name.TextValue;
|
||||||
|
if (me is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter mePc)
|
||||||
|
meWorld = (ushort)mePc.HomeWorld.RowId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(displayName))
|
||||||
|
{
|
||||||
|
var selfHash = (saltHex + displayName + meWorld.ToString()).GetHash256();
|
||||||
|
_lastPublishedSignature = null; // ensure future loop doesn't skip
|
||||||
|
var okNow = await _api.PublishAsync(ep!, new[] { selfHash }, displayName, ct, _config.Current.AllowAutoDetectPairRequests).ConfigureAwait(false);
|
||||||
|
_logger.LogInformation("Nearby immediate publish on toggle ON: {result}", okNow ? "success" : "failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Nearby immediate publish on toggle ON failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_notifiedEnabled)
|
||||||
|
{
|
||||||
|
_mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect enabled : you are now visible.", default));
|
||||||
|
_notifiedEnabled = true;
|
||||||
|
_notifiedDisabled = false;
|
||||||
|
_disableSent = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var ep = _configProvider.PublishEndpoint;
|
||||||
|
if (!string.IsNullOrEmpty(ep) && !_disableSent)
|
||||||
|
{
|
||||||
|
var disableUrl = ep.Replace("/publish", "/disable");
|
||||||
|
try { await _api.DisableAsync(disableUrl, ct).ConfigureAwait(false); _disableSent = true; } catch { }
|
||||||
|
}
|
||||||
|
if (!_notifiedDisabled)
|
||||||
|
{
|
||||||
|
_mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect disabled : you are not visible.", default));
|
||||||
|
_notifiedDisabled = true;
|
||||||
|
_notifiedEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!_config.Current.EnableAutoDetectDiscovery || !_dalamud.IsLoggedIn || !_isConnected)
|
||||||
|
{
|
||||||
|
if (!_config.Current.EnableAutoDetectDiscovery && !string.IsNullOrEmpty(_configProvider.PublishEndpoint))
|
||||||
|
{
|
||||||
|
var disableUrl = _configProvider.PublishEndpoint.Replace("/publish", "/disable");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_disableSent)
|
||||||
|
{
|
||||||
|
await _api.DisableAsync(disableUrl, ct).ConfigureAwait(false);
|
||||||
|
_disableSent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
if (!_notifiedDisabled)
|
||||||
|
{
|
||||||
|
_mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect disabled : you are not visible.", default));
|
||||||
|
_notifiedDisabled = true;
|
||||||
|
_notifiedEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Task.Delay(1000, ct).ConfigureAwait(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!_configProvider.HasConfig || _configProvider.IsExpired())
|
||||||
|
{
|
||||||
|
if (!_configProvider.TryLoadFromStapled())
|
||||||
|
{
|
||||||
|
await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!_loggedConfigReady && _configProvider.NearbyEnabled)
|
||||||
|
{
|
||||||
|
_loggedConfigReady = true;
|
||||||
|
_logger.LogInformation("Nearby: well-known loaded and enabled; refresh={refresh}s, expires={exp}", _configProvider.RefreshSec, _configProvider.SaltExpiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries = await GetLocalNearbyAsync().ConfigureAwait(false);
|
||||||
|
// Log when local count changes (including 0) to indicate activity
|
||||||
|
if (entries.Count != _lastLocalCount)
|
||||||
|
{
|
||||||
|
_lastLocalCount = entries.Count;
|
||||||
|
_logger.LogTrace("Nearby: {count} players detected locally", _lastLocalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try query server if config and endpoints are present
|
||||||
|
if (_configProvider.NearbyEnabled && !_configProvider.IsExpired() &&
|
||||||
|
_configProvider.Salt is { Length: > 0 })
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var saltHex = Convert.ToHexString(_configProvider.Salt!);
|
||||||
|
// map hash->index for result matching
|
||||||
|
Dictionary<string, int> hashToIndex = new(StringComparer.Ordinal);
|
||||||
|
List<string> hashes = new(entries.Count);
|
||||||
|
foreach (var (entry, idx) in entries.Select((e, i) => (e, i)))
|
||||||
|
{
|
||||||
|
var h = (saltHex + entry.Name + entry.WorldId.ToString()).GetHash256();
|
||||||
|
hashToIndex[h] = idx;
|
||||||
|
hashes.Add(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var snapSig = string.Join(',', hashes.OrderBy(s => s, StringComparer.Ordinal)).GetHash256();
|
||||||
|
if (!string.Equals(snapSig, _lastSnapshotSig, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_lastSnapshotSig = snapSig;
|
||||||
|
var sample = entries.Take(5).Select(e =>
|
||||||
|
{
|
||||||
|
var hh = (saltHex + e.Name + e.WorldId.ToString()).GetHash256();
|
||||||
|
var shortH = hh.Length > 8 ? hh[..8] : hh;
|
||||||
|
return $"{e.Name}({e.WorldId})->{shortH}";
|
||||||
|
});
|
||||||
|
var saltShort = saltHex.Length > 8 ? saltHex[..8] : saltHex;
|
||||||
|
_logger.LogTrace("Nearby snapshot: {count} entries; salt={saltShort}…; samples=[{samples}]",
|
||||||
|
entries.Count, saltShort, string.Join(", ", sample));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_configProvider.PublishEndpoint))
|
||||||
|
{
|
||||||
|
string? displayName = null;
|
||||||
|
string? selfHash = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var me = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
|
||||||
|
if (me != null)
|
||||||
|
{
|
||||||
|
displayName = me.Name.TextValue;
|
||||||
|
ushort meWorld = 0;
|
||||||
|
if (me is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter mePc)
|
||||||
|
meWorld = (ushort)mePc.HomeWorld.RowId;
|
||||||
|
_logger.LogTrace("Nearby self ident: {name} ({world})", displayName, meWorld);
|
||||||
|
selfHash = (saltHex + displayName + meWorld.ToString()).GetHash256();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* ignore */ }
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(selfHash))
|
||||||
|
{
|
||||||
|
var sig = selfHash!;
|
||||||
|
if (!string.Equals(sig, _lastPublishedSignature, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_lastPublishedSignature = sig;
|
||||||
|
var shortSelf = selfHash!.Length > 8 ? selfHash[..8] : selfHash;
|
||||||
|
_logger.LogDebug("Nearby publish: self presence updated (hash={hash})", shortSelf);
|
||||||
|
var ok = await _api.PublishAsync(_configProvider.PublishEndpoint!, new[] { selfHash! }, displayName, ct, _config.Current.AllowAutoDetectPairRequests).ConfigureAwait(false);
|
||||||
|
_logger.LogInformation("Nearby publish result: {result}", ok ? "success" : "failed");
|
||||||
|
if (ok) _lastHeartbeat = DateTime.UtcNow;
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
if (!_notifiedEnabled)
|
||||||
|
{
|
||||||
|
_mediator.Publish(new NotificationMessage("Nearby Detection", "AutoDetect enabled : you are now visible.", default));
|
||||||
|
_notifiedEnabled = true;
|
||||||
|
_notifiedDisabled = false;
|
||||||
|
_disableSent = false; // allow future /disable when turning off again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No changes; perform heartbeat publish if interval elapsed
|
||||||
|
if (DateTime.UtcNow - _lastHeartbeat >= HeartbeatInterval)
|
||||||
|
{
|
||||||
|
var okHb = await _api.PublishAsync(_configProvider.PublishEndpoint!, new[] { selfHash! }, displayName, ct, _config.Current.AllowAutoDetectPairRequests).ConfigureAwait(false);
|
||||||
|
_logger.LogDebug("Nearby heartbeat publish: {result}", okHb ? "success" : "failed");
|
||||||
|
if (okHb) _lastHeartbeat = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Nearby publish skipped (no changes)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else: no self character available; skip publish silently
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query for matches if endpoint is available
|
||||||
|
if (!string.IsNullOrEmpty(_configProvider.QueryEndpoint))
|
||||||
|
{
|
||||||
|
// chunked queries
|
||||||
|
int batch = Math.Max(1, _configProvider.MaxQueryBatch);
|
||||||
|
List<ServerMatch> allMatches = new();
|
||||||
|
for (int i = 0; i < hashes.Count; i += batch)
|
||||||
|
{
|
||||||
|
var slice = hashes.Skip(i).Take(batch).ToArray();
|
||||||
|
var res = await _api.QueryAsync(_configProvider.QueryEndpoint!, slice, ct).ConfigureAwait(false);
|
||||||
|
if (res != null && res.Count > 0) allMatches.AddRange(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allMatches.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var m in allMatches)
|
||||||
|
{
|
||||||
|
if (hashToIndex.TryGetValue(m.Hash, out var idx))
|
||||||
|
{
|
||||||
|
var e = entries[idx];
|
||||||
|
entries[idx] = new NearbyEntry(e.Name, e.WorldId, e.Distance, true, m.Token, m.DisplayName, m.Uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allMatches.Count > 0)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Nearby: server returned {count} matches", allMatches.Count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogTrace("Nearby: server returned {count} matches", allMatches.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log change in number of Umbra matches
|
||||||
|
int matchCount = entries.Count(e => e.IsMatch);
|
||||||
|
if (matchCount != _lastMatchCount)
|
||||||
|
{
|
||||||
|
_lastMatchCount = matchCount;
|
||||||
|
if (matchCount > 0)
|
||||||
|
{
|
||||||
|
var matchSamples = entries.Where(e => e.IsMatch).Take(5)
|
||||||
|
.Select(e => string.IsNullOrEmpty(e.DisplayName) ? e.Name : e.DisplayName!);
|
||||||
|
_logger.LogInformation("Nearby: {count} Umbra users nearby [{samples}]",
|
||||||
|
matchCount, string.Join(", ", matchSamples));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogTrace("Nearby: {count} Umbra users nearby", matchCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Nearby query failed; falling back to local list");
|
||||||
|
if (ex.Message.Contains("DISCOVERY_SALT_EXPIRED", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Nearby: salt expired, refetching well-known");
|
||||||
|
try { await _configProvider.TryFetchFromServerAsync(ct).ConfigureAwait(false); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!_loggedLocalOnly)
|
||||||
|
{
|
||||||
|
_loggedLocalOnly = true;
|
||||||
|
_logger.LogDebug("Nearby: well-known not available or disabled; running in local-only mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_mediator.Publish(new DiscoveryListUpdated(entries));
|
||||||
|
|
||||||
|
var delayMs = Math.Max(1000, _configProvider.MinQueryIntervalMs);
|
||||||
|
if (entries.Count == 0) delayMs = Math.Max(delayMs, 5000);
|
||||||
|
await Task.Delay(delayMs, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "NearbyDiscoveryService loop error");
|
||||||
|
await Task.Delay(2000, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<NearbyEntry>> GetLocalNearbyAsync()
|
||||||
|
{
|
||||||
|
var list = new List<NearbyEntry>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var local = await _dalamud.RunOnFrameworkThread(() => _dalamud.GetPlayerCharacter()).ConfigureAwait(false);
|
||||||
|
var localPos = local?.Position ?? Vector3.Zero;
|
||||||
|
int maxDist = Math.Clamp(_config.Current.AutoDetectMaxDistanceMeters, 5, 100);
|
||||||
|
|
||||||
|
int limit = Math.Min(200, _objectTable.Length);
|
||||||
|
for (int i = 0; i < limit; i++)
|
||||||
|
{
|
||||||
|
var obj = await _dalamud.RunOnFrameworkThread(() => _objectTable[i]).ConfigureAwait(false);
|
||||||
|
if (obj == null || obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
|
||||||
|
if (local != null && obj.Address == local.Address) continue;
|
||||||
|
|
||||||
|
float dist = local == null ? float.NaN : Vector3.Distance(localPos, obj.Position);
|
||||||
|
if (!float.IsNaN(dist) && dist > maxDist) continue;
|
||||||
|
|
||||||
|
string name = obj.Name.TextValue;
|
||||||
|
ushort worldId = 0;
|
||||||
|
if (obj is Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter pc)
|
||||||
|
worldId = (ushort)pc.HomeWorld.RowId;
|
||||||
|
|
||||||
|
list.Add(new NearbyEntry(name, worldId, dist, false, null, null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
87
MareSynchronos/Services/AutoDetect/NearbyPendingService.cs
Normal file
87
MareSynchronos/Services/AutoDetect/NearbyPendingService.cs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using MareSynchronos.Services.Mediator;
|
||||||
|
using MareSynchronos.WebAPI;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Services.AutoDetect;
|
||||||
|
|
||||||
|
public sealed class NearbyPendingService : IMediatorSubscriber
|
||||||
|
{
|
||||||
|
private readonly ILogger<NearbyPendingService> _logger;
|
||||||
|
private readonly MareMediator _mediator;
|
||||||
|
private readonly ApiController _api;
|
||||||
|
private readonly AutoDetectRequestService _requestService;
|
||||||
|
private readonly ConcurrentDictionary<string, string> _pending = new(StringComparer.Ordinal);
|
||||||
|
private static readonly Regex ReqRegex = new(@"^Nearby Request: (.+) \[(?<uid>[A-Z0-9]+)\]$", RegexOptions.Compiled);
|
||||||
|
private static readonly Regex AcceptRegex = new(@"^Nearby Accept: (.+) \[(?<uid>[A-Z0-9]+)\]$", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
public NearbyPendingService(ILogger<NearbyPendingService> logger, MareMediator mediator, ApiController api, AutoDetectRequestService requestService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_mediator = mediator;
|
||||||
|
_api = api;
|
||||||
|
_requestService = requestService;
|
||||||
|
_mediator.Subscribe<NotificationMessage>(this, OnNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MareMediator Mediator => _mediator;
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, string> Pending => _pending;
|
||||||
|
|
||||||
|
private void OnNotification(NotificationMessage msg)
|
||||||
|
{
|
||||||
|
// Watch info messages for Nearby request pattern
|
||||||
|
if (msg.Type != MareSynchronos.MareConfiguration.Models.NotificationType.Info) return;
|
||||||
|
var ma = AcceptRegex.Match(msg.Message);
|
||||||
|
if (ma.Success)
|
||||||
|
{
|
||||||
|
var uidA = ma.Groups["uid"].Value;
|
||||||
|
if (!string.IsNullOrEmpty(uidA))
|
||||||
|
{
|
||||||
|
_ = _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uidA)));
|
||||||
|
_pending.TryRemove(uidA, out _);
|
||||||
|
_logger.LogInformation("NearbyPending: auto-accepted pairing with {uid}", uidA);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = ReqRegex.Match(msg.Message);
|
||||||
|
if (!m.Success) return;
|
||||||
|
var uid = m.Groups["uid"].Value;
|
||||||
|
if (string.IsNullOrEmpty(uid)) return;
|
||||||
|
// Try to extract name as everything before space and '['
|
||||||
|
var name = msg.Message;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var idx = msg.Message.IndexOf(':');
|
||||||
|
if (idx >= 0) name = msg.Message[(idx + 1)..].Trim();
|
||||||
|
var br = name.LastIndexOf('[');
|
||||||
|
if (br > 0) name = name[..br].Trim();
|
||||||
|
}
|
||||||
|
catch { name = uid; }
|
||||||
|
_pending[uid] = name;
|
||||||
|
_logger.LogInformation("NearbyPending: received request from {uid} ({name})", uid, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(string uid)
|
||||||
|
{
|
||||||
|
_pending.TryRemove(uid, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> AcceptAsync(string uid)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uid))).ConfigureAwait(false);
|
||||||
|
_pending.TryRemove(uid, out _);
|
||||||
|
_ = _requestService.SendAcceptNotifyAsync(uid);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "NearbyPending: accept failed for {uid}", uid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,20 +13,18 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
|
|||||||
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
|
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
|
||||||
private readonly DalamudUtilService _dalamudUtilService;
|
private readonly DalamudUtilService _dalamudUtilService;
|
||||||
private readonly IpcManager _ipcManager;
|
private readonly IpcManager _ipcManager;
|
||||||
private readonly NoSnapService _noSnapService;
|
|
||||||
private readonly Dictionary<string, HandledCharaDataEntry> _handledCharaData = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, HandledCharaDataEntry> _handledCharaData = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, HandledCharaDataEntry> HandledCharaData => _handledCharaData;
|
public IReadOnlyDictionary<string, HandledCharaDataEntry> HandledCharaData => _handledCharaData;
|
||||||
|
|
||||||
public CharaDataCharacterHandler(ILogger<CharaDataCharacterHandler> logger, MareMediator mediator,
|
public CharaDataCharacterHandler(ILogger<CharaDataCharacterHandler> logger, MareMediator mediator,
|
||||||
GameObjectHandlerFactory gameObjectHandlerFactory, DalamudUtilService dalamudUtilService,
|
GameObjectHandlerFactory gameObjectHandlerFactory, DalamudUtilService dalamudUtilService,
|
||||||
IpcManager ipcManager, NoSnapService noSnapService)
|
IpcManager ipcManager)
|
||||||
: base(logger, mediator)
|
: base(logger, mediator)
|
||||||
{
|
{
|
||||||
_gameObjectHandlerFactory = gameObjectHandlerFactory;
|
_gameObjectHandlerFactory = gameObjectHandlerFactory;
|
||||||
_dalamudUtilService = dalamudUtilService;
|
_dalamudUtilService = dalamudUtilService;
|
||||||
_ipcManager = ipcManager;
|
_ipcManager = ipcManager;
|
||||||
_noSnapService = noSnapService;
|
|
||||||
mediator.Subscribe<GposeEndMessage>(this, msg =>
|
mediator.Subscribe<GposeEndMessage>(this, msg =>
|
||||||
{
|
{
|
||||||
foreach (var chara in _handledCharaData)
|
foreach (var chara in _handledCharaData)
|
||||||
@@ -94,7 +92,6 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
|
|||||||
_handledCharaData.Remove(handled.Name);
|
_handledCharaData.Remove(handled.Name);
|
||||||
await _dalamudUtilService.RunOnFrameworkThread(async () =>
|
await _dalamudUtilService.RunOnFrameworkThread(async () =>
|
||||||
{
|
{
|
||||||
RemoveGposer(handled);
|
|
||||||
await RevertChara(handled.Name, handled.CustomizePlus).ConfigureAwait(false);
|
await RevertChara(handled.Name, handled.CustomizePlus).ConfigureAwait(false);
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
return true;
|
return true;
|
||||||
@@ -103,7 +100,6 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
|
|||||||
internal void AddHandledChara(HandledCharaDataEntry handledCharaDataEntry)
|
internal void AddHandledChara(HandledCharaDataEntry handledCharaDataEntry)
|
||||||
{
|
{
|
||||||
_handledCharaData.Add(handledCharaDataEntry.Name, handledCharaDataEntry);
|
_handledCharaData.Add(handledCharaDataEntry.Name, handledCharaDataEntry);
|
||||||
_ = _dalamudUtilService.RunOnFrameworkThread(() => AddGposer(handledCharaDataEntry));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateHandledData(Dictionary<string, CharaDataMetaInfoExtendedDto?> newData)
|
public void UpdateHandledData(Dictionary<string, CharaDataMetaInfoExtendedDto?> newData)
|
||||||
@@ -134,23 +130,4 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
|
|||||||
if (handler.Address == nint.Zero) return null;
|
if (handler.Address == nint.Zero) return null;
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetGposerObjectIndex(string name)
|
|
||||||
{
|
|
||||||
return _dalamudUtilService.GetGposeCharacterFromObjectTableByName(name, _dalamudUtilService.IsInGpose)?.ObjectIndex ?? -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddGposer(HandledCharaDataEntry handled)
|
|
||||||
{
|
|
||||||
int objectIndex = GetGposerObjectIndex(handled.Name);
|
|
||||||
if (objectIndex > 0)
|
|
||||||
_noSnapService.AddGposer(objectIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveGposer(HandledCharaDataEntry handled)
|
|
||||||
{
|
|
||||||
int objectIndex = GetGposerObjectIndex(handled.Name);
|
|
||||||
if (objectIndex > 0)
|
|
||||||
_noSnapService.RemoveGposer(objectIndex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public class ChatService : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
var chatMsg = message.ChatMsg;
|
var chatMsg = message.ChatMsg;
|
||||||
var prefix = new SeStringBuilder();
|
var prefix = new SeStringBuilder();
|
||||||
prefix.AddText("[BnnuyChat] ");
|
prefix.AddText("[UmbraChat] ");
|
||||||
_chatGui.Print(new XivChatEntry{
|
_chatGui.Print(new XivChatEntry{
|
||||||
MessageBytes = [..prefix.Build().Encode(), ..message.ChatMsg.PayloadContent],
|
MessageBytes = [..prefix.Build().Encode(), ..message.ChatMsg.PayloadContent],
|
||||||
Name = chatMsg.SenderName,
|
Name = chatMsg.SenderName,
|
||||||
@@ -207,7 +207,7 @@ public class ChatService : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_chatGui.PrintError($"[UmbraSyncSync] Syncshell number #{shellNumber} not found");
|
_chatGui.PrintError($"[UmbraSync] Syncshell number #{shellNumber} not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendChatShell(int shellNumber, byte[] chatBytes)
|
public void SendChatShell(int shellNumber, byte[] chatBytes)
|
||||||
@@ -236,6 +236,6 @@ public class ChatService : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_chatGui.PrintError($"[UmbraSyncSync] Syncshell number #{shellNumber} not found");
|
_chatGui.PrintError($"[UmbraSync] Syncshell number #{shellNumber} not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,10 +14,8 @@ namespace MareSynchronos.Services;
|
|||||||
|
|
||||||
public sealed class CommandManagerService : IDisposable
|
public sealed class CommandManagerService : IDisposable
|
||||||
{
|
{
|
||||||
private const string _commandName = "/sync";
|
private const string _commandName = "/usync";
|
||||||
private const string _commandName2 = "/loporrit";
|
private const string _ssCommandPrefix = "/ums";
|
||||||
|
|
||||||
private const string _ssCommandPrefix = "/ss";
|
|
||||||
|
|
||||||
private readonly ApiController _apiController;
|
private readonly ApiController _apiController;
|
||||||
private readonly ICommandManager _commandManager;
|
private readonly ICommandManager _commandManager;
|
||||||
@@ -44,10 +42,6 @@ public sealed class CommandManagerService : IDisposable
|
|||||||
{
|
{
|
||||||
HelpMessage = "Opens the UmbraSync UI"
|
HelpMessage = "Opens the UmbraSync UI"
|
||||||
});
|
});
|
||||||
_commandManager.AddHandler(_commandName2, new CommandInfo(OnCommand)
|
|
||||||
{
|
|
||||||
HelpMessage = "Opens the UmbraSync UI"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Lazy registration of all possible /ss# commands which tbf is what the game does for linkshells anyway
|
// Lazy registration of all possible /ss# commands which tbf is what the game does for linkshells anyway
|
||||||
for (int i = 1; i <= ChatService.CommandMaxNumber; ++i)
|
for (int i = 1; i <= ChatService.CommandMaxNumber; ++i)
|
||||||
@@ -62,7 +56,7 @@ public sealed class CommandManagerService : IDisposable
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_commandManager.RemoveHandler(_commandName);
|
_commandManager.RemoveHandler(_commandName);
|
||||||
_commandManager.RemoveHandler(_commandName2);
|
|
||||||
|
|
||||||
for (int i = 1; i <= ChatService.CommandMaxNumber; ++i)
|
for (int i = 1; i <= ChatService.CommandMaxNumber; ++i)
|
||||||
_commandManager.RemoveHandler($"{_ssCommandPrefix}{i}");
|
_commandManager.RemoveHandler($"{_ssCommandPrefix}{i}");
|
||||||
@@ -86,7 +80,7 @@ public sealed class CommandManagerService : IDisposable
|
|||||||
{
|
{
|
||||||
if (_apiController.ServerState == WebAPI.SignalR.Utils.ServerState.Disconnecting)
|
if (_apiController.ServerState == WebAPI.SignalR.Utils.ServerState.Disconnecting)
|
||||||
{
|
{
|
||||||
_mediator.Publish(new NotificationMessage("UmbraSync disconnecting", "Cannot use /toggle while UmbraSync is still disconnecting",
|
_mediator.Publish(new NotificationMessage("Umbra disconnecting", "Cannot use /toggle while Umbra is still disconnecting",
|
||||||
NotificationType.Error));
|
NotificationType.Error));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +141,6 @@ public sealed class CommandManagerService : IDisposable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// FIXME: Chat content seems to already be stripped of any special characters here?
|
|
||||||
byte[] chatBytes = Encoding.UTF8.GetBytes(args);
|
byte[] chatBytes = Encoding.UTF8.GetBytes(args);
|
||||||
_chatService.SendChatShell(shellNumber, chatBytes);
|
_chatService.SendChatShell(shellNumber, chatBytes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,9 +462,9 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Starting DalamudUtilService");
|
_logger.LogInformation("Starting DalamudUtilService");
|
||||||
#pragma warning disable S2696 // Instance members should not write to "static" fields
|
#pragma warning disable S2696 // Instance members should not write to "static" fields
|
||||||
UmbraSyncSync.Plugin.Self.RealOnFrameworkUpdate = this.FrameworkOnUpdate;
|
Umbra.Plugin.Self.RealOnFrameworkUpdate = this.FrameworkOnUpdate;
|
||||||
#pragma warning restore S2696
|
#pragma warning restore S2696
|
||||||
_framework.Update += UmbraSyncSync.Plugin.Self.OnFrameworkUpdate;
|
_framework.Update += Umbra.Plugin.Self.OnFrameworkUpdate;
|
||||||
if (IsLoggedIn)
|
if (IsLoggedIn)
|
||||||
{
|
{
|
||||||
_classJobId = _clientState.LocalPlayer!.ClassJob.RowId;
|
_classJobId = _clientState.LocalPlayer!.ClassJob.RowId;
|
||||||
@@ -479,7 +479,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
_logger.LogTrace("Stopping {type}", GetType());
|
_logger.LogTrace("Stopping {type}", GetType());
|
||||||
|
|
||||||
Mediator.UnsubscribeAll(this);
|
Mediator.UnsubscribeAll(this);
|
||||||
_framework.Update -= UmbraSyncSync.Plugin.Self.OnFrameworkUpdate;
|
_framework.Update -= Umbra.Plugin.Self.OnFrameworkUpdate;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using System.Numerics;
|
|||||||
|
|
||||||
namespace MareSynchronos.Services.Mediator;
|
namespace MareSynchronos.Services.Mediator;
|
||||||
|
|
||||||
#pragma warning disable MA0048 // File name must match type name
|
#pragma warning disable MA0048
|
||||||
#pragma warning disable S2094
|
#pragma warning disable S2094
|
||||||
public record SwitchToIntroUiMessage : MessageBase;
|
public record SwitchToIntroUiMessage : MessageBase;
|
||||||
public record SwitchToMainUiMessage : MessageBase;
|
public record SwitchToMainUiMessage : MessageBase;
|
||||||
@@ -108,6 +108,11 @@ public record GPoseLobbyReceiveCharaData(CharaDataDownloadDto CharaDataDownloadD
|
|||||||
public record GPoseLobbyReceivePoseData(UserData UserData, PoseData PoseData) : MessageBase;
|
public record GPoseLobbyReceivePoseData(UserData UserData, PoseData PoseData) : MessageBase;
|
||||||
public record GPoseLobbyReceiveWorldData(UserData UserData, WorldData WorldData) : MessageBase;
|
public record GPoseLobbyReceiveWorldData(UserData UserData, WorldData WorldData) : MessageBase;
|
||||||
|
|
||||||
|
public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMatch, string? Token, string? DisplayName, string? Uid, bool AcceptPairRequests = true);
|
||||||
|
public record DiscoveryListUpdated(List<NearbyEntry> Entries) : MessageBase;
|
||||||
|
public record NearbyDetectionToggled(bool Enabled) : MessageBase;
|
||||||
|
public record AllowPairRequestsToggled(bool Enabled) : MessageBase;
|
||||||
|
|
||||||
public record PluginChangeMessage(string InternalName, Version Version, bool IsLoaded) : KeyedMessage(InternalName);
|
public record PluginChangeMessage(string InternalName, Version Version, bool IsLoaded) : KeyedMessage(InternalName);
|
||||||
#pragma warning restore S2094
|
#pragma warning restore S2094
|
||||||
#pragma warning restore MA0048 // File name must match type name
|
#pragma warning restore MA0048
|
||||||
|
|||||||
@@ -1,226 +0,0 @@
|
|||||||
using Dalamud.Plugin;
|
|
||||||
using MareSynchronos.Interop.Ipc;
|
|
||||||
using MareSynchronos.MareConfiguration.Models;
|
|
||||||
using MareSynchronos.Services.Mediator;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace MareSynchronos.Services;
|
|
||||||
|
|
||||||
public sealed class NoSnapService : IHostedService, IMediatorSubscriber
|
|
||||||
{
|
|
||||||
private record NoSnapConfig
|
|
||||||
{
|
|
||||||
[JsonPropertyName("listOfPlugins")]
|
|
||||||
public string[]? ListOfPlugins { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ILogger<NoSnapService> _logger;
|
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
|
||||||
private readonly Dictionary<string, bool> _listOfPlugins = new(StringComparer.Ordinal)
|
|
||||||
{
|
|
||||||
["Snapper"] = false,
|
|
||||||
["Snappy"] = false,
|
|
||||||
["Meddle.Plugin"] = false,
|
|
||||||
};
|
|
||||||
private static readonly HashSet<int> _gposers = new();
|
|
||||||
private static readonly HashSet<string> _gposersNamed = new(StringComparer.Ordinal);
|
|
||||||
private readonly IHostApplicationLifetime _hostApplicationLifetime;
|
|
||||||
private readonly DalamudUtilService _dalamudUtilService;
|
|
||||||
private readonly IpcManager _ipcManager;
|
|
||||||
private readonly RemoteConfigurationService _remoteConfig;
|
|
||||||
|
|
||||||
public static bool AnyLoaded { get; private set; } = false;
|
|
||||||
public static string ActivePlugins { get; private set; } = string.Empty;
|
|
||||||
|
|
||||||
public MareMediator Mediator { get; init; }
|
|
||||||
|
|
||||||
public NoSnapService(ILogger<NoSnapService> logger, IDalamudPluginInterface pluginInterface, MareMediator mediator,
|
|
||||||
IHostApplicationLifetime hostApplicationLifetime, DalamudUtilService dalamudUtilService, IpcManager ipcManager,
|
|
||||||
RemoteConfigurationService remoteConfig)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_pluginInterface = pluginInterface;
|
|
||||||
Mediator = mediator;
|
|
||||||
_hostApplicationLifetime = hostApplicationLifetime;
|
|
||||||
_dalamudUtilService = dalamudUtilService;
|
|
||||||
_ipcManager = ipcManager;
|
|
||||||
_remoteConfig = remoteConfig;
|
|
||||||
|
|
||||||
Mediator.Subscribe<GposeEndMessage>(this, msg => ClearGposeList());
|
|
||||||
Mediator.Subscribe<CutsceneEndMessage>(this, msg => ClearGposeList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddGposer(int objectIndex)
|
|
||||||
{
|
|
||||||
if (AnyLoaded || _hostApplicationLifetime.ApplicationStopping.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
_logger.LogTrace("Immediately reverting object index {id}", objectIndex);
|
|
||||||
RevertAndRedraw(objectIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogTrace("Registering gposer object index {id}", objectIndex);
|
|
||||||
lock (_gposers)
|
|
||||||
_gposers.Add(objectIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveGposer(int objectIndex)
|
|
||||||
{
|
|
||||||
_logger.LogTrace("Un-registering gposer object index {id}", objectIndex);
|
|
||||||
lock (_gposers)
|
|
||||||
_gposers.Remove(objectIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddGposerNamed(string name)
|
|
||||||
{
|
|
||||||
if (AnyLoaded || _hostApplicationLifetime.ApplicationStopping.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
_logger.LogTrace("Immediately reverting {name}", name);
|
|
||||||
RevertAndRedraw(name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogTrace("Registering gposer {name}", name);
|
|
||||||
lock (_gposers)
|
|
||||||
_gposersNamed.Add(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearGposeList()
|
|
||||||
{
|
|
||||||
if (_gposers.Count > 0 || _gposersNamed.Count > 0)
|
|
||||||
_logger.LogTrace("Clearing gposer list");
|
|
||||||
lock (_gposers)
|
|
||||||
_gposers.Clear();
|
|
||||||
lock (_gposersNamed)
|
|
||||||
_gposersNamed.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RevertAndRedraw(int objIndex, Guid applicationId = default)
|
|
||||||
{
|
|
||||||
if (applicationId == default)
|
|
||||||
applicationId = Guid.NewGuid();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ipcManager.Glamourer.RevertNow(_logger, applicationId, objIndex);
|
|
||||||
_ipcManager.Penumbra.RedrawNow(_logger, applicationId, objIndex);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RevertAndRedraw(string name, Guid applicationId = default)
|
|
||||||
{
|
|
||||||
if (applicationId == default)
|
|
||||||
applicationId = Guid.NewGuid();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ipcManager.Glamourer.RevertByNameNow(_logger, applicationId, name);
|
|
||||||
var addr = _dalamudUtilService.GetPlayerCharacterFromCachedTableByName(name);
|
|
||||||
if (addr != 0)
|
|
||||||
{
|
|
||||||
var obj = _dalamudUtilService.CreateGameObject(addr);
|
|
||||||
if (obj != null)
|
|
||||||
_ipcManager.Penumbra.RedrawNow(_logger, applicationId, obj.ObjectIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RevertGposers()
|
|
||||||
{
|
|
||||||
List<int>? gposersList = null;
|
|
||||||
List<string>? gposersList2 = null;
|
|
||||||
|
|
||||||
lock (_gposers)
|
|
||||||
{
|
|
||||||
if (_gposers.Count > 0)
|
|
||||||
{
|
|
||||||
gposersList = _gposers.ToList();
|
|
||||||
_gposers.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_gposersNamed)
|
|
||||||
{
|
|
||||||
if (_gposersNamed.Count > 0)
|
|
||||||
{
|
|
||||||
gposersList2 = _gposersNamed.ToList();
|
|
||||||
_gposersNamed.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gposersList == null && gposersList2 == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_logger.LogInformation("Reverting gposers");
|
|
||||||
|
|
||||||
_dalamudUtilService.RunOnFrameworkThread(() =>
|
|
||||||
{
|
|
||||||
Guid applicationId = Guid.NewGuid();
|
|
||||||
|
|
||||||
foreach (var gposer in gposersList ?? [])
|
|
||||||
RevertAndRedraw(gposer, applicationId);
|
|
||||||
|
|
||||||
foreach (var gposerName in gposersList2 ?? [])
|
|
||||||
RevertAndRedraw(gposerName, applicationId);
|
|
||||||
}).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var config = await _remoteConfig.GetConfigAsync<NoSnapConfig>("noSnap").ConfigureAwait(false) ?? new();
|
|
||||||
|
|
||||||
if (config.ListOfPlugins != null)
|
|
||||||
{
|
|
||||||
_listOfPlugins.Clear();
|
|
||||||
foreach (var pluginName in config.ListOfPlugins)
|
|
||||||
_listOfPlugins.TryAdd(pluginName, value: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var pluginName in _listOfPlugins.Keys)
|
|
||||||
{
|
|
||||||
_listOfPlugins[pluginName] = PluginWatcherService.GetInitialPluginState(_pluginInterface, pluginName)?.IsLoaded ?? false;
|
|
||||||
Mediator.SubscribeKeyed<PluginChangeMessage>(this, pluginName, (msg) =>
|
|
||||||
{
|
|
||||||
_listOfPlugins[pluginName] = msg.IsLoaded;
|
|
||||||
_logger.LogDebug("{pluginName} isLoaded = {isLoaded}", pluginName, msg.IsLoaded);
|
|
||||||
Update();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
RevertGposers();
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
|
||||||
{
|
|
||||||
bool anyLoadedNow = _listOfPlugins.Values.Any(p => p);
|
|
||||||
|
|
||||||
if (AnyLoaded != anyLoadedNow)
|
|
||||||
{
|
|
||||||
AnyLoaded = anyLoadedNow;
|
|
||||||
Mediator.Publish(new RecalculatePerformanceMessage(null));
|
|
||||||
|
|
||||||
if (AnyLoaded)
|
|
||||||
{
|
|
||||||
RevertGposers();
|
|
||||||
var pluginList = string.Join(", ", _listOfPlugins.Where(p => p.Value).Select(p => p.Key));
|
|
||||||
Mediator.Publish(new NotificationMessage("Incompatible plugin loaded", $"Synced player appearances will not apply until incompatible plugins are disabled: {pluginList}.",
|
|
||||||
NotificationType.Error));
|
|
||||||
ActivePlugins = pluginList;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ActivePlugins = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,19 +41,19 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
|
|
||||||
private void PrintErrorChat(string? message)
|
private void PrintErrorChat(string? message)
|
||||||
{
|
{
|
||||||
SeStringBuilder se = new SeStringBuilder().AddText("[UmbraSyncSync] Error: " + message);
|
SeStringBuilder se = new SeStringBuilder().AddText("[UmbraSync] Error: " + message);
|
||||||
_chatGui.PrintError(se.BuiltString);
|
_chatGui.PrintError(se.BuiltString);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintInfoChat(string? message)
|
private void PrintInfoChat(string? message)
|
||||||
{
|
{
|
||||||
SeStringBuilder se = new SeStringBuilder().AddText("[UmbraSyncSync] Info: ").AddItalics(message ?? string.Empty);
|
SeStringBuilder se = new SeStringBuilder().AddText("[UmbraSync] Info: ").AddItalics(message ?? string.Empty);
|
||||||
_chatGui.Print(se.BuiltString);
|
_chatGui.Print(se.BuiltString);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintWarnChat(string? message)
|
private void PrintWarnChat(string? message)
|
||||||
{
|
{
|
||||||
SeStringBuilder se = new SeStringBuilder().AddText("[UmbraSyncSync] ").AddUiForeground("Warning: " + (message ?? string.Empty), 31).AddUiForegroundOff();
|
SeStringBuilder se = new SeStringBuilder().AddText("[UmbraSync] ").AddUiForeground("Warning: " + (message ?? string.Empty), 31).AddUiForegroundOff();
|
||||||
_chatGui.Print(se.BuiltString);
|
_chatGui.Print(se.BuiltString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,201 +0,0 @@
|
|||||||
using Chaos.NaCl;
|
|
||||||
using MareSynchronos.MareConfiguration;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
|
|
||||||
namespace MareSynchronos.Services;
|
|
||||||
|
|
||||||
public sealed class RemoteConfigurationService
|
|
||||||
{
|
|
||||||
private readonly static Dictionary<string, string> ConfigPublicKeys = new(StringComparer.Ordinal)
|
|
||||||
{
|
|
||||||
{ "UMBR4KEY", "+MwCXedODmU+yD7vtdI+Ho2iLx+PV3U0H2XRLP/gReA=" }
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly static string[] ConfigSources = [
|
|
||||||
"https://umbra-sync.net/config/umbra.json"
|
|
||||||
];
|
|
||||||
|
|
||||||
private readonly ILogger<RemoteConfigurationService> _logger;
|
|
||||||
private readonly RemoteConfigCacheService _configService;
|
|
||||||
private readonly Task _initTask;
|
|
||||||
|
|
||||||
public RemoteConfigurationService(ILogger<RemoteConfigurationService> logger, RemoteConfigCacheService configService)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_configService = configService;
|
|
||||||
_initTask = Task.Run(DownloadConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<JsonObject> GetConfigAsync(string sectionName)
|
|
||||||
{
|
|
||||||
await _initTask.ConfigureAwait(false);
|
|
||||||
if (!_configService.Current.Configuration.TryGetPropertyValue(sectionName, out var section))
|
|
||||||
section = null;
|
|
||||||
return (section as JsonObject) ?? new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<T?> GetConfigAsync<T>(string sectionName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var json = await GetConfigAsync(sectionName).ConfigureAwait(false);
|
|
||||||
return JsonSerializer.Deserialize<T>(json);
|
|
||||||
}
|
|
||||||
catch (JsonException ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Invalid JSON in remote config: {sectionName}", sectionName);
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DownloadConfig()
|
|
||||||
{
|
|
||||||
string? jsonResponse = null;
|
|
||||||
|
|
||||||
foreach (var remoteUrl in ConfigSources)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Fetching {url}", remoteUrl);
|
|
||||||
|
|
||||||
using var httpClient = new HttpClient(
|
|
||||||
new HttpClientHandler
|
|
||||||
{
|
|
||||||
AllowAutoRedirect = true,
|
|
||||||
MaxAutomaticRedirections = 5
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(6);
|
|
||||||
|
|
||||||
var ver = Assembly.GetExecutingAssembly().GetName().Version;
|
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("MareSynchronos", ver!.Major + "." + ver!.Minor + "." + ver!.Build));
|
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, remoteUrl);
|
|
||||||
|
|
||||||
if (remoteUrl.Equals(_configService.Current.Origin, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(_configService.Current.ETag))
|
|
||||||
request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue(_configService.Current.ETag));
|
|
||||||
|
|
||||||
if (_configService.Current.LastModified != null)
|
|
||||||
request.Headers.IfModifiedSince = _configService.Current.LastModified;
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.NotModified)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Using cached remote configuration from {url}", remoteUrl);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
var contentType = response.Content.Headers.ContentType?.MediaType;
|
|
||||||
|
|
||||||
if (contentType == null || !contentType.Equals("application/json", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("HTTP request for remote config failed: wrong MIME type");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Downloaded new configuration from {url}", remoteUrl);
|
|
||||||
|
|
||||||
_configService.Current.Origin = remoteUrl;
|
|
||||||
_configService.Current.ETag = response.Headers.ETag?.ToString() ?? string.Empty;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (response.Content.Headers.Contains("Last-Modified"))
|
|
||||||
{
|
|
||||||
var lastModified = response.Content.Headers.GetValues("Last-Modified").First();
|
|
||||||
_configService.Current.LastModified = DateTimeOffset.Parse(lastModified, System.Globalization.CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_configService.Current.LastModified = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "HTTP request for remote config failed");
|
|
||||||
|
|
||||||
if (remoteUrl.Equals(_configService.Current.Origin, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
_configService.Current.ETag = string.Empty;
|
|
||||||
_configService.Current.LastModified = null;
|
|
||||||
_configService.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsonResponse == null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Could not download remote config");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var jsonDoc = JsonNode.Parse(jsonResponse) as JsonObject;
|
|
||||||
|
|
||||||
if (jsonDoc == null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Downloaded remote config is not a JSON object");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadConfig(jsonDoc);
|
|
||||||
}
|
|
||||||
catch (JsonException ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Invalid JSON in remote config response");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool VerifySignature(string message, ulong ts, string signature, string pubKey)
|
|
||||||
{
|
|
||||||
byte[] msg = [.. BitConverter.GetBytes(ts), .. Encoding.UTF8.GetBytes(message)];
|
|
||||||
byte[] sig = Convert.FromBase64String(signature);
|
|
||||||
byte[] pub = Convert.FromBase64String(pubKey);
|
|
||||||
return Ed25519.Verify(sig, msg, pub);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadConfig(JsonObject jsonDoc)
|
|
||||||
{
|
|
||||||
var ts = jsonDoc["ts"]!.GetValue<ulong>();
|
|
||||||
|
|
||||||
if (ts <= _configService.Current.Timestamp)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Remote configuration is not newer than cached config");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var signatures = jsonDoc["sig"]!.AsObject();
|
|
||||||
var configString = jsonDoc["config"]!.GetValue<string>();
|
|
||||||
bool verified = signatures.Any(sig =>
|
|
||||||
ConfigPublicKeys.TryGetValue(sig.Key, out var pubKey) &&
|
|
||||||
VerifySignature(configString, ts, sig.Value!.GetValue<string>(), pubKey));
|
|
||||||
|
|
||||||
if (!verified)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Could not verify signature for downloaded remote config");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_configService.Current.Configuration = JsonNode.Parse(configString)!.AsObject();
|
|
||||||
_configService.Current.Timestamp = ts;
|
|
||||||
_configService.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace MareSynchronos.Services;
|
|
||||||
|
|
||||||
public record RepoChangeConfig
|
|
||||||
{
|
|
||||||
[JsonPropertyName("current_repo")]
|
|
||||||
public string? CurrentRepo { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("valid_repos")]
|
|
||||||
public string[]? ValidRepos { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,401 +0,0 @@
|
|||||||
using Dalamud.Plugin;
|
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
using Dalamud.Utility;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace MareSynchronos.Services;
|
|
||||||
|
|
||||||
/* Reflection code based almost entirely on ECommons DalamudReflector
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2023 NightmareXIV
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
public sealed class RepoChangeService : IHostedService
|
|
||||||
{
|
|
||||||
#region Reflection Helpers
|
|
||||||
private const BindingFlags AllFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
|
|
||||||
private const BindingFlags StaticFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
|
|
||||||
private const BindingFlags InstanceFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
|
||||||
|
|
||||||
private static object GetFoP(object obj, string name)
|
|
||||||
{
|
|
||||||
Type? type = obj.GetType();
|
|
||||||
while (type != null)
|
|
||||||
{
|
|
||||||
var fieldInfo = type.GetField(name, AllFlags);
|
|
||||||
if (fieldInfo != null)
|
|
||||||
{
|
|
||||||
return fieldInfo.GetValue(obj)!;
|
|
||||||
}
|
|
||||||
var propertyInfo = type.GetProperty(name, AllFlags);
|
|
||||||
if (propertyInfo != null)
|
|
||||||
{
|
|
||||||
return propertyInfo.GetValue(obj)!;
|
|
||||||
}
|
|
||||||
type = type.BaseType;
|
|
||||||
}
|
|
||||||
throw new Exception($"Reflection GetFoP failed (not found: {obj.GetType().Name}.{name})");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static T GetFoP<T>(object obj, string name)
|
|
||||||
{
|
|
||||||
return (T)GetFoP(obj, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetFoP(object obj, string name, object value)
|
|
||||||
{
|
|
||||||
var type = obj.GetType();
|
|
||||||
var field = type.GetField(name, AllFlags);
|
|
||||||
if (field != null)
|
|
||||||
{
|
|
||||||
field.SetValue(obj, value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var prop = type.GetProperty(name, AllFlags)!;
|
|
||||||
if (prop == null)
|
|
||||||
throw new Exception($"Reflection SetFoP failed (not found: {type.Name}.{name})");
|
|
||||||
prop.SetValue(obj, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static object? Call(object obj, string name, object[] @params, bool matchExactArgumentTypes = false)
|
|
||||||
{
|
|
||||||
MethodInfo? info;
|
|
||||||
var type = obj.GetType();
|
|
||||||
if (!matchExactArgumentTypes)
|
|
||||||
{
|
|
||||||
info = type.GetMethod(name, AllFlags);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
info = type.GetMethod(name, AllFlags, @params.Select(x => x.GetType()).ToArray());
|
|
||||||
}
|
|
||||||
if (info == null)
|
|
||||||
throw new Exception($"Reflection Call failed (not found: {type.Name}.{name})");
|
|
||||||
return info.Invoke(obj, @params);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static T Call<T>(object obj, string name, object[] @params, bool matchExactArgumentTypes = false)
|
|
||||||
{
|
|
||||||
return (T)Call(obj, name, @params, matchExactArgumentTypes)!;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Dalamud Reflection
|
|
||||||
public object GetService(string serviceFullName)
|
|
||||||
{
|
|
||||||
return _pluginInterface.GetType().Assembly.
|
|
||||||
GetType("Dalamud.Service`1", true)!.MakeGenericType(_pluginInterface.GetType().Assembly.GetType(serviceFullName, true)!).
|
|
||||||
GetMethod("Get")!.Invoke(null, BindingFlags.Default, null, Array.Empty<object>(), null)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
private object GetPluginManager()
|
|
||||||
{
|
|
||||||
return _pluginInterface.GetType().Assembly.
|
|
||||||
GetType("Dalamud.Service`1", true)!.MakeGenericType(_pluginInterface.GetType().Assembly.GetType("Dalamud.Plugin.Internal.PluginManager", true)!).
|
|
||||||
GetMethod("Get")!.Invoke(null, BindingFlags.Default, null, Array.Empty<object>(), null)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReloadPluginMasters()
|
|
||||||
{
|
|
||||||
var mgr = GetService("Dalamud.Plugin.Internal.PluginManager");
|
|
||||||
var pluginReload = mgr.GetType().GetMethod("SetPluginReposFromConfigAsync", BindingFlags.Instance | BindingFlags.Public)!;
|
|
||||||
pluginReload.Invoke(mgr, [true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveDalamudConfig()
|
|
||||||
{
|
|
||||||
var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration");
|
|
||||||
var configSave = conf?.GetType().GetMethod("QueueSave", BindingFlags.Instance | BindingFlags.Public);
|
|
||||||
configSave?.Invoke(conf, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<object> GetRepoByURL(string repoURL)
|
|
||||||
{
|
|
||||||
var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration");
|
|
||||||
var repolist = (System.Collections.IEnumerable)GetFoP(conf, "ThirdRepoList");
|
|
||||||
foreach (var r in repolist)
|
|
||||||
{
|
|
||||||
if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase))
|
|
||||||
yield return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HasRepo(string repoURL)
|
|
||||||
{
|
|
||||||
var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration");
|
|
||||||
var repolist = (System.Collections.IEnumerable)GetFoP(conf, "ThirdRepoList");
|
|
||||||
foreach (var r in repolist)
|
|
||||||
{
|
|
||||||
if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddRepo(string repoURL, bool enabled)
|
|
||||||
{
|
|
||||||
var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration");
|
|
||||||
var repolist = (System.Collections.IEnumerable)GetFoP(conf, "ThirdRepoList");
|
|
||||||
foreach (var r in repolist)
|
|
||||||
{
|
|
||||||
if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var instance = Activator.CreateInstance(_pluginInterface.GetType().Assembly.GetType("Dalamud.Configuration.ThirdPartyRepoSettings")!)!;
|
|
||||||
SetFoP(instance, "Url", repoURL);
|
|
||||||
SetFoP(instance, "IsEnabled", enabled);
|
|
||||||
GetFoP<System.Collections.IList>(conf, "ThirdRepoList").Add(instance!);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveRepo(string repoURL)
|
|
||||||
{
|
|
||||||
var toRemove = new List<object>();
|
|
||||||
var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration");
|
|
||||||
var repolist = (System.Collections.IList)GetFoP(conf, "ThirdRepoList");
|
|
||||||
foreach (var r in repolist)
|
|
||||||
{
|
|
||||||
if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase))
|
|
||||||
toRemove.Add(r);
|
|
||||||
}
|
|
||||||
foreach (var r in toRemove)
|
|
||||||
repolist.Remove(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<(object LocalPlugin, string InstalledFromUrl)> GetLocalPluginsByName(string internalName)
|
|
||||||
{
|
|
||||||
List<(object LocalPlugin, string RepoURL)> result = [];
|
|
||||||
|
|
||||||
var pluginManager = GetPluginManager();
|
|
||||||
var installedPlugins = (System.Collections.IList)pluginManager.GetType().GetProperty("InstalledPlugins")!.GetValue(pluginManager)!;
|
|
||||||
|
|
||||||
foreach (var plugin in installedPlugins)
|
|
||||||
{
|
|
||||||
if (((string)plugin.GetType().GetProperty("InternalName")!.GetValue(plugin)!).Equals(internalName, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
var type = plugin.GetType();
|
|
||||||
if (type.Name.Equals("LocalDevPlugin", StringComparison.Ordinal))
|
|
||||||
continue;
|
|
||||||
var manifest = GetFoP(plugin, "manifest");
|
|
||||||
string installedFromUrl = (string)GetFoP(manifest, "InstalledFromUrl");
|
|
||||||
result.Add((plugin, installedFromUrl));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private readonly ILogger<RepoChangeService> _logger;
|
|
||||||
private readonly RemoteConfigurationService _remoteConfig;
|
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
|
||||||
private readonly IFramework _framework;
|
|
||||||
|
|
||||||
public RepoChangeService(ILogger<RepoChangeService> logger, RemoteConfigurationService remoteConfig, IDalamudPluginInterface pluginInterface, IFramework framework)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_remoteConfig = remoteConfig;
|
|
||||||
_pluginInterface = pluginInterface;
|
|
||||||
_framework = framework;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Starting RepoChange Service");
|
|
||||||
var repoChangeConfig = await _remoteConfig.GetConfigAsync<RepoChangeConfig>("repoChange").ConfigureAwait(false) ?? new();
|
|
||||||
|
|
||||||
var currentRepo = repoChangeConfig.CurrentRepo;
|
|
||||||
var validRepos = (repoChangeConfig.ValidRepos ?? []).ToList();
|
|
||||||
|
|
||||||
if (!currentRepo.IsNullOrEmpty() && !validRepos.Contains(currentRepo, StringComparer.Ordinal))
|
|
||||||
validRepos.Add(currentRepo);
|
|
||||||
|
|
||||||
if (validRepos.Count == 0)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("No valid repos configured, skipping");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _framework.RunOnTick(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var internalName = Assembly.GetExecutingAssembly().GetName().Name!;
|
|
||||||
var localPlugins = GetLocalPluginsByName(internalName);
|
|
||||||
|
|
||||||
var suffix = string.Empty;
|
|
||||||
|
|
||||||
if (localPlugins.Count == 0)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Skipping: No intalled plugin found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasValidCustomRepoUrl = false;
|
|
||||||
|
|
||||||
foreach (var vr in validRepos)
|
|
||||||
{
|
|
||||||
var vrCN = vr.Replace(".json", "_CN.json", StringComparison.Ordinal);
|
|
||||||
var vrKR = vr.Replace(".json", "_KR.json", StringComparison.Ordinal);
|
|
||||||
if (HasRepo(vr) || HasRepo(vrCN) || HasRepo(vrKR))
|
|
||||||
{
|
|
||||||
hasValidCustomRepoUrl = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<string> oldRepos = [];
|
|
||||||
var pluginRepoUrl = localPlugins[0].InstalledFromUrl;
|
|
||||||
|
|
||||||
if (pluginRepoUrl.Contains("_CN.json", StringComparison.Ordinal))
|
|
||||||
suffix = "_CN";
|
|
||||||
else if (pluginRepoUrl.Contains("_KR.json", StringComparison.Ordinal))
|
|
||||||
suffix = "_KR";
|
|
||||||
|
|
||||||
bool hasOldPluginRepoUrl = false;
|
|
||||||
|
|
||||||
foreach (var plugin in localPlugins)
|
|
||||||
{
|
|
||||||
foreach (var vr in validRepos)
|
|
||||||
{
|
|
||||||
var validRepo = vr.Replace(".json", $"{suffix}.json");
|
|
||||||
if (!plugin.InstalledFromUrl.Equals(validRepo, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
oldRepos.Add(plugin.InstalledFromUrl);
|
|
||||||
hasOldPluginRepoUrl = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasValidCustomRepoUrl)
|
|
||||||
{
|
|
||||||
if (hasOldPluginRepoUrl)
|
|
||||||
_logger.LogInformation("Result: Repo URL is up to date, but plugin install source is incorrect");
|
|
||||||
else
|
|
||||||
_logger.LogInformation("Result: Repo URL is up to date");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Result: Repo URL needs to be replaced");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentRepo.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No current repo URL configured");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-test plugin repo url rewriting to ensure it succeeds before replacing the custom repo URL
|
|
||||||
if (hasOldPluginRepoUrl)
|
|
||||||
{
|
|
||||||
foreach (var plugin in localPlugins)
|
|
||||||
{
|
|
||||||
var manifest = GetFoP(plugin.LocalPlugin, "manifest");
|
|
||||||
if (manifest == null)
|
|
||||||
throw new Exception("Plugin manifest is null");
|
|
||||||
var manifestFile = GetFoP(plugin.LocalPlugin, "manifestFile");
|
|
||||||
if (manifestFile == null)
|
|
||||||
throw new Exception("Plugin manifestFile is null");
|
|
||||||
var repo = GetFoP(manifest, "InstalledFromUrl");
|
|
||||||
if (((string)repo).IsNullOrEmpty())
|
|
||||||
throw new Exception("Plugin repo url is null or empty");
|
|
||||||
SetFoP(manifest, "InstalledFromUrl", repo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasValidCustomRepoUrl)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var oldRepo in oldRepos)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("* Removing old repo: {r}", oldRepo);
|
|
||||||
RemoveRepo(oldRepo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_logger.LogInformation("* Adding current repo: {r}", currentRepo);
|
|
||||||
AddRepo(currentRepo, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This time do it for real, and crash the game if we fail, to avoid saving a broken state
|
|
||||||
if (hasOldPluginRepoUrl)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.LogInformation("* Updating plugins");
|
|
||||||
foreach (var plugin in localPlugins)
|
|
||||||
{
|
|
||||||
var manifest = GetFoP(plugin.LocalPlugin, "manifest");
|
|
||||||
if (manifest == null)
|
|
||||||
throw new Exception("Plugin manifest is null");
|
|
||||||
var manifestFile = GetFoP(plugin.LocalPlugin, "manifestFile");
|
|
||||||
if (manifestFile == null)
|
|
||||||
throw new Exception("Plugin manifestFile is null");
|
|
||||||
var repo = GetFoP(manifest, "InstalledFromUrl");
|
|
||||||
if (((string)repo).IsNullOrEmpty())
|
|
||||||
throw new Exception("Plugin repo url is null or empty");
|
|
||||||
SetFoP(manifest, "InstalledFromUrl", currentRepo);
|
|
||||||
Call(manifest, "Save", [manifestFile, "RepoChange"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Exception while changing plugin install repo");
|
|
||||||
foreach (var oldRepo in oldRepos)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("* Restoring old repo: {r}", oldRepo);
|
|
||||||
AddRepo(oldRepo, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasValidCustomRepoUrl || hasOldPluginRepoUrl)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("* Saving dalamud config");
|
|
||||||
SaveDalamudConfig();
|
|
||||||
_logger.LogInformation("* Reloading plugin masters");
|
|
||||||
ReloadPluginMasters();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Exception in RepoChangeService");
|
|
||||||
}
|
|
||||||
}, default, 10, cancellationToken).ConfigureAwait(false);
|
|
||||||
_logger.LogInformation("Started RepoChangeService");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_ = cancellationToken;
|
|
||||||
_logger.LogDebug("Stopping RepoChange Service");
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -496,17 +496,17 @@ public class ServerConfigurationManager
|
|||||||
|
|
||||||
private void EnsureMainExists()
|
private void EnsureMainExists()
|
||||||
{
|
{
|
||||||
bool lopExists = false;
|
bool elfExists = false;
|
||||||
for (int i = 0; i < _configService.Current.ServerStorage.Count; ++i)
|
for (int i = 0; i < _configService.Current.ServerStorage.Count; ++i)
|
||||||
{
|
{
|
||||||
var x = _configService.Current.ServerStorage[i];
|
var x = _configService.Current.ServerStorage[i];
|
||||||
if (x.ServerUri.Equals(ApiController.UmbraSyncServiceUri, StringComparison.OrdinalIgnoreCase))
|
if (x.ServerUri.Equals(ApiController.UmbraServiceUri, StringComparison.OrdinalIgnoreCase))
|
||||||
lopExists = true;
|
elfExists = true;
|
||||||
}
|
}
|
||||||
if (!lopExists)
|
if (!elfExists)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Re-adding missing server {uri}", ApiController.UmbraSyncServiceUri);
|
_logger.LogDebug("Re-adding missing server {uri}", ApiController.UmbraServiceUri);
|
||||||
_configService.Current.ServerStorage.Insert(0, new ServerStorage() { ServerUri = ApiController.UmbraSyncServiceUri, ServerName = ApiController.UmbraSyncServer });
|
_configService.Current.ServerStorage.Insert(0, new ServerStorage() { ServerUri = ApiController.UmbraServiceUri, ServerName = ApiController.UmbraServer });
|
||||||
if (_configService.Current.CurrentServer >= 0)
|
if (_configService.Current.CurrentServer >= 0)
|
||||||
_configService.Current.CurrentServer++;
|
_configService.Current.CurrentServer++;
|
||||||
}
|
}
|
||||||
|
|||||||
225
MareSynchronos/Services/TemporarySyncshellNotificationService.cs
Normal file
225
MareSynchronos/Services/TemporarySyncshellNotificationService.cs
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Threading;
|
||||||
|
using MareSynchronos.API.Dto.Group;
|
||||||
|
using MareSynchronos.MareConfiguration.Models;
|
||||||
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
|
using MareSynchronos.Services.Mediator;
|
||||||
|
using MareSynchronos.WebAPI;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Services;
|
||||||
|
|
||||||
|
public sealed class TemporarySyncshellNotificationService : MediatorSubscriberBase, IHostedService
|
||||||
|
{
|
||||||
|
private static readonly int[] NotificationThresholdMinutes = [30, 15, 5, 1];
|
||||||
|
private readonly ApiController _apiController;
|
||||||
|
private readonly PairManager _pairManager;
|
||||||
|
private readonly Lock _stateLock = new();
|
||||||
|
private readonly Dictionary<string, TrackedGroup> _trackedGroups = new(StringComparer.Ordinal);
|
||||||
|
private CancellationTokenSource? _loopCts;
|
||||||
|
private Task? _loopTask;
|
||||||
|
|
||||||
|
public TemporarySyncshellNotificationService(ILogger<TemporarySyncshellNotificationService> logger, MareMediator mediator, PairManager pairManager, ApiController apiController)
|
||||||
|
: base(logger, mediator)
|
||||||
|
{
|
||||||
|
_pairManager = pairManager;
|
||||||
|
_apiController = apiController;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_loopCts = new CancellationTokenSource();
|
||||||
|
Mediator.Subscribe<ConnectedMessage>(this, _ => ResetTrackedGroups());
|
||||||
|
Mediator.Subscribe<DisconnectedMessage>(this, _ => ResetTrackedGroups());
|
||||||
|
_loopTask = Task.Run(() => MonitorLoopAsync(_loopCts.Token), _loopCts.Token);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Mediator.UnsubscribeAll(this);
|
||||||
|
if (_loopCts == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_loopCts.Cancel();
|
||||||
|
if (_loopTask != null)
|
||||||
|
{
|
||||||
|
await _loopTask.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loopTask = null;
|
||||||
|
_loopCts.Dispose();
|
||||||
|
_loopCts = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task MonitorLoopAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
var delay = TimeSpan.FromSeconds(30);
|
||||||
|
while (!ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CheckGroups();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogDebug(ex, "Failed to check temporary syncshell expirations");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(delay, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckGroups()
|
||||||
|
{
|
||||||
|
var nowUtc = DateTime.UtcNow;
|
||||||
|
var groupsSnapshot = _pairManager.Groups.Values.ToList();
|
||||||
|
var notifications = new List<NotificationPayload>();
|
||||||
|
var expiredGroups = new List<GroupFullInfoDto>();
|
||||||
|
var seenTemporaryGids = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
using (var guard = _stateLock.EnterScope())
|
||||||
|
{
|
||||||
|
foreach (var group in groupsSnapshot)
|
||||||
|
{
|
||||||
|
if (!group.IsTemporary || group.ExpiresAt == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_apiController.UID) || !string.Equals(group.OwnerUID, _apiController.UID, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gid = group.Group.GID;
|
||||||
|
seenTemporaryGids.Add(gid);
|
||||||
|
var expiresAtUtc = NormalizeToUtc(group.ExpiresAt.Value);
|
||||||
|
var remaining = expiresAtUtc - nowUtc;
|
||||||
|
|
||||||
|
if (!_trackedGroups.TryGetValue(gid, out var state))
|
||||||
|
{
|
||||||
|
state = new TrackedGroup(expiresAtUtc);
|
||||||
|
_trackedGroups[gid] = state;
|
||||||
|
}
|
||||||
|
else if (state.ExpiresAtUtc != expiresAtUtc)
|
||||||
|
{
|
||||||
|
state.UpdateExpiresAt(expiresAtUtc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
_trackedGroups.Remove(gid);
|
||||||
|
expiredGroups.Add(group);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.LastRemaining.HasValue)
|
||||||
|
{
|
||||||
|
state.UpdateRemaining(remaining);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousRemaining = state.LastRemaining.Value;
|
||||||
|
|
||||||
|
foreach (var thresholdMinutes in NotificationThresholdMinutes)
|
||||||
|
{
|
||||||
|
var threshold = TimeSpan.FromMinutes(thresholdMinutes);
|
||||||
|
if (previousRemaining > threshold && remaining <= threshold)
|
||||||
|
{
|
||||||
|
notifications.Add(new NotificationPayload(group, thresholdMinutes, expiresAtUtc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.UpdateRemaining(remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
var toRemove = _trackedGroups.Keys.Where(k => !seenTemporaryGids.Contains(k)).ToList();
|
||||||
|
foreach (var gid in toRemove)
|
||||||
|
{
|
||||||
|
_trackedGroups.Remove(gid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var expiredGroup in expiredGroups)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Temporary syncshell {gid} expired locally; removing", expiredGroup.Group.GID);
|
||||||
|
_pairManager.RemoveGroup(expiredGroup.Group);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var notification in notifications)
|
||||||
|
{
|
||||||
|
PublishNotification(notification.Group, notification.ThresholdMinutes, notification.ExpiresAtUtc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PublishNotification(GroupFullInfoDto group, int thresholdMinutes, DateTime expiresAtUtc)
|
||||||
|
{
|
||||||
|
string displayName = string.IsNullOrWhiteSpace(group.GroupAlias) ? group.Group.GID : group.GroupAlias!;
|
||||||
|
string threshold = thresholdMinutes == 1 ? "1 minute" : $"{thresholdMinutes} minutes";
|
||||||
|
string expiresLocal = expiresAtUtc.ToLocalTime().ToString("t", CultureInfo.CurrentCulture);
|
||||||
|
|
||||||
|
string message = $"La Syncshell temporaire \"{displayName}\" sera supprimee dans {threshold} (a {expiresLocal}).";
|
||||||
|
Mediator.Publish(new NotificationMessage("Syncshell temporaire", message, NotificationType.Warning, TimeSpan.FromSeconds(6)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateTime NormalizeToUtc(DateTime expiresAt)
|
||||||
|
{
|
||||||
|
return expiresAt.Kind switch
|
||||||
|
{
|
||||||
|
DateTimeKind.Utc => expiresAt,
|
||||||
|
DateTimeKind.Local => expiresAt.ToUniversalTime(),
|
||||||
|
_ => DateTime.SpecifyKind(expiresAt, DateTimeKind.Utc)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetTrackedGroups()
|
||||||
|
{
|
||||||
|
using (var guard = _stateLock.EnterScope())
|
||||||
|
{
|
||||||
|
_trackedGroups.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TrackedGroup
|
||||||
|
{
|
||||||
|
public TrackedGroup(DateTime expiresAtUtc)
|
||||||
|
{
|
||||||
|
ExpiresAtUtc = expiresAtUtc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime ExpiresAtUtc { get; private set; }
|
||||||
|
public TimeSpan? LastRemaining { get; private set; }
|
||||||
|
|
||||||
|
public void UpdateExpiresAt(DateTime expiresAtUtc)
|
||||||
|
{
|
||||||
|
ExpiresAtUtc = expiresAtUtc;
|
||||||
|
LastRemaining = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateRemaining(TimeSpan remaining)
|
||||||
|
{
|
||||||
|
LastRemaining = remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record NotificationPayload(GroupFullInfoDto Group, int ThresholdMinutes, DateTime ExpiresAtUtc);
|
||||||
|
}
|
||||||
208
MareSynchronos/UI/AutoDetectUi.cs
Normal file
208
MareSynchronos/UI/AutoDetectUi.cs
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using MareSynchronos.Localization;
|
||||||
|
using MareSynchronos.MareConfiguration;
|
||||||
|
using MareSynchronos.Services;
|
||||||
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
|
using MareSynchronos.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MareSynchronos.UI;
|
||||||
|
|
||||||
|
public class AutoDetectUi : WindowMediatorSubscriberBase
|
||||||
|
{
|
||||||
|
private readonly MareConfigService _configService;
|
||||||
|
private readonly DalamudUtilService _dalamud;
|
||||||
|
private readonly IObjectTable _objectTable;
|
||||||
|
private readonly Services.AutoDetect.AutoDetectRequestService _requestService;
|
||||||
|
private readonly PairManager _pairManager;
|
||||||
|
private List<Services.Mediator.NearbyEntry> _entries = new();
|
||||||
|
|
||||||
|
private static string L(string key, string fallback, params object[] args)
|
||||||
|
=> LocalizationService.Instance?.GetString(key, fallback, args)
|
||||||
|
?? string.Format(CultureInfo.CurrentCulture, fallback, args);
|
||||||
|
|
||||||
|
public AutoDetectUi(ILogger<AutoDetectUi> logger, MareMediator mediator,
|
||||||
|
MareConfigService configService, DalamudUtilService dalamudUtilService, IObjectTable objectTable,
|
||||||
|
Services.AutoDetect.AutoDetectRequestService requestService, PairManager pairManager,
|
||||||
|
PerformanceCollectorService performanceCollectorService)
|
||||||
|
: base(logger, mediator, "AutoDetect", performanceCollectorService)
|
||||||
|
{
|
||||||
|
_configService = configService;
|
||||||
|
_dalamud = dalamudUtilService;
|
||||||
|
_objectTable = objectTable;
|
||||||
|
_requestService = requestService;
|
||||||
|
_pairManager = pairManager;
|
||||||
|
|
||||||
|
Flags |= ImGuiWindowFlags.NoScrollbar;
|
||||||
|
SizeConstraints = new WindowSizeConstraints()
|
||||||
|
{
|
||||||
|
MinimumSize = new Vector2(350, 220),
|
||||||
|
MaximumSize = new Vector2(600, 600),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool DrawConditions()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawInternal()
|
||||||
|
{
|
||||||
|
using var idScope = ImRaii.PushId("autodetect-ui");
|
||||||
|
|
||||||
|
if (!_configService.Current.EnableAutoDetectDiscovery)
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped(L("AutoDetect.Disabled", "Nearby detection is disabled. Enable it in Settings to start detecting nearby Umbra users."), ImGuiColors.DalamudYellow);
|
||||||
|
ImGuiHelpers.ScaledDummy(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxDist = Math.Clamp(_configService.Current.AutoDetectMaxDistanceMeters, 5, 100);
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.TextUnformatted(L("AutoDetect.MaxDistance", "Max distance (m)"));
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetNextItemWidth(120 * ImGuiHelpers.GlobalScale);
|
||||||
|
if (ImGui.SliderInt("##autodetect-dist", ref maxDist, 5, 100))
|
||||||
|
{
|
||||||
|
_configService.Current.AutoDetectMaxDistanceMeters = maxDist;
|
||||||
|
_configService.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(6);
|
||||||
|
|
||||||
|
// Table header
|
||||||
|
if (ImGui.BeginTable("autodetect-nearby", 5, ImGuiTableFlags.SizingStretchProp))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn(L("AutoDetect.Table.Name", "Name"));
|
||||||
|
ImGui.TableSetupColumn(L("AutoDetect.Table.World", "World"));
|
||||||
|
ImGui.TableSetupColumn(L("AutoDetect.Table.Distance", "Distance"));
|
||||||
|
ImGui.TableSetupColumn(L("AutoDetect.Table.Status", "Status"));
|
||||||
|
ImGui.TableSetupColumn(L("AutoDetect.Table.Action", "Action"));
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
var data = _entries.Count > 0 ? _entries.Where(e => e.IsMatch).ToList() : new List<Services.Mediator.NearbyEntry>();
|
||||||
|
foreach (var e in data)
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted(e.Name);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted(e.WorldId == 0 ? L("AutoDetect.World.Unknown", "-") : (_dalamud.WorldData.Value.TryGetValue(e.WorldId, out var w) ? w : e.WorldId.ToString()));
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted(float.IsNaN(e.Distance)
|
||||||
|
? L("AutoDetect.Distance.Unknown", "-")
|
||||||
|
: string.Format(CultureInfo.CurrentCulture, L("AutoDetect.Distance.Format", "{0:0.0} m"), e.Distance));
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
bool alreadyPaired = IsAlreadyPairedByUidOrAlias(e);
|
||||||
|
string status = alreadyPaired
|
||||||
|
? L("AutoDetect.Status.Paired", "Paired")
|
||||||
|
: (string.IsNullOrEmpty(e.Token)
|
||||||
|
? L("AutoDetect.Status.RequestsDisabled", "Requests disabled")
|
||||||
|
: L("AutoDetect.Status.OnUmbra", "On Umbra"));
|
||||||
|
ImGui.TextUnformatted(status);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
using (ImRaii.Disabled(alreadyPaired || string.IsNullOrEmpty(e.Token)))
|
||||||
|
{
|
||||||
|
if (alreadyPaired)
|
||||||
|
{
|
||||||
|
ImGui.Button(L("AutoDetect.Action.AlreadySynced", "Already sync") + "##" + e.Name);
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrEmpty(e.Token))
|
||||||
|
{
|
||||||
|
ImGui.Button(L("AutoDetect.Action.RequestsDisabled", "Requests disabled") + "##" + e.Name);
|
||||||
|
}
|
||||||
|
else if (ImGui.Button(L("AutoDetect.Action.SendRequest", "Send request") + "##" + e.Name))
|
||||||
|
{
|
||||||
|
_ = _requestService.SendRequestAsync(e.Token!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnOpen()
|
||||||
|
{
|
||||||
|
base.OnOpen();
|
||||||
|
Mediator.Subscribe<Services.Mediator.DiscoveryListUpdated>(this, OnDiscoveryUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnClose()
|
||||||
|
{
|
||||||
|
Mediator.Unsubscribe<Services.Mediator.DiscoveryListUpdated>(this);
|
||||||
|
base.OnClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDiscoveryUpdated(Services.Mediator.DiscoveryListUpdated msg)
|
||||||
|
{
|
||||||
|
_entries = msg.Entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Services.Mediator.NearbyEntry> BuildLocalSnapshot(int maxDist)
|
||||||
|
{
|
||||||
|
var list = new List<Services.Mediator.NearbyEntry>();
|
||||||
|
var local = _dalamud.GetPlayerCharacter();
|
||||||
|
var localPos = local?.Position ?? Vector3.Zero;
|
||||||
|
for (int i = 0; i < 200; i += 2)
|
||||||
|
{
|
||||||
|
var obj = _objectTable[i];
|
||||||
|
if (obj == null || obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
|
||||||
|
if (local != null && obj.Address == local.Address) continue;
|
||||||
|
float dist = local == null ? float.NaN : Vector3.Distance(localPos, obj.Position);
|
||||||
|
if (!float.IsNaN(dist) && dist > maxDist) continue;
|
||||||
|
string name = obj.Name.ToString();
|
||||||
|
ushort worldId = 0;
|
||||||
|
if (obj is IPlayerCharacter pc) worldId = (ushort)pc.HomeWorld.RowId;
|
||||||
|
list.Add(new Services.Mediator.NearbyEntry(name, worldId, dist, false, null, null, null));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsAlreadyPairedByUidOrAlias(Services.Mediator.NearbyEntry e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1) Match by UID when available (authoritative)
|
||||||
|
if (!string.IsNullOrEmpty(e.Uid))
|
||||||
|
{
|
||||||
|
foreach (var p in _pairManager.DirectPairs)
|
||||||
|
{
|
||||||
|
if (string.Equals(p.UserData.UID, e.Uid, StringComparison.Ordinal))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var key = NormalizeKey(e.DisplayName ?? e.Name);
|
||||||
|
if (string.IsNullOrEmpty(key)) return false;
|
||||||
|
foreach (var p in _pairManager.DirectPairs)
|
||||||
|
{
|
||||||
|
if (NormalizeKey(p.UserData.AliasOrUID) == key) return true;
|
||||||
|
if (!string.IsNullOrEmpty(p.UserData.Alias) && NormalizeKey(p.UserData.Alias!) == key) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeKey(string? input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input)) return string.Empty;
|
||||||
|
var formD = input.Normalize(NormalizationForm.FormD);
|
||||||
|
var sb = new StringBuilder(formD.Length);
|
||||||
|
foreach (var ch in formD)
|
||||||
|
{
|
||||||
|
var cat = CharUnicodeInfo.GetUnicodeCategory(ch);
|
||||||
|
if (cat != UnicodeCategory.NonSpacingMark)
|
||||||
|
sb.Append(char.ToLowerInvariant(ch));
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,7 +79,7 @@ internal sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
|||||||
UiSharedService uiSharedService, ServerConfigurationManager serverConfigurationManager,
|
UiSharedService uiSharedService, ServerConfigurationManager serverConfigurationManager,
|
||||||
DalamudUtilService dalamudUtilService, FileDialogManager fileDialogManager, PairManager pairManager,
|
DalamudUtilService dalamudUtilService, FileDialogManager fileDialogManager, PairManager pairManager,
|
||||||
CharaDataGposeTogetherManager charaDataGposeTogetherManager)
|
CharaDataGposeTogetherManager charaDataGposeTogetherManager)
|
||||||
: base(logger, mediator, "UmbraSync Character Data Hub###UmbraSyncCharaDataUI", performanceCollectorService)
|
: base(logger, mediator, "Umbra Character Data Hub###UmbraCharaDataUI", performanceCollectorService)
|
||||||
{
|
{
|
||||||
SetWindowSizeConstraints();
|
SetWindowSizeConstraints();
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using MareSynchronos.PlayerData.Pairs;
|
|||||||
using MareSynchronos.Services;
|
using MareSynchronos.Services;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.Services.ServerConfiguration;
|
using MareSynchronos.Services.ServerConfiguration;
|
||||||
|
using MareSynchronos.Services.AutoDetect;
|
||||||
using MareSynchronos.UI.Components;
|
using MareSynchronos.UI.Components;
|
||||||
using MareSynchronos.UI.Handlers;
|
using MareSynchronos.UI.Handlers;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
@@ -24,6 +25,7 @@ using System.Diagnostics;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MareSynchronos.UI;
|
namespace MareSynchronos.UI;
|
||||||
|
|
||||||
@@ -43,6 +45,8 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
private readonly ServerConfigurationManager _serverManager;
|
private readonly ServerConfigurationManager _serverManager;
|
||||||
private readonly Stopwatch _timeout = new();
|
private readonly Stopwatch _timeout = new();
|
||||||
private readonly CharaDataManager _charaDataManager;
|
private readonly CharaDataManager _charaDataManager;
|
||||||
|
private readonly NearbyPendingService _nearbyPending;
|
||||||
|
private readonly AutoDetectRequestService _autoDetectRequestService;
|
||||||
private readonly UidDisplayHandler _uidDisplayHandler;
|
private readonly UidDisplayHandler _uidDisplayHandler;
|
||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
private bool _buttonState;
|
private bool _buttonState;
|
||||||
@@ -56,11 +60,17 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
private bool _showModalForUserAddition;
|
private bool _showModalForUserAddition;
|
||||||
private bool _showSyncShells;
|
private bool _showSyncShells;
|
||||||
private bool _wasOpen;
|
private bool _wasOpen;
|
||||||
|
private bool _nearbyOpen = true;
|
||||||
|
private List<Services.Mediator.NearbyEntry> _nearbyEntries = new();
|
||||||
|
|
||||||
|
private string L(string key, string fallback, params object[] args) => _uiSharedService.Localize(key, fallback, args);
|
||||||
|
|
||||||
public CompactUi(ILogger<CompactUi> logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager, ChatService chatService,
|
public CompactUi(ILogger<CompactUi> logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager, ChatService chatService,
|
||||||
ServerConfigurationManager serverManager, MareMediator mediator, FileUploadManager fileTransferManager, UidDisplayHandler uidDisplayHandler, CharaDataManager charaDataManager,
|
ServerConfigurationManager serverManager, MareMediator mediator, FileUploadManager fileTransferManager, UidDisplayHandler uidDisplayHandler, CharaDataManager charaDataManager,
|
||||||
|
NearbyPendingService nearbyPendingService,
|
||||||
|
AutoDetectRequestService autoDetectRequestService,
|
||||||
PerformanceCollectorService performanceCollectorService)
|
PerformanceCollectorService performanceCollectorService)
|
||||||
: base(logger, mediator, "###UmbraSyncSyncMainUI", performanceCollectorService)
|
: base(logger, mediator, "###UmbraSyncMainUI", performanceCollectorService)
|
||||||
{
|
{
|
||||||
_uiSharedService = uiShared;
|
_uiSharedService = uiShared;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
@@ -70,21 +80,23 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
_fileTransferManager = fileTransferManager;
|
_fileTransferManager = fileTransferManager;
|
||||||
_uidDisplayHandler = uidDisplayHandler;
|
_uidDisplayHandler = uidDisplayHandler;
|
||||||
_charaDataManager = charaDataManager;
|
_charaDataManager = charaDataManager;
|
||||||
|
_nearbyPending = nearbyPendingService;
|
||||||
|
_autoDetectRequestService = autoDetectRequestService;
|
||||||
var tagHandler = new TagHandler(_serverManager);
|
var tagHandler = new TagHandler(_serverManager);
|
||||||
|
|
||||||
_groupPanel = new(this, uiShared, _pairManager, chatService, uidDisplayHandler, _configService, _serverManager, _charaDataManager);
|
_groupPanel = new(this, uiShared, _pairManager, chatService, uidDisplayHandler, _configService, _serverManager, _charaDataManager);
|
||||||
_selectGroupForPairUi = new(tagHandler, uidDisplayHandler, _uiSharedService);
|
_selectGroupForPairUi = new(tagHandler, uidDisplayHandler, _uiSharedService);
|
||||||
_selectPairsForGroupUi = new(tagHandler, uidDisplayHandler);
|
_selectPairsForGroupUi = new(tagHandler, uidDisplayHandler, _uiSharedService);
|
||||||
_pairGroupsUi = new(configService, tagHandler, uidDisplayHandler, apiController, _selectPairsForGroupUi, _uiSharedService);
|
_pairGroupsUi = new(configService, tagHandler, uidDisplayHandler, apiController, _selectPairsForGroupUi, _uiSharedService);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
string dev = "Dev Build";
|
string dev = "Dev Build";
|
||||||
var ver = Assembly.GetExecutingAssembly().GetName().Version!;
|
var ver = Assembly.GetExecutingAssembly().GetName().Version!;
|
||||||
WindowName = $"UmbraSync Sync {dev} ({ver.Major}.{ver.Minor}.{ver.Build})###UmbraSyncSyncMainUIDev";
|
WindowName = $"UmbraSync {dev} ({ver.Major}.{ver.Minor}.{ver.Build})###UmbraSyncMainUIDev";
|
||||||
Toggle();
|
Toggle();
|
||||||
#else
|
#else
|
||||||
var ver = Assembly.GetExecutingAssembly().GetName().Version!;
|
var ver = Assembly.GetExecutingAssembly().GetName().Version!;
|
||||||
WindowName = "UmbraSync Sync " + ver.Major + "." + ver.Minor + "." + ver.Build + "###UmbraSyncSyncMainUI";
|
WindowName = "UmbraSync " + ver.Major + "." + ver.Minor + "." + ver.Build + "###UmbracSyncMainUI";
|
||||||
#endif
|
#endif
|
||||||
Mediator.Subscribe<SwitchToMainUiMessage>(this, (_) => IsOpen = true);
|
Mediator.Subscribe<SwitchToMainUiMessage>(this, (_) => IsOpen = true);
|
||||||
Mediator.Subscribe<SwitchToIntroUiMessage>(this, (_) => IsOpen = false);
|
Mediator.Subscribe<SwitchToIntroUiMessage>(this, (_) => IsOpen = false);
|
||||||
@@ -92,6 +104,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiSharedService_GposeEnd());
|
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiSharedService_GposeEnd());
|
||||||
Mediator.Subscribe<DownloadStartedMessage>(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus);
|
Mediator.Subscribe<DownloadStartedMessage>(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus);
|
||||||
Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _));
|
Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _));
|
||||||
|
Mediator.Subscribe<DiscoveryListUpdated>(this, (msg) => _nearbyEntries = msg.Entries);
|
||||||
|
|
||||||
Flags |= ImGuiWindowFlags.NoDocking;
|
Flags |= ImGuiWindowFlags.NoDocking;
|
||||||
|
|
||||||
@@ -104,16 +117,13 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
{
|
{
|
||||||
if (_serverManager.CurrentApiUrl.Equals(ApiController.UmbraSyncServiceUri, StringComparison.Ordinal))
|
UiSharedService.AccentColor = new Vector4(0.63f, 0.25f, 1f, 1f);
|
||||||
UiSharedService.AccentColor = new Vector4(1.0f, 0.8666f, 0.06666f, 1.0f);
|
|
||||||
else
|
|
||||||
UiSharedService.AccentColor = ImGuiColors.ParsedGreen;
|
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ImGui.GetStyle().WindowPadding.Y - 1f * ImGuiHelpers.GlobalScale + ImGui.GetStyle().ItemSpacing.Y);
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ImGui.GetStyle().WindowPadding.Y - 1f * ImGuiHelpers.GlobalScale + ImGui.GetStyle().ItemSpacing.Y);
|
||||||
WindowContentWidth = UiSharedService.GetWindowContentRegionWidth();
|
WindowContentWidth = UiSharedService.GetWindowContentRegionWidth();
|
||||||
if (!_apiController.IsCurrentVersion)
|
if (!_apiController.IsCurrentVersion)
|
||||||
{
|
{
|
||||||
var ver = _apiController.CurrentClientVersion;
|
var ver = _apiController.CurrentClientVersion;
|
||||||
var unsupported = "UNSUPPORTED VERSION";
|
var unsupported = L("Compact.Version.UnsupportedTitle", "UNSUPPORTED VERSION");
|
||||||
using (_uiSharedService.UidFont.Push())
|
using (_uiSharedService.UidFont.Push())
|
||||||
{
|
{
|
||||||
var uidTextSize = ImGui.CalcTextSize(unsupported);
|
var uidTextSize = ImGui.CalcTextSize(unsupported);
|
||||||
@@ -121,8 +131,10 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextColored(ImGuiColors.DalamudRed, unsupported);
|
ImGui.TextColored(ImGuiColors.DalamudRed, unsupported);
|
||||||
}
|
}
|
||||||
UiSharedService.ColorTextWrapped($"Your UmbraSync installation is out of date, the current version is {ver.Major}.{ver.Minor}.{ver.Build}. " +
|
UiSharedService.ColorTextWrapped(L("Compact.Version.Outdated",
|
||||||
$"It is highly recommended to keep UmbraSync up to date. Open /xlplugins and update the plugin.", ImGuiColors.DalamudRed);
|
"Your UmbraSync installation is out of date, the current version is {0}.{1}.{2}. It is highly recommended to keep UmbraSync up to date. Open /xlplugins and update the plugin.",
|
||||||
|
ver.Major, ver.Minor, ver.Build),
|
||||||
|
ImGuiColors.DalamudRed);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (ImRaii.PushId("header")) DrawUIDHeader();
|
using (ImRaii.PushId("header")) DrawUIDHeader();
|
||||||
@@ -147,7 +159,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
}
|
}
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
UiSharedService.AttachToolTip("Individual pairs");
|
UiSharedService.AttachToolTip(L("Compact.Toggle.IndividualPairs", "Individual pairs"));
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
@@ -166,7 +178,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
|
|
||||||
UiSharedService.AttachToolTip("Syncshells");
|
UiSharedService.AttachToolTip(L("Compact.Toggle.Syncshells", "Syncshells"));
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
if (!hasShownSyncShells)
|
if (!hasShownSyncShells)
|
||||||
@@ -188,12 +200,14 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
_lastAddedUser = _pairManager.LastAddedUser;
|
_lastAddedUser = _pairManager.LastAddedUser;
|
||||||
_pairManager.LastAddedUser = null;
|
_pairManager.LastAddedUser = null;
|
||||||
ImGui.OpenPopup("Set Notes for New User");
|
var setNotesTitle = L("Compact.AddUser.ModalTitle", "Set Notes for New User");
|
||||||
|
ImGui.OpenPopup(setNotesTitle);
|
||||||
_showModalForUserAddition = true;
|
_showModalForUserAddition = true;
|
||||||
_lastAddedUserComment = string.Empty;
|
_lastAddedUserComment = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginPopupModal("Set Notes for New User", ref _showModalForUserAddition, UiSharedService.PopupWindowFlags))
|
var setNotesModalTitle = L("Compact.AddUser.ModalTitle", "Set Notes for New User");
|
||||||
|
if (ImGui.BeginPopupModal(setNotesModalTitle, ref _showModalForUserAddition, UiSharedService.PopupWindowFlags))
|
||||||
{
|
{
|
||||||
if (_lastAddedUser == null)
|
if (_lastAddedUser == null)
|
||||||
{
|
{
|
||||||
@@ -201,9 +215,9 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UiSharedService.TextWrapped($"You have successfully added {_lastAddedUser.UserData.AliasOrUID}. Set a local note for the user in the field below:");
|
UiSharedService.TextWrapped(L("Compact.AddUser.Description", "You have successfully added {0}. Set a local note for the user in the field below:", _lastAddedUser.UserData.AliasOrUID));
|
||||||
ImGui.InputTextWithHint("##noteforuser", $"Note for {_lastAddedUser.UserData.AliasOrUID}", ref _lastAddedUserComment, 100);
|
ImGui.InputTextWithHint("##noteforuser", L("Compact.AddUser.NoteHint", "Note for {0}", _lastAddedUser.UserData.AliasOrUID), ref _lastAddedUserComment, 100);
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Note"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, L("Compact.AddUser.Save", "Save Note")))
|
||||||
{
|
{
|
||||||
_serverManager.SetNoteForUid(_lastAddedUser.UserData.UID, _lastAddedUserComment);
|
_serverManager.SetNoteForUid(_lastAddedUser.UserData.UID, _lastAddedUserComment);
|
||||||
_lastAddedUser = null;
|
_lastAddedUser = null;
|
||||||
@@ -238,7 +252,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
if (keys.Any())
|
if (keys.Any())
|
||||||
{
|
{
|
||||||
if (_secretKeyIdx == -1) _secretKeyIdx = keys.First().Key;
|
if (_secretKeyIdx == -1) _secretKeyIdx = keys.First().Key;
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Plus, "Add current character with secret key"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Plus, L("Compact.AddCharacter.Button", "Add current character with secret key")))
|
||||||
{
|
{
|
||||||
_serverManager.CurrentServer!.Authentications.Add(new MareConfiguration.Models.Authentication()
|
_serverManager.CurrentServer!.Authentications.Add(new MareConfiguration.Models.Authentication()
|
||||||
{
|
{
|
||||||
@@ -252,11 +266,11 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
_ = _apiController.CreateConnections();
|
_ = _apiController.CreateConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
_uiSharedService.DrawCombo("Secret Key##addCharacterSecretKey", keys, (f) => f.Value.FriendlyName, (f) => _secretKeyIdx = f.Key);
|
_uiSharedService.DrawCombo(L("Compact.AddCharacter.SecretKeyLabel", "Secret Key") + "##addCharacterSecretKey", keys, (f) => f.Value.FriendlyName, (f) => _secretKeyIdx = f.Key);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped("No secret keys are configured for the current server.", ImGuiColors.DalamudYellow);
|
UiSharedService.ColorTextWrapped(L("Compact.AddCharacter.NoKeys", "No secret keys are configured for the current server."), ImGuiColors.DalamudYellow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +278,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
var buttonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Plus);
|
var buttonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Plus);
|
||||||
ImGui.SetNextItemWidth(UiSharedService.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X);
|
ImGui.SetNextItemWidth(UiSharedService.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X);
|
||||||
ImGui.InputTextWithHint("##otheruid", "Other players UID/Alias", ref _pairToAdd, 20);
|
ImGui.InputTextWithHint("##otheruid", L("Compact.AddPair.Hint", "Other player's UID/Alias"), ref _pairToAdd, 20);
|
||||||
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - buttonSize.X);
|
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - buttonSize.X);
|
||||||
var canAdd = !_pairManager.DirectPairs.Any(p => string.Equals(p.UserData.UID, _pairToAdd, StringComparison.Ordinal) || string.Equals(p.UserData.Alias, _pairToAdd, StringComparison.Ordinal));
|
var canAdd = !_pairManager.DirectPairs.Any(p => string.Equals(p.UserData.UID, _pairToAdd, StringComparison.Ordinal) || string.Equals(p.UserData.Alias, _pairToAdd, StringComparison.Ordinal));
|
||||||
using (ImRaii.Disabled(!canAdd))
|
using (ImRaii.Disabled(!canAdd))
|
||||||
@@ -274,7 +288,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
_ = _apiController.UserAddPair(new(new(_pairToAdd)));
|
_ = _apiController.UserAddPair(new(new(_pairToAdd)));
|
||||||
_pairToAdd = string.Empty;
|
_pairToAdd = string.Empty;
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Pair with " + (_pairToAdd.IsNullOrEmpty() ? "other user" : _pairToAdd));
|
UiSharedService.AttachToolTip(L("Compact.AddPair.Tooltip", "Pair with {0}", _pairToAdd.IsNullOrEmpty() ? L("Compact.AddPair.Tooltip.DefaultUser", "other user") : _pairToAdd));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(2);
|
ImGuiHelpers.ScaledDummy(2);
|
||||||
@@ -292,7 +306,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
ImGui.SetNextItemWidth(WindowContentWidth - spacing);
|
ImGui.SetNextItemWidth(WindowContentWidth - spacing);
|
||||||
ImGui.InputTextWithHint("##filter", "Filter for UID/notes", ref _characterOrCommentFilter, 255);
|
ImGui.InputTextWithHint("##filter", L("Compact.Filter.Hint", "Filter for UID/notes"), ref _characterOrCommentFilter, 255);
|
||||||
|
|
||||||
if (userCount == 0) return;
|
if (userCount == 0) return;
|
||||||
|
|
||||||
@@ -341,9 +355,12 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
_buttonState = !_buttonState;
|
_buttonState = !_buttonState;
|
||||||
}
|
}
|
||||||
if (!_timeout.IsRunning)
|
if (!_timeout.IsRunning)
|
||||||
UiSharedService.AttachToolTip($"Hold Control to {(button == FontAwesomeIcon.Play ? "resume" : "pause")} pairing with {users.Count} out of {userCount} displayed users.");
|
UiSharedService.AttachToolTip(L("Compact.Filter.ToggleTooltip", "Hold Control to {0} pairing with {1} out of {2} displayed users.",
|
||||||
|
button == FontAwesomeIcon.Play ? L("Compact.Filter.ToggleTooltip.Resume", "resume") : L("Compact.Filter.ToggleTooltip.Pause", "pause"),
|
||||||
|
users.Count, userCount));
|
||||||
else
|
else
|
||||||
UiSharedService.AttachToolTip($"Next execution is available at {(5000 - _timeout.ElapsedMilliseconds) / 1000} seconds");
|
UiSharedService.AttachToolTip(L("Compact.Filter.CooldownTooltip", "Next execution is available at {0} seconds",
|
||||||
|
(5000 - _timeout.ElapsedMilliseconds) / 1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,6 +387,122 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
_pairGroupsUi.Draw(visibleUsers, onlineUsers, offlineUsers);
|
_pairGroupsUi.Draw(visibleUsers, onlineUsers, offlineUsers);
|
||||||
|
|
||||||
|
// Always show a Nearby group when detection is enabled, even if empty
|
||||||
|
if (_configService.Current.EnableAutoDetectDiscovery)
|
||||||
|
{
|
||||||
|
using (ImRaii.PushId("group-Nearby"))
|
||||||
|
{
|
||||||
|
var icon = _nearbyOpen ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight;
|
||||||
|
_uiSharedService.IconText(icon);
|
||||||
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _nearbyOpen = !_nearbyOpen;
|
||||||
|
ImGui.SameLine();
|
||||||
|
var onUmbra = _nearbyEntries?.Count(e => e.IsMatch) ?? 0;
|
||||||
|
ImGui.TextUnformatted(L("Compact.Nearby.Title", "Nearby ({0})", onUmbra));
|
||||||
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _nearbyOpen = !_nearbyOpen;
|
||||||
|
var nearbyButtonLabel = L("Compact.Nearby.Button", "Nearby");
|
||||||
|
var btnWidth = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.UserPlus, nearbyButtonLabel);
|
||||||
|
var headerRight = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth();
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPosX(headerRight - btnWidth);
|
||||||
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, nearbyButtonLabel, btnWidth))
|
||||||
|
{
|
||||||
|
Mediator.Publish(new UiToggleMessage(typeof(AutoDetectUi)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_nearbyOpen)
|
||||||
|
{
|
||||||
|
ImGui.Indent();
|
||||||
|
var nearby = _nearbyEntries == null
|
||||||
|
? new List<Services.Mediator.NearbyEntry>()
|
||||||
|
: _nearbyEntries.Where(e => e.IsMatch)
|
||||||
|
.OrderBy(e => e.Distance)
|
||||||
|
.ToList();
|
||||||
|
if (nearby.Count == 0)
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped(L("Compact.Nearby.None", "No nearby players detected."), ImGuiColors.DalamudGrey3);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var e in nearby)
|
||||||
|
{
|
||||||
|
var name = e.DisplayName ?? e.Name;
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.TextUnformatted(name);
|
||||||
|
var right = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth();
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
bool isPaired = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
isPaired = _pairManager.DirectPairs.Any(p => string.Equals(p.UserData.UID, e.Uid, StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
var key = (e.DisplayName ?? e.Name) ?? string.Empty;
|
||||||
|
isPaired = _pairManager.DirectPairs.Any(p => string.Equals(p.UserData.AliasOrUID, key, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.UserPlus);
|
||||||
|
ImGui.SetCursorPosX(right - statusButtonSize.X);
|
||||||
|
|
||||||
|
if (isPaired)
|
||||||
|
{
|
||||||
|
_uiSharedService.IconText(FontAwesomeIcon.Check, ImGuiColors.ParsedGreen);
|
||||||
|
UiSharedService.AttachToolTip(L("Compact.Nearby.Tooltip.AlreadyPaired", "Already paired on Umbra"));
|
||||||
|
}
|
||||||
|
else if (!e.AcceptPairRequests)
|
||||||
|
{
|
||||||
|
_uiSharedService.IconText(FontAwesomeIcon.Ban, ImGuiColors.DalamudGrey3);
|
||||||
|
UiSharedService.AttachToolTip(L("Compact.Nearby.Tooltip.RequestsDisabled", "Pair requests are disabled for this player"));
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(e.Token))
|
||||||
|
{
|
||||||
|
if (_uiSharedService.IconButton(FontAwesomeIcon.UserPlus))
|
||||||
|
{
|
||||||
|
_ = _autoDetectRequestService.SendRequestAsync(e.Token!);
|
||||||
|
}
|
||||||
|
UiSharedService.AttachToolTip(L("Compact.Nearby.Tooltip.SendInvite", "Send Umbra invitation"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_uiSharedService.IconText(FontAwesomeIcon.QuestionCircle, ImGuiColors.DalamudGrey3);
|
||||||
|
UiSharedService.AttachToolTip(L("Compact.Nearby.Tooltip.CannotInvite", "Unable to invite this player"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var inbox = _nearbyPending;
|
||||||
|
if (inbox != null && inbox.Pending.Count > 0)
|
||||||
|
{
|
||||||
|
ImGuiHelpers.ScaledDummy(6);
|
||||||
|
_uiSharedService.BigText(L("Compact.Nearby.Incoming", "Incoming requests"));
|
||||||
|
foreach (var kv in inbox.Pending)
|
||||||
|
{
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.TextUnformatted(L("Compact.Nearby.Incoming.Entry", "{0} [{1}]", kv.Value, kv.Key));
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (_uiSharedService.IconButton(FontAwesomeIcon.Check))
|
||||||
|
{
|
||||||
|
_ = inbox.AcceptAsync(kv.Key);
|
||||||
|
}
|
||||||
|
UiSharedService.AttachToolTip(L("Compact.Nearby.Incoming.Accept", "Accept and add as pair"));
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (_uiSharedService.IconButton(FontAwesomeIcon.Times))
|
||||||
|
{
|
||||||
|
inbox.Remove(kv.Key);
|
||||||
|
}
|
||||||
|
UiSharedService.AttachToolTip(L("Compact.Nearby.Incoming.Dismiss", "Dismiss request"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
ImGui.Unindent();
|
||||||
|
ImGui.Separator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.EndChild();
|
ImGui.EndChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,8 +511,11 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
var buttonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Link);
|
var buttonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Link);
|
||||||
var userCount = _apiController.OnlineUsers.ToString(CultureInfo.InvariantCulture);
|
var userCount = _apiController.OnlineUsers.ToString(CultureInfo.InvariantCulture);
|
||||||
var userSize = ImGui.CalcTextSize(userCount);
|
var userSize = ImGui.CalcTextSize(userCount);
|
||||||
var textSize = ImGui.CalcTextSize("Users Online");
|
var usersOnlineText = L("Compact.ServerStatus.UsersOnline", "Users Online");
|
||||||
string shardConnection = string.Equals(_apiController.ServerInfo.ShardName, "Main", StringComparison.OrdinalIgnoreCase) ? string.Empty : $"Shard: {_apiController.ServerInfo.ShardName}";
|
var textSize = ImGui.CalcTextSize(usersOnlineText);
|
||||||
|
string shardConnection = string.Equals(_apiController.ServerInfo.ShardName, "Main", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? string.Empty
|
||||||
|
: L("Compact.ServerStatus.Shard", "Shard: {0}", _apiController.ServerInfo.ShardName);
|
||||||
var shardTextSize = ImGui.CalcTextSize(shardConnection);
|
var shardTextSize = ImGui.CalcTextSize(shardConnection);
|
||||||
var printShard = !string.IsNullOrEmpty(_apiController.ServerInfo.ShardName) && shardConnection != string.Empty;
|
var printShard = !string.IsNullOrEmpty(_apiController.ServerInfo.ShardName) && shardConnection != string.Empty;
|
||||||
|
|
||||||
@@ -387,15 +523,15 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth()) / 2 - (userSize.X + textSize.X) / 2 - ImGui.GetStyle().ItemSpacing.X / 2);
|
ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth()) / 2 - (userSize.X + textSize.X) / 2 - ImGui.GetStyle().ItemSpacing.X / 2);
|
||||||
if (!printShard) ImGui.AlignTextToFramePadding();
|
if (!printShard) ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextColored(ImGuiColors.ParsedGreen, userCount);
|
ImGui.TextColored(UiSharedService.AccentColor, userCount);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (!printShard) ImGui.AlignTextToFramePadding();
|
if (!printShard) ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted("Users Online");
|
ImGui.TextUnformatted(usersOnlineText);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextColored(ImGuiColors.DalamudRed, "Not connected to any server");
|
ImGui.TextColored(ImGuiColors.DalamudRed, L("Compact.ServerStatus.NotConnected", "Not connected to any server"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (printShard)
|
if (printShard)
|
||||||
@@ -410,8 +546,9 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ((userSize.Y + textSize.Y) / 2 + shardTextSize.Y) / 2 - ImGui.GetStyle().ItemSpacing.Y + buttonSize.Y / 2);
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ((userSize.Y + textSize.Y) / 2 + shardTextSize.Y) / 2 - ImGui.GetStyle().ItemSpacing.Y + buttonSize.Y / 2);
|
||||||
}
|
}
|
||||||
var color = UiSharedService.GetBoolColor(!_serverManager.CurrentServer!.FullPause);
|
var isLinked = !_serverManager.CurrentServer!.FullPause;
|
||||||
var connectedIcon = !_serverManager.CurrentServer.FullPause ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink;
|
var color = isLinked ? new Vector4(0.63f, 0.25f, 1f, 1f) : UiSharedService.GetBoolColor(isLinked);
|
||||||
|
var connectedIcon = isLinked ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink;
|
||||||
|
|
||||||
if (_apiController.ServerState is ServerState.Connected)
|
if (_apiController.ServerState is ServerState.Connected)
|
||||||
{
|
{
|
||||||
@@ -420,7 +557,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
Mediator.Publish(new UiToggleMessage(typeof(EditProfileUi)));
|
Mediator.Publish(new UiToggleMessage(typeof(EditProfileUi)));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Edit your Profile");
|
UiSharedService.AttachToolTip(L("Compact.ServerStatus.EditProfile", "Edit your Profile"));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - buttonSize.X);
|
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - buttonSize.X);
|
||||||
@@ -439,7 +576,9 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
_ = _apiController.CreateConnections();
|
_ = _apiController.CreateConnections();
|
||||||
}
|
}
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
UiSharedService.AttachToolTip(!_serverManager.CurrentServer.FullPause ? "Disconnect from " + _serverManager.CurrentServer.ServerName : "Connect to " + _serverManager.CurrentServer.ServerName);
|
UiSharedService.AttachToolTip(!_serverManager.CurrentServer.FullPause
|
||||||
|
? L("Compact.ServerStatus.Disconnect", "Disconnect from {0}", _serverManager.CurrentServer.ServerName)
|
||||||
|
: L("Compact.ServerStatus.Connect", "Connect to {0}", _serverManager.CurrentServer.ServerName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,22 +625,19 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.SameLine(WindowContentWidth - textSize.X);
|
ImGui.SameLine(WindowContentWidth - textSize.X);
|
||||||
ImGui.TextUnformatted(downloadText);
|
ImGui.TextUnformatted(downloadText);
|
||||||
}
|
}
|
||||||
|
var spacing = ImGui.GetStyle().ItemSpacing.X;
|
||||||
var bottomButtonWidth = (WindowContentWidth - ImGui.GetStyle().ItemSpacing.X) / 2;
|
var bottomButtonWidth = (WindowContentWidth - spacing) / 2f;
|
||||||
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, L("Compact.Transfers.CharacterAnalysis", "Character Analysis"), bottomButtonWidth))
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Character Analysis", bottomButtonWidth))
|
|
||||||
{
|
{
|
||||||
Mediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
|
Mediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Running, L("Compact.Transfers.CharacterDataHub", "Character Data Hub"), bottomButtonWidth))
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Running, "Character Data Hub", bottomButtonWidth))
|
|
||||||
{
|
{
|
||||||
Mediator.Publish(new UiToggleMessage(typeof(CharaDataHubUi)));
|
Mediator.Publish(new UiToggleMessage(typeof(CharaDataHubUi)));
|
||||||
}
|
}
|
||||||
|
ImGuiHelpers.ScaledDummy(2);
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawUIDHeader()
|
private void DrawUIDHeader()
|
||||||
@@ -525,9 +661,9 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
Mediator.Publish(new OpenSettingsUiMessage());
|
Mediator.Publish(new OpenSettingsUiMessage());
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Open the UmbraSync Settings");
|
UiSharedService.AttachToolTip(L("Compact.Header.SettingsTooltip", "Open the UmbraSync settings"));
|
||||||
|
|
||||||
ImGui.SameLine(); //Important to draw the uidText consistently
|
ImGui.SameLine();
|
||||||
ImGui.SetCursorPos(originalPos);
|
ImGui.SetCursorPos(originalPos);
|
||||||
|
|
||||||
if (_apiController.ServerState is ServerState.Connected)
|
if (_apiController.ServerState is ServerState.Connected)
|
||||||
@@ -538,7 +674,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
ImGui.SetClipboardText(_apiController.DisplayName);
|
ImGui.SetClipboardText(_apiController.DisplayName);
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Copy your UID to clipboard");
|
UiSharedService.AttachToolTip(L("Compact.Header.CopyUid", "Copy your UID to clipboard"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
ImGui.SetWindowFontScale(1f);
|
ImGui.SetWindowFontScale(1f);
|
||||||
@@ -573,18 +709,19 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
return _apiController.ServerState switch
|
return _apiController.ServerState switch
|
||||||
{
|
{
|
||||||
ServerState.Connecting => "Attempting to connect to the server.",
|
ServerState.Connecting => L("Compact.ServerError.Connecting", "Attempting to connect to the server."),
|
||||||
ServerState.Reconnecting => "Connection to server interrupted, attempting to reconnect to the server.",
|
ServerState.Reconnecting => L("Compact.ServerError.Reconnecting", "Connection to server interrupted, attempting to reconnect to the server."),
|
||||||
ServerState.Disconnected => "You are currently disconnected from the sync server.",
|
ServerState.Disconnected => L("Compact.ServerError.Disconnected", "You are currently disconnected from the sync server."),
|
||||||
ServerState.Disconnecting => "Disconnecting from the server",
|
ServerState.Disconnecting => L("Compact.ServerError.Disconnecting", "Disconnecting from the server"),
|
||||||
ServerState.Unauthorized => "Server Response: " + _apiController.AuthFailureMessage,
|
ServerState.Unauthorized => L("Compact.ServerError.Unauthorized", "Server Response: {0}", _apiController.AuthFailureMessage),
|
||||||
ServerState.Offline => "Your selected sync server is currently offline.",
|
ServerState.Offline => L("Compact.ServerError.Offline", "Your selected sync server is currently offline."),
|
||||||
ServerState.VersionMisMatch =>
|
ServerState.VersionMisMatch =>
|
||||||
"Your plugin or the server you are connecting to is out of date. Please update your plugin now. If you already did so, contact the server provider to update their server to the latest version.",
|
L("Compact.ServerError.VersionMismatch",
|
||||||
ServerState.RateLimited => "You are rate limited for (re)connecting too often. Disconnect, wait 10 minutes and try again.",
|
"Your plugin or the server you are connecting to is out of date. Please update your plugin now. If you already did so, contact the server provider to update their server to the latest version."),
|
||||||
|
ServerState.RateLimited => L("Compact.ServerError.RateLimited", "You are rate limited for (re)connecting too often. Disconnect, wait 10 minutes and try again."),
|
||||||
ServerState.Connected => string.Empty,
|
ServerState.Connected => string.Empty,
|
||||||
ServerState.NoSecretKey => "You have no secret key set for this current character. Use the button below or open the settings and set a secret key for the current character. You can reuse the same secret key for multiple characters.",
|
ServerState.NoSecretKey => L("Compact.ServerError.NoSecretKey", "You have no secret key set for this current character. Use the button below or open the settings and set a secret key for the current character. You can reuse the same secret key for multiple characters."),
|
||||||
ServerState.MultiChara => "Your Character Configuration has multiple characters configured with same name and world. You will not be able to connect until you fix this issue. Remove the duplicates from the configuration in Settings -> Service Settings -> Character Management and reconnect manually after.",
|
ServerState.MultiChara => L("Compact.ServerError.MultiChara", "Your Character Configuration has multiple characters configured with same name and world. You will not be able to connect until you fix this issue. Remove the duplicates from the configuration in Settings -> Service Settings -> Character Management and reconnect manually after."),
|
||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -595,7 +732,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
ServerState.Connecting => ImGuiColors.DalamudYellow,
|
ServerState.Connecting => ImGuiColors.DalamudYellow,
|
||||||
ServerState.Reconnecting => ImGuiColors.DalamudRed,
|
ServerState.Reconnecting => ImGuiColors.DalamudRed,
|
||||||
ServerState.Connected => UiSharedService.AccentColor,
|
ServerState.Connected => new Vector4(0.63f, 0.25f, 1f, 1f), // custom violet
|
||||||
ServerState.Disconnected => ImGuiColors.DalamudYellow,
|
ServerState.Disconnected => ImGuiColors.DalamudYellow,
|
||||||
ServerState.Disconnecting => ImGuiColors.DalamudYellow,
|
ServerState.Disconnecting => ImGuiColors.DalamudYellow,
|
||||||
ServerState.Unauthorized => ImGuiColors.DalamudRed,
|
ServerState.Unauthorized => ImGuiColors.DalamudRed,
|
||||||
@@ -612,16 +749,16 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
return _apiController.ServerState switch
|
return _apiController.ServerState switch
|
||||||
{
|
{
|
||||||
ServerState.Reconnecting => "Reconnecting",
|
ServerState.Reconnecting => L("Compact.UidText.Reconnecting", "Reconnecting"),
|
||||||
ServerState.Connecting => "Connecting",
|
ServerState.Connecting => L("Compact.UidText.Connecting", "Connecting"),
|
||||||
ServerState.Disconnected => "Disconnected",
|
ServerState.Disconnected => L("Compact.UidText.Disconnected", "Disconnected"),
|
||||||
ServerState.Disconnecting => "Disconnecting",
|
ServerState.Disconnecting => L("Compact.UidText.Disconnecting", "Disconnecting"),
|
||||||
ServerState.Unauthorized => "Unauthorized",
|
ServerState.Unauthorized => L("Compact.UidText.Unauthorized", "Unauthorized"),
|
||||||
ServerState.VersionMisMatch => "Version mismatch",
|
ServerState.VersionMisMatch => L("Compact.UidText.VersionMismatch", "Version mismatch"),
|
||||||
ServerState.Offline => "Unavailable",
|
ServerState.Offline => L("Compact.UidText.Offline", "Unavailable"),
|
||||||
ServerState.RateLimited => "Rate Limited",
|
ServerState.RateLimited => L("Compact.UidText.RateLimited", "Rate Limited"),
|
||||||
ServerState.NoSecretKey => "No Secret Key",
|
ServerState.NoSecretKey => L("Compact.UidText.NoSecretKey", "No Secret Key"),
|
||||||
ServerState.MultiChara => "Duplicate Characters",
|
ServerState.MultiChara => L("Compact.UidText.MultiChara", "Duplicate Characters"),
|
||||||
ServerState.Connected => _apiController.DisplayName,
|
ServerState.Connected => _apiController.DisplayName,
|
||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
@@ -11,6 +13,7 @@ using MareSynchronos.Services;
|
|||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.UI.Handlers;
|
using MareSynchronos.UI.Handlers;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
|
using MareSynchronos.Localization;
|
||||||
|
|
||||||
namespace MareSynchronos.UI.Components;
|
namespace MareSynchronos.UI.Components;
|
||||||
|
|
||||||
@@ -21,6 +24,13 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
private readonly GroupFullInfoDto _group;
|
private readonly GroupFullInfoDto _group;
|
||||||
private readonly CharaDataManager _charaDataManager;
|
private readonly CharaDataManager _charaDataManager;
|
||||||
|
|
||||||
|
private static string L(string key, string fallback, params object[] args)
|
||||||
|
{
|
||||||
|
var safeArgs = args ?? Array.Empty<object>();
|
||||||
|
return LocalizationService.Instance?.GetString(key, fallback, safeArgs)
|
||||||
|
?? string.Format(System.Globalization.CultureInfo.CurrentCulture, fallback, safeArgs);
|
||||||
|
}
|
||||||
|
|
||||||
public DrawGroupPair(string id, Pair entry, ApiController apiController,
|
public DrawGroupPair(string id, Pair entry, ApiController apiController,
|
||||||
MareMediator mareMediator, GroupFullInfoDto group, GroupPairFullInfoDto fullInfoDto,
|
MareMediator mareMediator, GroupFullInfoDto group, GroupPairFullInfoDto fullInfoDto,
|
||||||
UidDisplayHandler handler, UiSharedService uiSharedService, CharaDataManager charaDataManager)
|
UidDisplayHandler handler, UiSharedService uiSharedService, CharaDataManager charaDataManager)
|
||||||
@@ -38,40 +48,50 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
var entryIsMod = _fullInfoDto.GroupPairStatusInfo.IsModerator();
|
var entryIsMod = _fullInfoDto.GroupPairStatusInfo.IsModerator();
|
||||||
var entryIsOwner = string.Equals(_pair.UserData.UID, _group.OwnerUID, StringComparison.Ordinal);
|
var entryIsOwner = string.Equals(_pair.UserData.UID, _group.OwnerUID, StringComparison.Ordinal);
|
||||||
var entryIsPinned = _fullInfoDto.GroupPairStatusInfo.IsPinned();
|
var entryIsPinned = _fullInfoDto.GroupPairStatusInfo.IsPinned();
|
||||||
var presenceIcon = _pair.IsVisible ? FontAwesomeIcon.Eye : (_pair.IsOnline ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink);
|
var presenceIcon = _pair.IsVisible ? FontAwesomeIcon.Eye : FontAwesomeIcon.CloudMoon;
|
||||||
var presenceColor = (_pair.IsOnline || _pair.IsVisible) ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed;
|
var presenceColor = (_pair.IsOnline || _pair.IsVisible) ? new Vector4(0.63f, 0.25f, 1f, 1f) : ImGuiColors.DalamudGrey;
|
||||||
var presenceText = entryUID + " is offline";
|
var presenceText = L("GroupPair.Presence.Offline", "{0} is offline", entryUID);
|
||||||
|
|
||||||
ImGui.SetCursorPosY(textPosY);
|
ImGui.SetCursorPosY(textPosY);
|
||||||
|
bool drewPrefixIcon = false;
|
||||||
|
|
||||||
if (_pair.IsPaused)
|
if (_pair.IsPaused)
|
||||||
{
|
{
|
||||||
presenceIcon = FontAwesomeIcon.Question;
|
presenceText = L("GroupPair.Presence.Paused", "{0} online status is unknown (paused)", entryUID);
|
||||||
presenceColor = ImGuiColors.DalamudGrey;
|
|
||||||
presenceText = entryUID + " online status is unknown (paused)";
|
|
||||||
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
UiSharedService.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow);
|
UiSharedService.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow);
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
|
|
||||||
UiSharedService.AttachToolTip("Pairing status with " + entryUID + " is paused");
|
UiSharedService.AttachToolTip(L("GroupPair.Tooltip.Paused", "Pairing status with {0} is paused", entryUID));
|
||||||
|
drewPrefixIcon = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
bool individuallyPaired = _pair.UserPair != null;
|
||||||
UiSharedService.ColorText(FontAwesomeIcon.Check.ToIconString(), ImGuiColors.ParsedGreen);
|
var violet = new Vector4(0.63f, 0.25f, 1f, 1f);
|
||||||
ImGui.PopFont();
|
if (individuallyPaired && (_pair.IsOnline || _pair.IsVisible))
|
||||||
|
{
|
||||||
UiSharedService.AttachToolTip("You are paired with " + entryUID);
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
UiSharedService.ColorText(FontAwesomeIcon.Moon.ToIconString(), violet);
|
||||||
|
ImGui.PopFont();
|
||||||
|
UiSharedService.AttachToolTip(L("GroupPair.Tooltip.IndividuallyPaired", "You are individually paired with {0}", entryUID));
|
||||||
|
drewPrefixIcon = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (drewPrefixIcon)
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
if (_pair.IsOnline && !_pair.IsVisible) presenceText = entryUID + " is online";
|
|
||||||
else if (_pair.IsOnline && _pair.IsVisible) presenceText = entryUID + " is visible: " + _pair.PlayerName + Environment.NewLine + "Click to target this player";
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.SetCursorPosY(textPosY);
|
ImGui.SetCursorPosY(textPosY);
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
UiSharedService.ColorText(presenceIcon.ToIconString(), presenceColor);
|
UiSharedService.ColorText(presenceIcon.ToIconString(), presenceColor);
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
if (_pair.IsOnline && !_pair.IsVisible)
|
||||||
|
presenceText = L("GroupPair.Presence.Online", "{0} is online", entryUID);
|
||||||
|
else if (_pair.IsOnline && _pair.IsVisible)
|
||||||
|
presenceText = L("GroupPair.Presence.Visible", "{0} is visible: {1}\nClick to target this player", entryUID, _pair.PlayerName);
|
||||||
|
|
||||||
if (_pair.IsVisible)
|
if (_pair.IsVisible)
|
||||||
{
|
{
|
||||||
if (ImGui.IsItemClicked())
|
if (ImGui.IsItemClicked())
|
||||||
@@ -81,19 +101,23 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
if (_pair.LastAppliedDataBytes >= 0)
|
if (_pair.LastAppliedDataBytes >= 0)
|
||||||
{
|
{
|
||||||
presenceText += UiSharedService.TooltipSeparator;
|
presenceText += UiSharedService.TooltipSeparator;
|
||||||
presenceText += ((!_pair.IsVisible) ? "(Last) " : string.Empty) + "Mods Info" + Environment.NewLine;
|
presenceText += (!_pair.IsVisible ? L("GroupPair.Presence.LastPrefix", "(Last) ") : string.Empty)
|
||||||
presenceText += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true);
|
+ L("GroupPair.Presence.ModsInfo", "Mods Info") + Environment.NewLine;
|
||||||
|
presenceText += L("GroupPair.Presence.FilesSize", "Files Size: {0}", UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true));
|
||||||
if (_pair.LastAppliedApproximateVRAMBytes >= 0)
|
if (_pair.LastAppliedApproximateVRAMBytes >= 0)
|
||||||
{
|
{
|
||||||
presenceText += Environment.NewLine + "Approx. VRAM Usage: " + UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true);
|
presenceText += Environment.NewLine + L("GroupPair.Presence.Vram", "Approx. VRAM Usage: {0}", UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true));
|
||||||
}
|
}
|
||||||
if (_pair.LastAppliedDataTris >= 0)
|
if (_pair.LastAppliedDataTris >= 0)
|
||||||
{
|
{
|
||||||
presenceText += Environment.NewLine + "Triangle Count (excl. Vanilla): "
|
var trisValue = _pair.LastAppliedDataTris > 1000
|
||||||
+ (_pair.LastAppliedDataTris > 1000 ? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'") : _pair.LastAppliedDataTris);
|
? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'", System.Globalization.CultureInfo.CurrentCulture)
|
||||||
|
: _pair.LastAppliedDataTris.ToString(System.Globalization.CultureInfo.CurrentCulture);
|
||||||
|
presenceText += Environment.NewLine + L("GroupPair.Presence.Tris", "Triangle Count (excl. Vanilla): {0}", trisValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UiSharedService.AttachToolTip(presenceText);
|
UiSharedService.AttachToolTip(presenceText);
|
||||||
|
|
||||||
if (entryIsOwner)
|
if (entryIsOwner)
|
||||||
@@ -103,7 +127,7 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
ImGui.PushFont(UiBuilder.IconFont);
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
ImGui.TextUnformatted(FontAwesomeIcon.Crown.ToIconString());
|
ImGui.TextUnformatted(FontAwesomeIcon.Crown.ToIconString());
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
UiSharedService.AttachToolTip("User is owner of this Syncshell");
|
UiSharedService.AttachToolTip(L("GroupPair.Tooltip.Owner", "User is owner of this Syncshell"));
|
||||||
}
|
}
|
||||||
else if (entryIsMod)
|
else if (entryIsMod)
|
||||||
{
|
{
|
||||||
@@ -112,7 +136,7 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
ImGui.PushFont(UiBuilder.IconFont);
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
ImGui.TextUnformatted(FontAwesomeIcon.UserShield.ToIconString());
|
ImGui.TextUnformatted(FontAwesomeIcon.UserShield.ToIconString());
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
UiSharedService.AttachToolTip("User is moderator of this Syncshell");
|
UiSharedService.AttachToolTip(L("GroupPair.Tooltip.Moderator", "User is moderator of this Syncshell"));
|
||||||
}
|
}
|
||||||
else if (entryIsPinned)
|
else if (entryIsPinned)
|
||||||
{
|
{
|
||||||
@@ -121,7 +145,7 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
ImGui.PushFont(UiBuilder.IconFont);
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
ImGui.TextUnformatted(FontAwesomeIcon.Thumbtack.ToIconString());
|
ImGui.TextUnformatted(FontAwesomeIcon.Thumbtack.ToIconString());
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
UiSharedService.AttachToolTip("User is pinned in this Syncshell");
|
UiSharedService.AttachToolTip(L("GroupPair.Tooltip.Pinned", "User is pinned in this Syncshell"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,8 +190,9 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
{
|
{
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.Running);
|
_uiSharedService.IconText(FontAwesomeIcon.Running);
|
||||||
|
|
||||||
UiSharedService.AttachToolTip($"This user has shared {sharedData!.Count} Character Data Sets with you." + UiSharedService.TooltipSeparator
|
UiSharedService.AttachToolTip(L("GroupPair.Tooltip.SharedData", "This user has shared {0} Character Data Sets with you.", sharedData!.Count)
|
||||||
+ "Click to open the Character Data Hub and show the entries.");
|
+ UiSharedService.TooltipSeparator
|
||||||
|
+ L("GroupPair.Tooltip.SharedData.OpenHub", "Click to open the Character Data Hub and show the entries."));
|
||||||
|
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||||
{
|
{
|
||||||
@@ -176,7 +201,7 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (individualAnimDisabled || individualSoundsDisabled)
|
if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled)
|
||||||
{
|
{
|
||||||
ImGui.SetCursorPosY(textPosY);
|
ImGui.SetCursorPosY(textPosY);
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
|
||||||
@@ -186,46 +211,52 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
ImGui.BeginTooltip();
|
||||||
|
|
||||||
ImGui.TextUnformatted("Individual User permissions");
|
ImGui.TextUnformatted(L("GroupPair.Tooltip.IndividualHeader", "Individual User permissions"));
|
||||||
|
|
||||||
if (individualSoundsDisabled)
|
if (individualSoundsDisabled)
|
||||||
{
|
{
|
||||||
var userSoundsText = "Sound sync disabled with " + _pair.UserData.AliasOrUID;
|
var userSoundsText = L("GroupPair.Tooltip.SoundWith", "Sound sync disabled with {0}", _pair.UserData.AliasOrUID);
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.VolumeOff);
|
_uiSharedService.IconText(FontAwesomeIcon.VolumeOff);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userSoundsText);
|
ImGui.TextUnformatted(userSoundsText);
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableSounds() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableSounds() ? "Disabled" : "Enabled"));
|
ImGui.TextUnformatted(L("GroupPair.Tooltip.YouThey", "You: {0}, They: {1}",
|
||||||
|
_pair.UserPair!.OwnPermissions.IsDisableSounds() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled"),
|
||||||
|
_pair.UserPair!.OtherPermissions.IsDisableSounds() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (individualAnimDisabled)
|
if (individualAnimDisabled)
|
||||||
{
|
{
|
||||||
var userAnimText = "Animation sync disabled with " + _pair.UserData.AliasOrUID;
|
var userAnimText = L("GroupPair.Tooltip.AnimWith", "Animation sync disabled with {0}", _pair.UserData.AliasOrUID);
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.Stop);
|
_uiSharedService.IconText(FontAwesomeIcon.Stop);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userAnimText);
|
ImGui.TextUnformatted(userAnimText);
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableAnimations() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableAnimations() ? "Disabled" : "Enabled"));
|
ImGui.TextUnformatted(L("GroupPair.Tooltip.YouThey", "You: {0}, They: {1}",
|
||||||
|
_pair.UserPair!.OwnPermissions.IsDisableAnimations() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled"),
|
||||||
|
_pair.UserPair!.OtherPermissions.IsDisableAnimations() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (individualVFXDisabled)
|
if (individualVFXDisabled)
|
||||||
{
|
{
|
||||||
var userVFXText = "VFX sync disabled with " + _pair.UserData.AliasOrUID;
|
var userVFXText = L("GroupPair.Tooltip.VfxWith", "VFX sync disabled with {0}", _pair.UserData.AliasOrUID);
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.Circle);
|
_uiSharedService.IconText(FontAwesomeIcon.Circle);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userVFXText);
|
ImGui.TextUnformatted(userVFXText);
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableVFX() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableVFX() ? "Disabled" : "Enabled"));
|
ImGui.TextUnformatted(L("GroupPair.Tooltip.YouThey", "You: {0}, They: {1}",
|
||||||
|
_pair.UserPair!.OwnPermissions.IsDisableVFX() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled"),
|
||||||
|
_pair.UserPair!.OtherPermissions.IsDisableVFX() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled")));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTooltip();
|
ImGui.EndTooltip();
|
||||||
}
|
}
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
else if ((animDisabled || soundsDisabled))
|
else if ((animDisabled || soundsDisabled || vfxDisabled))
|
||||||
{
|
{
|
||||||
ImGui.SetCursorPosY(textPosY);
|
ImGui.SetCursorPosY(textPosY);
|
||||||
_uiSharedService.IconText(permIcon);
|
_uiSharedService.IconText(permIcon);
|
||||||
@@ -233,11 +264,11 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
ImGui.BeginTooltip();
|
||||||
|
|
||||||
ImGui.TextUnformatted("Syncshell User permissions");
|
ImGui.TextUnformatted(L("GroupPair.Tooltip.SyncshellHeader", "Syncshell User permissions"));
|
||||||
|
|
||||||
if (soundsDisabled)
|
if (soundsDisabled)
|
||||||
{
|
{
|
||||||
var userSoundsText = "Sound sync disabled by " + _pair.UserData.AliasOrUID;
|
var userSoundsText = L("GroupPair.Tooltip.SoundBy", "Sound sync disabled by {0}", _pair.UserData.AliasOrUID);
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.VolumeOff);
|
_uiSharedService.IconText(FontAwesomeIcon.VolumeOff);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userSoundsText);
|
ImGui.TextUnformatted(userSoundsText);
|
||||||
@@ -245,7 +276,7 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
|
|
||||||
if (animDisabled)
|
if (animDisabled)
|
||||||
{
|
{
|
||||||
var userAnimText = "Animation sync disabled by " + _pair.UserData.AliasOrUID;
|
var userAnimText = L("GroupPair.Tooltip.AnimBy", "Animation sync disabled by {0}", _pair.UserData.AliasOrUID);
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.Stop);
|
_uiSharedService.IconText(FontAwesomeIcon.Stop);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userAnimText);
|
ImGui.TextUnformatted(userAnimText);
|
||||||
@@ -253,7 +284,7 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
|
|
||||||
if (vfxDisabled)
|
if (vfxDisabled)
|
||||||
{
|
{
|
||||||
var userVFXText = "VFX sync disabled by " + _pair.UserData.AliasOrUID;
|
var userVFXText = L("GroupPair.Tooltip.VfxBy", "VFX sync disabled by {0}", _pair.UserData.AliasOrUID);
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.Circle);
|
_uiSharedService.IconText(FontAwesomeIcon.Circle);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userVFXText);
|
ImGui.TextUnformatted(userVFXText);
|
||||||
@@ -272,7 +303,7 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
{
|
{
|
||||||
_ = _apiController.UserAddPair(new UserDto(new(_pair.UserData.UID)));
|
_ = _apiController.UserAddPair(new UserDto(new(_pair.UserData.UID)));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Pair with " + entryUID + " individually");
|
UiSharedService.AttachToolTip(L("GroupPair.Popup.PairIndividually", "Pair with {0} individually", entryUID));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,21 +5,32 @@ using Dalamud.Interface.Utility;
|
|||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using MareSynchronos.API.Data.Extensions;
|
using MareSynchronos.API.Data.Extensions;
|
||||||
using MareSynchronos.API.Dto.User;
|
using MareSynchronos.API.Dto.User;
|
||||||
|
using MareSynchronos.Localization;
|
||||||
using MareSynchronos.PlayerData.Pairs;
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
using MareSynchronos.Services;
|
using MareSynchronos.Services;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.UI.Handlers;
|
using MareSynchronos.UI.Handlers;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace MareSynchronos.UI.Components;
|
namespace MareSynchronos.UI.Components;
|
||||||
|
|
||||||
public class DrawUserPair : DrawPairBase
|
public class DrawUserPair : DrawPairBase
|
||||||
{
|
{
|
||||||
|
private static readonly Vector4 Violet = new(0.63f, 0.25f, 1f, 1f);
|
||||||
protected readonly MareMediator _mediator;
|
protected readonly MareMediator _mediator;
|
||||||
private readonly SelectGroupForPairUi _selectGroupForPairUi;
|
private readonly SelectGroupForPairUi _selectGroupForPairUi;
|
||||||
private readonly CharaDataManager _charaDataManager;
|
private readonly CharaDataManager _charaDataManager;
|
||||||
|
|
||||||
|
private static string L(string key, string fallback, params object[] args)
|
||||||
|
{
|
||||||
|
var safeArgs = args ?? Array.Empty<object>();
|
||||||
|
return LocalizationService.Instance?.GetString(key, fallback, safeArgs)
|
||||||
|
?? string.Format(CultureInfo.CurrentCulture, fallback, safeArgs);
|
||||||
|
}
|
||||||
|
|
||||||
public DrawUserPair(string id, Pair entry, UidDisplayHandler displayHandler, ApiController apiController,
|
public DrawUserPair(string id, Pair entry, UidDisplayHandler displayHandler, ApiController apiController,
|
||||||
MareMediator mareMediator, SelectGroupForPairUi selectGroupForPairUi,
|
MareMediator mareMediator, SelectGroupForPairUi selectGroupForPairUi,
|
||||||
UiSharedService uiSharedService, CharaDataManager charaDataManager)
|
UiSharedService uiSharedService, CharaDataManager charaDataManager)
|
||||||
@@ -38,58 +49,62 @@ public class DrawUserPair : DrawPairBase
|
|||||||
|
|
||||||
protected override void DrawLeftSide(float textPosY, float originalY)
|
protected override void DrawLeftSide(float textPosY, float originalY)
|
||||||
{
|
{
|
||||||
FontAwesomeIcon connectionIcon;
|
var online = _pair.IsOnline;
|
||||||
Vector4 connectionColor;
|
var offlineGrey = ImGuiColors.DalamudGrey3;
|
||||||
string connectionText;
|
|
||||||
if (!(_pair.UserPair!.OwnPermissions.IsPaired() && _pair.UserPair!.OtherPermissions.IsPaired()))
|
|
||||||
{
|
|
||||||
connectionIcon = FontAwesomeIcon.ArrowUp;
|
|
||||||
connectionText = _pair.UserData.AliasOrUID + " has not added you back";
|
|
||||||
connectionColor = ImGuiColors.DalamudRed;
|
|
||||||
}
|
|
||||||
else if (_pair.UserPair!.OwnPermissions.IsPaused() || _pair.UserPair!.OtherPermissions.IsPaused())
|
|
||||||
{
|
|
||||||
connectionIcon = FontAwesomeIcon.PauseCircle;
|
|
||||||
connectionText = "Pairing status with " + _pair.UserData.AliasOrUID + " is paused";
|
|
||||||
connectionColor = ImGuiColors.DalamudYellow;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
connectionIcon = FontAwesomeIcon.Check;
|
|
||||||
connectionText = "You are paired with " + _pair.UserData.AliasOrUID;
|
|
||||||
connectionColor = ImGuiColors.ParsedGreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SetCursorPosY(textPosY);
|
ImGui.SetCursorPosY(textPosY);
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
UiSharedService.ColorText(connectionIcon.ToIconString(), connectionColor);
|
UiSharedService.ColorText(FontAwesomeIcon.Moon.ToIconString(), online ? Violet : offlineGrey);
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
UiSharedService.AttachToolTip(connectionText);
|
UiSharedService.AttachToolTip(online
|
||||||
|
? L("UserPair.Status.Online", "User is online")
|
||||||
|
: L("UserPair.Status.Offline", "User is offline"));
|
||||||
|
if (!(_pair.UserPair!.OwnPermissions.IsPaired() && _pair.UserPair!.OtherPermissions.IsPaired()))
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPosY(textPosY);
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
UiSharedService.ColorText(FontAwesomeIcon.ArrowUp.ToIconString(), ImGuiColors.DalamudRed);
|
||||||
|
ImGui.PopFont();
|
||||||
|
UiSharedService.AttachToolTip(L("UserPair.Tooltip.NotAddedBack", "{0} has not added you back", _pair.UserData.AliasOrUID));
|
||||||
|
}
|
||||||
|
else if (_pair.UserPair!.OwnPermissions.IsPaused() || _pair.UserPair!.OtherPermissions.IsPaused())
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPosY(textPosY);
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
UiSharedService.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow);
|
||||||
|
ImGui.PopFont();
|
||||||
|
UiSharedService.AttachToolTip(L("UserPair.Tooltip.Paused", "Pairing with {0} is paused", _pair.UserData.AliasOrUID));
|
||||||
|
}
|
||||||
if (_pair is { IsOnline: true, IsVisible: true })
|
if (_pair is { IsOnline: true, IsVisible: true })
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetCursorPosY(textPosY);
|
ImGui.SetCursorPosY(textPosY);
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
UiSharedService.ColorText(FontAwesomeIcon.Eye.ToIconString(), ImGuiColors.ParsedGreen);
|
UiSharedService.ColorText(FontAwesomeIcon.Eye.ToIconString(), Violet);
|
||||||
if (ImGui.IsItemClicked())
|
if (ImGui.IsItemClicked())
|
||||||
{
|
{
|
||||||
_mediator.Publish(new TargetPairMessage(_pair));
|
_mediator.Publish(new TargetPairMessage(_pair));
|
||||||
}
|
}
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
var visibleTooltip = _pair.UserData.AliasOrUID + " is visible: " + _pair.PlayerName! + Environment.NewLine + "Click to target this player";
|
var visibleTooltip = L("UserPair.Tooltip.Visible", "{0} is visible: {1}\nClick to target this player", _pair.UserData.AliasOrUID, _pair.PlayerName!);
|
||||||
if (_pair.LastAppliedDataBytes >= 0)
|
if (_pair.LastAppliedDataBytes >= 0)
|
||||||
{
|
{
|
||||||
visibleTooltip += UiSharedService.TooltipSeparator;
|
visibleTooltip += UiSharedService.TooltipSeparator;
|
||||||
visibleTooltip += ((!_pair.IsVisible) ? "(Last) " : string.Empty) + "Mods Info" + Environment.NewLine;
|
visibleTooltip += (!_pair.IsVisible ? L("UserPair.Tooltip.Visible.LastPrefix", "(Last) ") : string.Empty)
|
||||||
visibleTooltip += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true);
|
+ L("UserPair.Tooltip.Visible.ModsInfo", "Mods Info") + Environment.NewLine;
|
||||||
|
visibleTooltip += L("UserPair.Tooltip.Visible.FilesSize", "Files Size: {0}", UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true));
|
||||||
if (_pair.LastAppliedApproximateVRAMBytes >= 0)
|
if (_pair.LastAppliedApproximateVRAMBytes >= 0)
|
||||||
{
|
{
|
||||||
visibleTooltip += Environment.NewLine + "Approx. VRAM Usage: " + UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true);
|
visibleTooltip += Environment.NewLine + L("UserPair.Tooltip.Visible.Vram", "Approx. VRAM Usage: {0}", UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true));
|
||||||
}
|
}
|
||||||
if (_pair.LastAppliedDataTris >= 0)
|
if (_pair.LastAppliedDataTris >= 0)
|
||||||
{
|
{
|
||||||
visibleTooltip += Environment.NewLine + "Triangle Count (excl. Vanilla): "
|
var trisValue = _pair.LastAppliedDataTris > 1000
|
||||||
+ (_pair.LastAppliedDataTris > 1000 ? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'") : _pair.LastAppliedDataTris);
|
? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'", CultureInfo.CurrentCulture)
|
||||||
|
: _pair.LastAppliedDataTris.ToString(CultureInfo.CurrentCulture);
|
||||||
|
visibleTooltip += Environment.NewLine + L("UserPair.Tooltip.Visible.Tris", "Triangle Count (excl. Vanilla): {0}", trisValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,15 +149,13 @@ public class DrawUserPair : DrawPairBase
|
|||||||
_ = _apiController.UserSetPairPermissions(new(_pair.UserData, perm));
|
_ = _apiController.UserSetPairPermissions(new(_pair.UserData, perm));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip(!_pair.UserPair!.OwnPermissions.IsPaused()
|
UiSharedService.AttachToolTip(!_pair.UserPair!.OwnPermissions.IsPaused()
|
||||||
? "Pause pairing with " + entryUID
|
? L("UserPair.Tooltip.Pause", "Pause pairing with {0}", entryUID)
|
||||||
: "Resume pairing with " + entryUID);
|
: L("UserPair.Tooltip.Resume", "Resume pairing with {0}", entryUID));
|
||||||
|
|
||||||
|
|
||||||
var individualSoundsDisabled = (_pair.UserPair?.OwnPermissions.IsDisableSounds() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableSounds() ?? false);
|
var individualSoundsDisabled = (_pair.UserPair?.OwnPermissions.IsDisableSounds() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableSounds() ?? false);
|
||||||
var individualAnimDisabled = (_pair.UserPair?.OwnPermissions.IsDisableAnimations() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableAnimations() ?? false);
|
var individualAnimDisabled = (_pair.UserPair?.OwnPermissions.IsDisableAnimations() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableAnimations() ?? false);
|
||||||
var individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false);
|
var individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false);
|
||||||
|
|
||||||
// Icon for individually applied permissions
|
|
||||||
if (individualSoundsDisabled || individualAnimDisabled || individualVFXDisabled)
|
if (individualSoundsDisabled || individualAnimDisabled || individualVFXDisabled)
|
||||||
{
|
{
|
||||||
var icon = FontAwesomeIcon.ExclamationTriangle;
|
var icon = FontAwesomeIcon.ExclamationTriangle;
|
||||||
@@ -158,47 +171,63 @@ public class DrawUserPair : DrawPairBase
|
|||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
ImGui.BeginTooltip();
|
||||||
|
|
||||||
ImGui.TextUnformatted("Individual User permissions");
|
ImGui.TextUnformatted(L("UserPair.Tooltip.Permission.Header", "Individual user permissions"));
|
||||||
|
|
||||||
if (individualSoundsDisabled)
|
if (individualSoundsDisabled)
|
||||||
{
|
{
|
||||||
var userSoundsText = "Sound sync disabled with " + _pair.UserData.AliasOrUID;
|
var userSoundsText = L("UserPair.Tooltip.Permission.Sound", "Sound sync disabled with {0}", _pair.UserData.AliasOrUID);
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.VolumeOff);
|
_uiSharedService.IconText(FontAwesomeIcon.VolumeOff);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userSoundsText);
|
ImGui.TextUnformatted(userSoundsText);
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableSounds() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableSounds() ? "Disabled" : "Enabled"));
|
var youStatus = _pair.UserPair!.OwnPermissions.IsDisableSounds()
|
||||||
|
? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled")
|
||||||
|
: L("UserPair.Tooltip.Permission.State.Enabled", "Enabled");
|
||||||
|
var theyStatus = _pair.UserPair!.OtherPermissions.IsDisableSounds()
|
||||||
|
? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled")
|
||||||
|
: L("UserPair.Tooltip.Permission.State.Enabled", "Enabled");
|
||||||
|
ImGui.TextUnformatted(L("UserPair.Tooltip.Permission.Status", "You: {0}, They: {1}", youStatus, theyStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (individualAnimDisabled)
|
if (individualAnimDisabled)
|
||||||
{
|
{
|
||||||
var userAnimText = "Animation sync disabled with " + _pair.UserData.AliasOrUID;
|
var userAnimText = L("UserPair.Tooltip.Permission.Animation", "Animation sync disabled with {0}", _pair.UserData.AliasOrUID);
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.Stop);
|
_uiSharedService.IconText(FontAwesomeIcon.Stop);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userAnimText);
|
ImGui.TextUnformatted(userAnimText);
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableAnimations() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableAnimations() ? "Disabled" : "Enabled"));
|
var youStatus = _pair.UserPair!.OwnPermissions.IsDisableAnimations()
|
||||||
|
? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled")
|
||||||
|
: L("UserPair.Tooltip.Permission.State.Enabled", "Enabled");
|
||||||
|
var theyStatus = _pair.UserPair!.OtherPermissions.IsDisableAnimations()
|
||||||
|
? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled")
|
||||||
|
: L("UserPair.Tooltip.Permission.State.Enabled", "Enabled");
|
||||||
|
ImGui.TextUnformatted(L("UserPair.Tooltip.Permission.Status", "You: {0}, They: {1}", youStatus, theyStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (individualVFXDisabled)
|
if (individualVFXDisabled)
|
||||||
{
|
{
|
||||||
var userVFXText = "VFX sync disabled with " + _pair.UserData.AliasOrUID;
|
var userVFXText = L("UserPair.Tooltip.Permission.Vfx", "VFX sync disabled with {0}", _pair.UserData.AliasOrUID);
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.Circle);
|
_uiSharedService.IconText(FontAwesomeIcon.Circle);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userVFXText);
|
ImGui.TextUnformatted(userVFXText);
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableVFX() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableVFX() ? "Disabled" : "Enabled"));
|
var youStatus = _pair.UserPair!.OwnPermissions.IsDisableVFX()
|
||||||
|
? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled")
|
||||||
|
: L("UserPair.Tooltip.Permission.State.Enabled", "Enabled");
|
||||||
|
var theyStatus = _pair.UserPair!.OtherPermissions.IsDisableVFX()
|
||||||
|
? L("UserPair.Tooltip.Permission.State.Disabled", "Disabled")
|
||||||
|
: L("UserPair.Tooltip.Permission.State.Enabled", "Enabled");
|
||||||
|
ImGui.TextUnformatted(L("UserPair.Tooltip.Permission.Status", "You: {0}, They: {1}", youStatus, theyStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTooltip();
|
ImGui.EndTooltip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Icon for shared character data
|
|
||||||
if (_charaDataManager.SharedWithYouData.TryGetValue(_pair.UserData, out var sharedData))
|
if (_charaDataManager.SharedWithYouData.TryGetValue(_pair.UserData, out var sharedData))
|
||||||
{
|
{
|
||||||
var icon = FontAwesomeIcon.Running;
|
var icon = FontAwesomeIcon.Running;
|
||||||
@@ -207,8 +236,9 @@ public class DrawUserPair : DrawPairBase
|
|||||||
ImGui.SameLine(rightSidePos);
|
ImGui.SameLine(rightSidePos);
|
||||||
_uiSharedService.IconText(icon);
|
_uiSharedService.IconText(icon);
|
||||||
|
|
||||||
UiSharedService.AttachToolTip($"This user has shared {sharedData.Count} Character Data Sets with you." + UiSharedService.TooltipSeparator
|
UiSharedService.AttachToolTip(L("UserPair.Tooltip.SharedData", "This user has shared {0} Character Data Sets with you.", sharedData.Count)
|
||||||
+ "Click to open the Character Data Hub and show the entries.");
|
+ UiSharedService.TooltipSeparator
|
||||||
|
+ L("UserPair.Tooltip.SharedData.OpenHub", "Click to open the Character Data Hub and show the entries."));
|
||||||
|
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||||
{
|
{
|
||||||
@@ -223,7 +253,7 @@ public class DrawUserPair : DrawPairBase
|
|||||||
{
|
{
|
||||||
if (entry.IsVisible)
|
if (entry.IsVisible)
|
||||||
{
|
{
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Eye, "Target player"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Eye, L("UserPair.Menu.Target", "Target player")))
|
||||||
{
|
{
|
||||||
_mediator.Publish(new TargetPairMessage(entry));
|
_mediator.Publish(new TargetPairMessage(entry));
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
@@ -231,44 +261,46 @@ public class DrawUserPair : DrawPairBase
|
|||||||
}
|
}
|
||||||
if (!entry.IsPaused)
|
if (!entry.IsPaused)
|
||||||
{
|
{
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.User, "Open Profile"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.User, L("UserPair.Menu.OpenProfile", "Open Profile")))
|
||||||
{
|
{
|
||||||
_displayHandler.OpenProfile(entry);
|
_displayHandler.OpenProfile(entry);
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Opens the profile for this user in a new window");
|
UiSharedService.AttachToolTip(L("UserPair.Menu.OpenProfile.Tooltip", "Opens the profile for this user in a new window"));
|
||||||
}
|
}
|
||||||
if (entry.IsVisible)
|
if (entry.IsVisible)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Open Analysis"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, L("UserPair.Menu.OpenAnalysis", "Open Analysis")))
|
||||||
{
|
{
|
||||||
_displayHandler.OpenAnalysis(_pair);
|
_displayHandler.OpenAnalysis(_pair);
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Sync, "Reload last data"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Sync, L("UserPair.Menu.ReloadData", "Reload last data")))
|
||||||
{
|
{
|
||||||
entry.ApplyLastReceivedData(forced: true);
|
entry.ApplyLastReceivedData(forced: true);
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("This reapplies the last received character data to this character");
|
UiSharedService.AttachToolTip(L("UserPair.Menu.ReloadData.Tooltip", "This reapplies the last received character data to this character"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Cycle pause state"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, L("UserPair.Menu.CyclePause", "Cycle pause state")))
|
||||||
{
|
{
|
||||||
_ = _apiController.CyclePause(entry.UserData);
|
_ = _apiController.CyclePause(entry.UserData);
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
var entryUID = entry.UserData.AliasOrUID;
|
var entryUID = entry.UserData.AliasOrUID;
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Folder, "Pair Groups"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Folder, L("UserPair.Menu.PairGroups", "Pair Groups")))
|
||||||
{
|
{
|
||||||
_selectGroupForPairUi.Open(entry);
|
_selectGroupForPairUi.Open(entry);
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Choose pair groups for " + entryUID);
|
UiSharedService.AttachToolTip(L("UserPair.Menu.PairGroups.Tooltip", "Choose pair groups for {0}", entryUID));
|
||||||
|
|
||||||
var isDisableSounds = entry.UserPair!.OwnPermissions.IsDisableSounds();
|
var isDisableSounds = entry.UserPair!.OwnPermissions.IsDisableSounds();
|
||||||
string disableSoundsText = isDisableSounds ? "Enable sound sync" : "Disable sound sync";
|
string disableSoundsText = isDisableSounds
|
||||||
|
? L("UserPair.Menu.EnableSoundSync", "Enable sound sync")
|
||||||
|
: L("UserPair.Menu.DisableSoundSync", "Disable sound sync");
|
||||||
var disableSoundsIcon = isDisableSounds ? FontAwesomeIcon.VolumeUp : FontAwesomeIcon.VolumeMute;
|
var disableSoundsIcon = isDisableSounds ? FontAwesomeIcon.VolumeUp : FontAwesomeIcon.VolumeMute;
|
||||||
if (_uiSharedService.IconTextButton(disableSoundsIcon, disableSoundsText))
|
if (_uiSharedService.IconTextButton(disableSoundsIcon, disableSoundsText))
|
||||||
{
|
{
|
||||||
@@ -278,7 +310,9 @@ public class DrawUserPair : DrawPairBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isDisableAnims = entry.UserPair!.OwnPermissions.IsDisableAnimations();
|
var isDisableAnims = entry.UserPair!.OwnPermissions.IsDisableAnimations();
|
||||||
string disableAnimsText = isDisableAnims ? "Enable animation sync" : "Disable animation sync";
|
string disableAnimsText = isDisableAnims
|
||||||
|
? L("UserPair.Menu.EnableAnimationSync", "Enable animation sync")
|
||||||
|
: L("UserPair.Menu.DisableAnimationSync", "Disable animation sync");
|
||||||
var disableAnimsIcon = isDisableAnims ? FontAwesomeIcon.Running : FontAwesomeIcon.Stop;
|
var disableAnimsIcon = isDisableAnims ? FontAwesomeIcon.Running : FontAwesomeIcon.Stop;
|
||||||
if (_uiSharedService.IconTextButton(disableAnimsIcon, disableAnimsText))
|
if (_uiSharedService.IconTextButton(disableAnimsIcon, disableAnimsText))
|
||||||
{
|
{
|
||||||
@@ -288,7 +322,9 @@ public class DrawUserPair : DrawPairBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isDisableVFX = entry.UserPair!.OwnPermissions.IsDisableVFX();
|
var isDisableVFX = entry.UserPair!.OwnPermissions.IsDisableVFX();
|
||||||
string disableVFXText = isDisableVFX ? "Enable VFX sync" : "Disable VFX sync";
|
string disableVFXText = isDisableVFX
|
||||||
|
? L("UserPair.Menu.EnableVfxSync", "Enable VFX sync")
|
||||||
|
: L("UserPair.Menu.DisableVfxSync", "Disable VFX sync");
|
||||||
var disableVFXIcon = isDisableVFX ? FontAwesomeIcon.Sun : FontAwesomeIcon.Circle;
|
var disableVFXIcon = isDisableVFX ? FontAwesomeIcon.Sun : FontAwesomeIcon.Circle;
|
||||||
if (_uiSharedService.IconTextButton(disableVFXIcon, disableVFXText))
|
if (_uiSharedService.IconTextButton(disableVFXIcon, disableVFXText))
|
||||||
{
|
{
|
||||||
@@ -297,10 +333,10 @@ public class DrawUserPair : DrawPairBase
|
|||||||
_ = _apiController.UserSetPairPermissions(new UserPermissionsDto(entry.UserData, permissions));
|
_ = _apiController.UserSetPairPermissions(new UserPermissionsDto(entry.UserData, permissions));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Unpair Permanently") && UiSharedService.CtrlPressed())
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, L("UserPair.Menu.Unpair", "Unpair Permanently")) && UiSharedService.CtrlPressed())
|
||||||
{
|
{
|
||||||
_ = _apiController.UserRemovePair(new(entry.UserData));
|
_ = _apiController.UserRemovePair(new(entry.UserData));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Hold CTRL and click to unpair permanently from " + entryUID);
|
UiSharedService.AttachToolTip(L("UserPair.Menu.Unpair.Tooltip", "Hold CTRL and click to unpair permanently from {0}", entryUID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
@@ -17,6 +17,8 @@ using MareSynchronos.Services.ServerConfiguration;
|
|||||||
using MareSynchronos.UI.Components;
|
using MareSynchronos.UI.Components;
|
||||||
using MareSynchronos.UI.Handlers;
|
using MareSynchronos.UI.Handlers;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
|
using MareSynchronos.Localization;
|
||||||
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -40,6 +42,7 @@ internal sealed class GroupPanel
|
|||||||
private string _editGroupComment = string.Empty;
|
private string _editGroupComment = string.Empty;
|
||||||
private string _editGroupEntry = string.Empty;
|
private string _editGroupEntry = string.Empty;
|
||||||
private bool _errorGroupCreate = false;
|
private bool _errorGroupCreate = false;
|
||||||
|
private string _errorGroupCreateMessage = string.Empty;
|
||||||
private bool _errorGroupJoin;
|
private bool _errorGroupJoin;
|
||||||
private bool _isPasswordValid;
|
private bool _isPasswordValid;
|
||||||
private GroupPasswordDto? _lastCreatedGroup = null;
|
private GroupPasswordDto? _lastCreatedGroup = null;
|
||||||
@@ -52,9 +55,31 @@ internal sealed class GroupPanel
|
|||||||
private bool _showModalChangePassword;
|
private bool _showModalChangePassword;
|
||||||
private bool _showModalCreateGroup;
|
private bool _showModalCreateGroup;
|
||||||
private bool _showModalEnterPassword;
|
private bool _showModalEnterPassword;
|
||||||
|
private string _newSyncShellAlias = string.Empty;
|
||||||
|
private bool _createIsTemporary = false;
|
||||||
|
private int _tempSyncshellDurationHours = 24;
|
||||||
|
private readonly int[] _temporaryDurationOptions = new[]
|
||||||
|
{
|
||||||
|
1,
|
||||||
|
12,
|
||||||
|
24,
|
||||||
|
48,
|
||||||
|
72,
|
||||||
|
96,
|
||||||
|
120,
|
||||||
|
144,
|
||||||
|
168
|
||||||
|
};
|
||||||
private string _syncShellPassword = string.Empty;
|
private string _syncShellPassword = string.Empty;
|
||||||
private string _syncShellToJoin = string.Empty;
|
private string _syncShellToJoin = string.Empty;
|
||||||
|
|
||||||
|
private static string L(string key, string fallback, params object[] args)
|
||||||
|
{
|
||||||
|
var safeArgs = args ?? Array.Empty<object>();
|
||||||
|
return LocalizationService.Instance?.GetString(key, fallback, safeArgs)
|
||||||
|
?? string.Format(CultureInfo.CurrentCulture, fallback, safeArgs);
|
||||||
|
}
|
||||||
|
|
||||||
public GroupPanel(CompactUi mainUi, UiSharedService uiShared, PairManager pairManager, ChatService chatServivce,
|
public GroupPanel(CompactUi mainUi, UiSharedService uiShared, PairManager pairManager, ChatService chatServivce,
|
||||||
UidDisplayHandler uidDisplayHandler, MareConfigService mareConfig, ServerConfigurationManager serverConfigurationManager,
|
UidDisplayHandler uidDisplayHandler, MareConfigService mareConfig, ServerConfigurationManager serverConfigurationManager,
|
||||||
CharaDataManager charaDataManager)
|
CharaDataManager charaDataManager)
|
||||||
@@ -82,13 +107,15 @@ internal sealed class GroupPanel
|
|||||||
{
|
{
|
||||||
var buttonSize = _uiShared.GetIconButtonSize(FontAwesomeIcon.Plus);
|
var buttonSize = _uiShared.GetIconButtonSize(FontAwesomeIcon.Plus);
|
||||||
ImGui.SetNextItemWidth(UiSharedService.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X);
|
ImGui.SetNextItemWidth(UiSharedService.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X);
|
||||||
ImGui.InputTextWithHint("##syncshellid", "Syncshell GID/Alias (leave empty to create)", ref _syncShellToJoin, 20);
|
ImGui.InputTextWithHint("##syncshellid", L("GroupPanel.Join.InputHint", "Syncshell GID/Alias (leave empty to create)"), ref _syncShellToJoin, 50);
|
||||||
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - buttonSize.X);
|
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - buttonSize.X);
|
||||||
|
|
||||||
bool userCanJoinMoreGroups = _pairManager.GroupPairs.Count < ApiController.ServerInfo.MaxGroupsJoinedByUser;
|
bool userCanJoinMoreGroups = _pairManager.GroupPairs.Count < ApiController.ServerInfo.MaxGroupsJoinedByUser;
|
||||||
bool userCanCreateMoreGroups = _pairManager.GroupPairs.Count(u => string.Equals(u.Key.Owner.UID, ApiController.UID, StringComparison.Ordinal)) < ApiController.ServerInfo.MaxGroupsCreatedByUser;
|
bool userCanCreateMoreGroups = _pairManager.GroupPairs.Count(u => string.Equals(u.Key.Owner.UID, ApiController.UID, StringComparison.Ordinal)) < ApiController.ServerInfo.MaxGroupsCreatedByUser;
|
||||||
bool alreadyInGroup = _pairManager.GroupPairs.Select(p => p.Key).Any(p => string.Equals(p.Group.Alias, _syncShellToJoin, StringComparison.Ordinal)
|
bool alreadyInGroup = _pairManager.GroupPairs.Select(p => p.Key).Any(p => string.Equals(p.Group.Alias, _syncShellToJoin, StringComparison.Ordinal)
|
||||||
|| string.Equals(p.Group.GID, _syncShellToJoin, StringComparison.Ordinal));
|
|| string.Equals(p.Group.GID, _syncShellToJoin, StringComparison.Ordinal));
|
||||||
|
var enterPasswordPopupTitle = L("GroupPanel.Join.PasswordPopup", "Enter Syncshell Password");
|
||||||
|
var createPopupTitle = L("GroupPanel.Create.PopupTitle", "Create Syncshell");
|
||||||
|
|
||||||
if (alreadyInGroup) ImGui.BeginDisabled();
|
if (alreadyInGroup) ImGui.BeginDisabled();
|
||||||
if (_uiShared.IconButton(FontAwesomeIcon.Plus))
|
if (_uiShared.IconButton(FontAwesomeIcon.Plus))
|
||||||
@@ -99,7 +126,7 @@ internal sealed class GroupPanel
|
|||||||
{
|
{
|
||||||
_errorGroupJoin = false;
|
_errorGroupJoin = false;
|
||||||
_showModalEnterPassword = true;
|
_showModalEnterPassword = true;
|
||||||
ImGui.OpenPopup("Enter Syncshell Password");
|
ImGui.OpenPopup(enterPasswordPopupTitle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -108,31 +135,42 @@ internal sealed class GroupPanel
|
|||||||
{
|
{
|
||||||
_lastCreatedGroup = null;
|
_lastCreatedGroup = null;
|
||||||
_errorGroupCreate = false;
|
_errorGroupCreate = false;
|
||||||
|
_newSyncShellAlias = string.Empty;
|
||||||
|
_createIsTemporary = false;
|
||||||
|
_tempSyncshellDurationHours = 24;
|
||||||
|
_errorGroupCreateMessage = string.Empty;
|
||||||
_showModalCreateGroup = true;
|
_showModalCreateGroup = true;
|
||||||
ImGui.OpenPopup("Create Syncshell");
|
ImGui.OpenPopup(createPopupTitle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip(_syncShellToJoin.IsNullOrEmpty()
|
UiSharedService.AttachToolTip(_syncShellToJoin.IsNullOrEmpty()
|
||||||
? (userCanCreateMoreGroups ? "Create Syncshell" : $"You cannot create more than {ApiController.ServerInfo.MaxGroupsCreatedByUser} Syncshells")
|
? (userCanCreateMoreGroups
|
||||||
: (userCanJoinMoreGroups ? "Join Syncshell" + _syncShellToJoin : $"You cannot join more than {ApiController.ServerInfo.MaxGroupsJoinedByUser} Syncshells"));
|
? L("GroupPanel.Create.Tooltip", "Create Syncshell")
|
||||||
|
: L("GroupPanel.Create.TooMany", "You cannot create more than {0} Syncshells", ApiController.ServerInfo.MaxGroupsCreatedByUser))
|
||||||
|
: (userCanJoinMoreGroups
|
||||||
|
? L("GroupPanel.Join.Tooltip", "Join Syncshell {0}", _syncShellToJoin)
|
||||||
|
: L("GroupPanel.Join.TooMany", "You cannot join more than {0} Syncshells", ApiController.ServerInfo.MaxGroupsJoinedByUser)));
|
||||||
|
|
||||||
if (alreadyInGroup) ImGui.EndDisabled();
|
if (alreadyInGroup) ImGui.EndDisabled();
|
||||||
|
|
||||||
if (ImGui.BeginPopupModal("Enter Syncshell Password", ref _showModalEnterPassword, UiSharedService.PopupWindowFlags))
|
if (ImGui.BeginPopupModal(enterPasswordPopupTitle, ref _showModalEnterPassword, UiSharedService.PopupWindowFlags))
|
||||||
{
|
{
|
||||||
UiSharedService.TextWrapped("Before joining any Syncshells please be aware that you will be automatically paired with everyone in the Syncshell.");
|
UiSharedService.TextWrapped(L("GroupPanel.Join.Warning", "Before joining any Syncshells please be aware that you will be automatically paired with everyone in the Syncshell."));
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
UiSharedService.TextWrapped("Enter the password for Syncshell " + _syncShellToJoin + ":");
|
UiSharedService.TextWrapped(L("GroupPanel.Join.EnterPassword", "Enter the password for Syncshell {0}:", _syncShellToJoin));
|
||||||
ImGui.SetNextItemWidth(-1);
|
ImGui.SetNextItemWidth(-1);
|
||||||
ImGui.InputTextWithHint("##password", _syncShellToJoin + " Password", ref _syncShellPassword, 255, ImGuiInputTextFlags.Password);
|
ImGui.InputTextWithHint("##password", L("GroupPanel.Join.PasswordHint", "{0} Password", _syncShellToJoin), ref _syncShellPassword, 255, ImGuiInputTextFlags.Password);
|
||||||
if (_errorGroupJoin)
|
if (_errorGroupJoin)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped($"An error occured during joining of this Syncshell: you either have joined the maximum amount of Syncshells ({ApiController.ServerInfo.MaxGroupsJoinedByUser}), " +
|
UiSharedService.ColorTextWrapped(
|
||||||
$"it does not exist, the password you entered is wrong, you already joined the Syncshell, the Syncshell is full ({ApiController.ServerInfo.MaxGroupUserCount} users) or the Syncshell has closed invites.",
|
L("GroupPanel.Join.Error",
|
||||||
|
"An error occured during joining of this Syncshell: you either have joined the maximum amount of Syncshells ({0}), it does not exist, the password you entered is wrong, you already joined the Syncshell, the Syncshell is full ({1} users) or the Syncshell has closed invites.",
|
||||||
|
ApiController.ServerInfo.MaxGroupsJoinedByUser,
|
||||||
|
ApiController.ServerInfo.MaxGroupUserCount),
|
||||||
new Vector4(1, 0, 0, 1));
|
new Vector4(1, 0, 0, 1));
|
||||||
}
|
}
|
||||||
if (ImGui.Button("Join " + _syncShellToJoin))
|
if (ImGui.Button(L("GroupPanel.Join.Button", "Join {0}", _syncShellToJoin)))
|
||||||
{
|
{
|
||||||
var shell = _syncShellToJoin;
|
var shell = _syncShellToJoin;
|
||||||
var pw = _syncShellPassword;
|
var pw = _syncShellPassword;
|
||||||
@@ -148,20 +186,100 @@ internal sealed class GroupPanel
|
|||||||
ImGui.EndPopup();
|
ImGui.EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginPopupModal("Create Syncshell", ref _showModalCreateGroup, UiSharedService.PopupWindowFlags))
|
if (ImGui.BeginPopupModal(createPopupTitle, ref _showModalCreateGroup, UiSharedService.PopupWindowFlags))
|
||||||
{
|
{
|
||||||
UiSharedService.TextWrapped("Press the button below to create a new Syncshell.");
|
UiSharedService.TextWrapped(L("GroupPanel.Create.ChooseType", "Choisissez le type de Syncshell à créer."));
|
||||||
|
bool showPermanent = !_createIsTemporary;
|
||||||
|
if (ImGui.RadioButton(L("GroupPanel.Create.Permanent", "Permanente"), showPermanent))
|
||||||
|
{
|
||||||
|
_createIsTemporary = false;
|
||||||
|
}
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.RadioButton(L("GroupPanel.Create.Temporary", "Temporaire"), _createIsTemporary))
|
||||||
|
{
|
||||||
|
_createIsTemporary = true;
|
||||||
|
_newSyncShellAlias = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_createIsTemporary)
|
||||||
|
{
|
||||||
|
UiSharedService.TextWrapped(L("GroupPanel.Create.AliasPrompt", "Donnez un nom à votre Syncshell (optionnel) puis créez-la."));
|
||||||
|
ImGui.SetNextItemWidth(-1);
|
||||||
|
ImGui.InputTextWithHint("##syncshellalias", L("GroupPanel.Create.AliasHint", "Nom du Syncshell"), ref _newSyncShellAlias, 50);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_newSyncShellAlias = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_createIsTemporary)
|
||||||
|
{
|
||||||
|
UiSharedService.TextWrapped(L("GroupPanel.Create.TempMaxDuration", "Durée maximale d'une Syncshell temporaire : 7 jours."));
|
||||||
|
if (_tempSyncshellDurationHours > 168) _tempSyncshellDurationHours = 168;
|
||||||
|
for (int i = 0; i < _temporaryDurationOptions.Length; i++)
|
||||||
|
{
|
||||||
|
var option = _temporaryDurationOptions[i];
|
||||||
|
var isSelected = _tempSyncshellDurationHours == option;
|
||||||
|
string label = option switch
|
||||||
|
{
|
||||||
|
>= 24 when option % 24 == 0 => option == 24
|
||||||
|
? L("GroupPanel.Create.Duration.SingleDay", "24h")
|
||||||
|
: L("GroupPanel.Create.Duration.Days", "{0}j", option / 24),
|
||||||
|
_ => L("GroupPanel.Create.Duration.Hours", "{0}h", option)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ImGui.RadioButton(label, isSelected))
|
||||||
|
{
|
||||||
|
_tempSyncshellDurationHours = option;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((i + 1) % 3 == 0)
|
||||||
|
{
|
||||||
|
ImGui.NewLine();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiresLocal = DateTime.Now.AddHours(_tempSyncshellDurationHours);
|
||||||
|
UiSharedService.TextWrapped(L("GroupPanel.Create.TempExpires", "Expiration le {0:g} (heure locale).", expiresLocal));
|
||||||
|
}
|
||||||
|
|
||||||
|
UiSharedService.TextWrapped(L("GroupPanel.Create.Instruction", "Appuyez sur le bouton ci-dessous pour créer une nouvelle Syncshell."));
|
||||||
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||||
if (ImGui.Button("Create Syncshell"))
|
if (ImGui.Button(L("GroupPanel.Create.Button", "Create Syncshell")))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_lastCreatedGroup = ApiController.GroupCreate().Result;
|
if (_createIsTemporary)
|
||||||
|
{
|
||||||
|
var expiresAtUtc = DateTime.UtcNow.AddHours(_tempSyncshellDurationHours);
|
||||||
|
_lastCreatedGroup = ApiController.GroupCreateTemporary(expiresAtUtc).Result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var aliasInput = string.IsNullOrWhiteSpace(_newSyncShellAlias) ? null : _newSyncShellAlias.Trim();
|
||||||
|
_lastCreatedGroup = ApiController.GroupCreate(aliasInput).Result;
|
||||||
|
if (_lastCreatedGroup != null)
|
||||||
|
{
|
||||||
|
_newSyncShellAlias = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_lastCreatedGroup = null;
|
_lastCreatedGroup = null;
|
||||||
_errorGroupCreate = true;
|
_errorGroupCreate = true;
|
||||||
|
if (ex.Message.Contains("name is already in use", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_errorGroupCreateMessage = L("GroupPanel.Create.Error.NameInUse", "Le nom de la Syncshell est déjà utilisé.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_errorGroupCreateMessage = ex.Message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,21 +287,33 @@ internal sealed class GroupPanel
|
|||||||
{
|
{
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
_errorGroupCreate = false;
|
_errorGroupCreate = false;
|
||||||
ImGui.TextUnformatted("Syncshell ID: " + _lastCreatedGroup.Group.GID);
|
_errorGroupCreateMessage = string.Empty;
|
||||||
|
if (!string.IsNullOrWhiteSpace(_lastCreatedGroup.Group.Alias))
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(L("GroupPanel.Create.Result.Name", "Syncshell Name: {0}", _lastCreatedGroup.Group.Alias));
|
||||||
|
}
|
||||||
|
ImGui.TextUnformatted(L("GroupPanel.Create.Result.Id", "Syncshell ID: {0}", _lastCreatedGroup.Group.GID));
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted("Syncshell Password: " + _lastCreatedGroup.Password);
|
ImGui.TextUnformatted(L("GroupPanel.Create.Result.Password", "Syncshell Password: {0}", _lastCreatedGroup.Password));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (_uiShared.IconButton(FontAwesomeIcon.Copy))
|
if (_uiShared.IconButton(FontAwesomeIcon.Copy))
|
||||||
{
|
{
|
||||||
ImGui.SetClipboardText(_lastCreatedGroup.Password);
|
ImGui.SetClipboardText(_lastCreatedGroup.Password);
|
||||||
}
|
}
|
||||||
UiSharedService.TextWrapped("You can change the Syncshell password later at any time.");
|
UiSharedService.TextWrapped(L("GroupPanel.Create.Result.ChangeLater", "You can change the Syncshell password later at any time."));
|
||||||
|
if (_lastCreatedGroup.IsTemporary && _lastCreatedGroup.ExpiresAt != null)
|
||||||
|
{
|
||||||
|
var expiresLocal = _lastCreatedGroup.ExpiresAt.Value.ToLocalTime();
|
||||||
|
UiSharedService.TextWrapped(L("GroupPanel.Create.Result.TempExpires", "Cette Syncshell expirera le {0:g} (heure locale).", expiresLocal));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_errorGroupCreate)
|
if (_errorGroupCreate)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped("You are already owner of the maximum amount of Syncshells (3) or joined the maximum amount of Syncshells (6). Relinquish ownership of your own Syncshells to someone else or leave existing Syncshells.",
|
var msg = string.IsNullOrWhiteSpace(_errorGroupCreateMessage)
|
||||||
new Vector4(1, 0, 0, 1));
|
? L("GroupPanel.Create.Error.Generic", "You are already owner of the maximum amount of Syncshells (3) or joined the maximum amount of Syncshells (6). Relinquish ownership of your own Syncshells to someone else or leave existing Syncshells.")
|
||||||
|
: _errorGroupCreateMessage;
|
||||||
|
UiSharedService.ColorTextWrapped(msg, new Vector4(1, 0, 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
UiSharedService.SetScaledWindowSize(350);
|
UiSharedService.SetScaledWindowSize(350);
|
||||||
@@ -220,7 +350,7 @@ internal sealed class GroupPanel
|
|||||||
ImGui.PushFont(UiBuilder.IconFont);
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
ImGui.TextUnformatted(FontAwesomeIcon.Crown.ToIconString());
|
ImGui.TextUnformatted(FontAwesomeIcon.Crown.ToIconString());
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
UiSharedService.AttachToolTip("You are the owner of Syncshell " + groupName);
|
UiSharedService.AttachToolTip(L("GroupPanel.Syncshell.OwnerTooltip", "You are the owner of Syncshell {0}", groupName));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
else if (groupDto.GroupUserInfo.IsModerator())
|
else if (groupDto.GroupUserInfo.IsModerator())
|
||||||
@@ -228,7 +358,7 @@ internal sealed class GroupPanel
|
|||||||
ImGui.PushFont(UiBuilder.IconFont);
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
ImGui.TextUnformatted(FontAwesomeIcon.UserShield.ToIconString());
|
ImGui.TextUnformatted(FontAwesomeIcon.UserShield.ToIconString());
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
UiSharedService.AttachToolTip("You are a moderator of Syncshell " + groupName);
|
UiSharedService.AttachToolTip(L("GroupPanel.Syncshell.ModeratorTooltip", "You are a moderator of Syncshell {0}", groupName));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,18 +373,38 @@ internal sealed class GroupPanel
|
|||||||
if (!string.Equals(_editGroupEntry, groupDto.GID, StringComparison.Ordinal))
|
if (!string.Equals(_editGroupEntry, groupDto.GID, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
var shellConfig = _serverConfigurationManager.GetShellConfigForGid(groupDto.GID);
|
var shellConfig = _serverConfigurationManager.GetShellConfigForGid(groupDto.GID);
|
||||||
if (!_mareConfig.Current.DisableSyncshellChat && shellConfig.Enabled)
|
var totalMembers = pairsInGroup.Count + 1;
|
||||||
{
|
var connectedMembers = pairsInGroup.Count(p => p.IsOnline) + 1;
|
||||||
ImGui.TextUnformatted($"[{shellNumber}]");
|
var maxCapacity = ApiController.ServerInfo.MaxGroupUserCount;
|
||||||
UiSharedService.AttachToolTip("Chat command prefix: /ss" + shellNumber);
|
ImGui.TextUnformatted(L("GroupPanel.Syncshell.MemberCount", "{0}/{1}", connectedMembers, totalMembers));
|
||||||
}
|
UiSharedService.AttachToolTip(
|
||||||
|
L("GroupPanel.Syncshell.MemberCountTooltip",
|
||||||
|
"Membres connectés / membres totaux\nCapacité maximale : {0}\nSyncshell ID: {1}",
|
||||||
|
maxCapacity,
|
||||||
|
groupDto.Group.GID));
|
||||||
if (textIsGid) ImGui.PushFont(UiBuilder.MonoFont);
|
if (textIsGid) ImGui.PushFont(UiBuilder.MonoFont);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(groupName);
|
ImGui.TextUnformatted(groupName);
|
||||||
if (textIsGid) ImGui.PopFont();
|
if (textIsGid) ImGui.PopFont();
|
||||||
UiSharedService.AttachToolTip("Left click to switch between GID display and comment" + Environment.NewLine +
|
UiSharedService.AttachToolTip(L("GroupPanel.Syncshell.NameTooltip",
|
||||||
"Right click to change comment for " + groupName + Environment.NewLine + Environment.NewLine
|
"Left click to switch between GID display and comment\nRight click to change comment for {0}\n\nUsers: {1}, Owner: {2}",
|
||||||
+ "Users: " + (pairsInGroup.Count + 1) + ", Owner: " + groupDto.OwnerAliasOrUID);
|
groupName,
|
||||||
|
pairsInGroup.Count + 1,
|
||||||
|
groupDto.OwnerAliasOrUID));
|
||||||
|
if (groupDto.IsTemporary)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
UiSharedService.ColorText(L("GroupPanel.Syncshell.TempTag", "(Temp)"), ImGuiColors.DalamudOrange);
|
||||||
|
if (groupDto.ExpiresAt != null)
|
||||||
|
{
|
||||||
|
var tempExpireLocal = groupDto.ExpiresAt.Value.ToLocalTime();
|
||||||
|
UiSharedService.AttachToolTip(L("GroupPanel.Syncshell.TempExpires", "Expire le {0:g}", tempExpireLocal));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UiSharedService.AttachToolTip(L("GroupPanel.Syncshell.TempTooltip", "Syncshell temporaire"));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||||
{
|
{
|
||||||
var prevState = textIsGid;
|
var prevState = textIsGid;
|
||||||
@@ -278,7 +428,7 @@ internal sealed class GroupPanel
|
|||||||
{
|
{
|
||||||
var buttonSizes = _uiShared.GetIconButtonSize(FontAwesomeIcon.Bars).X + _uiShared.GetIconButtonSize(FontAwesomeIcon.LockOpen).X;
|
var buttonSizes = _uiShared.GetIconButtonSize(FontAwesomeIcon.Bars).X + _uiShared.GetIconButtonSize(FontAwesomeIcon.LockOpen).X;
|
||||||
ImGui.SetNextItemWidth(UiSharedService.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * 2);
|
ImGui.SetNextItemWidth(UiSharedService.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * 2);
|
||||||
if (ImGui.InputTextWithHint("", "Comment/Notes", ref _editGroupComment, 255, ImGuiInputTextFlags.EnterReturnsTrue))
|
if (ImGui.InputTextWithHint("", L("GroupPanel.CommentHint", "Comment/Notes"), ref _editGroupComment, 255, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
{
|
{
|
||||||
_serverConfigurationManager.SetNoteForGid(groupDto.GID, _editGroupComment);
|
_serverConfigurationManager.SetNoteForGid(groupDto.GID, _editGroupComment);
|
||||||
_editGroupEntry = string.Empty;
|
_editGroupEntry = string.Empty;
|
||||||
@@ -289,7 +439,7 @@ internal sealed class GroupPanel
|
|||||||
{
|
{
|
||||||
_editGroupEntry = string.Empty;
|
_editGroupEntry = string.Empty;
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Hit ENTER to save\nRight click to cancel");
|
UiSharedService.AttachToolTip(L("GroupPanel.CommentTooltip", "Hit ENTER to save\nRight click to cancel"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -298,26 +448,26 @@ internal sealed class GroupPanel
|
|||||||
if (_showModalBanList && !_modalBanListOpened)
|
if (_showModalBanList && !_modalBanListOpened)
|
||||||
{
|
{
|
||||||
_modalBanListOpened = true;
|
_modalBanListOpened = true;
|
||||||
ImGui.OpenPopup("Manage Banlist for " + groupDto.GID);
|
ImGui.OpenPopup(L("GroupPanel.Banlist.Title", "Manage Banlist for {0}", groupDto.GID));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_showModalBanList) _modalBanListOpened = false;
|
if (!_showModalBanList) _modalBanListOpened = false;
|
||||||
|
|
||||||
if (ImGui.BeginPopupModal("Manage Banlist for " + groupDto.GID, ref _showModalBanList, UiSharedService.PopupWindowFlags))
|
if (ImGui.BeginPopupModal(L("GroupPanel.Banlist.Title", "Manage Banlist for {0}", groupDto.GID), ref _showModalBanList, UiSharedService.PopupWindowFlags))
|
||||||
{
|
{
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Retweet, "Refresh Banlist from Server"))
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Retweet, L("GroupPanel.Banlist.Refresh", "Refresh Banlist from Server")))
|
||||||
{
|
{
|
||||||
_bannedUsers = ApiController.GroupGetBannedUsers(groupDto).Result;
|
_bannedUsers = ApiController.GroupGetBannedUsers(groupDto).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginTable("bannedusertable" + groupDto.GID, 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.ScrollY))
|
if (ImGui.BeginTable("bannedusertable" + groupDto.GID, 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.ScrollY))
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("UID", ImGuiTableColumnFlags.None, 1);
|
ImGui.TableSetupColumn(L("GroupPanel.Banlist.Column.Uid", "UID"), ImGuiTableColumnFlags.None, 1);
|
||||||
ImGui.TableSetupColumn("Alias", ImGuiTableColumnFlags.None, 1);
|
ImGui.TableSetupColumn(L("GroupPanel.Banlist.Column.Alias", "Alias"), ImGuiTableColumnFlags.None, 1);
|
||||||
ImGui.TableSetupColumn("By", ImGuiTableColumnFlags.None, 1);
|
ImGui.TableSetupColumn(L("GroupPanel.Banlist.Column.By", "By"), ImGuiTableColumnFlags.None, 1);
|
||||||
ImGui.TableSetupColumn("Date", ImGuiTableColumnFlags.None, 2);
|
ImGui.TableSetupColumn(L("GroupPanel.Banlist.Column.Date", "Date"), ImGuiTableColumnFlags.None, 2);
|
||||||
ImGui.TableSetupColumn("Reason", ImGuiTableColumnFlags.None, 3);
|
ImGui.TableSetupColumn(L("GroupPanel.Banlist.Column.Reason", "Reason"), ImGuiTableColumnFlags.None, 3);
|
||||||
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 1);
|
ImGui.TableSetupColumn(L("GroupPanel.Banlist.Column.Actions", "Actions"), ImGuiTableColumnFlags.None, 1);
|
||||||
|
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
@@ -334,7 +484,7 @@ internal sealed class GroupPanel
|
|||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
UiSharedService.TextWrapped(bannedUser.Reason);
|
UiSharedService.TextWrapped(bannedUser.Reason);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Check, "Unban#" + bannedUser.UID))
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Check, L("GroupPanel.Banlist.Unban", "Unban") + "#" + bannedUser.UID))
|
||||||
{
|
{
|
||||||
_ = ApiController.GroupUnbanUser(bannedUser);
|
_ = ApiController.GroupUnbanUser(bannedUser);
|
||||||
_bannedUsers.RemoveAll(b => string.Equals(b.UID, bannedUser.UID, StringComparison.Ordinal));
|
_bannedUsers.RemoveAll(b => string.Equals(b.UID, bannedUser.UID, StringComparison.Ordinal));
|
||||||
@@ -350,18 +500,18 @@ internal sealed class GroupPanel
|
|||||||
if (_showModalChangePassword && !_modalChangePwOpened)
|
if (_showModalChangePassword && !_modalChangePwOpened)
|
||||||
{
|
{
|
||||||
_modalChangePwOpened = true;
|
_modalChangePwOpened = true;
|
||||||
ImGui.OpenPopup("Change Syncshell Password");
|
ImGui.OpenPopup(L("GroupPanel.Password.Title", "Change Syncshell Password"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_showModalChangePassword) _modalChangePwOpened = false;
|
if (!_showModalChangePassword) _modalChangePwOpened = false;
|
||||||
|
|
||||||
if (ImGui.BeginPopupModal("Change Syncshell Password", ref _showModalChangePassword, UiSharedService.PopupWindowFlags))
|
if (ImGui.BeginPopupModal(L("GroupPanel.Password.Title", "Change Syncshell Password"), ref _showModalChangePassword, UiSharedService.PopupWindowFlags))
|
||||||
{
|
{
|
||||||
UiSharedService.TextWrapped("Enter the new Syncshell password for Syncshell " + name + " here.");
|
UiSharedService.TextWrapped(L("GroupPanel.Password.Description", "Enter the new Syncshell password for Syncshell {0} here.", name));
|
||||||
UiSharedService.TextWrapped("This action is irreversible");
|
UiSharedService.TextWrapped(L("GroupPanel.Password.Warning", "This action is irreversible"));
|
||||||
ImGui.SetNextItemWidth(-1);
|
ImGui.SetNextItemWidth(-1);
|
||||||
ImGui.InputTextWithHint("##changepw", "New password for " + name, ref _newSyncShellPassword, 255);
|
ImGui.InputTextWithHint("##changepw", L("GroupPanel.Password.Hint", "New password for {0}", name), ref _newSyncShellPassword, 255);
|
||||||
if (ImGui.Button("Change password"))
|
if (ImGui.Button(L("GroupPanel.Password.Button", "Change password")))
|
||||||
{
|
{
|
||||||
var pw = _newSyncShellPassword;
|
var pw = _newSyncShellPassword;
|
||||||
_isPasswordValid = ApiController.GroupChangePassword(new(groupDto.Group, pw)).Result;
|
_isPasswordValid = ApiController.GroupChangePassword(new(groupDto.Group, pw)).Result;
|
||||||
@@ -371,7 +521,7 @@ internal sealed class GroupPanel
|
|||||||
|
|
||||||
if (!_isPasswordValid)
|
if (!_isPasswordValid)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped("The selected password is too short. It must be at least 10 characters.", new Vector4(1, 0, 0, 1));
|
UiSharedService.ColorTextWrapped(L("GroupPanel.Password.Error.TooShort", "The selected password is too short. It must be at least 10 characters."), new Vector4(1, 0, 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
UiSharedService.SetScaledWindowSize(290);
|
UiSharedService.SetScaledWindowSize(290);
|
||||||
@@ -381,29 +531,28 @@ internal sealed class GroupPanel
|
|||||||
if (_showModalBulkOneTimeInvites && !_modalBulkOneTimeInvitesOpened)
|
if (_showModalBulkOneTimeInvites && !_modalBulkOneTimeInvitesOpened)
|
||||||
{
|
{
|
||||||
_modalBulkOneTimeInvitesOpened = true;
|
_modalBulkOneTimeInvitesOpened = true;
|
||||||
ImGui.OpenPopup("Create Bulk One-Time Invites");
|
ImGui.OpenPopup(L("GroupPanel.Invites.Title", "Create Bulk One-Time Invites"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_showModalBulkOneTimeInvites) _modalBulkOneTimeInvitesOpened = false;
|
if (!_showModalBulkOneTimeInvites) _modalBulkOneTimeInvitesOpened = false;
|
||||||
|
|
||||||
if (ImGui.BeginPopupModal("Create Bulk One-Time Invites", ref _showModalBulkOneTimeInvites, UiSharedService.PopupWindowFlags))
|
if (ImGui.BeginPopupModal(L("GroupPanel.Invites.Title", "Create Bulk One-Time Invites"), ref _showModalBulkOneTimeInvites, UiSharedService.PopupWindowFlags))
|
||||||
{
|
{
|
||||||
UiSharedService.TextWrapped("This allows you to create up to 100 one-time invites at once for the Syncshell " + name + "." + Environment.NewLine
|
UiSharedService.TextWrapped(L("GroupPanel.Invites.Description", "This allows you to create up to 100 one-time invites at once for the Syncshell {0}.\nThe invites are valid for 24h after creation and will automatically expire.", name));
|
||||||
+ "The invites are valid for 24h after creation and will automatically expire.");
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
if (_bulkOneTimeInvites.Count == 0)
|
if (_bulkOneTimeInvites.Count == 0)
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth(-1);
|
ImGui.SetNextItemWidth(-1);
|
||||||
ImGui.SliderInt("Amount##bulkinvites", ref _bulkInviteCount, 1, 100);
|
ImGui.SliderInt(L("GroupPanel.Invites.AmountLabel", "Amount") + "##bulkinvites", ref _bulkInviteCount, 1, 100);
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.MailBulk, "Create invites"))
|
if (_uiShared.IconTextButton(FontAwesomeIcon.MailBulk, L("GroupPanel.Invites.CreateButton", "Create invites")))
|
||||||
{
|
{
|
||||||
_bulkOneTimeInvites = ApiController.GroupCreateTempInvite(groupDto, _bulkInviteCount).Result;
|
_bulkOneTimeInvites = ApiController.GroupCreateTempInvite(groupDto, _bulkInviteCount).Result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UiSharedService.TextWrapped("A total of " + _bulkOneTimeInvites.Count + " invites have been created.");
|
UiSharedService.TextWrapped(L("GroupPanel.Invites.Result", "A total of {0} invites have been created.", _bulkOneTimeInvites.Count));
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, "Copy invites to clipboard"))
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, L("GroupPanel.Invites.Copy", "Copy invites to clipboard")))
|
||||||
{
|
{
|
||||||
ImGui.SetClipboardText(string.Join(Environment.NewLine, _bulkOneTimeInvites));
|
ImGui.SetClipboardText(string.Join(Environment.NewLine, _bulkOneTimeInvites));
|
||||||
}
|
}
|
||||||
@@ -450,25 +599,27 @@ internal sealed class GroupPanel
|
|||||||
|
|
||||||
if (visibleUsers.Count > 0)
|
if (visibleUsers.Count > 0)
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("Visible");
|
ImGui.TextUnformatted(L("GroupPanel.List.Visible", "Visible"));
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
_uidDisplayHandler.RenderPairList(visibleUsers);
|
_uidDisplayHandler.RenderPairList(visibleUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onlineUsers.Count > 0)
|
if (onlineUsers.Count > 0)
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("Online");
|
ImGui.TextUnformatted(L("GroupPanel.List.Online", "Online"));
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
_uidDisplayHandler.RenderPairList(onlineUsers);
|
_uidDisplayHandler.RenderPairList(onlineUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offlineUsers.Count > 0)
|
if (offlineUsers.Count > 0)
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("Offline/Unknown");
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
|
||||||
|
ImGui.TextUnformatted(L("GroupPanel.List.Offline", "Offline/Unknown"));
|
||||||
|
ImGui.PopStyleColor();
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
if (hideOfflineUsers)
|
if (hideOfflineUsers)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorText($" {offlineUsers.Count} offline users omitted from display.", ImGuiColors.DalamudGrey);
|
UiSharedService.ColorText(L("GroupPanel.List.OfflineOmitted", " {0} offline users omitted from display.", offlineUsers.Count), ImGuiColors.DalamudGrey);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -523,11 +674,11 @@ internal sealed class GroupPanel
|
|||||||
ImGui.BeginTooltip();
|
ImGui.BeginTooltip();
|
||||||
if (!invitesEnabled || soundsDisabled || animDisabled || vfxDisabled)
|
if (!invitesEnabled || soundsDisabled || animDisabled || vfxDisabled)
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("Syncshell permissions");
|
ImGui.TextUnformatted(L("GroupPanel.Permissions.Header", "Syncshell permissions"));
|
||||||
|
|
||||||
if (!invitesEnabled)
|
if (!invitesEnabled)
|
||||||
{
|
{
|
||||||
var lockedText = "Syncshell is closed for joining";
|
var lockedText = L("GroupPanel.Permissions.InvitesDisabled", "Syncshell is closed for joining");
|
||||||
_uiShared.IconText(lockedIcon);
|
_uiShared.IconText(lockedIcon);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(lockedText);
|
ImGui.TextUnformatted(lockedText);
|
||||||
@@ -535,7 +686,7 @@ internal sealed class GroupPanel
|
|||||||
|
|
||||||
if (soundsDisabled)
|
if (soundsDisabled)
|
||||||
{
|
{
|
||||||
var soundsText = "Sound sync disabled through owner";
|
var soundsText = L("GroupPanel.Permissions.SoundDisabledOwner", "Sound sync disabled through owner");
|
||||||
_uiShared.IconText(soundsIcon);
|
_uiShared.IconText(soundsIcon);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(soundsText);
|
ImGui.TextUnformatted(soundsText);
|
||||||
@@ -543,7 +694,7 @@ internal sealed class GroupPanel
|
|||||||
|
|
||||||
if (animDisabled)
|
if (animDisabled)
|
||||||
{
|
{
|
||||||
var animText = "Animation sync disabled through owner";
|
var animText = L("GroupPanel.Permissions.AnimationDisabledOwner", "Animation sync disabled through owner");
|
||||||
_uiShared.IconText(animIcon);
|
_uiShared.IconText(animIcon);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(animText);
|
ImGui.TextUnformatted(animText);
|
||||||
@@ -551,7 +702,7 @@ internal sealed class GroupPanel
|
|||||||
|
|
||||||
if (vfxDisabled)
|
if (vfxDisabled)
|
||||||
{
|
{
|
||||||
var vfxText = "VFX sync disabled through owner";
|
var vfxText = L("GroupPanel.Permissions.VfxDisabledOwner", "VFX sync disabled through owner");
|
||||||
_uiShared.IconText(vfxIcon);
|
_uiShared.IconText(vfxIcon);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(vfxText);
|
ImGui.TextUnformatted(vfxText);
|
||||||
@@ -563,11 +714,11 @@ internal sealed class GroupPanel
|
|||||||
if (!invitesEnabled || soundsDisabled || animDisabled || vfxDisabled)
|
if (!invitesEnabled || soundsDisabled || animDisabled || vfxDisabled)
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
ImGui.TextUnformatted("Your permissions");
|
ImGui.TextUnformatted(L("GroupPanel.Permissions.OwnHeader", "Your permissions"));
|
||||||
|
|
||||||
if (userSoundsDisabled)
|
if (userSoundsDisabled)
|
||||||
{
|
{
|
||||||
var userSoundsText = "Sound sync disabled through you";
|
var userSoundsText = L("GroupPanel.Permissions.SoundDisabledSelf", "Sound sync disabled through you");
|
||||||
_uiShared.IconText(userSoundsIcon);
|
_uiShared.IconText(userSoundsIcon);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userSoundsText);
|
ImGui.TextUnformatted(userSoundsText);
|
||||||
@@ -575,7 +726,7 @@ internal sealed class GroupPanel
|
|||||||
|
|
||||||
if (userAnimDisabled)
|
if (userAnimDisabled)
|
||||||
{
|
{
|
||||||
var userAnimText = "Animation sync disabled through you";
|
var userAnimText = L("GroupPanel.Permissions.AnimationDisabledSelf", "Animation sync disabled through you");
|
||||||
_uiShared.IconText(userAnimIcon);
|
_uiShared.IconText(userAnimIcon);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userAnimText);
|
ImGui.TextUnformatted(userAnimText);
|
||||||
@@ -583,14 +734,14 @@ internal sealed class GroupPanel
|
|||||||
|
|
||||||
if (userVFXDisabled)
|
if (userVFXDisabled)
|
||||||
{
|
{
|
||||||
var userVFXText = "VFX sync disabled through you";
|
var userVFXText = L("GroupPanel.Permissions.VfxDisabledSelf", "VFX sync disabled through you");
|
||||||
_uiShared.IconText(userVFXIcon);
|
_uiShared.IconText(userVFXIcon);
|
||||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TextUnformatted(userVFXText);
|
ImGui.TextUnformatted(userVFXText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!invitesEnabled || soundsDisabled || animDisabled || vfxDisabled)
|
if (!invitesEnabled || soundsDisabled || animDisabled || vfxDisabled)
|
||||||
UiSharedService.TextWrapped("Note that syncshell permissions for disabling take precedence over your own set permissions");
|
UiSharedService.TextWrapped(L("GroupPanel.Permissions.NotePriority", "Note that syncshell permissions for disabling take precedence over your own set permissions"));
|
||||||
}
|
}
|
||||||
ImGui.EndTooltip();
|
ImGui.EndTooltip();
|
||||||
}
|
}
|
||||||
@@ -602,7 +753,8 @@ internal sealed class GroupPanel
|
|||||||
var userPerm = groupDto.GroupUserPermissions ^ GroupUserPermissions.Paused;
|
var userPerm = groupDto.GroupUserPermissions ^ GroupUserPermissions.Paused;
|
||||||
_ = ApiController.GroupChangeIndividualPermissionState(new GroupPairUserPermissionDto(groupDto.Group, new UserData(ApiController.UID), userPerm));
|
_ = ApiController.GroupChangeIndividualPermissionState(new GroupPairUserPermissionDto(groupDto.Group, new UserData(ApiController.UID), userPerm));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip((groupDto.GroupUserPermissions.IsPaused() ? "Resume" : "Pause") + " pairing with all users in this Syncshell");
|
UiSharedService.AttachToolTip(L("GroupPanel.PauseToggle.Tooltip", "{0} pairing with all users in this Syncshell",
|
||||||
|
groupDto.GroupUserPermissions.IsPaused() ? L("GroupPanel.PauseToggle.Resume", "Resume") : L("GroupPanel.PauseToggle.Pause", "Pause")));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
if (_uiShared.IconButton(FontAwesomeIcon.Bars))
|
if (_uiShared.IconButton(FontAwesomeIcon.Bars))
|
||||||
@@ -612,28 +764,32 @@ internal sealed class GroupPanel
|
|||||||
|
|
||||||
if (ImGui.BeginPopup("ShellPopup"))
|
if (ImGui.BeginPopup("ShellPopup"))
|
||||||
{
|
{
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.ArrowCircleLeft, "Leave Syncshell") && UiSharedService.CtrlPressed())
|
if (_uiShared.IconTextButton(FontAwesomeIcon.ArrowCircleLeft, L("GroupPanel.Popup.Leave", "Leave Syncshell")) && UiSharedService.CtrlPressed())
|
||||||
{
|
{
|
||||||
_ = ApiController.GroupLeave(groupDto);
|
_ = ApiController.GroupLeave(groupDto);
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Hold CTRL and click to leave this Syncshell" + (!string.Equals(groupDto.OwnerUID, ApiController.UID, StringComparison.Ordinal) ? string.Empty : Environment.NewLine
|
UiSharedService.AttachToolTip(L("GroupPanel.Popup.LeaveTooltip", "Hold CTRL and click to leave this Syncshell{0}",
|
||||||
+ "WARNING: This action is irreversible" + Environment.NewLine + "Leaving an owned Syncshell will transfer the ownership to a random person in the Syncshell."));
|
!string.Equals(groupDto.OwnerUID, ApiController.UID, StringComparison.Ordinal)
|
||||||
|
? string.Empty
|
||||||
|
: Environment.NewLine + L("GroupPanel.Popup.LeaveWarning", "WARNING: This action is irreversible\nLeaving an owned Syncshell will transfer the ownership to a random person in the Syncshell.")));
|
||||||
|
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, "Copy ID"))
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, L("GroupPanel.Popup.CopyId", "Copy ID")))
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
ImGui.SetClipboardText(groupDto.GroupAliasOrGID);
|
ImGui.SetClipboardText(groupDto.GroupAliasOrGID);
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Copy Syncshell ID to Clipboard");
|
UiSharedService.AttachToolTip(L("GroupPanel.Popup.CopyIdTooltip", "Copy Syncshell ID to Clipboard"));
|
||||||
|
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.StickyNote, "Copy Notes"))
|
if (_uiShared.IconTextButton(FontAwesomeIcon.StickyNote, L("GroupPanel.Popup.CopyNotes", "Copy Notes")))
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
ImGui.SetClipboardText(UiSharedService.GetNotes(groupPairs));
|
ImGui.SetClipboardText(UiSharedService.GetNotes(groupPairs));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Copies all your notes for all users in this Syncshell to the clipboard." + Environment.NewLine + "They can be imported via Settings -> General -> Notes -> Import notes from clipboard");
|
UiSharedService.AttachToolTip(L("GroupPanel.Popup.CopyNotesTooltip", "Copies all your notes for all users in this Syncshell to the clipboard.\nThey can be imported via Settings -> General -> Notes -> Import notes from clipboard"));
|
||||||
|
|
||||||
var soundsText = userSoundsDisabled ? "Enable sound sync" : "Disable sound sync";
|
var soundsText = userSoundsDisabled
|
||||||
|
? L("GroupPanel.Popup.EnableSound", "Enable sound sync")
|
||||||
|
: L("GroupPanel.Popup.DisableSound", "Disable sound sync");
|
||||||
if (_uiShared.IconTextButton(userSoundsIcon, soundsText))
|
if (_uiShared.IconTextButton(userSoundsIcon, soundsText))
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
@@ -641,12 +797,11 @@ internal sealed class GroupPanel
|
|||||||
perm.SetDisableSounds(!perm.IsDisableSounds());
|
perm.SetDisableSounds(!perm.IsDisableSounds());
|
||||||
_ = ApiController.GroupChangeIndividualPermissionState(new(groupDto.Group, new UserData(ApiController.UID), perm));
|
_ = ApiController.GroupChangeIndividualPermissionState(new(groupDto.Group, new UserData(ApiController.UID), perm));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Sets your allowance for sound synchronization for users of this syncshell."
|
UiSharedService.AttachToolTip(L("GroupPanel.Popup.SoundTooltip", "Sets your allowance for sound synchronization for users of this syncshell.\nDisabling the synchronization will stop applying sound modifications for users of this syncshell.\nNote: this setting can be forcefully overridden to 'disabled' through the syncshell owner.\nNote: this setting does not apply to individual pairs that are also in the syncshell."));
|
||||||
+ Environment.NewLine + "Disabling the synchronization will stop applying sound modifications for users of this syncshell."
|
|
||||||
+ Environment.NewLine + "Note: this setting can be forcefully overridden to 'disabled' through the syncshell owner."
|
|
||||||
+ Environment.NewLine + "Note: this setting does not apply to individual pairs that are also in the syncshell.");
|
|
||||||
|
|
||||||
var animText = userAnimDisabled ? "Enable animations sync" : "Disable animations sync";
|
var animText = userAnimDisabled
|
||||||
|
? L("GroupPanel.Popup.EnableAnimations", "Enable animations sync")
|
||||||
|
: L("GroupPanel.Popup.DisableAnimations", "Disable animations sync");
|
||||||
if (_uiShared.IconTextButton(userAnimIcon, animText))
|
if (_uiShared.IconTextButton(userAnimIcon, animText))
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
@@ -654,13 +809,11 @@ internal sealed class GroupPanel
|
|||||||
perm.SetDisableAnimations(!perm.IsDisableAnimations());
|
perm.SetDisableAnimations(!perm.IsDisableAnimations());
|
||||||
_ = ApiController.GroupChangeIndividualPermissionState(new(groupDto.Group, new UserData(ApiController.UID), perm));
|
_ = ApiController.GroupChangeIndividualPermissionState(new(groupDto.Group, new UserData(ApiController.UID), perm));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Sets your allowance for animations synchronization for users of this syncshell."
|
UiSharedService.AttachToolTip(L("GroupPanel.Popup.AnimTooltip", "Sets your allowance for animations synchronization for users of this syncshell.\nDisabling the synchronization will stop applying animations modifications for users of this syncshell.\nNote: this setting might also affect sound synchronization\nNote: this setting can be forcefully overridden to 'disabled' through the syncshell owner.\nNote: this setting does not apply to individual pairs that are also in the syncshell."));
|
||||||
+ Environment.NewLine + "Disabling the synchronization will stop applying animations modifications for users of this syncshell."
|
|
||||||
+ Environment.NewLine + "Note: this setting might also affect sound synchronization"
|
|
||||||
+ Environment.NewLine + "Note: this setting can be forcefully overridden to 'disabled' through the syncshell owner."
|
|
||||||
+ Environment.NewLine + "Note: this setting does not apply to individual pairs that are also in the syncshell.");
|
|
||||||
|
|
||||||
var vfxText = userVFXDisabled ? "Enable VFX sync" : "Disable VFX sync";
|
var vfxText = userVFXDisabled
|
||||||
|
? L("GroupPanel.Popup.EnableVfx", "Enable VFX sync")
|
||||||
|
: L("GroupPanel.Popup.DisableVfx", "Disable VFX sync");
|
||||||
if (_uiShared.IconTextButton(userVFXIcon, vfxText))
|
if (_uiShared.IconTextButton(userVFXIcon, vfxText))
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
@@ -668,16 +821,12 @@ internal sealed class GroupPanel
|
|||||||
perm.SetDisableVFX(!perm.IsDisableVFX());
|
perm.SetDisableVFX(!perm.IsDisableVFX());
|
||||||
_ = ApiController.GroupChangeIndividualPermissionState(new(groupDto.Group, new UserData(ApiController.UID), perm));
|
_ = ApiController.GroupChangeIndividualPermissionState(new(groupDto.Group, new UserData(ApiController.UID), perm));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Sets your allowance for VFX synchronization for users of this syncshell."
|
UiSharedService.AttachToolTip(L("GroupPanel.Popup.VfxTooltip", "Sets your allowance for VFX synchronization for users of this syncshell.\nDisabling the synchronization will stop applying VFX modifications for users of this syncshell.\nNote: this setting might also affect animation synchronization to some degree\nNote: this setting can be forcefully overridden to 'disabled' through the syncshell owner.\nNote: this setting does not apply to individual pairs that are also in the syncshell."));
|
||||||
+ Environment.NewLine + "Disabling the synchronization will stop applying VFX modifications for users of this syncshell."
|
|
||||||
+ Environment.NewLine + "Note: this setting might also affect animation synchronization to some degree"
|
|
||||||
+ Environment.NewLine + "Note: this setting can be forcefully overridden to 'disabled' through the syncshell owner."
|
|
||||||
+ Environment.NewLine + "Note: this setting does not apply to individual pairs that are also in the syncshell.");
|
|
||||||
|
|
||||||
if (isOwner || groupDto.GroupUserInfo.IsModerator())
|
if (isOwner || groupDto.GroupUserInfo.IsModerator())
|
||||||
{
|
{
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Cog, "Open Admin Panel"))
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Cog, L("GroupPanel.Popup.OpenAdmin", "Open Admin Panel")))
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
_mainUi.Mediator.Publish(new OpenSyncshellAdminPanel(groupDto));
|
_mainUi.Mediator.Publish(new OpenSyncshellAdminPanel(groupDto));
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using System;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using MareSynchronos.API.Data.Extensions;
|
using MareSynchronos.API.Data.Extensions;
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.UI.Handlers;
|
using MareSynchronos.UI.Handlers;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
|
using MareSynchronos.Localization;
|
||||||
|
|
||||||
namespace MareSynchronos.UI.Components;
|
namespace MareSynchronos.UI.Components;
|
||||||
|
|
||||||
@@ -17,6 +19,13 @@ public class PairGroupsUi
|
|||||||
private readonly UidDisplayHandler _uidDisplayHandler;
|
private readonly UidDisplayHandler _uidDisplayHandler;
|
||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
|
|
||||||
|
private string L(string key, string fallback, params object[] args)
|
||||||
|
{
|
||||||
|
var safeArgs = args ?? Array.Empty<object>();
|
||||||
|
return LocalizationService.Instance?.GetString(key, fallback, safeArgs)
|
||||||
|
?? string.Format(System.Globalization.CultureInfo.CurrentCulture, fallback, safeArgs);
|
||||||
|
}
|
||||||
|
|
||||||
public PairGroupsUi(MareConfigService mareConfig, TagHandler tagHandler, UidDisplayHandler uidDisplayHandler, ApiController apiController,
|
public PairGroupsUi(MareConfigService mareConfig, TagHandler tagHandler, UidDisplayHandler uidDisplayHandler, ApiController apiController,
|
||||||
SelectPairForGroupUi selectGroupForPairUi, UiSharedService uiSharedService)
|
SelectPairForGroupUi selectGroupForPairUi, UiSharedService uiSharedService)
|
||||||
{
|
{
|
||||||
@@ -54,37 +63,32 @@ public class PairGroupsUi
|
|||||||
ImGui.SameLine(buttonPauseOffset);
|
ImGui.SameLine(buttonPauseOffset);
|
||||||
if (_uiSharedService.IconButton(pauseButton))
|
if (_uiSharedService.IconButton(pauseButton))
|
||||||
{
|
{
|
||||||
// If all of the currently visible pairs (after applying filters to the pairs)
|
|
||||||
// are paused we display a resume button to resume all currently visible (after filters)
|
|
||||||
// pairs. Otherwise, we just pause all the remaining pairs.
|
|
||||||
if (allArePaused)
|
if (allArePaused)
|
||||||
{
|
{
|
||||||
// If all are paused => resume all
|
|
||||||
ResumeAllPairs(availablePairsInThisTag);
|
ResumeAllPairs(availablePairsInThisTag);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// otherwise pause all remaining
|
|
||||||
PauseRemainingPairs(availablePairsInThisTag);
|
PauseRemainingPairs(availablePairsInThisTag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (allArePaused)
|
if (allArePaused)
|
||||||
{
|
{
|
||||||
UiSharedService.AttachToolTip($"Resume pairing with all pairs in {tag}");
|
UiSharedService.AttachToolTip(L("PairGroups.ResumeAll", "Resume pairing with all pairs in {0}", tag));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UiSharedService.AttachToolTip($"Pause pairing with all pairs in {tag}");
|
UiSharedService.AttachToolTip(L("PairGroups.PauseAll", "Pause pairing with all pairs in {0}", tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
var buttonDeleteOffset = windowX + windowWidth - flyoutMenuX;
|
var buttonDeleteOffset = windowX + windowWidth - flyoutMenuX;
|
||||||
ImGui.SameLine(buttonDeleteOffset);
|
ImGui.SameLine(buttonDeleteOffset);
|
||||||
if (_uiSharedService.IconButton(FontAwesomeIcon.Bars))
|
if (_uiSharedService.IconButton(FontAwesomeIcon.Bars))
|
||||||
{
|
{
|
||||||
ImGui.OpenPopup("Group Flyout Menu");
|
ImGui.OpenPopup(L("PairGroups.Menu.Title", "Group Flyout Menu"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginPopup("Group Flyout Menu"))
|
if (ImGui.BeginPopup(L("PairGroups.Menu.Title", "Group Flyout Menu")))
|
||||||
{
|
{
|
||||||
using (ImRaii.PushId($"buttons-{tag}")) DrawGroupMenu(tag);
|
using (ImRaii.PushId($"buttons-{tag}")) DrawGroupMenu(tag);
|
||||||
ImGui.EndPopup();
|
ImGui.EndPopup();
|
||||||
@@ -120,7 +124,6 @@ public class PairGroupsUi
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Avoid uncomfortably close group names
|
|
||||||
if (!_tagHandler.IsTagOpen(tag))
|
if (!_tagHandler.IsTagOpen(tag))
|
||||||
{
|
{
|
||||||
var size = ImGui.CalcTextSize("").Y + ImGui.GetStyle().FramePadding.Y * 2f;
|
var size = ImGui.CalcTextSize("").Y + ImGui.GetStyle().FramePadding.Y * 2f;
|
||||||
@@ -138,33 +141,35 @@ public class PairGroupsUi
|
|||||||
|
|
||||||
private void DrawGroupMenu(string tag)
|
private void DrawGroupMenu(string tag)
|
||||||
{
|
{
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Users, "Add people to " + tag))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Users, L("PairGroups.Menu.AddPeople", "Add people to {0}", tag)))
|
||||||
{
|
{
|
||||||
_selectGroupForPairUi.Open(tag);
|
_selectGroupForPairUi.Open(tag);
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip($"Add more users to Group {tag}");
|
UiSharedService.AttachToolTip(L("PairGroups.Menu.AddPeople.Tooltip", "Add more users to Group {0}", tag));
|
||||||
|
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Delete " + tag) && UiSharedService.CtrlPressed())
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, L("PairGroups.Menu.Delete", "Delete {0}", tag)) && UiSharedService.CtrlPressed())
|
||||||
{
|
{
|
||||||
_tagHandler.RemoveTag(tag);
|
_tagHandler.RemoveTag(tag);
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip($"Delete Group {tag} (Will not delete the pairs)" + Environment.NewLine + "Hold CTRL to delete");
|
UiSharedService.AttachToolTip(L("PairGroups.Menu.Delete.Tooltip", "Delete Group {0} (Will not delete the pairs)\nHold CTRL to delete", tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawName(string tag, bool isSpecialTag, int visible, int online, int? total)
|
private void DrawName(string tag, bool isSpecialTag, int visible, int online, int? total)
|
||||||
{
|
{
|
||||||
string displayedName = tag switch
|
string displayedName = tag switch
|
||||||
{
|
{
|
||||||
TagHandler.CustomUnpairedTag => "Unpaired",
|
TagHandler.CustomUnpairedTag => L("PairGroups.Tag.Unpaired", "Unpaired"),
|
||||||
TagHandler.CustomOfflineTag => "Offline",
|
TagHandler.CustomOfflineTag => L("PairGroups.Tag.Offline", "Offline"),
|
||||||
TagHandler.CustomOnlineTag => _mareConfig.Current.ShowOfflineUsersSeparately ? "Online/Paused" : "Contacts",
|
TagHandler.CustomOnlineTag => _mareConfig.Current.ShowOfflineUsersSeparately
|
||||||
TagHandler.CustomVisibleTag => "Visible",
|
? L("PairGroups.Tag.Online", "Online")
|
||||||
|
: L("PairGroups.Tag.Contacts", "Contacts"),
|
||||||
|
TagHandler.CustomVisibleTag => L("PairGroups.Tag.Visible", "Visible"),
|
||||||
_ => tag
|
_ => tag
|
||||||
};
|
};
|
||||||
|
|
||||||
string resultFolderName = !isSpecialTag ? $"{displayedName} ({visible}/{online}/{total} Pairs)" : $"{displayedName} ({online} Pairs)";
|
string resultFolderName = !isSpecialTag
|
||||||
|
? L("PairGroups.Header.WithCounts", "{0} ({1}/{2}/{3} Pairs)", displayedName, visible, online, total ?? 0)
|
||||||
// FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight
|
: L("PairGroups.Header.Special", "{0} ({1} Pairs)", displayedName, online);
|
||||||
var icon = _tagHandler.IsTagOpen(tag) ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight;
|
var icon = _tagHandler.IsTagOpen(tag) ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight;
|
||||||
_uiSharedService.IconText(icon);
|
_uiSharedService.IconText(icon);
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||||
@@ -181,11 +186,11 @@ public class PairGroupsUi
|
|||||||
if (!isSpecialTag && ImGui.IsItemHovered())
|
if (!isSpecialTag && ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
ImGui.BeginTooltip();
|
||||||
ImGui.TextUnformatted($"Group {tag}");
|
ImGui.TextUnformatted(L("PairGroups.Tooltip.Title", "Group {0}", tag));
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.TextUnformatted($"{visible} Pairs visible");
|
ImGui.TextUnformatted(L("PairGroups.Tooltip.Visible", "{0} Pairs visible", visible));
|
||||||
ImGui.TextUnformatted($"{online} Pairs online/paused");
|
ImGui.TextUnformatted(L("PairGroups.Tooltip.Online", "{0} Pairs online/paused", online));
|
||||||
ImGui.TextUnformatted($"{total} Pairs total");
|
ImGui.TextUnformatted(L("PairGroups.Tooltip.Total", "{0} Pairs total", total ?? 0));
|
||||||
ImGui.EndTooltip();
|
ImGui.EndTooltip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,17 +28,17 @@ public class BanUserPopupHandler : IPopupHandler
|
|||||||
|
|
||||||
public void DrawContent()
|
public void DrawContent()
|
||||||
{
|
{
|
||||||
UiSharedService.TextWrapped("User " + (_reportedPair.UserData.AliasOrUID) + " will be banned and removed from this Syncshell.");
|
UiSharedService.TextWrapped(_uiSharedService.Localize("Popup.BanUser.Description", "User {0} will be banned and removed from this Syncshell.", _reportedPair.UserData.AliasOrUID));
|
||||||
ImGui.InputTextWithHint("##banreason", "Ban Reason", ref _banReason, 255);
|
ImGui.InputTextWithHint("##banreason", _uiSharedService.Localize("Popup.BanUser.ReasonHint", "Ban Reason"), ref _banReason, 255);
|
||||||
|
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserSlash, "Ban User"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserSlash, _uiSharedService.Localize("Popup.BanUser.Button", "Ban User")))
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
var reason = _banReason;
|
var reason = _banReason;
|
||||||
_ = _apiController.GroupBanUser(new GroupPairDto(_group.Group, _reportedPair.UserData), reason);
|
_ = _apiController.GroupBanUser(new GroupPairDto(_group.Group, _reportedPair.UserData), reason);
|
||||||
_banReason = string.Empty;
|
_banReason = string.Empty;
|
||||||
}
|
}
|
||||||
UiSharedService.TextWrapped("The reason will be displayed in the banlist. The current server-side alias if present (Vanity ID) will automatically be attached to the reason.");
|
UiSharedService.TextWrapped(_uiSharedService.Localize("Popup.BanUser.ReasonNote", "The reason will be displayed in the banlist. The current server-side alias if present (Vanity ID) will automatically be attached to the reason."));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Open(OpenBanUserPopupMessage message)
|
public void Open(OpenBanUserPopupMessage message)
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public class PopupHandler : WindowMediatorSubscriberBase
|
|||||||
if (_currentHandler.ShowClose)
|
if (_currentHandler.ShowClose)
|
||||||
{
|
{
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Times, "Close"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Times, _uiSharedService.Localize("Popup.Generic.Close", "Close")))
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,23 +29,21 @@ internal class ReportPopupHandler : IPopupHandler
|
|||||||
public void DrawContent()
|
public void DrawContent()
|
||||||
{
|
{
|
||||||
using (_uiSharedService.UidFont.Push())
|
using (_uiSharedService.UidFont.Push())
|
||||||
UiSharedService.TextWrapped("Report " + _reportedPair!.UserData.AliasOrUID + " Profile");
|
UiSharedService.TextWrapped(_uiSharedService.Localize("Popup.Report.Title", "Report {0} Profile", _reportedPair!.UserData.AliasOrUID));
|
||||||
|
|
||||||
ImGui.InputTextMultiline("##reportReason", ref _reportReason, 500, new Vector2(500 - ImGui.GetStyle().ItemSpacing.X * 2, 200));
|
ImGui.InputTextMultiline("##reportReason", ref _reportReason, 500, new Vector2(500 - ImGui.GetStyle().ItemSpacing.X * 2, 200));
|
||||||
UiSharedService.TextWrapped($"Note: Sending a report will disable the offending profile globally.{Environment.NewLine}" +
|
UiSharedService.TextWrapped(_uiSharedService.Localize("Popup.Report.Note", "Note: Sending a report will disable the offending profile globally.\nThe report will be sent to the team of your currently connected server.\nDepending on the severity of the offense the users profile or account can be permanently disabled or banned."));
|
||||||
$"The report will be sent to the team of your currently connected server.{Environment.NewLine}" +
|
UiSharedService.ColorTextWrapped(_uiSharedService.Localize("Popup.Report.Warning", "Report spam and wrong reports will not be tolerated and can lead to permanent account suspension."), ImGuiColors.DalamudRed);
|
||||||
$"Depending on the severity of the offense the users profile or account can be permanently disabled or banned.");
|
UiSharedService.ColorTextWrapped(_uiSharedService.Localize("Popup.Report.Scope", "This is not for reporting misbehavior but solely for the actual profile. Reports that are not solely for the profile will be ignored."), ImGuiColors.DalamudYellow);
|
||||||
UiSharedService.ColorTextWrapped("Report spam and wrong reports will not be tolerated and can lead to permanent account suspension.", ImGuiColors.DalamudRed);
|
|
||||||
UiSharedService.ColorTextWrapped("This is not for reporting misbehavior but solely for the actual profile. " +
|
|
||||||
"Reports that are not solely for the profile will be ignored.", ImGuiColors.DalamudYellow);
|
|
||||||
|
|
||||||
using (ImRaii.Disabled(string.IsNullOrEmpty(_reportReason)))
|
using (ImRaii.Disabled(string.IsNullOrEmpty(_reportReason)))
|
||||||
{
|
{
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.ExclamationTriangle, "Send Report"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.ExclamationTriangle, _uiSharedService.Localize("Popup.Report.Button", "Send Report")))
|
||||||
{
|
{
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
var reason = _reportReason;
|
var reason = _reportReason;
|
||||||
_ = _apiController.UserReportProfile(new(_reportedPair.UserData, reason));
|
_ = _apiController.UserReportProfile(new(_reportedPair.UserData, reason));
|
||||||
|
_reportReason = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,20 +15,8 @@ public class SelectGroupForPairUi
|
|||||||
private readonly UidDisplayHandler _uidDisplayHandler;
|
private readonly UidDisplayHandler _uidDisplayHandler;
|
||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The group UI is always open for a specific pair. This defines which pair the UI is open for.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
private Pair? _pair;
|
private Pair? _pair;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Should the panel show, yes/no
|
|
||||||
/// </summary>
|
|
||||||
private bool _show;
|
private bool _show;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For the add category option, this stores the currently typed in tag name
|
|
||||||
/// </summary>
|
|
||||||
private string _tagNameToAdd = "";
|
private string _tagNameToAdd = "";
|
||||||
|
|
||||||
public SelectGroupForPairUi(TagHandler tagHandler, UidDisplayHandler uidDisplayHandler, UiSharedService uiSharedService)
|
public SelectGroupForPairUi(TagHandler tagHandler, UidDisplayHandler uidDisplayHandler, UiSharedService uiSharedService)
|
||||||
@@ -48,8 +36,7 @@ public class SelectGroupForPairUi
|
|||||||
}
|
}
|
||||||
|
|
||||||
var name = PairName(_pair);
|
var name = PairName(_pair);
|
||||||
var popupName = $"Choose Groups for {name}";
|
var popupName = _uiSharedService.Localize("PairGroups.Popup.Title", "Choose Groups for {0}", name);
|
||||||
// Is the popup supposed to show but did not open yet? Open it
|
|
||||||
if (_show)
|
if (_show)
|
||||||
{
|
{
|
||||||
ImGui.OpenPopup(popupName);
|
ImGui.OpenPopup(popupName);
|
||||||
@@ -62,7 +49,7 @@ public class SelectGroupForPairUi
|
|||||||
var childHeight = tags.Count != 0 ? tags.Count * 25 : 1;
|
var childHeight = tags.Count != 0 ? tags.Count * 25 : 1;
|
||||||
var childSize = new Vector2(0, childHeight > 100 ? 100 : childHeight) * ImGuiHelpers.GlobalScale;
|
var childSize = new Vector2(0, childHeight > 100 ? 100 : childHeight) * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
ImGui.TextUnformatted($"Select the groups you want {name} to be in.");
|
ImGui.TextUnformatted(_uiSharedService.Localize("PairGroups.Popup.SelectPrompt", "Select the groups you want {0} to be in.", name));
|
||||||
if (ImGui.BeginChild(name + "##listGroups", childSize))
|
if (ImGui.BeginChild(name + "##listGroups", childSize))
|
||||||
{
|
{
|
||||||
foreach (var tag in tags)
|
foreach (var tag in tags)
|
||||||
@@ -73,13 +60,13 @@ public class SelectGroupForPairUi
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.TextUnformatted($"Create a new group for {name}.");
|
ImGui.TextUnformatted(_uiSharedService.Localize("PairGroups.Popup.CreatePrompt", "Create a new group for {0}.", name));
|
||||||
if (_uiSharedService.IconButton(FontAwesomeIcon.Plus))
|
if (_uiSharedService.IconButton(FontAwesomeIcon.Plus))
|
||||||
{
|
{
|
||||||
HandleAddTag();
|
HandleAddTag();
|
||||||
}
|
}
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.InputTextWithHint("##category_name", "New Group", ref _tagNameToAdd, 40);
|
ImGui.InputTextWithHint("##category_name", _uiSharedService.Localize("PairGroups.Popup.NewGroupHint", "New Group"), ref _tagNameToAdd, 40);
|
||||||
if (ImGui.IsKeyDown(ImGuiKey.Enter))
|
if (ImGui.IsKeyDown(ImGuiKey.Enter))
|
||||||
{
|
{
|
||||||
HandleAddTag();
|
HandleAddTag();
|
||||||
@@ -91,10 +78,6 @@ public class SelectGroupForPairUi
|
|||||||
public void Open(Pair pair)
|
public void Open(Pair pair)
|
||||||
{
|
{
|
||||||
_pair = pair;
|
_pair = pair;
|
||||||
// Using "_show" here to de-couple the opening of the popup
|
|
||||||
// The popup name is derived from the name the user currently sees, which is
|
|
||||||
// based on the showUidForEntry dictionary.
|
|
||||||
// We'd have to derive the name here to open it popup modal here, when the Open() is called
|
|
||||||
_show = true;
|
_show = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using MareSynchronos.PlayerData.Pairs;
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
|
using MareSynchronos.UI;
|
||||||
using MareSynchronos.UI.Handlers;
|
using MareSynchronos.UI.Handlers;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -15,11 +16,13 @@ public class SelectPairForGroupUi
|
|||||||
private HashSet<string> _peopleInGroup = new(StringComparer.Ordinal);
|
private HashSet<string> _peopleInGroup = new(StringComparer.Ordinal);
|
||||||
private bool _show = false;
|
private bool _show = false;
|
||||||
private string _tag = string.Empty;
|
private string _tag = string.Empty;
|
||||||
|
private readonly UiSharedService _uiSharedService;
|
||||||
|
|
||||||
public SelectPairForGroupUi(TagHandler tagHandler, UidDisplayHandler uidDisplayHandler)
|
public SelectPairForGroupUi(TagHandler tagHandler, UidDisplayHandler uidDisplayHandler, UiSharedService uiSharedService)
|
||||||
{
|
{
|
||||||
_tagHandler = tagHandler;
|
_tagHandler = tagHandler;
|
||||||
_uidDisplayHandler = uidDisplayHandler;
|
_uidDisplayHandler = uidDisplayHandler;
|
||||||
|
_uiSharedService = uiSharedService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw(List<Pair> pairs)
|
public void Draw(List<Pair> pairs)
|
||||||
@@ -28,7 +31,7 @@ public class SelectPairForGroupUi
|
|||||||
var minSize = new Vector2(300, workHeight < 400 ? workHeight : 400) * ImGuiHelpers.GlobalScale;
|
var minSize = new Vector2(300, workHeight < 400 ? workHeight : 400) * ImGuiHelpers.GlobalScale;
|
||||||
var maxSize = new Vector2(300, 1000) * ImGuiHelpers.GlobalScale;
|
var maxSize = new Vector2(300, 1000) * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
var popupName = $"Choose Users for Group {_tag}";
|
var popupName = _uiSharedService.Localize("PairGroups.SelectPairs.Title", "Choose Users for Group {0}", _tag);
|
||||||
|
|
||||||
if (!_show)
|
if (!_show)
|
||||||
{
|
{
|
||||||
@@ -46,9 +49,9 @@ public class SelectPairForGroupUi
|
|||||||
ImGui.SetNextWindowSizeConstraints(minSize, maxSize);
|
ImGui.SetNextWindowSizeConstraints(minSize, maxSize);
|
||||||
if (ImGui.BeginPopupModal(popupName, ref _show, ImGuiWindowFlags.Popup | ImGuiWindowFlags.Modal))
|
if (ImGui.BeginPopupModal(popupName, ref _show, ImGuiWindowFlags.Popup | ImGuiWindowFlags.Modal))
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted($"Select users for group {_tag}");
|
ImGui.TextUnformatted(_uiSharedService.Localize("PairGroups.SelectPairs.SelectPrompt", "Select users for group {0}", _tag));
|
||||||
|
|
||||||
ImGui.InputTextWithHint("##filter", "Filter", ref _filter, 255, ImGuiInputTextFlags.None);
|
ImGui.InputTextWithHint("##filter", _uiSharedService.Localize("PairGroups.SelectPairs.FilterHint", "Filter"), ref _filter, 255, ImGuiInputTextFlags.None);
|
||||||
foreach (var item in pairs
|
foreach (var item in pairs
|
||||||
.Where(p => string.IsNullOrEmpty(_filter) || PairName(p).Contains(_filter, StringComparison.OrdinalIgnoreCase))
|
.Where(p => string.IsNullOrEmpty(_filter) || PairName(p).Contains(_filter, StringComparison.OrdinalIgnoreCase))
|
||||||
.OrderBy(p => PairName(p), StringComparer.OrdinalIgnoreCase)
|
.OrderBy(p => PairName(p), StringComparer.OrdinalIgnoreCase)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ using MareSynchronos.Services.Mediator;
|
|||||||
using MareSynchronos.Utils;
|
using MareSynchronos.Utils;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using MareSynchronos.Localization;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace MareSynchronos.UI;
|
namespace MareSynchronos.UI;
|
||||||
|
|
||||||
@@ -33,11 +35,17 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
private ObjectKind _selectedObjectTab;
|
private ObjectKind _selectedObjectTab;
|
||||||
private bool _showModal = false;
|
private bool _showModal = false;
|
||||||
|
|
||||||
|
private string L(string key, string fallback, params object[] args)
|
||||||
|
{
|
||||||
|
var safeArgs = args ?? Array.Empty<object>();
|
||||||
|
return _uiSharedService.Localize(key, fallback, safeArgs);
|
||||||
|
}
|
||||||
|
|
||||||
public DataAnalysisUi(ILogger<DataAnalysisUi> logger, MareMediator mediator,
|
public DataAnalysisUi(ILogger<DataAnalysisUi> logger, MareMediator mediator,
|
||||||
CharacterAnalyzer characterAnalyzer, IpcManager ipcManager,
|
CharacterAnalyzer characterAnalyzer, IpcManager ipcManager,
|
||||||
PerformanceCollectorService performanceCollectorService,
|
PerformanceCollectorService performanceCollectorService,
|
||||||
UiSharedService uiSharedService)
|
UiSharedService uiSharedService)
|
||||||
: base(logger, mediator, "Character Data Analysis", performanceCollectorService)
|
: base(logger, mediator, uiSharedService.Localize("DataAnalysis.WindowTitle", "Character Data Analysis"), performanceCollectorService)
|
||||||
{
|
{
|
||||||
_characterAnalyzer = characterAnalyzer;
|
_characterAnalyzer = characterAnalyzer;
|
||||||
_ipcManager = ipcManager;
|
_ipcManager = ipcManager;
|
||||||
@@ -65,14 +73,15 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
{
|
{
|
||||||
|
var modalTitle = L("DataAnalysis.Bc7.ModalTitle", "BC7 Conversion in Progress");
|
||||||
if (_conversionTask != null && !_conversionTask.IsCompleted)
|
if (_conversionTask != null && !_conversionTask.IsCompleted)
|
||||||
{
|
{
|
||||||
_showModal = true;
|
_showModal = true;
|
||||||
if (ImGui.BeginPopupModal("BC7 Conversion in Progress"))
|
if (ImGui.BeginPopupModal(modalTitle))
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("BC7 Conversion in progress: " + _conversionCurrentFileProgress + "/" + _texturesToConvert.Count);
|
ImGui.TextUnformatted(L("DataAnalysis.Bc7.Status", "BC7 Conversion in progress: {0}/{1}", _conversionCurrentFileProgress, _texturesToConvert.Count));
|
||||||
UiSharedService.TextWrapped("Current file: " + _conversionCurrentFileName);
|
UiSharedService.TextWrapped(L("DataAnalysis.Bc7.CurrentFile", "Current file: {0}", _conversionCurrentFileName));
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel conversion"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, L("DataAnalysis.Bc7.Cancel", "Cancel conversion")))
|
||||||
{
|
{
|
||||||
_conversionCancellationTokenSource.Cancel();
|
_conversionCancellationTokenSource.Cancel();
|
||||||
}
|
}
|
||||||
@@ -95,7 +104,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
if (_showModal && !_modalOpen)
|
if (_showModal && !_modalOpen)
|
||||||
{
|
{
|
||||||
ImGui.OpenPopup("BC7 Conversion in Progress");
|
ImGui.OpenPopup(modalTitle);
|
||||||
_modalOpen = true;
|
_modalOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +115,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
_sortDirty = true;
|
_sortDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
UiSharedService.TextWrapped("This window shows you all files and their sizes that are currently in use through your character and associated entities");
|
UiSharedService.TextWrapped(L("DataAnalysis.Description", "This window shows you all files and their sizes that are currently in use through your character and associated entities"));
|
||||||
|
|
||||||
if (_cachedAnalysis == null || _cachedAnalysis.Count == 0) return;
|
if (_cachedAnalysis == null || _cachedAnalysis.Count == 0) return;
|
||||||
|
|
||||||
@@ -114,9 +123,9 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
bool needAnalysis = _cachedAnalysis!.Any(c => c.Value.Any(f => !f.Value.IsComputed));
|
bool needAnalysis = _cachedAnalysis!.Any(c => c.Value.Any(f => !f.Value.IsComputed));
|
||||||
if (isAnalyzing)
|
if (isAnalyzing)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped($"Analyzing {_characterAnalyzer.CurrentFile}/{_characterAnalyzer.TotalFiles}",
|
UiSharedService.ColorTextWrapped(L("DataAnalysis.Analyzing", "Analyzing {0}/{1}", _characterAnalyzer.CurrentFile, _characterAnalyzer.TotalFiles),
|
||||||
ImGuiColors.DalamudYellow);
|
ImGuiColors.DalamudYellow);
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel analysis"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, L("DataAnalysis.Button.CancelAnalysis", "Cancel analysis")))
|
||||||
{
|
{
|
||||||
_characterAnalyzer.CancelAnalyze();
|
_characterAnalyzer.CancelAnalyze();
|
||||||
}
|
}
|
||||||
@@ -125,16 +134,16 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
if (needAnalysis)
|
if (needAnalysis)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped("Some entries in the analysis have file size not determined yet, press the button below to analyze your current data",
|
UiSharedService.ColorTextWrapped(L("DataAnalysis.Analyze.MissingNotice", "Some entries in the analysis have file size not determined yet, press the button below to analyze your current data"),
|
||||||
ImGuiColors.DalamudYellow);
|
ImGuiColors.DalamudYellow);
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start analysis (missing entries)"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, L("DataAnalysis.Button.StartMissing", "Start analysis (missing entries)")))
|
||||||
{
|
{
|
||||||
_ = _characterAnalyzer.ComputeAnalysis(print: false);
|
_ = _characterAnalyzer.ComputeAnalysis(print: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start analysis (recalculate all entries)"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, L("DataAnalysis.Button.StartAll", "Start analysis (recalculate all entries)")))
|
||||||
{
|
{
|
||||||
_ = _characterAnalyzer.ComputeAnalysis(print: false, recalculate: true);
|
_ = _characterAnalyzer.ComputeAnalysis(print: false, recalculate: true);
|
||||||
}
|
}
|
||||||
@@ -143,7 +152,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
ImGui.TextUnformatted("Total files:");
|
ImGui.TextUnformatted(L("DataAnalysis.TotalFiles", "Total files:"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(_cachedAnalysis!.Values.Sum(c => c.Values.Count).ToString());
|
ImGui.TextUnformatted(_cachedAnalysis!.Values.Sum(c => c.Values.Count).ToString());
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
@@ -153,17 +162,16 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
string text = "";
|
|
||||||
var groupedfiles = _cachedAnalysis.Values.SelectMany(f => f.Values).GroupBy(f => f.FileType, StringComparer.Ordinal);
|
var groupedfiles = _cachedAnalysis.Values.SelectMany(f => f.Values).GroupBy(f => f.FileType, StringComparer.Ordinal);
|
||||||
text = string.Join(Environment.NewLine, groupedfiles.OrderBy(f => f.Key, StringComparer.Ordinal)
|
var summary = string.Join(Environment.NewLine, groupedfiles.OrderBy(f => f.Key, StringComparer.Ordinal)
|
||||||
.Select(f => f.Key + ": " + f.Count() + " files, size: " + UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))
|
.Select(f => L("DataAnalysis.Tooltip.FileSummary", "{0}: {1} files, size: {2}, compressed: {3}",
|
||||||
+ ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))));
|
f.Key, f.Count(), UiSharedService.ByteToString(f.Sum(v => v.OriginalSize)), UiSharedService.ByteToString(f.Sum(v => v.CompressedSize)))));
|
||||||
ImGui.SetTooltip(text);
|
ImGui.SetTooltip(summary);
|
||||||
}
|
}
|
||||||
ImGui.TextUnformatted("Total size (actual):");
|
ImGui.TextUnformatted(L("DataAnalysis.TotalSizeActual", "Total size (actual):"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize))));
|
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize))));
|
||||||
ImGui.TextUnformatted("Total size (download size):");
|
ImGui.TextUnformatted(L("DataAnalysis.TotalSizeDownload", "Total size (download size):"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis))
|
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis))
|
||||||
{
|
{
|
||||||
@@ -173,10 +181,11 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString());
|
ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString());
|
||||||
UiSharedService.AttachToolTip("Click \"Start analysis\" to calculate download size");
|
UiSharedService.AttachToolTip(L("DataAnalysis.Tooltip.CalculateDownloadSize", "Click \"Start analysis\" to calculate download size"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui.TextUnformatted($"Total modded model triangles: {UiSharedService.TrisToString(_cachedAnalysis.Sum(c => c.Value.Sum(f => f.Value.Triangles)))}");
|
ImGui.TextUnformatted(L("DataAnalysis.TotalTriangles", "Total modded model triangles: {0}",
|
||||||
|
UiSharedService.TrisToString(_cachedAnalysis.Sum(c => c.Value.Sum(f => f.Value.Triangles)))));
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
using var tabbar = ImRaii.TabBar("objectSelection");
|
using var tabbar = ImRaii.TabBar("objectSelection");
|
||||||
@@ -190,7 +199,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal)
|
var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal)
|
||||||
.OrderBy(k => k.Key, StringComparer.Ordinal).ToList();
|
.OrderBy(k => k.Key, StringComparer.Ordinal).ToList();
|
||||||
|
|
||||||
ImGui.TextUnformatted("Files for " + kvp.Key);
|
ImGui.TextUnformatted(L("DataAnalysis.FilesFor", "Files for {0}", kvp.Key));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(kvp.Value.Count.ToString());
|
ImGui.TextUnformatted(kvp.Value.Count.ToString());
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
@@ -201,16 +210,15 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
string text = "";
|
var summary = string.Join(Environment.NewLine, groupedfiles
|
||||||
text = string.Join(Environment.NewLine, groupedfiles
|
.Select(f => L("DataAnalysis.Tooltip.FileSummary", "{0}: {1} files, size: {2}, compressed: {3}",
|
||||||
.Select(f => f.Key + ": " + f.Count() + " files, size: " + UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))
|
f.Key, f.Count(), UiSharedService.ByteToString(f.Sum(v => v.OriginalSize)), UiSharedService.ByteToString(f.Sum(v => v.CompressedSize)))));
|
||||||
+ ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))));
|
ImGui.SetTooltip(summary);
|
||||||
ImGui.SetTooltip(text);
|
|
||||||
}
|
}
|
||||||
ImGui.TextUnformatted($"{kvp.Key} size (actual):");
|
ImGui.TextUnformatted(L("DataAnalysis.Object.SizeActual", "{0} size (actual):", kvp.Key));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
|
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
|
||||||
ImGui.TextUnformatted($"{kvp.Key} size (download size):");
|
ImGui.TextUnformatted(L("DataAnalysis.Object.SizeDownload", "{0} size (download size):", kvp.Key));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis))
|
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis))
|
||||||
{
|
{
|
||||||
@@ -220,17 +228,18 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString());
|
ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString());
|
||||||
UiSharedService.AttachToolTip("Click \"Start analysis\" to calculate download size");
|
UiSharedService.AttachToolTip(L("DataAnalysis.Tooltip.CalculateDownloadSize", "Click \"Start analysis\" to calculate download size"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui.TextUnformatted($"{kvp.Key} VRAM usage:");
|
ImGui.TextUnformatted(L("DataAnalysis.Object.Vram", "{0} VRAM usage:", kvp.Key));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var vramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal));
|
var vramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal));
|
||||||
if (vramUsage != null)
|
if (vramUsage != null)
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted(UiSharedService.ByteToString(vramUsage.Sum(f => f.OriginalSize)));
|
ImGui.TextUnformatted(UiSharedService.ByteToString(vramUsage.Sum(f => f.OriginalSize)));
|
||||||
}
|
}
|
||||||
ImGui.TextUnformatted($"{kvp.Key} modded model triangles: {UiSharedService.TrisToString(kvp.Value.Sum(f => f.Value.Triangles))}");
|
ImGui.TextUnformatted(L("DataAnalysis.Object.Triangles", "{0} modded model triangles: {1}", kvp.Key,
|
||||||
|
UiSharedService.TrisToString(kvp.Value.Sum(f => f.Value.Triangles))));
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
if (_selectedObjectTab != kvp.Key)
|
if (_selectedObjectTab != kvp.Key)
|
||||||
@@ -266,33 +275,35 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
_texturesToConvert.Clear();
|
_texturesToConvert.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TextUnformatted($"{fileGroup.Key} files");
|
ImGui.TextUnformatted(L("DataAnalysis.FileGroup.Count", "{0} files", fileGroup.Key));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(fileGroup.Count().ToString());
|
ImGui.TextUnformatted(fileGroup.Count().ToString());
|
||||||
|
|
||||||
ImGui.TextUnformatted($"{fileGroup.Key} files size (actual):");
|
ImGui.TextUnformatted(L("DataAnalysis.FileGroup.SizeActual", "{0} files size (actual):", fileGroup.Key));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.OriginalSize)));
|
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.OriginalSize)));
|
||||||
|
|
||||||
ImGui.TextUnformatted($"{fileGroup.Key} files size (download size):");
|
ImGui.TextUnformatted(L("DataAnalysis.FileGroup.SizeDownload", "{0} files size (download size):", fileGroup.Key));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize)));
|
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize)));
|
||||||
|
|
||||||
if (string.Equals(_selectedFileTypeTab, "tex", StringComparison.Ordinal))
|
if (string.Equals(_selectedFileTypeTab, "tex", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
ImGui.Checkbox("Enable BC7 Conversion Mode", ref _enableBc7ConversionMode);
|
ImGui.Checkbox(L("DataAnalysis.Bc7.EnableMode", "Enable BC7 Conversion Mode"), ref _enableBc7ConversionMode);
|
||||||
if (_enableBc7ConversionMode)
|
if (_enableBc7ConversionMode)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorText("WARNING BC7 CONVERSION:", ImGuiColors.DalamudYellow);
|
UiSharedService.ColorText(L("DataAnalysis.Bc7.WarningTitle", "WARNING BC7 CONVERSION:"), ImGuiColors.DalamudYellow);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
UiSharedService.ColorText("Converting textures to BC7 is irreversible!", ImGuiColors.DalamudRed);
|
UiSharedService.ColorText(L("DataAnalysis.Bc7.WarningIrreversible", "Converting textures to BC7 is irreversible!"), ImGuiColors.DalamudRed);
|
||||||
UiSharedService.ColorTextWrapped("- Converting textures to BC7 will reduce their size (compressed and uncompressed) drastically. It is recommended to be used for large (4k+) textures." +
|
UiSharedService.ColorTextWrapped(L("DataAnalysis.Bc7.WarningDetails",
|
||||||
Environment.NewLine + "- Some textures, especially ones utilizing colorsets, might not be suited for BC7 conversion and might produce visual artifacts." +
|
"- Converting textures to BC7 will reduce their size (compressed and uncompressed) drastically. It is recommended to be used for large (4k+) textures.\n"
|
||||||
Environment.NewLine + "- Before converting textures, make sure to have the original files of the mod you are converting so you can reimport it in case of issues." +
|
+ "- Some textures, especially ones utilizing colorsets, might not be suited for BC7 conversion and might produce visual artifacts.\n"
|
||||||
Environment.NewLine + "- Conversion will convert all found texture duplicates (entries with more than 1 file path) automatically." +
|
+ "- Before converting textures, make sure to have the original files of the mod you are converting so you can reimport it in case of issues.\n"
|
||||||
Environment.NewLine + "- Converting textures to BC7 is a very expensive operation and, depending on the amount of textures to convert, will take a while to complete."
|
+ "- Conversion will convert all found texture duplicates (entries with more than 1 file path) automatically.\n"
|
||||||
, ImGuiColors.DalamudYellow);
|
+ "- Converting textures to BC7 is a very expensive operation and, depending on the amount of textures to convert, will take a while to complete."),
|
||||||
if (_texturesToConvert.Count > 0 && _uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start conversion of " + _texturesToConvert.Count + " texture(s)"))
|
ImGuiColors.DalamudYellow);
|
||||||
|
if (_texturesToConvert.Count > 0 && _uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle,
|
||||||
|
L("DataAnalysis.Bc7.StartConversion", "Start conversion of {0} texture(s)", _texturesToConvert.Count)))
|
||||||
{
|
{
|
||||||
_conversionCancellationTokenSource = _conversionCancellationTokenSource.CancelRecreate();
|
_conversionCancellationTokenSource = _conversionCancellationTokenSource.CancelRecreate();
|
||||||
_conversionTask = _ipcManager.Penumbra.ConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, _conversionCancellationTokenSource.Token);
|
_conversionTask = _ipcManager.Penumbra.ConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, _conversionCancellationTokenSource.Token);
|
||||||
@@ -310,33 +321,33 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
ImGui.TextUnformatted("Selected file:");
|
ImGui.TextUnformatted(L("DataAnalysis.SelectedFile", "Selected file:"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
UiSharedService.ColorText(_selectedHash, ImGuiColors.DalamudYellow);
|
UiSharedService.ColorText(_selectedHash, ImGuiColors.DalamudYellow);
|
||||||
|
|
||||||
if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item))
|
if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item))
|
||||||
{
|
{
|
||||||
var filePaths = item.FilePaths;
|
var filePaths = item.FilePaths;
|
||||||
ImGui.TextUnformatted("Local file path:");
|
ImGui.TextUnformatted(L("DataAnalysis.LocalFilePath", "Local file path:"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
UiSharedService.TextWrapped(filePaths[0]);
|
UiSharedService.TextWrapped(filePaths[0]);
|
||||||
if (filePaths.Count > 1)
|
if (filePaths.Count > 1)
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted($"(and {filePaths.Count - 1} more)");
|
ImGui.TextUnformatted(L("DataAnalysis.MoreCount", "(and {0} more)", filePaths.Count - 1));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.InfoCircle);
|
_uiSharedService.IconText(FontAwesomeIcon.InfoCircle);
|
||||||
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, filePaths.Skip(1)));
|
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, filePaths.Skip(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
var gamepaths = item.GamePaths;
|
var gamepaths = item.GamePaths;
|
||||||
ImGui.TextUnformatted("Used by game path:");
|
ImGui.TextUnformatted(L("DataAnalysis.GamePath", "Used by game path:"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
UiSharedService.TextWrapped(gamepaths[0]);
|
UiSharedService.TextWrapped(gamepaths[0]);
|
||||||
if (gamepaths.Count > 1)
|
if (gamepaths.Count > 1)
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted($"(and {gamepaths.Count - 1} more)");
|
ImGui.TextUnformatted(L("DataAnalysis.MoreCount", "(and {0} more)", gamepaths.Count - 1));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.InfoCircle);
|
_uiSharedService.IconText(FontAwesomeIcon.InfoCircle);
|
||||||
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, gamepaths.Skip(1)));
|
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, gamepaths.Skip(1)));
|
||||||
@@ -372,20 +383,20 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
using var table = ImRaii.Table("Analysis", tableColumns, ImGuiTableFlags.Sortable | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit,
|
using var table = ImRaii.Table("Analysis", tableColumns, ImGuiTableFlags.Sortable | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit,
|
||||||
new Vector2(0, 300));
|
new Vector2(0, 300));
|
||||||
if (!table.Success) return;
|
if (!table.Success) return;
|
||||||
ImGui.TableSetupColumn("Hash");
|
ImGui.TableSetupColumn(L("DataAnalysis.Table.Hash", "Hash"));
|
||||||
ImGui.TableSetupColumn("Filepaths", ImGuiTableColumnFlags.PreferSortDescending);
|
ImGui.TableSetupColumn(L("DataAnalysis.Table.Filepaths", "Filepaths"), ImGuiTableColumnFlags.PreferSortDescending);
|
||||||
ImGui.TableSetupColumn("Gamepaths", ImGuiTableColumnFlags.PreferSortDescending);
|
ImGui.TableSetupColumn(L("DataAnalysis.Table.Gamepaths", "Gamepaths"), ImGuiTableColumnFlags.PreferSortDescending);
|
||||||
ImGui.TableSetupColumn("File Size", ImGuiTableColumnFlags.DefaultSort | ImGuiTableColumnFlags.PreferSortDescending);
|
ImGui.TableSetupColumn(L("DataAnalysis.Table.FileSize", "File Size"), ImGuiTableColumnFlags.DefaultSort | ImGuiTableColumnFlags.PreferSortDescending);
|
||||||
ImGui.TableSetupColumn("Download Size", ImGuiTableColumnFlags.PreferSortDescending);
|
ImGui.TableSetupColumn(L("DataAnalysis.Table.DownloadSize", "Download Size"), ImGuiTableColumnFlags.PreferSortDescending);
|
||||||
if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal))
|
if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("Format");
|
ImGui.TableSetupColumn(L("DataAnalysis.Table.Format", "Format"));
|
||||||
if (_enableBc7ConversionMode) ImGui.TableSetupColumn("Convert to BC7");
|
if (_enableBc7ConversionMode) ImGui.TableSetupColumn(L("DataAnalysis.Table.ConvertToBc7", "Convert to BC7"));
|
||||||
}
|
}
|
||||||
if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal))
|
if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("Triangles", ImGuiTableColumnFlags.PreferSortDescending);
|
ImGui.TableSetupColumn(L("DataAnalysis.Table.Triangles", "Triangles"), ImGuiTableColumnFlags.PreferSortDescending);
|
||||||
}
|
}
|
||||||
ImGui.TableSetupScrollFreeze(0, 1);
|
ImGui.TableSetupScrollFreeze(0, 1);
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
public DownloadUi(ILogger<DownloadUi> logger, DalamudUtilService dalamudUtilService, MareConfigService configService,
|
public DownloadUi(ILogger<DownloadUi> logger, DalamudUtilService dalamudUtilService, MareConfigService configService,
|
||||||
FileUploadManager fileTransferManager, MareMediator mediator, UiSharedService uiShared, PerformanceCollectorService performanceCollectorService)
|
FileUploadManager fileTransferManager, MareMediator mediator, UiSharedService uiShared, PerformanceCollectorService performanceCollectorService)
|
||||||
: base(logger, mediator, "UmbraSync Downloads", performanceCollectorService)
|
: base(logger, mediator, uiShared.Localize("DownloadUi.WindowTitle", "Umbra Downloads"), performanceCollectorService)
|
||||||
{
|
{
|
||||||
_dalamudUtilService = dalamudUtilService;
|
_dalamudUtilService = dalamudUtilService;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
@@ -69,6 +69,8 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string L(string key, string fallback, params object[] args) => _uiShared.Localize(key, fallback, args);
|
||||||
|
|
||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
{
|
{
|
||||||
if (_configService.Current.ShowTransferWindow)
|
if (_configService.Current.ShowTransferWindow)
|
||||||
@@ -87,7 +89,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
UiSharedService.DrawOutlinedFont($"▲", ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
UiSharedService.DrawOutlinedFont($"▲", ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var xDistance = ImGui.GetCursorPosX();
|
var xDistance = ImGui.GetCursorPosX();
|
||||||
UiSharedService.DrawOutlinedFont($"Compressing+Uploading {doneUploads}/{totalUploads}",
|
UiSharedService.DrawOutlinedFont(L("DownloadUi.UploadStatus", "Compressing+Uploading {0}/{1}", doneUploads, totalUploads),
|
||||||
ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
ImGui.SameLine(xDistance);
|
ImGui.SameLine(xDistance);
|
||||||
@@ -120,7 +122,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var xDistance = ImGui.GetCursorPosX();
|
var xDistance = ImGui.GetCursorPosX();
|
||||||
UiSharedService.DrawOutlinedFont(
|
UiSharedService.DrawOutlinedFont(
|
||||||
$"{item.Key.Name} [W:{dlSlot}/Q:{dlQueue}/P:{dlProg}/D:{dlDecomp}]",
|
L("DownloadUi.DownloadStatus", "{0} [W:{1}/Q:{2}/P:{3}/D:{4}]", item.Key.Name, dlSlot, dlQueue, dlProg, dlDecomp),
|
||||||
ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
ImGuiColors.DalamudWhite, new Vector4(0, 0, 0, 255), 1);
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
ImGui.SameLine(xDistance);
|
ImGui.SameLine(xDistance);
|
||||||
@@ -163,13 +165,13 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
UiSharedService.Color(0, 0, 0, transparency), 1);
|
UiSharedService.Color(0, 0, 0, transparency), 1);
|
||||||
drawList.AddRectFilled(dlBarStart with { X = dlBarStart.X - dlBarBorder, Y = dlBarStart.Y - dlBarBorder },
|
drawList.AddRectFilled(dlBarStart with { X = dlBarStart.X - dlBarBorder, Y = dlBarStart.Y - dlBarBorder },
|
||||||
dlBarEnd with { X = dlBarEnd.X + dlBarBorder, Y = dlBarEnd.Y + dlBarBorder },
|
dlBarEnd with { X = dlBarEnd.X + dlBarBorder, Y = dlBarEnd.Y + dlBarBorder },
|
||||||
UiSharedService.Color(220, 220, 220, transparency), 1);
|
UiSharedService.Color(230, 200, 255, transparency), 1);
|
||||||
drawList.AddRectFilled(dlBarStart, dlBarEnd,
|
drawList.AddRectFilled(dlBarStart, dlBarEnd,
|
||||||
UiSharedService.Color(0, 0, 0, transparency), 1);
|
UiSharedService.Color(0, 0, 0, transparency), 1);
|
||||||
var dlProgressPercent = transferredBytes / (double)totalBytes;
|
var dlProgressPercent = transferredBytes / (double)totalBytes;
|
||||||
drawList.AddRectFilled(dlBarStart,
|
drawList.AddRectFilled(dlBarStart,
|
||||||
dlBarEnd with { X = dlBarStart.X + (float)(dlProgressPercent * dlBarWidth) },
|
dlBarEnd with { X = dlBarStart.X + (float)(dlProgressPercent * dlBarWidth) },
|
||||||
UiSharedService.Color(50, 205, 50, transparency), 1);
|
UiSharedService.Color(160, 64, 255, transparency), 1);
|
||||||
|
|
||||||
if (_configService.Current.TransferBarsShowText)
|
if (_configService.Current.TransferBarsShowText)
|
||||||
{
|
{
|
||||||
@@ -191,7 +193,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var _ = _uiShared.UidFont.Push();
|
using var _ = _uiShared.UidFont.Push();
|
||||||
var uploadText = "Uploading";
|
var uploadText = L("DownloadUi.UploadingLabel", "Uploading");
|
||||||
|
|
||||||
var textSize = ImGui.CalcTextSize(uploadText);
|
var textSize = ImGui.CalcTextSize(uploadText);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using Dalamud.Interface;
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.MareConfiguration.Configurations;
|
using MareSynchronos.MareConfiguration.Configurations;
|
||||||
using MareSynchronos.PlayerData.Pairs;
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
@@ -10,11 +11,14 @@ using MareSynchronos.WebAPI;
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using MareSynchronos.Localization;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace MareSynchronos.UI;
|
namespace MareSynchronos.UI;
|
||||||
|
|
||||||
public sealed class DtrEntry : IDisposable, IHostedService
|
public sealed class DtrEntry : IDisposable, IHostedService
|
||||||
{
|
{
|
||||||
|
public const string DefaultGlyph = "\u25CB";
|
||||||
private enum DtrStyle
|
private enum DtrStyle
|
||||||
{
|
{
|
||||||
Default,
|
Default,
|
||||||
@@ -44,6 +48,13 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
|||||||
private string? _tooltip;
|
private string? _tooltip;
|
||||||
private Colors _colors;
|
private Colors _colors;
|
||||||
|
|
||||||
|
private static string L(string key, string fallback, params object[] args)
|
||||||
|
{
|
||||||
|
var safeArgs = args ?? Array.Empty<object>();
|
||||||
|
return LocalizationService.Instance?.GetString(key, fallback, safeArgs)
|
||||||
|
?? string.Format(CultureInfo.CurrentCulture, fallback, safeArgs);
|
||||||
|
}
|
||||||
|
|
||||||
public DtrEntry(ILogger<DtrEntry> logger, IDtrBar dtrBar, MareConfigService configService, MareMediator mareMediator, PairManager pairManager, ApiController apiController)
|
public DtrEntry(ILogger<DtrEntry> logger, IDtrBar dtrBar, MareConfigService configService, MareMediator mareMediator, PairManager pairManager, ApiController apiController)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@@ -104,7 +115,7 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
|||||||
private IDtrBarEntry CreateEntry()
|
private IDtrBarEntry CreateEntry()
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Creating new DtrBar entry");
|
_logger.LogTrace("Creating new DtrBar entry");
|
||||||
var entry = _dtrBar.Get("UmbraSync");
|
var entry = _dtrBar.Get(L("DtrEntry.EntryName", "Umbra"));
|
||||||
entry.OnClick = _ => _mareMediator.Publish(new UiToggleMessage(typeof(CompactUi)));
|
entry.OnClick = _ => _mareMediator.Publish(new UiToggleMessage(typeof(CompactUi)));
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
@@ -163,19 +174,20 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
|||||||
.Select(x => string.Format("{0}", _configService.Current.PreferNoteInDtrTooltip ? x.GetNoteOrName() : x.PlayerName));
|
.Select(x => string.Format("{0}", _configService.Current.PreferNoteInDtrTooltip ? x.GetNoteOrName() : x.PlayerName));
|
||||||
}
|
}
|
||||||
|
|
||||||
tooltip = $"UmbraSync: Connected{Environment.NewLine}----------{Environment.NewLine}{string.Join(Environment.NewLine, visiblePairs)}";
|
var header = L("DtrEntry.Tooltip.Connected", "Umbra: Connected");
|
||||||
|
tooltip = header + Environment.NewLine + "----------" + Environment.NewLine + string.Join(Environment.NewLine, visiblePairs);
|
||||||
colors = _configService.Current.DtrColorsPairsInRange;
|
colors = _configService.Current.DtrColorsPairsInRange;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
tooltip = "UmbraSync: Connected";
|
tooltip = L("DtrEntry.Tooltip.Connected", "Umbra: Connected");
|
||||||
colors = _configService.Current.DtrColorsDefault;
|
colors = _configService.Current.DtrColorsDefault;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
text = RenderDtrStyle(_configService.Current.DtrStyle, "\uE04C");
|
text = RenderDtrStyle(_configService.Current.DtrStyle, "\uE04C");
|
||||||
tooltip = "UmbraSync: Not Connected";
|
tooltip = L("DtrEntry.Tooltip.Disconnected", "Umbra: Not Connected");
|
||||||
colors = _configService.Current.DtrColorsNotConnected;
|
colors = _configService.Current.DtrColorsNotConnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +208,8 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
|||||||
{
|
{
|
||||||
var style = (DtrStyle)styleNum;
|
var style = (DtrStyle)styleNum;
|
||||||
|
|
||||||
return style switch {
|
return style switch
|
||||||
|
{
|
||||||
DtrStyle.Style1 => $"\xE039 {text}",
|
DtrStyle.Style1 => $"\xE039 {text}",
|
||||||
DtrStyle.Style2 => $"\xE0BC {text}",
|
DtrStyle.Style2 => $"\xE0BC {text}",
|
||||||
DtrStyle.Style3 => $"\xE0BD {text}",
|
DtrStyle.Style3 => $"\xE0BD {text}",
|
||||||
@@ -206,7 +219,7 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
|||||||
DtrStyle.Style7 => $"\xE05D {text}",
|
DtrStyle.Style7 => $"\xE05D {text}",
|
||||||
DtrStyle.Style8 => $"\xE03C{text}",
|
DtrStyle.Style8 => $"\xE03C{text}",
|
||||||
DtrStyle.Style9 => $"\xE040 {text} \xE041",
|
DtrStyle.Style9 => $"\xE040 {text} \xE041",
|
||||||
_ => $"\uE044 {text}"
|
_ => DefaultGlyph + " " + text
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager,
|
ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager,
|
||||||
ServerConfigurationManager serverConfigurationManager,
|
ServerConfigurationManager serverConfigurationManager,
|
||||||
MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService)
|
MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService)
|
||||||
: base(logger, mediator, "UmbraSync Edit Profile###UmbraSyncSyncEditProfileUI", performanceCollectorService)
|
: base(logger, mediator, uiSharedService.Localize("EditProfile.WindowTitle", "Umbra Edit Profile") + "###UmbraSyncEditProfileUI", performanceCollectorService)
|
||||||
{
|
{
|
||||||
IsOpen = false;
|
IsOpen = false;
|
||||||
this.SizeConstraints = new()
|
this.SizeConstraints = new()
|
||||||
@@ -62,9 +62,11 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string L(string key, string fallback, params object[] args) => _uiSharedService.Localize(key, fallback, args);
|
||||||
|
|
||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
{
|
{
|
||||||
_uiSharedService.BigText("Current Profile (as saved on server)");
|
_uiSharedService.BigText(L("EditProfile.CurrentProfile", "Current Profile (as saved on server)"));
|
||||||
|
|
||||||
var profile = _mareProfileManager.GetMareProfile(new UserData(_apiController.UID));
|
var profile = _mareProfileManager.GetMareProfile(new UserData(_apiController.UID));
|
||||||
|
|
||||||
@@ -125,9 +127,9 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
_uiSharedService.BigText("Profile Settings");
|
_uiSharedService.BigText("Profile Settings");
|
||||||
|
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile picture"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, L("EditProfile.Button.UploadPicture", "Upload new profile picture")))
|
||||||
{
|
{
|
||||||
_fileDialogManager.OpenFileDialog("Select new Profile picture", ".png", (success, file) =>
|
_fileDialogManager.OpenFileDialog(L("EditProfile.Dialog.PictureTitle", "Select new Profile picture"), ".png", (success, file) =>
|
||||||
{
|
{
|
||||||
if (!success) return;
|
if (!success) return;
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
@@ -148,29 +150,29 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Select and upload a new profile picture");
|
UiSharedService.AttachToolTip(L("EditProfile.Tooltip.UploadPicture", "Select and upload a new profile picture"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear uploaded profile picture"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, L("EditProfile.Button.ClearPicture", "Clear uploaded profile picture")))
|
||||||
{
|
{
|
||||||
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, "", Description: null));
|
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, "", Description: null));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Clear your currently uploaded profile picture");
|
UiSharedService.AttachToolTip(L("EditProfile.Tooltip.ClearPicture", "Clear your currently uploaded profile picture"));
|
||||||
if (_showFileDialogError)
|
if (_showFileDialogError)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped("The profile picture must be a PNG file with a maximum height and width of 256px and 250KiB size", ImGuiColors.DalamudRed);
|
UiSharedService.ColorTextWrapped(L("EditProfile.Error.PictureTooLarge", "The profile picture must be a PNG file with a maximum height and width of 256px and 250KiB size"), ImGuiColors.DalamudRed);
|
||||||
}
|
}
|
||||||
var isNsfw = profile.IsNSFW;
|
var isNsfw = profile.IsNSFW;
|
||||||
if (ImGui.Checkbox("Profile is NSFW", ref isNsfw))
|
if (ImGui.Checkbox(L("EditProfile.Checkbox.Nsfw", "Profile is NSFW"), ref isNsfw))
|
||||||
{
|
{
|
||||||
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, isNsfw, ProfilePictureBase64: null, Description: null));
|
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, isNsfw, ProfilePictureBase64: null, Description: null));
|
||||||
}
|
}
|
||||||
_uiSharedService.DrawHelpText("If your profile description or image can be considered NSFW, toggle this to ON");
|
_uiSharedService.DrawHelpText(L("EditProfile.Help.Nsfw", "If your profile description or image can be considered NSFW, toggle this to ON"));
|
||||||
var widthTextBox = 400;
|
var widthTextBox = 400;
|
||||||
var posX = ImGui.GetCursorPosX();
|
var posX = ImGui.GetCursorPosX();
|
||||||
ImGui.TextUnformatted($"Description {_descriptionText.Length}/1500");
|
ImGui.TextUnformatted(L("EditProfile.DescriptionCounter", "Description {0}/1500", _descriptionText.Length));
|
||||||
ImGui.SetCursorPosX(posX);
|
ImGui.SetCursorPosX(posX);
|
||||||
ImGuiHelpers.ScaledRelativeSameLine(widthTextBox, ImGui.GetStyle().ItemSpacing.X);
|
ImGuiHelpers.ScaledRelativeSameLine(widthTextBox, ImGui.GetStyle().ItemSpacing.X);
|
||||||
ImGui.TextUnformatted("Preview (approximate)");
|
ImGui.TextUnformatted(L("EditProfile.PreviewLabel", "Preview (approximate)"));
|
||||||
using (_uiSharedService.GameFont.Push())
|
using (_uiSharedService.GameFont.Push())
|
||||||
ImGui.InputTextMultiline("##description", ref _descriptionText, 1500, ImGuiHelpers.ScaledVector2(widthTextBox, 200));
|
ImGui.InputTextMultiline("##description", ref _descriptionText, 1500, ImGuiHelpers.ScaledVector2(widthTextBox, 200));
|
||||||
|
|
||||||
@@ -199,17 +201,17 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.EndChildFrame();
|
ImGui.EndChildFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Description"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, L("EditProfile.Button.SaveDescription", "Save Description")))
|
||||||
{
|
{
|
||||||
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, _descriptionText));
|
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, _descriptionText));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Sets your profile description text");
|
UiSharedService.AttachToolTip(L("EditProfile.Tooltip.SaveDescription", "Sets your profile description text"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, L("EditProfile.Button.ClearDescription", "Clear Description")))
|
||||||
{
|
{
|
||||||
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, ""));
|
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, ""));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Clears your profile description text");
|
UiSharedService.AttachToolTip(L("EditProfile.Tooltip.ClearDescription", "Clears your profile description text"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ internal class EventViewerUI : WindowMediatorSubscriberBase
|
|||||||
public EventViewerUI(ILogger<EventViewerUI> logger, MareMediator mediator,
|
public EventViewerUI(ILogger<EventViewerUI> logger, MareMediator mediator,
|
||||||
EventAggregator eventAggregator, UiSharedService uiSharedService, MareConfigService configService,
|
EventAggregator eventAggregator, UiSharedService uiSharedService, MareConfigService configService,
|
||||||
PerformanceCollectorService performanceCollectorService)
|
PerformanceCollectorService performanceCollectorService)
|
||||||
: base(logger, mediator, "Event Viewer", performanceCollectorService)
|
: base(logger, mediator, uiSharedService.Localize("EventViewer.WindowTitle", "Event Viewer"), performanceCollectorService)
|
||||||
{
|
{
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
@@ -52,6 +52,8 @@ internal class EventViewerUI : WindowMediatorSubscriberBase
|
|||||||
_filteredEvents = RecreateFilter();
|
_filteredEvents = RecreateFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string L(string key, string fallback, params object[] args) => _uiSharedService.Localize(key, fallback, args);
|
||||||
|
|
||||||
private Lazy<List<Event>> RecreateFilter()
|
private Lazy<List<Event>> RecreateFilter()
|
||||||
{
|
{
|
||||||
return new(() =>
|
return new(() =>
|
||||||
@@ -81,20 +83,22 @@ internal class EventViewerUI : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
var newEventsAvailable = _eventAggregator.NewEventsAvailable;
|
var newEventsAvailable = _eventAggregator.NewEventsAvailable;
|
||||||
|
|
||||||
var freezeSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.PlayCircle, "Unfreeze View");
|
var unfreezeLabel = L("EventViewer.Button.Unfreeze", "Unfreeze View");
|
||||||
|
var freezeLabel = L("EventViewer.Button.Freeze", "Freeze View");
|
||||||
|
var freezeSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.PlayCircle, unfreezeLabel);
|
||||||
if (_isPaused)
|
if (_isPaused)
|
||||||
{
|
{
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, newEventsAvailable))
|
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, newEventsAvailable))
|
||||||
{
|
{
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Unfreeze View"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, unfreezeLabel))
|
||||||
_isPaused = false;
|
_isPaused = false;
|
||||||
if (newEventsAvailable)
|
if (newEventsAvailable)
|
||||||
UiSharedService.AttachToolTip("New events are available. Click to resume updating.");
|
UiSharedService.AttachToolTip(L("EventViewer.Tooltip.NewEvents", "New events are available. Click to resume updating."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PauseCircle, "Freeze View"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PauseCircle, freezeLabel))
|
||||||
_isPaused = true;
|
_isPaused = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +109,7 @@ internal class EventViewerUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
bool changedFilter = false;
|
bool changedFilter = false;
|
||||||
ImGui.SetNextItemWidth(200);
|
ImGui.SetNextItemWidth(200);
|
||||||
changedFilter |= ImGui.InputText("Filter lines", ref _filterFreeText, 50);
|
changedFilter |= ImGui.InputText(L("EventViewer.FilterLabel", "Filter lines"), ref _filterFreeText, 50);
|
||||||
if (changedFilter) _filteredEvents = RecreateFilter();
|
if (changedFilter) _filteredEvents = RecreateFilter();
|
||||||
|
|
||||||
using (ImRaii.Disabled(_filterFreeText.IsNullOrEmpty()))
|
using (ImRaii.Disabled(_filterFreeText.IsNullOrEmpty()))
|
||||||
@@ -120,10 +124,11 @@ internal class EventViewerUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
if (_configService.Current.LogEvents)
|
if (_configService.Current.LogEvents)
|
||||||
{
|
{
|
||||||
var buttonSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.FolderOpen, "Open EventLog Folder");
|
var openLogLabel = L("EventViewer.Button.OpenLog", "Open EventLog folder");
|
||||||
|
var buttonSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.FolderOpen, openLogLabel);
|
||||||
var dist = ImGui.GetWindowContentRegionMax().X - buttonSize;
|
var dist = ImGui.GetWindowContentRegionMax().X - buttonSize;
|
||||||
ImGui.SameLine(dist);
|
ImGui.SameLine(dist);
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.FolderOpen, "Open EventLog folder"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.FolderOpen, openLogLabel))
|
||||||
{
|
{
|
||||||
ProcessStartInfo ps = new()
|
ProcessStartInfo ps = new()
|
||||||
{
|
{
|
||||||
@@ -152,11 +157,11 @@ internal class EventViewerUI : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
ImGui.TableSetupScrollFreeze(0, 1);
|
ImGui.TableSetupScrollFreeze(0, 1);
|
||||||
ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.NoSort);
|
ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.NoSort);
|
||||||
ImGui.TableSetupColumn("Time", ImGuiTableColumnFlags.None, timeColWidth);
|
ImGui.TableSetupColumn(L("EventViewer.Column.Time", "Time"), ImGuiTableColumnFlags.None, timeColWidth);
|
||||||
ImGui.TableSetupColumn("Source", ImGuiTableColumnFlags.None, sourceColWidth);
|
ImGui.TableSetupColumn(L("EventViewer.Column.Source", "Source"), ImGuiTableColumnFlags.None, sourceColWidth);
|
||||||
ImGui.TableSetupColumn("UID", ImGuiTableColumnFlags.None, uidColWidth);
|
ImGui.TableSetupColumn(L("EventViewer.Column.Uid", "UID"), ImGuiTableColumnFlags.None, uidColWidth);
|
||||||
ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.None, characterColWidth);
|
ImGui.TableSetupColumn(L("EventViewer.Column.Character", "Character"), ImGuiTableColumnFlags.None, characterColWidth);
|
||||||
ImGui.TableSetupColumn("Event", ImGuiTableColumnFlags.None);
|
ImGui.TableSetupColumn(L("EventViewer.Column.Event", "Event"), ImGuiTableColumnFlags.None);
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (var ev in _filteredEvents.Value)
|
foreach (var ev in _filteredEvents.Value)
|
||||||
@@ -181,7 +186,7 @@ internal class EventViewerUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
_uiSharedService.IconText(icon, iconColor == new Vector4() ? null : iconColor);
|
_uiSharedService.IconText(icon, iconColor == new Vector4() ? null : iconColor);
|
||||||
UiSharedService.AttachToolTip(ev.EventSeverity.ToString());
|
UiSharedService.AttachToolTip(L($"EventViewer.Severity.{ev.EventSeverity}", ev.EventSeverity.ToString()));
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted(ev.EventTime.ToString("T", CultureInfo.CurrentCulture));
|
ImGui.TextUnformatted(ev.EventTime.ToString("T", CultureInfo.CurrentCulture));
|
||||||
@@ -200,7 +205,7 @@ internal class EventViewerUI : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("--");
|
ImGui.TextUnformatted(L("EventViewer.NoValue", "--"));
|
||||||
}
|
}
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
@@ -214,7 +219,7 @@ internal class EventViewerUI : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("--");
|
ImGui.TextUnformatted(L("EventViewer.NoValue", "--"));
|
||||||
}
|
}
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using MareSynchronos.Localization;
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.PlayerData.Pairs;
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.Services.ServerConfiguration;
|
using MareSynchronos.Services.ServerConfiguration;
|
||||||
using MareSynchronos.UI.Components;
|
using MareSynchronos.UI.Components;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace MareSynchronos.UI.Handlers;
|
namespace MareSynchronos.UI.Handlers;
|
||||||
|
|
||||||
@@ -22,6 +24,13 @@ public class UidDisplayHandler
|
|||||||
private bool _popupShown = false;
|
private bool _popupShown = false;
|
||||||
private DateTime? _popupTime;
|
private DateTime? _popupTime;
|
||||||
|
|
||||||
|
private static string L(string key, string fallback, params object[] args)
|
||||||
|
{
|
||||||
|
var safeArgs = args ?? Array.Empty<object>();
|
||||||
|
return LocalizationService.Instance?.GetString(key, fallback, safeArgs)
|
||||||
|
?? string.Format(CultureInfo.CurrentCulture, fallback, safeArgs);
|
||||||
|
}
|
||||||
|
|
||||||
public UidDisplayHandler(MareMediator mediator, PairManager pairManager,
|
public UidDisplayHandler(MareMediator mediator, PairManager pairManager,
|
||||||
ServerConfigurationManager serverManager, MareConfigService mareConfigService)
|
ServerConfigurationManager serverManager, MareConfigService mareConfigService)
|
||||||
{
|
{
|
||||||
@@ -77,9 +86,7 @@ public class UidDisplayHandler
|
|||||||
|
|
||||||
if (_popupTime > DateTime.UtcNow || !_mareConfigService.Current.ProfilesShow)
|
if (_popupTime > DateTime.UtcNow || !_mareConfigService.Current.ProfilesShow)
|
||||||
{
|
{
|
||||||
ImGui.SetTooltip("Left click to switch between UID display and nick" + Environment.NewLine
|
ImGui.SetTooltip(L("UidDisplay.Tooltip", "Left click to switch between UID display and nick\nRight click to change nick for {0}\nMiddle Mouse Button to open their profile in a separate window", pair.UserData.AliasOrUID));
|
||||||
+ "Right click to change nick for " + pair.UserData.AliasOrUID + Environment.NewLine
|
|
||||||
+ "Middle Mouse Button to open their profile in a separate window");
|
|
||||||
}
|
}
|
||||||
else if (_popupTime < DateTime.UtcNow && !_popupShown)
|
else if (_popupTime < DateTime.UtcNow && !_popupShown)
|
||||||
{
|
{
|
||||||
@@ -125,7 +132,7 @@ public class UidDisplayHandler
|
|||||||
ImGui.SetCursorPosY(originalY);
|
ImGui.SetCursorPosY(originalY);
|
||||||
|
|
||||||
ImGui.SetNextItemWidth(editBoxWidth.Invoke());
|
ImGui.SetNextItemWidth(editBoxWidth.Invoke());
|
||||||
if (ImGui.InputTextWithHint("##" + pair.UserData.UID, "Nick/Notes", ref _editUserComment, 255, ImGuiInputTextFlags.EnterReturnsTrue))
|
if (ImGui.InputTextWithHint("##" + pair.UserData.UID, L("UidDisplay.EditNotes.Hint", "Nick/Notes"), ref _editUserComment, 255, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
{
|
{
|
||||||
_serverManager.SetNoteForUid(pair.UserData.UID, _editUserComment);
|
_serverManager.SetNoteForUid(pair.UserData.UID, _editUserComment);
|
||||||
_serverManager.SaveNotes();
|
_serverManager.SaveNotes();
|
||||||
@@ -136,7 +143,7 @@ public class UidDisplayHandler
|
|||||||
{
|
{
|
||||||
_editNickEntry = string.Empty;
|
_editNickEntry = string.Empty;
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Hit ENTER to save\nRight click to cancel");
|
UiSharedService.AttachToolTip(L("GroupPanel.CommentTooltip", "Hit ENTER to save\nRight click to cancel"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Dalamud.Interface.Utility.Raii;
|
|||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using MareSynchronos.API.Dto.Account;
|
using MareSynchronos.API.Dto.Account;
|
||||||
using MareSynchronos.FileCache;
|
using MareSynchronos.FileCache;
|
||||||
|
using MareSynchronos.Localization;
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.MareConfiguration.Models;
|
using MareSynchronos.MareConfiguration.Models;
|
||||||
using MareSynchronos.Services;
|
using MareSynchronos.Services;
|
||||||
@@ -36,9 +37,11 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
private string? _registrationMessage;
|
private string? _registrationMessage;
|
||||||
private RegisterReplyDto? _registrationReply;
|
private RegisterReplyDto? _registrationReply;
|
||||||
|
|
||||||
|
private string L(string key, string fallback, params object[] args) => _uiShared.Localize(key, fallback, args);
|
||||||
|
|
||||||
public IntroUi(ILogger<IntroUi> logger, UiSharedService uiShared, MareConfigService configService,
|
public IntroUi(ILogger<IntroUi> logger, UiSharedService uiShared, MareConfigService configService,
|
||||||
CacheMonitor fileCacheManager, ServerConfigurationManager serverConfigurationManager, MareMediator mareMediator,
|
CacheMonitor fileCacheManager, ServerConfigurationManager serverConfigurationManager, MareMediator mareMediator,
|
||||||
PerformanceCollectorService performanceCollectorService, DalamudUtilService dalamudUtilService, AccountRegistrationService registerService) : base(logger, mareMediator, "UmbraSync Setup", performanceCollectorService)
|
PerformanceCollectorService performanceCollectorService, DalamudUtilService dalamudUtilService, AccountRegistrationService registerService) : base(logger, mareMediator, "Umbra Setup", performanceCollectorService)
|
||||||
{
|
{
|
||||||
_uiShared = uiShared;
|
_uiShared = uiShared;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
@@ -87,17 +90,17 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
return _uiShared.ApiController.ServerState switch
|
return _uiShared.ApiController.ServerState switch
|
||||||
{
|
{
|
||||||
ServerState.Reconnecting => "Reconnecting",
|
ServerState.Reconnecting => L("Compact.UidText.Reconnecting", "Reconnecting"),
|
||||||
ServerState.Connecting => "Connecting",
|
ServerState.Connecting => L("Compact.UidText.Connecting", "Connecting"),
|
||||||
ServerState.Disconnected => "Disconnected",
|
ServerState.Disconnected => L("Compact.UidText.Disconnected", "Disconnected"),
|
||||||
ServerState.Disconnecting => "Disconnecting",
|
ServerState.Disconnecting => L("Compact.UidText.Disconnecting", "Disconnecting"),
|
||||||
ServerState.Unauthorized => "Unauthorized",
|
ServerState.Unauthorized => L("Compact.UidText.Unauthorized", "Unauthorized"),
|
||||||
ServerState.VersionMisMatch => "Version mismatch",
|
ServerState.VersionMisMatch => L("Compact.UidText.VersionMismatch", "Version mismatch"),
|
||||||
ServerState.Offline => "Unavailable",
|
ServerState.Offline => L("Compact.UidText.Offline", "Unavailable"),
|
||||||
ServerState.RateLimited => "Rate Limited",
|
ServerState.RateLimited => L("Compact.UidText.RateLimited", "Rate Limited"),
|
||||||
ServerState.NoSecretKey => "No Secret Key",
|
ServerState.NoSecretKey => L("Compact.UidText.NoSecretKey", "No Secret Key"),
|
||||||
ServerState.MultiChara => "Duplicate Characters",
|
ServerState.MultiChara => L("Compact.UidText.MultiChara", "Duplicate Characters"),
|
||||||
ServerState.Connected => "Connected",
|
ServerState.Connected => L("Intro.ConnectionStatus.Connected", "Connected"),
|
||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -108,18 +111,15 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
if (!_configService.Current.AcceptedAgreement && !_readFirstPage)
|
if (!_configService.Current.AcceptedAgreement && !_readFirstPage)
|
||||||
{
|
{
|
||||||
_uiShared.BigText("Welcome to UmbraSync");
|
_uiShared.BigText(L("Intro.Welcome.Title", "Welcome to Umbra"));
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
UiSharedService.TextWrapped("UmbraSync is a plugin that will replicate your full current character state including all Penumbra mods to other paired users. " +
|
UiSharedService.TextWrapped(L("Intro.Welcome.Paragraph1", "Umbra is a plugin that will replicate your full current character state including all Penumbra mods to other paired users. Note that you will have to have Penumbra as well as Glamourer installed to use this plugin."));
|
||||||
"Note that you will have to have Penumbra as well as Glamourer installed to use this plugin.");
|
UiSharedService.TextWrapped(L("Intro.Welcome.Paragraph2", "We will have to setup a few things first before you can start using this plugin. Click on next to continue."));
|
||||||
UiSharedService.TextWrapped("We will have to setup a few things first before you can start using this plugin. Click on next to continue.");
|
|
||||||
|
|
||||||
UiSharedService.ColorTextWrapped("Note: Any modifications you have applied through anything but Penumbra cannot be shared and your character state on other clients " +
|
UiSharedService.ColorTextWrapped(L("Intro.Welcome.Note", "Note: Any modifications you have applied through anything but Penumbra cannot be shared and your character state on other clients might look broken because of this or others players mods might not apply on your end altogether. If you want to use this plugin you will have to move your mods to Penumbra."), ImGuiColors.DalamudYellow);
|
||||||
"might look broken because of this or others players mods might not apply on your end altogether. " +
|
|
||||||
"If you want to use this plugin you will have to move your mods to Penumbra.", ImGuiColors.DalamudYellow);
|
|
||||||
if (!_uiShared.DrawOtherPluginState(intro: true)) return;
|
if (!_uiShared.DrawOtherPluginState(intro: true)) return;
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
if (ImGui.Button("Next##toAgreement"))
|
if (ImGui.Button(L("Intro.Welcome.Next", "Next") + "##toAgreement"))
|
||||||
{
|
{
|
||||||
_readFirstPage = true;
|
_readFirstPage = true;
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
@@ -127,7 +127,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
for (int i = 10; i > 0; i--)
|
for (int i = 10; i > 0; i--)
|
||||||
{
|
{
|
||||||
_timeoutLabel = $"'I agree' button will be available in {i}s";
|
_timeoutLabel = L("Intro.Agreement.Timeout", "'I agree' button will be available in {0}s", i);
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -140,41 +140,40 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
using (_uiShared.UidFont.Push())
|
using (_uiShared.UidFont.Push())
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("Agreement of Usage of Service");
|
ImGui.TextUnformatted(L("Intro.Agreement.Title", "Agreement of Usage of Service"));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.SetWindowFontScale(1.5f);
|
ImGui.SetWindowFontScale(1.5f);
|
||||||
string readThis = "READ THIS CAREFULLY";
|
string readThis = L("Intro.Agreement.Callout", "READ THIS CAREFULLY");
|
||||||
Vector2 textSize = ImGui.CalcTextSize(readThis);
|
Vector2 textSize = ImGui.CalcTextSize(readThis);
|
||||||
ImGui.SetCursorPosX(ImGui.GetWindowSize().X / 2 - textSize.X / 2);
|
ImGui.SetCursorPosX(ImGui.GetWindowSize().X / 2 - textSize.X / 2);
|
||||||
UiSharedService.ColorText(readThis, ImGuiColors.DalamudRed);
|
UiSharedService.ColorText(readThis, ImGuiColors.DalamudRed);
|
||||||
ImGui.SetWindowFontScale(1.0f);
|
ImGui.SetWindowFontScale(1.0f);
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
var agreementParagraphs = new[]
|
||||||
|
{
|
||||||
|
L("Intro.Agreement.Paragraph1", "To use Umbra, you must be over the age of 18, or 21 in some jurisdictions."),
|
||||||
|
L("Intro.Agreement.Paragraph2", "All of the mod files currently active on your character as well as your current character state will be uploaded to the service you registered yourself at automatically. The plugin will exclusively upload the necessary mod files and not the whole mod."),
|
||||||
|
L("Intro.Agreement.Paragraph3", "If you are on a data capped internet connection, higher fees due to data usage depending on the amount of downloaded and uploaded mod files might occur. Mod files will be compressed on up- and download to save on bandwidth usage. Due to varying up- and download speeds, changes in characters might not be visible immediately. Files present on the service that already represent your active mod files will not be uploaded again."),
|
||||||
|
L("Intro.Agreement.Paragraph4", "The mod files you are uploading are confidential and will not be distributed to parties other than the ones who are requesting the exact same mod files. Please think about who you are going to pair since it is unavoidable that they will receive and locally cache the necessary mod files that you have currently in use. Locally cached mod files will have arbitrary file names to discourage attempts at replicating the original mod."),
|
||||||
|
L("Intro.Agreement.Paragraph5", "The plugin creator tried their best to keep you secure. However, there is no guarantee for 100% security. Do not blindly pair your client with everyone."),
|
||||||
|
L("Intro.Agreement.Paragraph6", "Mod files that are saved on the service will remain on the service as long as there are requests for the files from clients. After a period of not being used, the mod files will be automatically deleted."),
|
||||||
|
L("Intro.Agreement.Paragraph7", "Accounts that are inactive for ninety (90) days will be deleted for privacy reasons."),
|
||||||
|
L("Intro.Agreement.Paragraph8", "Umbra is operated from servers located in the European Union. You agree not to upload any content to the service that violates EU law; and more specifically, German law."),
|
||||||
|
L("Intro.Agreement.Paragraph9", "You may delete your account at any time from within the Settings panel of the plugin. Any mods unique to you will then be removed from the server within 14 days."),
|
||||||
|
L("Intro.Agreement.Paragraph10", "This service is provided as-is."),
|
||||||
|
};
|
||||||
|
|
||||||
UiSharedService.TextWrapped("""
|
foreach (var paragraph in agreementParagraphs)
|
||||||
All of the mod files currently active on your character as well as your current character state will be uploaded to the service you registered yourself at automatically. The plugin will exclusively upload the necessary mod files and not the whole mod.
|
{
|
||||||
""");
|
UiSharedService.TextWrapped(paragraph);
|
||||||
UiSharedService.TextWrapped("""
|
}
|
||||||
If you are on a data capped internet connection, higher fees due to data usage depending on the amount of downloaded and uploaded mod files might occur. Mod files will be compressed on up- and download to save on bandwidth usage. Due to varying up- and download speeds, changes in characters might not be visible immediately. Files present on the service that already represent your active mod files will not be uploaded again.
|
|
||||||
""");
|
|
||||||
UiSharedService.TextWrapped("""
|
|
||||||
The mod files you are uploading are confidential and will not be distributed to parties other than the ones who are requesting the exact same mod files. Please think about who you are going to pair since it is unavoidable that they will receive and locally cache the necessary mod files that you have currently in use. Locally cached mod files will have arbitrary file names to discourage attempts at replicating the original mod.
|
|
||||||
""");
|
|
||||||
UiSharedService.TextWrapped("""
|
|
||||||
The plugin creator tried their best to keep you secure. However, there is no guarantee for 100% security. Do not blindly pair your client with everyone.
|
|
||||||
""");
|
|
||||||
UiSharedService.TextWrapped("""
|
|
||||||
Mod files that are saved on the service will remain on the service as long as there are requests for the files from clients. After a period of not being used, the mod files will be automatically deleted.
|
|
||||||
""");
|
|
||||||
UiSharedService.TextWrapped("""
|
|
||||||
This service is provided as-is.
|
|
||||||
""");
|
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
if (_timeoutTask?.IsCompleted ?? true)
|
if (_timeoutTask?.IsCompleted ?? true)
|
||||||
{
|
{
|
||||||
if (ImGui.Button("I agree##toSetup"))
|
if (ImGui.Button(L("Intro.Agreement.Accept", "I agree") + "##toSetup"))
|
||||||
{
|
{
|
||||||
_configService.Current.AcceptedAgreement = true;
|
_configService.Current.AcceptedAgreement = true;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
@@ -191,29 +190,26 @@ This service is provided as-is.
|
|||||||
|| !Directory.Exists(_configService.Current.CacheFolder)))
|
|| !Directory.Exists(_configService.Current.CacheFolder)))
|
||||||
{
|
{
|
||||||
using (_uiShared.UidFont.Push())
|
using (_uiShared.UidFont.Push())
|
||||||
ImGui.TextUnformatted("File Storage Setup");
|
ImGui.TextUnformatted(L("Intro.Storage.Title", "File Storage Setup"));
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
if (!_uiShared.HasValidPenumbraModPath)
|
if (!_uiShared.HasValidPenumbraModPath)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped("You do not have a valid Penumbra path set. Open Penumbra and set up a valid path for the mod directory.", ImGuiColors.DalamudRed);
|
UiSharedService.ColorTextWrapped(L("Intro.Storage.NoPenumbra", "You do not have a valid Penumbra path set. Open Penumbra and set up a valid path for the mod directory."), ImGuiColors.DalamudRed);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UiSharedService.TextWrapped("To not unnecessary download files already present on your computer, UmbraSync will have to scan your Penumbra mod directory. " +
|
UiSharedService.TextWrapped(L("Intro.Storage.Description", "To not unnecessarily download files already present on your computer, Umbra will have to scan your Penumbra mod directory. Additionally, a local storage folder must be set where Umbra will download other character files to. Once the storage folder is set and the scan complete, this page will automatically forward to registration at a service."));
|
||||||
"Additionally, a local storage folder must be set where UmbraSync will download other character files to. " +
|
UiSharedService.TextWrapped(L("Intro.Storage.ScanNote", "Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed."));
|
||||||
"Once the storage folder is set and the scan complete, this page will automatically forward to registration at a service.");
|
UiSharedService.ColorTextWrapped(L("Intro.Storage.Warning.FileCache", "Warning: once past this step you should not delete the FileCache.csv of Umbra in the Plugin Configurations folder of Dalamud. Otherwise on the next launch a full re-scan of the file cache database will be initiated."), ImGuiColors.DalamudYellow);
|
||||||
UiSharedService.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed.");
|
UiSharedService.ColorTextWrapped(L("Intro.Storage.Warning.ScanHang", "Warning: if the scan is hanging and does nothing for a long time, chances are high your Penumbra folder is not set up properly."), ImGuiColors.DalamudYellow);
|
||||||
UiSharedService.ColorTextWrapped("Warning: once past this step you should not delete the FileCache.csv of UmbraSync in the Plugin Configurations folder of Dalamud. " +
|
|
||||||
"Otherwise on the next launch a full re-scan of the file cache database will be initiated.", ImGuiColors.DalamudYellow);
|
|
||||||
UiSharedService.ColorTextWrapped("Warning: if the scan is hanging and does nothing for a long time, chances are high your Penumbra folder is not set up properly.", ImGuiColors.DalamudYellow);
|
|
||||||
_uiShared.DrawCacheDirectorySetting();
|
_uiShared.DrawCacheDirectorySetting();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_cacheMonitor.IsScanRunning && !string.IsNullOrEmpty(_configService.Current.CacheFolder) && _uiShared.HasValidPenumbraModPath && Directory.Exists(_configService.Current.CacheFolder))
|
if (!_cacheMonitor.IsScanRunning && !string.IsNullOrEmpty(_configService.Current.CacheFolder) && _uiShared.HasValidPenumbraModPath && Directory.Exists(_configService.Current.CacheFolder))
|
||||||
{
|
{
|
||||||
if (ImGui.Button("Start Scan##startScan"))
|
if (ImGui.Button(L("Intro.Storage.StartScan", "Start Scan") + "##startScan"))
|
||||||
{
|
{
|
||||||
_cacheMonitor.InvokeScan();
|
_cacheMonitor.InvokeScan();
|
||||||
}
|
}
|
||||||
@@ -225,22 +221,21 @@ This service is provided as-is.
|
|||||||
if (!_dalamudUtilService.IsWine)
|
if (!_dalamudUtilService.IsWine)
|
||||||
{
|
{
|
||||||
var useFileCompactor = _configService.Current.UseCompactor;
|
var useFileCompactor = _configService.Current.UseCompactor;
|
||||||
if (ImGui.Checkbox("Use File Compactor", ref useFileCompactor))
|
if (ImGui.Checkbox(L("Intro.Storage.UseCompactor", "Use File Compactor"), ref useFileCompactor))
|
||||||
{
|
{
|
||||||
_configService.Current.UseCompactor = useFileCompactor;
|
_configService.Current.UseCompactor = useFileCompactor;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
}
|
}
|
||||||
UiSharedService.ColorTextWrapped("The File Compactor can save a tremendeous amount of space on the hard disk for downloads through UmbraSync. It will incur a minor CPU penalty on download but can speed up " +
|
UiSharedService.ColorTextWrapped(L("Intro.Storage.CompactorDescription", "The File Compactor can save a tremendous amount of space on the hard disk for downloads through Umbra. It will incur a minor CPU penalty on download but can speed up loading of other characters. It is recommended to keep it enabled. You can change this setting later anytime in the Umbra settings."), ImGuiColors.DalamudYellow);
|
||||||
"loading of other characters. It is recommended to keep it enabled. You can change this setting later anytime in the UmbraSync settings.", ImGuiColors.DalamudYellow);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!_uiShared.ApiController.IsConnected)
|
else if (!_uiShared.ApiController.IsConnected)
|
||||||
{
|
{
|
||||||
using (_uiShared.UidFont.Push())
|
using (_uiShared.UidFont.Push())
|
||||||
ImGui.TextUnformatted("Service Registration");
|
ImGui.TextUnformatted(L("Intro.Registration.Title", "Service Registration"));
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
UiSharedService.TextWrapped("To be able to use UmbraSync you will have to register an account.");
|
UiSharedService.TextWrapped(L("Intro.Registration.Description", "To be able to use Umbra you will have to register an account."));
|
||||||
UiSharedService.TextWrapped("Refer to the instructions at the location you obtained this plugin for more information or support.");
|
UiSharedService.TextWrapped(L("Intro.Registration.Support", "Refer to the instructions at the location you obtained this plugin for more information or support."));
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
@@ -251,8 +246,8 @@ This service is provided as-is.
|
|||||||
{
|
{
|
||||||
ImGui.BeginDisabled(_registrationInProgress || _registrationSuccess || _secretKey.Length > 0);
|
ImGui.BeginDisabled(_registrationInProgress || _registrationSuccess || _secretKey.Length > 0);
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.TextUnformatted("If you have not used UmbraSync before, click below to register a new account.");
|
ImGui.TextUnformatted(L("Intro.Registration.NewAccountInfo", "If you have not used Umbra before, click below to register a new account."));
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Plus, "Register a new UmbraSync account"))
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Plus, L("Intro.Registration.RegisterButton", "Register a new Umbra account")))
|
||||||
{
|
{
|
||||||
_registrationInProgress = true;
|
_registrationInProgress = true;
|
||||||
_ = Task.Run(async () => {
|
_ = Task.Run(async () => {
|
||||||
@@ -262,12 +257,12 @@ This service is provided as-is.
|
|||||||
if (!reply.Success)
|
if (!reply.Success)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Registration failed: {err}", reply.ErrorMessage);
|
_logger.LogWarning("Registration failed: {err}", reply.ErrorMessage);
|
||||||
_registrationMessage = reply.ErrorMessage;
|
_registrationMessage = string.IsNullOrEmpty(reply.ErrorMessage)
|
||||||
if (_registrationMessage.IsNullOrEmpty())
|
? L("Intro.Registration.UnknownError", "An unknown error occured. Please try again later.")
|
||||||
_registrationMessage = "An unknown error occured. Please try again later.";
|
: reply.ErrorMessage;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_registrationMessage = "New account registered.\nPlease keep a copy of your secret key in case you need to reset your plugins, or to use it on another PC.";
|
_registrationMessage = L("Intro.Registration.Success", "New account registered.\nPlease keep a copy of your secret key in case you need to reset your plugins, or to use it on another PC.");
|
||||||
_secretKey = reply.SecretKey ?? "";
|
_secretKey = reply.SecretKey ?? "";
|
||||||
_registrationReply = reply;
|
_registrationReply = reply;
|
||||||
_registrationSuccess = true;
|
_registrationSuccess = true;
|
||||||
@@ -276,7 +271,7 @@ This service is provided as-is.
|
|||||||
{
|
{
|
||||||
_logger.LogWarning(ex, "Registration failed");
|
_logger.LogWarning(ex, "Registration failed");
|
||||||
_registrationSuccess = false;
|
_registrationSuccess = false;
|
||||||
_registrationMessage = "An unknown error occured. Please try again later.";
|
_registrationMessage = L("Intro.Registration.UnknownError", "An unknown error occured. Please try again later.");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -287,7 +282,7 @@ This service is provided as-is.
|
|||||||
ImGui.EndDisabled(); // _registrationInProgress || _registrationSuccess
|
ImGui.EndDisabled(); // _registrationInProgress || _registrationSuccess
|
||||||
if (_registrationInProgress)
|
if (_registrationInProgress)
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("Sending request...");
|
ImGui.TextUnformatted(L("Intro.Registration.SendingRequest", "Sending request..."));
|
||||||
}
|
}
|
||||||
else if (!_registrationMessage.IsNullOrEmpty())
|
else if (!_registrationMessage.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
@@ -300,42 +295,40 @@ This service is provided as-is.
|
|||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
var text = "Enter Secret Key";
|
var secretKeyLabel = _registrationSuccess
|
||||||
|
? L("Intro.Registration.SecretKeyLabelRegistered", "Secret Key")
|
||||||
|
: L("Intro.Registration.SecretKeyLabel", "Enter Secret Key");
|
||||||
|
|
||||||
if (_registrationSuccess)
|
if (!_registrationSuccess)
|
||||||
{
|
{
|
||||||
text = "Secret Key";
|
ImGui.TextUnformatted(L("Intro.Registration.SecretKeyInstructions", "If you already have a registered account, you can enter its secret key below to use it instead."));
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.TextUnformatted("If you already have a registered account, you can enter its secret key below to use it instead.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var textSize = ImGui.CalcTextSize(text);
|
var textSize = ImGui.CalcTextSize(secretKeyLabel);
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted(text);
|
ImGui.TextUnformatted(secretKeyLabel);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetNextItemWidth(UiSharedService.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - textSize.X);
|
ImGui.SetNextItemWidth(UiSharedService.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - textSize.X);
|
||||||
ImGui.InputText("", ref _secretKey, 64);
|
ImGui.InputText("", ref _secretKey, 64);
|
||||||
if (_secretKey.Length > 0 && _secretKey.Length != 64)
|
if (_secretKey.Length > 0 && _secretKey.Length != 64)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped("Your secret key must be exactly 64 characters long.", ImGuiColors.DalamudRed);
|
UiSharedService.ColorTextWrapped(L("Intro.Registration.SecretKeyLength", "Your secret key must be exactly 64 characters long."), ImGuiColors.DalamudRed);
|
||||||
}
|
}
|
||||||
else if (_secretKey.Length == 64 && !HexRegex().IsMatch(_secretKey))
|
else if (_secretKey.Length == 64 && !HexRegex().IsMatch(_secretKey))
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped("Your secret key can only contain ABCDEF and the numbers 0-9.", ImGuiColors.DalamudRed);
|
UiSharedService.ColorTextWrapped(L("Intro.Registration.SecretKeyCharacters", "Your secret key can only contain ABCDEF and the numbers 0-9."), ImGuiColors.DalamudRed);
|
||||||
}
|
}
|
||||||
else if (_secretKey.Length == 64)
|
else if (_secretKey.Length == 64)
|
||||||
{
|
{
|
||||||
using var saveDisabled = ImRaii.Disabled(_uiShared.ApiController.ServerState == ServerState.Connecting || _uiShared.ApiController.ServerState == ServerState.Reconnecting);
|
using var saveDisabled = ImRaii.Disabled(_uiShared.ApiController.ServerState == ServerState.Connecting || _uiShared.ApiController.ServerState == ServerState.Reconnecting);
|
||||||
if (ImGui.Button("Save and Connect"))
|
if (ImGui.Button(L("Intro.Registration.SaveAndConnect", "Save and Connect")))
|
||||||
{
|
{
|
||||||
string keyName;
|
string keyName;
|
||||||
if (_serverConfigurationManager.CurrentServer == null) _serverConfigurationManager.SelectServer(0);
|
if (_serverConfigurationManager.CurrentServer == null) _serverConfigurationManager.SelectServer(0);
|
||||||
if (_registrationReply != null && _secretKey.Equals(_registrationReply.SecretKey, StringComparison.Ordinal))
|
if (_registrationReply != null && _secretKey.Equals(_registrationReply.SecretKey, StringComparison.Ordinal))
|
||||||
keyName = _registrationReply.UID + $" (registered {DateTime.Now:yyyy-MM-dd})";
|
keyName = _registrationReply.UID + " " + L("Intro.Registration.SavedKeyRegistered", "(registered {0})", DateTime.Now.ToString("yyyy-MM-dd"));
|
||||||
else
|
else
|
||||||
keyName = $"Secret Key added on Setup ({DateTime.Now:yyyy-MM-dd})";
|
keyName = L("Intro.Registration.SavedKeySetup", "Secret Key added on Setup ({0})", DateTime.Now.ToString("yyyy-MM-dd"));
|
||||||
_serverConfigurationManager.CurrentServer!.SecretKeys.Add(_serverConfigurationManager.CurrentServer.SecretKeys.Select(k => k.Key).LastOrDefault() + 1, new SecretKey()
|
_serverConfigurationManager.CurrentServer!.SecretKeys.Add(_serverConfigurationManager.CurrentServer.SecretKeys.Select(k => k.Key).LastOrDefault() + 1, new SecretKey()
|
||||||
{
|
{
|
||||||
FriendlyName = keyName,
|
FriendlyName = keyName,
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
public PermissionWindowUI(ILogger<PermissionWindowUI> logger, Pair pair, MareMediator mediator, UiSharedService uiSharedService,
|
public PermissionWindowUI(ILogger<PermissionWindowUI> logger, Pair pair, MareMediator mediator, UiSharedService uiSharedService,
|
||||||
ApiController apiController, PerformanceCollectorService performanceCollectorService)
|
ApiController apiController, PerformanceCollectorService performanceCollectorService)
|
||||||
: base(logger, mediator, "Permissions for " + pair.UserData.AliasOrUID + "###UmbraSyncSyncPermissions" + pair.UserData.UID, performanceCollectorService)
|
: base(logger, mediator,
|
||||||
|
uiSharedService.Localize("PermissionWindow.Title", "Permissions for {0}", pair.UserData.AliasOrUID) + "###UmbraSyncPermissions" + pair.UserData.UID,
|
||||||
|
performanceCollectorService)
|
||||||
{
|
{
|
||||||
Pair = pair;
|
Pair = pair;
|
||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
@@ -37,6 +39,8 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase
|
|||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string L(string key, string fallback, params object[] args) => _uiSharedService.Localize(key, fallback, args);
|
||||||
|
|
||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
{
|
{
|
||||||
var paused = _ownPermissions.IsPaused();
|
var paused = _ownPermissions.IsPaused();
|
||||||
@@ -46,18 +50,19 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase
|
|||||||
var style = ImGui.GetStyle();
|
var style = ImGui.GetStyle();
|
||||||
var indentSize = ImGui.GetFrameHeight() + style.ItemSpacing.X;
|
var indentSize = ImGui.GetFrameHeight() + style.ItemSpacing.X;
|
||||||
|
|
||||||
_uiSharedService.BigText("Permissions for " + Pair.UserData.AliasOrUID);
|
_uiSharedService.BigText(L("PermissionWindow.Title", "Permissions for {0}", Pair.UserData.AliasOrUID));
|
||||||
ImGuiHelpers.ScaledDummy(1f);
|
ImGuiHelpers.ScaledDummy(1f);
|
||||||
|
|
||||||
if (Pair.UserPair == null)
|
if (Pair.UserPair == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (ImGui.Checkbox("Pause Sync", ref paused))
|
if (ImGui.Checkbox(L("PermissionWindow.Pause.Label", "Pause Sync"), ref paused))
|
||||||
{
|
{
|
||||||
_ownPermissions.SetPaused(paused);
|
_ownPermissions.SetPaused(paused);
|
||||||
}
|
}
|
||||||
_uiSharedService.DrawHelpText("Pausing will completely cease any sync with this user." + UiSharedService.TooltipSeparator
|
_uiSharedService.DrawHelpText(L("PermissionWindow.Pause.HelpMain", "Pausing will completely cease any sync with this user.")
|
||||||
+ "Note: this is bidirectional, either user pausing will cease sync completely.");
|
+ UiSharedService.TooltipSeparator
|
||||||
|
+ L("PermissionWindow.Pause.HelpNote", "Note: this is bidirectional, either user pausing will cease sync completely."));
|
||||||
var otherPerms = Pair.UserPair.OtherPermissions;
|
var otherPerms = Pair.UserPair.OtherPermissions;
|
||||||
|
|
||||||
var otherIsPaused = otherPerms.IsPaused();
|
var otherIsPaused = otherPerms.IsPaused();
|
||||||
@@ -70,53 +75,68 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase
|
|||||||
_uiSharedService.BooleanToColoredIcon(!otherIsPaused, false);
|
_uiSharedService.BooleanToColoredIcon(!otherIsPaused, false);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted(Pair.UserData.AliasOrUID + " has " + (!otherIsPaused ? "not " : string.Empty) + "paused you");
|
var pausedText = otherIsPaused
|
||||||
|
? L("PermissionWindow.OtherPaused.True", "{0} has paused you", Pair.UserData.AliasOrUID)
|
||||||
|
: L("PermissionWindow.OtherPaused.False", "{0} has not paused you", Pair.UserData.AliasOrUID);
|
||||||
|
ImGui.TextUnformatted(pausedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(0.5f);
|
ImGuiHelpers.ScaledDummy(0.5f);
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGuiHelpers.ScaledDummy(0.5f);
|
ImGuiHelpers.ScaledDummy(0.5f);
|
||||||
|
|
||||||
if (ImGui.Checkbox("Disable Sounds", ref disableSounds))
|
if (ImGui.Checkbox(L("PermissionWindow.Sounds.Label", "Disable Sounds"), ref disableSounds))
|
||||||
{
|
{
|
||||||
_ownPermissions.SetDisableSounds(disableSounds);
|
_ownPermissions.SetDisableSounds(disableSounds);
|
||||||
}
|
}
|
||||||
_uiSharedService.DrawHelpText("Disabling sounds will remove all sounds synced with this user on both sides." + UiSharedService.TooltipSeparator
|
_uiSharedService.DrawHelpText(L("PermissionWindow.Sounds.HelpMain", "Disabling sounds will remove all sounds synced with this user on both sides.")
|
||||||
+ "Note: this is bidirectional, either user disabling sound sync will stop sound sync on both sides.");
|
+ UiSharedService.TooltipSeparator
|
||||||
|
+ L("PermissionWindow.Sounds.HelpNote", "Note: this is bidirectional, either user disabling sound sync will stop sound sync on both sides."));
|
||||||
using (ImRaii.PushIndent(indentSize, false))
|
using (ImRaii.PushIndent(indentSize, false))
|
||||||
{
|
{
|
||||||
_uiSharedService.BooleanToColoredIcon(!otherDisableSounds, false);
|
_uiSharedService.BooleanToColoredIcon(!otherDisableSounds, false);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted(Pair.UserData.AliasOrUID + " has " + (!otherDisableSounds ? "not " : string.Empty) + "disabled sound sync with you");
|
var soundText = otherDisableSounds
|
||||||
|
? L("PermissionWindow.OtherSoundDisabled.True", "{0} has disabled sound sync with you", Pair.UserData.AliasOrUID)
|
||||||
|
: L("PermissionWindow.OtherSoundDisabled.False", "{0} has not disabled sound sync with you", Pair.UserData.AliasOrUID);
|
||||||
|
ImGui.TextUnformatted(soundText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Checkbox("Disable Animations", ref disableAnimations))
|
if (ImGui.Checkbox(L("PermissionWindow.Animations.Label", "Disable Animations"), ref disableAnimations))
|
||||||
{
|
{
|
||||||
_ownPermissions.SetDisableAnimations(disableAnimations);
|
_ownPermissions.SetDisableAnimations(disableAnimations);
|
||||||
}
|
}
|
||||||
_uiSharedService.DrawHelpText("Disabling sounds will remove all animations synced with this user on both sides." + UiSharedService.TooltipSeparator
|
_uiSharedService.DrawHelpText(L("PermissionWindow.Animations.HelpMain", "Disabling sounds will remove all animations synced with this user on both sides.")
|
||||||
+ "Note: this is bidirectional, either user disabling animation sync will stop animation sync on both sides.");
|
+ UiSharedService.TooltipSeparator
|
||||||
|
+ L("PermissionWindow.Animations.HelpNote", "Note: this is bidirectional, either user disabling animation sync will stop animation sync on both sides."));
|
||||||
using (ImRaii.PushIndent(indentSize, false))
|
using (ImRaii.PushIndent(indentSize, false))
|
||||||
{
|
{
|
||||||
_uiSharedService.BooleanToColoredIcon(!otherDisableAnimations, false);
|
_uiSharedService.BooleanToColoredIcon(!otherDisableAnimations, false);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted(Pair.UserData.AliasOrUID + " has " + (!otherDisableAnimations ? "not " : string.Empty) + "disabled animation sync with you");
|
var animText = otherDisableAnimations
|
||||||
|
? L("PermissionWindow.OtherAnimationDisabled.True", "{0} has disabled animation sync with you", Pair.UserData.AliasOrUID)
|
||||||
|
: L("PermissionWindow.OtherAnimationDisabled.False", "{0} has not disabled animation sync with you", Pair.UserData.AliasOrUID);
|
||||||
|
ImGui.TextUnformatted(animText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Checkbox("Disable VFX", ref disableVfx))
|
if (ImGui.Checkbox(L("PermissionWindow.Vfx.Label", "Disable VFX"), ref disableVfx))
|
||||||
{
|
{
|
||||||
_ownPermissions.SetDisableVFX(disableVfx);
|
_ownPermissions.SetDisableVFX(disableVfx);
|
||||||
}
|
}
|
||||||
_uiSharedService.DrawHelpText("Disabling sounds will remove all VFX synced with this user on both sides." + UiSharedService.TooltipSeparator
|
_uiSharedService.DrawHelpText(L("PermissionWindow.Vfx.HelpMain", "Disabling sounds will remove all VFX synced with this user on both sides.")
|
||||||
+ "Note: this is bidirectional, either user disabling VFX sync will stop VFX sync on both sides.");
|
+ UiSharedService.TooltipSeparator
|
||||||
|
+ L("PermissionWindow.Vfx.HelpNote", "Note: this is bidirectional, either user disabling VFX sync will stop VFX sync on both sides."));
|
||||||
using (ImRaii.PushIndent(indentSize, false))
|
using (ImRaii.PushIndent(indentSize, false))
|
||||||
{
|
{
|
||||||
_uiSharedService.BooleanToColoredIcon(!otherDisableVFX, false);
|
_uiSharedService.BooleanToColoredIcon(!otherDisableVFX, false);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted(Pair.UserData.AliasOrUID + " has " + (!otherDisableVFX ? "not " : string.Empty) + "disabled VFX sync with you");
|
var vfxText = otherDisableVFX
|
||||||
|
? L("PermissionWindow.OtherVfxDisabled.True", "{0} has disabled VFX sync with you", Pair.UserData.AliasOrUID)
|
||||||
|
: L("PermissionWindow.OtherVfxDisabled.False", "{0} has not disabled VFX sync with you", Pair.UserData.AliasOrUID);
|
||||||
|
ImGui.TextUnformatted(vfxText);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(0.5f);
|
ImGuiHelpers.ScaledDummy(0.5f);
|
||||||
@@ -126,27 +146,27 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase
|
|||||||
bool hasChanges = _ownPermissions != Pair.UserPair.OwnPermissions;
|
bool hasChanges = _ownPermissions != Pair.UserPair.OwnPermissions;
|
||||||
|
|
||||||
using (ImRaii.Disabled(!hasChanges))
|
using (ImRaii.Disabled(!hasChanges))
|
||||||
if (_uiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.Save, "Save"))
|
if (_uiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.Save, L("PermissionWindow.Button.Save", "Save")))
|
||||||
{
|
{
|
||||||
_ = _apiController.UserSetPairPermissions(new(Pair.UserData, _ownPermissions));
|
_ = _apiController.UserSetPairPermissions(new(Pair.UserData, _ownPermissions));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Save and apply all changes");
|
UiSharedService.AttachToolTip(L("PermissionWindow.Tooltip.Save", "Save and apply all changes"));
|
||||||
|
|
||||||
var rightSideButtons = _uiSharedService.GetIconTextButtonSize(Dalamud.Interface.FontAwesomeIcon.Undo, "Revert") +
|
var rightSideButtons = _uiSharedService.GetIconTextButtonSize(Dalamud.Interface.FontAwesomeIcon.Undo, L("PermissionWindow.Button.Revert", "Revert")) +
|
||||||
_uiSharedService.GetIconTextButtonSize(Dalamud.Interface.FontAwesomeIcon.ArrowsSpin, "Reset to Default");
|
_uiSharedService.GetIconTextButtonSize(Dalamud.Interface.FontAwesomeIcon.ArrowsSpin, L("PermissionWindow.Button.Reset", "Reset to Default"));
|
||||||
var availableWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X;
|
var availableWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X;
|
||||||
|
|
||||||
ImGui.SameLine(availableWidth - rightSideButtons);
|
ImGui.SameLine(availableWidth - rightSideButtons);
|
||||||
|
|
||||||
using (ImRaii.Disabled(!hasChanges))
|
using (ImRaii.Disabled(!hasChanges))
|
||||||
if (_uiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.Undo, "Revert"))
|
if (_uiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.Undo, L("PermissionWindow.Button.Revert", "Revert")))
|
||||||
{
|
{
|
||||||
_ownPermissions = Pair.UserPair.OwnPermissions.DeepClone();
|
_ownPermissions = Pair.UserPair.OwnPermissions.DeepClone();
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Revert all changes");
|
UiSharedService.AttachToolTip(L("PermissionWindow.Tooltip.Revert", "Revert all changes"));
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (_uiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.ArrowsSpin, "Reset to Default"))
|
if (_uiSharedService.IconTextButton(Dalamud.Interface.FontAwesomeIcon.ArrowsSpin, L("PermissionWindow.Button.Reset", "Reset to Default")))
|
||||||
{
|
{
|
||||||
_ownPermissions.SetPaused(false);
|
_ownPermissions.SetPaused(false);
|
||||||
_ownPermissions.SetDisableVFX(false);
|
_ownPermissions.SetDisableVFX(false);
|
||||||
@@ -154,7 +174,7 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase
|
|||||||
_ownPermissions.SetDisableAnimations(false);
|
_ownPermissions.SetDisableAnimations(false);
|
||||||
_ = _apiController.UserSetPairPermissions(new(Pair.UserData, _ownPermissions));
|
_ = _apiController.UserSetPairPermissions(new(Pair.UserData, _ownPermissions));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("This will set all permissions to their default setting");
|
UiSharedService.AttachToolTip(L("PermissionWindow.Tooltip.Reset", "This will set all permissions to their default setting"));
|
||||||
|
|
||||||
var ySize = ImGui.GetCursorPosY() + style.FramePadding.Y * ImGuiHelpers.GlobalScale + style.FrameBorderSize;
|
var ySize = ImGui.GetCursorPosY() + style.FramePadding.Y * ImGuiHelpers.GlobalScale + style.FrameBorderSize;
|
||||||
ImGui.SetWindowSize(new(400, ySize));
|
ImGui.SetWindowSize(new(400, ySize));
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class PlayerAnalysisUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
public PlayerAnalysisUI(ILogger<PlayerAnalysisUI> logger, Pair pair, MareMediator mediator, UiSharedService uiSharedService,
|
public PlayerAnalysisUI(ILogger<PlayerAnalysisUI> logger, Pair pair, MareMediator mediator, UiSharedService uiSharedService,
|
||||||
PerformanceCollectorService performanceCollectorService)
|
PerformanceCollectorService performanceCollectorService)
|
||||||
: base(logger, mediator, "Character Data Analysis for " + pair.UserData.AliasOrUID + "###UmbraSyncPairAnalysis" + pair.UserData.UID, performanceCollectorService)
|
: base(logger, mediator, "Character Data Analysis for " + pair.UserData.AliasOrUID + "###UmbraPairAnalysis" + pair.UserData.UID, performanceCollectorService)
|
||||||
{
|
{
|
||||||
Pair = pair;
|
Pair = pair;
|
||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class PopoutProfileUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
public PopoutProfileUi(ILogger<PopoutProfileUi> logger, MareMediator mediator, UiSharedService uiSharedService,
|
public PopoutProfileUi(ILogger<PopoutProfileUi> logger, MareMediator mediator, UiSharedService uiSharedService,
|
||||||
ServerConfigurationManager serverManager, MareConfigService mareConfigService,
|
ServerConfigurationManager serverManager, MareConfigService mareConfigService,
|
||||||
MareProfileManager mareProfileManager, PairManager pairManager, PerformanceCollectorService performanceCollectorService) : base(logger, mediator, "###UmbraSyncSyncPopoutProfileUI", performanceCollectorService)
|
MareProfileManager mareProfileManager, PairManager pairManager, PerformanceCollectorService performanceCollectorService) : base(logger, mediator, "###UmbraSyncPopoutProfileUI", performanceCollectorService)
|
||||||
{
|
{
|
||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
_serverManager = serverManager;
|
_serverManager = serverManager;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
|||||||
public StandaloneProfileUi(ILogger<StandaloneProfileUi> logger, MareMediator mediator, UiSharedService uiBuilder,
|
public StandaloneProfileUi(ILogger<StandaloneProfileUi> logger, MareMediator mediator, UiSharedService uiBuilder,
|
||||||
ServerConfigurationManager serverManager, MareProfileManager mareProfileManager, PairManager pairManager, Pair pair,
|
ServerConfigurationManager serverManager, MareProfileManager mareProfileManager, PairManager pairManager, Pair pair,
|
||||||
PerformanceCollectorService performanceCollector)
|
PerformanceCollectorService performanceCollector)
|
||||||
: base(logger, mediator, "Profile of " + pair.UserData.AliasOrUID + "##UmbraSyncSyncStandaloneProfileUI" + pair.UserData.AliasOrUID, performanceCollector)
|
: base(logger, mediator, "Profile of " + pair.UserData.AliasOrUID + "##UmbraSyncStandaloneProfileUI" + pair.UserData.AliasOrUID, performanceCollector)
|
||||||
{
|
{
|
||||||
_uiSharedService = uiBuilder;
|
_uiSharedService = uiBuilder;
|
||||||
_serverManager = serverManager;
|
_serverManager = serverManager;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using Dalamud.Plugin.Services;
|
|||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using MareSynchronos.FileCache;
|
using MareSynchronos.FileCache;
|
||||||
using MareSynchronos.Interop.Ipc;
|
using MareSynchronos.Interop.Ipc;
|
||||||
|
using MareSynchronos.Localization;
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.MareConfiguration.Models;
|
using MareSynchronos.MareConfiguration.Models;
|
||||||
using MareSynchronos.PlayerData.Pairs;
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
@@ -20,6 +21,7 @@ using MareSynchronos.Services.Mediator;
|
|||||||
using MareSynchronos.Services.ServerConfiguration;
|
using MareSynchronos.Services.ServerConfiguration;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Globalization;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -36,7 +38,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGuiWindowFlags.NoScrollbar |
|
ImGuiWindowFlags.NoScrollbar |
|
||||||
ImGuiWindowFlags.NoScrollWithMouse;
|
ImGuiWindowFlags.NoScrollWithMouse;
|
||||||
|
|
||||||
public static Vector4 AccentColor { get; set; } = ImGuiColors.DalamudYellow;
|
public static Vector4 AccentColor { get; set; } = ImGuiColors.DalamudViolet;
|
||||||
|
|
||||||
public readonly FileDialogManager FileDialogManager;
|
public readonly FileDialogManager FileDialogManager;
|
||||||
|
|
||||||
@@ -53,6 +55,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
private readonly IpcManager _ipcManager;
|
private readonly IpcManager _ipcManager;
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
|
private readonly LocalizationService _localizationService;
|
||||||
private readonly ITextureProvider _textureProvider;
|
private readonly ITextureProvider _textureProvider;
|
||||||
private readonly Dictionary<string, object> _selectedComboItems = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, object> _selectedComboItems = new(StringComparer.Ordinal);
|
||||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||||
@@ -84,7 +87,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
public UiSharedService(ILogger<UiSharedService> logger, IpcManager ipcManager, ApiController apiController,
|
public UiSharedService(ILogger<UiSharedService> logger, IpcManager ipcManager, ApiController apiController,
|
||||||
CacheMonitor cacheMonitor, FileDialogManager fileDialogManager,
|
CacheMonitor cacheMonitor, FileDialogManager fileDialogManager,
|
||||||
MareConfigService configService, DalamudUtilService dalamudUtil, IDalamudPluginInterface pluginInterface,
|
MareConfigService configService, DalamudUtilService dalamudUtil, IDalamudPluginInterface pluginInterface,
|
||||||
ITextureProvider textureProvider,
|
LocalizationService localizationService, ITextureProvider textureProvider,
|
||||||
ServerConfigurationManager serverManager, MareMediator mediator) : base(logger, mediator)
|
ServerConfigurationManager serverManager, MareMediator mediator) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
_ipcManager = ipcManager;
|
_ipcManager = ipcManager;
|
||||||
@@ -94,6 +97,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
_configService = configService;
|
_configService = configService;
|
||||||
_dalamudUtil = dalamudUtil;
|
_dalamudUtil = dalamudUtil;
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
|
_localizationService = localizationService;
|
||||||
_textureProvider = textureProvider;
|
_textureProvider = textureProvider;
|
||||||
_serverConfigurationManager = serverManager;
|
_serverConfigurationManager = serverManager;
|
||||||
|
|
||||||
@@ -124,6 +128,10 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ApiController ApiController => _apiController;
|
public ApiController ApiController => _apiController;
|
||||||
|
public LocalizationService Localization => _localizationService;
|
||||||
|
|
||||||
|
public string Localize(string key, string fallback, params object[] args) => _localizationService.GetString(key, fallback, args);
|
||||||
|
public string Localize(string fallback, params object[] args) => _localizationService.GetString(fallback, args);
|
||||||
|
|
||||||
public bool EditTrackerPosition { get; set; }
|
public bool EditTrackerPosition { get; set; }
|
||||||
|
|
||||||
@@ -310,7 +318,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vector4 GetBoolColor(bool input) => input ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed;
|
public static Vector4 GetBoolColor(bool input) => input ? AccentColor : ImGuiColors.DalamudRed;
|
||||||
|
|
||||||
public float GetIconTextButtonSize(FontAwesomeIcon icon, string text)
|
public float GetIconTextButtonSize(FontAwesomeIcon icon, string text)
|
||||||
{
|
{
|
||||||
@@ -517,7 +525,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
public void BooleanToColoredIcon(bool value, bool inline = true)
|
public void BooleanToColoredIcon(bool value, bool inline = true)
|
||||||
{
|
{
|
||||||
using var colorgreen = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.HealerGreen, value);
|
using var colorgreen = ImRaii.PushColor(ImGuiCol.Text, AccentColor, value);
|
||||||
using var colorred = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed, !value);
|
using var colorred = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed, !value);
|
||||||
|
|
||||||
if (inline) ImGui.SameLine();
|
if (inline) ImGui.SameLine();
|
||||||
@@ -534,7 +542,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
public void DrawCacheDirectorySetting()
|
public void DrawCacheDirectorySetting()
|
||||||
{
|
{
|
||||||
ColorTextWrapped("Note: The storage folder should be somewhere close to root (i.e. C:\\UmbraSyncStorage) in a new empty folder. DO NOT point this to your game folder. DO NOT point this to your Penumbra folder.", ImGuiColors.DalamudYellow);
|
ColorTextWrapped("Note: The storage folder should be somewhere close to root (i.e. C:\\UmbraStorage) in a new empty folder. DO NOT point this to your game folder. DO NOT point this to your Penumbra folder.", ImGuiColors.DalamudYellow);
|
||||||
var cacheDirectory = _configService.Current.CacheFolder;
|
var cacheDirectory = _configService.Current.CacheFolder;
|
||||||
ImGui.SetNextItemWidth(400 * ImGuiHelpers.GlobalScale);
|
ImGui.SetNextItemWidth(400 * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.InputText("Storage Folder##cache", ref cacheDirectory, 255, ImGuiInputTextFlags.ReadOnly);
|
ImGui.InputText("Storage Folder##cache", ref cacheDirectory, 255, ImGuiInputTextFlags.ReadOnly);
|
||||||
@@ -544,7 +552,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
if (IconButton(FontAwesomeIcon.Folder))
|
if (IconButton(FontAwesomeIcon.Folder))
|
||||||
{
|
{
|
||||||
FileDialogManager.OpenFolderDialog("Pick UmbraSync Storage Folder", (success, path) =>
|
FileDialogManager.OpenFolderDialog("Pick Umbra Storage Folder", (success, path) =>
|
||||||
{
|
{
|
||||||
if (!success) return;
|
if (!success) return;
|
||||||
|
|
||||||
@@ -604,7 +612,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
else if (_cacheDirectoryHasOtherFilesThanCache)
|
else if (_cacheDirectoryHasOtherFilesThanCache)
|
||||||
{
|
{
|
||||||
ColorTextWrapped("Your selected directory has files or directories inside that are not UmbraSync related. Use an empty directory or a previous storage directory only.", ImGuiColors.DalamudRed);
|
ColorTextWrapped("Your selected directory has files or directories inside that are not Umbra related. Use an empty directory or a previous storage directory only.", ImGuiColors.DalamudRed);
|
||||||
}
|
}
|
||||||
else if (!_cacheDirectoryIsValidPath)
|
else if (!_cacheDirectoryIsValidPath)
|
||||||
{
|
{
|
||||||
@@ -619,7 +627,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
_configService.Current.MaxLocalCacheInGiB = maxCacheSize;
|
_configService.Current.MaxLocalCacheInGiB = maxCacheSize;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
}
|
}
|
||||||
DrawHelpText("The storage is automatically governed by UmbraSync. It will clear itself automatically once it reaches the set capacity by removing the oldest unused files. You typically do not need to clear it yourself.");
|
DrawHelpText("The storage is automatically governed by Umbra. It will clear itself automatically once it reaches the set capacity by removing the oldest unused files. You typically do not need to clear it yourself.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public T? DrawCombo<T>(string comboName, IEnumerable<T> comboItems, Func<T, string> toName,
|
public T? DrawCombo<T>(string comboName, IEnumerable<T> comboItems, Func<T, string> toName,
|
||||||
@@ -761,15 +769,19 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
var check = FontAwesomeIcon.Check;
|
var check = FontAwesomeIcon.Check;
|
||||||
var cross = FontAwesomeIcon.SquareXmark;
|
var cross = FontAwesomeIcon.SquareXmark;
|
||||||
|
|
||||||
|
var availableTemplate = Localize("Settings.Plugins.Tooltip.Available", "{0} is available and up to date.");
|
||||||
|
var unavailableTemplate = Localize("Settings.Plugins.Tooltip.Unavailable", "{0} is unavailable or not up to date.");
|
||||||
|
string FormatTooltip(bool exists, string pluginName) => string.Format(CultureInfo.CurrentCulture, exists ? availableTemplate : unavailableTemplate, pluginName);
|
||||||
|
|
||||||
if (intro)
|
if (intro)
|
||||||
{
|
{
|
||||||
ImGui.SetWindowFontScale(0.8f);
|
ImGui.SetWindowFontScale(0.8f);
|
||||||
BigText("Mandatory Plugins");
|
BigText(Localize("Settings.Plugins.MandatoryHeading", "Mandatory Plugins"));
|
||||||
ImGui.SetWindowFontScale(1.0f);
|
ImGui.SetWindowFontScale(1.0f);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("Mandatory Plugins:");
|
ImGui.TextUnformatted(Localize("Settings.Plugins.MandatoryLabel", "Mandatory Plugins:"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,23 +789,23 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
IconText(_penumbraExists ? check : cross, GetBoolColor(_penumbraExists));
|
IconText(_penumbraExists ? check : cross, GetBoolColor(_penumbraExists));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"Penumbra is " + (_penumbraExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip(FormatTooltip(_penumbraExists, "Penumbra"));
|
||||||
|
|
||||||
ImGui.TextUnformatted("Glamourer");
|
ImGui.TextUnformatted("Glamourer");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
IconText(_glamourerExists ? check : cross, GetBoolColor(_glamourerExists));
|
IconText(_glamourerExists ? check : cross, GetBoolColor(_glamourerExists));
|
||||||
AttachToolTip($"Glamourer is " + (_glamourerExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip(FormatTooltip(_glamourerExists, "Glamourer"));
|
||||||
|
|
||||||
if (intro)
|
if (intro)
|
||||||
{
|
{
|
||||||
ImGui.SetWindowFontScale(0.8f);
|
ImGui.SetWindowFontScale(0.8f);
|
||||||
BigText("Optional Addons");
|
BigText(Localize("Settings.Plugins.OptionalHeading", "Optional Addons"));
|
||||||
ImGui.SetWindowFontScale(1.0f);
|
ImGui.SetWindowFontScale(1.0f);
|
||||||
UiSharedService.TextWrapped("These addons are not required for basic operation, but without them you may not see others as intended.");
|
UiSharedService.TextWrapped(Localize("Settings.Plugins.OptionalDescription", "These addons are not required for basic operation, but without them you may not see others as intended."));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("Optional Addons:");
|
ImGui.TextUnformatted(Localize("Settings.Plugins.OptionalLabel", "Optional Addons:"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -803,7 +815,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
IconText(_heelsExists ? check : cross, GetBoolColor(_heelsExists));
|
IconText(_heelsExists ? check : cross, GetBoolColor(_heelsExists));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"SimpleHeels is " + (_heelsExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip(FormatTooltip(_heelsExists, "SimpleHeels"));
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
@@ -811,7 +823,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
IconText(_customizePlusExists ? check : cross, GetBoolColor(_customizePlusExists));
|
IconText(_customizePlusExists ? check : cross, GetBoolColor(_customizePlusExists));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"Customize+ is " + (_customizePlusExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip(FormatTooltip(_customizePlusExists, "Customize+"));
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
@@ -819,7 +831,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
IconText(_honorificExists ? check : cross, GetBoolColor(_honorificExists));
|
IconText(_honorificExists ? check : cross, GetBoolColor(_honorificExists));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"Honorific is " + (_honorificExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip(FormatTooltip(_honorificExists, "Honorific"));
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
@@ -827,7 +839,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
IconText(_petNamesExists ? check : cross, GetBoolColor(_petNamesExists));
|
IconText(_petNamesExists ? check : cross, GetBoolColor(_petNamesExists));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"PetNicknames is " + (_petNamesExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip(FormatTooltip(_petNamesExists, "PetNicknames"));
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
ImGui.SetCursorPosX(alignPos);
|
ImGui.SetCursorPosX(alignPos);
|
||||||
@@ -835,7 +847,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
IconText(_moodlesExists ? check : cross, GetBoolColor(_moodlesExists));
|
IconText(_moodlesExists ? check : cross, GetBoolColor(_moodlesExists));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"Moodles is " + (_moodlesExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip(FormatTooltip(_moodlesExists, "Moodles"));
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
@@ -843,22 +855,12 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
IconText(_brioExists ? check : cross, GetBoolColor(_brioExists));
|
IconText(_brioExists ? check : cross, GetBoolColor(_brioExists));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"Brio is " + (_moodlesExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip(FormatTooltip(_brioExists, "Brio"));
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
if (!_penumbraExists || !_glamourerExists)
|
if (!_penumbraExists || !_glamourerExists)
|
||||||
{
|
{
|
||||||
ImGui.TextColored(ImGuiColors.DalamudRed, "You need to install both Penumbra and Glamourer and keep them up to date to use UmbraSync.");
|
ImGui.TextColored(ImGuiColors.DalamudRed, Localize("Settings.Plugins.WarningMandatoryMissing", "You need to install both Penumbra and Glamourer and keep them up to date to use Umbra."));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (NoSnapService.AnyLoaded)
|
|
||||||
{
|
|
||||||
IconText(FontAwesomeIcon.ExclamationTriangle, ImGuiColors.DalamudYellow);
|
|
||||||
ImGui.SameLine();
|
|
||||||
var cursorX = ImGui.GetCursorPosX();
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudYellow, "Synced player appearances will not apply until incompatible plugins are disabled:");
|
|
||||||
ImGui.SetCursorPosX(cursorX + 16.0f);
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudYellow, NoSnapService.ActivePlugins);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"Author": "SirConstance",
|
"Author": "Keda",
|
||||||
"Name": "UmbraSync",
|
"Name": "UmbraSync",
|
||||||
"Punchline": "Il revient!",
|
"Punchline": "Parce que nous le valons bien.",
|
||||||
"Description": "This plugin will synchronize your Penumbra mods and current Glamourer state with other paired clients automatically.",
|
"Description": "Ce plugin synchronisera automatiquement vos mods Penumbra et l'état actuel de Glamourer avec les autres clients appairés.",
|
||||||
"InternalName": "UmbraSync",
|
"InternalName": "UmbraSync",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"Tags": [
|
"Tags": [
|
||||||
"customization"
|
"customization"
|
||||||
],
|
],
|
||||||
"IconUrl": "",
|
"IconUrl": "https://repo.umbra-sync.net/images/logo.png",
|
||||||
"RepoUrl": "",
|
"RepoUrl": "https://repo.umbra-sync.net/plugin.json",
|
||||||
"CanUnloadAsync": true
|
"CanUnloadAsync": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,18 +17,16 @@ public sealed class AccountRegistrationService : IDisposable
|
|||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly ILogger<AccountRegistrationService> _logger;
|
private readonly ILogger<AccountRegistrationService> _logger;
|
||||||
private readonly ServerConfigurationManager _serverManager;
|
private readonly ServerConfigurationManager _serverManager;
|
||||||
private readonly RemoteConfigurationService _remoteConfig;
|
|
||||||
|
|
||||||
private string GenerateSecretKey()
|
private string GenerateSecretKey()
|
||||||
{
|
{
|
||||||
return Convert.ToHexString(SHA256.HashData(RandomNumberGenerator.GetBytes(64)));
|
return Convert.ToHexString(SHA256.HashData(RandomNumberGenerator.GetBytes(64)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountRegistrationService(ILogger<AccountRegistrationService> logger, ServerConfigurationManager serverManager, RemoteConfigurationService remoteConfig)
|
public AccountRegistrationService(ILogger<AccountRegistrationService> logger, ServerConfigurationManager serverManager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serverManager = serverManager;
|
_serverManager = serverManager;
|
||||||
_remoteConfig = remoteConfig;
|
|
||||||
_httpClient = new(
|
_httpClient = new(
|
||||||
new HttpClientHandler
|
new HttpClientHandler
|
||||||
{
|
{
|
||||||
@@ -47,22 +45,10 @@ public sealed class AccountRegistrationService : IDisposable
|
|||||||
|
|
||||||
public async Task<RegisterReplyDto> RegisterAccount(CancellationToken token)
|
public async Task<RegisterReplyDto> RegisterAccount(CancellationToken token)
|
||||||
{
|
{
|
||||||
var authApiUrl = _serverManager.CurrentApiUrl;
|
|
||||||
|
|
||||||
// Override the API URL used for auth from remote config, if one is available
|
|
||||||
if (authApiUrl.Equals(ApiController.UmbraSyncServiceUri, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
var config = await _remoteConfig.GetConfigAsync<HubConnectionConfig>("mainServer").ConfigureAwait(false) ?? new();
|
|
||||||
if (!string.IsNullOrEmpty(config.ApiUrl))
|
|
||||||
authApiUrl = config.ApiUrl;
|
|
||||||
else
|
|
||||||
authApiUrl = ApiController.UmbraSyncServiceApiUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
var secretKey = GenerateSecretKey();
|
var secretKey = GenerateSecretKey();
|
||||||
var hashedSecretKey = secretKey.GetHash256();
|
var hashedSecretKey = secretKey.GetHash256();
|
||||||
|
|
||||||
Uri postUri = MareAuth.AuthRegisterV2FullPath(new Uri(authApiUrl
|
Uri postUri = MareAuth.AuthRegisterV2FullPath(new Uri(_serverManager.CurrentApiUrl
|
||||||
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
|
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
|
||||||
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
|
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
|
||||||
|
|
||||||
|
|||||||
209
MareSynchronos/WebAPI/AutoDetect/DiscoveryApiClient.cs
Normal file
209
MareSynchronos/WebAPI/AutoDetect/DiscoveryApiClient.cs
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MareSynchronos.WebAPI.SignalR;
|
||||||
|
using MareSynchronos.Services.AutoDetect;
|
||||||
|
|
||||||
|
namespace MareSynchronos.WebAPI.AutoDetect;
|
||||||
|
|
||||||
|
public class DiscoveryApiClient
|
||||||
|
{
|
||||||
|
private readonly ILogger<DiscoveryApiClient> _logger;
|
||||||
|
private readonly TokenProvider _tokenProvider;
|
||||||
|
private readonly DiscoveryConfigProvider _configProvider;
|
||||||
|
private readonly HttpClient _httpClient = new();
|
||||||
|
private static readonly JsonSerializerOptions JsonOpt = new() { PropertyNameCaseInsensitive = true };
|
||||||
|
|
||||||
|
public DiscoveryApiClient(ILogger<DiscoveryApiClient> logger, TokenProvider tokenProvider, DiscoveryConfigProvider configProvider)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_tokenProvider = tokenProvider;
|
||||||
|
_configProvider = configProvider;
|
||||||
|
_httpClient.Timeout = TimeSpan.FromSeconds(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<ServerMatch>> QueryAsync(string endpoint, IEnumerable<string> hashes, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var token = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(token)) return [];
|
||||||
|
var distinctHashes = hashes.Distinct(StringComparer.Ordinal).ToArray();
|
||||||
|
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
var body = JsonSerializer.Serialize(new
|
||||||
|
{
|
||||||
|
hashes = distinctHashes,
|
||||||
|
salt = _configProvider.SaltB64
|
||||||
|
});
|
||||||
|
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
|
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
||||||
|
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
var token2 = await _tokenProvider.ForceRefreshToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(token2)) return [];
|
||||||
|
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token2);
|
||||||
|
var body2 = JsonSerializer.Serialize(new
|
||||||
|
{
|
||||||
|
hashes = distinctHashes,
|
||||||
|
salt = _configProvider.SaltB64
|
||||||
|
});
|
||||||
|
req2.Content = new StringContent(body2, Encoding.UTF8, "application/json");
|
||||||
|
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
resp.EnsureSuccessStatusCode();
|
||||||
|
var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||||
|
var result = JsonSerializer.Deserialize<List<ServerMatch>>(json, JsonOpt) ?? [];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Discovery query failed");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SendRequestAsync(string endpoint, string token, string? displayName, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jwt = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt)) return false;
|
||||||
|
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
|
||||||
|
var body = JsonSerializer.Serialize(new { token, displayName });
|
||||||
|
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
|
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
||||||
|
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
var jwt2 = await _tokenProvider.ForceRefreshToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt2)) return false;
|
||||||
|
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt2);
|
||||||
|
var body2 = JsonSerializer.Serialize(new { token, displayName });
|
||||||
|
req2.Content = new StringContent(body2, Encoding.UTF8, "application/json");
|
||||||
|
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
if (!resp.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
string txt = string.Empty;
|
||||||
|
try { txt = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); } catch { }
|
||||||
|
_logger.LogWarning("Discovery request failed: {code} {reason} {body}", (int)resp.StatusCode, resp.ReasonPhrase, txt);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Discovery send request failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> PublishAsync(string endpoint, IEnumerable<string> hashes, string? displayName, CancellationToken ct, bool allowRequests = true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jwt = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt)) return false;
|
||||||
|
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
|
||||||
|
var bodyObj = new
|
||||||
|
{
|
||||||
|
hashes = hashes.Distinct(StringComparer.Ordinal).ToArray(),
|
||||||
|
displayName,
|
||||||
|
salt = _configProvider.SaltB64,
|
||||||
|
allowRequests
|
||||||
|
};
|
||||||
|
var body = JsonSerializer.Serialize(bodyObj);
|
||||||
|
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
|
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
||||||
|
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
var jwt2 = await _tokenProvider.ForceRefreshToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt2)) return false;
|
||||||
|
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt2);
|
||||||
|
var body2 = JsonSerializer.Serialize(bodyObj);
|
||||||
|
req2.Content = new StringContent(body2, Encoding.UTF8, "application/json");
|
||||||
|
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
return resp.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Discovery publish failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SendAcceptAsync(string endpoint, string targetUid, string? displayName, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jwt = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt)) return false;
|
||||||
|
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
|
||||||
|
var bodyObj = new { targetUid, displayName };
|
||||||
|
var body = JsonSerializer.Serialize(bodyObj);
|
||||||
|
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
|
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
||||||
|
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
var jwt2 = await _tokenProvider.ForceRefreshToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt2)) return false;
|
||||||
|
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt2);
|
||||||
|
var body2 = JsonSerializer.Serialize(bodyObj);
|
||||||
|
req2.Content = new StringContent(body2, Encoding.UTF8, "application/json");
|
||||||
|
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
return resp.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Discovery accept notify failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async Task DisableAsync(string endpoint, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jwt = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt)) return;
|
||||||
|
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
|
||||||
|
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
||||||
|
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
var jwt2 = await _tokenProvider.ForceRefreshToken(ct).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(jwt2)) return;
|
||||||
|
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
|
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt2);
|
||||||
|
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
if (!resp.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
string txt = string.Empty;
|
||||||
|
try { txt = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); } catch { }
|
||||||
|
_logger.LogWarning("Discovery disable failed: {code} {reason} {body}", (int)resp.StatusCode, resp.ReasonPhrase, txt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Discovery disable failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ServerMatch
|
||||||
|
{
|
||||||
|
public string Hash { get; set; } = string.Empty;
|
||||||
|
public string? Token { get; set; }
|
||||||
|
public string? Uid { get; set; }
|
||||||
|
public string? DisplayName { get; set; }
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
public List<FileTransfer> ForbiddenTransfers => _orchestrator.ForbiddenTransfers;
|
public List<FileTransfer> ForbiddenTransfers => _orchestrator.ForbiddenTransfers;
|
||||||
|
|
||||||
public bool IsDownloading => !CurrentDownloads.Any();
|
public bool IsDownloading => CurrentDownloads.Any();
|
||||||
|
|
||||||
public void ClearDownload()
|
public void ClearDownload()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ public partial class ApiController
|
|||||||
await _mareHub!.SendAsync(nameof(GroupClear), group).ConfigureAwait(false);
|
await _mareHub!.SendAsync(nameof(GroupClear), group).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<GroupPasswordDto> GroupCreate()
|
public async Task<GroupPasswordDto> GroupCreate(string? alias = null)
|
||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
return await _mareHub!.InvokeAsync<GroupPasswordDto>(nameof(GroupCreate)).ConfigureAwait(false);
|
return await _mareHub!.InvokeAsync<GroupPasswordDto>(nameof(GroupCreate), string.IsNullOrWhiteSpace(alias) ? null : alias.Trim()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<string>> GroupCreateTempInvite(GroupDto group, int amount)
|
public async Task<List<string>> GroupCreateTempInvite(GroupDto group, int amount)
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ namespace MareSynchronos.WebAPI;
|
|||||||
#pragma warning disable MA0040
|
#pragma warning disable MA0040
|
||||||
public sealed partial class ApiController : DisposableMediatorSubscriberBase, IMareHubClient
|
public sealed partial class ApiController : DisposableMediatorSubscriberBase, IMareHubClient
|
||||||
{
|
{
|
||||||
public const string UmbraSyncServer = "UmbraSync Main Server (BETA)";
|
public const string UmbraServer = "UmbraSync Main Server (BETA)";
|
||||||
public const string UmbraSyncServiceUri = "wss://umbra-sync.net/";
|
public const string UmbraServiceUri = "wss://umbra-sync.net/";
|
||||||
public const string UmbraSyncServiceApiUri = "wss://umbra-sync.net/";
|
public const string UmbraServiceApiUri = "wss://umbra-sync.net/";
|
||||||
public const string UmbraSyncServiceHubUri = "wss://umbra-sync.net/mare";
|
public const string UmbraServiceHubUri = "wss://umbra-sync.net/mare";
|
||||||
|
|
||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
private readonly HubFactory _hubFactory;
|
private readonly HubFactory _hubFactory;
|
||||||
@@ -194,7 +194,7 @@ public const string UmbraSyncServiceHubUri = "wss://umbra-sync.net/mare";
|
|||||||
Mediator.Publish(new NotificationMessage("Client incompatible",
|
Mediator.Publish(new NotificationMessage("Client incompatible",
|
||||||
$"Your client is outdated ({currentClientVer.Major}.{currentClientVer.Minor}.{currentClientVer.Build}), current is: " +
|
$"Your client is outdated ({currentClientVer.Major}.{currentClientVer.Minor}.{currentClientVer.Build}), current is: " +
|
||||||
$"{_connectionDto.CurrentClientVersion.Major}.{_connectionDto.CurrentClientVersion.Minor}.{_connectionDto.CurrentClientVersion.Build}. " +
|
$"{_connectionDto.CurrentClientVersion.Major}.{_connectionDto.CurrentClientVersion.Minor}.{_connectionDto.CurrentClientVersion.Build}. " +
|
||||||
$"This client version is incompatible and will not be able to connect. Please update your UmbraSync client.",
|
$"This client version is incompatible and will not be able to connect. Please update your Umbra client.",
|
||||||
NotificationType.Error));
|
NotificationType.Error));
|
||||||
}
|
}
|
||||||
await StopConnection(ServerState.VersionMisMatch).ConfigureAwait(false);
|
await StopConnection(ServerState.VersionMisMatch).ConfigureAwait(false);
|
||||||
@@ -206,7 +206,7 @@ public const string UmbraSyncServiceHubUri = "wss://umbra-sync.net/mare";
|
|||||||
Mediator.Publish(new NotificationMessage("Client outdated",
|
Mediator.Publish(new NotificationMessage("Client outdated",
|
||||||
$"Your client is outdated ({currentClientVer.Major}.{currentClientVer.Minor}.{currentClientVer.Build}), current is: " +
|
$"Your client is outdated ({currentClientVer.Major}.{currentClientVer.Minor}.{currentClientVer.Build}), current is: " +
|
||||||
$"{_connectionDto.CurrentClientVersion.Major}.{_connectionDto.CurrentClientVersion.Minor}.{_connectionDto.CurrentClientVersion.Build}. " +
|
$"{_connectionDto.CurrentClientVersion.Major}.{_connectionDto.CurrentClientVersion.Minor}.{_connectionDto.CurrentClientVersion.Build}. " +
|
||||||
$"Please keep your UmbraSync client up-to-date.",
|
$"Please keep your Umbra client up-to-date.",
|
||||||
NotificationType.Warning, TimeSpan.FromSeconds(15)));
|
NotificationType.Warning, TimeSpan.FromSeconds(15)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@ public const string UmbraSyncServiceHubUri = "wss://umbra-sync.net/mare";
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
await LoadIninitialPairs().ConfigureAwait(false);
|
await LoadInitialPairs().ConfigureAwait(false);
|
||||||
await LoadOnlinePairs().ConfigureAwait(false);
|
await LoadOnlinePairs().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -375,7 +375,7 @@ public const string UmbraSyncServiceHubUri = "wss://umbra-sync.net/mare";
|
|||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadIninitialPairs()
|
private async Task LoadInitialPairs()
|
||||||
{
|
{
|
||||||
foreach (var userPair in await UserGetPairedClients().ConfigureAwait(false))
|
foreach (var userPair in await UserGetPairedClients().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
@@ -435,7 +435,7 @@ public const string UmbraSyncServiceHubUri = "wss://umbra-sync.net/mare";
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ServerState = ServerState.Connected;
|
ServerState = ServerState.Connected;
|
||||||
await LoadIninitialPairs().ConfigureAwait(false);
|
await LoadInitialPairs().ConfigureAwait(false);
|
||||||
await LoadOnlinePairs().ConfigureAwait(false);
|
await LoadOnlinePairs().ConfigureAwait(false);
|
||||||
Mediator.Publish(new ConnectedMessage(_connectionDto));
|
Mediator.Publish(new ConnectedMessage(_connectionDto));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ public class HubFactory : MediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
private readonly ILoggerProvider _loggingProvider;
|
private readonly ILoggerProvider _loggingProvider;
|
||||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||||
private readonly RemoteConfigurationService _remoteConfig;
|
|
||||||
private readonly TokenProvider _tokenProvider;
|
private readonly TokenProvider _tokenProvider;
|
||||||
private HubConnection? _instance;
|
private HubConnection? _instance;
|
||||||
private string _cachedConfigFor = string.Empty;
|
private string _cachedConfigFor = string.Empty;
|
||||||
@@ -27,11 +26,10 @@ public class HubFactory : MediatorSubscriberBase
|
|||||||
private bool _isDisposed = false;
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
public HubFactory(ILogger<HubFactory> logger, MareMediator mediator,
|
public HubFactory(ILogger<HubFactory> logger, MareMediator mediator,
|
||||||
ServerConfigurationManager serverConfigurationManager, RemoteConfigurationService remoteConfig,
|
ServerConfigurationManager serverConfigurationManager,
|
||||||
TokenProvider tokenProvider, ILoggerProvider pluginLog) : base(logger, mediator)
|
TokenProvider tokenProvider, ILoggerProvider pluginLog) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
_serverConfigurationManager = serverConfigurationManager;
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
_remoteConfig = remoteConfig;
|
|
||||||
_tokenProvider = tokenProvider;
|
_tokenProvider = tokenProvider;
|
||||||
_loggingProvider = pluginLog;
|
_loggingProvider = pluginLog;
|
||||||
}
|
}
|
||||||
@@ -87,16 +85,6 @@ public class HubFactory : MediatorSubscriberBase
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_serverConfigurationManager.CurrentApiUrl.Equals(ApiController.UmbraSyncServiceUri, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
var mainServerConfig = await _remoteConfig.GetConfigAsync<HubConnectionConfig>("mainServer").ConfigureAwait(false) ?? new();
|
|
||||||
defaultConfig = mainServerConfig;
|
|
||||||
if (string.IsNullOrEmpty(mainServerConfig.ApiUrl))
|
|
||||||
defaultConfig.ApiUrl = ApiController.UmbraSyncServiceApiUri;
|
|
||||||
if (string.IsNullOrEmpty(mainServerConfig.HubUrl))
|
|
||||||
defaultConfig.HubUrl = ApiController.UmbraSyncServiceHubUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
string jsonResponse;
|
string jsonResponse;
|
||||||
|
|
||||||
if (stapledWellKnown != null)
|
if (stapledWellKnown != null)
|
||||||
@@ -115,7 +103,7 @@ public class HubFactory : MediatorSubscriberBase
|
|||||||
_ => apiUrl.Scheme
|
_ => apiUrl.Scheme
|
||||||
};
|
};
|
||||||
|
|
||||||
var wellKnownUrl = $"{httpScheme}://{apiUrl.Host}/.well-known/umbra/client";
|
var wellKnownUrl = $"{httpScheme}://{apiUrl.Host}/.well-known/Umbra/client";
|
||||||
Logger.LogTrace("Fetching hub config for {uri} via {wk}", _serverConfigurationManager.CurrentApiUrl, wellKnownUrl);
|
Logger.LogTrace("Fetching hub config for {uri} via {wk}", _serverConfigurationManager.CurrentApiUrl, wellKnownUrl);
|
||||||
|
|
||||||
using var httpClient = new HttpClient(
|
using var httpClient = new HttpClient(
|
||||||
|
|||||||
@@ -20,16 +20,14 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
|
|||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly ILogger<TokenProvider> _logger;
|
private readonly ILogger<TokenProvider> _logger;
|
||||||
private readonly ServerConfigurationManager _serverManager;
|
private readonly ServerConfigurationManager _serverManager;
|
||||||
private readonly RemoteConfigurationService _remoteConfig;
|
|
||||||
private readonly ConcurrentDictionary<JwtIdentifier, string> _tokenCache = new();
|
private readonly ConcurrentDictionary<JwtIdentifier, string> _tokenCache = new();
|
||||||
private readonly ConcurrentDictionary<string, string?> _wellKnownCache = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, string?> _wellKnownCache = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
public TokenProvider(ILogger<TokenProvider> logger, ServerConfigurationManager serverManager, RemoteConfigurationService remoteConfig,
|
public TokenProvider(ILogger<TokenProvider> logger, ServerConfigurationManager serverManager,
|
||||||
DalamudUtilService dalamudUtil, MareMediator mareMediator)
|
DalamudUtilService dalamudUtil, MareMediator mareMediator)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serverManager = serverManager;
|
_serverManager = serverManager;
|
||||||
_remoteConfig = remoteConfig;
|
|
||||||
_dalamudUtil = dalamudUtil;
|
_dalamudUtil = dalamudUtil;
|
||||||
_httpClient = new(
|
_httpClient = new(
|
||||||
new HttpClientHandler
|
new HttpClientHandler
|
||||||
@@ -70,23 +68,11 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
|
|||||||
Uri tokenUri;
|
Uri tokenUri;
|
||||||
HttpResponseMessage result;
|
HttpResponseMessage result;
|
||||||
|
|
||||||
var authApiUrl = _serverManager.CurrentApiUrl;
|
|
||||||
|
|
||||||
// Override the API URL used for auth from remote config, if one is available
|
|
||||||
if (authApiUrl.Equals(ApiController.UmbraSyncServiceUri, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
var config = await _remoteConfig.GetConfigAsync<HubConnectionConfig>("mainServer").ConfigureAwait(false) ?? new();
|
|
||||||
if (!string.IsNullOrEmpty(config.ApiUrl))
|
|
||||||
authApiUrl = config.ApiUrl;
|
|
||||||
else
|
|
||||||
authApiUrl = ApiController.UmbraSyncServiceApiUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogDebug("GetNewToken: Requesting");
|
_logger.LogDebug("GetNewToken: Requesting");
|
||||||
|
|
||||||
tokenUri = MareAuth.AuthV2FullPath(new Uri(authApiUrl
|
tokenUri = MareAuth.AuthV2FullPath(new Uri(_serverManager.CurrentApiUrl
|
||||||
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
|
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
|
||||||
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
|
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
|
||||||
var secretKey = _serverManager.GetSecretKey(out _)!;
|
var secretKey = _serverManager.GetSecretKey(out _)!;
|
||||||
@@ -186,6 +172,16 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
|
|||||||
return await GetNewToken(jwtIdentifier, ct).ConfigureAwait(false);
|
return await GetNewToken(jwtIdentifier, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string?> ForceRefreshToken(CancellationToken ct)
|
||||||
|
{
|
||||||
|
JwtIdentifier? jwtIdentifier = await GetIdentifier().ConfigureAwait(false);
|
||||||
|
if (jwtIdentifier == null) return null;
|
||||||
|
|
||||||
|
_tokenCache.TryRemove(jwtIdentifier, out _);
|
||||||
|
_logger.LogTrace("ForceRefresh: Getting new token");
|
||||||
|
return await GetNewToken(jwtIdentifier, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
public string? GetStapledWellKnown(string apiUrl)
|
public string? GetStapledWellKnown(string apiUrl)
|
||||||
{
|
{
|
||||||
_wellKnownCache.TryGetValue(apiUrl, out var wellKnown);
|
_wellKnownCache.TryGetValue(apiUrl, out var wellKnown);
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
BIN
MareSynchronos/images/logo.png
Normal file
BIN
MareSynchronos/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 928 KiB |
@@ -2,6 +2,12 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"net9.0-windows7.0": {
|
"net9.0-windows7.0": {
|
||||||
|
"Chaos.NaCl.Standard": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[1.0.0, )",
|
||||||
|
"resolved": "1.0.0",
|
||||||
|
"contentHash": "8ajPyzu49LSIdPgeg56eDdKu1j8FZJngKgTn9rXHV3GNVly49XFvPAwoWuUT8xPze3OjVUOWQZaO82HWrYFFEw=="
|
||||||
|
},
|
||||||
"DalamudPackager": {
|
"DalamudPackager": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[13.0.0, )",
|
"requested": "[13.0.0, )",
|
||||||
@@ -25,9 +31,9 @@
|
|||||||
},
|
},
|
||||||
"Glamourer.Api": {
|
"Glamourer.Api": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[2.4.1, )",
|
"requested": "[2.6.0, )",
|
||||||
"resolved": "2.4.1",
|
"resolved": "2.6.0",
|
||||||
"contentHash": "Q9jrGWDlIAXoMXihu+HbSRu8FpgR9xNy2j5YpAoqDHzhl2MZLgqAJPlQ+N5ISp6cn7xqQVMmxB9PchW2uEYoEA=="
|
"contentHash": "zysCZgNBRm3k3qvibyw/31MmEckX0Uh0ZsT+Sax3ZHnYIRELr9Qhbz3cjJz7u0RHGIrNJiRpktu/LxgHEqDItw=="
|
||||||
},
|
},
|
||||||
"K4os.Compression.LZ4.Legacy": {
|
"K4os.Compression.LZ4.Legacy": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
@@ -51,80 +57,80 @@
|
|||||||
},
|
},
|
||||||
"Meziantou.Analyzer": {
|
"Meziantou.Analyzer": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[2.0.189, )",
|
"requested": "[2.0.212, )",
|
||||||
"resolved": "2.0.189",
|
"resolved": "2.0.212",
|
||||||
"contentHash": "/e+dh95vDdvCTbViV2cWpXJEXAj+VHq7FsBXCTTTsLcffV0bkgXDFAPY0zMpy+Vt91Cl2cBoSOfaAoSdtn796Q=="
|
"contentHash": "U91ktjjTRTccUs3Lk+hrLD9vW+2+lhnsOf4G1GpRSJi1pLn3uK5CU6wGP9Bmz1KlJs6Oz1GGoMhxQBoqQsmAuQ=="
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.SignalR.Client": {
|
"Microsoft.AspNetCore.SignalR.Client": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[9.0.3, )",
|
"requested": "[9.0.8, )",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "V8K94AN9ADbpP2jxwt8Y++g7t/XZ7oEV+GZizNvLnR8dpCYWeveIZ/tItO54jfZJ5jmt5YyideOc+ErZbr1IZg==",
|
"contentHash": "cO+TZaWdhMn2cIYfPH9oFZaisJrx7X6SBAYdmGektPUAW2BYtMbH4HyLOnJ5CYo42zP9WgqhWHKqmoDm7+Ol5w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNetCore.Http.Connections.Client": "9.0.3",
|
"Microsoft.AspNetCore.Http.Connections.Client": "9.0.8",
|
||||||
"Microsoft.AspNetCore.SignalR.Client.Core": "9.0.3"
|
"Microsoft.AspNetCore.SignalR.Client.Core": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.SignalR.Protocols.MessagePack": {
|
"Microsoft.AspNetCore.SignalR.Protocols.MessagePack": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[9.0.3, )",
|
"requested": "[9.0.8, )",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "mMQ21T4NuqGrX1UzSe1WBmg6TUlOmpMgCoA9kAy/uBWBZlAA4+NFavbCULyJy6zTSUAvZkG3cGSnQN4dLJlF/w==",
|
"contentHash": "e6SC/Tp+SZKeEVYdu8blz9q4MkFW08D56IkQv9V3perF3a7v+GgGZ0DAY/HRS9zBuhFrqpXhJvxeHMw3PJLcOg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"MessagePack": "2.5.187",
|
"MessagePack": "2.5.187",
|
||||||
"Microsoft.AspNetCore.SignalR.Common": "9.0.3"
|
"Microsoft.AspNetCore.SignalR.Common": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Hosting": {
|
"Microsoft.Extensions.Hosting": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[9.0.3, )",
|
"requested": "[9.0.8, )",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "ioFXglqFA9uCYcKHI3CLVTO3I75jWIhvVxiZBzGeSPxw7XdhDLh0QvbNFrMTbZk9qqEVQcylblcvcNXnFHYXyA==",
|
"contentHash": "O2VlzORrBbS2it203k5FOHrudDdmdrJovA73P/shdRGeLzvet4e4yXhGx52V2PNjYBQ0IO5M4xiNcL+6xIX6Bg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration": "9.0.3",
|
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.Binder": "9.0.3",
|
"Microsoft.Extensions.Configuration.Binder": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.CommandLine": "9.0.3",
|
"Microsoft.Extensions.Configuration.CommandLine": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "9.0.3",
|
"Microsoft.Extensions.Configuration.EnvironmentVariables": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.FileExtensions": "9.0.3",
|
"Microsoft.Extensions.Configuration.FileExtensions": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.Json": "9.0.3",
|
"Microsoft.Extensions.Configuration.Json": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.UserSecrets": "9.0.3",
|
"Microsoft.Extensions.Configuration.UserSecrets": "9.0.8",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Diagnostics": "9.0.3",
|
"Microsoft.Extensions.Diagnostics": "9.0.8",
|
||||||
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.3",
|
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.FileProviders.Physical": "9.0.3",
|
"Microsoft.Extensions.FileProviders.Physical": "9.0.8",
|
||||||
"Microsoft.Extensions.Hosting.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Hosting.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging": "9.0.3",
|
"Microsoft.Extensions.Logging": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Configuration": "9.0.3",
|
"Microsoft.Extensions.Logging.Configuration": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Console": "9.0.3",
|
"Microsoft.Extensions.Logging.Console": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Debug": "9.0.3",
|
"Microsoft.Extensions.Logging.Debug": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.EventLog": "9.0.3",
|
"Microsoft.Extensions.Logging.EventLog": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.EventSource": "9.0.3",
|
"Microsoft.Extensions.Logging.EventSource": "9.0.8",
|
||||||
"Microsoft.Extensions.Options": "9.0.3"
|
"Microsoft.Extensions.Options": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Penumbra.Api": {
|
"Penumbra.Api": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[5.6.1, )",
|
"requested": "[5.12.0, )",
|
||||||
"resolved": "5.6.1",
|
"resolved": "5.12.0",
|
||||||
"contentHash": "pZesXuBB9cMXKN10EfLJclLay3vjRYQy376PXXcPdx33kNI0jHwgPXGcy6BtVmoaMvn4UvK+EqGVYlt2b/Ns9Q=="
|
"contentHash": "XGWviAZgokj2djpH50FWgM24jOTpKUuDHvd0HwrzBRY6BEMmpb3HfGIl8+BDE/DqbpH63u6aO2TvzUV6BmXT5w=="
|
||||||
},
|
},
|
||||||
"SonarAnalyzer.CSharp": {
|
"SonarAnalyzer.CSharp": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[10.7.0.110445, )",
|
"requested": "[10.15.0.120848, )",
|
||||||
"resolved": "10.7.0.110445",
|
"resolved": "10.15.0.120848",
|
||||||
"contentHash": "U4v2LWopxADYkUv7Z5CX7ifKMdDVqHb7a1bzppIQnQi4WQR6z1Zi5rDkCHlVYGEd1U/WMz1IJCU8OmFZLJpVig=="
|
"contentHash": "1hM3HVRl5jdC/ZBDu+G7CCYLXRGe/QaP01Zy+c9ETPhY7lWD8g8HiefY6sGaH0T3CJ4wAy0/waGgQTh0TYy0oQ=="
|
||||||
},
|
},
|
||||||
"System.IdentityModel.Tokens.Jwt": {
|
"System.IdentityModel.Tokens.Jwt": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[8.7.0, )",
|
"requested": "[8.14.0, )",
|
||||||
"resolved": "8.7.0",
|
"resolved": "8.14.0",
|
||||||
"contentHash": "8dKL3A9pVqYCJIXHd4H2epQqLxSvKeNxGonR0e5g89yMchyvsM/NLuB06otx29BicUd6+LUJZgNZmvYjjPsPGg==",
|
"contentHash": "EYGgN/S+HK7S6F3GaaPLFAfK0UzMrkXFyWCvXpQWFYmZln3dqtbyIO7VuTM/iIIPMzkelg8ZLlBPvMhxj6nOAA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.IdentityModel.JsonWebTokens": "8.7.0",
|
"Microsoft.IdentityModel.JsonWebTokens": "8.14.0",
|
||||||
"Microsoft.IdentityModel.Tokens": "8.7.0"
|
"Microsoft.IdentityModel.Tokens": "8.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"K4os.Compression.LZ4": {
|
"K4os.Compression.LZ4": {
|
||||||
@@ -153,342 +159,342 @@
|
|||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.Connections.Abstractions": {
|
"Microsoft.AspNetCore.Connections.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "MWkNy/Yhv2q5ZVYPHjHN6pEE5Ya1r4opqSpnsW60bgpDOT54zZ6Kpqub4Tcat8ENsR5PZcTZ3eeSAthweUb/KA==",
|
"contentHash": "mONfcKx7I4h6Rg+3b20bRyuy/GWz2yLsCNzKKqh1X4OfxnI7l0rdSxBwO203ebZFhjrdXnqMl7Op0N1FQ1Q5DQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Features": "9.0.3"
|
"Microsoft.Extensions.Features": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.Http.Connections.Client": {
|
"Microsoft.AspNetCore.Http.Connections.Client": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "bLoLX67FBeYK1KKGfXrmBki/F9EAK8EKCNkyADtfFjQkJ1Qhhw1sjBlcL8TbVnZxk+FaFsyCeBPmSHgOwNIJ/A==",
|
"contentHash": "Ob2n+H3358kvubgXu9hY95MZB6X91PUGJvtWaHGEX7eZ+9bYdUCYs57ukJiIziH+aD9yO9e36bgKIT1WJEtfmA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNetCore.Http.Connections.Common": "9.0.3",
|
"Microsoft.AspNetCore.Http.Connections.Common": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Options": "9.0.3",
|
"Microsoft.Extensions.Options": "9.0.8",
|
||||||
"System.Net.ServerSentEvents": "9.0.3"
|
"System.Net.ServerSentEvents": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.Http.Connections.Common": {
|
"Microsoft.AspNetCore.Http.Connections.Common": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "GYDAXEmaG/q9UgixPchsLAVbBUbdgG3hd8J7Af4k4GIKLsibAhos7QY7hHicyULJvRtl03totiRi5Z+JIKEnUA==",
|
"contentHash": "150BRlecnjL+6C+yw/bDP49+ONh7BmaJZTRik6KtbaS+cWnEDVXnhE5PTKlFqCYBD5T8wdjKoF5+lzKHJUK47A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNetCore.Connections.Abstractions": "9.0.3"
|
"Microsoft.AspNetCore.Connections.Abstractions": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.SignalR.Client.Core": {
|
"Microsoft.AspNetCore.SignalR.Client.Core": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "R2N03AK5FH8KIENfJGER4SgjJFMJTBiYuLbovbRunp5R4knO+iysfbYMfEFO3kn98ElWr/747dS4AeWQOEEQsg==",
|
"contentHash": "EZ4KaPVQ9rDxZYWQ1sYiPfXEbomhKwp5Fn/0q1XtOgTilV/nN2lgA06KTofVJSeVVRwYdlZggflcQNcKCG0xcg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNetCore.SignalR.Common": "9.0.3",
|
"Microsoft.AspNetCore.SignalR.Common": "9.0.8",
|
||||||
"Microsoft.AspNetCore.SignalR.Protocols.Json": "9.0.3",
|
"Microsoft.AspNetCore.SignalR.Protocols.Json": "9.0.8",
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging": "9.0.3",
|
"Microsoft.Extensions.Logging": "9.0.8",
|
||||||
"System.Threading.Channels": "9.0.3"
|
"System.Threading.Channels": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.SignalR.Common": {
|
"Microsoft.AspNetCore.SignalR.Common": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "/568tq8YVas1mDgeScmQdQV4ZDRjdyqDS3rAo17R5Bs4puMaNM80wQSwcvsmN5gSwH6L/XRTmD1J1uRIyKXrCg==",
|
"contentHash": "oNOEDf2UGLU63Qi7LB8OJdfG1CGybVO34bhotpkvAQUJ5zH8Ewf7EvqeHlUgg6cVyrdC+vewOFxTysw212FTyw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNetCore.Connections.Abstractions": "9.0.3",
|
"Microsoft.AspNetCore.Connections.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Options": "9.0.3"
|
"Microsoft.Extensions.Options": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.SignalR.Protocols.Json": {
|
"Microsoft.AspNetCore.SignalR.Protocols.Json": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "jvOdsquqrbWMP3/Aq4s8/yVeCxBkjvxarv/2WgubKkQT8nZ46aKY3Rvj1uolp4N3TuaMGlnd6mhK/tF7jCat2Q==",
|
"contentHash": "9LtBkzS2iYOSiUx1NDI91abM5xxD5MUYtdlvwCtMMr6YdsMzHvDUrgPK2N3hpYE94vmj0srt423Kwd1aOqmGPg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNetCore.SignalR.Common": "9.0.3"
|
"Microsoft.AspNetCore.SignalR.Common": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Configuration": {
|
"Microsoft.Extensions.Configuration": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "RIEeZxWYm77+OWLwgik7DzSVSONjqkmcbuCb1koZdGAV7BgOUWnLz80VMyHZMw3onrVwFCCMHBBdruBPuQTvkg==",
|
"contentHash": "6m+8Xgmf8UWL0p/oGqBM+0KbHE5/ePXbV1hKXgC59zEv0aa0DW5oiiyxDbK5kH5j4gIvyD5uWL0+HadKBJngvQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Primitives": "9.0.3"
|
"Microsoft.Extensions.Primitives": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "q5qlbm6GRUrle2ZZxy9aqS/wWoc+mRD3JeP6rcpiJTh5XcemYkplAcJKq8lU11ZfPom5lfbZZfnQvDqcUhqD5Q==",
|
"contentHash": "yNou2KM35RvzOh4vUFtl2l33rWPvOCoba+nzEDJ+BgD8aOL/jew4WPCibQvntRfOJ2pJU8ARygSMD+pdjvDHuA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Primitives": "9.0.3"
|
"Microsoft.Extensions.Primitives": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Configuration.Binder": {
|
"Microsoft.Extensions.Configuration.Binder": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "ad82pYBUSQbd3WIboxsS1HzFdRuHKRa2CpYwie/o6dZAxUjt62yFwjoVdM7Iw2VO5fHV1rJwa7jJZBNZin0E7Q==",
|
"contentHash": "0vK9DnYrYChdiH3yRZWkkp4x4LbrfkWEdBc5HOsQ8t/0CLOWKXKkkhOE8A1shlex0hGydbGrhObeypxz/QTm+w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.3"
|
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Configuration.CommandLine": {
|
"Microsoft.Extensions.Configuration.CommandLine": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "rVwz4ml/Jve/QzzUlyTVOKXVZ37op9RK6Ize4uPmJ3S5c2ErExoy816+dslBQ06ZrFq8M9bpnV5LVBuPD1ONHQ==",
|
"contentHash": "vB6eDQ5prED5jHBqmSDNYzlCXsTSylYY7co9c7guhnz0zhx+jZ8BTHgO7y/Wl1dV2jAO15mKNWuyHRIRtWwGQg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration": "9.0.3",
|
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.3"
|
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": {
|
"Microsoft.Extensions.Configuration.EnvironmentVariables": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "fo84UIa8aSBG3pOtzLsgkj1YkOVfYFy2YWcRTCevHHAkuVsxnYnKBrcW2pyFgqqfQ/rT8K1nmRXHDdQIZ8PDig==",
|
"contentHash": "9qileEYXDodlPN9DfPd5sHSfU2nSrI1r5BHVqLaLyb/7mPi335cy4ar/0ix4tXb2Aer/Pu4e5/zdwxt7lrtSyQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration": "9.0.3",
|
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.3"
|
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Configuration.FileExtensions": {
|
"Microsoft.Extensions.Configuration.FileExtensions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "tBNMSDJ2q7WQK2zwPhHY5I/q95t7sf6dT079mGrNm0yOZF/gM9JvR/LtCb/rwhRmh7A6XMnzv5WbpCh9KLq9EQ==",
|
"contentHash": "2jgx58Jpk3oKT7KRn8x/cFf3QDTjQP+KUbyBnynAcB2iBx1Eq9EdNMCu0QEbYuaZOaQru/Kwdffary+hn58Wwg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration": "9.0.3",
|
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.3",
|
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.FileProviders.Physical": "9.0.3",
|
"Microsoft.Extensions.FileProviders.Physical": "9.0.8",
|
||||||
"Microsoft.Extensions.Primitives": "9.0.3"
|
"Microsoft.Extensions.Primitives": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Configuration.Json": {
|
"Microsoft.Extensions.Configuration.Json": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "mjkp3ZwynNacZk4uq93I0DyCY48FZmi3yRV0xlfeDuWh44KcDunPXHwt8IWr4kL7cVM6eiFVe6YTJg97KzUAUA==",
|
"contentHash": "vjxzcnL7ul322+kpvELisXaZl8/5MYs6JfI9DZLQWsao1nA/4FL48yPwDK986hbJTWc64JxOOaMym0SQ/dy32w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration": "9.0.3",
|
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.FileExtensions": "9.0.3",
|
"Microsoft.Extensions.Configuration.FileExtensions": "9.0.8",
|
||||||
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.3"
|
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Configuration.UserSecrets": {
|
"Microsoft.Extensions.Configuration.UserSecrets": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "vwkBQ5jqmfX7nD7CFvB3k1uSeNBKRcYRDvlk3pxJzJfm/cgT4R+hQg5AFXW/1aLKjz0q7brpRocHC5GK2sjvEw==",
|
"contentHash": "UgH18nQkuMJgxjn1539I83N6LhnKQlLhQm3ppe+PGsFpYsC6eGpF/1KvDRm/bmqsrg0NXhurrv4k2r0e8vWX/Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.Json": "9.0.3",
|
"Microsoft.Extensions.Configuration.Json": "9.0.8",
|
||||||
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.3",
|
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.FileProviders.Physical": "9.0.3"
|
"Microsoft.Extensions.FileProviders.Physical": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.DependencyInjection": {
|
"Microsoft.Extensions.DependencyInjection": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "lDbxJpkl6X8KZGpkAxgrrthQ42YeiR0xjPp7KPx+sCPc3ZbpaIbjzd0QQ+9kDdK2RU2DOl3pc6tQyAgEZY3V0A==",
|
"contentHash": "JJjI2Fa+QtZcUyuNjbKn04OjIUX5IgFGFu/Xc+qvzh1rXdZHLcnqqVXhR4093bGirTwacRlHiVg1XYI9xum6QQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3"
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "TfaHPSe39NyL2wxkisRxXK7xvHGZYBZ+dy3r+mqGvnxKgAPdHkMu3QMQZI4pquP6W5FIQBqs8FJpWV8ffCgDqQ=="
|
"contentHash": "xY3lTjj4+ZYmiKIkyWitddrp1uL5uYiweQjqo4BKBw01ZC4HhcfgLghDpPZcUlppgWAFqFy9SgkiYWOMx365pw=="
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Diagnostics": {
|
"Microsoft.Extensions.Diagnostics": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "gqhbIq6adm0+/9IlDYmchekoxNkmUTm7rfTG3k4zzoQkjRuD8TQGwL1WnIcTDt4aQ+j+Vu0OQrjI8GlpJQQhIA==",
|
"contentHash": "BKkLCFXzJvNmdngeYBf72VXoZqTJSb1orvjdzDLaGobicoGFBPW8ug2ru1nnEewMEwJzMgnsjHQY8EaKWmVhKg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration": "9.0.3",
|
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||||
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.3"
|
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "/fn0Xe8t+3YbMfwyTk4hFirWyAG1pBA5ogVYsrKAuuD2gbqOWhFuSA28auCmS3z8Y2eq3miDIKq4pFVRWA+J6g==",
|
"contentHash": "UDY7blv4DCyIJ/8CkNrQKLaAZFypXQavRZ2DWf/2zi1mxYYKKw2t8AOCBWxNntyPZHPGhtEmL3snFM98ADZqTw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Options": "9.0.3"
|
"Microsoft.Extensions.Options": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Features": {
|
"Microsoft.Extensions.Features": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "jZuO3APLh0ePwtT9PDxiMdPwpDdct/kuExlXLCZZ+XFje/Xt815MM827EFJuxTBAbL148ywyfJyjIZ92osP5WA=="
|
"contentHash": "oyPrbpRFa0uWik3PMwpK1mbAr+inZTEkaBsnMjHyT74YN0ot6knA7OnyFLg+oM4MwW5PZIS4HHW9efy0+gj+oQ=="
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "umczZ3+QPpzlrW/lkvy+IB0p52+qZ5w++aqx2lTCMOaPKzwcbVdrJgiQ3ajw5QWBp7gChLUiCYkSlWUpfjv24g==",
|
"contentHash": "4zZbQ4w+hCMm9J+z5NOj3giIPT2MhZxx05HX/MGuAmDBbjOuXlYIIRN+t4V6OLxy5nXZIcXO+dQMB/OWubuDkw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Primitives": "9.0.3"
|
"Microsoft.Extensions.Primitives": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.FileProviders.Physical": {
|
"Microsoft.Extensions.FileProviders.Physical": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "th2+tQBV5oWjgKhip9GjiIv2AEK3QvfAO3tZcqV3F3dEt5D6Gb411RntCj1+8GS9HaRRSxjSGx/fCrMqIjkb1Q==",
|
"contentHash": "FlOe2i7UUIfY0l0ChaIYtlXjdWWutR4DMRKZaGD6z4G1uVTteFkbBfxUIoi1uGmrZQxXe/yv/cfwiT0tK2xyXA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.3",
|
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.FileSystemGlobbing": "9.0.3",
|
"Microsoft.Extensions.FileSystemGlobbing": "9.0.8",
|
||||||
"Microsoft.Extensions.Primitives": "9.0.3"
|
"Microsoft.Extensions.Primitives": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.FileSystemGlobbing": {
|
"Microsoft.Extensions.FileSystemGlobbing": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "Rec77KHk4iNpFznHi5/6wF3MlUDcKqg26t8gRYbUm1PSukZ4B6mrXpZsJSNOiwyhhQVkjYbaoZxi5XJgRQ5lFg=="
|
"contentHash": "96Ub5LmwYfIGVoXkbe4kjs+ivK6fLBTwKJAOMfUNV0R+AkZRItlgROFqXEWMUlXBTPM1/kKu26Ueu5As6RDzJA=="
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Hosting.Abstractions": {
|
"Microsoft.Extensions.Hosting.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "rHabYVhQsGYNfgnfnYLqZRx/hLe85i6jW5rnDjA9pjt3x7yjPv8T/EXcgN5T9T38FAVwZRA+RMGUkEHbxvCOBQ==",
|
"contentHash": "WNrad20tySNCPe9aJUK7Wfwh+RiyLF+id02FKW8Qfc+HAzNQHazcqMXAbwG/kmbS89uvan/nKK1MufkRahjrJA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Diagnostics.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.3",
|
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.3"
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Logging": {
|
"Microsoft.Extensions.Logging": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "utIi2R1nm+PCWkvWBf1Ou6LWqg9iLfHU23r8yyU9VCvda4dEs7xbTZSwGa5KuwbpzpgCbHCIuKaFHB3zyFmnGw==",
|
"contentHash": "Z/7ze+0iheT7FJeZPqJKARYvyC2bmwu3whbm/48BJjdlGVvgDguoCqJIkI/67NkroTYobd5geai1WheNQvWrgA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Options": "9.0.3"
|
"Microsoft.Extensions.Options": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Logging.Abstractions": {
|
"Microsoft.Extensions.Logging.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "H/MBMLt9A/69Ux4OrV7oCKt3DcMT04o5SCqDolulzQA66TLFEpYYb4qedMs/uwrLtyHXGuDGWKZse/oa8W9AZw==",
|
"contentHash": "pYnAffJL7ARD/HCnnPvnFKSIHnTSmWz84WIlT9tPeQ4lHNiu0Az7N/8itihWvcF8sT+VVD5lq8V+ckMzu4SbOw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3"
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Logging.Configuration": {
|
"Microsoft.Extensions.Logging.Configuration": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "eVZsaKNyK0g0C1qp0mmn4Q2PiX+bXdkz8+zVkXyVMk8IvoWfmTjLjEq1MQlwt1A22lToANPiUrxPJ7Tt3V5puw==",
|
"contentHash": "Us4evDN3lbp1beVgrpxkSXKrbntVGAK+YbSo9P9driiU9PK05+ShhgesJ3aj7SuDfr3mqqcEgrMJ87Vu8t5dhw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration": "9.0.3",
|
"Microsoft.Extensions.Configuration": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.Binder": "9.0.3",
|
"Microsoft.Extensions.Configuration.Binder": "9.0.8",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging": "9.0.3",
|
"Microsoft.Extensions.Logging": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Options": "9.0.3",
|
"Microsoft.Extensions.Options": "9.0.8",
|
||||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.3"
|
"Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Logging.Console": {
|
"Microsoft.Extensions.Logging.Console": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "o9VXLOdpTAro1q7ZThIB3S8OHrRn5pr8cFUCiN85fiwlfAt2DhU4ZIfHy+jCNbf7y7S5Exbr3dlDE8mKNrs0Yg==",
|
"contentHash": "mPp9xB9MjiPuodh9z/+6zEGNj2kSVeXQtdbIBHlhUYqxX22gzJkx0ycPY42q4/OT/SzFV/TJ989Pa3sA/8ZBeA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging": "9.0.3",
|
"Microsoft.Extensions.Logging": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Configuration": "9.0.3",
|
"Microsoft.Extensions.Logging.Configuration": "9.0.8",
|
||||||
"Microsoft.Extensions.Options": "9.0.3"
|
"Microsoft.Extensions.Options": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Logging.Debug": {
|
"Microsoft.Extensions.Logging.Debug": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "BlKgvNYjD6mY5GXpMCf9zPAsrovMgW5mzCOT7SpoOSyI1478zldf+7PKvDIscC277z5zjSO3yi/OuIWpnTZmdA==",
|
"contentHash": "OwHQFVITsONEoizShc1yNYTUvMq0kT9j/LhwAKMsA7OZqtrBXuqjosbSvzkJZ9o+KWAozDh5Y1Vtpe5p/8/1qA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging": "9.0.3",
|
"Microsoft.Extensions.Logging": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.3"
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Logging.EventLog": {
|
"Microsoft.Extensions.Logging.EventLog": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "/+elZUHGgB3oHKO9St/Ql/qfze9O+UbXj+9FOj1gIshLCFXcPlhpKoI11jE6eIV0kbs1P/EeffJl4KDFyvAiJQ==",
|
"contentHash": "/gMwlll21UJcaXlitUqd+rs9jH36EJz5BpFVPshyOqz5u0qyV1pFnTWm5vhyx+g6gwVYENSLgpazR1urNv83xw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging": "9.0.3",
|
"Microsoft.Extensions.Logging": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Options": "9.0.3",
|
"Microsoft.Extensions.Options": "9.0.8",
|
||||||
"System.Diagnostics.EventLog": "9.0.3"
|
"System.Diagnostics.EventLog": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Logging.EventSource": {
|
"Microsoft.Extensions.Logging.EventSource": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "hgG0EGEHnngQFQNqJ5ungEykqaQ5Tik0Gpkb38pea2a5cR3pWlZR4vuYLDdtTgSiKEKByXz/3wNQ7qAqXamEEA==",
|
"contentHash": "aGMFc/1P+315d07iyxSe6lEoZ0JjOPJ+Mfv9rrV2PvR2DFu1/pSi/SItHw1iChJOZgslNKJE97g1a9nLX3qQYA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging": "9.0.3",
|
"Microsoft.Extensions.Logging": "9.0.8",
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Options": "9.0.3",
|
"Microsoft.Extensions.Options": "9.0.8",
|
||||||
"Microsoft.Extensions.Primitives": "9.0.3"
|
"Microsoft.Extensions.Primitives": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Options": {
|
"Microsoft.Extensions.Options": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "xE7MpY70lkw1oiid5y6FbL9dVw8oLfkx8RhSNGN8sSzBlCqGn0SyT3Fqc8tZnDaPIq7Z8R9RTKlS564DS+MV3g==",
|
"contentHash": "OmTaQ0v4gxGQkehpwWIqPoEiwsPuG/u4HUsbOFoWGx4DKET2AXzopnFe/fE608FIhzc/kcg2p8JdyMRCCUzitQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Primitives": "9.0.3"
|
"Microsoft.Extensions.Primitives": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Options.ConfigurationExtensions": {
|
"Microsoft.Extensions.Options.ConfigurationExtensions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "PcyYHQglKnWVZHSPaL6v2qnfsIuFw8tSq7cyXHg3OeuDVn/CqmdWUjRiZomCF/Gi+qCi+ksz0lFphg2cNvB8zQ==",
|
"contentHash": "eW2s6n06x0w6w4nsX+SvpgsFYkl+Y0CttYAt6DKUXeqprX+hzNqjSfOh637fwNJBg7wRBrOIRHe49gKiTgJxzQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.3",
|
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Configuration.Binder": "9.0.3",
|
"Microsoft.Extensions.Configuration.Binder": "9.0.8",
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.3",
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
|
||||||
"Microsoft.Extensions.Options": "9.0.3",
|
"Microsoft.Extensions.Options": "9.0.8",
|
||||||
"Microsoft.Extensions.Primitives": "9.0.3"
|
"Microsoft.Extensions.Primitives": "9.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Primitives": {
|
"Microsoft.Extensions.Primitives": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "yCCJHvBcRyqapMSNzP+kTc57Eaavq2cr5Tmuil6/XVnipQf5xmskxakSQ1enU6S4+fNg3sJ27WcInV64q24JsA=="
|
"contentHash": "tizSIOEsIgSNSSh+hKeUVPK7xmTIjR8s+mJWOu1KXV3htvNQiPMFRMO17OdI1y/4ZApdBVk49u/08QGC9yvLug=="
|
||||||
},
|
},
|
||||||
"Microsoft.IdentityModel.Abstractions": {
|
"Microsoft.IdentityModel.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.7.0",
|
"resolved": "8.14.0",
|
||||||
"contentHash": "OQd5aVepYvh5evOmBMeAYjMIpEcTf1ZCBZaU7Nh/RlhhdXefjFDJeP1L2F2zeNT1unFr+wUu/h3Ac2Xb4BXU6w=="
|
"contentHash": "iwbCpSjD3ehfTwBhtSNEtKPK0ICun6ov7Ibx6ISNA9bfwIyzI2Siwyi9eJFCJBwxowK9xcA1mj+jBWiigeqgcQ=="
|
||||||
},
|
},
|
||||||
"Microsoft.IdentityModel.JsonWebTokens": {
|
"Microsoft.IdentityModel.JsonWebTokens": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.7.0",
|
"resolved": "8.14.0",
|
||||||
"contentHash": "uzsSAWhNhbrkWbQKBTE8QhzviU6sr3bJ1Bkv7gERlhswfSKOp7HsxTRLTPBpx/whQ/GRRHEwMg8leRIPbMrOgw==",
|
"contentHash": "4jOpiA4THdtpLyMdAb24dtj7+6GmvhOhxf5XHLYWmPKF8ApEnApal1UnJsKO4HxUWRXDA6C4WQVfYyqsRhpNpQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.IdentityModel.Tokens": "8.7.0"
|
"Microsoft.IdentityModel.Tokens": "8.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.IdentityModel.Logging": {
|
"Microsoft.IdentityModel.Logging": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.7.0",
|
"resolved": "8.14.0",
|
||||||
"contentHash": "Bs0TznPAu+nxa9rAVHJ+j3CYECHJkT3tG8AyBfhFYlT5ldsDhoxFT7J+PKxJHLf+ayqWfvDZHHc4639W2FQCxA==",
|
"contentHash": "eqqnemdW38CKZEHS6diA50BV94QICozDZEvSrsvN3SJXUFwVB9gy+/oz76gldP7nZliA16IglXjXTCTdmU/Ejg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.IdentityModel.Abstractions": "8.7.0"
|
"Microsoft.IdentityModel.Abstractions": "8.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.IdentityModel.Tokens": {
|
"Microsoft.IdentityModel.Tokens": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.7.0",
|
"resolved": "8.14.0",
|
||||||
"contentHash": "5Z6voXjRXAnGklhmZd1mKz89UhcF5ZQQZaZc2iKrOuL4Li1UihG2vlJx8IbiFAOIxy/xdbsAm0A+WZEaH5fxng==",
|
"contentHash": "lKIZiBiGd36k02TCdMHp1KlNWisyIvQxcYJvIkz7P4gSQ9zi8dgh6S5Grj8NNG7HWYIPfQymGyoZ6JB5d1Lo1g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2",
|
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
|
||||||
"Microsoft.IdentityModel.Logging": "8.7.0"
|
"Microsoft.IdentityModel.Logging": "8.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.NET.StringTools": {
|
"Microsoft.NET.StringTools": {
|
||||||
@@ -498,8 +504,8 @@
|
|||||||
},
|
},
|
||||||
"System.Diagnostics.EventLog": {
|
"System.Diagnostics.EventLog": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "0nDJBZ06DVdTG2vvCZ4XjazLVaFawdT0pnji23ISX8I8fEOlRJyzH2I0kWiAbCtFwry2Zir4qE4l/GStLATfFw=="
|
"contentHash": "gebRF3JLLJ76jz1CQpvwezNapZUjFq20JQsaGHzBH0DzlkHBLpdhwkOei9usiOkIGMwU/L0ALWpNe1JE+5/itw=="
|
||||||
},
|
},
|
||||||
"System.IO.Pipelines": {
|
"System.IO.Pipelines": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
@@ -508,16 +514,13 @@
|
|||||||
},
|
},
|
||||||
"System.Net.ServerSentEvents": {
|
"System.Net.ServerSentEvents": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "Vs/C2V27bjtwLqYag9ATzHilcUn8VQTICre4jSBMGFUeSTxEZffTjb+xZwjcmPsVAjmSZmBI5N7Ezq8UFvqQQg=="
|
"contentHash": "wrpra4YvKXL7VdsQMKPcPxyA8pXK22LcxaKGA8oEndgjLZ1ZSdKXTxEA2cPvvNpMEUBwZlgJ6oZYQ8aJcpapPg=="
|
||||||
},
|
},
|
||||||
"System.Threading.Channels": {
|
"System.Threading.Channels": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.3",
|
"resolved": "9.0.8",
|
||||||
"contentHash": "Ao0iegVONKYVw0eWxJv0ArtMVfkFjgyyYKtUXru6xX5H95flSZWW3QCavD4PAgwpc0ETP38kGHaYbPzSE7sw2w=="
|
"contentHash": "kpvkzWJoHR9os3/4LL5feaTTLD92+XzTqPyYLU2tw2BoJ4MrWCfkjGXtL7MsdpV/20e1+SamCbrPj2L9ptwgBA=="
|
||||||
},
|
|
||||||
"chaos.nacl": {
|
|
||||||
"type": "Project"
|
|
||||||
},
|
},
|
||||||
"maresynchronos.api": {
|
"maresynchronos.api": {
|
||||||
"type": "Project",
|
"type": "Project",
|
||||||
|
|||||||
1
Penumbra.Api
Submodule
1
Penumbra.Api
Submodule
Submodule Penumbra.Api added at dd14131793
@@ -1,2 +1,4 @@
|
|||||||
# UmbraClient
|
# Umbra Sync
|
||||||
|
|
||||||
|
🇫🇷
|
||||||
|
Ce projet est basé sur Mare Synchronos de DarkArchon. Le code original est sous licence MIT ; voir le fichier LICENSE_MIT pour plus de détails. Les commits après celui-ci sont sous licence AGPL v3.
|
||||||
351
UmbraAPI/.gitignore
vendored
351
UmbraAPI/.gitignore
vendored
@@ -1,351 +0,0 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
##
|
|
||||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.rsuser
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Mono auto generated files
|
|
||||||
mono_crash.*
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
[Aa][Rr][Mm]/
|
|
||||||
[Aa][Rr][Mm]64/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
[Ll]ogs/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# Visual Studio 2017 auto generated files
|
|
||||||
Generated\ Files/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUnit
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
nunit-*.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# Benchmark Results
|
|
||||||
BenchmarkDotNet.Artifacts/
|
|
||||||
|
|
||||||
# .NET Core
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
# StyleCop
|
|
||||||
StyleCopReport.xml
|
|
||||||
|
|
||||||
# Files built by Visual Studio
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_h.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.iobj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.ipdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*_wpftmp.csproj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
|
||||||
*.e2e
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
|
||||||
.axoCover/*
|
|
||||||
!.axoCover/settings.json
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# NuGet Symbol Packages
|
|
||||||
*.snupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/[Pp]ackages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/[Pp]ackages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/[Pp]ackages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
*.appx
|
|
||||||
*.appxbundle
|
|
||||||
*.appxupload
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!?*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Including strong name files can present a security risk
|
|
||||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
|
||||||
#*.snk
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
ServiceFabricBackup/
|
|
||||||
*.rptproj.bak
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
*.rptproj.rsuser
|
|
||||||
*- [Bb]ackup.rdl
|
|
||||||
*- [Bb]ackup ([0-9]).rdl
|
|
||||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# CodeRush personal settings
|
|
||||||
.cr/personal
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Tabs Studio
|
|
||||||
*.tss
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
|
||||||
OpenCover/
|
|
||||||
|
|
||||||
# Azure Stream Analytics local run output
|
|
||||||
ASALocalRun/
|
|
||||||
|
|
||||||
# MSBuild Binary and Structured Log
|
|
||||||
*.binlog
|
|
||||||
|
|
||||||
# NVidia Nsight GPU debugger configuration file
|
|
||||||
*.nvuser
|
|
||||||
|
|
||||||
# MFractors (Xamarin productivity tool) working folder
|
|
||||||
.mfractor/
|
|
||||||
|
|
||||||
# Local History for Visual Studio
|
|
||||||
.localhistory/
|
|
||||||
|
|
||||||
# BeatPulse healthcheck temp database
|
|
||||||
healthchecksdb
|
|
||||||
|
|
||||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
|
||||||
MigrationBackup/
|
|
||||||
|
|
||||||
# Ionide (cross platform F# VS Code tools) working folder
|
|
||||||
.ionide/
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MessagePack;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public class CharacterData
|
|
||||||
{
|
|
||||||
public CharacterData()
|
|
||||||
{
|
|
||||||
DataHash = new(() =>
|
|
||||||
{
|
|
||||||
var json = JsonSerializer.Serialize(this);
|
|
||||||
#pragma warning disable SYSLIB0021 // Type or member is obsolete
|
|
||||||
using SHA256CryptoServiceProvider cryptoProvider = new();
|
|
||||||
#pragma warning restore SYSLIB0021 // Type or member is obsolete
|
|
||||||
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(json))).Replace("-", "", StringComparison.Ordinal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<ObjectKind, string> CustomizePlusData { get; set; } = new();
|
|
||||||
[JsonIgnore]
|
|
||||||
public Lazy<string> DataHash { get; }
|
|
||||||
|
|
||||||
public Dictionary<ObjectKind, List<FileReplacementData>> FileReplacements { get; set; } = new();
|
|
||||||
public Dictionary<ObjectKind, string> GlamourerData { get; set; } = new();
|
|
||||||
public string HeelsData { get; set; } = string.Empty;
|
|
||||||
public string HonorificData { get; set; } = string.Empty;
|
|
||||||
public string ManipulationData { get; set; } = string.Empty;
|
|
||||||
public string MoodlesData { get; set; } = string.Empty;
|
|
||||||
public string PetNamesData { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record ChatMessage
|
|
||||||
{
|
|
||||||
public string SenderName { get; set; } = string.Empty;
|
|
||||||
public uint SenderHomeWorldId { get; set; } = 0;
|
|
||||||
public byte[] PayloadContent { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Comparer;
|
|
||||||
|
|
||||||
public class GroupDataComparer : IEqualityComparer<GroupData>
|
|
||||||
{
|
|
||||||
public static GroupDataComparer Instance => _instance;
|
|
||||||
private static GroupDataComparer _instance = new GroupDataComparer();
|
|
||||||
|
|
||||||
private GroupDataComparer() { }
|
|
||||||
public bool Equals(GroupData? x, GroupData? y)
|
|
||||||
{
|
|
||||||
if (x == null || y == null) return false;
|
|
||||||
return x.GID.Equals(y.GID, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(GroupData obj)
|
|
||||||
{
|
|
||||||
return obj.GID.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Comparer;
|
|
||||||
|
|
||||||
|
|
||||||
public class GroupDtoComparer : IEqualityComparer<GroupDto>
|
|
||||||
{
|
|
||||||
public static GroupDtoComparer Instance => _instance;
|
|
||||||
private static GroupDtoComparer _instance = new GroupDtoComparer();
|
|
||||||
|
|
||||||
private GroupDtoComparer() { }
|
|
||||||
|
|
||||||
public bool Equals(GroupDto? x, GroupDto? y)
|
|
||||||
{
|
|
||||||
if (x == null || y == null) return false;
|
|
||||||
return x.GID.Equals(y.GID, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(GroupDto obj)
|
|
||||||
{
|
|
||||||
return obj.Group.GID.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Comparer;
|
|
||||||
|
|
||||||
public class GroupPairDtoComparer : IEqualityComparer<GroupPairDto>
|
|
||||||
{
|
|
||||||
public static GroupPairDtoComparer Instance => _instance;
|
|
||||||
private static GroupPairDtoComparer _instance = new();
|
|
||||||
private GroupPairDtoComparer() { }
|
|
||||||
public bool Equals(GroupPairDto? x, GroupPairDto? y)
|
|
||||||
{
|
|
||||||
if (x == null || y == null) return false;
|
|
||||||
return x.GID.Equals(y.GID, StringComparison.Ordinal) && x.UID.Equals(y.UID, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(GroupPairDto obj)
|
|
||||||
{
|
|
||||||
return HashCode.Combine(obj.Group.GID.GetHashCode(), obj.User.UID.GetHashCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Comparer;
|
|
||||||
|
|
||||||
public class UserDataComparer : IEqualityComparer<UserData>
|
|
||||||
{
|
|
||||||
public static UserDataComparer Instance => _instance;
|
|
||||||
private static UserDataComparer _instance = new();
|
|
||||||
|
|
||||||
private UserDataComparer() { }
|
|
||||||
|
|
||||||
public bool Equals(UserData? x, UserData? y)
|
|
||||||
{
|
|
||||||
if (x == null || y == null) return false;
|
|
||||||
return x.UID.Equals(y.UID, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(UserData obj)
|
|
||||||
{
|
|
||||||
return obj.UID.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using MareSynchronos.API.Dto.User;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Comparer;
|
|
||||||
|
|
||||||
public class UserDtoComparer : IEqualityComparer<UserDto>
|
|
||||||
{
|
|
||||||
public static UserDtoComparer Instance => _instance;
|
|
||||||
private static UserDtoComparer _instance = new();
|
|
||||||
private UserDtoComparer() { }
|
|
||||||
public bool Equals(UserDto? x, UserDto? y)
|
|
||||||
{
|
|
||||||
if (x == null || y == null) return false;
|
|
||||||
return x.User.UID.Equals(y.User.UID, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(UserDto obj)
|
|
||||||
{
|
|
||||||
return obj.User.UID.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum GroupPermissions
|
|
||||||
{
|
|
||||||
NoneSet = 0x0,
|
|
||||||
DisableAnimations = 0x1,
|
|
||||||
DisableSounds = 0x2,
|
|
||||||
DisableInvites = 0x4,
|
|
||||||
DisableVFX = 0x8,
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum GroupUserInfo
|
|
||||||
{
|
|
||||||
None = 0x0,
|
|
||||||
IsModerator = 0x2,
|
|
||||||
IsPinned = 0x4
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum GroupUserPermissions
|
|
||||||
{
|
|
||||||
NoneSet = 0x0,
|
|
||||||
Paused = 0x1,
|
|
||||||
DisableAnimations = 0x2,
|
|
||||||
DisableSounds = 0x4,
|
|
||||||
DisableVFX = 0x8,
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
public enum MessageSeverity
|
|
||||||
{
|
|
||||||
Information,
|
|
||||||
Warning,
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
public enum ObjectKind
|
|
||||||
{
|
|
||||||
Player = 0,
|
|
||||||
MinionOrMount = 1,
|
|
||||||
Companion = 2,
|
|
||||||
Pet = 3,
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum UserPermissions
|
|
||||||
{
|
|
||||||
NoneSet = 0,
|
|
||||||
Paired = 1,
|
|
||||||
Paused = 2,
|
|
||||||
DisableAnimations = 4,
|
|
||||||
DisableSounds = 8,
|
|
||||||
DisableVFX = 16,
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Extensions;
|
|
||||||
|
|
||||||
public static class GroupPermissionsExtensions
|
|
||||||
{
|
|
||||||
public static bool IsDisableAnimations(this GroupPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupPermissions.DisableAnimations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableSounds(this GroupPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupPermissions.DisableSounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableInvites(this GroupPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupPermissions.DisableInvites);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableVFX(this GroupPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupPermissions.DisableVFX);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableAnimations(this ref GroupPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupPermissions.DisableAnimations;
|
|
||||||
else perm &= ~GroupPermissions.DisableAnimations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableSounds(this ref GroupPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupPermissions.DisableSounds;
|
|
||||||
else perm &= ~GroupPermissions.DisableSounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableInvites(this ref GroupPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupPermissions.DisableInvites;
|
|
||||||
else perm &= ~GroupPermissions.DisableInvites;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableVFX(this ref GroupPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupPermissions.DisableVFX;
|
|
||||||
else perm &= ~GroupPermissions.DisableVFX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Extensions;
|
|
||||||
|
|
||||||
public static class GroupUserInfoExtensions
|
|
||||||
{
|
|
||||||
public static bool IsModerator(this GroupUserInfo info)
|
|
||||||
{
|
|
||||||
return info.HasFlag(GroupUserInfo.IsModerator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsPinned(this GroupUserInfo info)
|
|
||||||
{
|
|
||||||
return info.HasFlag(GroupUserInfo.IsPinned);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetModerator(this ref GroupUserInfo info, bool isModerator)
|
|
||||||
{
|
|
||||||
if (isModerator) info |= GroupUserInfo.IsModerator;
|
|
||||||
else info &= ~GroupUserInfo.IsModerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetPinned(this ref GroupUserInfo info, bool isPinned)
|
|
||||||
{
|
|
||||||
if (isPinned) info |= GroupUserInfo.IsPinned;
|
|
||||||
else info &= ~GroupUserInfo.IsPinned;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Extensions;
|
|
||||||
|
|
||||||
public static class GroupUserPermissionsExtensions
|
|
||||||
{
|
|
||||||
public static bool IsDisableAnimations(this GroupUserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupUserPermissions.DisableAnimations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableSounds(this GroupUserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupUserPermissions.DisableSounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsPaused(this GroupUserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupUserPermissions.Paused);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableVFX(this GroupUserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupUserPermissions.DisableVFX);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableAnimations(this ref GroupUserPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupUserPermissions.DisableAnimations;
|
|
||||||
else perm &= ~GroupUserPermissions.DisableAnimations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableSounds(this ref GroupUserPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupUserPermissions.DisableSounds;
|
|
||||||
else perm &= ~GroupUserPermissions.DisableSounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetPaused(this ref GroupUserPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupUserPermissions.Paused;
|
|
||||||
else perm &= ~GroupUserPermissions.Paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableVFX(this ref GroupUserPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupUserPermissions.DisableVFX;
|
|
||||||
else perm &= ~GroupUserPermissions.DisableVFX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user