Compare commits
1 Commits
620ebf9195
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
e1d71ee33a
|
2
MareAPI
2
MareAPI
Submodule MareAPI updated: deb911cb0a...0abb078c21
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using MareSynchronos.Interop.Ipc;
|
||||
using MareSynchronos.Interop.Ipc;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
@@ -607,35 +606,14 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
try
|
||||
{
|
||||
_scanCancellationTokenSource.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
_scanCancellationTokenSource.Dispose();
|
||||
_scanCancellationTokenSource?.Cancel();
|
||||
PenumbraWatcher?.Dispose();
|
||||
MareWatcher?.Dispose();
|
||||
SubstWatcher?.Dispose();
|
||||
TryCancelAndDispose(_penumbraFswCts);
|
||||
TryCancelAndDispose(_mareFswCts);
|
||||
TryCancelAndDispose(_substFswCts);
|
||||
TryCancelAndDispose(_periodicCalculationTokenSource);
|
||||
}
|
||||
|
||||
private static void TryCancelAndDispose(CancellationTokenSource? cts)
|
||||
{
|
||||
if (cts == null) return;
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
|
||||
cts.Dispose();
|
||||
_penumbraFswCts?.CancelDispose();
|
||||
_mareFswCts?.CancelDispose();
|
||||
_substFswCts?.CancelDispose();
|
||||
_periodicCalculationTokenSource?.CancelDispose();
|
||||
}
|
||||
|
||||
private void FullFileScan(CancellationToken ct)
|
||||
@@ -878,4 +856,4 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
||||
StartPenumbraWatcher(penumbraDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public sealed class FileCompactor
|
||||
|
||||
private readonly Dictionary<string, int> _clusterSizes;
|
||||
|
||||
private readonly WofFileCompressionInfoV1 _efInfo;
|
||||
private readonly WOF_FILE_COMPRESSION_INFO_V1 _efInfo;
|
||||
private readonly ILogger<FileCompactor> _logger;
|
||||
|
||||
private readonly MareConfigService _mareConfigService;
|
||||
@@ -24,7 +24,7 @@ public sealed class FileCompactor
|
||||
_logger = logger;
|
||||
_mareConfigService = mareConfigService;
|
||||
_dalamudUtilService = dalamudUtilService;
|
||||
_efInfo = new WofFileCompressionInfoV1
|
||||
_efInfo = new WOF_FILE_COMPRESSION_INFO_V1
|
||||
{
|
||||
Algorithm = CompressionAlgorithm.XPRESS8K,
|
||||
Flags = 0
|
||||
@@ -123,7 +123,7 @@ public sealed class FileCompactor
|
||||
out uint lpTotalNumberOfClusters);
|
||||
|
||||
[DllImport("WoFUtil.dll")]
|
||||
private static extern int WofIsExternalFile([MarshalAs(UnmanagedType.LPWStr)] string Filepath, out int IsExternalFile, out uint Provider, out WofFileCompressionInfoV1 Info, ref uint BufferLength);
|
||||
private static extern int WofIsExternalFile([MarshalAs(UnmanagedType.LPWStr)] string Filepath, out int IsExternalFile, out uint Provider, out WOF_FILE_COMPRESSION_INFO_V1 Info, ref uint BufferLength);
|
||||
|
||||
[DllImport("WofUtil.dll")]
|
||||
private static extern int WofSetFileDataLocation(IntPtr FileHandle, ulong Provider, IntPtr ExternalFileInfo, ulong Length);
|
||||
@@ -242,9 +242,9 @@ public sealed class FileCompactor
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct WofFileCompressionInfoV1
|
||||
private struct WOF_FILE_COMPRESSION_INFO_V1
|
||||
{
|
||||
public CompressionAlgorithm Algorithm;
|
||||
public ulong Flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ namespace MareSynchronos.Interop;
|
||||
|
||||
public record ChatChannelOverride
|
||||
{
|
||||
public string ChannelName { get; set; } = string.Empty;
|
||||
public Action<byte[]>? ChatMessageHandler { get; set; }
|
||||
public string ChannelName = string.Empty;
|
||||
public Action<byte[]>? ChatMessageHandler;
|
||||
}
|
||||
|
||||
public unsafe sealed class GameChatHooks : IDisposable
|
||||
|
||||
@@ -31,11 +31,11 @@ public class MdlFile
|
||||
public ushort Unknown9;
|
||||
|
||||
// Offsets are stored relative to RuntimeSize instead of file start.
|
||||
public uint[] VertexOffset;
|
||||
public uint[] IndexOffset;
|
||||
public uint[] VertexOffset = [0, 0, 0];
|
||||
public uint[] IndexOffset = [0, 0, 0];
|
||||
|
||||
public uint[] VertexBufferSize;
|
||||
public uint[] IndexBufferSize;
|
||||
public uint[] VertexBufferSize = [0, 0, 0];
|
||||
public uint[] IndexBufferSize = [0, 0, 0];
|
||||
public byte LodCount;
|
||||
public bool EnableIndexBufferStreaming;
|
||||
public bool EnableEdgeGeometry;
|
||||
@@ -43,26 +43,15 @@ public class MdlFile
|
||||
public ModelFlags1 Flags1;
|
||||
public ModelFlags2 Flags2;
|
||||
|
||||
public VertexDeclarationStruct[] VertexDeclarations;
|
||||
public ElementIdStruct[] ElementIds;
|
||||
public MeshStruct[] Meshes;
|
||||
public BoundingBoxStruct[] BoneBoundingBoxes;
|
||||
public LodStruct[] Lods;
|
||||
public ExtraLodStruct[] ExtraLods;
|
||||
public VertexDeclarationStruct[] VertexDeclarations = [];
|
||||
public ElementIdStruct[] ElementIds = [];
|
||||
public MeshStruct[] Meshes = [];
|
||||
public BoundingBoxStruct[] BoneBoundingBoxes = [];
|
||||
public LodStruct[] Lods = [];
|
||||
public ExtraLodStruct[] ExtraLods = [];
|
||||
|
||||
public MdlFile(string filePath)
|
||||
{
|
||||
VertexOffset = Array.Empty<uint>();
|
||||
IndexOffset = Array.Empty<uint>();
|
||||
VertexBufferSize = Array.Empty<uint>();
|
||||
IndexBufferSize = Array.Empty<uint>();
|
||||
VertexDeclarations = Array.Empty<VertexDeclarationStruct>();
|
||||
ElementIds = Array.Empty<ElementIdStruct>();
|
||||
Meshes = Array.Empty<MeshStruct>();
|
||||
BoneBoundingBoxes = Array.Empty<BoundingBoxStruct>();
|
||||
Lods = Array.Empty<LodStruct>();
|
||||
ExtraLods = Array.Empty<ExtraLodStruct>();
|
||||
|
||||
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
using var r = new LuminaBinaryReader(stream);
|
||||
|
||||
@@ -267,4 +256,4 @@ public class MdlFile
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore S1104 // Fields should not have public accessibility
|
||||
#pragma warning restore S1104 // Fields should not have public accessibility
|
||||
@@ -1,5 +1,4 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using System;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
@@ -30,7 +29,7 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
private bool _marePluginEnabled = false;
|
||||
private bool _impersonating = false;
|
||||
private DateTime _unregisterTime = DateTime.UtcNow;
|
||||
private CancellationTokenSource? _registerDelayCts = new();
|
||||
private CancellationTokenSource _registerDelayCts = new();
|
||||
|
||||
public bool MarePluginEnabled => _marePluginEnabled;
|
||||
public bool ImpersonationActive => _impersonating;
|
||||
@@ -101,7 +100,7 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
{
|
||||
if (_mareConfig.Current.MareAPI)
|
||||
{
|
||||
var cancelToken = EnsureFreshCts(ref _registerDelayCts).Token;
|
||||
var cancelToken = _registerDelayCts.Token;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// Wait before registering to reduce the chance of a race condition
|
||||
@@ -126,7 +125,7 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
}
|
||||
else
|
||||
{
|
||||
EnsureFreshCts(ref _registerDelayCts);
|
||||
_registerDelayCts = _registerDelayCts.CancelRecreate();
|
||||
if (_impersonating)
|
||||
{
|
||||
_loadFileProviderMare?.UnregisterFunc();
|
||||
@@ -147,7 +146,7 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
_loadFileAsyncProvider?.UnregisterFunc();
|
||||
_handledGameAddresses?.UnregisterFunc();
|
||||
|
||||
TryCancel(_registerDelayCts);
|
||||
_registerDelayCts.Cancel();
|
||||
if (_impersonating)
|
||||
{
|
||||
_loadFileProviderMare?.UnregisterFunc();
|
||||
@@ -156,7 +155,6 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
}
|
||||
|
||||
Mediator.UnsubscribeAll(this);
|
||||
CancelAndDispose(ref _registerDelayCts);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -195,31 +193,4 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
|
||||
return _activeGameObjectHandlers.Where(g => g.Address != nint.Zero).Select(g => g.Address).Distinct().ToList();
|
||||
}
|
||||
|
||||
private static CancellationTokenSource EnsureFreshCts(ref CancellationTokenSource? cts)
|
||||
{
|
||||
CancelAndDispose(ref cts);
|
||||
cts = new CancellationTokenSource();
|
||||
return cts;
|
||||
}
|
||||
|
||||
private static void CancelAndDispose(ref CancellationTokenSource? cts)
|
||||
{
|
||||
if (cts == null) return;
|
||||
TryCancel(cts);
|
||||
cts.Dispose();
|
||||
cts = null;
|
||||
}
|
||||
|
||||
private static void TryCancel(CancellationTokenSource? cts)
|
||||
{
|
||||
if (cts == null) return;
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using System;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public class RedrawManager : IDisposable
|
||||
public class RedrawManager
|
||||
{
|
||||
private readonly MareMediator _mareMediator;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly ConcurrentDictionary<nint, bool> _penumbraRedrawRequests = [];
|
||||
private CancellationTokenSource? _disposalCts = new();
|
||||
private bool _disposed;
|
||||
private CancellationTokenSource _disposalCts = new();
|
||||
|
||||
public SemaphoreSlim RedrawSemaphore { get; init; } = new(2, 2);
|
||||
|
||||
@@ -33,12 +32,12 @@ public class RedrawManager : IDisposable
|
||||
try
|
||||
{
|
||||
using CancellationTokenSource cancelToken = new CancellationTokenSource();
|
||||
using CancellationTokenSource combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancelToken.Token, token, EnsureFreshCts(ref _disposalCts).Token);
|
||||
using CancellationTokenSource combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancelToken.Token, token, _disposalCts.Token);
|
||||
var combinedToken = combinedCts.Token;
|
||||
cancelToken.CancelAfter(TimeSpan.FromSeconds(15));
|
||||
await handler.ActOnFrameworkAfterEnsureNoDrawAsync(action, combinedToken).ConfigureAwait(false);
|
||||
|
||||
if (!_disposalCts!.Token.IsCancellationRequested)
|
||||
if (!_disposalCts.Token.IsCancellationRequested)
|
||||
await _dalamudUtil.WaitWhileCharacterIsDrawing(logger, handler, applicationId, 30000, combinedToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
@@ -50,45 +49,6 @@ public class RedrawManager : IDisposable
|
||||
|
||||
internal void Cancel()
|
||||
{
|
||||
EnsureFreshCts(ref _disposalCts);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
CancelAndDispose(ref _disposalCts);
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private static CancellationTokenSource EnsureFreshCts(ref CancellationTokenSource? cts)
|
||||
{
|
||||
CancelAndDispose(ref cts);
|
||||
cts = new CancellationTokenSource();
|
||||
return cts;
|
||||
}
|
||||
|
||||
private static void CancelAndDispose(ref CancellationTokenSource? cts)
|
||||
{
|
||||
if (cts == null) return;
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
|
||||
cts.Dispose();
|
||||
cts = null;
|
||||
_disposalCts = _disposalCts.CancelRecreate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<AssemblyName>UmbraSync</AssemblyName>
|
||||
<RootNamespace>UmbraSync</RootNamespace>
|
||||
<Version>0.1.9.9</Version>
|
||||
<Version>0.1.9.6</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,6 +5,7 @@ using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -22,6 +23,7 @@ public class PairHandlerFactory
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly MareMediator _mareMediator;
|
||||
private readonly PlayerPerformanceService _playerPerformanceService;
|
||||
private readonly ServerConfigurationManager _serverConfigManager;
|
||||
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
||||
private readonly PairAnalyzerFactory _pairAnalyzerFactory;
|
||||
private readonly VisibilityService _visibilityService;
|
||||
@@ -30,7 +32,7 @@ public class PairHandlerFactory
|
||||
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
|
||||
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
|
||||
FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService,
|
||||
PairAnalyzerFactory pairAnalyzerFactory,
|
||||
ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory,
|
||||
MareConfigService configService, VisibilityService visibilityService)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
@@ -43,6 +45,7 @@ public class PairHandlerFactory
|
||||
_fileCacheManager = fileCacheManager;
|
||||
_mareMediator = mareMediator;
|
||||
_playerPerformanceService = playerPerformanceService;
|
||||
_serverConfigManager = serverConfigManager;
|
||||
_pairAnalyzerFactory = pairAnalyzerFactory;
|
||||
_configService = configService;
|
||||
_visibilityService = visibilityService;
|
||||
@@ -52,6 +55,6 @@ public class PairHandlerFactory
|
||||
{
|
||||
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory,
|
||||
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
|
||||
_fileCacheManager, _mareMediator, _playerPerformanceService, _configService, _visibilityService);
|
||||
_fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager, _configService, _visibilityService);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Events;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI.Files;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -28,6 +29,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly PlayerPerformanceService _playerPerformanceService;
|
||||
private readonly ServerConfigurationManager _serverConfigManager;
|
||||
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
||||
private readonly VisibilityService _visibilityService;
|
||||
private CancellationTokenSource? _applicationCancellationTokenSource = new();
|
||||
@@ -51,6 +53,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime,
|
||||
FileCacheManager fileDbManager, MareMediator mediator,
|
||||
PlayerPerformanceService playerPerformanceService,
|
||||
ServerConfigurationManager serverConfigManager,
|
||||
MareConfigService configService, VisibilityService visibilityService) : base(logger, mediator)
|
||||
{
|
||||
Pair = pair;
|
||||
@@ -62,6 +65,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_fileDbManager = fileDbManager;
|
||||
_playerPerformanceService = playerPerformanceService;
|
||||
_serverConfigManager = serverConfigManager;
|
||||
_configService = configService;
|
||||
_visibilityService = visibilityService;
|
||||
|
||||
@@ -883,4 +887,4 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
Logger.LogDebug("[BASE-{appBase}] ModdedPaths calculated in {time}ms, missing files: {count}, total files: {total}", applicationBase, st.ElapsedMilliseconds, missingFiles.Count, moddedDictionary.Keys.Count);
|
||||
return [.. missingFiles];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,20 +270,6 @@ public class Pair : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
try
|
||||
{
|
||||
_applicationCts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
|
||||
_applicationCts.Dispose();
|
||||
}
|
||||
|
||||
public void SetNote(string note)
|
||||
{
|
||||
_serverConfigurationManager.SetNoteForUid(UserData.UID, note);
|
||||
@@ -382,4 +368,4 @@ public class Pair : DisposableMediatorSubscriberBase
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Events;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.Services.Notifications;
|
||||
using MareSynchronos.UI;
|
||||
using MareSynchronos.UI.Components;
|
||||
using MareSynchronos.UI.Components.Popup;
|
||||
@@ -103,7 +102,6 @@ public sealed class Plugin : IDalamudPlugin
|
||||
collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyDiscoveryService>();
|
||||
collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyPendingService>();
|
||||
collection.AddSingleton<MareSynchronos.Services.AutoDetect.AutoDetectSuppressionService>();
|
||||
collection.AddSingleton<MareSynchronos.Services.AutoDetect.SyncshellDiscoveryService>();
|
||||
collection.AddSingleton<MarePlugin>();
|
||||
collection.AddSingleton<MareProfileManager>();
|
||||
collection.AddSingleton<GameObjectHandlerFactory>();
|
||||
@@ -128,7 +126,6 @@ public sealed class Plugin : IDalamudPlugin
|
||||
collection.AddSingleton<CharaDataCharacterHandler>();
|
||||
collection.AddSingleton<CharaDataNearbyManager>();
|
||||
collection.AddSingleton<CharaDataGposeTogetherManager>();
|
||||
collection.AddSingleton<McdfShareManager>();
|
||||
|
||||
collection.AddSingleton<VfxSpawnManager>();
|
||||
collection.AddSingleton<BlockedCharacterHandler>();
|
||||
@@ -154,7 +151,6 @@ public sealed class Plugin : IDalamudPlugin
|
||||
collection.AddSingleton<PartyListTypingService>();
|
||||
collection.AddSingleton<TypingIndicatorStateService>();
|
||||
collection.AddSingleton<ChatTwoCompatibilityService>();
|
||||
collection.AddSingleton<NotificationTracker>();
|
||||
|
||||
collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||
collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||
@@ -186,23 +182,17 @@ public sealed class Plugin : IDalamudPlugin
|
||||
// add scoped services
|
||||
collection.AddScoped<CacheMonitor>();
|
||||
collection.AddScoped<UiFactory>();
|
||||
collection.AddScoped<SettingsUi>();
|
||||
collection.AddScoped<CompactUi>();
|
||||
collection.AddScoped<EditProfileUi>();
|
||||
collection.AddScoped<DataAnalysisUi>();
|
||||
collection.AddScoped<CharaDataHubUi>();
|
||||
collection.AddScoped<AutoDetectUi>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase>(sp => sp.GetRequiredService<SettingsUi>());
|
||||
collection.AddScoped<WindowMediatorSubscriberBase>(sp => sp.GetRequiredService<CompactUi>());
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, SettingsUi>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, CompactUi>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, IntroUi>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, DownloadUi>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase>(sp => sp.GetRequiredService<AutoDetectUi>());
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, AutoDetectUi>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, ChangelogUi>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, PopoutProfileUi>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase>(sp => sp.GetRequiredService<DataAnalysisUi>());
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, DataAnalysisUi>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, EventViewerUI>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, CharaDataHubUi>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase>(sp => sp.GetRequiredService<EditProfileUi>());
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, EditProfileUi>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, PopupHandler>();
|
||||
collection.AddScoped<WindowMediatorSubscriberBase, TypingIndicatorOverlay>();
|
||||
collection.AddScoped<IPopupHandler, ReportPopupHandler>();
|
||||
@@ -232,7 +222,6 @@ public sealed class Plugin : IDalamudPlugin
|
||||
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());
|
||||
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
|
||||
collection.AddHostedService(p => p.GetRequiredService<MareSynchronos.Services.AutoDetect.NearbyDiscoveryService>());
|
||||
collection.AddHostedService(p => p.GetRequiredService<MareSynchronos.Services.AutoDetect.SyncshellDiscoveryService>());
|
||||
collection.AddHostedService(p => p.GetRequiredService<ChatTwoCompatibilityService>());
|
||||
collection.AddHostedService(p => p.GetRequiredService<MareSynchronos.Services.AutoDetect.AutoDetectSuppressionService>());
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ public class DiscoveryConfigProvider
|
||||
private readonly TokenProvider _tokenProvider;
|
||||
|
||||
private WellKnownRoot? _config;
|
||||
private DateTimeOffset _lastLoad = DateTimeOffset.MinValue;
|
||||
|
||||
public DiscoveryConfigProvider(ILogger<DiscoveryConfigProvider> logger, ServerConfigurationManager serverManager, TokenProvider tokenProvider)
|
||||
{
|
||||
@@ -50,6 +51,7 @@ public class DiscoveryConfigProvider
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -95,6 +97,7 @@ public class DiscoveryConfigProvider
|
||||
|
||||
root.NearbyDiscovery?.Hydrate();
|
||||
_config = root;
|
||||
_lastLoad = DateTimeOffset.UtcNow;
|
||||
_logger.LogInformation("Loaded Nearby well-known (http {path}), enabled={enabled}", path, NearbyEnabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
@@ -8,7 +7,6 @@ using MareSynchronos.WebAPI.AutoDetect;
|
||||
using Dalamud.Plugin.Services;
|
||||
using System.Numerics;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using MareSynchronos.Utils;
|
||||
|
||||
namespace MareSynchronos.Services.AutoDetect;
|
||||
@@ -36,8 +34,6 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
|
||||
private bool _lastAutoDetectState;
|
||||
private DateTime _lastHeartbeat = DateTime.MinValue;
|
||||
private static readonly TimeSpan HeartbeatInterval = TimeSpan.FromSeconds(75);
|
||||
private readonly object _entriesLock = new();
|
||||
private List<NearbyEntry> _lastEntries = [];
|
||||
|
||||
public NearbyDiscoveryService(ILogger<NearbyDiscoveryService> logger, MareMediator mediator,
|
||||
MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService,
|
||||
@@ -56,7 +52,6 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
CancelAndDispose(ref _loopCts);
|
||||
_loopCts = new CancellationTokenSource();
|
||||
_mediator.Subscribe<ConnectedMessage>(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); });
|
||||
_mediator.Subscribe<DisconnectedMessage>(this, _ => { _isConnected = false; _lastPublishedSignature = null; });
|
||||
@@ -133,41 +128,10 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_mediator.UnsubscribeAll(this);
|
||||
CancelAndDispose(ref _loopCts);
|
||||
try { _loopCts?.Cancel(); } catch { }
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public List<NearbyEntry> SnapshotEntries()
|
||||
{
|
||||
lock (_entriesLock)
|
||||
{
|
||||
return _lastEntries.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSnapshot(List<NearbyEntry> entries)
|
||||
{
|
||||
lock (_entriesLock)
|
||||
{
|
||||
_lastEntries = entries.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CancelAndDispose(ref CancellationTokenSource? cts)
|
||||
{
|
||||
if (cts == null) return;
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
|
||||
cts.Dispose();
|
||||
cts = null;
|
||||
}
|
||||
|
||||
private async Task Loop(CancellationToken ct)
|
||||
{
|
||||
_configProvider.TryLoadFromStapled();
|
||||
@@ -462,7 +426,6 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
|
||||
_logger.LogDebug("Nearby: well-known not available or disabled; running in local-only mode");
|
||||
}
|
||||
}
|
||||
UpdateSnapshot(entries);
|
||||
_mediator.Publish(new DiscoveryListUpdated(entries));
|
||||
|
||||
var delayMs = Math.Max(1000, _configProvider.MinQueryIntervalMs);
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
|
||||
using System.Text.RegularExpressions;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.Notifications;
|
||||
using MareSynchronos.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -15,19 +14,16 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
||||
private readonly MareMediator _mediator;
|
||||
private readonly ApiController _api;
|
||||
private readonly AutoDetectRequestService _requestService;
|
||||
private readonly NotificationTracker _notificationTracker;
|
||||
private readonly ConcurrentDictionary<string, string> _pending = new(StringComparer.Ordinal);
|
||||
private static readonly TimeSpan RegexTimeout = TimeSpan.FromSeconds(1);
|
||||
private static readonly Regex ReqRegex = new(@"^Nearby Request: .+ \[(?<uid>[A-Z0-9]+)\]$", RegexOptions.Compiled | RegexOptions.ExplicitCapture, RegexTimeout);
|
||||
private static readonly Regex AcceptRegex = new(@"^Nearby Accept: .+ \[(?<uid>[A-Z0-9]+)\]$", RegexOptions.Compiled | RegexOptions.ExplicitCapture, RegexTimeout);
|
||||
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, NotificationTracker notificationTracker)
|
||||
public NearbyPendingService(ILogger<NearbyPendingService> logger, MareMediator mediator, ApiController api, AutoDetectRequestService requestService)
|
||||
{
|
||||
_logger = logger;
|
||||
_mediator = mediator;
|
||||
_api = api;
|
||||
_requestService = requestService;
|
||||
_notificationTracker = notificationTracker;
|
||||
_mediator.Subscribe<NotificationMessage>(this, OnNotification);
|
||||
_mediator.Subscribe<ManualPairInviteMessage>(this, OnManualPairInvite);
|
||||
}
|
||||
@@ -49,7 +45,6 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
||||
_ = _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uidA)));
|
||||
_pending.TryRemove(uidA, out _);
|
||||
_requestService.RemovePendingRequestByUid(uidA);
|
||||
_notificationTracker.Remove(NotificationCategory.AutoDetect, uidA);
|
||||
_logger.LogInformation("NearbyPending: auto-accepted pairing with {uid}", uidA);
|
||||
}
|
||||
return;
|
||||
@@ -71,7 +66,6 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
||||
catch { name = uid; }
|
||||
_pending[uid] = name;
|
||||
_logger.LogInformation("NearbyPending: received request from {uid} ({name})", uid, name);
|
||||
_notificationTracker.Upsert(NotificationEntry.AutoDetect(uid, name));
|
||||
}
|
||||
|
||||
private void OnManualPairInvite(ManualPairInviteMessage msg)
|
||||
@@ -86,14 +80,12 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
||||
_pending[msg.SourceUid] = display;
|
||||
_logger.LogInformation("NearbyPending: received manual invite from {uid} ({name})", msg.SourceUid, display);
|
||||
_mediator.Publish(new NotificationMessage("Nearby request", $"{display} vous a envoyé une invitation de pair.", NotificationType.Info, TimeSpan.FromSeconds(5)));
|
||||
_notificationTracker.Upsert(NotificationEntry.AutoDetect(msg.SourceUid, display));
|
||||
}
|
||||
|
||||
public void Remove(string uid)
|
||||
{
|
||||
_pending.TryRemove(uid, out _);
|
||||
_requestService.RemovePendingRequestByUid(uid);
|
||||
_notificationTracker.Remove(NotificationCategory.AutoDetect, uid);
|
||||
}
|
||||
|
||||
public async Task<bool> AcceptAsync(string uid)
|
||||
@@ -104,7 +96,6 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
||||
_pending.TryRemove(uid, out _);
|
||||
_requestService.RemovePendingRequestByUid(uid);
|
||||
_ = _requestService.SendAcceptNotifyAsync(uid);
|
||||
_notificationTracker.Remove(NotificationCategory.AutoDetect, uid);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronos.API.Data;
|
||||
using MareSynchronos.API.Dto.Group;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.WebAPI;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Services.AutoDetect;
|
||||
|
||||
public sealed class SyncshellDiscoveryService : IHostedService, IMediatorSubscriber
|
||||
{
|
||||
private readonly ILogger<SyncshellDiscoveryService> _logger;
|
||||
private readonly MareMediator _mediator;
|
||||
private readonly ApiController _apiController;
|
||||
private readonly SemaphoreSlim _refreshSemaphore = new(1, 1);
|
||||
private readonly object _entriesLock = new();
|
||||
private List<SyncshellDiscoveryEntryDto> _entries = [];
|
||||
private string? _lastError;
|
||||
private bool _isRefreshing;
|
||||
|
||||
public SyncshellDiscoveryService(ILogger<SyncshellDiscoveryService> logger, MareMediator mediator, ApiController apiController)
|
||||
{
|
||||
_logger = logger;
|
||||
_mediator = mediator;
|
||||
_apiController = apiController;
|
||||
}
|
||||
|
||||
public MareMediator Mediator => _mediator;
|
||||
|
||||
public IReadOnlyList<SyncshellDiscoveryEntryDto> Entries
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_entriesLock)
|
||||
{
|
||||
return _entries.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRefreshing => _isRefreshing;
|
||||
public string? LastError => _lastError;
|
||||
|
||||
public async Task<bool> JoinAsync(string gid, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _apiController.SyncshellDiscoveryJoin(new GroupDto(new GroupData(gid))).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Join syncshell discovery failed for {gid}", gid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SyncshellDiscoveryStateDto?> GetStateAsync(string gid, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _apiController.SyncshellDiscoveryGetState(new GroupDto(new GroupData(gid))).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to fetch syncshell discovery state for {gid}", gid);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SetVisibilityAsync(string gid, bool visible, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new SyncshellDiscoveryVisibilityRequestDto
|
||||
{
|
||||
GID = gid,
|
||||
AutoDetectVisible = visible,
|
||||
};
|
||||
var success = await _apiController.SyncshellDiscoverySetVisibility(request).ConfigureAwait(false);
|
||||
if (!success) return false;
|
||||
|
||||
var state = await GetStateAsync(gid, ct).ConfigureAwait(false);
|
||||
if (state != null)
|
||||
{
|
||||
_mediator.Publish(new SyncshellAutoDetectStateChanged(state.GID, state.AutoDetectVisible, state.PasswordTemporarilyDisabled));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to set syncshell visibility for {gid}", gid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshAsync(CancellationToken ct)
|
||||
{
|
||||
if (!await _refreshSemaphore.WaitAsync(0, ct).ConfigureAwait(false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_isRefreshing = true;
|
||||
var discovered = await _apiController.SyncshellDiscoveryList().ConfigureAwait(false);
|
||||
lock (_entriesLock)
|
||||
{
|
||||
_entries = discovered ?? [];
|
||||
}
|
||||
_lastError = null;
|
||||
_mediator.Publish(new SyncshellDiscoveryUpdated(Entries.ToList()));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to refresh syncshell discovery list");
|
||||
_lastError = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isRefreshing = false;
|
||||
_refreshSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_mediator.Subscribe<ConnectedMessage>(this, msg =>
|
||||
{
|
||||
_ = RefreshAsync(CancellationToken.None);
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_mediator.UnsubscribeAll(this);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ using MareSynchronos.Services.CharaData.Models;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI.Files;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
@@ -296,32 +295,6 @@ public sealed class CharaDataFileHandler : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<byte[]?> CreateCharaFileBytesAsync(string description, CancellationToken token = default)
|
||||
{
|
||||
var tempFilePath = Path.Combine(Path.GetTempPath(), "umbra_mcdfshare_" + Guid.NewGuid().ToString("N") + ".mcdf");
|
||||
try
|
||||
{
|
||||
await SaveCharaFileAsync(description, tempFilePath).ConfigureAwait(false);
|
||||
if (!File.Exists(tempFilePath)) return null;
|
||||
token.ThrowIfCancellationRequested();
|
||||
return await File.ReadAllBytesAsync(tempFilePath, token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(tempFilePath))
|
||||
{
|
||||
File.Delete(tempFilePath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<List<string>> UploadFiles(List<string> fileList, ValueProgress<string> uploadProgress, CancellationToken token)
|
||||
{
|
||||
return await _fileUploadManager.UploadFiles(fileList, uploadProgress, token).ConfigureAwait(false);
|
||||
|
||||
@@ -13,9 +13,7 @@ using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
@@ -459,14 +457,6 @@ public sealed partial class CharaDataManager : DisposableMediatorSubscriberBase
|
||||
LoadedMcdfHeader = _fileHandler.LoadCharaFileHeader(filePath);
|
||||
}
|
||||
|
||||
public async Task<string> LoadMcdfFromBytes(byte[] data, CancellationToken token = default)
|
||||
{
|
||||
var tempFilePath = Path.Combine(Path.GetTempPath(), "umbra_mcdfshare_" + Guid.NewGuid().ToString("N") + ".mcdf");
|
||||
await File.WriteAllBytesAsync(tempFilePath, data, token).ConfigureAwait(false);
|
||||
LoadedMcdfHeader = _fileHandler.LoadCharaFileHeader(tempFilePath);
|
||||
return tempFilePath;
|
||||
}
|
||||
|
||||
public void McdfApplyToTarget(string charaName)
|
||||
{
|
||||
if (LoadedMcdfHeader == null || !LoadedMcdfHeader.IsCompletedSuccessfully) return;
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed class CharaDataNearbyManager : DisposableMediatorSubscriberBase
|
||||
private Task? _filterEntriesRunningTask;
|
||||
private (Guid VfxId, PoseEntryExtended Pose)? _hoveredVfx = null;
|
||||
private DateTime _lastExecutionTime = DateTime.UtcNow;
|
||||
private readonly SemaphoreSlim _sharedDataUpdateSemaphore = new(1, 1);
|
||||
private SemaphoreSlim _sharedDataUpdateSemaphore = new(1, 1);
|
||||
public CharaDataNearbyManager(ILogger<CharaDataNearbyManager> logger, MareMediator mediator,
|
||||
DalamudUtilService dalamudUtilService, VfxSpawnManager vfxSpawnManager,
|
||||
ServerConfigurationManager serverConfigurationManager,
|
||||
|
||||
@@ -1,309 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronos.API.Dto.McdfShare;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Services.CharaData;
|
||||
|
||||
public sealed class McdfShareManager
|
||||
{
|
||||
private readonly ILogger<McdfShareManager> _logger;
|
||||
private readonly ApiController _apiController;
|
||||
private readonly CharaDataFileHandler _fileHandler;
|
||||
private readonly CharaDataManager _charaDataManager;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly SemaphoreSlim _operationSemaphore = new(1, 1);
|
||||
private readonly List<McdfShareEntryDto> _ownShares = new();
|
||||
private readonly List<McdfShareEntryDto> _sharedWithMe = new();
|
||||
private Task? _currentTask;
|
||||
|
||||
public McdfShareManager(ILogger<McdfShareManager> logger, ApiController apiController,
|
||||
CharaDataFileHandler fileHandler, CharaDataManager charaDataManager,
|
||||
ServerConfigurationManager serverConfigurationManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_apiController = apiController;
|
||||
_fileHandler = fileHandler;
|
||||
_charaDataManager = charaDataManager;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
}
|
||||
|
||||
public IReadOnlyList<McdfShareEntryDto> OwnShares => _ownShares;
|
||||
public IReadOnlyList<McdfShareEntryDto> SharedShares => _sharedWithMe;
|
||||
public bool IsBusy => _currentTask is { IsCompleted: false };
|
||||
public string? LastError { get; private set; }
|
||||
public string? LastSuccess { get; private set; }
|
||||
|
||||
public Task RefreshAsync(CancellationToken token)
|
||||
{
|
||||
return RunOperation(() => InternalRefreshAsync(token));
|
||||
}
|
||||
|
||||
public Task CreateShareAsync(string description, IReadOnlyList<string> allowedIndividuals, IReadOnlyList<string> allowedSyncshells, DateTime? expiresAtUtc, CancellationToken token)
|
||||
{
|
||||
return RunOperation(async () =>
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var mcdfBytes = await _fileHandler.CreateCharaFileBytesAsync(description, token).ConfigureAwait(false);
|
||||
if (mcdfBytes == null || mcdfBytes.Length == 0)
|
||||
{
|
||||
LastError = "Impossible de préparer les données MCDF.";
|
||||
return;
|
||||
}
|
||||
|
||||
var secretKey = _serverConfigurationManager.GetSecretKey(out bool hasMultiple);
|
||||
if (hasMultiple)
|
||||
{
|
||||
LastError = "Plusieurs clés secrètes sont configurées pour ce personnage. Corrigez cela dans les paramètres.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(secretKey))
|
||||
{
|
||||
LastError = "Aucune clé secrète n'est configurée pour ce personnage.";
|
||||
return;
|
||||
}
|
||||
|
||||
var shareId = Guid.NewGuid();
|
||||
byte[] salt = RandomNumberGenerator.GetBytes(16);
|
||||
byte[] nonce = RandomNumberGenerator.GetBytes(12);
|
||||
byte[] key = DeriveKey(secretKey, shareId, salt);
|
||||
|
||||
byte[] cipher = new byte[mcdfBytes.Length];
|
||||
byte[] tag = new byte[16];
|
||||
|
||||
using (var aes = new AesGcm(key, 16))
|
||||
{
|
||||
aes.Encrypt(nonce, mcdfBytes, cipher, tag);
|
||||
}
|
||||
|
||||
var uploadDto = new McdfShareUploadRequestDto
|
||||
{
|
||||
ShareId = shareId,
|
||||
Description = description,
|
||||
CipherData = cipher,
|
||||
Nonce = nonce,
|
||||
Salt = salt,
|
||||
Tag = tag,
|
||||
ExpiresAtUtc = expiresAtUtc,
|
||||
AllowedIndividuals = allowedIndividuals.ToList(),
|
||||
AllowedSyncshells = allowedSyncshells.ToList()
|
||||
};
|
||||
|
||||
await _apiController.McdfShareUpload(uploadDto).ConfigureAwait(false);
|
||||
await InternalRefreshAsync(token).ConfigureAwait(false);
|
||||
LastSuccess = "Partage MCDF créé.";
|
||||
});
|
||||
}
|
||||
|
||||
public Task DeleteShareAsync(Guid shareId)
|
||||
{
|
||||
return RunOperation(async () =>
|
||||
{
|
||||
var result = await _apiController.McdfShareDelete(shareId).ConfigureAwait(false);
|
||||
if (!result)
|
||||
{
|
||||
LastError = "Le serveur a refusé de supprimer le partage MCDF.";
|
||||
return;
|
||||
}
|
||||
|
||||
_ownShares.RemoveAll(s => s.Id == shareId);
|
||||
_sharedWithMe.RemoveAll(s => s.Id == shareId);
|
||||
await InternalRefreshAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
LastSuccess = "Partage MCDF supprimé.";
|
||||
});
|
||||
}
|
||||
|
||||
public Task UpdateShareAsync(McdfShareUpdateRequestDto updateRequest)
|
||||
{
|
||||
return RunOperation(async () =>
|
||||
{
|
||||
var updated = await _apiController.McdfShareUpdate(updateRequest).ConfigureAwait(false);
|
||||
if (updated == null)
|
||||
{
|
||||
LastError = "Le serveur a refusé de mettre à jour le partage MCDF.";
|
||||
return;
|
||||
}
|
||||
|
||||
var idx = _ownShares.FindIndex(s => s.Id == updated.Id);
|
||||
if (idx >= 0)
|
||||
{
|
||||
_ownShares[idx] = updated;
|
||||
}
|
||||
LastSuccess = "Partage MCDF mis à jour.";
|
||||
});
|
||||
}
|
||||
|
||||
public Task ApplyShareAsync(Guid shareId, CancellationToken token)
|
||||
{
|
||||
return RunOperation(async () =>
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var plainBytes = await DownloadAndDecryptShareAsync(shareId, token).ConfigureAwait(false);
|
||||
if (plainBytes == null)
|
||||
{
|
||||
LastError ??= "Échec du téléchargement du partage MCDF.";
|
||||
return;
|
||||
}
|
||||
|
||||
var tempPath = await _charaDataManager.LoadMcdfFromBytes(plainBytes, token).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _charaDataManager.McdfApplyToGposeTarget().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(tempPath))
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
LastSuccess = "Partage MCDF appliqué sur la cible GPose.";
|
||||
});
|
||||
}
|
||||
|
||||
public Task ExportShareAsync(Guid shareId, string filePath, CancellationToken token)
|
||||
{
|
||||
return RunOperation(async () =>
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var plainBytes = await DownloadAndDecryptShareAsync(shareId, token).ConfigureAwait(false);
|
||||
if (plainBytes == null)
|
||||
{
|
||||
LastError ??= "Échec du téléchargement du partage MCDF.";
|
||||
return;
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
await File.WriteAllBytesAsync(filePath, plainBytes, token).ConfigureAwait(false);
|
||||
LastSuccess = "Partage MCDF exporté.";
|
||||
});
|
||||
}
|
||||
|
||||
public Task DownloadShareToFileAsync(McdfShareEntryDto entry, string filePath, CancellationToken token)
|
||||
{
|
||||
return ExportShareAsync(entry.Id, filePath, token);
|
||||
}
|
||||
|
||||
private async Task<byte[]?> DownloadAndDecryptShareAsync(Guid shareId, CancellationToken token)
|
||||
{
|
||||
var payload = await _apiController.McdfShareDownload(shareId).ConfigureAwait(false);
|
||||
if (payload == null)
|
||||
{
|
||||
LastError = "Partage indisponible.";
|
||||
return null;
|
||||
}
|
||||
|
||||
var secretKey = _serverConfigurationManager.GetSecretKey(out bool hasMultiple);
|
||||
if (hasMultiple)
|
||||
{
|
||||
LastError = "Plusieurs clés secrètes sont configurées pour ce personnage.";
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(secretKey))
|
||||
{
|
||||
LastError = "Aucune clé secrète n'est configurée pour ce personnage.";
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] key = DeriveKey(secretKey, payload.ShareId, payload.Salt);
|
||||
byte[] plaintext = new byte[payload.CipherData.Length];
|
||||
try
|
||||
{
|
||||
using var aes = new AesGcm(key, 16);
|
||||
aes.Decrypt(payload.Nonce, payload.CipherData, payload.Tag, plaintext);
|
||||
}
|
||||
catch (CryptographicException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to decrypt MCDF share {ShareId}", shareId);
|
||||
LastError = "Impossible de déchiffrer le partage MCDF.";
|
||||
return null;
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
private async Task InternalRefreshAsync(CancellationToken token)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var own = await _apiController.McdfShareGetOwn().ConfigureAwait(false);
|
||||
token.ThrowIfCancellationRequested();
|
||||
var shared = await _apiController.McdfShareGetShared().ConfigureAwait(false);
|
||||
_ownShares.Clear();
|
||||
_ownShares.AddRange(own);
|
||||
_sharedWithMe.Clear();
|
||||
_sharedWithMe.AddRange(shared);
|
||||
LastSuccess = "Partages MCDF actualisés.";
|
||||
}
|
||||
|
||||
private Task RunOperation(Func<Task> operation)
|
||||
{
|
||||
async Task Wrapper()
|
||||
{
|
||||
await _operationSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
LastError = null;
|
||||
LastSuccess = null;
|
||||
await operation().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during MCDF share operation");
|
||||
LastError = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_operationSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
var task = Wrapper();
|
||||
_currentTask = task;
|
||||
return task;
|
||||
}
|
||||
|
||||
private static byte[] DeriveKey(string secretKey, Guid shareId, byte[] salt)
|
||||
{
|
||||
byte[] secretBytes;
|
||||
try
|
||||
{
|
||||
secretBytes = Convert.FromHexString(secretKey);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// fallback to UTF8 if not hex
|
||||
secretBytes = System.Text.Encoding.UTF8.GetBytes(secretKey);
|
||||
}
|
||||
|
||||
byte[] shareBytes = shareId.ToByteArray();
|
||||
byte[] material = new byte[secretBytes.Length + shareBytes.Length + salt.Length];
|
||||
Buffer.BlockCopy(secretBytes, 0, material, 0, secretBytes.Length);
|
||||
Buffer.BlockCopy(shareBytes, 0, material, secretBytes.Length, shareBytes.Length);
|
||||
Buffer.BlockCopy(salt, 0, material, secretBytes.Length + shareBytes.Length, salt.Length);
|
||||
return SHA256.HashData(material);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Lumina.Data.Files;
|
||||
using MareSynchronos.API.Data;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
@@ -17,8 +16,9 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
{
|
||||
private readonly FileCacheManager _fileCacheManager;
|
||||
private readonly XivDataAnalyzer _xivDataAnalyzer;
|
||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||
private CancellationTokenSource? _analysisCts;
|
||||
private CancellationTokenSource? _baseAnalysisCts = new();
|
||||
private CancellationTokenSource _baseAnalysisCts = new();
|
||||
private string _lastDataHash = string.Empty;
|
||||
private CharacterAnalysisSummary _previousSummary = CharacterAnalysisSummary.Empty;
|
||||
private DateTime _lastAutoAnalysis = DateTime.MinValue;
|
||||
@@ -30,15 +30,14 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
private const long NotificationTriangleThreshold = 150_000;
|
||||
private bool _sizeWarningShown;
|
||||
private bool _triangleWarningShown;
|
||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||
|
||||
public CharacterAnalyzer(ILogger<CharacterAnalyzer> logger, MareMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer, PlayerPerformanceConfigService playerPerformanceConfigService)
|
||||
: base(logger, mediator)
|
||||
{
|
||||
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) =>
|
||||
{
|
||||
var tokenSource = EnsureFreshCts(ref _baseAnalysisCts);
|
||||
var token = tokenSource.Token;
|
||||
_baseAnalysisCts = _baseAnalysisCts.CancelRecreate();
|
||||
var token = _baseAnalysisCts.Token;
|
||||
_ = BaseAnalysis(msg.CharacterData, token);
|
||||
});
|
||||
_fileCacheManager = fileCacheManager;
|
||||
@@ -55,15 +54,17 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
|
||||
public void CancelAnalyze()
|
||||
{
|
||||
CancelAndDispose(ref _analysisCts);
|
||||
_analysisCts?.CancelDispose();
|
||||
_analysisCts = null;
|
||||
}
|
||||
|
||||
public async Task ComputeAnalysis(bool print = true, bool recalculate = false)
|
||||
{
|
||||
Logger.LogDebug("=== Calculating Character Analysis ===");
|
||||
|
||||
var analysisCts = EnsureFreshCts(ref _analysisCts);
|
||||
var cancelToken = analysisCts.Token;
|
||||
_analysisCts = _analysisCts?.CancelRecreate() ?? new();
|
||||
|
||||
var cancelToken = _analysisCts.Token;
|
||||
|
||||
var allFiles = LastAnalysis.SelectMany(v => v.Value.Select(d => d.Value)).ToList();
|
||||
if (allFiles.Exists(c => !c.IsComputed || recalculate))
|
||||
@@ -105,7 +106,8 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
LastCompletedAnalysis = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
CancelAndDispose(ref _analysisCts);
|
||||
_analysisCts.CancelDispose();
|
||||
_analysisCts = null;
|
||||
|
||||
if (print) PrintAnalysis();
|
||||
}
|
||||
@@ -116,8 +118,8 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
|
||||
if (!disposing) return;
|
||||
|
||||
CancelAndDispose(ref _analysisCts);
|
||||
CancelAndDispose(ref _baseAnalysisCts);
|
||||
_analysisCts?.CancelDispose();
|
||||
_baseAnalysisCts.CancelDispose();
|
||||
}
|
||||
|
||||
private async Task BaseAnalysis(CharacterData charaData, CancellationToken token)
|
||||
@@ -373,7 +375,6 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public readonly record struct CharacterAnalysisSummary(int TotalFiles, long TotalOriginalSize, long TotalCompressedSize, long TotalTriangles, bool HasUncomputedEntries)
|
||||
{
|
||||
public static CharacterAnalysisSummary Empty => new();
|
||||
@@ -426,26 +427,4 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static CancellationTokenSource EnsureFreshCts(ref CancellationTokenSource? cts)
|
||||
{
|
||||
CancelAndDispose(ref cts);
|
||||
cts = new CancellationTokenSource();
|
||||
return cts;
|
||||
}
|
||||
|
||||
private static void CancelAndDispose(ref CancellationTokenSource? cts)
|
||||
{
|
||||
if (cts == null) return;
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
|
||||
cts.Dispose();
|
||||
cts = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,26 +16,37 @@ namespace MareSynchronos.Services;
|
||||
|
||||
public class GuiHookService : DisposableMediatorSubscriberBase
|
||||
{
|
||||
private readonly ILogger<GuiHookService> _logger;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly MareConfigService _configService;
|
||||
private readonly INamePlateGui _namePlateGui;
|
||||
private readonly IGameConfig _gameConfig;
|
||||
private readonly IPartyList _partyList;
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly ApiController _apiController;
|
||||
private readonly TypingIndicatorStateService _typingStateService;
|
||||
|
||||
private static readonly TimeSpan TypingDisplayTime = TimeSpan.FromSeconds(2);
|
||||
|
||||
private bool _isModified = false;
|
||||
private bool _namePlateRoleColorsEnabled = false;
|
||||
|
||||
public GuiHookService(ILogger<GuiHookService> logger, DalamudUtilService dalamudUtil, MareMediator mediator, MareConfigService configService,
|
||||
INamePlateGui namePlateGui, IGameConfig gameConfig, IPartyList partyList, PairManager pairManager)
|
||||
INamePlateGui namePlateGui, IGameConfig gameConfig, IPartyList partyList, PairManager pairManager, ApiController apiController,
|
||||
IClientState clientState, TypingIndicatorStateService typingStateService)
|
||||
: base(logger, mediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_configService = configService;
|
||||
_namePlateGui = namePlateGui;
|
||||
_gameConfig = gameConfig;
|
||||
_partyList = partyList;
|
||||
_pairManager = pairManager;
|
||||
_apiController = apiController;
|
||||
_clientState = clientState;
|
||||
_typingStateService = typingStateService;
|
||||
|
||||
_namePlateGui.OnNamePlateUpdate += OnNamePlateUpdate;
|
||||
_namePlateGui.RequestRedraw();
|
||||
@@ -49,13 +60,18 @@ public class GuiHookService : DisposableMediatorSubscriberBase
|
||||
public void RequestRedraw(bool force = false)
|
||||
{
|
||||
var useColors = _configService.Current.UseNameColors;
|
||||
var showTyping = _configService.Current.TypingIndicatorShowOnNameplates;
|
||||
|
||||
if (!useColors)
|
||||
if (!useColors && !showTyping)
|
||||
{
|
||||
if (!_isModified && !force)
|
||||
return;
|
||||
_isModified = false;
|
||||
}
|
||||
else if (!useColors)
|
||||
{
|
||||
_isModified = false;
|
||||
}
|
||||
|
||||
_ = Task.Run(async () => {
|
||||
await _dalamudUtil.RunOnFrameworkThread(() => _namePlateGui.RequestRedraw()).ConfigureAwait(false);
|
||||
@@ -75,7 +91,8 @@ public class GuiHookService : DisposableMediatorSubscriberBase
|
||||
private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
|
||||
{
|
||||
var applyColors = _configService.Current.UseNameColors;
|
||||
if (!applyColors)
|
||||
var showTypingIndicator = _configService.Current.TypingIndicatorShowOnNameplates;
|
||||
if (!applyColors && !showTypingIndicator)
|
||||
return;
|
||||
|
||||
var visibleUsers = _pairManager.GetOnlineUserPairs().Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue);
|
||||
@@ -88,6 +105,11 @@ public class GuiHookService : DisposableMediatorSubscriberBase
|
||||
for (int i = 0; i < _partyList.Count; ++i)
|
||||
partyMembers[i] = _partyList[i]?.GameObject?.Address ?? nint.MaxValue;
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var activeTypers = _typingStateService.GetActiveTypers(TypingDisplayTime);
|
||||
var selfTypingActive = showTypingIndicator && _typingStateService.TryGetSelfTyping(TypingDisplayTime, out _, out _);
|
||||
var localPlayerAddress = selfTypingActive ? _clientState.LocalPlayer?.Address ?? nint.Zero : nint.Zero;
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
if (handler != null && visibleUsersIds.Contains(handler.GameObjectId))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using MareSynchronos.API.Data.Comparer;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
@@ -14,6 +15,7 @@ public class MareProfileManager : MediatorSubscriberBase
|
||||
private const string _nsfw = "Profile not displayed - NSFW";
|
||||
private readonly ApiController _apiController;
|
||||
private readonly MareConfigService _mareConfigService;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly ConcurrentDictionary<UserData, MareProfileData> _mareProfiles = new(UserDataComparer.Instance);
|
||||
|
||||
private readonly MareProfileData _defaultProfileData = new(IsFlagged: false, IsNSFW: false, string.Empty, _noDescription);
|
||||
@@ -21,10 +23,11 @@ public class MareProfileManager : MediatorSubscriberBase
|
||||
private readonly MareProfileData _nsfwProfileData = new(IsFlagged: false, IsNSFW: false, string.Empty, _nsfw);
|
||||
|
||||
public MareProfileManager(ILogger<MareProfileManager> logger, MareConfigService mareConfigService,
|
||||
MareMediator mediator, ApiController apiController) : base(logger, mediator)
|
||||
MareMediator mediator, ApiController apiController, ServerConfigurationManager serverConfigurationManager) : base(logger, mediator)
|
||||
{
|
||||
_mareConfigService = mareConfigService;
|
||||
_apiController = apiController;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
|
||||
Mediator.Subscribe<ClearProfileDataMessage>(this, (msg) =>
|
||||
{
|
||||
@@ -72,4 +75,4 @@ public class MareProfileManager : MediatorSubscriberBase
|
||||
_mareProfiles[data] = _defaultProfileData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using System.Text;
|
||||
|
||||
namespace MareSynchronos.Services.Mediator;
|
||||
|
||||
public sealed class MareMediator : IHostedService, IDisposable
|
||||
public sealed class MareMediator : IHostedService
|
||||
{
|
||||
private readonly Lock _addRemoveLock = new();
|
||||
private readonly ConcurrentDictionary<SubscriberAction, DateTime> _lastErrorTime = [];
|
||||
@@ -109,26 +109,6 @@ public sealed class MareMediator : IHostedService, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
if (!_loopCts.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
_loopCts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// already disposed, swallow
|
||||
}
|
||||
}
|
||||
_loopCts.Dispose();
|
||||
}
|
||||
|
||||
public void SubscribeKeyed<T>(IMediatorSubscriber subscriber, string key, Action<T> action) where T : MessageBase
|
||||
{
|
||||
lock (_addRemoveLock)
|
||||
@@ -239,4 +219,4 @@ public sealed class MareMediator : IHostedService, IDisposable
|
||||
public object Action { get; }
|
||||
public IMediatorSubscriber Subscriber { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,15 +115,12 @@ public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMa
|
||||
public record DiscoveryListUpdated(List<NearbyEntry> Entries) : MessageBase;
|
||||
public record NearbyDetectionToggled(bool Enabled) : MessageBase;
|
||||
public record AllowPairRequestsToggled(bool Enabled) : MessageBase;
|
||||
public record SyncshellDiscoveryUpdated(List<SyncshellDiscoveryEntryDto> Entries) : MessageBase;
|
||||
public record SyncshellAutoDetectStateChanged(string Gid, bool Visible, bool PasswordTemporarilyDisabled) : MessageBase;
|
||||
public record ManualPairInviteMessage(string SourceUid, string SourceAlias, string TargetUid, string? DisplayName, string InviteId) : MessageBase;
|
||||
public record ApplyDefaultPairPermissionsMessage(UserPairDto Pair) : MessageBase;
|
||||
public record ApplyDefaultGroupPermissionsMessage(GroupPairFullInfoDto GroupPair) : MessageBase;
|
||||
public record ApplyDefaultsToAllSyncsMessage(string? Context = null, bool? Disabled = null) : MessageBase;
|
||||
public record PairSyncOverrideChanged(string Uid, bool? DisableSounds, bool? DisableAnimations, bool? DisableVfx) : MessageBase;
|
||||
public record GroupSyncOverrideChanged(string Gid, bool? DisableSounds, bool? DisableAnimations, bool? DisableVfx) : MessageBase;
|
||||
public record NotificationStateChanged(int TotalCount) : MessageBase;
|
||||
|
||||
public record PluginChangeMessage(string InternalName, Version Version, bool IsLoaded) : KeyedMessage(InternalName);
|
||||
#pragma warning restore S2094
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
|
||||
namespace MareSynchronos.Services.Notifications;
|
||||
|
||||
public enum NotificationCategory
|
||||
{
|
||||
AutoDetect,
|
||||
}
|
||||
|
||||
public sealed record NotificationEntry(NotificationCategory Category, string Id, string Title, string? Description, DateTime CreatedAt)
|
||||
{
|
||||
public static NotificationEntry AutoDetect(string uid, string displayName)
|
||||
=> new(NotificationCategory.AutoDetect, uid, displayName, "Nouvelle demande d'appairage via AutoDetect.", DateTime.UtcNow);
|
||||
}
|
||||
|
||||
public sealed class NotificationTracker
|
||||
{
|
||||
private readonly MareMediator _mediator;
|
||||
private readonly Dictionary<(NotificationCategory Category, string Id), NotificationEntry> _entries = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
public NotificationTracker(MareMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public void Upsert(NotificationEntry entry)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_entries[(entry.Category, entry.Id)] = entry;
|
||||
}
|
||||
PublishState();
|
||||
}
|
||||
|
||||
public void Remove(NotificationCategory category, string id)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_entries.Remove((category, id));
|
||||
}
|
||||
PublishState();
|
||||
}
|
||||
|
||||
public IReadOnlyList<NotificationEntry> GetEntries()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _entries.Values
|
||||
.OrderBy(e => e.CreatedAt)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _entries.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishState()
|
||||
{
|
||||
_mediator.Publish(new NotificationStateChanged(Count));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using MareSynchronos.API.Data;
|
||||
using MareSynchronos.API.Data;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
@@ -15,7 +14,7 @@ public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
|
||||
private readonly FileCacheManager _fileCacheManager;
|
||||
private readonly XivDataAnalyzer _xivDataAnalyzer;
|
||||
private CancellationTokenSource? _analysisCts;
|
||||
private CancellationTokenSource? _baseAnalysisCts = new();
|
||||
private CancellationTokenSource _baseAnalysisCts = new();
|
||||
private string _lastDataHash = string.Empty;
|
||||
|
||||
public PairAnalyzer(ILogger<PairAnalyzer> logger, Pair pair, MareMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer)
|
||||
@@ -25,8 +24,8 @@ public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
|
||||
#if DEBUG
|
||||
Mediator.SubscribeKeyed<PairDataAppliedMessage>(this, pair.UserData.UID, (msg) =>
|
||||
{
|
||||
var tokenSource = EnsureFreshCts(ref _baseAnalysisCts);
|
||||
var token = tokenSource.Token;
|
||||
_baseAnalysisCts = _baseAnalysisCts.CancelRecreate();
|
||||
var token = _baseAnalysisCts.Token;
|
||||
if (msg.CharacterData != null)
|
||||
{
|
||||
_ = BaseAnalysis(msg.CharacterData, token);
|
||||
@@ -57,15 +56,17 @@ public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
|
||||
|
||||
public void CancelAnalyze()
|
||||
{
|
||||
CancelAndDispose(ref _analysisCts);
|
||||
_analysisCts?.CancelDispose();
|
||||
_analysisCts = null;
|
||||
}
|
||||
|
||||
public async Task ComputeAnalysis(bool print = true, bool recalculate = false)
|
||||
{
|
||||
Logger.LogDebug("=== Calculating Character Analysis ===");
|
||||
|
||||
var analysisCts = EnsureFreshCts(ref _analysisCts);
|
||||
var cancelToken = analysisCts.Token;
|
||||
_analysisCts = _analysisCts?.CancelRecreate() ?? new();
|
||||
|
||||
var cancelToken = _analysisCts.Token;
|
||||
|
||||
var allFiles = LastAnalysis.SelectMany(v => v.Value.Select(d => d.Value)).ToList();
|
||||
if (allFiles.Exists(c => !c.IsComputed || recalculate))
|
||||
@@ -101,7 +102,8 @@ public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
|
||||
LastPlayerName = Pair.PlayerName ?? string.Empty;
|
||||
Mediator.Publish(new PairDataAnalyzedMessage(Pair.UserData.UID));
|
||||
|
||||
CancelAndDispose(ref _analysisCts);
|
||||
_analysisCts.CancelDispose();
|
||||
_analysisCts = null;
|
||||
|
||||
if (print) PrintAnalysis();
|
||||
}
|
||||
@@ -112,8 +114,8 @@ public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
|
||||
|
||||
if (!disposing) return;
|
||||
|
||||
CancelAndDispose(ref _analysisCts);
|
||||
CancelAndDispose(ref _baseAnalysisCts);
|
||||
_analysisCts?.CancelDispose();
|
||||
_baseAnalysisCts.CancelDispose();
|
||||
}
|
||||
|
||||
private async Task BaseAnalysis(CharacterData charaData, CancellationToken token)
|
||||
@@ -209,26 +211,4 @@ public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
|
||||
UiSharedService.ByteToString(LastAnalysis.Values.Sum(c => c.Values.Sum(v => v.OriginalSize))),
|
||||
UiSharedService.ByteToString(LastAnalysis.Values.Sum(c => c.Values.Sum(v => v.CompressedSize))));
|
||||
}
|
||||
|
||||
private static CancellationTokenSource EnsureFreshCts(ref CancellationTokenSource? cts)
|
||||
{
|
||||
CancelAndDispose(ref cts);
|
||||
cts = new CancellationTokenSource();
|
||||
return cts;
|
||||
}
|
||||
|
||||
private static void CancelAndDispose(ref CancellationTokenSource? cts)
|
||||
{
|
||||
if (cts == null) return;
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
|
||||
cts.Dispose();
|
||||
cts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ public class PartyListTypingService : DisposableMediatorSubscriberBase
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly TypingIndicatorStateService _typingStateService;
|
||||
private static readonly TimeSpan TypingDisplayTime = TimeSpan.FromSeconds(2);
|
||||
private static readonly TimeSpan TypingDisplayDelay = TimeSpan.FromMilliseconds(500);
|
||||
private static readonly TimeSpan TypingDisplayFade = TypingDisplayTime;
|
||||
|
||||
public PartyListTypingService(ILogger<PartyListTypingService> logger,
|
||||
|
||||
@@ -23,6 +23,7 @@ public class PlayerPerformanceService : DisposableMediatorSubscriberBase
|
||||
private readonly MareMediator _mediator;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||
private readonly Dictionary<string, bool> _warnedForPlayers = new(StringComparer.Ordinal);
|
||||
|
||||
public PlayerPerformanceService(ILogger<PlayerPerformanceService> logger, MareMediator mediator,
|
||||
ServerConfigurationManager serverConfigurationManager,
|
||||
@@ -326,4 +327,4 @@ public class PlayerPerformanceService : DisposableMediatorSubscriberBase
|
||||
|
||||
return shrunken;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using MareSynchronos.API.Dto.Group;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.Services.AutoDetect;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.UI;
|
||||
@@ -20,10 +19,9 @@ public class UiFactory
|
||||
private readonly ServerConfigurationManager _serverConfigManager;
|
||||
private readonly MareProfileManager _mareProfileManager;
|
||||
private readonly PerformanceCollectorService _performanceCollectorService;
|
||||
private readonly SyncshellDiscoveryService _syncshellDiscoveryService;
|
||||
|
||||
public UiFactory(ILoggerFactory loggerFactory, MareMediator mareMediator, ApiController apiController,
|
||||
UiSharedService uiSharedService, PairManager pairManager, SyncshellDiscoveryService syncshellDiscoveryService, ServerConfigurationManager serverConfigManager,
|
||||
UiSharedService uiSharedService, PairManager pairManager, ServerConfigurationManager serverConfigManager,
|
||||
MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
@@ -31,7 +29,6 @@ public class UiFactory
|
||||
_apiController = apiController;
|
||||
_uiSharedService = uiSharedService;
|
||||
_pairManager = pairManager;
|
||||
_syncshellDiscoveryService = syncshellDiscoveryService;
|
||||
_serverConfigManager = serverConfigManager;
|
||||
_mareProfileManager = mareProfileManager;
|
||||
_performanceCollectorService = performanceCollectorService;
|
||||
@@ -40,13 +37,13 @@ public class UiFactory
|
||||
public SyncshellAdminUI CreateSyncshellAdminUi(GroupFullInfoDto dto)
|
||||
{
|
||||
return new SyncshellAdminUI(_loggerFactory.CreateLogger<SyncshellAdminUI>(), _mareMediator,
|
||||
_apiController, _uiSharedService, _pairManager, _syncshellDiscoveryService, dto, _performanceCollectorService);
|
||||
_apiController, _uiSharedService, _pairManager, dto, _performanceCollectorService);
|
||||
}
|
||||
|
||||
public StandaloneProfileUi CreateStandaloneProfileUi(Pair pair)
|
||||
{
|
||||
return new StandaloneProfileUi(_loggerFactory.CreateLogger<StandaloneProfileUi>(), _mareMediator,
|
||||
_uiSharedService, _serverConfigManager, _mareProfileManager, pair, _performanceCollectorService);
|
||||
_uiSharedService, _serverConfigManager, _mareProfileManager, _pairManager, pair, _performanceCollectorService);
|
||||
}
|
||||
|
||||
public PermissionWindowUI CreatePermissionPopupUi(Pair pair)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using MareSynchronos.API.Dto.Group;
|
||||
using Dalamud.Plugin.Services;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
@@ -25,35 +25,25 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
private readonly MareConfigService _configService;
|
||||
private readonly DalamudUtilService _dalamud;
|
||||
private readonly IObjectTable _objectTable;
|
||||
private readonly AutoDetectRequestService _requestService;
|
||||
private readonly NearbyDiscoveryService _discoveryService;
|
||||
private readonly NearbyPendingService _pendingService;
|
||||
private readonly PairManager _pairManager;
|
||||
private List<Services.Mediator.NearbyEntry> _entries;
|
||||
private List<Services.Mediator.NearbyEntry> _entries = new();
|
||||
private readonly HashSet<string> _acceptInFlight = new(StringComparer.Ordinal);
|
||||
private readonly SyncshellDiscoveryService _syncshellDiscoveryService;
|
||||
private List<SyncshellDiscoveryEntryDto> _syncshellEntries = [];
|
||||
private bool _syncshellInitialized;
|
||||
private readonly HashSet<string> _syncshellJoinInFlight = new(StringComparer.OrdinalIgnoreCase);
|
||||
private string? _syncshellLastError;
|
||||
|
||||
public AutoDetectUi(ILogger<AutoDetectUi> logger, MareMediator mediator,
|
||||
MareConfigService configService, DalamudUtilService dalamudUtilService,
|
||||
MareConfigService configService, DalamudUtilService dalamudUtilService, IObjectTable objectTable,
|
||||
AutoDetectRequestService requestService, NearbyPendingService pendingService, PairManager pairManager,
|
||||
NearbyDiscoveryService discoveryService, SyncshellDiscoveryService syncshellDiscoveryService,
|
||||
PerformanceCollectorService performanceCollectorService)
|
||||
: base(logger, mediator, "AutoDetect", performanceCollectorService)
|
||||
{
|
||||
_configService = configService;
|
||||
_dalamud = dalamudUtilService;
|
||||
_objectTable = objectTable;
|
||||
_requestService = requestService;
|
||||
_pendingService = pendingService;
|
||||
_pairManager = pairManager;
|
||||
_discoveryService = discoveryService;
|
||||
_syncshellDiscoveryService = syncshellDiscoveryService;
|
||||
Mediator.Subscribe<Services.Mediator.DiscoveryListUpdated>(this, OnDiscoveryUpdated);
|
||||
Mediator.Subscribe<SyncshellDiscoveryUpdated>(this, OnSyncshellDiscoveryUpdated);
|
||||
_entries = _discoveryService.SnapshotEntries();
|
||||
|
||||
Flags |= ImGuiWindowFlags.NoScrollbar;
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
@@ -90,12 +80,14 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
|
||||
});
|
||||
|
||||
DrawStyledTab("Proximité", accent, inactiveTab, hoverTab, DrawNearbyTab);
|
||||
DrawStyledTab("Syncshell", accent, inactiveTab, hoverTab, DrawSyncshellTab);
|
||||
}
|
||||
|
||||
public void DrawInline()
|
||||
{
|
||||
DrawInternal();
|
||||
using (ImRaii.Disabled(true))
|
||||
{
|
||||
DrawStyledTab("Syncshell", accent, inactiveTab, hoverTab, () =>
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Disponible prochainement.", ImGuiColors.DalamudGrey3);
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawStyledTab(string label, Vector4 accent, Vector4 inactive, Vector4 hover, Action draw, bool disabled = false)
|
||||
@@ -221,229 +213,61 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
|
||||
|
||||
ImGuiHelpers.ScaledDummy(6);
|
||||
|
||||
var sourceEntries = _entries.Count > 0 ? _entries : _discoveryService.SnapshotEntries();
|
||||
var orderedEntries = sourceEntries
|
||||
.Where(e => e.IsMatch)
|
||||
.OrderBy(e => float.IsNaN(e.Distance) ? float.MaxValue : e.Distance)
|
||||
.ToList();
|
||||
|
||||
if (orderedEntries.Count == 0)
|
||||
// Table header
|
||||
if (ImGui.BeginTable("autodetect-nearby", 5, ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Aucune présence UmbraSync détectée à proximité pour le moment.", ImGuiColors.DalamudGrey3);
|
||||
return;
|
||||
}
|
||||
ImGui.TableSetupColumn("Name");
|
||||
ImGui.TableSetupColumn("World");
|
||||
ImGui.TableSetupColumn("Distance");
|
||||
ImGui.TableSetupColumn("Status");
|
||||
ImGui.TableSetupColumn("Action");
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
if (!ImGui.BeginTable("autodetect-nearby", 5, ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.RowBg))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.TableSetupColumn("Nom");
|
||||
ImGui.TableSetupColumn("Monde");
|
||||
ImGui.TableSetupColumn("Distance");
|
||||
ImGui.TableSetupColumn("Statut");
|
||||
ImGui.TableSetupColumn("Action");
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
for (int i = 0; i < orderedEntries.Count; i++)
|
||||
{
|
||||
var entry = orderedEntries[i];
|
||||
bool alreadyPaired = IsAlreadyPairedByUidOrAlias(entry);
|
||||
bool overDistance = !float.IsNaN(entry.Distance) && entry.Distance > maxDist;
|
||||
bool canRequest = entry.AcceptPairRequests && !string.IsNullOrEmpty(entry.Token) && !alreadyPaired;
|
||||
|
||||
string displayName = entry.DisplayName ?? entry.Name;
|
||||
string worldName = entry.WorldId == 0
|
||||
? "-"
|
||||
: (_dalamud.WorldData.Value.TryGetValue(entry.WorldId, out var mappedWorld) ? mappedWorld : entry.WorldId.ToString(CultureInfo.InvariantCulture));
|
||||
string distanceText = float.IsNaN(entry.Distance) ? "-" : $"{entry.Distance:0.0} m";
|
||||
|
||||
string status = alreadyPaired
|
||||
? "Déjà appairé"
|
||||
: overDistance
|
||||
? $"Hors portée (> {maxDist} m)"
|
||||
: !entry.AcceptPairRequests
|
||||
? "Invitations refusées"
|
||||
: string.IsNullOrEmpty(entry.Token)
|
||||
? "Indisponible"
|
||||
: "Disponible";
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(displayName);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(worldName);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(distanceText);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(status);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
using (ImRaii.PushId(i))
|
||||
var data = _entries.Count > 0 ? _entries.Where(e => e.IsMatch).ToList() : new List<Services.Mediator.NearbyEntry>();
|
||||
foreach (var e in data)
|
||||
{
|
||||
if (canRequest && !overDistance)
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(e.Name);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(e.WorldId == 0 ? "-" : (_dalamud.WorldData.Value.TryGetValue(e.WorldId, out var w) ? w : e.WorldId.ToString()));
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(float.IsNaN(e.Distance) ? "-" : $"{e.Distance:0.0} m");
|
||||
ImGui.TableNextColumn();
|
||||
bool alreadyPaired = IsAlreadyPairedByUidOrAlias(e);
|
||||
string status = alreadyPaired ? "Paired" : (string.IsNullOrEmpty(e.Token) ? "Requests disabled" : "On Umbra");
|
||||
ImGui.TextUnformatted(status);
|
||||
ImGui.TableNextColumn();
|
||||
using (ImRaii.Disabled(alreadyPaired || string.IsNullOrEmpty(e.Token)))
|
||||
{
|
||||
if (ImGui.Button("Envoyer invitation"))
|
||||
if (alreadyPaired)
|
||||
{
|
||||
_ = _requestService.SendRequestAsync(entry.Token!, entry.Uid, entry.DisplayName);
|
||||
ImGui.Button($"Already sync##{e.Name}");
|
||||
}
|
||||
UiSharedService.AttachToolTip("Envoie une demande d'appairage via AutoDetect.");
|
||||
}
|
||||
else
|
||||
{
|
||||
string reason = alreadyPaired
|
||||
? "Vous êtes déjà appairé avec ce joueur."
|
||||
: overDistance
|
||||
? $"Ce joueur est au-delà de la distance maximale configurée ({maxDist} m)."
|
||||
: !entry.AcceptPairRequests
|
||||
? "Ce joueur a désactivé la réception automatique des invitations."
|
||||
: string.IsNullOrEmpty(entry.Token)
|
||||
? "Impossible d'obtenir un jeton d'invitation pour ce joueur."
|
||||
: string.Empty;
|
||||
|
||||
ImGui.TextDisabled(status);
|
||||
if (!string.IsNullOrEmpty(reason))
|
||||
else if (string.IsNullOrEmpty(e.Token))
|
||||
{
|
||||
UiSharedService.AttachToolTip(reason);
|
||||
ImGui.Button($"Requests disabled##{e.Name}");
|
||||
}
|
||||
else if (ImGui.Button($"Send request##{e.Name}"))
|
||||
{
|
||||
_ = _requestService.SendRequestAsync(e.Token!, e.Uid, e.DisplayName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
private async Task JoinSyncshellAsync(SyncshellDiscoveryEntryDto entry)
|
||||
{
|
||||
if (!_syncshellJoinInFlight.Add(entry.GID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var joined = await _syncshellDiscoveryService.JoinAsync(entry.GID, CancellationToken.None).ConfigureAwait(false);
|
||||
if (joined)
|
||||
{
|
||||
Mediator.Publish(new NotificationMessage("AutoDetect Syncshell", $"Rejoint {entry.Alias ?? entry.GID}.", NotificationType.Info, TimeSpan.FromSeconds(5)));
|
||||
await _syncshellDiscoveryService.RefreshAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_syncshellLastError = $"Impossible de rejoindre {entry.Alias ?? entry.GID}.";
|
||||
Mediator.Publish(new NotificationMessage("AutoDetect Syncshell", _syncshellLastError, NotificationType.Warning, TimeSpan.FromSeconds(5)));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_syncshellLastError = $"Erreur lors de l'adhésion : {ex.Message}";
|
||||
Mediator.Publish(new NotificationMessage("AutoDetect Syncshell", _syncshellLastError, NotificationType.Error, TimeSpan.FromSeconds(5)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncshellJoinInFlight.Remove(entry.GID);
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSyncshellTab()
|
||||
public override void OnOpen()
|
||||
{
|
||||
if (!_syncshellInitialized)
|
||||
{
|
||||
_syncshellInitialized = true;
|
||||
_ = _syncshellDiscoveryService.RefreshAsync(CancellationToken.None);
|
||||
}
|
||||
base.OnOpen();
|
||||
Mediator.Subscribe<Services.Mediator.DiscoveryListUpdated>(this, OnDiscoveryUpdated);
|
||||
}
|
||||
|
||||
bool isRefreshing = _syncshellDiscoveryService.IsRefreshing;
|
||||
var serviceError = _syncshellDiscoveryService.LastError;
|
||||
|
||||
if (ImGui.Button("Actualiser la liste"))
|
||||
{
|
||||
_ = _syncshellDiscoveryService.RefreshAsync(CancellationToken.None);
|
||||
}
|
||||
UiSharedService.AttachToolTip("Met à jour la liste des Syncshells ayant activé l'AutoDetect.");
|
||||
|
||||
if (isRefreshing)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("Actualisation...");
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(4);
|
||||
UiSharedService.TextWrapped("Les Syncshells affichées ont temporairement désactivé leur mot de passe pour permettre un accès direct via AutoDetect. Rejoignez-les uniquement si vous faites confiance aux administrateurs.");
|
||||
|
||||
if (!string.IsNullOrEmpty(serviceError))
|
||||
{
|
||||
UiSharedService.ColorTextWrapped(serviceError, ImGuiColors.DalamudRed);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(_syncshellLastError))
|
||||
{
|
||||
UiSharedService.ColorTextWrapped(_syncshellLastError!, ImGuiColors.DalamudOrange);
|
||||
}
|
||||
|
||||
var entries = _syncshellEntries.Count > 0 ? _syncshellEntries : _syncshellDiscoveryService.Entries.ToList();
|
||||
if (entries.Count == 0)
|
||||
{
|
||||
ImGuiHelpers.ScaledDummy(4);
|
||||
UiSharedService.ColorTextWrapped("Aucune Syncshell n'est actuellement visible dans AutoDetect.", ImGuiColors.DalamudGrey3);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ImGui.BeginTable("autodetect-syncshells", 5, ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.RowBg))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.TableSetupColumn("Nom");
|
||||
ImGui.TableSetupColumn("Propriétaire");
|
||||
ImGui.TableSetupColumn("Membres");
|
||||
ImGui.TableSetupColumn("Invitations");
|
||||
ImGui.TableSetupColumn("Action");
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (var entry in entries.OrderBy(e => e.Alias ?? e.GID, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
bool alreadyMember = _pairManager.Groups.Keys.Any(g => string.Equals(g.GID, entry.GID, StringComparison.OrdinalIgnoreCase));
|
||||
bool joining = _syncshellJoinInFlight.Contains(entry.GID);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(string.IsNullOrEmpty(entry.Alias) ? entry.GID : $"{entry.Alias} ({entry.GID})");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(string.IsNullOrEmpty(entry.OwnerAlias) ? entry.OwnerUID : $"{entry.OwnerAlias} ({entry.OwnerUID})");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(entry.MemberCount.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
string inviteMode = entry.AutoAcceptPairs ? "Auto" : "Manuel";
|
||||
ImGui.TextUnformatted(inviteMode);
|
||||
if (!entry.AutoAcceptPairs)
|
||||
{
|
||||
UiSharedService.AttachToolTip("L'administrateur doit approuver manuellement les nouveaux membres.");
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
using (ImRaii.Disabled(alreadyMember || joining))
|
||||
{
|
||||
if (alreadyMember)
|
||||
{
|
||||
ImGui.TextDisabled("Déjà membre");
|
||||
}
|
||||
else if (joining)
|
||||
{
|
||||
ImGui.TextDisabled("Connexion...");
|
||||
}
|
||||
else if (ImGui.Button("Rejoindre"))
|
||||
{
|
||||
_syncshellLastError = null;
|
||||
_ = JoinSyncshellAsync(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
public override void OnClose()
|
||||
{
|
||||
Mediator.Unsubscribe<Services.Mediator.DiscoveryListUpdated>(this);
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
private void OnDiscoveryUpdated(Services.Mediator.DiscoveryListUpdated msg)
|
||||
@@ -451,11 +275,6 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
|
||||
_entries = msg.Entries;
|
||||
}
|
||||
|
||||
private void OnSyncshellDiscoveryUpdated(SyncshellDiscoveryUpdated msg)
|
||||
{
|
||||
_syncshellEntries = msg.Entries;
|
||||
}
|
||||
|
||||
private bool IsAlreadyPairedByUidOrAlias(Services.Mediator.NearbyEntry e)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -169,6 +169,10 @@ public sealed class ChangelogUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
return new List<ChangelogEntry>
|
||||
{
|
||||
new(new Version(0, 1, 9, 6), "0.1.9.6", new List<ChangelogLine>
|
||||
{
|
||||
new("Possibilité de désactiver l'alerte self-analysis (Settings => Performance)."),
|
||||
}),
|
||||
new(new Version(0, 1, 9, 5), "0.1.9.5", new List<ChangelogLine>
|
||||
{
|
||||
new("Fix l'affichage de la bulle dans la liste du groupe."),
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.Text;
|
||||
|
||||
namespace MareSynchronos.UI;
|
||||
|
||||
public sealed partial class CharaDataHubUi
|
||||
internal sealed partial class CharaDataHubUi
|
||||
{
|
||||
private static string GetAccessTypeString(AccessTypeDto dto) => dto switch
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ using MareSynchronos.Services.CharaData.Models;
|
||||
|
||||
namespace MareSynchronos.UI;
|
||||
|
||||
public sealed partial class CharaDataHubUi
|
||||
internal sealed partial class CharaDataHubUi
|
||||
{
|
||||
private string _joinLobbyId = string.Empty;
|
||||
private void DrawGposeTogether()
|
||||
@@ -90,7 +90,7 @@ public sealed partial class CharaDataHubUi
|
||||
if (!_uiSharedService.IsInGpose)
|
||||
{
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
UiSharedService.DrawGroupedCenteredColorText("Assigning users to characters is only available in GPose.", UiSharedService.AccentColor, 300);
|
||||
UiSharedService.DrawGroupedCenteredColorText("Assigning users to characters is only available in GPose.", ImGuiColors.DalamudYellow, 300);
|
||||
}
|
||||
UiSharedService.DistanceSeparator();
|
||||
ImGui.TextUnformatted("Users In Lobby");
|
||||
@@ -104,7 +104,7 @@ public sealed partial class CharaDataHubUi
|
||||
|
||||
if (!_charaDataGposeTogetherManager.UsersInLobby.Any() && !string.IsNullOrEmpty(_charaDataGposeTogetherManager.CurrentGPoseLobbyId))
|
||||
{
|
||||
UiSharedService.DrawGroupedCenteredColorText("No other users in current GPose lobby", UiSharedService.AccentColor);
|
||||
UiSharedService.DrawGroupedCenteredColorText("No other users in current GPose lobby", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ using System.Numerics;
|
||||
|
||||
namespace MareSynchronos.UI;
|
||||
|
||||
public sealed partial class CharaDataHubUi
|
||||
internal sealed partial class CharaDataHubUi
|
||||
{
|
||||
private void DrawEditCharaData(CharaDataFullExtendedDto? dataDto)
|
||||
{
|
||||
@@ -18,7 +18,7 @@ public sealed partial class CharaDataHubUi
|
||||
if (dataDto == null)
|
||||
{
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
UiSharedService.DrawGroupedCenteredColorText("Select an entry above to edit its data.", UiSharedService.AccentColor);
|
||||
UiSharedService.DrawGroupedCenteredColorText("Select an entry above to edit its data.", ImGuiColors.DalamudYellow);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed partial class CharaDataHubUi
|
||||
|
||||
if (updateDto == null)
|
||||
{
|
||||
UiSharedService.DrawGroupedCenteredColorText("Something went awfully wrong and there's no update DTO. Try updating Character Data via the button above.", UiSharedService.AccentColor);
|
||||
UiSharedService.DrawGroupedCenteredColorText("Something went awfully wrong and there's no update DTO. Try updating Character Data via the button above.", ImGuiColors.DalamudYellow);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public sealed partial class CharaDataHubUi
|
||||
}
|
||||
if (_charaDataManager.CharaUpdateTask != null && !_charaDataManager.CharaUpdateTask.IsCompleted)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Updating data on server, please wait.", UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped("Updating data on server, please wait.", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public sealed partial class CharaDataHubUi
|
||||
{
|
||||
if (_charaDataManager.UploadProgress != null)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped(_charaDataManager.UploadProgress.Value ?? string.Empty, UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped(_charaDataManager.UploadProgress.Value ?? string.Empty, ImGuiColors.DalamudYellow);
|
||||
}
|
||||
if ((!_charaDataManager.UploadTask?.IsCompleted ?? false) && _uiSharedService.IconTextButton(FontAwesomeIcon.Ban, "Cancel Upload"))
|
||||
{
|
||||
@@ -230,7 +230,7 @@ public sealed partial class CharaDataHubUi
|
||||
ImGui.SameLine();
|
||||
ImGuiHelpers.ScaledDummy(20, 1);
|
||||
ImGui.SameLine();
|
||||
UiSharedService.ColorTextWrapped("New data was set. It may contain files that require to be uploaded (will happen on Saving to server)", UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped("New data was set. It may contain files that require to be uploaded (will happen on Saving to server)", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted("Contains Manipulation Data");
|
||||
@@ -385,7 +385,7 @@ public sealed partial class CharaDataHubUi
|
||||
}
|
||||
}
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, UiSharedService.AccentColor, poseCount == maxPoses))
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, poseCount == maxPoses))
|
||||
ImGui.TextUnformatted($"{poseCount}/{maxPoses} poses attached");
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
|
||||
@@ -395,7 +395,7 @@ public sealed partial class CharaDataHubUi
|
||||
if (!_uiSharedService.IsInGpose && _charaDataManager.BrioAvailable)
|
||||
{
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
UiSharedService.DrawGroupedCenteredColorText("To attach pose and world data you need to be in GPose.", UiSharedService.AccentColor);
|
||||
UiSharedService.DrawGroupedCenteredColorText("To attach pose and world data you need to be in GPose.", ImGuiColors.DalamudYellow);
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
}
|
||||
else if (!_charaDataManager.BrioAvailable)
|
||||
@@ -414,7 +414,7 @@ public sealed partial class CharaDataHubUi
|
||||
if (pose.Id == null)
|
||||
{
|
||||
ImGui.SameLine(50);
|
||||
_uiSharedService.IconText(FontAwesomeIcon.Plus, UiSharedService.AccentColor);
|
||||
_uiSharedService.IconText(FontAwesomeIcon.Plus, ImGuiColors.DalamudYellow);
|
||||
UiSharedService.AttachToolTip("This pose has not been added to the server yet. Save changes to upload this Pose data.");
|
||||
}
|
||||
|
||||
@@ -422,14 +422,14 @@ public sealed partial class CharaDataHubUi
|
||||
if (poseHasChanges)
|
||||
{
|
||||
ImGui.SameLine(50);
|
||||
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UiSharedService.AccentColor);
|
||||
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, ImGuiColors.DalamudYellow);
|
||||
UiSharedService.AttachToolTip("This pose has changes that have not been saved to the server yet.");
|
||||
}
|
||||
|
||||
ImGui.SameLine(75);
|
||||
if (pose.Description == null && pose.WorldData == null && pose.PoseData == null)
|
||||
{
|
||||
UiSharedService.ColorText("Pose scheduled for deletion", UiSharedService.AccentColor);
|
||||
UiSharedService.ColorText("Pose scheduled for deletion", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -544,8 +544,7 @@ public sealed partial class CharaDataHubUi
|
||||
{
|
||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowCircleDown, "Download your Online Character Data from Server"))
|
||||
{
|
||||
var cts = EnsureFreshCts(ref _disposalCts);
|
||||
_ = _charaDataManager.GetAllData(cts.Token);
|
||||
_ = _charaDataManager.GetAllData(_disposalCts.Token);
|
||||
}
|
||||
}
|
||||
if (_charaDataManager.DataGetTimeoutTask != null && !_charaDataManager.DataGetTimeoutTask.IsCompleted)
|
||||
@@ -586,7 +585,7 @@ public sealed partial class CharaDataHubUi
|
||||
var idText = entry.FullId;
|
||||
if (uDto?.HasChanges ?? false)
|
||||
{
|
||||
UiSharedService.ColorText(idText, UiSharedService.AccentColor);
|
||||
UiSharedService.ColorText(idText, ImGuiColors.DalamudYellow);
|
||||
UiSharedService.AttachToolTip("This entry has unsaved changes");
|
||||
}
|
||||
else
|
||||
@@ -641,7 +640,7 @@ public sealed partial class CharaDataHubUi
|
||||
FontAwesomeIcon eIcon = FontAwesomeIcon.None;
|
||||
if (!Equals(DateTime.MaxValue, entry.ExpiryDate))
|
||||
eIcon = FontAwesomeIcon.Clock;
|
||||
_uiSharedService.IconText(eIcon, UiSharedService.AccentColor);
|
||||
_uiSharedService.IconText(eIcon, ImGuiColors.DalamudYellow);
|
||||
if (ImGui.IsItemClicked()) SelectedDtoId = entry.Id;
|
||||
if (eIcon != FontAwesomeIcon.None)
|
||||
{
|
||||
@@ -655,8 +654,7 @@ public sealed partial class CharaDataHubUi
|
||||
{
|
||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Plus, "New Character Data Entry"))
|
||||
{
|
||||
var cts = EnsureFreshCts(ref _closalCts);
|
||||
_charaDataManager.CreateCharaDataEntry(cts.Token);
|
||||
_charaDataManager.CreateCharaDataEntry(_closalCts.Token);
|
||||
_selectNewEntry = true;
|
||||
}
|
||||
}
|
||||
@@ -677,13 +675,13 @@ public sealed partial class CharaDataHubUi
|
||||
if (_charaDataManager.OwnCharaData.Count == _charaDataManager.MaxCreatableCharaData)
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
UiSharedService.ColorTextWrapped("You have reached the maximum Character Data entries and cannot create more.", UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped("You have reached the maximum Character Data entries and cannot create more.", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
}
|
||||
|
||||
if (_charaDataManager.DataCreationTask != null && !_charaDataManager.DataCreationTask.IsCompleted)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Creating new character data entry on server...", UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped("Creating new character data entry on server...", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
else if (_charaDataManager.DataCreationTask != null && _charaDataManager.DataCreationTask.IsCompleted)
|
||||
{
|
||||
@@ -850,4 +848,4 @@ public sealed partial class CharaDataHubUi
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using System.Numerics;
|
||||
|
||||
namespace MareSynchronos.UI;
|
||||
|
||||
public sealed partial class CharaDataHubUi
|
||||
internal partial class CharaDataHubUi
|
||||
{
|
||||
private void DrawNearbyPoses()
|
||||
{
|
||||
@@ -86,7 +86,7 @@ public sealed partial class CharaDataHubUi
|
||||
if (!_uiSharedService.IsInGpose)
|
||||
{
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
UiSharedService.DrawGroupedCenteredColorText("Spawning and applying pose data is only available in GPose.", UiSharedService.AccentColor);
|
||||
UiSharedService.DrawGroupedCenteredColorText("Spawning and applying pose data is only available in GPose.", ImGuiColors.DalamudYellow);
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public sealed partial class CharaDataHubUi
|
||||
using var indent = ImRaii.PushIndent(5f);
|
||||
if (_charaDataNearbyManager.NearbyData.Count == 0)
|
||||
{
|
||||
UiSharedService.DrawGroupedCenteredColorText("No Shared World Poses found nearby.", UiSharedService.AccentColor);
|
||||
UiSharedService.DrawGroupedCenteredColorText("No Shared World Poses found nearby.", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
|
||||
bool wasAnythingHovered = false;
|
||||
@@ -204,8 +204,7 @@ public sealed partial class CharaDataHubUi
|
||||
{
|
||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowCircleDown, "Update Data Shared With You"))
|
||||
{
|
||||
var cts = EnsureFreshCts(ref _disposalCts);
|
||||
_ = _charaDataManager.GetAllSharedData(cts.Token).ContinueWith(u => UpdateFilteredItems());
|
||||
_ = _charaDataManager.GetAllSharedData(_disposalCts.Token).ContinueWith(u => UpdateFilteredItems());
|
||||
}
|
||||
}
|
||||
if (_charaDataManager.GetSharedWithYouTimeoutTask != null && !_charaDataManager.GetSharedWithYouTimeoutTask.IsCompleted)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
@@ -17,13 +15,10 @@ using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace MareSynchronos.UI;
|
||||
|
||||
public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
internal sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
private const int maxPoses = 10;
|
||||
private readonly CharaDataManager _charaDataManager;
|
||||
@@ -35,10 +30,9 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
private readonly CharaDataGposeTogetherManager _charaDataGposeTogetherManager;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly UiSharedService _uiSharedService;
|
||||
private readonly McdfShareManager _mcdfShareManager;
|
||||
private CancellationTokenSource? _closalCts = new();
|
||||
private CancellationTokenSource _closalCts = new();
|
||||
private bool _disableUI = false;
|
||||
private CancellationTokenSource? _disposalCts = new();
|
||||
private CancellationTokenSource _disposalCts = new();
|
||||
private string _exportDescription = string.Empty;
|
||||
private string _filterCodeNote = string.Empty;
|
||||
private string _filterDescription = string.Empty;
|
||||
@@ -68,15 +62,6 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string? candidate, string fallback)
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
if (string.IsNullOrWhiteSpace(candidate)) return fallback;
|
||||
|
||||
var sanitized = new string(candidate.Select(ch => invalidChars.Contains(ch) ? '_' : ch).ToArray()).Trim('_');
|
||||
return string.IsNullOrWhiteSpace(sanitized) ? fallback : sanitized;
|
||||
}
|
||||
private string _selectedSpecificUserIndividual = string.Empty;
|
||||
private string _selectedSpecificGroupIndividual = string.Empty;
|
||||
private string _sharedWithYouDescriptionFilter = string.Empty;
|
||||
@@ -88,21 +73,12 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
private string? _openComboHybridId = null;
|
||||
private (string Id, string? Alias, string AliasOrId, string? Note)[]? _openComboHybridEntries = null;
|
||||
private bool _comboHybridUsedLastFrame = false;
|
||||
private bool _mcdfShareInitialized;
|
||||
private string _mcdfShareDescription = string.Empty;
|
||||
private readonly List<string> _mcdfShareAllowedIndividuals = new();
|
||||
private readonly List<string> _mcdfShareAllowedSyncshells = new();
|
||||
private string _mcdfShareIndividualDropdownSelection = string.Empty;
|
||||
private string _mcdfShareIndividualInput = string.Empty;
|
||||
private string _mcdfShareSyncshellDropdownSelection = string.Empty;
|
||||
private string _mcdfShareSyncshellInput = string.Empty;
|
||||
private int _mcdfShareExpireDays;
|
||||
|
||||
public CharaDataHubUi(ILogger<CharaDataHubUi> logger, MareMediator mediator, PerformanceCollectorService performanceCollectorService,
|
||||
CharaDataManager charaDataManager, CharaDataNearbyManager charaDataNearbyManager, CharaDataConfigService configService,
|
||||
UiSharedService uiSharedService, ServerConfigurationManager serverConfigurationManager,
|
||||
DalamudUtilService dalamudUtilService, FileDialogManager fileDialogManager, PairManager pairManager,
|
||||
CharaDataGposeTogetherManager charaDataGposeTogetherManager, McdfShareManager mcdfShareManager)
|
||||
CharaDataGposeTogetherManager charaDataGposeTogetherManager)
|
||||
: base(logger, mediator, "Umbra Character Data Hub###UmbraCharaDataUI", performanceCollectorService)
|
||||
{
|
||||
SetWindowSizeConstraints();
|
||||
@@ -116,7 +92,6 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
_fileDialogManager = fileDialogManager;
|
||||
_pairManager = pairManager;
|
||||
_charaDataGposeTogetherManager = charaDataGposeTogetherManager;
|
||||
_mcdfShareManager = mcdfShareManager;
|
||||
Mediator.Subscribe<GposeStartMessage>(this, (_) => IsOpen |= _configService.Current.OpenMareHubOnGposeStart);
|
||||
Mediator.Subscribe<OpenCharaDataHubWithFilterMessage>(this, (msg) =>
|
||||
{
|
||||
@@ -148,14 +123,7 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_closalCts?.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
EnsureFreshCts(ref _closalCts);
|
||||
_closalCts.Cancel();
|
||||
SelectedDtoId = string.Empty;
|
||||
_filteredDict = null;
|
||||
_sharedWithYouOwnerFilter = string.Empty;
|
||||
@@ -167,34 +135,21 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
|
||||
public override void OnOpen()
|
||||
{
|
||||
EnsureFreshCts(ref _closalCts);
|
||||
_closalCts = _closalCts.CancelRecreate();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
CancelAndDispose(ref _closalCts);
|
||||
CancelAndDispose(ref _disposalCts);
|
||||
_closalCts.CancelDispose();
|
||||
_disposalCts.CancelDispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void DrawInternal()
|
||||
{
|
||||
DrawHubContent();
|
||||
}
|
||||
|
||||
public void DrawInline()
|
||||
{
|
||||
using (ImRaii.PushId("CharaDataHubInline"))
|
||||
{
|
||||
DrawHubContent();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHubContent()
|
||||
{
|
||||
if (!_comboHybridUsedLastFrame)
|
||||
{
|
||||
@@ -235,7 +190,7 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_charaDataManager.DataApplicationProgress))
|
||||
{
|
||||
UiSharedService.ColorTextWrapped(_charaDataManager.DataApplicationProgress, UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped(_charaDataManager.DataApplicationProgress, ImGuiColors.DalamudYellow);
|
||||
}
|
||||
if (_charaDataManager.DataApplicationTask != null)
|
||||
{
|
||||
@@ -245,140 +200,115 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
});
|
||||
|
||||
using var tabs = ImRaii.TabBar("TabsTopLevel");
|
||||
bool smallUi = false;
|
||||
using (var topTabColor = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.AccentColor))
|
||||
using (var topTabHoverColor = ImRaii.PushColor(ImGuiCol.TabHovered, UiSharedService.AccentHoverColor))
|
||||
using (var topTabActiveColor = ImRaii.PushColor(ImGuiCol.TabActive, UiSharedService.AccentActiveColor))
|
||||
|
||||
_isHandlingSelf = _charaDataManager.HandledCharaData.Any(c => c.Value.IsSelf);
|
||||
if (_isHandlingSelf) _openMcdOnlineOnNextRun = false;
|
||||
|
||||
using (var gposeTogetherTabItem = ImRaii.TabItem("GPose Together"))
|
||||
{
|
||||
using var tabs = ImRaii.TabBar("TabsTopLevel");
|
||||
|
||||
_isHandlingSelf = _charaDataManager.HandledCharaData.Any(c => c.Value.IsSelf);
|
||||
if (_isHandlingSelf) _openMcdOnlineOnNextRun = false;
|
||||
|
||||
using (var gposeTogetherTabItem = ImRaii.TabItem("GPose Together"))
|
||||
if (gposeTogetherTabItem)
|
||||
{
|
||||
if (gposeTogetherTabItem)
|
||||
{
|
||||
smallUi = true;
|
||||
smallUi = true;
|
||||
|
||||
DrawGposeTogether();
|
||||
}
|
||||
DrawGposeTogether();
|
||||
}
|
||||
}
|
||||
|
||||
using (var applicationTabItem = ImRaii.TabItem("Data Application", _openDataApplicationShared ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None))
|
||||
using (var applicationTabItem = ImRaii.TabItem("Data Application", _openDataApplicationShared ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None))
|
||||
{
|
||||
if (applicationTabItem)
|
||||
{
|
||||
if (applicationTabItem)
|
||||
smallUi = true;
|
||||
using var appTabs = ImRaii.TabBar("TabsApplicationLevel");
|
||||
|
||||
using (ImRaii.Disabled(!_uiSharedService.IsInGpose))
|
||||
{
|
||||
smallUi = true;
|
||||
using (var appTabColor = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.AccentColor))
|
||||
using (var appTabHoverColor = ImRaii.PushColor(ImGuiCol.TabHovered, UiSharedService.AccentHoverColor))
|
||||
using (var appTabActiveColor = ImRaii.PushColor(ImGuiCol.TabActive, UiSharedService.AccentActiveColor))
|
||||
using (var gposeTabItem = ImRaii.TabItem("GPose Actors"))
|
||||
{
|
||||
using var appTabs = ImRaii.TabBar("TabsApplicationLevel");
|
||||
|
||||
using (ImRaii.Disabled(!_uiSharedService.IsInGpose))
|
||||
if (gposeTabItem)
|
||||
{
|
||||
using (var gposeTabItem = ImRaii.TabItem("GPose Actors"))
|
||||
{
|
||||
if (gposeTabItem)
|
||||
{
|
||||
using var id = ImRaii.PushId("gposeControls");
|
||||
DrawGposeControls();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_uiSharedService.IsInGpose)
|
||||
UiSharedService.AttachToolTip("Only available in GPose");
|
||||
|
||||
using (var nearbyPosesTabItem = ImRaii.TabItem("Poses Nearby"))
|
||||
{
|
||||
if (nearbyPosesTabItem)
|
||||
{
|
||||
using var id = ImRaii.PushId("nearbyPoseControls");
|
||||
_charaDataNearbyManager.ComputeNearbyData = true;
|
||||
|
||||
DrawNearbyPoses();
|
||||
}
|
||||
else
|
||||
{
|
||||
_charaDataNearbyManager.ComputeNearbyData = false;
|
||||
}
|
||||
}
|
||||
|
||||
using (var gposeTabItem = ImRaii.TabItem("Apply Data", _openDataApplicationShared ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None))
|
||||
{
|
||||
if (gposeTabItem)
|
||||
{
|
||||
smallUi |= true;
|
||||
using var id = ImRaii.PushId("applyData");
|
||||
DrawDataApplication();
|
||||
}
|
||||
using var id = ImRaii.PushId("gposeControls");
|
||||
DrawGposeControls();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!_uiSharedService.IsInGpose)
|
||||
UiSharedService.AttachToolTip("Only available in GPose");
|
||||
|
||||
using (var nearbyPosesTabItem = ImRaii.TabItem("Poses Nearby"))
|
||||
{
|
||||
_charaDataNearbyManager.ComputeNearbyData = false;
|
||||
if (nearbyPosesTabItem)
|
||||
{
|
||||
using var id = ImRaii.PushId("nearbyPoseControls");
|
||||
_charaDataNearbyManager.ComputeNearbyData = true;
|
||||
|
||||
DrawNearbyPoses();
|
||||
}
|
||||
else
|
||||
{
|
||||
_charaDataNearbyManager.ComputeNearbyData = false;
|
||||
}
|
||||
}
|
||||
|
||||
using (var gposeTabItem = ImRaii.TabItem("Apply Data", _openDataApplicationShared ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None))
|
||||
{
|
||||
if (gposeTabItem)
|
||||
{
|
||||
smallUi |= true;
|
||||
using var id = ImRaii.PushId("applyData");
|
||||
DrawDataApplication();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (ImRaii.Disabled(_isHandlingSelf))
|
||||
else
|
||||
{
|
||||
ImGuiTabItemFlags flagsTopLevel = ImGuiTabItemFlags.None;
|
||||
if (_openMcdOnlineOnNextRun)
|
||||
{
|
||||
flagsTopLevel = ImGuiTabItemFlags.SetSelected;
|
||||
_openMcdOnlineOnNextRun = false;
|
||||
}
|
||||
_charaDataNearbyManager.ComputeNearbyData = false;
|
||||
}
|
||||
}
|
||||
|
||||
using (var creationTabItem = ImRaii.TabItem("Data Creation", flagsTopLevel))
|
||||
using (ImRaii.Disabled(_isHandlingSelf))
|
||||
{
|
||||
ImGuiTabItemFlags flagsTopLevel = ImGuiTabItemFlags.None;
|
||||
if (_openMcdOnlineOnNextRun)
|
||||
{
|
||||
flagsTopLevel = ImGuiTabItemFlags.SetSelected;
|
||||
_openMcdOnlineOnNextRun = false;
|
||||
}
|
||||
|
||||
using (var creationTabItem = ImRaii.TabItem("Data Creation", flagsTopLevel))
|
||||
{
|
||||
if (creationTabItem)
|
||||
{
|
||||
if (creationTabItem)
|
||||
using var creationTabs = ImRaii.TabBar("TabsCreationLevel");
|
||||
|
||||
ImGuiTabItemFlags flags = ImGuiTabItemFlags.None;
|
||||
if (_openMcdOnlineOnNextRun)
|
||||
{
|
||||
using (var creationTabColor = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.AccentColor))
|
||||
using (var creationTabHoverColor = ImRaii.PushColor(ImGuiCol.TabHovered, UiSharedService.AccentHoverColor))
|
||||
using (var creationTabActiveColor = ImRaii.PushColor(ImGuiCol.TabActive, UiSharedService.AccentActiveColor))
|
||||
flags = ImGuiTabItemFlags.SetSelected;
|
||||
_openMcdOnlineOnNextRun = false;
|
||||
}
|
||||
using (var mcdOnlineTabItem = ImRaii.TabItem("Online Data", flags))
|
||||
{
|
||||
if (mcdOnlineTabItem)
|
||||
{
|
||||
using var creationTabs = ImRaii.TabBar("TabsCreationLevel");
|
||||
using var id = ImRaii.PushId("mcdOnline");
|
||||
DrawMcdOnline();
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiTabItemFlags flags = ImGuiTabItemFlags.None;
|
||||
if (_openMcdOnlineOnNextRun)
|
||||
{
|
||||
flags = ImGuiTabItemFlags.SetSelected;
|
||||
_openMcdOnlineOnNextRun = false;
|
||||
}
|
||||
using (var mcdOnlineTabItem = ImRaii.TabItem("Online Data", flags))
|
||||
{
|
||||
if (mcdOnlineTabItem)
|
||||
{
|
||||
using var id = ImRaii.PushId("mcdOnline");
|
||||
DrawMcdOnline();
|
||||
}
|
||||
}
|
||||
|
||||
using (var mcdfTabItem = ImRaii.TabItem("MCDF Export"))
|
||||
{
|
||||
if (mcdfTabItem)
|
||||
{
|
||||
using var id = ImRaii.PushId("mcdfExport");
|
||||
DrawMcdfExport();
|
||||
}
|
||||
}
|
||||
|
||||
using (var mcdfShareTabItem = ImRaii.TabItem("Partage MCDF"))
|
||||
{
|
||||
if (mcdfShareTabItem)
|
||||
{
|
||||
using var id = ImRaii.PushId("mcdfShare");
|
||||
DrawMcdfShare();
|
||||
}
|
||||
}
|
||||
using (var mcdfTabItem = ImRaii.TabItem("MCDF Export"))
|
||||
{
|
||||
if (mcdfTabItem)
|
||||
{
|
||||
using var id = ImRaii.PushId("mcdfExport");
|
||||
DrawMcdfExport();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_isHandlingSelf)
|
||||
{
|
||||
UiSharedService.AttachToolTip("Cannot use creation tools while having Character Data applied to self.");
|
||||
@@ -506,18 +436,14 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
if (!_hasValidGposeTarget)
|
||||
{
|
||||
ImGuiHelpers.ScaledDummy(3);
|
||||
UiSharedService.DrawGroupedCenteredColorText("Applying data is only available in GPose with a valid selected GPose target.", UiSharedService.AccentColor, 350);
|
||||
UiSharedService.DrawGroupedCenteredColorText("Applying data is only available in GPose with a valid selected GPose target.", ImGuiColors.DalamudYellow, 350);
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10);
|
||||
|
||||
using (var applyTabColor = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.AccentColor))
|
||||
using (var applyTabHoverColor = ImRaii.PushColor(ImGuiCol.TabHovered, UiSharedService.AccentHoverColor))
|
||||
using (var applyTabActiveColor = ImRaii.PushColor(ImGuiCol.TabActive, UiSharedService.AccentActiveColor))
|
||||
{
|
||||
using var tabs = ImRaii.TabBar("Tabs");
|
||||
using var tabs = ImRaii.TabBar("Tabs");
|
||||
|
||||
using (var byFavoriteTabItem = ImRaii.TabItem("Favorites"))
|
||||
using (var byFavoriteTabItem = ImRaii.TabItem("Favorites"))
|
||||
{
|
||||
if (byFavoriteTabItem)
|
||||
{
|
||||
@@ -669,7 +595,7 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
|
||||
if (_configService.Current.FavoriteCodes.Count == 0)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("You have no favorites added. Add Favorites through the other tabs before you can use this tab.", UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped("You have no favorites added. Add Favorites through the other tabs before you can use this tab.", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -718,7 +644,7 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
ImGui.NewLine();
|
||||
if (!_charaDataManager.DownloadMetaInfoTask?.IsCompleted ?? false)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Downloading meta info. Please wait.", UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped("Downloading meta info. Please wait.", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
if ((_charaDataManager.DownloadMetaInfoTask?.IsCompleted ?? false) && !_charaDataManager.DownloadMetaInfoTask.Result.Success)
|
||||
{
|
||||
@@ -763,8 +689,7 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowCircleDown, "Download your Character Data"))
|
||||
{
|
||||
var cts = EnsureFreshCts(ref _disposalCts);
|
||||
_ = _charaDataManager.GetAllData(cts.Token);
|
||||
_ = _charaDataManager.GetAllData(_disposalCts.Token);
|
||||
}
|
||||
}
|
||||
if (_charaDataManager.DataGetTimeoutTask != null && !_charaDataManager.DataGetTimeoutTask.IsCompleted)
|
||||
@@ -925,16 +850,15 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
UiSharedService.ColorTextWrapped("Failure to read MCDF file. MCDF file is possibly corrupt. Re-export the MCDF file and try again.",
|
||||
UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped("Note: if this is your MCDF, try redrawing yourself, wait and re-export the file. " +
|
||||
"If you received it from someone else have them do the same.", UiSharedService.AccentColor);
|
||||
"If you received it from someone else have them do the same.", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Loading Character...", UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped("Loading Character...", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMcdfExport()
|
||||
@@ -959,7 +883,7 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
string defaultFileName = string.IsNullOrEmpty(_exportDescription)
|
||||
? "export.mcdf"
|
||||
: SanitizeFileName(_exportDescription, "export") + ".mcdf";
|
||||
: string.Join('_', $"{_exportDescription}.mcdf".Split(Path.GetInvalidFileNameChars()));
|
||||
_uiSharedService.FileDialogManager.SaveFileDialog("Export Character to file", ".mcdf", defaultFileName, ".mcdf", (success, path) =>
|
||||
{
|
||||
if (!success) return;
|
||||
@@ -972,418 +896,12 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
}, Directory.Exists(_configService.Current.LastSavedCharaDataLocation) ? _configService.Current.LastSavedCharaDataLocation : null);
|
||||
}
|
||||
UiSharedService.ColorTextWrapped("Note: For best results make sure you have everything you want to be shared as well as the correct character appearance" +
|
||||
" equipped and redraw your character before exporting.", UiSharedService.AccentColor);
|
||||
" equipped and redraw your character before exporting.", ImGuiColors.DalamudYellow);
|
||||
|
||||
ImGui.Unindent();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMcdfShare()
|
||||
{
|
||||
if (!_mcdfShareInitialized && !_mcdfShareManager.IsBusy)
|
||||
{
|
||||
_mcdfShareInitialized = true;
|
||||
_ = _mcdfShareManager.RefreshAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
if (_mcdfShareManager.IsBusy)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Traitement en cours...", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_mcdfShareManager.LastError))
|
||||
{
|
||||
UiSharedService.ColorTextWrapped(_mcdfShareManager.LastError!, ImGuiColors.DalamudRed);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(_mcdfShareManager.LastSuccess))
|
||||
{
|
||||
UiSharedService.ColorTextWrapped(_mcdfShareManager.LastSuccess!, ImGuiColors.HealerGreen);
|
||||
}
|
||||
|
||||
if (ImGui.Button("Actualiser les partages"))
|
||||
{
|
||||
_ = _mcdfShareManager.RefreshAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
_uiSharedService.BigText("Créer un partage MCDF");
|
||||
|
||||
ImGui.InputTextWithHint("##mcdfShareDescription", "Description", ref _mcdfShareDescription, 128);
|
||||
ImGui.InputInt("Expiration (jours, 0 = jamais)", ref _mcdfShareExpireDays);
|
||||
|
||||
DrawMcdfShareIndividualDropdown();
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth(220f);
|
||||
if (ImGui.InputTextWithHint("##mcdfShareUidInput", "UID ou vanity", ref _mcdfShareIndividualInput, 32))
|
||||
{
|
||||
_mcdfShareIndividualDropdownSelection = string.Empty;
|
||||
}
|
||||
ImGui.SameLine();
|
||||
var normalizedUid = NormalizeUidCandidate(_mcdfShareIndividualInput);
|
||||
using (ImRaii.Disabled(string.IsNullOrEmpty(normalizedUid)
|
||||
|| _mcdfShareAllowedIndividuals.Any(p => string.Equals(p, normalizedUid, StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
if (ImGui.SmallButton("Ajouter"))
|
||||
{
|
||||
_mcdfShareAllowedIndividuals.Add(normalizedUid);
|
||||
_mcdfShareIndividualInput = string.Empty;
|
||||
_mcdfShareIndividualDropdownSelection = string.Empty;
|
||||
}
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted("UID synchronisé à ajouter");
|
||||
_uiSharedService.DrawHelpText("Choisissez un pair synchronisé dans la liste ou saisissez un UID. Les utilisateurs listés pourront récupérer ce partage MCDF.");
|
||||
|
||||
foreach (var uid in _mcdfShareAllowedIndividuals.ToArray())
|
||||
{
|
||||
using (ImRaii.PushId("mcdfShareUid" + uid))
|
||||
{
|
||||
ImGui.BulletText(FormatPairLabel(uid));
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("Retirer"))
|
||||
{
|
||||
_mcdfShareAllowedIndividuals.Remove(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DrawMcdfShareSyncshellDropdown();
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth(220f);
|
||||
if (ImGui.InputTextWithHint("##mcdfShareSyncshellInput", "GID ou alias", ref _mcdfShareSyncshellInput, 32))
|
||||
{
|
||||
_mcdfShareSyncshellDropdownSelection = string.Empty;
|
||||
}
|
||||
ImGui.SameLine();
|
||||
var normalizedSyncshell = NormalizeSyncshellCandidate(_mcdfShareSyncshellInput);
|
||||
using (ImRaii.Disabled(string.IsNullOrEmpty(normalizedSyncshell)
|
||||
|| _mcdfShareAllowedSyncshells.Any(p => string.Equals(p, normalizedSyncshell, StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
if (ImGui.SmallButton("Ajouter"))
|
||||
{
|
||||
_mcdfShareAllowedSyncshells.Add(normalizedSyncshell);
|
||||
_mcdfShareSyncshellInput = string.Empty;
|
||||
_mcdfShareSyncshellDropdownSelection = string.Empty;
|
||||
}
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted("Syncshell à ajouter");
|
||||
_uiSharedService.DrawHelpText("Sélectionnez une syncshell synchronisée ou saisissez un identifiant. Les syncshells listées auront accès au partage.");
|
||||
|
||||
foreach (var shell in _mcdfShareAllowedSyncshells.ToArray())
|
||||
{
|
||||
using (ImRaii.PushId("mcdfShareShell" + shell))
|
||||
{
|
||||
ImGui.BulletText(FormatSyncshellLabel(shell));
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("Retirer"))
|
||||
{
|
||||
_mcdfShareAllowedSyncshells.Remove(shell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (ImRaii.Disabled(_mcdfShareManager.IsBusy))
|
||||
{
|
||||
if (ImGui.Button("Créer"))
|
||||
{
|
||||
DateTime? expiresAt = _mcdfShareExpireDays <= 0 ? null : DateTime.UtcNow.AddDays(_mcdfShareExpireDays);
|
||||
_ = _mcdfShareManager.CreateShareAsync(_mcdfShareDescription, _mcdfShareAllowedIndividuals.ToList(), _mcdfShareAllowedSyncshells.ToList(), expiresAt, CancellationToken.None);
|
||||
_mcdfShareDescription = string.Empty;
|
||||
_mcdfShareAllowedIndividuals.Clear();
|
||||
_mcdfShareAllowedSyncshells.Clear();
|
||||
_mcdfShareIndividualInput = string.Empty;
|
||||
_mcdfShareIndividualDropdownSelection = string.Empty;
|
||||
_mcdfShareSyncshellInput = string.Empty;
|
||||
_mcdfShareSyncshellDropdownSelection = string.Empty;
|
||||
_mcdfShareExpireDays = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
_uiSharedService.BigText("Mes partages : ");
|
||||
|
||||
if (_mcdfShareManager.OwnShares.Count == 0)
|
||||
{
|
||||
ImGui.TextDisabled("Aucun partage MCDF créé.");
|
||||
}
|
||||
else if (ImGui.BeginTable("mcdf-own-shares", 6, ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter))
|
||||
{
|
||||
ImGui.TableSetupColumn("Description");
|
||||
ImGui.TableSetupColumn("Créé le");
|
||||
ImGui.TableSetupColumn("Expire");
|
||||
ImGui.TableSetupColumn("Téléchargements");
|
||||
ImGui.TableSetupColumn("Accès");
|
||||
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 220f);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (var entry in _mcdfShareManager.OwnShares)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(string.IsNullOrEmpty(entry.Description) ? entry.Id.ToString() : entry.Description);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(entry.CreatedUtc.ToLocalTime().ToString("g"));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(entry.ExpiresAtUtc.HasValue ? entry.ExpiresAtUtc.Value.ToLocalTime().ToString("g") : "Jamais");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(entry.DownloadCount.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted($"UID : {entry.AllowedIndividuals.Count}, Syncshells : {entry.AllowedSyncshells.Count}");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
using (ImRaii.PushId("ownShare" + entry.Id))
|
||||
{
|
||||
if (ImGui.SmallButton("Appliquer en GPose"))
|
||||
{
|
||||
_ = _mcdfShareManager.ApplyShareAsync(entry.Id, CancellationToken.None);
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("Enregistrer"))
|
||||
{
|
||||
var baseName = SanitizeFileName(entry.Description, entry.Id.ToString());
|
||||
var defaultName = baseName + ".mcdf";
|
||||
_fileDialogManager.SaveFileDialog("Enregistrer le partage MCDF", ".mcdf", defaultName, ".mcdf", async (success, path) =>
|
||||
{
|
||||
if (!success || string.IsNullOrEmpty(path)) return;
|
||||
await _mcdfShareManager.ExportShareAsync(entry.Id, path, CancellationToken.None).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("Supprimer"))
|
||||
{
|
||||
_ = _mcdfShareManager.DeleteShareAsync(entry.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
_uiSharedService.BigText("Partagés avec moi : ");
|
||||
|
||||
if (_mcdfShareManager.SharedShares.Count == 0)
|
||||
{
|
||||
ImGui.TextDisabled("Aucun partage MCDF reçu.");
|
||||
}
|
||||
else if (ImGui.BeginTable("mcdf-shared-shares", 5, ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter))
|
||||
{
|
||||
ImGui.TableSetupColumn("Description");
|
||||
ImGui.TableSetupColumn("Propriétaire");
|
||||
ImGui.TableSetupColumn("Expire");
|
||||
ImGui.TableSetupColumn("Téléchargements");
|
||||
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 180f);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (var entry in _mcdfShareManager.SharedShares)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(string.IsNullOrEmpty(entry.Description) ? entry.Id.ToString() : entry.Description);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(string.IsNullOrEmpty(entry.OwnerAlias) ? entry.OwnerUid : entry.OwnerAlias);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(entry.ExpiresAtUtc.HasValue ? entry.ExpiresAtUtc.Value.ToLocalTime().ToString("g") : "Jamais");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(entry.DownloadCount.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
using (ImRaii.PushId("sharedShare" + entry.Id))
|
||||
{
|
||||
if (ImGui.SmallButton("Appliquer"))
|
||||
{
|
||||
_ = _mcdfShareManager.ApplyShareAsync(entry.Id, CancellationToken.None);
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("Enregistrer"))
|
||||
{
|
||||
var baseName = SanitizeFileName(entry.Description, entry.Id.ToString());
|
||||
var defaultName = baseName + ".mcdf";
|
||||
_fileDialogManager.SaveFileDialog("Enregistrer le partage MCDF", ".mcdf", defaultName, ".mcdf", async (success, path) =>
|
||||
{
|
||||
if (!success || string.IsNullOrEmpty(path)) return;
|
||||
await _mcdfShareManager.ExportShareAsync(entry.Id, path, CancellationToken.None).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMcdfShareIndividualDropdown()
|
||||
{
|
||||
ImGui.SetNextItemWidth(220f);
|
||||
var previewSource = string.IsNullOrEmpty(_mcdfShareIndividualDropdownSelection)
|
||||
? _mcdfShareIndividualInput
|
||||
: _mcdfShareIndividualDropdownSelection;
|
||||
var previewLabel = string.IsNullOrEmpty(previewSource)
|
||||
? "Sélectionner un pair synchronisé..."
|
||||
: FormatPairLabel(previewSource);
|
||||
|
||||
using var combo = ImRaii.Combo("##mcdfShareUidDropdown", previewLabel, ImGuiComboFlags.None);
|
||||
if (!combo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pair in _pairManager.DirectPairs
|
||||
.OrderBy(p => p.GetNoteOrName() ?? p.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var normalized = pair.UserData.UID;
|
||||
var display = FormatPairLabel(normalized);
|
||||
bool selected = string.Equals(normalized, _mcdfShareIndividualDropdownSelection, StringComparison.OrdinalIgnoreCase);
|
||||
if (ImGui.Selectable(display, selected))
|
||||
{
|
||||
_mcdfShareIndividualDropdownSelection = normalized;
|
||||
_mcdfShareIndividualInput = normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMcdfShareSyncshellDropdown()
|
||||
{
|
||||
ImGui.SetNextItemWidth(220f);
|
||||
var previewSource = string.IsNullOrEmpty(_mcdfShareSyncshellDropdownSelection)
|
||||
? _mcdfShareSyncshellInput
|
||||
: _mcdfShareSyncshellDropdownSelection;
|
||||
var previewLabel = string.IsNullOrEmpty(previewSource)
|
||||
? "Sélectionner une syncshell..."
|
||||
: FormatSyncshellLabel(previewSource);
|
||||
|
||||
using var combo = ImRaii.Combo("##mcdfShareSyncshellDropdown", previewLabel, ImGuiComboFlags.None);
|
||||
if (!combo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var group in _pairManager.Groups.Values
|
||||
.OrderBy(g => _serverConfigurationManager.GetNoteForGid(g.GID) ?? g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var gid = group.GID;
|
||||
var display = FormatSyncshellLabel(gid);
|
||||
bool selected = string.Equals(gid, _mcdfShareSyncshellDropdownSelection, StringComparison.OrdinalIgnoreCase);
|
||||
if (ImGui.Selectable(display, selected))
|
||||
{
|
||||
_mcdfShareSyncshellDropdownSelection = gid;
|
||||
_mcdfShareSyncshellInput = gid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string NormalizeUidCandidate(string candidate)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(candidate))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var trimmed = candidate.Trim();
|
||||
|
||||
foreach (var pair in _pairManager.DirectPairs)
|
||||
{
|
||||
var alias = pair.UserData.Alias;
|
||||
var aliasOrUid = pair.UserData.AliasOrUID;
|
||||
var note = pair.GetNoteOrName();
|
||||
|
||||
if (string.Equals(pair.UserData.UID, trimmed, StringComparison.OrdinalIgnoreCase)
|
||||
|| (!string.IsNullOrEmpty(alias) && string.Equals(alias, trimmed, StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(aliasOrUid, trimmed, StringComparison.OrdinalIgnoreCase)
|
||||
|| (!string.IsNullOrEmpty(note) && string.Equals(note, trimmed, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return pair.UserData.UID;
|
||||
}
|
||||
}
|
||||
|
||||
return trimmed.ToUpperInvariant();
|
||||
}
|
||||
|
||||
private string NormalizeSyncshellCandidate(string candidate)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(candidate))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var trimmed = candidate.Trim();
|
||||
|
||||
foreach (var group in _pairManager.Groups.Values)
|
||||
{
|
||||
var alias = group.GroupAlias;
|
||||
var aliasOrGid = group.GroupAliasOrGID;
|
||||
var note = _serverConfigurationManager.GetNoteForGid(group.GID);
|
||||
|
||||
if (string.Equals(group.GID, trimmed, StringComparison.OrdinalIgnoreCase)
|
||||
|| (!string.IsNullOrEmpty(alias) && string.Equals(alias, trimmed, StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(aliasOrGid, trimmed, StringComparison.OrdinalIgnoreCase)
|
||||
|| (!string.IsNullOrEmpty(note) && string.Equals(note, trimmed, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return group.GID;
|
||||
}
|
||||
}
|
||||
|
||||
return trimmed.ToUpperInvariant();
|
||||
}
|
||||
|
||||
private string FormatPairLabel(string candidate)
|
||||
{
|
||||
if (string.IsNullOrEmpty(candidate))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
foreach (var pair in _pairManager.DirectPairs)
|
||||
{
|
||||
var alias = pair.UserData.Alias;
|
||||
var aliasOrUid = pair.UserData.AliasOrUID;
|
||||
var note = pair.GetNoteOrName();
|
||||
|
||||
if (string.Equals(pair.UserData.UID, candidate, StringComparison.OrdinalIgnoreCase)
|
||||
|| (!string.IsNullOrEmpty(alias) && string.Equals(alias, candidate, StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(aliasOrUid, candidate, StringComparison.OrdinalIgnoreCase)
|
||||
|| (!string.IsNullOrEmpty(note) && string.Equals(note, candidate, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return string.IsNullOrEmpty(note) ? aliasOrUid : $"{note} ({aliasOrUid})";
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private string FormatSyncshellLabel(string candidate)
|
||||
{
|
||||
if (string.IsNullOrEmpty(candidate))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
foreach (var group in _pairManager.Groups.Values)
|
||||
{
|
||||
var alias = group.GroupAlias;
|
||||
var aliasOrGid = group.GroupAliasOrGID;
|
||||
var note = _serverConfigurationManager.GetNoteForGid(group.GID);
|
||||
|
||||
if (string.Equals(group.GID, candidate, StringComparison.OrdinalIgnoreCase)
|
||||
|| (!string.IsNullOrEmpty(alias) && string.Equals(alias, candidate, StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(aliasOrGid, candidate, StringComparison.OrdinalIgnoreCase)
|
||||
|| (!string.IsNullOrEmpty(note) && string.Equals(note, candidate, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return string.IsNullOrEmpty(note) ? aliasOrGid : $"{note} ({aliasOrGid})";
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private void DrawMetaInfoData(string selectedGposeActor, bool hasValidGposeTarget, CharaDataMetaInfoExtendedDto data, bool canOpen = false)
|
||||
{
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
@@ -1586,26 +1104,4 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
|
||||
drawAction();
|
||||
if (_disableUI) ImGui.BeginDisabled();
|
||||
}
|
||||
|
||||
private static CancellationTokenSource EnsureFreshCts(ref CancellationTokenSource? cts)
|
||||
{
|
||||
CancelAndDispose(ref cts);
|
||||
cts = new CancellationTokenSource();
|
||||
return cts;
|
||||
}
|
||||
|
||||
private static void CancelAndDispose(ref CancellationTokenSource? cts)
|
||||
{
|
||||
if (cts == null) return;
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
|
||||
cts.Dispose();
|
||||
cts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -45,43 +45,6 @@ public class DrawGroupPair : DrawPairBase
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
}
|
||||
|
||||
protected override float GetRightSideExtraWidth()
|
||||
{
|
||||
float width = 0f;
|
||||
float spacing = ImGui.GetStyle().ItemSpacing.X;
|
||||
|
||||
var soundsDisabled = _fullInfoDto.GroupUserPermissions.IsDisableSounds();
|
||||
var animDisabled = _fullInfoDto.GroupUserPermissions.IsDisableAnimations();
|
||||
var vfxDisabled = _fullInfoDto.GroupUserPermissions.IsDisableVFX();
|
||||
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 individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false);
|
||||
|
||||
bool showInfo = individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled || animDisabled || soundsDisabled || vfxDisabled;
|
||||
bool showShared = _charaDataManager.SharedWithYouData.TryGetValue(_pair.UserData, out var sharedData);
|
||||
bool showPlus = _pair.UserPair == null && _pair.IsOnline;
|
||||
|
||||
if (showShared)
|
||||
{
|
||||
width += _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Running).X + spacing;
|
||||
}
|
||||
|
||||
if (showInfo)
|
||||
{
|
||||
var icon = (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled)
|
||||
? FontAwesomeIcon.ExclamationTriangle
|
||||
: FontAwesomeIcon.InfoCircle;
|
||||
width += UiSharedService.GetIconSize(icon).X + spacing;
|
||||
}
|
||||
|
||||
if (showPlus)
|
||||
{
|
||||
width += _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Plus).X + spacing;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
protected override void DrawLeftSide(float textPosY, float originalY)
|
||||
{
|
||||
var entryUID = _pair.UserData.AliasOrUID;
|
||||
@@ -201,7 +164,7 @@ public class DrawGroupPair : DrawPairBase
|
||||
var individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false);
|
||||
|
||||
bool showShared = _charaDataManager.SharedWithYouData.TryGetValue(_pair.UserData, out var sharedData);
|
||||
bool showInfo = (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled || animDisabled || soundsDisabled || vfxDisabled);
|
||||
bool showInfo = (individualAnimDisabled || individualSoundsDisabled || animDisabled || soundsDisabled);
|
||||
bool showPlus = _pair.UserPair == null && _pair.IsOnline;
|
||||
bool showBars = (userIsOwner || (userIsModerator && !entryIsMod && !entryIsOwner)) || !_pair.IsPaused;
|
||||
bool showPause = true;
|
||||
@@ -210,47 +173,23 @@ public class DrawGroupPair : DrawPairBase
|
||||
var permIcon = (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled) ? FontAwesomeIcon.ExclamationTriangle
|
||||
: ((soundsDisabled || animDisabled || vfxDisabled) ? FontAwesomeIcon.InfoCircle : FontAwesomeIcon.None);
|
||||
var runningIconWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Running).X;
|
||||
var infoIconWidth = showInfo && permIcon != FontAwesomeIcon.None ? UiSharedService.GetIconSize(permIcon).X : 0f;
|
||||
var infoIconWidth = UiSharedService.GetIconSize(permIcon).X;
|
||||
var plusButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Plus).X;
|
||||
var pauseIcon = _fullInfoDto.GroupUserPermissions.IsPaused() ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
|
||||
var pauseButtonWidth = _uiSharedService.GetIconButtonSize(pauseIcon).X;
|
||||
var barButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars).X;
|
||||
|
||||
float totalWidth = 0f;
|
||||
void Accumulate(bool condition, float width)
|
||||
{
|
||||
if (!condition || width <= 0f) return;
|
||||
if (totalWidth > 0f) totalWidth += spacing;
|
||||
totalWidth += width;
|
||||
}
|
||||
var pos = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() + spacing
|
||||
- (showShared ? (runningIconWidth + spacing) : 0)
|
||||
- (showInfo ? (infoIconWidth + spacing) : 0)
|
||||
- (showPlus ? (plusButtonWidth + spacing) : 0)
|
||||
- (showPause ? (pauseButtonWidth + spacing) : 0)
|
||||
- (showBars ? (barButtonWidth + spacing) : 0);
|
||||
|
||||
Accumulate(showShared, runningIconWidth);
|
||||
Accumulate(showInfo && infoIconWidth > 0f, infoIconWidth);
|
||||
Accumulate(showPlus, plusButtonWidth);
|
||||
Accumulate(showPause, pauseButtonWidth);
|
||||
if (showBars)
|
||||
{
|
||||
if (totalWidth > 0f) totalWidth += spacing;
|
||||
totalWidth += barButtonWidth;
|
||||
}
|
||||
if (showPause && showBars)
|
||||
{
|
||||
totalWidth -= spacing * 0.5f;
|
||||
if (totalWidth < 0f) totalWidth = 0f;
|
||||
}
|
||||
|
||||
float cardPaddingX = UiSharedService.GetCardContentPaddingX();
|
||||
float rightMargin = cardPaddingX + 6f * ImGuiHelpers.GlobalScale;
|
||||
float baseX = MathF.Max(ImGui.GetCursorPosX(),
|
||||
ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - rightMargin - totalWidth);
|
||||
float currentX = baseX;
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPosX(baseX);
|
||||
ImGui.SameLine(pos);
|
||||
|
||||
if (showShared)
|
||||
{
|
||||
ImGui.SetCursorPosY(textPosY);
|
||||
_uiSharedService.IconText(FontAwesomeIcon.Running);
|
||||
|
||||
UiSharedService.AttachToolTip($"This user has shared {sharedData!.Count} Character Data Sets with you." + UiSharedService.TooltipSeparator
|
||||
@@ -260,99 +199,95 @@ public class DrawGroupPair : DrawPairBase
|
||||
{
|
||||
_mediator.Publish(new OpenCharaDataHubWithFilterMessage(_pair.UserData));
|
||||
}
|
||||
currentX += runningIconWidth + spacing;
|
||||
ImGui.SetCursorPosX(currentX);
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
if (showInfo && infoIconWidth > 0f)
|
||||
if (individualAnimDisabled || individualSoundsDisabled)
|
||||
{
|
||||
ImGui.SetCursorPosY(textPosY);
|
||||
if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled)
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
|
||||
_uiSharedService.IconText(permIcon);
|
||||
ImGui.PopStyleColor();
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
|
||||
_uiSharedService.IconText(permIcon);
|
||||
ImGui.PopStyleColor();
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.BeginTooltip();
|
||||
|
||||
ImGui.TextUnformatted("Individual User permissions");
|
||||
|
||||
if (individualSoundsDisabled)
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
|
||||
ImGui.TextUnformatted("Individual User permissions");
|
||||
|
||||
if (individualSoundsDisabled)
|
||||
{
|
||||
var userSoundsText = "Sound sync disabled with " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.VolumeMute);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userSoundsText);
|
||||
ImGui.NewLine();
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableSounds() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableSounds() ? "Disabled" : "Enabled"));
|
||||
}
|
||||
|
||||
if (individualAnimDisabled)
|
||||
{
|
||||
var userAnimText = "Animation sync disabled with " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.WindowClose);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userAnimText);
|
||||
ImGui.NewLine();
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableAnimations() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableAnimations() ? "Disabled" : "Enabled"));
|
||||
}
|
||||
|
||||
if (individualVFXDisabled)
|
||||
{
|
||||
var userVFXText = "VFX sync disabled with " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.TimesCircle);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userVFXText);
|
||||
ImGui.NewLine();
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableVFX() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableVFX() ? "Disabled" : "Enabled"));
|
||||
}
|
||||
|
||||
ImGui.EndTooltip();
|
||||
var userSoundsText = "Sound sync disabled with " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.VolumeMute);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userSoundsText);
|
||||
ImGui.NewLine();
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableSounds() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableSounds() ? "Disabled" : "Enabled"));
|
||||
}
|
||||
|
||||
if (individualAnimDisabled)
|
||||
{
|
||||
var userAnimText = "Animation sync disabled with " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.WindowClose);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userAnimText);
|
||||
ImGui.NewLine();
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableAnimations() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableAnimations() ? "Disabled" : "Enabled"));
|
||||
}
|
||||
|
||||
if (individualVFXDisabled)
|
||||
{
|
||||
var userVFXText = "VFX sync disabled with " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.TimesCircle);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userVFXText);
|
||||
ImGui.NewLine();
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted("You: " + (_pair.UserPair!.OwnPermissions.IsDisableVFX() ? "Disabled" : "Enabled") + ", They: " + (_pair.UserPair!.OtherPermissions.IsDisableVFX() ? "Disabled" : "Enabled"));
|
||||
}
|
||||
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
else
|
||||
ImGui.SameLine();
|
||||
}
|
||||
else if ((animDisabled || soundsDisabled))
|
||||
{
|
||||
ImGui.SetCursorPosY(textPosY);
|
||||
_uiSharedService.IconText(permIcon);
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
_uiSharedService.IconText(permIcon);
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.BeginTooltip();
|
||||
|
||||
ImGui.TextUnformatted("Syncshell User permissions");
|
||||
|
||||
if (soundsDisabled)
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
|
||||
ImGui.TextUnformatted("Syncshell User permissions");
|
||||
|
||||
if (soundsDisabled)
|
||||
{
|
||||
var userSoundsText = "Sound sync disabled by " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.VolumeMute);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userSoundsText);
|
||||
}
|
||||
|
||||
if (animDisabled)
|
||||
{
|
||||
var userAnimText = "Animation sync disabled by " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.WindowClose);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userAnimText);
|
||||
}
|
||||
|
||||
if (vfxDisabled)
|
||||
{
|
||||
var userVFXText = "VFX sync disabled by " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.TimesCircle);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userVFXText);
|
||||
}
|
||||
|
||||
ImGui.EndTooltip();
|
||||
var userSoundsText = "Sound sync disabled by " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.VolumeMute);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userSoundsText);
|
||||
}
|
||||
}
|
||||
|
||||
currentX += infoIconWidth + spacing;
|
||||
ImGui.SetCursorPosX(currentX);
|
||||
if (animDisabled)
|
||||
{
|
||||
var userAnimText = "Animation sync disabled by " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.WindowClose);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userAnimText);
|
||||
}
|
||||
|
||||
if (vfxDisabled)
|
||||
{
|
||||
var userVFXText = "VFX sync disabled by " + _pair.UserData.AliasOrUID;
|
||||
_uiSharedService.IconText(FontAwesomeIcon.TimesCircle);
|
||||
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TextUnformatted(userVFXText);
|
||||
}
|
||||
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
if (showPlus)
|
||||
@@ -368,14 +303,13 @@ public class DrawGroupPair : DrawPairBase
|
||||
}
|
||||
}
|
||||
UiSharedService.AttachToolTip(AppendSeenInfo("Send pairing invite to " + entryUID));
|
||||
currentX += plusButtonWidth + spacing;
|
||||
ImGui.SetCursorPosX(currentX);
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
if (showPause)
|
||||
{
|
||||
float gapToBars = showBars ? spacing * 0.5f : spacing;
|
||||
ImGui.SetCursorPosY(originalY);
|
||||
|
||||
if (_uiSharedService.IconButton(pauseIcon))
|
||||
{
|
||||
var newPermissions = _fullInfoDto.GroupUserPermissions ^ GroupUserPermissions.Paused;
|
||||
@@ -384,20 +318,19 @@ public class DrawGroupPair : DrawPairBase
|
||||
}
|
||||
|
||||
UiSharedService.AttachToolTip(AppendSeenInfo((_fullInfoDto.GroupUserPermissions.IsPaused() ? "Resume" : "Pause") + " syncing with " + entryUID));
|
||||
currentX += pauseButtonWidth + gapToBars;
|
||||
ImGui.SetCursorPosX(currentX);
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
if (showBars)
|
||||
{
|
||||
ImGui.SetCursorPosY(originalY);
|
||||
|
||||
if (_uiSharedService.IconButton(FontAwesomeIcon.Bars))
|
||||
{
|
||||
ImGui.OpenPopup("Syncshell Flyout Menu");
|
||||
ImGui.OpenPopup("Popup");
|
||||
}
|
||||
currentX += barButtonWidth;
|
||||
ImGui.SetCursorPosX(currentX);
|
||||
}
|
||||
|
||||
if (ImGui.BeginPopup("Popup"))
|
||||
{
|
||||
if ((userIsModerator || userIsOwner) && !(entryIsMod || entryIsOwner))
|
||||
@@ -483,7 +416,7 @@ public class DrawGroupPair : DrawPairBase
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
return baseX - spacing;
|
||||
return pos - spacing;
|
||||
}
|
||||
|
||||
private string AppendSeenInfo(string tooltip)
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.UI.Handlers;
|
||||
using MareSynchronos.WebAPI;
|
||||
@@ -31,77 +28,38 @@ public abstract class DrawPairBase
|
||||
|
||||
public void DrawPairedClient()
|
||||
{
|
||||
var style = ImGui.GetStyle();
|
||||
var padding = style.FramePadding;
|
||||
var spacing = style.ItemSpacing;
|
||||
var rowStartCursor = ImGui.GetCursorPos();
|
||||
var rowStartScreen = ImGui.GetCursorScreenPos();
|
||||
var originalY = ImGui.GetCursorPosY();
|
||||
var pauseIconSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Play);
|
||||
var textSize = ImGui.CalcTextSize(_pair.UserData.AliasOrUID);
|
||||
|
||||
var pauseButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Pause);
|
||||
var playButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Play);
|
||||
var menuButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars);
|
||||
var startPos = ImGui.GetCursorStartPos();
|
||||
|
||||
float pauseClusterWidth = Math.Max(pauseButtonSize.X, playButtonSize.X);
|
||||
float pauseClusterHeight = Math.Max(pauseButtonSize.Y, playButtonSize.Y);
|
||||
float reservedSpacing = style.ItemSpacing.X * 2.4f;
|
||||
float rightButtonWidth =
|
||||
menuButtonSize.X +
|
||||
pauseClusterWidth +
|
||||
reservedSpacing +
|
||||
GetRightSideExtraWidth();
|
||||
var framePadding = ImGui.GetStyle().FramePadding;
|
||||
var lineHeight = textSize.Y + framePadding.Y * 2;
|
||||
|
||||
float availableWidth = Math.Max(ImGui.GetContentRegionAvail().X - rightButtonWidth, 1f);
|
||||
float textHeight = ImGui.GetFontSize();
|
||||
var presenceIconSize = UiSharedService.GetIconSize(FontAwesomeIcon.Moon);
|
||||
float iconHeight = presenceIconSize.Y;
|
||||
float contentHeight = Math.Max(textHeight, Math.Max(iconHeight, pauseClusterHeight));
|
||||
float rowHeight = contentHeight + padding.Y * 2f;
|
||||
float totalHeight = rowHeight + spacing.Y;
|
||||
var off = startPos.Y;
|
||||
var height = UiSharedService.GetWindowContentRegionHeight();
|
||||
|
||||
var origin = ImGui.GetCursorStartPos();
|
||||
var top = origin.Y + rowStartCursor.Y;
|
||||
var bottom = top + totalHeight;
|
||||
var visibleHeight = UiSharedService.GetWindowContentRegionHeight();
|
||||
if (bottom < 0 || top > visibleHeight)
|
||||
if ((originalY + off) < -lineHeight || (originalY + off) > height)
|
||||
{
|
||||
ImGui.SetCursorPos(new Vector2(rowStartCursor.X, rowStartCursor.Y + totalHeight));
|
||||
ImGui.Dummy(new System.Numerics.Vector2(0f, lineHeight));
|
||||
return;
|
||||
}
|
||||
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
var backgroundColor = new Vector4(0.10f, 0.10f, 0.14f, 0.95f);
|
||||
var borderColor = new Vector4(0f, 0f, 0f, 0.9f);
|
||||
float rounding = Math.Max(style.FrameRounding, 7f * ImGuiHelpers.GlobalScale);
|
||||
|
||||
var panelMin = rowStartScreen + new Vector2(0f, spacing.Y * 0.15f);
|
||||
var panelMax = panelMin + new Vector2(availableWidth, rowHeight - spacing.Y * 0.3f);
|
||||
drawList.AddRectFilled(panelMin, panelMax, ImGui.ColorConvertFloat4ToU32(backgroundColor), rounding);
|
||||
drawList.AddRect(panelMin, panelMax, ImGui.ColorConvertFloat4ToU32(borderColor), rounding);
|
||||
|
||||
float iconTop = rowStartCursor.Y + (rowHeight - iconHeight) / 2f;
|
||||
float textTop = rowStartCursor.Y + (rowHeight - textHeight) / 2f - padding.Y * 0.6f;
|
||||
float buttonTop = rowStartCursor.Y + (rowHeight - pauseClusterHeight) / 2f;
|
||||
|
||||
ImGui.SetCursorPos(new Vector2(rowStartCursor.X + padding.X, iconTop));
|
||||
DrawLeftSide(iconTop, iconTop);
|
||||
var textPosY = originalY + pauseIconSize.Y / 2 - textSize.Y / 2;
|
||||
DrawLeftSide(textPosY, originalY);
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPosY(textTop);
|
||||
var posX = ImGui.GetCursorPosX();
|
||||
var rightSide = DrawRightSide(buttonTop, buttonTop);
|
||||
DrawName(textTop + padding.Y * 0.15f, posX, rightSide);
|
||||
|
||||
ImGui.SetCursorPos(new Vector2(rowStartCursor.X, rowStartCursor.Y + totalHeight));
|
||||
ImGui.SetCursorPosX(rowStartCursor.X);
|
||||
var rightSide = DrawRightSide(textPosY, originalY);
|
||||
DrawName(originalY, posX, rightSide);
|
||||
}
|
||||
|
||||
protected abstract void DrawLeftSide(float textPosY, float originalY);
|
||||
|
||||
protected abstract float DrawRightSide(float textPosY, float originalY);
|
||||
|
||||
protected virtual float GetRightSideExtraWidth() => 0f;
|
||||
|
||||
private void DrawName(float originalY, float leftSide, float rightSide)
|
||||
{
|
||||
_displayHandler.DrawPairText(_id, _pair, leftSide, originalY, () => rightSide - leftSide);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,28 +41,6 @@ public class DrawUserPair : DrawPairBase
|
||||
public bool IsVisible => _pair.IsVisible;
|
||||
public UserPairDto UserPair => _pair.UserPair!;
|
||||
|
||||
protected override float GetRightSideExtraWidth()
|
||||
{
|
||||
float width = 0f;
|
||||
var spacingX = ImGui.GetStyle().ItemSpacing.X;
|
||||
|
||||
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 individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false);
|
||||
|
||||
if (individualSoundsDisabled || individualAnimDisabled || individualVFXDisabled)
|
||||
{
|
||||
width += _uiSharedService.GetIconButtonSize(FontAwesomeIcon.ExclamationTriangle).X + spacingX * 0.5f;
|
||||
}
|
||||
|
||||
if (_charaDataManager.SharedWithYouData.TryGetValue(_pair.UserData, out var sharedData))
|
||||
{
|
||||
width += _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Running).X + spacingX * 0.5f;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
protected override void DrawLeftSide(float textPosY, float originalY)
|
||||
{
|
||||
var online = _pair.IsOnline;
|
||||
@@ -132,8 +110,7 @@ public class DrawUserPair : DrawPairBase
|
||||
var barButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars);
|
||||
var entryUID = _pair.UserData.AliasOrUID;
|
||||
var spacingX = ImGui.GetStyle().ItemSpacing.X;
|
||||
var edgePadding = UiSharedService.GetCardContentPaddingX() + 6f * ImGuiHelpers.GlobalScale;
|
||||
var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - edgePadding;
|
||||
var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth();
|
||||
var rightSidePos = windowEndX - barButtonSize.X;
|
||||
|
||||
// Flyout Menu
|
||||
|
||||
@@ -30,6 +30,7 @@ internal sealed class GroupPanel
|
||||
private readonly CompactUi _mainUi;
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly ChatService _chatService;
|
||||
private readonly MareConfigService _mareConfig;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly CharaDataManager _charaDataManager;
|
||||
private readonly AutoDetectRequestService _autoDetectRequestService;
|
||||
@@ -74,7 +75,7 @@ internal sealed class GroupPanel
|
||||
private string _syncShellToJoin = string.Empty;
|
||||
|
||||
public GroupPanel(CompactUi mainUi, UiSharedService uiShared, PairManager pairManager, ChatService chatServivce,
|
||||
UidDisplayHandler uidDisplayHandler, ServerConfigurationManager serverConfigurationManager,
|
||||
UidDisplayHandler uidDisplayHandler, MareConfigService mareConfig, ServerConfigurationManager serverConfigurationManager,
|
||||
CharaDataManager charaDataManager, AutoDetectRequestService autoDetectRequestService)
|
||||
{
|
||||
_mainUi = mainUi;
|
||||
@@ -82,6 +83,7 @@ internal sealed class GroupPanel
|
||||
_pairManager = pairManager;
|
||||
_chatService = chatServivce;
|
||||
_uidDisplayHandler = uidDisplayHandler;
|
||||
_mareConfig = mareConfig;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
_charaDataManager = charaDataManager;
|
||||
_autoDetectRequestService = autoDetectRequestService;
|
||||
@@ -91,7 +93,6 @@ internal sealed class GroupPanel
|
||||
|
||||
public void DrawSyncshells()
|
||||
{
|
||||
using var fontScale = UiSharedService.PushFontScale(UiSharedService.ContentFontScale);
|
||||
using (ImRaii.PushId("addsyncshell")) DrawAddSyncshell();
|
||||
using (ImRaii.PushId("syncshelllist")) DrawSyncshellList();
|
||||
_mainUi.TransferPartHeight = ImGui.GetCursorPosY();
|
||||
@@ -312,20 +313,19 @@ internal sealed class GroupPanel
|
||||
int shellNumber = _serverConfigurationManager.GetShellNumberForGid(groupDto.GID);
|
||||
|
||||
var name = groupDto.Group.Alias ?? groupDto.GID;
|
||||
if (!_expandedGroupState.ContainsKey(groupDto.GID))
|
||||
if (!_expandedGroupState.TryGetValue(groupDto.GID, out bool isExpanded))
|
||||
{
|
||||
_expandedGroupState[groupDto.GID] = false;
|
||||
isExpanded = false;
|
||||
_expandedGroupState.Add(groupDto.GID, isExpanded);
|
||||
}
|
||||
|
||||
UiSharedService.DrawCard($"syncshell-card-{groupDto.GID}", () =>
|
||||
var icon = isExpanded ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight;
|
||||
_uiShared.IconText(icon);
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||
{
|
||||
bool expandedState = _expandedGroupState[groupDto.GID];
|
||||
UiSharedService.DrawArrowToggle(ref expandedState, $"##syncshell-toggle-{groupDto.GID}");
|
||||
if (expandedState != _expandedGroupState[groupDto.GID])
|
||||
{
|
||||
_expandedGroupState[groupDto.GID] = expandedState;
|
||||
_expandedGroupState[groupDto.GID] = !_expandedGroupState[groupDto.GID];
|
||||
}
|
||||
ImGui.SameLine(0f, 6f * ImGuiHelpers.GlobalScale);
|
||||
ImGui.SameLine();
|
||||
|
||||
var textIsGid = true;
|
||||
string groupName = groupDto.GroupAliasOrGID;
|
||||
@@ -547,7 +547,7 @@ internal sealed class GroupPanel
|
||||
bool hideOfflineUsers = pairsInGroup.Count > 1000;
|
||||
|
||||
ImGui.Indent(20);
|
||||
if (expandedState)
|
||||
if (_expandedGroupState[groupDto.GID])
|
||||
{
|
||||
var sortedPairs = pairsInGroup
|
||||
.OrderByDescending(u => string.Equals(u.UserData.UID, groupDto.OwnerUID, StringComparison.Ordinal))
|
||||
@@ -614,9 +614,6 @@ internal sealed class GroupPanel
|
||||
ImGui.Separator();
|
||||
}
|
||||
ImGui.Unindent(20);
|
||||
}, stretchWidth: true);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(4f);
|
||||
}
|
||||
|
||||
private void DrawSyncShellButtons(GroupFullInfoDto groupDto, List<Pair> groupPairs)
|
||||
@@ -647,8 +644,7 @@ internal sealed class GroupPanel
|
||||
var isOwner = string.Equals(groupDto.OwnerUID, ApiController.UID, StringComparison.Ordinal);
|
||||
|
||||
var spacingX = ImGui.GetStyle().ItemSpacing.X;
|
||||
var cardPaddingX = UiSharedService.GetCardContentPaddingX();
|
||||
var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - cardPaddingX - 6f * ImGuiHelpers.GlobalScale;
|
||||
var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth();
|
||||
var pauseIcon = groupDto.GroupUserPermissions.IsPaused() ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
|
||||
var pauseIconSize = _uiShared.GetIconButtonSize(pauseIcon);
|
||||
|
||||
@@ -832,22 +828,9 @@ internal sealed class GroupPanel
|
||||
|
||||
private void DrawSyncshellList()
|
||||
{
|
||||
float availableHeight = ImGui.GetContentRegionAvail().Y;
|
||||
float ySize;
|
||||
if (_mainUi.TransferPartHeight <= 0)
|
||||
{
|
||||
float reserve = ImGui.GetFrameHeightWithSpacing() * 2f;
|
||||
ySize = availableHeight - reserve;
|
||||
if (ySize <= 0)
|
||||
{
|
||||
ySize = System.Math.Max(availableHeight, 1f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ySize = (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _mainUi.TransferPartHeight - ImGui.GetCursorPosY();
|
||||
}
|
||||
|
||||
var ySize = _mainUi.TransferPartHeight == 0
|
||||
? 1
|
||||
: (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _mainUi.TransferPartHeight - ImGui.GetCursorPosY();
|
||||
ImGui.BeginChild("list", new Vector2(_mainUi.WindowContentWidth, ySize), border: false);
|
||||
foreach (var entry in _pairManager.GroupPairs.OrderBy(g => g.Key.Group.AliasOrGID, StringComparer.OrdinalIgnoreCase).ToList())
|
||||
{
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using MareSynchronos.API.Data.Extensions;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.UI.Handlers;
|
||||
using MareSynchronos.WebAPI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MareSynchronos.UI.Components;
|
||||
|
||||
@@ -32,7 +28,7 @@ public class PairGroupsUi
|
||||
_uiSharedService = uiSharedService;
|
||||
}
|
||||
|
||||
public void Draw<T>(List<T> visibleUsers, List<T> onlineUsers, List<T> offlineUsers, Action? drawVisibleExtras = null) where T : DrawPairBase
|
||||
public void Draw<T>(List<T> visibleUsers, List<T> onlineUsers, List<T> offlineUsers) where T : DrawPairBase
|
||||
{
|
||||
// Only render those tags that actually have pairs in them, otherwise
|
||||
// we can end up with a bunch of useless pair groups
|
||||
@@ -40,7 +36,7 @@ public class PairGroupsUi
|
||||
var allUsers = onlineUsers.Concat(offlineUsers).ToList();
|
||||
if (typeof(T) == typeof(DrawUserPair))
|
||||
{
|
||||
DrawUserPairs(tagsWithPairsInThem, allUsers.Cast<DrawUserPair>().ToList(), visibleUsers.Cast<DrawUserPair>(), onlineUsers.Cast<DrawUserPair>(), offlineUsers.Cast<DrawUserPair>(), drawVisibleExtras);
|
||||
DrawUserPairs(tagsWithPairsInThem, allUsers.Cast<DrawUserPair>().ToList(), visibleUsers.Cast<DrawUserPair>(), onlineUsers.Cast<DrawUserPair>(), offlineUsers.Cast<DrawUserPair>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,15 +44,14 @@ public class PairGroupsUi
|
||||
{
|
||||
var allArePaused = availablePairsInThisTag.All(pair => pair.UserPair!.OwnPermissions.IsPaused());
|
||||
var pauseButton = allArePaused ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
|
||||
var flyoutMenuSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars);
|
||||
var pauseButtonSize = _uiSharedService.GetIconButtonSize(pauseButton);
|
||||
var flyoutMenuX = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars).X;
|
||||
var pauseButtonX = _uiSharedService.GetIconButtonSize(pauseButton).X;
|
||||
var windowX = ImGui.GetWindowContentRegionMin().X;
|
||||
var windowWidth = UiSharedService.GetWindowContentRegionWidth();
|
||||
var spacingX = ImGui.GetStyle().ItemSpacing.X;
|
||||
var currentX = ImGui.GetCursorPosX();
|
||||
var availableWidth = ImGui.GetContentRegionAvail().X;
|
||||
var buttonsWidth = pauseButtonSize.X + flyoutMenuSize.X + spacingX;
|
||||
var pauseStart = Math.Max(currentX, currentX + availableWidth - buttonsWidth);
|
||||
|
||||
ImGui.SameLine(pauseStart);
|
||||
var buttonPauseOffset = windowX + windowWidth - flyoutMenuX - spacingX - pauseButtonX;
|
||||
ImGui.SameLine(buttonPauseOffset);
|
||||
if (_uiSharedService.IconButton(pauseButton))
|
||||
{
|
||||
if (allArePaused)
|
||||
@@ -77,8 +72,8 @@ public class PairGroupsUi
|
||||
UiSharedService.AttachToolTip($"Pause pairing with all pairs in {tag}");
|
||||
}
|
||||
|
||||
var menuStart = Math.Max(pauseStart + pauseButtonSize.X + spacingX, currentX);
|
||||
ImGui.SameLine(menuStart);
|
||||
var buttonDeleteOffset = windowX + windowWidth - flyoutMenuX;
|
||||
ImGui.SameLine(buttonDeleteOffset);
|
||||
if (_uiSharedService.IconButton(FontAwesomeIcon.Bars))
|
||||
{
|
||||
ImGui.OpenPopup("Group Flyout Menu");
|
||||
@@ -91,7 +86,7 @@ public class PairGroupsUi
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCategory(string tag, IEnumerable<DrawPairBase> onlineUsers, IEnumerable<DrawPairBase> allUsers, IEnumerable<DrawPairBase>? visibleUsers = null, Action? drawExtraContent = null)
|
||||
private void DrawCategory(string tag, IEnumerable<DrawPairBase> onlineUsers, IEnumerable<DrawPairBase> allUsers, IEnumerable<DrawPairBase>? visibleUsers = null)
|
||||
{
|
||||
IEnumerable<DrawPairBase> usersInThisTag;
|
||||
HashSet<string>? otherUidsTaggedWithTag = null;
|
||||
@@ -113,25 +108,26 @@ public class PairGroupsUi
|
||||
|
||||
if (isSpecialTag && !usersInThisTag.Any()) return;
|
||||
|
||||
UiSharedService.DrawCard($"pair-group-{tag}", () =>
|
||||
DrawName(tag, isSpecialTag, visibleInThisTag, usersInThisTag.Count(), otherUidsTaggedWithTag?.Count);
|
||||
if (!isSpecialTag)
|
||||
{
|
||||
DrawName(tag, isSpecialTag, visibleInThisTag, usersInThisTag.Count(), otherUidsTaggedWithTag?.Count);
|
||||
if (!isSpecialTag)
|
||||
using (ImRaii.PushId($"group-{tag}-buttons")) DrawButtons(tag, allUsers.Cast<DrawUserPair>().Where(p => otherUidsTaggedWithTag!.Contains(p.UID)).ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_tagHandler.IsTagOpen(tag))
|
||||
{
|
||||
using (ImRaii.PushId($"group-{tag}-buttons")) DrawButtons(tag, allUsers.Cast<DrawUserPair>().Where(p => otherUidsTaggedWithTag!.Contains(p.UID)).ToList());
|
||||
var size = ImGui.CalcTextSize("").Y + ImGui.GetStyle().FramePadding.Y * 2f;
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy(new(size, size));
|
||||
}
|
||||
}
|
||||
|
||||
if (!_tagHandler.IsTagOpen(tag)) return;
|
||||
if (!_tagHandler.IsTagOpen(tag)) return;
|
||||
|
||||
ImGuiHelpers.ScaledDummy(4f);
|
||||
var indent = 18f * ImGuiHelpers.GlobalScale;
|
||||
ImGui.Indent(indent);
|
||||
DrawPairs(tag, usersInThisTag);
|
||||
drawExtraContent?.Invoke();
|
||||
ImGui.Unindent(indent);
|
||||
}, stretchWidth: true);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(4f);
|
||||
ImGui.Indent(20);
|
||||
DrawPairs(tag, usersInThisTag);
|
||||
ImGui.Unindent(20);
|
||||
}
|
||||
|
||||
private void DrawGroupMenu(string tag)
|
||||
@@ -161,21 +157,17 @@ public class PairGroupsUi
|
||||
};
|
||||
|
||||
string resultFolderName = !isSpecialTag ? $"{displayedName} ({visible}/{online}/{total} Pairs)" : $"{displayedName} ({online} Pairs)";
|
||||
bool isOpen = _tagHandler.IsTagOpen(tag);
|
||||
bool previousState = isOpen;
|
||||
UiSharedService.DrawArrowToggle(ref isOpen, $"##group-toggle-{tag}");
|
||||
if (isOpen != previousState)
|
||||
var icon = _tagHandler.IsTagOpen(tag) ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight;
|
||||
_uiSharedService.IconText(icon);
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||
{
|
||||
_tagHandler.SetTagOpen(tag, isOpen);
|
||||
ToggleTagOpen(tag);
|
||||
}
|
||||
ImGui.SameLine(0f, 6f * ImGuiHelpers.GlobalScale);
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(resultFolderName);
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||
{
|
||||
bool newState = !_tagHandler.IsTagOpen(tag);
|
||||
_tagHandler.SetTagOpen(tag, newState);
|
||||
isOpen = newState;
|
||||
ToggleTagOpen(tag);
|
||||
}
|
||||
|
||||
if (!isSpecialTag && ImGui.IsItemHovered())
|
||||
@@ -194,11 +186,15 @@ public class PairGroupsUi
|
||||
{
|
||||
// These are all the OtherUIDs that are tagged with this tag
|
||||
_uidDisplayHandler.RenderPairList(availablePairsInThisCategory);
|
||||
ImGui.Separator();
|
||||
}
|
||||
|
||||
private void DrawUserPairs(List<string> tagsWithPairsInThem, List<DrawUserPair> allUsers, IEnumerable<DrawUserPair> visibleUsers, IEnumerable<DrawUserPair> onlineUsers, IEnumerable<DrawUserPair> offlineUsers, Action? drawVisibleExtras)
|
||||
private void DrawUserPairs(List<string> tagsWithPairsInThem, List<DrawUserPair> allUsers, IEnumerable<DrawUserPair> visibleUsers, IEnumerable<DrawUserPair> onlineUsers, IEnumerable<DrawUserPair> offlineUsers)
|
||||
{
|
||||
// Visible section intentionally omitted for Individual Pairs view.
|
||||
if (_mareConfig.Current.ShowVisibleUsersSeparately)
|
||||
{
|
||||
using (ImRaii.PushId("$group-VisibleCustomTag")) DrawCategory(TagHandler.CustomVisibleTag, visibleUsers, allUsers);
|
||||
}
|
||||
foreach (var tag in tagsWithPairsInThem)
|
||||
{
|
||||
if (_mareConfig.Current.ShowOfflineUsersSeparately)
|
||||
@@ -246,4 +242,9 @@ public class PairGroupsUi
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private void ToggleTagOpen(string tag)
|
||||
{
|
||||
bool open = !_tagHandler.IsTagOpen(tag);
|
||||
_tagHandler.SetTagOpen(tag, open);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Numerics;
|
||||
using System;
|
||||
|
||||
namespace MareSynchronos.UI;
|
||||
|
||||
@@ -21,7 +20,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
private readonly UiSharedService _uiSharedService;
|
||||
private readonly Dictionary<string, string[]> _texturesToConvert = new(StringComparer.Ordinal);
|
||||
private Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>? _cachedAnalysis;
|
||||
private CancellationTokenSource? _conversionCancellationTokenSource = new();
|
||||
private CancellationTokenSource _conversionCancellationTokenSource = new();
|
||||
private string _conversionCurrentFileName = string.Empty;
|
||||
private int _conversionCurrentFileProgress = 0;
|
||||
private Task? _conversionTask;
|
||||
@@ -65,19 +64,6 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
|
||||
protected override void DrawInternal()
|
||||
{
|
||||
DrawAnalysisContent();
|
||||
}
|
||||
|
||||
public void DrawInline()
|
||||
{
|
||||
using (ImRaii.PushId("CharacterAnalysisInline"))
|
||||
{
|
||||
DrawAnalysisContent();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAnalysisContent()
|
||||
{
|
||||
if (_conversionTask != null && !_conversionTask.IsCompleted)
|
||||
{
|
||||
@@ -88,7 +74,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
UiSharedService.TextWrapped("Current file: " + _conversionCurrentFileName);
|
||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel conversion"))
|
||||
{
|
||||
TryCancel(_conversionCancellationTokenSource);
|
||||
_conversionCancellationTokenSource.Cancel();
|
||||
}
|
||||
UiSharedService.SetScaledWindowSize(500);
|
||||
ImGui.EndPopup();
|
||||
@@ -129,7 +115,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
if (isAnalyzing)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped($"Analyzing {_characterAnalyzer.CurrentFile}/{_characterAnalyzer.TotalFiles}",
|
||||
UiSharedService.AccentColor);
|
||||
ImGuiColors.DalamudYellow);
|
||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel analysis"))
|
||||
{
|
||||
_characterAnalyzer.CancelAnalyze();
|
||||
@@ -140,7 +126,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
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.AccentColor);
|
||||
ImGuiColors.DalamudYellow);
|
||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start analysis (missing entries)"))
|
||||
{
|
||||
_ = _characterAnalyzer.ComputeAnalysis(print: false);
|
||||
@@ -179,7 +165,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize))));
|
||||
ImGui.TextUnformatted("Total size (download size):");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, UiSharedService.AccentColor, needAnalysis))
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis))
|
||||
{
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.CompressedSize))));
|
||||
if (needAnalysis && !isAnalyzing)
|
||||
@@ -193,149 +179,140 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
ImGui.TextUnformatted($"Total modded model triangles: {UiSharedService.TrisToString(_cachedAnalysis.Sum(c => c.Value.Sum(f => f.Value.Triangles)))}");
|
||||
ImGui.Separator();
|
||||
|
||||
using var tabbar = ImRaii.TabBar("objectSelection");
|
||||
foreach (var kvp in _cachedAnalysis)
|
||||
{
|
||||
using var objectTabColor = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.AccentColor);
|
||||
using var objectTabHoverColor = ImRaii.PushColor(ImGuiCol.TabHovered, UiSharedService.AccentHoverColor);
|
||||
using var objectTabActiveColor = ImRaii.PushColor(ImGuiCol.TabActive, UiSharedService.AccentActiveColor);
|
||||
using var tabbar = ImRaii.TabBar("objectSelection");
|
||||
foreach (var kvp in _cachedAnalysis)
|
||||
using var id = ImRaii.PushId(kvp.Key.ToString());
|
||||
string tabText = kvp.Key.ToString();
|
||||
using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString());
|
||||
if (tab.Success)
|
||||
{
|
||||
using var id = ImRaii.PushId(kvp.Key.ToString());
|
||||
string tabText = kvp.Key.ToString();
|
||||
using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString());
|
||||
if (tab.Success)
|
||||
var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal)
|
||||
.OrderBy(k => k.Key, StringComparer.Ordinal).ToList();
|
||||
|
||||
ImGui.TextUnformatted("Files for " + kvp.Key);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(kvp.Value.Count.ToString());
|
||||
ImGui.SameLine();
|
||||
|
||||
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal)
|
||||
.OrderBy(k => k.Key, StringComparer.Ordinal).ToList();
|
||||
ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
string text = "";
|
||||
text = string.Join(Environment.NewLine, groupedfiles
|
||||
.Select(f => f.Key + ": " + f.Count() + " files, size: " + UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))
|
||||
+ ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))));
|
||||
ImGui.SetTooltip(text);
|
||||
}
|
||||
ImGui.TextUnformatted($"{kvp.Key} size (actual):");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
|
||||
ImGui.TextUnformatted($"{kvp.Key} size (download size):");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis))
|
||||
{
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
|
||||
if (needAnalysis && !isAnalyzing)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString());
|
||||
UiSharedService.AttachToolTip("Click \"Start analysis\" to calculate download size");
|
||||
}
|
||||
}
|
||||
ImGui.TextUnformatted($"{kvp.Key} VRAM usage:");
|
||||
ImGui.SameLine();
|
||||
var vramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal));
|
||||
if (vramUsage != null)
|
||||
{
|
||||
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("Files for " + kvp.Key);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(kvp.Value.Count.ToString());
|
||||
ImGui.SameLine();
|
||||
ImGui.Separator();
|
||||
if (_selectedObjectTab != kvp.Key)
|
||||
{
|
||||
_selectedHash = string.Empty;
|
||||
_selectedObjectTab = kvp.Key;
|
||||
_selectedFileTypeTab = string.Empty;
|
||||
_enableBc7ConversionMode = false;
|
||||
_texturesToConvert.Clear();
|
||||
}
|
||||
|
||||
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
string text = "";
|
||||
text = string.Join(Environment.NewLine, groupedfiles
|
||||
.Select(f => f.Key + ": " + f.Count() + " files, size: " + UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))
|
||||
+ ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))));
|
||||
ImGui.SetTooltip(text);
|
||||
}
|
||||
ImGui.TextUnformatted($"{kvp.Key} size (actual):");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
|
||||
ImGui.TextUnformatted($"{kvp.Key} size (download size):");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, UiSharedService.AccentColor, needAnalysis))
|
||||
{
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
|
||||
if (needAnalysis && !isAnalyzing)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString());
|
||||
UiSharedService.AttachToolTip("Click \"Start analysis\" to calculate download size");
|
||||
}
|
||||
}
|
||||
ImGui.TextUnformatted($"{kvp.Key} VRAM usage:");
|
||||
ImGui.SameLine();
|
||||
var vramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal));
|
||||
if (vramUsage != null)
|
||||
{
|
||||
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))}");
|
||||
using var fileTabBar = ImRaii.TabBar("fileTabs");
|
||||
|
||||
ImGui.Separator();
|
||||
if (_selectedObjectTab != kvp.Key)
|
||||
foreach (IGrouping<string, CharacterAnalyzer.FileDataEntry>? fileGroup in groupedfiles)
|
||||
{
|
||||
string fileGroupText = fileGroup.Key + " [" + fileGroup.Count() + "]";
|
||||
var requiresCompute = fileGroup.Any(k => !k.IsComputed);
|
||||
using var tabcol = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.Color(ImGuiColors.DalamudYellow), requiresCompute);
|
||||
ImRaii.IEndObject fileTab;
|
||||
using (var textcol = ImRaii.PushColor(ImGuiCol.Text, UiSharedService.Color(new(0, 0, 0, 1)),
|
||||
requiresCompute && !string.Equals(_selectedFileTypeTab, fileGroup.Key, StringComparison.Ordinal)))
|
||||
{
|
||||
fileTab = ImRaii.TabItem(fileGroupText + "###" + fileGroup.Key);
|
||||
}
|
||||
|
||||
if (!fileTab) { fileTab.Dispose(); continue; }
|
||||
|
||||
if (!string.Equals(fileGroup.Key, _selectedFileTypeTab, StringComparison.Ordinal))
|
||||
{
|
||||
_selectedFileTypeTab = fileGroup.Key;
|
||||
_selectedHash = string.Empty;
|
||||
_selectedObjectTab = kvp.Key;
|
||||
_selectedFileTypeTab = string.Empty;
|
||||
_enableBc7ConversionMode = false;
|
||||
_texturesToConvert.Clear();
|
||||
}
|
||||
|
||||
using var fileTabColor = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.AccentColor);
|
||||
using var fileTabHoverColor = ImRaii.PushColor(ImGuiCol.TabHovered, UiSharedService.AccentHoverColor);
|
||||
using var fileTabActiveColor = ImRaii.PushColor(ImGuiCol.TabActive, UiSharedService.AccentActiveColor);
|
||||
using var fileTabBar = ImRaii.TabBar("fileTabs");
|
||||
ImGui.TextUnformatted($"{fileGroup.Key} files");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(fileGroup.Count().ToString());
|
||||
|
||||
foreach (IGrouping<string, CharacterAnalyzer.FileDataEntry>? fileGroup in groupedfiles)
|
||||
ImGui.TextUnformatted($"{fileGroup.Key} files size (actual):");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.OriginalSize)));
|
||||
|
||||
ImGui.TextUnformatted($"{fileGroup.Key} files size (download size):");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize)));
|
||||
|
||||
if (string.Equals(_selectedFileTypeTab, "tex", StringComparison.Ordinal))
|
||||
{
|
||||
string fileGroupText = fileGroup.Key + " [" + fileGroup.Count() + "]";
|
||||
var requiresCompute = fileGroup.Any(k => !k.IsComputed);
|
||||
ImRaii.IEndObject fileTab;
|
||||
using (var textcol = ImRaii.PushColor(ImGuiCol.Text, UiSharedService.Color(Vector4.One),
|
||||
requiresCompute && !string.Equals(_selectedFileTypeTab, fileGroup.Key, StringComparison.Ordinal)))
|
||||
ImGui.Checkbox("Enable BC7 Conversion Mode", ref _enableBc7ConversionMode);
|
||||
if (_enableBc7ConversionMode)
|
||||
{
|
||||
fileTab = ImRaii.TabItem(fileGroupText + "###" + fileGroup.Key);
|
||||
}
|
||||
|
||||
if (!fileTab) { fileTab.Dispose(); continue; }
|
||||
|
||||
if (!string.Equals(fileGroup.Key, _selectedFileTypeTab, StringComparison.Ordinal))
|
||||
{
|
||||
_selectedFileTypeTab = fileGroup.Key;
|
||||
_selectedHash = string.Empty;
|
||||
_enableBc7ConversionMode = false;
|
||||
_texturesToConvert.Clear();
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted($"{fileGroup.Key} files");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(fileGroup.Count().ToString());
|
||||
|
||||
ImGui.TextUnformatted($"{fileGroup.Key} files size (actual):");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.OriginalSize)));
|
||||
|
||||
ImGui.TextUnformatted($"{fileGroup.Key} files size (download size):");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize)));
|
||||
|
||||
if (string.Equals(_selectedFileTypeTab, "tex", StringComparison.Ordinal))
|
||||
{
|
||||
ImGui.Checkbox("Enable BC7 Conversion Mode", ref _enableBc7ConversionMode);
|
||||
if (_enableBc7ConversionMode)
|
||||
UiSharedService.ColorText("WARNING BC7 CONVERSION:", ImGuiColors.DalamudYellow);
|
||||
ImGui.SameLine();
|
||||
UiSharedService.ColorText("Converting textures to BC7 is irreversible!", UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped("- Converting textures to BC7 will reduce their size (compressed and uncompressed) drastically. It is recommended to be used for large (4k+) textures." +
|
||||
Environment.NewLine + "- Some textures, especially ones utilizing colorsets, might not be suited for BC7 conversion and might produce visual artifacts." +
|
||||
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." +
|
||||
Environment.NewLine + "- Conversion will convert all found texture duplicates (entries with more than 1 file path) automatically." +
|
||||
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."
|
||||
, ImGuiColors.DalamudYellow);
|
||||
if (_texturesToConvert.Count > 0 && _uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start conversion of " + _texturesToConvert.Count + " texture(s)"))
|
||||
{
|
||||
UiSharedService.ColorText("WARNING BC7 CONVERSION:", UiSharedService.AccentColor);
|
||||
ImGui.SameLine();
|
||||
UiSharedService.ColorText("Converting textures to BC7 is irreversible!", UiSharedService.AccentColor);
|
||||
UiSharedService.ColorTextWrapped("- Converting textures to BC7 will reduce their size (compressed and uncompressed) drastically. It is recommended to be used for large (4k+) textures." +
|
||||
Environment.NewLine + "- Some textures, especially ones utilizing colorsets, might not be suited for BC7 conversion and might produce visual artifacts." +
|
||||
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." +
|
||||
Environment.NewLine + "- Conversion will convert all found texture duplicates (entries with more than 1 file path) automatically." +
|
||||
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."
|
||||
, UiSharedService.AccentColor);
|
||||
if (_texturesToConvert.Count > 0 && _uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start conversion of " + _texturesToConvert.Count + " texture(s)"))
|
||||
{
|
||||
var conversionCts = EnsureFreshCts(ref _conversionCancellationTokenSource);
|
||||
_conversionTask = _ipcManager.Penumbra.ConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, conversionCts.Token);
|
||||
}
|
||||
_conversionCancellationTokenSource = _conversionCancellationTokenSource.CancelRecreate();
|
||||
_conversionTask = _ipcManager.Penumbra.ConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, _conversionCancellationTokenSource.Token);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
DrawTable(fileGroup);
|
||||
|
||||
fileTab.Dispose();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
DrawTable(fileGroup);
|
||||
|
||||
fileTab.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
ImGui.TextUnformatted("Selected file:");
|
||||
ImGui.SameLine();
|
||||
UiSharedService.ColorText(_selectedHash, UiSharedService.AccentColor);
|
||||
UiSharedService.ColorText(_selectedHash, ImGuiColors.DalamudYellow);
|
||||
|
||||
if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item))
|
||||
{
|
||||
@@ -377,13 +354,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
CancelAndDispose(ref _conversionCancellationTokenSource);
|
||||
_conversionProgress.ProgressChanged -= ConversionProgress_ProgressChanged;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
_conversionProgress.ProgressChanged -= ConversionProgress_ProgressChanged;
|
||||
}
|
||||
|
||||
private void ConversionProgress_ProgressChanged(object? sender, (string, int) e)
|
||||
@@ -462,8 +434,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
ImGui.TableNextColumn();
|
||||
if (string.Equals(_selectedHash, item.Hash, StringComparison.Ordinal))
|
||||
{
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(UiSharedService.AccentColor));
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(UiSharedService.AccentColor));
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(ImGuiColors.DalamudYellow));
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(ImGuiColors.DalamudYellow));
|
||||
}
|
||||
ImGui.TextUnformatted(item.Hash);
|
||||
if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
|
||||
@@ -477,7 +449,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(item.OriginalSize));
|
||||
if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
|
||||
ImGui.TableNextColumn();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, UiSharedService.AccentColor, !item.IsComputed))
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, !item.IsComputed))
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(item.CompressedSize));
|
||||
if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
|
||||
if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal))
|
||||
@@ -517,31 +489,4 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static CancellationTokenSource EnsureFreshCts(ref CancellationTokenSource? cts)
|
||||
{
|
||||
CancelAndDispose(ref cts);
|
||||
cts = new CancellationTokenSource();
|
||||
return cts;
|
||||
}
|
||||
|
||||
private static void CancelAndDispose(ref CancellationTokenSource? cts)
|
||||
{
|
||||
if (cts == null) return;
|
||||
TryCancel(cts);
|
||||
cts.Dispose();
|
||||
cts = null;
|
||||
}
|
||||
|
||||
private static void TryCancel(CancellationTokenSource? cts)
|
||||
{
|
||||
if (cts == null) return;
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
||||
private readonly FileDialogManager _fileDialogManager;
|
||||
private readonly MareProfileManager _mareProfileManager;
|
||||
private readonly UiSharedService _uiSharedService;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
private bool _adjustedForScollBarsLocalProfile = false;
|
||||
private bool _adjustedForScollBarsOnlineProfile = false;
|
||||
private string _descriptionText = string.Empty;
|
||||
@@ -33,6 +34,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
||||
|
||||
public EditProfileUi(ILogger<EditProfileUi> logger, MareMediator mediator,
|
||||
ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager,
|
||||
ServerConfigurationManager serverConfigurationManager,
|
||||
MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService)
|
||||
: base(logger, mediator, "Umbra Edit Profile###UmbraSyncEditProfileUI", performanceCollectorService)
|
||||
{
|
||||
@@ -45,6 +47,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
||||
_apiController = apiController;
|
||||
_uiSharedService = uiSharedService;
|
||||
_fileDialogManager = fileDialogManager;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
_mareProfileManager = mareProfileManager;
|
||||
|
||||
Mediator.Subscribe<GposeStartMessage>(this, (_) => { _wasOpen = IsOpen; IsOpen = false; });
|
||||
@@ -61,16 +64,6 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
|
||||
protected override void DrawInternal()
|
||||
{
|
||||
DrawProfileContent();
|
||||
}
|
||||
|
||||
public void DrawInline()
|
||||
{
|
||||
DrawProfileContent();
|
||||
}
|
||||
|
||||
private void DrawProfileContent()
|
||||
{
|
||||
_uiSharedService.BigText("Current Profile (as saved on server)");
|
||||
ImGuiHelpers.ScaledDummy(new Vector2(0f, ImGui.GetStyle().ItemSpacing.Y / 2));
|
||||
|
||||
@@ -144,12 +144,12 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
UiSharedService.SetFontScale(1.5f);
|
||||
ImGui.SetWindowFontScale(1.5f);
|
||||
string readThis = "MERCI DE LIRE ATTENTIVEMENT";
|
||||
Vector2 textSize = ImGui.CalcTextSize(readThis);
|
||||
ImGui.SetCursorPosX(ImGui.GetWindowSize().X / 2 - textSize.X / 2);
|
||||
UiSharedService.ColorText(readThis, UiSharedService.AccentColor);
|
||||
UiSharedService.SetFontScale(1.0f);
|
||||
ImGui.SetWindowFontScale(1.0f);
|
||||
ImGui.Separator();
|
||||
UiSharedService.TextWrapped("""
|
||||
Pour utiliser les services UmbraSync, vous devez être âgé de plus de 18 ans, où plus de 21 ans dans certaines juridictions.
|
||||
|
||||
@@ -16,22 +16,25 @@ namespace MareSynchronos.UI;
|
||||
public class PopoutProfileUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
private readonly MareProfileManager _mareProfileManager;
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly ServerConfigurationManager _serverManager;
|
||||
private readonly UiSharedService _uiSharedService;
|
||||
private Vector2 _lastMainPos = Vector2.Zero;
|
||||
private Vector2 _lastMainSize = Vector2.Zero;
|
||||
private byte[] _lastProfilePicture = [];
|
||||
private byte[] _lastSupporterPicture = [];
|
||||
private Pair? _pair;
|
||||
private IDalamudTextureWrap? _supporterTextureWrap;
|
||||
private IDalamudTextureWrap? _textureWrap;
|
||||
|
||||
public PopoutProfileUi(ILogger<PopoutProfileUi> logger, MareMediator mediator, UiSharedService uiSharedService,
|
||||
ServerConfigurationManager serverManager, MareConfigService mareConfigService,
|
||||
MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService) : base(logger, mediator, "###UmbraSyncPopoutProfileUI", performanceCollectorService)
|
||||
MareProfileManager mareProfileManager, PairManager pairManager, PerformanceCollectorService performanceCollectorService) : base(logger, mediator, "###UmbraSyncPopoutProfileUI", performanceCollectorService)
|
||||
{
|
||||
_uiSharedService = uiSharedService;
|
||||
_serverManager = serverManager;
|
||||
_mareProfileManager = mareProfileManager;
|
||||
_pairManager = pairManager;
|
||||
Flags = ImGuiWindowFlags.NoDecoration;
|
||||
|
||||
Mediator.Subscribe<ProfilePopoutToggle>(this, (msg) =>
|
||||
@@ -39,6 +42,7 @@ public class PopoutProfileUi : WindowMediatorSubscriberBase
|
||||
IsOpen = msg.Pair != null;
|
||||
_pair = msg.Pair;
|
||||
_lastProfilePicture = [];
|
||||
_lastSupporterPicture = [];
|
||||
_textureWrap?.Dispose();
|
||||
_textureWrap = null;
|
||||
_supporterTextureWrap?.Dispose();
|
||||
@@ -178,4 +182,4 @@ public class PopoutProfileUi : WindowMediatorSubscriberBase
|
||||
_logger.LogWarning(ex, "Error during draw tooltip");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,11 +131,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
DrawSettingsContent();
|
||||
}
|
||||
|
||||
public void DrawInline()
|
||||
{
|
||||
DrawSettingsContent();
|
||||
}
|
||||
|
||||
public override void OnClose()
|
||||
{
|
||||
_uiShared.EditTrackerPosition = false;
|
||||
@@ -534,9 +529,9 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
}, globalChatTypeIdx);
|
||||
_uiShared.DrawHelpText("FFXIV chat channel to output chat messages on.");
|
||||
|
||||
UiSharedService.SetFontScale(0.6f);
|
||||
ImGui.SetWindowFontScale(0.6f);
|
||||
_uiShared.BigText("\"Chat 2\" Plugin Integration");
|
||||
UiSharedService.SetFontScale(1.0f);
|
||||
ImGui.SetWindowFontScale(1.0f);
|
||||
|
||||
var extraChatTags = _configService.Current.ExtraChatTags;
|
||||
if (ImGui.Checkbox("Tag messages as ExtraChat", ref extraChatTags))
|
||||
@@ -577,9 +572,9 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
if (shellEnabled)
|
||||
shellName = $"[{shellNumber}] {shellName}";
|
||||
|
||||
UiSharedService.SetFontScale(0.6f);
|
||||
ImGui.SetWindowFontScale(0.6f);
|
||||
_uiShared.BigText(shellName);
|
||||
UiSharedService.SetFontScale(1.0f);
|
||||
ImGui.SetWindowFontScale(1.0f);
|
||||
|
||||
using var pushIndent = ImRaii.PushIndent();
|
||||
|
||||
@@ -1007,7 +1002,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
|
||||
_lastTab = "General";
|
||||
|
||||
_uiShared.BigText("Notes");
|
||||
_uiShared.BigText("Notes");
|
||||
if (_uiShared.IconTextButton(FontAwesomeIcon.StickyNote, "Export all your user notes to clipboard"))
|
||||
{
|
||||
ImGui.SetClipboardText(UiSharedService.GetNotes(_pairManager.DirectPairs.UnionBy(_pairManager.GroupPairs.SelectMany(p => p.Value), p => p.UserData, UserDataComparer.Instance).ToList()));
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace MareSynchronos.UI;
|
||||
public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
private readonly MareProfileManager _mareProfileManager;
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly ServerConfigurationManager _serverManager;
|
||||
private readonly UiSharedService _uiSharedService;
|
||||
private bool _adjustedForScrollBars = false;
|
||||
@@ -23,7 +24,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
private IDalamudTextureWrap? _textureWrap;
|
||||
|
||||
public StandaloneProfileUi(ILogger<StandaloneProfileUi> logger, MareMediator mediator, UiSharedService uiBuilder,
|
||||
ServerConfigurationManager serverManager, MareProfileManager mareProfileManager, Pair pair,
|
||||
ServerConfigurationManager serverManager, MareProfileManager mareProfileManager, PairManager pairManager, Pair pair,
|
||||
PerformanceCollectorService performanceCollector)
|
||||
: base(logger, mediator, "Profile of " + pair.UserData.AliasOrUID + "##UmbraSyncStandaloneProfileUI" + pair.UserData.AliasOrUID, performanceCollector)
|
||||
{
|
||||
@@ -31,6 +32,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
_serverManager = serverManager;
|
||||
_mareProfileManager = mareProfileManager;
|
||||
Pair = pair;
|
||||
_pairManager = pairManager;
|
||||
Flags = ImGuiWindowFlags.NoResize | ImGuiWindowFlags.AlwaysAutoResize;
|
||||
|
||||
var spacing = ImGui.GetStyle().ItemSpacing;
|
||||
@@ -162,4 +164,4 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
Mediator.Publish(new RemoveWindowMessage(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,15 @@ using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using System;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.API.Data.Extensions;
|
||||
using MareSynchronos.API.Dto.Group;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.AutoDetect;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.UI.Components.Popup;
|
||||
|
||||
@@ -27,7 +23,6 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
private readonly List<string> _oneTimeInvites = [];
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly UiSharedService _uiSharedService;
|
||||
private readonly SyncshellDiscoveryService _syncshellDiscoveryService;
|
||||
private List<BannedGroupUserDto> _bannedUsers = [];
|
||||
private int _multiInvites;
|
||||
private string _newPassword;
|
||||
@@ -35,31 +30,20 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
private Task<int>? _pruneTestTask;
|
||||
private Task<int>? _pruneTask;
|
||||
private int _pruneDays = 14;
|
||||
private bool _autoDetectStateInitialized;
|
||||
private bool _autoDetectStateLoading;
|
||||
private bool _autoDetectToggleInFlight;
|
||||
private bool _autoDetectVisible;
|
||||
private bool _autoDetectPasswordDisabled;
|
||||
private string? _autoDetectMessage;
|
||||
|
||||
public SyncshellAdminUI(ILogger<SyncshellAdminUI> logger, MareMediator mediator, ApiController apiController,
|
||||
UiSharedService uiSharedService, PairManager pairManager, SyncshellDiscoveryService syncshellDiscoveryService,
|
||||
GroupFullInfoDto groupFullInfo, PerformanceCollectorService performanceCollectorService)
|
||||
UiSharedService uiSharedService, PairManager pairManager, GroupFullInfoDto groupFullInfo, PerformanceCollectorService performanceCollectorService)
|
||||
: base(logger, mediator, "Syncshell Admin Panel (" + groupFullInfo.GroupAliasOrGID + ")", performanceCollectorService)
|
||||
{
|
||||
GroupFullInfo = groupFullInfo;
|
||||
_apiController = apiController;
|
||||
_uiSharedService = uiSharedService;
|
||||
_pairManager = pairManager;
|
||||
_syncshellDiscoveryService = syncshellDiscoveryService;
|
||||
_isOwner = string.Equals(GroupFullInfo.OwnerUID, _apiController.UID, StringComparison.Ordinal);
|
||||
_isModerator = GroupFullInfo.GroupUserInfo.IsModerator();
|
||||
_newPassword = string.Empty;
|
||||
_multiInvites = 30;
|
||||
_pwChangeSuccess = true;
|
||||
_autoDetectVisible = groupFullInfo.AutoDetectVisible;
|
||||
_autoDetectPasswordDisabled = groupFullInfo.PasswordTemporarilyDisabled;
|
||||
Mediator.Subscribe<SyncshellAutoDetectStateChanged>(this, OnSyncshellAutoDetectStateChanged);
|
||||
IsOpen = true;
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
{
|
||||
@@ -75,11 +59,6 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
if (!_isModerator && !_isOwner) return;
|
||||
|
||||
GroupFullInfo = _pairManager.Groups[GroupFullInfo.Group];
|
||||
if (!_autoDetectToggleInFlight && !_autoDetectStateLoading)
|
||||
{
|
||||
_autoDetectVisible = GroupFullInfo.AutoDetectVisible;
|
||||
_autoDetectPasswordDisabled = GroupFullInfo.PasswordTemporarilyDisabled;
|
||||
}
|
||||
|
||||
using var id = ImRaii.PushId("syncshell_admin_" + GroupFullInfo.GID);
|
||||
|
||||
@@ -384,13 +363,6 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
}
|
||||
mgmtTab.Dispose();
|
||||
|
||||
var discoveryTab = ImRaii.TabItem("AutoDetect");
|
||||
if (discoveryTab)
|
||||
{
|
||||
DrawAutoDetectTab();
|
||||
}
|
||||
discoveryTab.Dispose();
|
||||
|
||||
var permissionTab = ImRaii.TabItem("Permissions");
|
||||
if (permissionTab)
|
||||
{
|
||||
@@ -476,128 +448,6 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAutoDetectTab()
|
||||
{
|
||||
if (!_autoDetectStateInitialized && !_autoDetectStateLoading)
|
||||
{
|
||||
_autoDetectStateInitialized = true;
|
||||
_autoDetectStateLoading = true;
|
||||
_ = EnsureAutoDetectStateAsync();
|
||||
}
|
||||
|
||||
UiSharedService.TextWrapped("Activer l'affichage AutoDetect rend la Syncshell visible dans l'onglet AutoDetect et désactive temporairement le mot de passe.");
|
||||
ImGuiHelpers.ScaledDummy(4);
|
||||
|
||||
if (_autoDetectStateLoading)
|
||||
{
|
||||
ImGui.TextDisabled("Chargement de l'état en cours...");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_autoDetectMessage))
|
||||
{
|
||||
UiSharedService.ColorTextWrapped(_autoDetectMessage!, ImGuiColors.DalamudYellow);
|
||||
}
|
||||
|
||||
bool desiredVisibility = _autoDetectVisible;
|
||||
using (ImRaii.Disabled(_autoDetectToggleInFlight || _autoDetectStateLoading))
|
||||
{
|
||||
if (ImGui.Checkbox("Afficher cette Syncshell dans l'AutoDetect", ref desiredVisibility))
|
||||
{
|
||||
_ = ToggleAutoDetectAsync(desiredVisibility);
|
||||
}
|
||||
}
|
||||
_uiSharedService.DrawHelpText("Quand cette option est activée, le mot de passe devient inactif tant que la visibilité est maintenue.");
|
||||
|
||||
if (_autoDetectPasswordDisabled && _autoDetectVisible)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Le mot de passe est actuellement désactivé pendant la visibilité AutoDetect.", ImGuiColors.DalamudYellow);
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(6);
|
||||
if (ImGui.Button("Recharger l'état"))
|
||||
{
|
||||
_autoDetectStateLoading = true;
|
||||
_ = EnsureAutoDetectStateAsync(true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnsureAutoDetectStateAsync(bool force = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var state = await _syncshellDiscoveryService.GetStateAsync(GroupFullInfo.GID, CancellationToken.None).ConfigureAwait(false);
|
||||
if (state != null)
|
||||
{
|
||||
ApplyAutoDetectState(state.AutoDetectVisible, state.PasswordTemporarilyDisabled, true);
|
||||
_autoDetectMessage = null;
|
||||
}
|
||||
else if (force)
|
||||
{
|
||||
_autoDetectMessage = "Impossible de récupérer l'état AutoDetect.";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_autoDetectMessage = force ? $"Erreur lors du rafraîchissement : {ex.Message}" : _autoDetectMessage;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_autoDetectStateLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ToggleAutoDetectAsync(bool desiredVisibility)
|
||||
{
|
||||
if (_autoDetectToggleInFlight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_autoDetectToggleInFlight = true;
|
||||
_autoDetectMessage = null;
|
||||
|
||||
try
|
||||
{
|
||||
var success = await _syncshellDiscoveryService.SetVisibilityAsync(GroupFullInfo.GID, desiredVisibility, CancellationToken.None).ConfigureAwait(false);
|
||||
if (!success)
|
||||
{
|
||||
_autoDetectMessage = "Impossible de mettre à jour la visibilité AutoDetect.";
|
||||
return;
|
||||
}
|
||||
|
||||
await EnsureAutoDetectStateAsync(true).ConfigureAwait(false);
|
||||
_autoDetectMessage = desiredVisibility
|
||||
? "La Syncshell est désormais visible dans AutoDetect."
|
||||
: "La Syncshell n'est plus visible dans AutoDetect.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_autoDetectMessage = $"Erreur lors de la mise à jour AutoDetect : {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
_autoDetectToggleInFlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyAutoDetectState(bool visible, bool passwordDisabled, bool fromServer)
|
||||
{
|
||||
_autoDetectVisible = visible;
|
||||
_autoDetectPasswordDisabled = passwordDisabled;
|
||||
if (fromServer)
|
||||
{
|
||||
GroupFullInfo.AutoDetectVisible = visible;
|
||||
GroupFullInfo.PasswordTemporarilyDisabled = passwordDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSyncshellAutoDetectStateChanged(SyncshellAutoDetectStateChanged msg)
|
||||
{
|
||||
if (!string.Equals(msg.Gid, GroupFullInfo.GID, StringComparison.OrdinalIgnoreCase)) return;
|
||||
ApplyAutoDetectState(msg.Visible, msg.PasswordTemporarilyDisabled, true);
|
||||
_autoDetectMessage = null;
|
||||
}
|
||||
|
||||
public override void OnClose()
|
||||
{
|
||||
Mediator.Publish(new RemoveWindowMessage(this));
|
||||
|
||||
@@ -29,7 +29,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
private static readonly TimeSpan TypingDisplayDelay = TimeSpan.FromMilliseconds(500);
|
||||
private static readonly TimeSpan TypingDisplayFade = TypingDisplayTime;
|
||||
|
||||
private readonly ILogger<TypingIndicatorOverlay> _typedLogger;
|
||||
private readonly ILogger<TypingIndicatorOverlay> _logger;
|
||||
private readonly MareConfigService _configService;
|
||||
private readonly IGameGui _gameGui;
|
||||
private readonly ITextureProvider _textureProvider;
|
||||
@@ -47,7 +47,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
TypingIndicatorStateService typingStateService, ApiController apiController)
|
||||
: base(logger, mediator, nameof(TypingIndicatorOverlay), performanceCollectorService)
|
||||
{
|
||||
_typedLogger = logger;
|
||||
_logger = logger;
|
||||
_configService = configService;
|
||||
_gameGui = gameGui;
|
||||
_textureProvider = textureProvider;
|
||||
@@ -216,7 +216,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
|
||||
if (objectId != uint.MaxValue && objectId != 0 && TryDrawNameplateBubble(drawList, iconWrap, objectId))
|
||||
{
|
||||
_typedLogger.LogTrace("TypingIndicator: drew nameplate bubble for {uid} (objectId={objectId})", uid, objectId);
|
||||
_logger.LogTrace("TypingIndicator: drew nameplate bubble for {uid} (objectId={objectId})", uid, objectId);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -228,20 +228,20 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
|
||||
if (pair == null)
|
||||
{
|
||||
_typedLogger.LogTrace("TypingIndicator: no pair found for {uid}, attempting fallback", uid);
|
||||
_logger.LogTrace("TypingIndicator: no pair found for {uid}, attempting fallback", uid);
|
||||
}
|
||||
|
||||
_typedLogger.LogTrace("TypingIndicator: fallback draw for {uid} (objectId={objectId}, name={name}, ident={ident})",
|
||||
_logger.LogTrace("TypingIndicator: fallback draw for {uid} (objectId={objectId}, name={name}, ident={ident})",
|
||||
uid, objectId, pairName, pairIdent);
|
||||
|
||||
if (hasWorldPosition)
|
||||
{
|
||||
DrawWorldFallbackIcon(drawList, iconWrap, worldPos);
|
||||
_typedLogger.LogTrace("TypingIndicator: fallback world draw for {uid} at {pos}", uid, worldPos);
|
||||
_logger.LogTrace("TypingIndicator: fallback world draw for {uid} at {pos}", uid, worldPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
_typedLogger.LogTrace("TypingIndicator: could not resolve position for {uid}", uid);
|
||||
_logger.LogTrace("TypingIndicator: could not resolve position for {uid}", uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,7 +393,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
{
|
||||
if (TryGetWorldPosition(objectId, out position))
|
||||
{
|
||||
_typedLogger.LogTrace("TypingIndicator: resolved by objectId {objectId}", objectId);
|
||||
_logger.LogTrace("TypingIndicator: resolved by objectId {objectId}", objectId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
var name = pair.PlayerName;
|
||||
if (!string.IsNullOrEmpty(name) && TryGetWorldPositionByName(name!, out position))
|
||||
{
|
||||
_typedLogger.LogTrace("TypingIndicator: resolved by pair name {name}", name);
|
||||
_logger.LogTrace("TypingIndicator: resolved by pair name {name}", name);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -412,7 +412,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
var cached = _dalamudUtil.FindPlayerByNameHash(ident);
|
||||
if (!string.IsNullOrEmpty(cached.Name) && TryGetWorldPositionByName(cached.Name, out position))
|
||||
{
|
||||
_typedLogger.LogTrace("TypingIndicator: resolved by cached name {name}", cached.Name);
|
||||
_logger.LogTrace("TypingIndicator: resolved by cached name {name}", cached.Name);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -422,7 +422,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
if (objRef != null)
|
||||
{
|
||||
position = objRef.Position;
|
||||
_typedLogger.LogTrace("TypingIndicator: resolved by cached address {addr}", cached.Address);
|
||||
_logger.LogTrace("TypingIndicator: resolved by cached address {addr}", cached.Address);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -432,7 +432,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
||||
var alias = userData.AliasOrUID;
|
||||
if (!string.IsNullOrEmpty(alias) && TryGetWorldPositionByName(alias, out position))
|
||||
{
|
||||
_typedLogger.LogTrace("TypingIndicator: resolved by user alias {alias}", alias);
|
||||
_logger.LogTrace("TypingIndicator: resolved by user alias {alias}", alias);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@ using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
@@ -38,8 +36,6 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
ImGuiWindowFlags.NoScrollbar |
|
||||
ImGuiWindowFlags.NoScrollWithMouse;
|
||||
|
||||
public const float ContentFontScale = 0.92f;
|
||||
|
||||
public static Vector4 AccentColor { get; set; } = ImGuiColors.DalamudViolet;
|
||||
public static Vector4 AccentHoverColor { get; set; } = new Vector4(0x3A / 255f, 0x15 / 255f, 0x50 / 255f, 1f);
|
||||
public static Vector4 AccentActiveColor { get; set; } = AccentHoverColor;
|
||||
@@ -63,8 +59,6 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
private readonly Dictionary<string, object> _selectedComboItems = new(StringComparer.Ordinal);
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
private bool _cacheDirectoryHasOtherFilesThanCache = false;
|
||||
private static readonly Stack<float> _fontScaleStack = new();
|
||||
private static float _currentWindowFontScale = 1f;
|
||||
|
||||
private bool _cacheDirectoryIsValidPath = true;
|
||||
|
||||
@@ -123,12 +117,8 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
{
|
||||
e.OnPreBuild(tk => tk.AddDalamudAssetFont(Dalamud.DalamudAsset.NotoSansJpMedium, new()
|
||||
{
|
||||
SizePx = 27,
|
||||
GlyphRanges = [
|
||||
0x0020, 0x007E,
|
||||
0x00A0, 0x017F,
|
||||
0
|
||||
]
|
||||
SizePx = 35,
|
||||
GlyphRanges = [0x20, 0x7E, 0]
|
||||
}));
|
||||
});
|
||||
GameFont = _pluginInterface.UiBuilder.FontAtlas.NewGameFontHandle(new(GameFontFamilyAndSize.Axis12));
|
||||
@@ -226,38 +216,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
|
||||
public static bool CtrlPressed() => (GetKeyState(0xA2) & 0x8000) != 0 || (GetKeyState(0xA3) & 0x8000) != 0;
|
||||
|
||||
public static IDisposable PushFontScale(float scale)
|
||||
{
|
||||
var previous = _currentWindowFontScale;
|
||||
_fontScaleStack.Push(previous);
|
||||
if (Math.Abs(previous - scale) > float.Epsilon)
|
||||
{
|
||||
SetFontScale(scale);
|
||||
}
|
||||
|
||||
return new FontScaleScope();
|
||||
}
|
||||
|
||||
private sealed class FontScaleScope : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
if (_fontScaleStack.Count == 0) return;
|
||||
var previous = _fontScaleStack.Pop();
|
||||
if (Math.Abs(previous - _currentWindowFontScale) > float.Epsilon)
|
||||
{
|
||||
SetFontScale(previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetFontScale(float scale)
|
||||
{
|
||||
ImGui.SetWindowFontScale(scale);
|
||||
_currentWindowFontScale = scale;
|
||||
}
|
||||
|
||||
public static void DrawGrouped(Action imguiDrawAction, float rounding = 5f, float? expectedWidth = null, bool drawBorder = true)
|
||||
public static void DrawGrouped(Action imguiDrawAction, float rounding = 5f, float? expectedWidth = null)
|
||||
{
|
||||
var cursorPos = ImGui.GetCursorPos();
|
||||
using (ImRaii.Group())
|
||||
@@ -271,128 +230,10 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
imguiDrawAction.Invoke();
|
||||
}
|
||||
|
||||
if (drawBorder)
|
||||
{
|
||||
ImGui.GetWindowDrawList().AddRect(
|
||||
ImGui.GetItemRectMin() - ImGui.GetStyle().ItemInnerSpacing,
|
||||
ImGui.GetItemRectMax() + ImGui.GetStyle().ItemInnerSpacing,
|
||||
Color(ImGuiColors.DalamudGrey2), rounding);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawCard(string id, Action draw, Vector2? padding = null, Vector4? background = null,
|
||||
Vector4? border = null, float? rounding = null, bool stretchWidth = false)
|
||||
{
|
||||
var style = ImGui.GetStyle();
|
||||
var padBase = style.FramePadding;
|
||||
var pad = padding ?? new Vector2(
|
||||
padBase.X + 4f * ImGuiHelpers.GlobalScale,
|
||||
padBase.Y + 3f * ImGuiHelpers.GlobalScale);
|
||||
var cardBg = background ?? new Vector4(0.08f, 0.08f, 0.10f, 0.94f);
|
||||
var cardBorder = border ?? new Vector4(0f, 0f, 0f, 0.85f);
|
||||
float cardRounding = rounding ?? Math.Max(style.FrameRounding, 8f * ImGuiHelpers.GlobalScale);
|
||||
float borderThickness = Math.Max(1f, Math.Max(style.FrameBorderSize, 1f) * ImGuiHelpers.GlobalScale);
|
||||
float borderInset = borderThickness;
|
||||
|
||||
var originalCursor = ImGui.GetCursorPos();
|
||||
if (stretchWidth)
|
||||
{
|
||||
ImGui.SetCursorPosX(ImGui.GetWindowContentRegionMin().X);
|
||||
}
|
||||
|
||||
var startCursor = ImGui.GetCursorPos();
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
drawList.ChannelsSplit(2);
|
||||
drawList.ChannelsSetCurrent(1);
|
||||
|
||||
ImGui.PushID(id);
|
||||
ImGui.SetCursorPos(new Vector2(startCursor.X + pad.X, startCursor.Y + pad.Y));
|
||||
ImGui.BeginGroup();
|
||||
draw();
|
||||
ImGui.EndGroup();
|
||||
ImGui.PopID();
|
||||
|
||||
var contentMin = ImGui.GetItemRectMin();
|
||||
var contentMax = ImGui.GetItemRectMax();
|
||||
var cardMin = contentMin - pad;
|
||||
var cardMax = contentMax + pad;
|
||||
var outerMin = cardMin;
|
||||
var outerMax = cardMax;
|
||||
|
||||
if (stretchWidth)
|
||||
{
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
var regionMin = ImGui.GetWindowContentRegionMin();
|
||||
var regionMax = ImGui.GetWindowContentRegionMax();
|
||||
var scrollX = ImGui.GetScrollX();
|
||||
cardMin.X = windowPos.X + regionMin.X + scrollX;
|
||||
cardMax.X = windowPos.X + regionMax.X + scrollX;
|
||||
outerMin.X = cardMin.X;
|
||||
outerMax.X = cardMax.X;
|
||||
startCursor.X = ImGui.GetWindowContentRegionMin().X;
|
||||
}
|
||||
|
||||
var drawMin = new Vector2(cardMin.X + borderInset, cardMin.Y + borderInset);
|
||||
var drawMax = new Vector2(cardMax.X - borderInset, cardMax.Y - borderInset);
|
||||
var clipMin = drawList.GetClipRectMin();
|
||||
var clipMax = drawList.GetClipRectMax();
|
||||
var clipInset = new Vector2(borderThickness * 0.5f + 0.5f, borderThickness * 0.5f + 0.5f);
|
||||
drawMin = Vector2.Max(drawMin, clipMin + clipInset);
|
||||
drawMax = Vector2.Min(drawMax, clipMax - clipInset);
|
||||
if (drawMax.X <= drawMin.X)
|
||||
{
|
||||
drawMax.X = drawMin.X + borderThickness;
|
||||
}
|
||||
if (drawMax.Y <= drawMin.Y)
|
||||
{
|
||||
drawMax.Y = drawMin.Y + borderThickness;
|
||||
}
|
||||
|
||||
drawList.ChannelsSetCurrent(0);
|
||||
drawList.AddRectFilled(drawMin, drawMax, ImGui.ColorConvertFloat4ToU32(cardBg), cardRounding);
|
||||
if (cardBorder.W > 0f && borderThickness > 0f)
|
||||
{
|
||||
drawList.AddRect(drawMin, drawMax, ImGui.ColorConvertFloat4ToU32(cardBorder), cardRounding, ImDrawFlags.None, borderThickness);
|
||||
}
|
||||
drawList.ChannelsMerge();
|
||||
|
||||
ImGui.SetCursorPos(startCursor);
|
||||
var dummyWidth = outerMax.X - outerMin.X;
|
||||
var dummyHeight = outerMax.Y - outerMin.Y;
|
||||
ImGui.Dummy(new Vector2(dummyWidth, dummyHeight));
|
||||
ImGui.SetCursorPos(new Vector2(startCursor.X, startCursor.Y + dummyHeight));
|
||||
|
||||
if (!stretchWidth)
|
||||
{
|
||||
ImGui.SetCursorPosX(originalCursor.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.SetCursorPosX(startCursor.X);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DrawArrowToggle(ref bool state, string id)
|
||||
{
|
||||
var framePadding = ImGui.GetStyle().FramePadding;
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(framePadding.X, framePadding.Y * 0.85f));
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(1f, 1f, 1f, 0.08f));
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(1f, 1f, 1f, 0.16f));
|
||||
bool clicked = ImGui.ArrowButton(id, state ? ImGuiDir.Down : ImGuiDir.Right);
|
||||
ImGui.PopStyleColor(3);
|
||||
ImGui.PopStyleVar();
|
||||
if (clicked)
|
||||
{
|
||||
state = !state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
public static float GetCardContentPaddingX()
|
||||
{
|
||||
var style = ImGui.GetStyle();
|
||||
return style.FramePadding.X + 4f * ImGuiHelpers.GlobalScale;
|
||||
ImGui.GetWindowDrawList().AddRect(
|
||||
ImGui.GetItemRectMin() - ImGui.GetStyle().ItemInnerSpacing,
|
||||
ImGui.GetItemRectMax() + ImGui.GetStyle().ItemInnerSpacing,
|
||||
Color(ImGuiColors.DalamudGrey2), rounding);
|
||||
}
|
||||
|
||||
public static void DrawGroupedCenteredColorText(string text, Vector4 color, float? maxWidth = null)
|
||||
@@ -934,9 +775,9 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
|
||||
if (intro)
|
||||
{
|
||||
SetFontScale(0.8f);
|
||||
ImGui.SetWindowFontScale(0.8f);
|
||||
BigText("Mandatory Plugins");
|
||||
SetFontScale(1.0f);
|
||||
ImGui.SetWindowFontScale(1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -957,9 +798,9 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
|
||||
if (intro)
|
||||
{
|
||||
SetFontScale(0.8f);
|
||||
ImGui.SetWindowFontScale(0.8f);
|
||||
BigText("Optional Addons");
|
||||
SetFontScale(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.");
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
public static class PngHdr
|
||||
public class PngHdr
|
||||
{
|
||||
private static readonly byte[] _magicSignature = [137, 80, 78, 71, 13, 10, 26, 10];
|
||||
private static readonly byte[] _IHDR = [(byte)'I', (byte)'H', (byte)'D', (byte)'R'];
|
||||
|
||||
@@ -4,6 +4,7 @@ using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Reflection;
|
||||
@@ -14,15 +15,17 @@ namespace MareSynchronos.WebAPI;
|
||||
public sealed class AccountRegistrationService : IDisposable
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<AccountRegistrationService> _logger;
|
||||
private readonly ServerConfigurationManager _serverManager;
|
||||
|
||||
private static string GenerateSecretKey()
|
||||
private string GenerateSecretKey()
|
||||
{
|
||||
return Convert.ToHexString(SHA256.HashData(RandomNumberGenerator.GetBytes(64)));
|
||||
}
|
||||
|
||||
public AccountRegistrationService(ServerConfigurationManager serverManager)
|
||||
public AccountRegistrationService(ILogger<AccountRegistrationService> logger, ServerConfigurationManager serverManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_serverManager = serverManager;
|
||||
_httpClient = new(
|
||||
new HttpClientHandler
|
||||
@@ -64,4 +67,4 @@ public sealed class AccountRegistrationService : IDisposable
|
||||
SecretKey = secretKey
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,16 +17,19 @@ namespace MareSynchronos.WebAPI.Files;
|
||||
public sealed class FileUploadManager : DisposableMediatorSubscriberBase
|
||||
{
|
||||
private readonly FileCacheManager _fileDbManager;
|
||||
private readonly MareConfigService _mareConfigService;
|
||||
private readonly FileTransferOrchestrator _orchestrator;
|
||||
private readonly ServerConfigurationManager _serverManager;
|
||||
private readonly Dictionary<string, DateTime> _verifiedUploadedHashes = new(StringComparer.Ordinal);
|
||||
private CancellationTokenSource? _uploadCancellationTokenSource = new();
|
||||
|
||||
public FileUploadManager(ILogger<FileUploadManager> logger, MareMediator mediator,
|
||||
MareConfigService mareConfigService,
|
||||
FileTransferOrchestrator orchestrator,
|
||||
FileCacheManager fileDbManager,
|
||||
ServerConfigurationManager serverManager) : base(logger, mediator)
|
||||
{
|
||||
_mareConfigService = mareConfigService;
|
||||
_orchestrator = orchestrator;
|
||||
_fileDbManager = fileDbManager;
|
||||
_serverManager = serverManager;
|
||||
@@ -283,4 +286,4 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
|
||||
|
||||
CurrentUploads.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,6 @@ public class DownloadFileTransfer : FileTransfer
|
||||
get => Dto.Size;
|
||||
}
|
||||
|
||||
public long TotalRaw => Dto.Size;
|
||||
public long TotalRaw => 0; // XXX
|
||||
private DownloadFileDto Dto => (DownloadFileDto)TransferDto;
|
||||
}
|
||||
}
|
||||
@@ -231,7 +231,7 @@ public partial class ApiController
|
||||
|
||||
public Task Client_GposeLobbyPushWorldData(UserData userData, WorldData worldData)
|
||||
{
|
||||
Logger.LogDebug("Client_GposeLobbyPushWorldData: {dto}", userData);
|
||||
//Logger.LogDebug("Client_GposeLobbyPushWorldData: {dto}", userData);
|
||||
ExecuteSafely(() => Mediator.Publish(new GPoseLobbyReceiveWorldData(userData, worldData)));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -415,4 +415,4 @@ public partial class ApiController
|
||||
Logger.LogCritical(ex, "Error on executing safely");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MareSynchronos.API.Dto.McdfShare;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.WebAPI;
|
||||
|
||||
public sealed partial class ApiController
|
||||
{
|
||||
public async Task<List<McdfShareEntryDto>> McdfShareGetOwn()
|
||||
{
|
||||
if (!IsConnected) return [];
|
||||
try
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<List<McdfShareEntryDto>>(nameof(McdfShareGetOwn)).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error during {method}", nameof(McdfShareGetOwn));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<McdfShareEntryDto>> McdfShareGetShared()
|
||||
{
|
||||
if (!IsConnected) return [];
|
||||
try
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<List<McdfShareEntryDto>>(nameof(McdfShareGetShared)).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error during {method}", nameof(McdfShareGetShared));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task McdfShareUpload(McdfShareUploadRequestDto requestDto)
|
||||
{
|
||||
if (!IsConnected) return;
|
||||
try
|
||||
{
|
||||
await _mareHub!.InvokeAsync(nameof(McdfShareUpload), requestDto).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error during {method}", nameof(McdfShareUpload));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<McdfSharePayloadDto?> McdfShareDownload(Guid shareId)
|
||||
{
|
||||
if (!IsConnected) return null;
|
||||
try
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<McdfSharePayloadDto?>(nameof(McdfShareDownload), shareId).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error during {method}", nameof(McdfShareDownload));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> McdfShareDelete(Guid shareId)
|
||||
{
|
||||
if (!IsConnected) return false;
|
||||
try
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<bool>(nameof(McdfShareDelete), shareId).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error during {method}", nameof(McdfShareDelete));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<McdfShareEntryDto?> McdfShareUpdate(McdfShareUpdateRequestDto requestDto)
|
||||
{
|
||||
if (!IsConnected) return null;
|
||||
try
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<McdfShareEntryDto?>(nameof(McdfShareUpdate), requestDto).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error during {method}", nameof(McdfShareUpdate));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using MareSynchronos.API.Dto.Group;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
|
||||
namespace MareSynchronos.WebAPI;
|
||||
|
||||
public partial class ApiController
|
||||
{
|
||||
public async Task<List<SyncshellDiscoveryEntryDto>> SyncshellDiscoveryList()
|
||||
{
|
||||
CheckConnection();
|
||||
return await _mareHub!.InvokeAsync<List<SyncshellDiscoveryEntryDto>>(nameof(SyncshellDiscoveryList)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<SyncshellDiscoveryStateDto?> SyncshellDiscoveryGetState(GroupDto group)
|
||||
{
|
||||
CheckConnection();
|
||||
return await _mareHub!.InvokeAsync<SyncshellDiscoveryStateDto?>(nameof(SyncshellDiscoveryGetState), group).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> SyncshellDiscoverySetVisibility(SyncshellDiscoveryVisibilityRequestDto request)
|
||||
{
|
||||
CheckConnection();
|
||||
return await _mareHub!.InvokeAsync<bool>(nameof(SyncshellDiscoverySetVisibility), request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> SyncshellDiscoveryJoin(GroupDto group)
|
||||
{
|
||||
CheckConnection();
|
||||
return await _mareHub!.InvokeAsync<bool>(nameof(SyncshellDiscoveryJoin), group).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user