1 Commits
2.0 ... main

Author SHA1 Message Date
e1d71ee33a Allow disable display self-analysis alert 2025-11-01 00:23:53 +01:00
71 changed files with 1144 additions and 4390 deletions

3
.gitignore vendored
View File

@@ -3,7 +3,6 @@
## ##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
.idea .idea
qodana.yaml
# User-specific files # User-specific files
*.rsuser *.rsuser
*.suo *.suo
@@ -14,8 +13,6 @@ qodana.yaml
MareSynchronos/.DS_Store MareSynchronos/.DS_Store
*.zip *.zip
UmbraServer_extracted/ UmbraServer_extracted/
NuGet.config
Directory.Build.props
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs

Submodule MareAPI updated: d105d20507...0abb078c21

View File

@@ -1,5 +1,4 @@
using System; using MareSynchronos.Interop.Ipc;
using MareSynchronos.Interop.Ipc;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
@@ -607,35 +606,14 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
base.Dispose(disposing); base.Dispose(disposing);
try _scanCancellationTokenSource?.Cancel();
{
_scanCancellationTokenSource.Cancel();
}
catch (ObjectDisposedException)
{
}
_scanCancellationTokenSource.Dispose();
PenumbraWatcher?.Dispose(); PenumbraWatcher?.Dispose();
MareWatcher?.Dispose(); MareWatcher?.Dispose();
SubstWatcher?.Dispose(); SubstWatcher?.Dispose();
TryCancelAndDispose(_penumbraFswCts); _penumbraFswCts?.CancelDispose();
TryCancelAndDispose(_mareFswCts); _mareFswCts?.CancelDispose();
TryCancelAndDispose(_substFswCts); _substFswCts?.CancelDispose();
TryCancelAndDispose(_periodicCalculationTokenSource); _periodicCalculationTokenSource?.CancelDispose();
}
private static void TryCancelAndDispose(CancellationTokenSource? cts)
{
if (cts == null) return;
try
{
cts.Cancel();
}
catch (ObjectDisposedException)
{
}
cts.Dispose();
} }
private void FullFileScan(CancellationToken ct) private void FullFileScan(CancellationToken ct)

View File

@@ -12,7 +12,7 @@ public sealed class FileCompactor
private readonly Dictionary<string, int> _clusterSizes; 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 ILogger<FileCompactor> _logger;
private readonly MareConfigService _mareConfigService; private readonly MareConfigService _mareConfigService;
@@ -24,7 +24,7 @@ public sealed class FileCompactor
_logger = logger; _logger = logger;
_mareConfigService = mareConfigService; _mareConfigService = mareConfigService;
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
_efInfo = new WofFileCompressionInfoV1 _efInfo = new WOF_FILE_COMPRESSION_INFO_V1
{ {
Algorithm = CompressionAlgorithm.XPRESS8K, Algorithm = CompressionAlgorithm.XPRESS8K,
Flags = 0 Flags = 0
@@ -123,7 +123,7 @@ public sealed class FileCompactor
out uint lpTotalNumberOfClusters); out uint lpTotalNumberOfClusters);
[DllImport("WoFUtil.dll")] [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")] [DllImport("WofUtil.dll")]
private static extern int WofSetFileDataLocation(IntPtr FileHandle, ulong Provider, IntPtr ExternalFileInfo, ulong Length); private static extern int WofSetFileDataLocation(IntPtr FileHandle, ulong Provider, IntPtr ExternalFileInfo, ulong Length);
@@ -242,7 +242,7 @@ public sealed class FileCompactor
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
private struct WofFileCompressionInfoV1 private struct WOF_FILE_COMPRESSION_INFO_V1
{ {
public CompressionAlgorithm Algorithm; public CompressionAlgorithm Algorithm;
public ulong Flags; public ulong Flags;

View File

@@ -17,8 +17,8 @@ namespace MareSynchronos.Interop;
public record ChatChannelOverride public record ChatChannelOverride
{ {
public string ChannelName { get; set; } = string.Empty; public string ChannelName = string.Empty;
public Action<byte[]>? ChatMessageHandler { get; set; } public Action<byte[]>? ChatMessageHandler;
} }
public unsafe sealed class GameChatHooks : IDisposable public unsafe sealed class GameChatHooks : IDisposable

View File

@@ -31,11 +31,11 @@ public class MdlFile
public ushort Unknown9; public ushort Unknown9;
// Offsets are stored relative to RuntimeSize instead of file start. // Offsets are stored relative to RuntimeSize instead of file start.
public uint[] VertexOffset; public uint[] VertexOffset = [0, 0, 0];
public uint[] IndexOffset; public uint[] IndexOffset = [0, 0, 0];
public uint[] VertexBufferSize; public uint[] VertexBufferSize = [0, 0, 0];
public uint[] IndexBufferSize; public uint[] IndexBufferSize = [0, 0, 0];
public byte LodCount; public byte LodCount;
public bool EnableIndexBufferStreaming; public bool EnableIndexBufferStreaming;
public bool EnableEdgeGeometry; public bool EnableEdgeGeometry;
@@ -43,26 +43,15 @@ public class MdlFile
public ModelFlags1 Flags1; public ModelFlags1 Flags1;
public ModelFlags2 Flags2; public ModelFlags2 Flags2;
public VertexDeclarationStruct[] VertexDeclarations; public VertexDeclarationStruct[] VertexDeclarations = [];
public ElementIdStruct[] ElementIds; public ElementIdStruct[] ElementIds = [];
public MeshStruct[] Meshes; public MeshStruct[] Meshes = [];
public BoundingBoxStruct[] BoneBoundingBoxes; public BoundingBoxStruct[] BoneBoundingBoxes = [];
public LodStruct[] Lods; public LodStruct[] Lods = [];
public ExtraLodStruct[] ExtraLods; public ExtraLodStruct[] ExtraLods = [];
public MdlFile(string filePath) 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 stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var r = new LuminaBinaryReader(stream); using var r = new LuminaBinaryReader(stream);

View File

@@ -1,5 +1,4 @@
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using System;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Ipc; using Dalamud.Plugin.Ipc;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
@@ -30,7 +29,7 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
private bool _marePluginEnabled = false; private bool _marePluginEnabled = false;
private bool _impersonating = false; private bool _impersonating = false;
private DateTime _unregisterTime = DateTime.UtcNow; private DateTime _unregisterTime = DateTime.UtcNow;
private CancellationTokenSource? _registerDelayCts = new(); private CancellationTokenSource _registerDelayCts = new();
public bool MarePluginEnabled => _marePluginEnabled; public bool MarePluginEnabled => _marePluginEnabled;
public bool ImpersonationActive => _impersonating; public bool ImpersonationActive => _impersonating;
@@ -101,7 +100,7 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
{ {
if (_mareConfig.Current.MareAPI) if (_mareConfig.Current.MareAPI)
{ {
var cancelToken = EnsureFreshCts(ref _registerDelayCts).Token; var cancelToken = _registerDelayCts.Token;
Task.Run(async () => Task.Run(async () =>
{ {
// Wait before registering to reduce the chance of a race condition // Wait before registering to reduce the chance of a race condition
@@ -126,7 +125,7 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
} }
else else
{ {
EnsureFreshCts(ref _registerDelayCts); _registerDelayCts = _registerDelayCts.CancelRecreate();
if (_impersonating) if (_impersonating)
{ {
_loadFileProviderMare?.UnregisterFunc(); _loadFileProviderMare?.UnregisterFunc();
@@ -147,7 +146,7 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
_loadFileAsyncProvider?.UnregisterFunc(); _loadFileAsyncProvider?.UnregisterFunc();
_handledGameAddresses?.UnregisterFunc(); _handledGameAddresses?.UnregisterFunc();
TryCancel(_registerDelayCts); _registerDelayCts.Cancel();
if (_impersonating) if (_impersonating)
{ {
_loadFileProviderMare?.UnregisterFunc(); _loadFileProviderMare?.UnregisterFunc();
@@ -156,7 +155,6 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
} }
Mediator.UnsubscribeAll(this); Mediator.UnsubscribeAll(this);
CancelAndDispose(ref _registerDelayCts);
return Task.CompletedTask; 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(); 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)
{
}
}
} }

View File

@@ -1,20 +1,19 @@
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using System;
using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Collections.Concurrent; using System.Collections.Concurrent;
namespace MareSynchronos.Interop.Ipc; namespace MareSynchronos.Interop.Ipc;
public class RedrawManager : IDisposable public class RedrawManager
{ {
private readonly MareMediator _mareMediator; private readonly MareMediator _mareMediator;
private readonly DalamudUtilService _dalamudUtil; private readonly DalamudUtilService _dalamudUtil;
private readonly ConcurrentDictionary<nint, bool> _penumbraRedrawRequests = []; private readonly ConcurrentDictionary<nint, bool> _penumbraRedrawRequests = [];
private CancellationTokenSource? _disposalCts = new(); private CancellationTokenSource _disposalCts = new();
private bool _disposed;
public SemaphoreSlim RedrawSemaphore { get; init; } = new(2, 2); public SemaphoreSlim RedrawSemaphore { get; init; } = new(2, 2);
@@ -33,12 +32,12 @@ public class RedrawManager : IDisposable
try try
{ {
using CancellationTokenSource cancelToken = new CancellationTokenSource(); 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; var combinedToken = combinedCts.Token;
cancelToken.CancelAfter(TimeSpan.FromSeconds(15)); cancelToken.CancelAfter(TimeSpan.FromSeconds(15));
await handler.ActOnFrameworkAfterEnsureNoDrawAsync(action, combinedToken).ConfigureAwait(false); 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); await _dalamudUtil.WaitWhileCharacterIsDrawing(logger, handler, applicationId, 30000, combinedToken).ConfigureAwait(false);
} }
finally finally
@@ -50,45 +49,6 @@ public class RedrawManager : IDisposable
internal void Cancel() internal void Cancel()
{ {
EnsureFreshCts(ref _disposalCts); _disposalCts = _disposalCts.CancelRecreate();
}
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;
} }
} }

View File

@@ -87,8 +87,6 @@ public class MareConfig : IMareConfiguration
public bool ExtraChatTags { get; set; } = false; public bool ExtraChatTags { get; set; } = false;
public bool TypingIndicatorShowOnNameplates { get; set; } = true; public bool TypingIndicatorShowOnNameplates { get; set; } = true;
public bool TypingIndicatorShowOnPartyList { get; set; } = true; public bool TypingIndicatorShowOnPartyList { get; set; } = true;
public bool TypingIndicatorEnabled { get; set; } = true;
public bool TypingIndicatorShowSelf { get; set; } = true;
public TypingIndicatorBubbleSize TypingIndicatorBubbleSize { get; set; } = TypingIndicatorBubbleSize.Large; public TypingIndicatorBubbleSize TypingIndicatorBubbleSize { get; set; } = TypingIndicatorBubbleSize.Large;
public bool MareAPI { get; set; } = true; public bool MareAPI { get; set; } = true;

View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using MareSynchronos.MareConfiguration.Models;
namespace MareSynchronos.MareConfiguration.Configurations;
[Serializable]
public class NotificationsConfig : IMareConfiguration
{
public List<StoredNotification> Notifications { get; set; } = new();
public int Version { get; set; } = 1;
}

View File

@@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
namespace MareSynchronos.MareConfiguration.Models;
[Serializable]
public class StoredNotification
{
public string Category { get; set; } = string.Empty; // name of enum NotificationCategory
public string Id { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string? Description { get; set; }
public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
}

View File

@@ -1,14 +0,0 @@
using MareSynchronos.MareConfiguration.Configurations;
namespace MareSynchronos.MareConfiguration;
public class NotificationsConfigService : ConfigurationServiceBase<NotificationsConfig>
{
public const string ConfigName = "notifications.json";
public NotificationsConfigService(string configDir) : base(configDir)
{
}
public override string ConfigurationName => ConfigName;
}

View File

@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<AssemblyName>UmbraSync</AssemblyName> <AssemblyName>UmbraSync</AssemblyName>
<RootNamespace>UmbraSync</RootNamespace> <RootNamespace>UmbraSync</RootNamespace>
<Version>0.1.9.9</Version> <Version>0.1.9.6</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -50,8 +50,6 @@
<PropertyGroup> <PropertyGroup>
<SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss:fffZ"))</SourceRevisionId> <SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss:fffZ"))</SourceRevisionId>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<DisablePackageVulnerabilityAnalysis>true</DisablePackageVulnerabilityAnalysis>
<NoWarn>$(NoWarn);NU1900</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -5,6 +5,7 @@ using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -22,6 +23,7 @@ public class PairHandlerFactory
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private readonly MareMediator _mareMediator; private readonly MareMediator _mareMediator;
private readonly PlayerPerformanceService _playerPerformanceService; private readonly PlayerPerformanceService _playerPerformanceService;
private readonly ServerConfigurationManager _serverConfigManager;
private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
private readonly PairAnalyzerFactory _pairAnalyzerFactory; private readonly PairAnalyzerFactory _pairAnalyzerFactory;
private readonly VisibilityService _visibilityService; private readonly VisibilityService _visibilityService;
@@ -30,7 +32,7 @@ public class PairHandlerFactory
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime, PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService, FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService,
PairAnalyzerFactory pairAnalyzerFactory, ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory,
MareConfigService configService, VisibilityService visibilityService) MareConfigService configService, VisibilityService visibilityService)
{ {
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
@@ -43,6 +45,7 @@ public class PairHandlerFactory
_fileCacheManager = fileCacheManager; _fileCacheManager = fileCacheManager;
_mareMediator = mareMediator; _mareMediator = mareMediator;
_playerPerformanceService = playerPerformanceService; _playerPerformanceService = playerPerformanceService;
_serverConfigManager = serverConfigManager;
_pairAnalyzerFactory = pairAnalyzerFactory; _pairAnalyzerFactory = pairAnalyzerFactory;
_configService = configService; _configService = configService;
_visibilityService = visibilityService; _visibilityService = visibilityService;
@@ -52,6 +55,6 @@ public class PairHandlerFactory
{ {
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory, return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory,
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
_fileCacheManager, _mareMediator, _playerPerformanceService, _configService, _visibilityService); _fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager, _configService, _visibilityService);
} }
} }

View File

@@ -7,6 +7,7 @@ using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.Events; using MareSynchronos.Services.Events;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Files; using MareSynchronos.WebAPI.Files;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@@ -28,6 +29,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
private readonly IpcManager _ipcManager; private readonly IpcManager _ipcManager;
private readonly PlayerPerformanceService _playerPerformanceService; private readonly PlayerPerformanceService _playerPerformanceService;
private readonly ServerConfigurationManager _serverConfigManager;
private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
private readonly VisibilityService _visibilityService; private readonly VisibilityService _visibilityService;
private CancellationTokenSource? _applicationCancellationTokenSource = new(); private CancellationTokenSource? _applicationCancellationTokenSource = new();
@@ -51,6 +53,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime,
FileCacheManager fileDbManager, MareMediator mediator, FileCacheManager fileDbManager, MareMediator mediator,
PlayerPerformanceService playerPerformanceService, PlayerPerformanceService playerPerformanceService,
ServerConfigurationManager serverConfigManager,
MareConfigService configService, VisibilityService visibilityService) : base(logger, mediator) MareConfigService configService, VisibilityService visibilityService) : base(logger, mediator)
{ {
Pair = pair; Pair = pair;
@@ -62,6 +65,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
_dalamudUtil = dalamudUtil; _dalamudUtil = dalamudUtil;
_fileDbManager = fileDbManager; _fileDbManager = fileDbManager;
_playerPerformanceService = playerPerformanceService; _playerPerformanceService = playerPerformanceService;
_serverConfigManager = serverConfigManager;
_configService = configService; _configService = configService;
_visibilityService = visibilityService; _visibilityService = visibilityService;

View File

@@ -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) public void SetNote(string note)
{ {
_serverConfigurationManager.SetNoteForUid(UserData.UID, note); _serverConfigurationManager.SetNoteForUid(UserData.UID, note);

View File

@@ -15,7 +15,6 @@ using MareSynchronos.Services;
using MareSynchronos.Services.Events; using MareSynchronos.Services.Events;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Services.Notifications;
using MareSynchronos.UI; using MareSynchronos.UI;
using MareSynchronos.UI.Components; using MareSynchronos.UI.Components;
using MareSynchronos.UI.Components.Popup; 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.NearbyDiscoveryService>();
collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyPendingService>(); collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyPendingService>();
collection.AddSingleton<MareSynchronos.Services.AutoDetect.AutoDetectSuppressionService>(); collection.AddSingleton<MareSynchronos.Services.AutoDetect.AutoDetectSuppressionService>();
collection.AddSingleton<MareSynchronos.Services.AutoDetect.SyncshellDiscoveryService>();
collection.AddSingleton<MarePlugin>(); collection.AddSingleton<MarePlugin>();
collection.AddSingleton<MareProfileManager>(); collection.AddSingleton<MareProfileManager>();
collection.AddSingleton<GameObjectHandlerFactory>(); collection.AddSingleton<GameObjectHandlerFactory>();
@@ -128,7 +126,6 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton<CharaDataCharacterHandler>(); collection.AddSingleton<CharaDataCharacterHandler>();
collection.AddSingleton<CharaDataNearbyManager>(); collection.AddSingleton<CharaDataNearbyManager>();
collection.AddSingleton<CharaDataGposeTogetherManager>(); collection.AddSingleton<CharaDataGposeTogetherManager>();
collection.AddSingleton<McdfShareManager>();
collection.AddSingleton<VfxSpawnManager>(); collection.AddSingleton<VfxSpawnManager>();
collection.AddSingleton<BlockedCharacterHandler>(); collection.AddSingleton<BlockedCharacterHandler>();
@@ -154,7 +151,6 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton<PartyListTypingService>(); collection.AddSingleton<PartyListTypingService>();
collection.AddSingleton<TypingIndicatorStateService>(); collection.AddSingleton<TypingIndicatorStateService>();
collection.AddSingleton<ChatTwoCompatibilityService>(); collection.AddSingleton<ChatTwoCompatibilityService>();
collection.AddSingleton<NotificationTracker>();
collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName));
@@ -167,7 +163,6 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton((s) => new ServerBlockConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new ServerBlockConfigService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new CharaDataConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new CharaDataConfigService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new RemoteConfigCacheService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new RemoteConfigCacheService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new NotificationsConfigService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<MareConfigService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<MareConfigService>());
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<ServerConfigService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<ServerConfigService>());
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<NotesConfigService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<NotesConfigService>());
@@ -179,7 +174,6 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<ServerBlockConfigService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<ServerBlockConfigService>());
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<CharaDataConfigService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<CharaDataConfigService>());
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<RemoteConfigCacheService>()); collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<RemoteConfigCacheService>());
collection.AddSingleton<IConfigService<IMareConfiguration>>(s => s.GetRequiredService<NotificationsConfigService>());
collection.AddSingleton<ConfigurationMigrator>(); collection.AddSingleton<ConfigurationMigrator>();
collection.AddSingleton<ConfigurationSaveService>(); collection.AddSingleton<ConfigurationSaveService>();
@@ -188,23 +182,17 @@ public sealed class Plugin : IDalamudPlugin
// add scoped services // add scoped services
collection.AddScoped<CacheMonitor>(); collection.AddScoped<CacheMonitor>();
collection.AddScoped<UiFactory>(); collection.AddScoped<UiFactory>();
collection.AddScoped<SettingsUi>(); collection.AddScoped<WindowMediatorSubscriberBase, SettingsUi>();
collection.AddScoped<CompactUi>(); collection.AddScoped<WindowMediatorSubscriberBase, 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, IntroUi>(); collection.AddScoped<WindowMediatorSubscriberBase, IntroUi>();
collection.AddScoped<WindowMediatorSubscriberBase, DownloadUi>(); collection.AddScoped<WindowMediatorSubscriberBase, DownloadUi>();
collection.AddScoped<WindowMediatorSubscriberBase>(sp => sp.GetRequiredService<AutoDetectUi>()); collection.AddScoped<WindowMediatorSubscriberBase, AutoDetectUi>();
collection.AddScoped<WindowMediatorSubscriberBase, ChangelogUi>(); collection.AddScoped<WindowMediatorSubscriberBase, ChangelogUi>();
collection.AddScoped<WindowMediatorSubscriberBase, PopoutProfileUi>(); collection.AddScoped<WindowMediatorSubscriberBase, PopoutProfileUi>();
collection.AddScoped<WindowMediatorSubscriberBase>(sp => sp.GetRequiredService<DataAnalysisUi>()); collection.AddScoped<WindowMediatorSubscriberBase, DataAnalysisUi>();
collection.AddScoped<WindowMediatorSubscriberBase, EventViewerUI>(); collection.AddScoped<WindowMediatorSubscriberBase, EventViewerUI>();
collection.AddScoped<WindowMediatorSubscriberBase, CharaDataHubUi>(); collection.AddScoped<WindowMediatorSubscriberBase, CharaDataHubUi>();
collection.AddScoped<WindowMediatorSubscriberBase>(sp => sp.GetRequiredService<EditProfileUi>()); collection.AddScoped<WindowMediatorSubscriberBase, EditProfileUi>();
collection.AddScoped<WindowMediatorSubscriberBase, PopupHandler>(); collection.AddScoped<WindowMediatorSubscriberBase, PopupHandler>();
collection.AddScoped<WindowMediatorSubscriberBase, TypingIndicatorOverlay>(); collection.AddScoped<WindowMediatorSubscriberBase, TypingIndicatorOverlay>();
collection.AddScoped<IPopupHandler, ReportPopupHandler>(); collection.AddScoped<IPopupHandler, ReportPopupHandler>();
@@ -234,7 +222,6 @@ public sealed class Plugin : IDalamudPlugin
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>()); collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>()); collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
collection.AddHostedService(p => p.GetRequiredService<MareSynchronos.Services.AutoDetect.NearbyDiscoveryService>()); 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<ChatTwoCompatibilityService>());
collection.AddHostedService(p => p.GetRequiredService<MareSynchronos.Services.AutoDetect.AutoDetectSuppressionService>()); collection.AddHostedService(p => p.GetRequiredService<MareSynchronos.Services.AutoDetect.AutoDetectSuppressionService>());
}) })

View File

@@ -17,6 +17,7 @@ public class DiscoveryConfigProvider
private readonly TokenProvider _tokenProvider; private readonly TokenProvider _tokenProvider;
private WellKnownRoot? _config; private WellKnownRoot? _config;
private DateTimeOffset _lastLoad = DateTimeOffset.MinValue;
public DiscoveryConfigProvider(ILogger<DiscoveryConfigProvider> logger, ServerConfigurationManager serverManager, TokenProvider tokenProvider) public DiscoveryConfigProvider(ILogger<DiscoveryConfigProvider> logger, ServerConfigurationManager serverManager, TokenProvider tokenProvider)
{ {
@@ -50,6 +51,7 @@ public class DiscoveryConfigProvider
root.NearbyDiscovery?.Hydrate(); root.NearbyDiscovery?.Hydrate();
_config = root; _config = root;
_lastLoad = DateTimeOffset.UtcNow;
_logger.LogDebug("Loaded Nearby well-known (stapled), enabled={enabled}, expires={exp}", NearbyEnabled, _config?.NearbyDiscovery?.SaltExpiresAt); _logger.LogDebug("Loaded Nearby well-known (stapled), enabled={enabled}, expires={exp}", NearbyEnabled, _config?.NearbyDiscovery?.SaltExpiresAt);
return true; return true;
} }
@@ -95,6 +97,7 @@ public class DiscoveryConfigProvider
root.NearbyDiscovery?.Hydrate(); root.NearbyDiscovery?.Hydrate();
_config = root; _config = root;
_lastLoad = DateTimeOffset.UtcNow;
_logger.LogInformation("Loaded Nearby well-known (http {path}), enabled={enabled}", path, NearbyEnabled); _logger.LogInformation("Loaded Nearby well-known (http {path}), enabled={enabled}", path, NearbyEnabled);
return true; return true;
} }

View File

@@ -1,4 +1,3 @@
using System;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
@@ -8,7 +7,6 @@ using MareSynchronos.WebAPI.AutoDetect;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using System.Numerics; using System.Numerics;
using System.Linq; using System.Linq;
using System.Collections.Generic;
using MareSynchronos.Utils; using MareSynchronos.Utils;
namespace MareSynchronos.Services.AutoDetect; namespace MareSynchronos.Services.AutoDetect;
@@ -36,8 +34,6 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
private bool _lastAutoDetectState; private bool _lastAutoDetectState;
private DateTime _lastHeartbeat = DateTime.MinValue; private DateTime _lastHeartbeat = DateTime.MinValue;
private static readonly TimeSpan HeartbeatInterval = TimeSpan.FromSeconds(75); private static readonly TimeSpan HeartbeatInterval = TimeSpan.FromSeconds(75);
private readonly object _entriesLock = new();
private List<NearbyEntry> _lastEntries = [];
public NearbyDiscoveryService(ILogger<NearbyDiscoveryService> logger, MareMediator mediator, public NearbyDiscoveryService(ILogger<NearbyDiscoveryService> logger, MareMediator mediator,
MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService, MareConfigService config, DiscoveryConfigProvider configProvider, DalamudUtilService dalamudUtilService,
@@ -56,7 +52,6 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
CancelAndDispose(ref _loopCts);
_loopCts = new CancellationTokenSource(); _loopCts = new CancellationTokenSource();
_mediator.Subscribe<ConnectedMessage>(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); }); _mediator.Subscribe<ConnectedMessage>(this, _ => { _isConnected = true; _configProvider.TryLoadFromStapled(); });
_mediator.Subscribe<DisconnectedMessage>(this, _ => { _isConnected = false; _lastPublishedSignature = null; }); _mediator.Subscribe<DisconnectedMessage>(this, _ => { _isConnected = false; _lastPublishedSignature = null; });
@@ -133,41 +128,10 @@ public class NearbyDiscoveryService : IHostedService, IMediatorSubscriber
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
_mediator.UnsubscribeAll(this); _mediator.UnsubscribeAll(this);
CancelAndDispose(ref _loopCts); try { _loopCts?.Cancel(); } catch { }
return Task.CompletedTask; 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) private async Task Loop(CancellationToken ct)
{ {
_configProvider.TryLoadFromStapled(); _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"); _logger.LogDebug("Nearby: well-known not available or disabled; running in local-only mode");
} }
} }
UpdateSnapshot(entries);
_mediator.Publish(new DiscoveryListUpdated(entries)); _mediator.Publish(new DiscoveryListUpdated(entries));
var delayMs = Math.Max(1000, _configProvider.MinQueryIntervalMs); var delayMs = Math.Max(1000, _configProvider.MinQueryIntervalMs);

View File

@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using MareSynchronos.MareConfiguration.Models; using MareSynchronos.MareConfiguration.Models;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.Notifications;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -15,19 +14,16 @@ public sealed class NearbyPendingService : IMediatorSubscriber
private readonly MareMediator _mediator; private readonly MareMediator _mediator;
private readonly ApiController _api; private readonly ApiController _api;
private readonly AutoDetectRequestService _requestService; private readonly AutoDetectRequestService _requestService;
private readonly NotificationTracker _notificationTracker;
private readonly ConcurrentDictionary<string, string> _pending = new(StringComparer.Ordinal); 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);
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);
private static readonly Regex AcceptRegex = new(@"^Nearby Accept: .+ \[(?<uid>[A-Z0-9]+)\]$", RegexOptions.Compiled | RegexOptions.ExplicitCapture, RegexTimeout);
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; _logger = logger;
_mediator = mediator; _mediator = mediator;
_api = api; _api = api;
_requestService = requestService; _requestService = requestService;
_notificationTracker = notificationTracker;
_mediator.Subscribe<NotificationMessage>(this, OnNotification); _mediator.Subscribe<NotificationMessage>(this, OnNotification);
_mediator.Subscribe<ManualPairInviteMessage>(this, OnManualPairInvite); _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))); _ = _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uidA)));
_pending.TryRemove(uidA, out _); _pending.TryRemove(uidA, out _);
_requestService.RemovePendingRequestByUid(uidA); _requestService.RemovePendingRequestByUid(uidA);
_notificationTracker.Remove(NotificationCategory.AutoDetect, uidA);
_logger.LogInformation("NearbyPending: auto-accepted pairing with {uid}", uidA); _logger.LogInformation("NearbyPending: auto-accepted pairing with {uid}", uidA);
} }
return; return;
@@ -71,7 +66,6 @@ public sealed class NearbyPendingService : IMediatorSubscriber
catch { name = uid; } catch { name = uid; }
_pending[uid] = name; _pending[uid] = name;
_logger.LogInformation("NearbyPending: received request from {uid} ({name})", uid, name); _logger.LogInformation("NearbyPending: received request from {uid} ({name})", uid, name);
_notificationTracker.Upsert(NotificationEntry.AutoDetect(uid, name));
} }
private void OnManualPairInvite(ManualPairInviteMessage msg) private void OnManualPairInvite(ManualPairInviteMessage msg)
@@ -86,14 +80,12 @@ public sealed class NearbyPendingService : IMediatorSubscriber
_pending[msg.SourceUid] = display; _pending[msg.SourceUid] = display;
_logger.LogInformation("NearbyPending: received manual invite from {uid} ({name})", 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))); _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) public void Remove(string uid)
{ {
_pending.TryRemove(uid, out _); _pending.TryRemove(uid, out _);
_requestService.RemovePendingRequestByUid(uid); _requestService.RemovePendingRequestByUid(uid);
_notificationTracker.Remove(NotificationCategory.AutoDetect, uid);
} }
public async Task<bool> AcceptAsync(string uid) public async Task<bool> AcceptAsync(string uid)
@@ -104,7 +96,6 @@ public sealed class NearbyPendingService : IMediatorSubscriber
_pending.TryRemove(uid, out _); _pending.TryRemove(uid, out _);
_requestService.RemovePendingRequestByUid(uid); _requestService.RemovePendingRequestByUid(uid);
_ = _requestService.SendAcceptNotifyAsync(uid); _ = _requestService.SendAcceptNotifyAsync(uid);
_notificationTracker.Remove(NotificationCategory.AutoDetect, uid);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -1,158 +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)
{
return await SetVisibilityAsync(gid, visible, null, null, null, null, null, ct).ConfigureAwait(false);
}
public async Task<bool> SetVisibilityAsync(string gid, bool visible, int? displayDurationHours,
int[]? activeWeekdays, TimeSpan? timeStartLocal, TimeSpan? timeEndLocal, string? timeZone,
CancellationToken ct)
{
try
{
var request = new SyncshellDiscoveryVisibilityRequestDto
{
GID = gid,
AutoDetectVisible = visible,
DisplayDurationHours = displayDurationHours,
ActiveWeekdays = activeWeekdays,
TimeStartLocal = timeStartLocal.HasValue ? new DateTime(timeStartLocal.Value.Ticks).ToString("HH:mm") : null,
TimeEndLocal = timeEndLocal.HasValue ? new DateTime(timeEndLocal.Value.Ticks).ToString("HH:mm") : null,
TimeZone = timeZone,
};
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;
}
}

View File

@@ -11,7 +11,6 @@ using MareSynchronos.Services.CharaData.Models;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Files; using MareSynchronos.WebAPI.Files;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Threading;
namespace MareSynchronos.Services; 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) internal async Task<List<string>> UploadFiles(List<string> fileList, ValueProgress<string> uploadProgress, CancellationToken token)
{ {
return await _fileUploadManager.UploadFiles(fileList, uploadProgress, token).ConfigureAwait(false); return await _fileUploadManager.UploadFiles(fileList, uploadProgress, token).ConfigureAwait(false);

View File

@@ -13,9 +13,7 @@ using MareSynchronos.Utils;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.IO;
using System.Text; using System.Text;
using System.Threading;
namespace MareSynchronos.Services; namespace MareSynchronos.Services;
@@ -459,14 +457,6 @@ public sealed partial class CharaDataManager : DisposableMediatorSubscriberBase
LoadedMcdfHeader = _fileHandler.LoadCharaFileHeader(filePath); 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) public void McdfApplyToTarget(string charaName)
{ {
if (LoadedMcdfHeader == null || !LoadedMcdfHeader.IsCompletedSuccessfully) return; if (LoadedMcdfHeader == null || !LoadedMcdfHeader.IsCompletedSuccessfully) return;

View File

@@ -28,7 +28,7 @@ public sealed class CharaDataNearbyManager : DisposableMediatorSubscriberBase
private Task? _filterEntriesRunningTask; private Task? _filterEntriesRunningTask;
private (Guid VfxId, PoseEntryExtended Pose)? _hoveredVfx = null; private (Guid VfxId, PoseEntryExtended Pose)? _hoveredVfx = null;
private DateTime _lastExecutionTime = DateTime.UtcNow; 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, public CharaDataNearbyManager(ILogger<CharaDataNearbyManager> logger, MareMediator mediator,
DalamudUtilService dalamudUtilService, VfxSpawnManager vfxSpawnManager, DalamudUtilService dalamudUtilService, VfxSpawnManager vfxSpawnManager,
ServerConfigurationManager serverConfigurationManager, ServerConfigurationManager serverConfigurationManager,

View File

@@ -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);
}
}

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Runtime.InteropServices;
using Lumina.Data.Files; using Lumina.Data.Files;
using MareSynchronos.API.Data; using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Enum; using MareSynchronos.API.Data.Enum;
@@ -17,8 +16,9 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
{ {
private readonly FileCacheManager _fileCacheManager; private readonly FileCacheManager _fileCacheManager;
private readonly XivDataAnalyzer _xivDataAnalyzer; private readonly XivDataAnalyzer _xivDataAnalyzer;
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
private CancellationTokenSource? _analysisCts; private CancellationTokenSource? _analysisCts;
private CancellationTokenSource? _baseAnalysisCts = new(); private CancellationTokenSource _baseAnalysisCts = new();
private string _lastDataHash = string.Empty; private string _lastDataHash = string.Empty;
private CharacterAnalysisSummary _previousSummary = CharacterAnalysisSummary.Empty; private CharacterAnalysisSummary _previousSummary = CharacterAnalysisSummary.Empty;
private DateTime _lastAutoAnalysis = DateTime.MinValue; private DateTime _lastAutoAnalysis = DateTime.MinValue;
@@ -30,15 +30,14 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
private const long NotificationTriangleThreshold = 150_000; private const long NotificationTriangleThreshold = 150_000;
private bool _sizeWarningShown; private bool _sizeWarningShown;
private bool _triangleWarningShown; private bool _triangleWarningShown;
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
public CharacterAnalyzer(ILogger<CharacterAnalyzer> logger, MareMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer, PlayerPerformanceConfigService playerPerformanceConfigService) public CharacterAnalyzer(ILogger<CharacterAnalyzer> logger, MareMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer, PlayerPerformanceConfigService playerPerformanceConfigService)
: base(logger, mediator) : base(logger, mediator)
{ {
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) => Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) =>
{ {
var tokenSource = EnsureFreshCts(ref _baseAnalysisCts); _baseAnalysisCts = _baseAnalysisCts.CancelRecreate();
var token = tokenSource.Token; var token = _baseAnalysisCts.Token;
_ = BaseAnalysis(msg.CharacterData, token); _ = BaseAnalysis(msg.CharacterData, token);
}); });
_fileCacheManager = fileCacheManager; _fileCacheManager = fileCacheManager;
@@ -55,15 +54,17 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
public void CancelAnalyze() public void CancelAnalyze()
{ {
CancelAndDispose(ref _analysisCts); _analysisCts?.CancelDispose();
_analysisCts = null;
} }
public async Task ComputeAnalysis(bool print = true, bool recalculate = false) public async Task ComputeAnalysis(bool print = true, bool recalculate = false)
{ {
Logger.LogDebug("=== Calculating Character Analysis ==="); Logger.LogDebug("=== Calculating Character Analysis ===");
var analysisCts = EnsureFreshCts(ref _analysisCts); _analysisCts = _analysisCts?.CancelRecreate() ?? new();
var cancelToken = analysisCts.Token;
var cancelToken = _analysisCts.Token;
var allFiles = LastAnalysis.SelectMany(v => v.Value.Select(d => d.Value)).ToList(); var allFiles = LastAnalysis.SelectMany(v => v.Value.Select(d => d.Value)).ToList();
if (allFiles.Exists(c => !c.IsComputed || recalculate)) if (allFiles.Exists(c => !c.IsComputed || recalculate))
@@ -105,7 +106,8 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
LastCompletedAnalysis = DateTime.UtcNow; LastCompletedAnalysis = DateTime.UtcNow;
} }
CancelAndDispose(ref _analysisCts); _analysisCts.CancelDispose();
_analysisCts = null;
if (print) PrintAnalysis(); if (print) PrintAnalysis();
} }
@@ -116,8 +118,8 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
if (!disposing) return; if (!disposing) return;
CancelAndDispose(ref _analysisCts); _analysisCts?.CancelDispose();
CancelAndDispose(ref _baseAnalysisCts); _baseAnalysisCts.CancelDispose();
} }
private async Task BaseAnalysis(CharacterData charaData, CancellationToken token) 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 readonly record struct CharacterAnalysisSummary(int TotalFiles, long TotalOriginalSize, long TotalCompressedSize, long TotalTriangles, bool HasUncomputedEntries)
{ {
public static CharacterAnalysisSummary Empty => new(); 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;
}
} }

View File

@@ -8,7 +8,6 @@ using Dalamud.Plugin.Services;
using MareSynchronos.API.Data; using MareSynchronos.API.Data;
using MareSynchronos.Interop; using MareSynchronos.Interop;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.MareConfiguration.Models; using MareSynchronos.MareConfiguration.Models;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
@@ -39,7 +38,6 @@ public class ChatService : DisposableMediatorSubscriberBase
private CancellationTokenSource? _typingCts; private CancellationTokenSource? _typingCts;
private bool _isTypingAnnounced; private bool _isTypingAnnounced;
private DateTime _lastTypingSent = DateTime.MinValue; private DateTime _lastTypingSent = DateTime.MinValue;
private TypingScope _lastScope = TypingScope.Unknown;
private static readonly TimeSpan TypingIdle = TimeSpan.FromSeconds(2); private static readonly TimeSpan TypingIdle = TimeSpan.FromSeconds(2);
private static readonly TimeSpan TypingResendInterval = TimeSpan.FromMilliseconds(750); private static readonly TimeSpan TypingResendInterval = TimeSpan.FromMilliseconds(750);
@@ -81,7 +79,7 @@ public class ChatService : DisposableMediatorSubscriberBase
if (_gameChatHooks.IsValueCreated) if (_gameChatHooks.IsValueCreated)
_gameChatHooks.Value!.Dispose(); _gameChatHooks.Value!.Dispose();
} }
public void NotifyTypingKeystroke(TypingScope scope) public void NotifyTypingKeystroke()
{ {
lock (_typingLock) lock (_typingLock)
{ {
@@ -90,12 +88,11 @@ public class ChatService : DisposableMediatorSubscriberBase
{ {
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
try { await _apiController.UserSetTypingState(true, scope).ConfigureAwait(false); } try { await _apiController.UserSetTypingState(true).ConfigureAwait(false); }
catch (Exception ex) { _logger.LogDebug(ex, "NotifyTypingKeystroke: failed to send typing=true"); } catch (Exception ex) { _logger.LogDebug(ex, "NotifyTypingKeystroke: failed to send typing=true"); }
}); });
_isTypingAnnounced = true; _isTypingAnnounced = true;
_lastTypingSent = now; _lastTypingSent = now;
_lastScope = scope;
} }
_typingCts?.Cancel(); _typingCts?.Cancel();
@@ -108,7 +105,7 @@ public class ChatService : DisposableMediatorSubscriberBase
try try
{ {
await Task.Delay(TypingIdle, token).ConfigureAwait(false); await Task.Delay(TypingIdle, token).ConfigureAwait(false);
await _apiController.UserSetTypingState(false, _lastScope).ConfigureAwait(false); await _apiController.UserSetTypingState(false).ConfigureAwait(false);
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
@@ -143,7 +140,7 @@ public class ChatService : DisposableMediatorSubscriberBase
{ {
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
try { await _apiController.UserSetTypingState(false, _lastScope).ConfigureAwait(false); } try { await _apiController.UserSetTypingState(false).ConfigureAwait(false); }
catch (Exception ex) { _logger.LogDebug(ex, "ClearTypingState: failed to send typing=false"); } catch (Exception ex) { _logger.LogDebug(ex, "ClearTypingState: failed to send typing=false"); }
}); });
_isTypingAnnounced = false; _isTypingAnnounced = false;

View File

@@ -10,8 +10,6 @@ using FFXIVClientStructs.FFXIV.Client.UI.Shell;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
using MareSynchronos.MareConfiguration;
using MareSynchronos.API.Data.Enum;
namespace MareSynchronos.Services; namespace MareSynchronos.Services;
@@ -26,18 +24,16 @@ public sealed class ChatTypingDetectionService : IDisposable
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly PairManager _pairManager; private readonly PairManager _pairManager;
private readonly IPartyList _partyList; private readonly IPartyList _partyList;
private readonly MareConfigService _configService;
private string _lastChatText = string.Empty; private string _lastChatText = string.Empty;
private bool _isTyping; private bool _isTyping;
private bool _notifyingRemote; private bool _notifyingRemote;
private bool _serverSupportWarnLogged; private bool _serverSupportWarnLogged;
private bool _remoteNotificationsEnabled; private bool _remoteNotificationsEnabled;
private bool _subscribed;
public ChatTypingDetectionService(ILogger<ChatTypingDetectionService> logger, IFramework framework, public ChatTypingDetectionService(ILogger<ChatTypingDetectionService> logger, IFramework framework,
IClientState clientState, IGameGui gameGui, ChatService chatService, PairManager pairManager, IPartyList partyList, IClientState clientState, IGameGui gameGui, ChatService chatService, PairManager pairManager, IPartyList partyList,
TypingIndicatorStateService typingStateService, ApiController apiController, MareConfigService configService) TypingIndicatorStateService typingStateService, ApiController apiController)
{ {
_logger = logger; _logger = logger;
_framework = framework; _framework = framework;
@@ -48,48 +44,15 @@ public sealed class ChatTypingDetectionService : IDisposable
_partyList = partyList; _partyList = partyList;
_typingStateService = typingStateService; _typingStateService = typingStateService;
_apiController = apiController; _apiController = apiController;
_configService = configService;
Subscribe(); _framework.Update += OnFrameworkUpdate;
_logger.LogInformation("ChatTypingDetectionService initialized"); _logger.LogInformation("ChatTypingDetectionService initialized");
} }
public void Dispose() public void Dispose()
{ {
Unsubscribe();
ResetTypingState();
}
public void SoftRestart()
{
try
{
_logger.LogInformation("TypingDetection: soft restarting");
Unsubscribe();
ResetTypingState();
_chatService.ClearTypingState();
_typingStateService.ClearAll();
Subscribe();
_logger.LogInformation("TypingDetection: soft restart completed");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "TypingDetection: soft restart failed");
}
}
private void Subscribe()
{
if (_subscribed) return;
_framework.Update += OnFrameworkUpdate;
_subscribed = true;
}
private void Unsubscribe()
{
if (!_subscribed) return;
_framework.Update -= OnFrameworkUpdate; _framework.Update -= OnFrameworkUpdate;
_subscribed = false; ResetTypingState();
} }
private void OnFrameworkUpdate(IFramework framework) private void OnFrameworkUpdate(IFramework framework)
@@ -102,13 +65,6 @@ public sealed class ChatTypingDetectionService : IDisposable
return; return;
} }
if (!_configService.Current.TypingIndicatorEnabled)
{
ResetTypingState();
_chatService.ClearTypingState();
return;
}
if (!TryGetChatInput(out var chatText) || string.IsNullOrEmpty(chatText)) if (!TryGetChatInput(out var chatText) || string.IsNullOrEmpty(chatText))
{ {
ResetTypingState(); ResetTypingState();
@@ -133,8 +89,7 @@ public sealed class ChatTypingDetectionService : IDisposable
{ {
if (notifyRemote) if (notifyRemote)
{ {
var scope = GetCurrentTypingScope(); _chatService.NotifyTypingKeystroke();
_chatService.NotifyTypingKeystroke(scope);
_notifyingRemote = true; _notifyingRemote = true;
} }
@@ -165,35 +120,6 @@ public sealed class ChatTypingDetectionService : IDisposable
_typingStateService.SetSelfTypingLocal(false); _typingStateService.SetSelfTypingLocal(false);
} }
private unsafe TypingScope GetCurrentTypingScope()
{
try
{
var shellModule = RaptureShellModule.Instance();
if (shellModule == null)
return TypingScope.Unknown;
var chatType = (XivChatType)shellModule->ChatType;
switch (chatType)
{
case XivChatType.Say:
case XivChatType.Shout:
case XivChatType.Yell:
return TypingScope.Proximity;
case XivChatType.Party:
return TypingScope.Party;
case XivChatType.CrossParty:
return TypingScope.CrossParty;
default:
return TypingScope.Unknown;
}
}
catch
{
return TypingScope.Unknown;
}
}
private static bool IsIgnoredCommand(string chatText) private static bool IsIgnoredCommand(string chatText)
{ {
if (string.IsNullOrWhiteSpace(chatText)) if (string.IsNullOrWhiteSpace(chatText))
@@ -220,11 +146,6 @@ public sealed class ChatTypingDetectionService : IDisposable
{ {
try try
{ {
if (!_configService.Current.TypingIndicatorEnabled)
{
return false;
}
var supportsTypingState = _apiController.SystemInfoDto.SupportsTypingState; var supportsTypingState = _apiController.SystemInfoDto.SupportsTypingState;
var connected = _apiController.IsConnected; var connected = _apiController.IsConnected;
if (!connected || !supportsTypingState) if (!connected || !supportsTypingState)

View File

@@ -16,26 +16,37 @@ namespace MareSynchronos.Services;
public class GuiHookService : DisposableMediatorSubscriberBase public class GuiHookService : DisposableMediatorSubscriberBase
{ {
private readonly ILogger<GuiHookService> _logger;
private readonly DalamudUtilService _dalamudUtil; private readonly DalamudUtilService _dalamudUtil;
private readonly MareConfigService _configService; private readonly MareConfigService _configService;
private readonly INamePlateGui _namePlateGui; private readonly INamePlateGui _namePlateGui;
private readonly IGameConfig _gameConfig; private readonly IGameConfig _gameConfig;
private readonly IPartyList _partyList; private readonly IPartyList _partyList;
private readonly PairManager _pairManager; 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 _isModified = false;
private bool _namePlateRoleColorsEnabled = false; private bool _namePlateRoleColorsEnabled = false;
public GuiHookService(ILogger<GuiHookService> logger, DalamudUtilService dalamudUtil, MareMediator mediator, MareConfigService configService, 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) : base(logger, mediator)
{ {
_logger = logger;
_dalamudUtil = dalamudUtil; _dalamudUtil = dalamudUtil;
_configService = configService; _configService = configService;
_namePlateGui = namePlateGui; _namePlateGui = namePlateGui;
_gameConfig = gameConfig; _gameConfig = gameConfig;
_partyList = partyList; _partyList = partyList;
_pairManager = pairManager; _pairManager = pairManager;
_apiController = apiController;
_clientState = clientState;
_typingStateService = typingStateService;
_namePlateGui.OnNamePlateUpdate += OnNamePlateUpdate; _namePlateGui.OnNamePlateUpdate += OnNamePlateUpdate;
_namePlateGui.RequestRedraw(); _namePlateGui.RequestRedraw();
@@ -49,13 +60,18 @@ public class GuiHookService : DisposableMediatorSubscriberBase
public void RequestRedraw(bool force = false) public void RequestRedraw(bool force = false)
{ {
var useColors = _configService.Current.UseNameColors; var useColors = _configService.Current.UseNameColors;
var showTyping = _configService.Current.TypingIndicatorShowOnNameplates;
if (!useColors) if (!useColors && !showTyping)
{ {
if (!_isModified && !force) if (!_isModified && !force)
return; return;
_isModified = false; _isModified = false;
} }
else if (!useColors)
{
_isModified = false;
}
_ = Task.Run(async () => { _ = Task.Run(async () => {
await _dalamudUtil.RunOnFrameworkThread(() => _namePlateGui.RequestRedraw()).ConfigureAwait(false); await _dalamudUtil.RunOnFrameworkThread(() => _namePlateGui.RequestRedraw()).ConfigureAwait(false);
@@ -75,7 +91,8 @@ public class GuiHookService : DisposableMediatorSubscriberBase
private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers) private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
{ {
var applyColors = _configService.Current.UseNameColors; var applyColors = _configService.Current.UseNameColors;
if (!applyColors) var showTypingIndicator = _configService.Current.TypingIndicatorShowOnNameplates;
if (!applyColors && !showTypingIndicator)
return; return;
var visibleUsers = _pairManager.GetOnlineUserPairs().Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue); 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) for (int i = 0; i < _partyList.Count; ++i)
partyMembers[i] = _partyList[i]?.GameObject?.Address ?? nint.MaxValue; 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) foreach (var handler in handlers)
{ {
if (handler != null && visibleUsersIds.Contains(handler.GameObjectId)) if (handler != null && visibleUsersIds.Contains(handler.GameObjectId))

View File

@@ -2,6 +2,7 @@
using MareSynchronos.API.Data.Comparer; using MareSynchronos.API.Data.Comparer;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@@ -14,6 +15,7 @@ public class MareProfileManager : MediatorSubscriberBase
private const string _nsfw = "Profile not displayed - NSFW"; private const string _nsfw = "Profile not displayed - NSFW";
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly MareConfigService _mareConfigService; private readonly MareConfigService _mareConfigService;
private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly ConcurrentDictionary<UserData, MareProfileData> _mareProfiles = new(UserDataComparer.Instance); private readonly ConcurrentDictionary<UserData, MareProfileData> _mareProfiles = new(UserDataComparer.Instance);
private readonly MareProfileData _defaultProfileData = new(IsFlagged: false, IsNSFW: false, string.Empty, _noDescription); 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); private readonly MareProfileData _nsfwProfileData = new(IsFlagged: false, IsNSFW: false, string.Empty, _nsfw);
public MareProfileManager(ILogger<MareProfileManager> logger, MareConfigService mareConfigService, 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; _mareConfigService = mareConfigService;
_apiController = apiController; _apiController = apiController;
_serverConfigurationManager = serverConfigurationManager;
Mediator.Subscribe<ClearProfileDataMessage>(this, (msg) => Mediator.Subscribe<ClearProfileDataMessage>(this, (msg) =>
{ {

View File

@@ -7,7 +7,7 @@ using System.Text;
namespace MareSynchronos.Services.Mediator; namespace MareSynchronos.Services.Mediator;
public sealed class MareMediator : IHostedService, IDisposable public sealed class MareMediator : IHostedService
{ {
private readonly Lock _addRemoveLock = new(); private readonly Lock _addRemoveLock = new();
private readonly ConcurrentDictionary<SubscriberAction, DateTime> _lastErrorTime = []; 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 public void SubscribeKeyed<T>(IMediatorSubscriber subscriber, string key, Action<T> action) where T : MessageBase
{ {
lock (_addRemoveLock) lock (_addRemoveLock)

View File

@@ -115,15 +115,12 @@ public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMa
public record DiscoveryListUpdated(List<NearbyEntry> Entries) : MessageBase; public record DiscoveryListUpdated(List<NearbyEntry> Entries) : MessageBase;
public record NearbyDetectionToggled(bool Enabled) : MessageBase; public record NearbyDetectionToggled(bool Enabled) : MessageBase;
public record AllowPairRequestsToggled(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 ManualPairInviteMessage(string SourceUid, string SourceAlias, string TargetUid, string? DisplayName, string InviteId) : MessageBase;
public record ApplyDefaultPairPermissionsMessage(UserPairDto Pair) : MessageBase; public record ApplyDefaultPairPermissionsMessage(UserPairDto Pair) : MessageBase;
public record ApplyDefaultGroupPermissionsMessage(GroupPairFullInfoDto GroupPair) : MessageBase; public record ApplyDefaultGroupPermissionsMessage(GroupPairFullInfoDto GroupPair) : MessageBase;
public record ApplyDefaultsToAllSyncsMessage(string? Context = null, bool? Disabled = null) : 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 PairSyncOverrideChanged(string Uid, bool? DisableSounds, bool? DisableAnimations, bool? DisableVfx) : MessageBase;
public record GroupSyncOverrideChanged(string Gid, 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); public record PluginChangeMessage(string InternalName, Version Version, bool IsLoaded) : KeyedMessage(InternalName);
#pragma warning restore S2094 #pragma warning restore S2094

View File

@@ -1,144 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MareSynchronos.Services.Mediator;
using MareSynchronos.MareConfiguration;
using MareSynchronos.MareConfiguration.Configurations;
using MareSynchronos.MareConfiguration.Models;
namespace MareSynchronos.Services.Notifications;
public enum NotificationCategory
{
AutoDetect,
Syncshell,
}
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 static NotificationEntry SyncshellPublic(string gid, string aliasOrGid)
=> new(NotificationCategory.Syncshell, gid, $"Syncshell publique: {aliasOrGid}", "La Syncshell est désormais visible via AutoDetect.", DateTime.UtcNow);
public static NotificationEntry SyncshellNotPublic(string gid, string aliasOrGid)
=> new(NotificationCategory.Syncshell, gid, $"Syncshell non publique: {aliasOrGid}", "La Syncshell n'est plus visible via AutoDetect.", DateTime.UtcNow);
}
public sealed class NotificationTracker
{
private const int MaxStored = 100;
private readonly MareMediator _mediator;
private readonly NotificationsConfigService _configService;
private readonly Dictionary<(NotificationCategory Category, string Id), NotificationEntry> _entries = new();
private readonly object _lock = new();
public NotificationTracker(MareMediator mediator, NotificationsConfigService configService)
{
_mediator = mediator;
_configService = configService;
LoadPersisted();
PublishState();
}
public void Upsert(NotificationEntry entry)
{
lock (_lock)
{
_entries[(entry.Category, entry.Id)] = entry;
TrimIfNecessary_NoLock();
Persist_NoLock();
}
PublishState();
}
public void Remove(NotificationCategory category, string id)
{
lock (_lock)
{
_entries.Remove((category, id));
Persist_NoLock();
}
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));
}
private void LoadPersisted()
{
try
{
var list = _configService.Current.Notifications ?? new List<StoredNotification>();
foreach (var s in list)
{
if (!Enum.TryParse<NotificationCategory>(s.Category, out var cat)) continue;
var entry = new NotificationEntry(cat, s.Id, s.Title, s.Description, s.CreatedAtUtc);
_entries[(entry.Category, entry.Id)] = entry;
}
TrimIfNecessary_NoLock();
}
catch
{
// ignore load errors, start empty
}
}
private void Persist_NoLock()
{
try
{
var stored = _entries.Values
.OrderBy(e => e.CreatedAt)
.Select(e => new StoredNotification
{
Category = e.Category.ToString(),
Id = e.Id,
Title = e.Title,
Description = e.Description,
CreatedAtUtc = e.CreatedAt
})
.ToList();
_configService.Current.Notifications = stored;
_configService.Save();
}
catch
{
// ignore persistence errors
}
}
private void TrimIfNecessary_NoLock()
{
if (_entries.Count <= MaxStored) return;
foreach (var kv in _entries.Values.OrderByDescending(v => v.CreatedAt).Skip(MaxStored).ToList())
{
_entries.Remove((kv.Category, kv.Id));
}
}
}

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Linq;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
@@ -18,29 +17,22 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
private readonly INotificationManager _notificationManager; private readonly INotificationManager _notificationManager;
private readonly IChatGui _chatGui; private readonly IChatGui _chatGui;
private readonly MareConfigService _configurationService; private readonly MareConfigService _configurationService;
private readonly Services.Notifications.NotificationTracker _notificationTracker;
private readonly PlayerData.Pairs.PairManager _pairManager;
public NotificationService(ILogger<NotificationService> logger, MareMediator mediator, public NotificationService(ILogger<NotificationService> logger, MareMediator mediator,
DalamudUtilService dalamudUtilService, DalamudUtilService dalamudUtilService,
INotificationManager notificationManager, INotificationManager notificationManager,
IChatGui chatGui, MareConfigService configurationService, IChatGui chatGui, MareConfigService configurationService) : base(logger, mediator)
Services.Notifications.NotificationTracker notificationTracker,
PlayerData.Pairs.PairManager pairManager) : base(logger, mediator)
{ {
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
_notificationManager = notificationManager; _notificationManager = notificationManager;
_chatGui = chatGui; _chatGui = chatGui;
_configurationService = configurationService; _configurationService = configurationService;
_notificationTracker = notificationTracker;
_pairManager = pairManager;
} }
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
Mediator.Subscribe<NotificationMessage>(this, ShowNotification); Mediator.Subscribe<NotificationMessage>(this, ShowNotification);
Mediator.Subscribe<DualNotificationMessage>(this, ShowDualNotification); Mediator.Subscribe<DualNotificationMessage>(this, ShowDualNotification);
Mediator.Subscribe<Services.Mediator.SyncshellAutoDetectStateChanged>(this, OnSyncshellAutoDetectStateChanged);
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -121,31 +113,6 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
ShowChat(baseMsg); ShowChat(baseMsg);
} }
private void OnSyncshellAutoDetectStateChanged(SyncshellAutoDetectStateChanged msg)
{
try
{
if (msg.Visible) return; // only handle transition to not visible
var gid = msg.Gid;
// Try to resolve alias from PairManager snapshot; fallback to gid
var alias = _pairManager.Groups.Values.FirstOrDefault(g => string.Equals(g.GID, gid, StringComparison.OrdinalIgnoreCase))?.GroupAliasOrGID ?? gid;
var title = $"Syncshell non publique: {alias}";
var message = "La Syncshell n'est plus visible via AutoDetect.";
// Show toast + chat
ShowDualNotification(new DualNotificationMessage(title, message, NotificationType.Info, TimeSpan.FromSeconds(4)));
// Persist into notification center
_notificationTracker.Upsert(Services.Notifications.NotificationEntry.SyncshellNotPublic(gid, alias));
}
catch
{
// ignore failures
}
}
private static bool ShouldForceChat(NotificationMessage msg, out bool appendInstruction) private static bool ShouldForceChat(NotificationMessage msg, out bool appendInstruction)
{ {
appendInstruction = false; appendInstruction = false;

View File

@@ -1,5 +1,4 @@
using System; using MareSynchronos.API.Data;
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Enum; using MareSynchronos.API.Data.Enum;
using MareSynchronos.FileCache; using MareSynchronos.FileCache;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
@@ -15,7 +14,7 @@ public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
private readonly FileCacheManager _fileCacheManager; private readonly FileCacheManager _fileCacheManager;
private readonly XivDataAnalyzer _xivDataAnalyzer; private readonly XivDataAnalyzer _xivDataAnalyzer;
private CancellationTokenSource? _analysisCts; private CancellationTokenSource? _analysisCts;
private CancellationTokenSource? _baseAnalysisCts = new(); private CancellationTokenSource _baseAnalysisCts = new();
private string _lastDataHash = string.Empty; private string _lastDataHash = string.Empty;
public PairAnalyzer(ILogger<PairAnalyzer> logger, Pair pair, MareMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer) public PairAnalyzer(ILogger<PairAnalyzer> logger, Pair pair, MareMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer)
@@ -25,8 +24,8 @@ public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
#if DEBUG #if DEBUG
Mediator.SubscribeKeyed<PairDataAppliedMessage>(this, pair.UserData.UID, (msg) => Mediator.SubscribeKeyed<PairDataAppliedMessage>(this, pair.UserData.UID, (msg) =>
{ {
var tokenSource = EnsureFreshCts(ref _baseAnalysisCts); _baseAnalysisCts = _baseAnalysisCts.CancelRecreate();
var token = tokenSource.Token; var token = _baseAnalysisCts.Token;
if (msg.CharacterData != null) if (msg.CharacterData != null)
{ {
_ = BaseAnalysis(msg.CharacterData, token); _ = BaseAnalysis(msg.CharacterData, token);
@@ -57,15 +56,17 @@ public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
public void CancelAnalyze() public void CancelAnalyze()
{ {
CancelAndDispose(ref _analysisCts); _analysisCts?.CancelDispose();
_analysisCts = null;
} }
public async Task ComputeAnalysis(bool print = true, bool recalculate = false) public async Task ComputeAnalysis(bool print = true, bool recalculate = false)
{ {
Logger.LogDebug("=== Calculating Character Analysis ==="); Logger.LogDebug("=== Calculating Character Analysis ===");
var analysisCts = EnsureFreshCts(ref _analysisCts); _analysisCts = _analysisCts?.CancelRecreate() ?? new();
var cancelToken = analysisCts.Token;
var cancelToken = _analysisCts.Token;
var allFiles = LastAnalysis.SelectMany(v => v.Value.Select(d => d.Value)).ToList(); var allFiles = LastAnalysis.SelectMany(v => v.Value.Select(d => d.Value)).ToList();
if (allFiles.Exists(c => !c.IsComputed || recalculate)) if (allFiles.Exists(c => !c.IsComputed || recalculate))
@@ -101,7 +102,8 @@ public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
LastPlayerName = Pair.PlayerName ?? string.Empty; LastPlayerName = Pair.PlayerName ?? string.Empty;
Mediator.Publish(new PairDataAnalyzedMessage(Pair.UserData.UID)); Mediator.Publish(new PairDataAnalyzedMessage(Pair.UserData.UID));
CancelAndDispose(ref _analysisCts); _analysisCts.CancelDispose();
_analysisCts = null;
if (print) PrintAnalysis(); if (print) PrintAnalysis();
} }
@@ -112,8 +114,8 @@ public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
if (!disposing) return; if (!disposing) return;
CancelAndDispose(ref _analysisCts); _analysisCts?.CancelDispose();
CancelAndDispose(ref _baseAnalysisCts); _baseAnalysisCts.CancelDispose();
} }
private async Task BaseAnalysis(CharacterData charaData, CancellationToken token) 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.OriginalSize))),
UiSharedService.ByteToString(LastAnalysis.Values.Sum(c => c.Values.Sum(v => v.CompressedSize)))); 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;
}
} }

View File

@@ -21,6 +21,7 @@ public class PartyListTypingService : DisposableMediatorSubscriberBase
private readonly PairManager _pairManager; private readonly PairManager _pairManager;
private readonly TypingIndicatorStateService _typingStateService; private readonly TypingIndicatorStateService _typingStateService;
private static readonly TimeSpan TypingDisplayTime = TimeSpan.FromSeconds(2); private static readonly TimeSpan TypingDisplayTime = TimeSpan.FromSeconds(2);
private static readonly TimeSpan TypingDisplayDelay = TimeSpan.FromMilliseconds(500);
private static readonly TimeSpan TypingDisplayFade = TypingDisplayTime; private static readonly TimeSpan TypingDisplayFade = TypingDisplayTime;
public PartyListTypingService(ILogger<PartyListTypingService> logger, public PartyListTypingService(ILogger<PartyListTypingService> logger,
@@ -41,8 +42,8 @@ public class PartyListTypingService : DisposableMediatorSubscriberBase
public void Draw() public void Draw()
{ {
if (!_configService.Current.TypingIndicatorEnabled) return;
if (!_configService.Current.TypingIndicatorShowOnPartyList) return; if (!_configService.Current.TypingIndicatorShowOnPartyList) return;
// Build map of visible users by AliasOrUID -> UID (case-insensitive)
var visibleByAlias = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var visibleByAlias = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
try try
{ {

View File

@@ -23,6 +23,7 @@ public class PlayerPerformanceService : DisposableMediatorSubscriberBase
private readonly MareMediator _mediator; private readonly MareMediator _mediator;
private readonly ServerConfigurationManager _serverConfigurationManager; private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
private readonly Dictionary<string, bool> _warnedForPlayers = new(StringComparer.Ordinal);
public PlayerPerformanceService(ILogger<PlayerPerformanceService> logger, MareMediator mediator, public PlayerPerformanceService(ILogger<PlayerPerformanceService> logger, MareMediator mediator,
ServerConfigurationManager serverConfigurationManager, ServerConfigurationManager serverConfigurationManager,

View File

@@ -5,27 +5,24 @@ using MareSynchronos.Services.Mediator;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MareSynchronos.API.Data; using MareSynchronos.API.Data;
using MareSynchronos.MareConfiguration;
namespace MareSynchronos.Services; namespace MareSynchronos.Services;
public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposable public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposable
{ {
private sealed record TypingEntry(UserData User, DateTime FirstSeen, DateTime LastUpdate, MareSynchronos.API.Data.Enum.TypingScope Scope); private sealed record TypingEntry(UserData User, DateTime FirstSeen, DateTime LastUpdate);
private readonly ConcurrentDictionary<string, TypingEntry> _typingUsers = new(StringComparer.Ordinal); private readonly ConcurrentDictionary<string, TypingEntry> _typingUsers = new(StringComparer.Ordinal);
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly ILogger<TypingIndicatorStateService> _logger; private readonly ILogger<TypingIndicatorStateService> _logger;
private readonly MareConfigService _configService;
private DateTime _selfTypingLast = DateTime.MinValue; private DateTime _selfTypingLast = DateTime.MinValue;
private DateTime _selfTypingStart = DateTime.MinValue; private DateTime _selfTypingStart = DateTime.MinValue;
private bool _selfTypingActive; private bool _selfTypingActive;
public TypingIndicatorStateService(ILogger<TypingIndicatorStateService> logger, MareMediator mediator, ApiController apiController, MareConfigService configService) public TypingIndicatorStateService(ILogger<TypingIndicatorStateService> logger, MareMediator mediator, ApiController apiController)
{ {
_logger = logger; _logger = logger;
_apiController = apiController; _apiController = apiController;
_configService = configService;
Mediator = mediator; Mediator = mediator;
mediator.Subscribe<UserTypingStateMessage>(this, OnTypingState); mediator.Subscribe<UserTypingStateMessage>(this, OnTypingState);
@@ -54,19 +51,8 @@ public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposab
_selfTypingActive = isTyping; _selfTypingActive = isTyping;
} }
public void ClearAll()
{
_typingUsers.Clear();
_selfTypingActive = false;
_selfTypingStart = DateTime.MinValue;
_selfTypingLast = DateTime.MinValue;
_logger.LogDebug("TypingIndicatorStateService: cleared all typing state");
}
private void OnTypingState(UserTypingStateMessage msg) private void OnTypingState(UserTypingStateMessage msg)
{ {
if (!_configService.Current.TypingIndicatorEnabled)
return;
var uid = msg.Typing.User.UID; var uid = msg.Typing.User.UID;
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
@@ -88,8 +74,8 @@ public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposab
else if (msg.Typing.IsTyping) else if (msg.Typing.IsTyping)
{ {
_typingUsers.AddOrUpdate(uid, _typingUsers.AddOrUpdate(uid,
_ => new TypingEntry(msg.Typing.User, now, now, msg.Typing.Scope), _ => new TypingEntry(msg.Typing.User, now, now),
(_, existing) => new TypingEntry(msg.Typing.User, existing.FirstSeen, now, msg.Typing.Scope)); (_, existing) => new TypingEntry(msg.Typing.User, existing.FirstSeen, now));
} }
else else
{ {
@@ -115,7 +101,7 @@ public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposab
return true; return true;
} }
public IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate, MareSynchronos.API.Data.Enum.TypingScope Scope)> GetActiveTypers(TimeSpan maxAge) public IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate)> GetActiveTypers(TimeSpan maxAge)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
foreach (var kvp in _typingUsers.ToArray()) foreach (var kvp in _typingUsers.ToArray())
@@ -126,6 +112,6 @@ public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposab
} }
} }
return _typingUsers.ToDictionary(k => k.Key, v => (v.Value.User, v.Value.FirstSeen, v.Value.LastUpdate, v.Value.Scope), StringComparer.Ordinal); return _typingUsers.ToDictionary(k => k.Key, v => (v.Value.User, v.Value.FirstSeen, v.Value.LastUpdate), StringComparer.Ordinal);
} }
} }

View File

@@ -1,9 +1,7 @@
using MareSynchronos.API.Dto.Group; using MareSynchronos.API.Dto.Group;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services.AutoDetect;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Services.Notifications;
using MareSynchronos.UI; using MareSynchronos.UI;
using MareSynchronos.UI.Components.Popup; using MareSynchronos.UI.Components.Popup;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
@@ -21,35 +19,31 @@ public class UiFactory
private readonly ServerConfigurationManager _serverConfigManager; private readonly ServerConfigurationManager _serverConfigManager;
private readonly MareProfileManager _mareProfileManager; private readonly MareProfileManager _mareProfileManager;
private readonly PerformanceCollectorService _performanceCollectorService; private readonly PerformanceCollectorService _performanceCollectorService;
private readonly SyncshellDiscoveryService _syncshellDiscoveryService;
private readonly NotificationTracker _notificationTracker;
public UiFactory(ILoggerFactory loggerFactory, MareMediator mareMediator, ApiController apiController, 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, NotificationTracker notificationTracker) MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService)
{ {
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_mareMediator = mareMediator; _mareMediator = mareMediator;
_apiController = apiController; _apiController = apiController;
_uiSharedService = uiSharedService; _uiSharedService = uiSharedService;
_pairManager = pairManager; _pairManager = pairManager;
_syncshellDiscoveryService = syncshellDiscoveryService;
_serverConfigManager = serverConfigManager; _serverConfigManager = serverConfigManager;
_mareProfileManager = mareProfileManager; _mareProfileManager = mareProfileManager;
_performanceCollectorService = performanceCollectorService; _performanceCollectorService = performanceCollectorService;
_notificationTracker = notificationTracker;
} }
public SyncshellAdminUI CreateSyncshellAdminUi(GroupFullInfoDto dto) public SyncshellAdminUI CreateSyncshellAdminUi(GroupFullInfoDto dto)
{ {
return new SyncshellAdminUI(_loggerFactory.CreateLogger<SyncshellAdminUI>(), _mareMediator, return new SyncshellAdminUI(_loggerFactory.CreateLogger<SyncshellAdminUI>(), _mareMediator,
_apiController, _uiSharedService, _pairManager, _syncshellDiscoveryService, dto, _performanceCollectorService, _notificationTracker); _apiController, _uiSharedService, _pairManager, dto, _performanceCollectorService);
} }
public StandaloneProfileUi CreateStandaloneProfileUi(Pair pair) public StandaloneProfileUi CreateStandaloneProfileUi(Pair pair)
{ {
return new StandaloneProfileUi(_loggerFactory.CreateLogger<StandaloneProfileUi>(), _mareMediator, return new StandaloneProfileUi(_loggerFactory.CreateLogger<StandaloneProfileUi>(), _mareMediator,
_uiSharedService, _serverConfigManager, _mareProfileManager, pair, _performanceCollectorService); _uiSharedService, _serverConfigManager, _mareProfileManager, _pairManager, pair, _performanceCollectorService);
} }
public PermissionWindowUI CreatePermissionPopupUi(Pair pair) public PermissionWindowUI CreatePermissionPopupUi(Pair pair)

View File

@@ -1,13 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using MareSynchronos.API.Dto.Group; using Dalamud.Plugin.Services;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
@@ -25,35 +25,25 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
{ {
private readonly MareConfigService _configService; private readonly MareConfigService _configService;
private readonly DalamudUtilService _dalamud; private readonly DalamudUtilService _dalamud;
private readonly IObjectTable _objectTable;
private readonly AutoDetectRequestService _requestService; private readonly AutoDetectRequestService _requestService;
private readonly NearbyDiscoveryService _discoveryService;
private readonly NearbyPendingService _pendingService; private readonly NearbyPendingService _pendingService;
private readonly PairManager _pairManager; 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 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, public AutoDetectUi(ILogger<AutoDetectUi> logger, MareMediator mediator,
MareConfigService configService, DalamudUtilService dalamudUtilService, MareConfigService configService, DalamudUtilService dalamudUtilService, IObjectTable objectTable,
AutoDetectRequestService requestService, NearbyPendingService pendingService, PairManager pairManager, AutoDetectRequestService requestService, NearbyPendingService pendingService, PairManager pairManager,
NearbyDiscoveryService discoveryService, SyncshellDiscoveryService syncshellDiscoveryService,
PerformanceCollectorService performanceCollectorService) PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "AutoDetect", performanceCollectorService) : base(logger, mediator, "AutoDetect", performanceCollectorService)
{ {
_configService = configService; _configService = configService;
_dalamud = dalamudUtilService; _dalamud = dalamudUtilService;
_objectTable = objectTable;
_requestService = requestService; _requestService = requestService;
_pendingService = pendingService; _pendingService = pendingService;
_pairManager = pairManager; _pairManager = pairManager;
_discoveryService = discoveryService;
_syncshellDiscoveryService = syncshellDiscoveryService;
Mediator.Subscribe<Services.Mediator.DiscoveryListUpdated>(this, OnDiscoveryUpdated);
Mediator.Subscribe<SyncshellDiscoveryUpdated>(this, OnSyncshellDiscoveryUpdated);
_entries = _discoveryService.SnapshotEntries();
Flags |= ImGuiWindowFlags.NoScrollbar; Flags |= ImGuiWindowFlags.NoScrollbar;
SizeConstraints = new WindowSizeConstraints() SizeConstraints = new WindowSizeConstraints()
@@ -90,12 +80,14 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
}); });
DrawStyledTab("Proximité", accent, inactiveTab, hoverTab, DrawNearbyTab); DrawStyledTab("Proximité", accent, inactiveTab, hoverTab, DrawNearbyTab);
DrawStyledTab("Syncshell", accent, inactiveTab, hoverTab, DrawSyncshellTab);
}
public void DrawInline() using (ImRaii.Disabled(true))
{ {
DrawInternal(); 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) 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); ImGuiHelpers.ScaledDummy(6);
var sourceEntries = _entries.Count > 0 ? _entries : _discoveryService.SnapshotEntries(); // Table header
var orderedEntries = sourceEntries if (ImGui.BeginTable("autodetect-nearby", 5, ImGuiTableFlags.SizingStretchProp))
.Where(e => e.IsMatch)
.OrderBy(e => float.IsNaN(e.Distance) ? float.MaxValue : e.Distance)
.ToList();
if (orderedEntries.Count == 0)
{ {
UiSharedService.ColorTextWrapped("Aucune présence UmbraSync détectée à proximité pour le moment.", ImGuiColors.DalamudGrey3); ImGui.TableSetupColumn("Name");
return; ImGui.TableSetupColumn("World");
}
if (!ImGui.BeginTable("autodetect-nearby", 5, ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.RowBg))
{
return;
}
ImGui.TableSetupColumn("Nom");
ImGui.TableSetupColumn("Monde");
ImGui.TableSetupColumn("Distance"); ImGui.TableSetupColumn("Distance");
ImGui.TableSetupColumn("Statut"); ImGui.TableSetupColumn("Status");
ImGui.TableSetupColumn("Action"); ImGui.TableSetupColumn("Action");
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
for (int i = 0; i < orderedEntries.Count; i++) var data = _entries.Count > 0 ? _entries.Where(e => e.IsMatch).ToList() : new List<Services.Mediator.NearbyEntry>();
foreach (var e in data)
{ {
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.TableNextColumn();
ImGui.TextUnformatted(displayName); ImGui.TextUnformatted(e.Name);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted(worldName); ImGui.TextUnformatted(e.WorldId == 0 ? "-" : (_dalamud.WorldData.Value.TryGetValue(e.WorldId, out var w) ? w : e.WorldId.ToString()));
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted(distanceText); ImGui.TextUnformatted(float.IsNaN(e.Distance) ? "-" : $"{e.Distance:0.0} m");
ImGui.TableNextColumn(); ImGui.TableNextColumn();
bool alreadyPaired = IsAlreadyPairedByUidOrAlias(e);
string status = alreadyPaired ? "Paired" : (string.IsNullOrEmpty(e.Token) ? "Requests disabled" : "On Umbra");
ImGui.TextUnformatted(status); ImGui.TextUnformatted(status);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
using (ImRaii.PushId(i)) using (ImRaii.Disabled(alreadyPaired || string.IsNullOrEmpty(e.Token)))
{ {
if (canRequest && !overDistance) if (alreadyPaired)
{ {
if (ImGui.Button("Envoyer invitation")) ImGui.Button($"Already sync##{e.Name}");
{
_ = _requestService.SendRequestAsync(entry.Token!, entry.Uid, entry.DisplayName);
} }
UiSharedService.AttachToolTip("Envoie une demande d'appairage via AutoDetect."); else if (string.IsNullOrEmpty(e.Token))
}
else
{ {
string reason = alreadyPaired ImGui.Button($"Requests disabled##{e.Name}");
? "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))
{
UiSharedService.AttachToolTip(reason);
} }
else if (ImGui.Button($"Send request##{e.Name}"))
{
_ = _requestService.SendRequestAsync(e.Token!, e.Uid, e.DisplayName);
} }
} }
} }
ImGui.EndTable(); ImGui.EndTable();
} }
private async Task JoinSyncshellAsync(SyncshellDiscoveryEntryDto entry)
{
if (!_syncshellJoinInFlight.Add(entry.GID))
{
return;
} }
try public override void OnOpen()
{ {
var joined = await _syncshellDiscoveryService.JoinAsync(entry.GID, CancellationToken.None).ConfigureAwait(false); base.OnOpen();
if (joined) Mediator.Subscribe<Services.Mediator.DiscoveryListUpdated>(this, OnDiscoveryUpdated);
{
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);
}
} }
private void DrawSyncshellTab() public override void OnClose()
{ {
if (!_syncshellInitialized) Mediator.Unsubscribe<Services.Mediator.DiscoveryListUpdated>(this);
{ base.OnClose();
_syncshellInitialized = true;
_ = _syncshellDiscoveryService.RefreshAsync(CancellationToken.None);
}
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();
} }
private void OnDiscoveryUpdated(Services.Mediator.DiscoveryListUpdated msg) private void OnDiscoveryUpdated(Services.Mediator.DiscoveryListUpdated msg)
@@ -451,11 +275,6 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
_entries = msg.Entries; _entries = msg.Entries;
} }
private void OnSyncshellDiscoveryUpdated(SyncshellDiscoveryUpdated msg)
{
_syncshellEntries = msg.Entries;
}
private bool IsAlreadyPairedByUidOrAlias(Services.Mediator.NearbyEntry e) private bool IsAlreadyPairedByUidOrAlias(Services.Mediator.NearbyEntry e)
{ {
try try

View File

@@ -169,6 +169,10 @@ public sealed class ChangelogUi : WindowMediatorSubscriberBase
{ {
return new List<ChangelogEntry> 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(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."), new("Fix l'affichage de la bulle dans la liste du groupe."),

View File

@@ -6,7 +6,7 @@ using System.Text;
namespace MareSynchronos.UI; namespace MareSynchronos.UI;
public sealed partial class CharaDataHubUi internal sealed partial class CharaDataHubUi
{ {
private static string GetAccessTypeString(AccessTypeDto dto) => dto switch private static string GetAccessTypeString(AccessTypeDto dto) => dto switch
{ {

View File

@@ -7,7 +7,7 @@ using MareSynchronos.Services.CharaData.Models;
namespace MareSynchronos.UI; namespace MareSynchronos.UI;
public sealed partial class CharaDataHubUi internal sealed partial class CharaDataHubUi
{ {
private string _joinLobbyId = string.Empty; private string _joinLobbyId = string.Empty;
private void DrawGposeTogether() private void DrawGposeTogether()
@@ -90,7 +90,7 @@ public sealed partial class CharaDataHubUi
if (!_uiSharedService.IsInGpose) if (!_uiSharedService.IsInGpose)
{ {
ImGuiHelpers.ScaledDummy(5); 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(); UiSharedService.DistanceSeparator();
ImGui.TextUnformatted("Users In Lobby"); ImGui.TextUnformatted("Users In Lobby");
@@ -104,7 +104,7 @@ public sealed partial class CharaDataHubUi
if (!_charaDataGposeTogetherManager.UsersInLobby.Any() && !string.IsNullOrEmpty(_charaDataGposeTogetherManager.CurrentGPoseLobbyId)) 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 else
{ {

View File

@@ -9,7 +9,7 @@ using System.Numerics;
namespace MareSynchronos.UI; namespace MareSynchronos.UI;
public sealed partial class CharaDataHubUi internal sealed partial class CharaDataHubUi
{ {
private void DrawEditCharaData(CharaDataFullExtendedDto? dataDto) private void DrawEditCharaData(CharaDataFullExtendedDto? dataDto)
{ {
@@ -18,7 +18,7 @@ public sealed partial class CharaDataHubUi
if (dataDto == null) if (dataDto == null)
{ {
ImGuiHelpers.ScaledDummy(5); 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; return;
} }
@@ -26,7 +26,7 @@ public sealed partial class CharaDataHubUi
if (updateDto == null) 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; return;
} }
@@ -61,7 +61,7 @@ public sealed partial class CharaDataHubUi
} }
if (_charaDataManager.CharaUpdateTask != null && !_charaDataManager.CharaUpdateTask.IsCompleted) 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) 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")) if ((!_charaDataManager.UploadTask?.IsCompleted ?? false) && _uiSharedService.IconTextButton(FontAwesomeIcon.Ban, "Cancel Upload"))
{ {
@@ -230,7 +230,7 @@ public sealed partial class CharaDataHubUi
ImGui.SameLine(); ImGui.SameLine();
ImGuiHelpers.ScaledDummy(20, 1); ImGuiHelpers.ScaledDummy(20, 1);
ImGui.SameLine(); 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"); ImGui.TextUnformatted("Contains Manipulation Data");
@@ -385,7 +385,7 @@ public sealed partial class CharaDataHubUi
} }
} }
ImGui.SameLine(); 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"); ImGui.TextUnformatted($"{poseCount}/{maxPoses} poses attached");
ImGuiHelpers.ScaledDummy(5); ImGuiHelpers.ScaledDummy(5);
@@ -395,7 +395,7 @@ public sealed partial class CharaDataHubUi
if (!_uiSharedService.IsInGpose && _charaDataManager.BrioAvailable) if (!_uiSharedService.IsInGpose && _charaDataManager.BrioAvailable)
{ {
ImGuiHelpers.ScaledDummy(5); 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); ImGuiHelpers.ScaledDummy(5);
} }
else if (!_charaDataManager.BrioAvailable) else if (!_charaDataManager.BrioAvailable)
@@ -414,7 +414,7 @@ public sealed partial class CharaDataHubUi
if (pose.Id == null) if (pose.Id == null)
{ {
ImGui.SameLine(50); 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."); 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) if (poseHasChanges)
{ {
ImGui.SameLine(50); 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."); UiSharedService.AttachToolTip("This pose has changes that have not been saved to the server yet.");
} }
ImGui.SameLine(75); ImGui.SameLine(75);
if (pose.Description == null && pose.WorldData == null && pose.PoseData == null) 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 else
{ {
@@ -544,8 +544,7 @@ public sealed partial class CharaDataHubUi
{ {
if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowCircleDown, "Download your Online Character Data from Server")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowCircleDown, "Download your Online Character Data from Server"))
{ {
var cts = EnsureFreshCts(ref _disposalCts); _ = _charaDataManager.GetAllData(_disposalCts.Token);
_ = _charaDataManager.GetAllData(cts.Token);
} }
} }
if (_charaDataManager.DataGetTimeoutTask != null && !_charaDataManager.DataGetTimeoutTask.IsCompleted) if (_charaDataManager.DataGetTimeoutTask != null && !_charaDataManager.DataGetTimeoutTask.IsCompleted)
@@ -586,7 +585,7 @@ public sealed partial class CharaDataHubUi
var idText = entry.FullId; var idText = entry.FullId;
if (uDto?.HasChanges ?? false) if (uDto?.HasChanges ?? false)
{ {
UiSharedService.ColorText(idText, UiSharedService.AccentColor); UiSharedService.ColorText(idText, ImGuiColors.DalamudYellow);
UiSharedService.AttachToolTip("This entry has unsaved changes"); UiSharedService.AttachToolTip("This entry has unsaved changes");
} }
else else
@@ -641,7 +640,7 @@ public sealed partial class CharaDataHubUi
FontAwesomeIcon eIcon = FontAwesomeIcon.None; FontAwesomeIcon eIcon = FontAwesomeIcon.None;
if (!Equals(DateTime.MaxValue, entry.ExpiryDate)) if (!Equals(DateTime.MaxValue, entry.ExpiryDate))
eIcon = FontAwesomeIcon.Clock; eIcon = FontAwesomeIcon.Clock;
_uiSharedService.IconText(eIcon, UiSharedService.AccentColor); _uiSharedService.IconText(eIcon, ImGuiColors.DalamudYellow);
if (ImGui.IsItemClicked()) SelectedDtoId = entry.Id; if (ImGui.IsItemClicked()) SelectedDtoId = entry.Id;
if (eIcon != FontAwesomeIcon.None) if (eIcon != FontAwesomeIcon.None)
{ {
@@ -655,8 +654,7 @@ public sealed partial class CharaDataHubUi
{ {
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Plus, "New Character Data Entry")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.Plus, "New Character Data Entry"))
{ {
var cts = EnsureFreshCts(ref _closalCts); _charaDataManager.CreateCharaDataEntry(_closalCts.Token);
_charaDataManager.CreateCharaDataEntry(cts.Token);
_selectNewEntry = true; _selectNewEntry = true;
} }
} }
@@ -677,13 +675,13 @@ public sealed partial class CharaDataHubUi
if (_charaDataManager.OwnCharaData.Count == _charaDataManager.MaxCreatableCharaData) if (_charaDataManager.OwnCharaData.Count == _charaDataManager.MaxCreatableCharaData)
{ {
ImGui.AlignTextToFramePadding(); 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) 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) else if (_charaDataManager.DataCreationTask != null && _charaDataManager.DataCreationTask.IsCompleted)
{ {

View File

@@ -7,7 +7,7 @@ using System.Numerics;
namespace MareSynchronos.UI; namespace MareSynchronos.UI;
public sealed partial class CharaDataHubUi internal partial class CharaDataHubUi
{ {
private void DrawNearbyPoses() private void DrawNearbyPoses()
{ {
@@ -86,7 +86,7 @@ public sealed partial class CharaDataHubUi
if (!_uiSharedService.IsInGpose) if (!_uiSharedService.IsInGpose)
{ {
ImGuiHelpers.ScaledDummy(5); 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); ImGuiHelpers.ScaledDummy(5);
} }
@@ -101,7 +101,7 @@ public sealed partial class CharaDataHubUi
using var indent = ImRaii.PushIndent(5f); using var indent = ImRaii.PushIndent(5f);
if (_charaDataNearbyManager.NearbyData.Count == 0) 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; bool wasAnythingHovered = false;
@@ -204,8 +204,7 @@ public sealed partial class CharaDataHubUi
{ {
if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowCircleDown, "Update Data Shared With You")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowCircleDown, "Update Data Shared With You"))
{ {
var cts = EnsureFreshCts(ref _disposalCts); _ = _charaDataManager.GetAllSharedData(_disposalCts.Token).ContinueWith(u => UpdateFilteredItems());
_ = _charaDataManager.GetAllSharedData(cts.Token).ContinueWith(u => UpdateFilteredItems());
} }
} }
if (_charaDataManager.GetSharedWithYouTimeoutTask != null && !_charaDataManager.GetSharedWithYouTimeoutTask.IsCompleted) if (_charaDataManager.GetSharedWithYouTimeoutTask != null && !_charaDataManager.GetSharedWithYouTimeoutTask.IsCompleted)

View File

@@ -1,6 +1,4 @@
using System; using Dalamud.Bindings.ImGui;
using System.Collections.Generic;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.ImGuiFileDialog;
@@ -17,13 +15,10 @@ using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.IO;
using System.Linq;
using System.Threading;
namespace MareSynchronos.UI; namespace MareSynchronos.UI;
public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase internal sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
{ {
private const int maxPoses = 10; private const int maxPoses = 10;
private readonly CharaDataManager _charaDataManager; private readonly CharaDataManager _charaDataManager;
@@ -35,10 +30,9 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
private readonly CharaDataGposeTogetherManager _charaDataGposeTogetherManager; private readonly CharaDataGposeTogetherManager _charaDataGposeTogetherManager;
private readonly ServerConfigurationManager _serverConfigurationManager; private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly McdfShareManager _mcdfShareManager; private CancellationTokenSource _closalCts = new();
private CancellationTokenSource? _closalCts = new();
private bool _disableUI = false; private bool _disableUI = false;
private CancellationTokenSource? _disposalCts = new(); private CancellationTokenSource _disposalCts = new();
private string _exportDescription = string.Empty; private string _exportDescription = string.Empty;
private string _filterCodeNote = string.Empty; private string _filterCodeNote = string.Empty;
private string _filterDescription = 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 _selectedSpecificUserIndividual = string.Empty;
private string _selectedSpecificGroupIndividual = string.Empty; private string _selectedSpecificGroupIndividual = string.Empty;
private string _sharedWithYouDescriptionFilter = string.Empty; private string _sharedWithYouDescriptionFilter = string.Empty;
@@ -88,21 +73,12 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
private string? _openComboHybridId = null; private string? _openComboHybridId = null;
private (string Id, string? Alias, string AliasOrId, string? Note)[]? _openComboHybridEntries = null; private (string Id, string? Alias, string AliasOrId, string? Note)[]? _openComboHybridEntries = null;
private bool _comboHybridUsedLastFrame = false; 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, public CharaDataHubUi(ILogger<CharaDataHubUi> logger, MareMediator mediator, PerformanceCollectorService performanceCollectorService,
CharaDataManager charaDataManager, CharaDataNearbyManager charaDataNearbyManager, CharaDataConfigService configService, CharaDataManager charaDataManager, CharaDataNearbyManager charaDataNearbyManager, CharaDataConfigService configService,
UiSharedService uiSharedService, ServerConfigurationManager serverConfigurationManager, UiSharedService uiSharedService, ServerConfigurationManager serverConfigurationManager,
DalamudUtilService dalamudUtilService, FileDialogManager fileDialogManager, PairManager pairManager, DalamudUtilService dalamudUtilService, FileDialogManager fileDialogManager, PairManager pairManager,
CharaDataGposeTogetherManager charaDataGposeTogetherManager, McdfShareManager mcdfShareManager) CharaDataGposeTogetherManager charaDataGposeTogetherManager)
: base(logger, mediator, "Umbra Character Data Hub###UmbraCharaDataUI", performanceCollectorService) : base(logger, mediator, "Umbra Character Data Hub###UmbraCharaDataUI", performanceCollectorService)
{ {
SetWindowSizeConstraints(); SetWindowSizeConstraints();
@@ -116,7 +92,6 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
_fileDialogManager = fileDialogManager; _fileDialogManager = fileDialogManager;
_pairManager = pairManager; _pairManager = pairManager;
_charaDataGposeTogetherManager = charaDataGposeTogetherManager; _charaDataGposeTogetherManager = charaDataGposeTogetherManager;
_mcdfShareManager = mcdfShareManager;
Mediator.Subscribe<GposeStartMessage>(this, (_) => IsOpen |= _configService.Current.OpenMareHubOnGposeStart); Mediator.Subscribe<GposeStartMessage>(this, (_) => IsOpen |= _configService.Current.OpenMareHubOnGposeStart);
Mediator.Subscribe<OpenCharaDataHubWithFilterMessage>(this, (msg) => Mediator.Subscribe<OpenCharaDataHubWithFilterMessage>(this, (msg) =>
{ {
@@ -148,14 +123,7 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
return; return;
} }
try _closalCts.Cancel();
{
_closalCts?.Cancel();
}
catch (ObjectDisposedException)
{
}
EnsureFreshCts(ref _closalCts);
SelectedDtoId = string.Empty; SelectedDtoId = string.Empty;
_filteredDict = null; _filteredDict = null;
_sharedWithYouOwnerFilter = string.Empty; _sharedWithYouOwnerFilter = string.Empty;
@@ -167,34 +135,21 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
public override void OnOpen() public override void OnOpen()
{ {
EnsureFreshCts(ref _closalCts); _closalCts = _closalCts.CancelRecreate();
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
{ {
CancelAndDispose(ref _closalCts); _closalCts.CancelDispose();
CancelAndDispose(ref _disposalCts); _disposalCts.CancelDispose();
} }
base.Dispose(disposing); base.Dispose(disposing);
} }
protected override void DrawInternal() protected override void DrawInternal()
{
DrawHubContent();
}
public void DrawInline()
{
using (ImRaii.PushId("CharaDataHubInline"))
{
DrawHubContent();
}
}
private void DrawHubContent()
{ {
if (!_comboHybridUsedLastFrame) if (!_comboHybridUsedLastFrame)
{ {
@@ -235,7 +190,7 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
} }
if (!string.IsNullOrEmpty(_charaDataManager.DataApplicationProgress)) if (!string.IsNullOrEmpty(_charaDataManager.DataApplicationProgress))
{ {
UiSharedService.ColorTextWrapped(_charaDataManager.DataApplicationProgress, UiSharedService.AccentColor); UiSharedService.ColorTextWrapped(_charaDataManager.DataApplicationProgress, ImGuiColors.DalamudYellow);
} }
if (_charaDataManager.DataApplicationTask != null) if (_charaDataManager.DataApplicationTask != null)
{ {
@@ -245,12 +200,8 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
} }
}); });
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))
{
using var tabs = ImRaii.TabBar("TabsTopLevel"); using var tabs = ImRaii.TabBar("TabsTopLevel");
bool smallUi = false;
_isHandlingSelf = _charaDataManager.HandledCharaData.Any(c => c.Value.IsSelf); _isHandlingSelf = _charaDataManager.HandledCharaData.Any(c => c.Value.IsSelf);
if (_isHandlingSelf) _openMcdOnlineOnNextRun = false; if (_isHandlingSelf) _openMcdOnlineOnNextRun = false;
@@ -270,10 +221,6 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
if (applicationTabItem) if (applicationTabItem)
{ {
smallUi = true; 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 appTabs = ImRaii.TabBar("TabsApplicationLevel"); using var appTabs = ImRaii.TabBar("TabsApplicationLevel");
using (ImRaii.Disabled(!_uiSharedService.IsInGpose)) using (ImRaii.Disabled(!_uiSharedService.IsInGpose))
@@ -315,7 +262,6 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
} }
} }
} }
}
else else
{ {
_charaDataNearbyManager.ComputeNearbyData = false; _charaDataNearbyManager.ComputeNearbyData = false;
@@ -334,10 +280,6 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
using (var creationTabItem = ImRaii.TabItem("Data Creation", flagsTopLevel)) using (var creationTabItem = ImRaii.TabItem("Data Creation", flagsTopLevel))
{ {
if (creationTabItem) if (creationTabItem)
{
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))
{ {
using var creationTabs = ImRaii.TabBar("TabsCreationLevel"); using var creationTabs = ImRaii.TabBar("TabsCreationLevel");
@@ -364,21 +306,9 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
DrawMcdfExport(); DrawMcdfExport();
} }
} }
using (var mcdfShareTabItem = ImRaii.TabItem("Partage MCDF"))
{
if (mcdfShareTabItem)
{
using var id = ImRaii.PushId("mcdfShare");
DrawMcdfShare();
} }
} }
} }
}
}
}
}
if (_isHandlingSelf) if (_isHandlingSelf)
{ {
UiSharedService.AttachToolTip("Cannot use creation tools while having Character Data applied to self."); UiSharedService.AttachToolTip("Cannot use creation tools while having Character Data applied to self.");
@@ -506,15 +436,11 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
if (!_hasValidGposeTarget) if (!_hasValidGposeTarget)
{ {
ImGuiHelpers.ScaledDummy(3); 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); 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"))
@@ -669,7 +595,7 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
if (_configService.Current.FavoriteCodes.Count == 0) 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(); ImGui.NewLine();
if (!_charaDataManager.DownloadMetaInfoTask?.IsCompleted ?? false) 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) 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")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.ArrowCircleDown, "Download your Character Data"))
{ {
var cts = EnsureFreshCts(ref _disposalCts); _ = _charaDataManager.GetAllData(_disposalCts.Token);
_ = _charaDataManager.GetAllData(cts.Token);
} }
} }
if (_charaDataManager.DataGetTimeoutTask != null && !_charaDataManager.DataGetTimeoutTask.IsCompleted) if (_charaDataManager.DataGetTimeoutTask != null && !_charaDataManager.DataGetTimeoutTask.IsCompleted)
@@ -925,13 +850,12 @@ 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.ColorTextWrapped("Failure to read MCDF file. MCDF file is possibly corrupt. Re-export the MCDF file and try again.",
UiSharedService.AccentColor); UiSharedService.AccentColor);
UiSharedService.ColorTextWrapped("Note: if this is your MCDF, try redrawing yourself, wait and re-export the file. " + 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 else
{ {
UiSharedService.ColorTextWrapped("Loading Character...", UiSharedService.AccentColor); UiSharedService.ColorTextWrapped("Loading Character...", ImGuiColors.DalamudYellow);
}
} }
} }
} }
@@ -959,7 +883,7 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
{ {
string defaultFileName = string.IsNullOrEmpty(_exportDescription) string defaultFileName = string.IsNullOrEmpty(_exportDescription)
? "export.mcdf" ? "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) => _uiSharedService.FileDialogManager.SaveFileDialog("Export Character to file", ".mcdf", defaultFileName, ".mcdf", (success, path) =>
{ {
if (!success) return; if (!success) return;
@@ -972,469 +896,12 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
}, Directory.Exists(_configService.Current.LastSavedCharaDataLocation) ? _configService.Current.LastSavedCharaDataLocation : null); }, 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" + 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(); 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");
var style = ImGui.GetStyle();
float BtnWidth(string label) => ImGui.CalcTextSize(label).X + style.FramePadding.X * 2f;
float ownActionsWidth = BtnWidth("Appliquer en GPose") + style.ItemSpacing.X + BtnWidth("Enregistrer") + style.ItemSpacing.X + BtnWidth("Supprimer") + 2f; // small margin
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, ownActionsWidth);
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}");
if (ImGui.IsItemHovered())
{
ImGui.BeginTooltip();
if (entry.AllowedIndividuals.Count > 0)
{
ImGui.TextUnformatted("UID autorisés:");
foreach (var uid in entry.AllowedIndividuals)
ImGui.BulletText(FormatUidWithName(uid));
}
else
{
ImGui.TextDisabled("Aucun UID autorisé");
}
ImGui.Separator();
if (entry.AllowedSyncshells.Count > 0)
{
ImGui.TextUnformatted("Syncshells autorisées:");
foreach (var gid in entry.AllowedSyncshells)
ImGui.BulletText(FormatSyncshellLabel(gid));
}
else
{
ImGui.TextDisabled("Aucune syncshell autorisée");
}
ImGui.EndTooltip();
}
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");
var style2 = ImGui.GetStyle();
float BtnWidth2(string label) => ImGui.CalcTextSize(label).X + style2.FramePadding.X * 2f;
float sharedActionsWidth = BtnWidth2("Appliquer") + style2.ItemSpacing.X + BtnWidth2("Enregistrer") + 2f;
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, sharedActionsWidth);
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);
if (ImGui.IsItemHovered())
{
ImGui.BeginTooltip();
ImGui.TextUnformatted($"UID propriétaire: {entry.OwnerUid}");
if (!string.IsNullOrEmpty(entry.OwnerAlias))
{
ImGui.Separator();
ImGui.TextUnformatted($"Alias: {entry.OwnerAlias}");
}
ImGui.EndTooltip();
}
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 FormatUidWithName(string uid)
{
if (string.IsNullOrEmpty(uid)) return string.Empty;
var note = _serverConfigurationManager.GetNoteForUid(uid);
if (!string.IsNullOrEmpty(note)) return $"{uid} ({note})";
return uid;
}
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) private void DrawMetaInfoData(string selectedGposeActor, bool hasValidGposeTarget, CharaDataMetaInfoExtendedDto data, bool canOpen = false)
{ {
ImGuiHelpers.ScaledDummy(5); ImGuiHelpers.ScaledDummy(5);
@@ -1637,26 +1104,4 @@ public sealed partial class CharaDataHubUi : WindowMediatorSubscriberBase
drawAction(); drawAction();
if (_disableUI) ImGui.BeginDisabled(); 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

View File

@@ -45,45 +45,6 @@ public class DrawGroupPair : DrawPairBase
_serverConfigurationManager = serverConfigurationManager; _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;
}
width += spacing * 1.2f;
return width;
}
protected override void DrawLeftSide(float textPosY, float originalY) protected override void DrawLeftSide(float textPosY, float originalY)
{ {
var entryUID = _pair.UserData.AliasOrUID; var entryUID = _pair.UserData.AliasOrUID;
@@ -203,7 +164,7 @@ public class DrawGroupPair : DrawPairBase
var individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false); var individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false);
bool showShared = _charaDataManager.SharedWithYouData.TryGetValue(_pair.UserData, out var sharedData); 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 showPlus = _pair.UserPair == null && _pair.IsOnline;
bool showBars = (userIsOwner || (userIsModerator && !entryIsMod && !entryIsOwner)) || !_pair.IsPaused; bool showBars = (userIsOwner || (userIsModerator && !entryIsMod && !entryIsOwner)) || !_pair.IsPaused;
bool showPause = true; bool showPause = true;
@@ -212,48 +173,23 @@ public class DrawGroupPair : DrawPairBase
var permIcon = (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled) ? FontAwesomeIcon.ExclamationTriangle var permIcon = (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled) ? FontAwesomeIcon.ExclamationTriangle
: ((soundsDisabled || animDisabled || vfxDisabled) ? FontAwesomeIcon.InfoCircle : FontAwesomeIcon.None); : ((soundsDisabled || animDisabled || vfxDisabled) ? FontAwesomeIcon.InfoCircle : FontAwesomeIcon.None);
var runningIconWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Running).X; 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 plusButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Plus).X;
var pauseIcon = _fullInfoDto.GroupUserPermissions.IsPaused() ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; var pauseIcon = _fullInfoDto.GroupUserPermissions.IsPaused() ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
var pauseButtonWidth = _uiSharedService.GetIconButtonSize(pauseIcon).X; var pauseButtonWidth = _uiSharedService.GetIconButtonSize(pauseIcon).X;
var barButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars).X; var barButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars).X;
var rightEdgeGap = spacing * 1.2f;
float totalWidth = 0f; var pos = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() + spacing
void Accumulate(bool condition, float width) - (showShared ? (runningIconWidth + spacing) : 0)
{ - (showInfo ? (infoIconWidth + spacing) : 0)
if (!condition || width <= 0f) return; - (showPlus ? (plusButtonWidth + spacing) : 0)
if (totalWidth > 0f) totalWidth += spacing; - (showPause ? (pauseButtonWidth + spacing) : 0)
totalWidth += width; - (showBars ? (barButtonWidth + spacing) : 0);
}
Accumulate(showShared, runningIconWidth); ImGui.SameLine(pos);
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 - rightEdgeGap - totalWidth);
float currentX = baseX;
ImGui.SameLine();
ImGui.SetCursorPosX(baseX);
if (showShared) if (showShared)
{ {
ImGui.SetCursorPosY(textPosY);
_uiSharedService.IconText(FontAwesomeIcon.Running); _uiSharedService.IconText(FontAwesomeIcon.Running);
UiSharedService.AttachToolTip($"This user has shared {sharedData!.Count} Character Data Sets with you." + UiSharedService.TooltipSeparator UiSharedService.AttachToolTip($"This user has shared {sharedData!.Count} Character Data Sets with you." + UiSharedService.TooltipSeparator
@@ -263,25 +199,12 @@ public class DrawGroupPair : DrawPairBase
{ {
_mediator.Publish(new OpenCharaDataHubWithFilterMessage(_pair.UserData)); _mediator.Publish(new OpenCharaDataHubWithFilterMessage(_pair.UserData));
} }
currentX += runningIconWidth + spacing; ImGui.SameLine();
ImGui.SetCursorPosX(currentX);
} }
if (showInfo && infoIconWidth > 0f) if (individualAnimDisabled || individualSoundsDisabled)
{ {
bool centerWarning = permIcon == FontAwesomeIcon.ExclamationTriangle && showPause && showBars && !showShared && !showPlus;
if (centerWarning)
{
float barsClusterWidth = showBars ? (barButtonWidth + spacing * 0.5f) : 0f;
float leftAreaWidth = MathF.Max(totalWidth - pauseButtonWidth - barsClusterWidth, 0f);
float warningX = baseX + MathF.Max((leftAreaWidth - infoIconWidth) / 2f, 0f);
currentX = warningX;
ImGui.SetCursorPosX(currentX);
}
ImGui.SetCursorPosY(textPosY); ImGui.SetCursorPosY(textPosY);
if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
_uiSharedService.IconText(permIcon); _uiSharedService.IconText(permIcon);
ImGui.PopStyleColor(); ImGui.PopStyleColor();
@@ -326,9 +249,11 @@ public class DrawGroupPair : DrawPairBase
ImGui.EndTooltip(); ImGui.EndTooltip();
} }
ImGui.SameLine();
} }
else else if ((animDisabled || soundsDisabled))
{ {
ImGui.SetCursorPosY(textPosY);
_uiSharedService.IconText(permIcon); _uiSharedService.IconText(permIcon);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ {
@@ -362,17 +287,14 @@ public class DrawGroupPair : DrawPairBase
ImGui.EndTooltip(); ImGui.EndTooltip();
} }
} ImGui.SameLine();
currentX += infoIconWidth + spacing;
ImGui.SetCursorPosX(currentX);
} }
if (showPlus) if (showPlus)
{ {
ImGui.SetCursorPosY(originalY); ImGui.SetCursorPosY(originalY);
if (_uiSharedService.IconPlusButtonCentered()) if (_uiSharedService.IconButton(FontAwesomeIcon.Plus))
{ {
var targetUid = _pair.UserData.UID; var targetUid = _pair.UserData.UID;
if (!string.IsNullOrEmpty(targetUid)) if (!string.IsNullOrEmpty(targetUid))
@@ -381,15 +303,14 @@ public class DrawGroupPair : DrawPairBase
} }
} }
UiSharedService.AttachToolTip(AppendSeenInfo("Send pairing invite to " + entryUID)); UiSharedService.AttachToolTip(AppendSeenInfo("Send pairing invite to " + entryUID));
currentX += plusButtonWidth + spacing; ImGui.SameLine();
ImGui.SetCursorPosX(currentX);
} }
if (showPause) if (showPause)
{ {
float gapToBars = showBars ? spacing * 0.5f : spacing;
ImGui.SetCursorPosY(originalY); ImGui.SetCursorPosY(originalY);
if (pauseIcon == FontAwesomeIcon.Pause ? _uiSharedService.IconPauseButtonCentered() : _uiSharedService.IconButtonCentered(pauseIcon))
if (_uiSharedService.IconButton(pauseIcon))
{ {
var newPermissions = _fullInfoDto.GroupUserPermissions ^ GroupUserPermissions.Paused; var newPermissions = _fullInfoDto.GroupUserPermissions ^ GroupUserPermissions.Paused;
_fullInfoDto.GroupUserPermissions = newPermissions; _fullInfoDto.GroupUserPermissions = newPermissions;
@@ -397,20 +318,19 @@ public class DrawGroupPair : DrawPairBase
} }
UiSharedService.AttachToolTip(AppendSeenInfo((_fullInfoDto.GroupUserPermissions.IsPaused() ? "Resume" : "Pause") + " syncing with " + entryUID)); UiSharedService.AttachToolTip(AppendSeenInfo((_fullInfoDto.GroupUserPermissions.IsPaused() ? "Resume" : "Pause") + " syncing with " + entryUID));
currentX += pauseButtonWidth + gapToBars; ImGui.SameLine();
ImGui.SetCursorPosX(currentX);
} }
if (showBars) if (showBars)
{ {
ImGui.SetCursorPosY(originalY); ImGui.SetCursorPosY(originalY);
if (_uiSharedService.IconButtonCentered(FontAwesomeIcon.Bars))
if (_uiSharedService.IconButton(FontAwesomeIcon.Bars))
{ {
ImGui.OpenPopup("Syncshell Flyout Menu"); ImGui.OpenPopup("Popup");
} }
currentX += barButtonWidth;
ImGui.SetCursorPosX(currentX);
} }
if (ImGui.BeginPopup("Popup")) if (ImGui.BeginPopup("Popup"))
{ {
if ((userIsModerator || userIsOwner) && !(entryIsMod || entryIsOwner)) if ((userIsModerator || userIsOwner) && !(entryIsMod || entryIsOwner))
@@ -496,7 +416,7 @@ public class DrawGroupPair : DrawPairBase
ImGui.EndPopup(); ImGui.EndPopup();
} }
return baseX - spacing; return pos - spacing;
} }
private string AppendSeenInfo(string tooltip) private string AppendSeenInfo(string tooltip)

View File

@@ -1,8 +1,5 @@
using System; using Dalamud.Bindings.ImGui;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.UI.Handlers; using MareSynchronos.UI.Handlers;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
@@ -31,81 +28,36 @@ public abstract class DrawPairBase
public void DrawPairedClient() public void DrawPairedClient()
{ {
var style = ImGui.GetStyle(); var originalY = ImGui.GetCursorPosY();
var padding = style.FramePadding; var pauseIconSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Play);
var spacing = style.ItemSpacing; var textSize = ImGui.CalcTextSize(_pair.UserData.AliasOrUID);
var rowStartCursor = ImGui.GetCursorPos();
var rowStartScreen = ImGui.GetCursorScreenPos();
var pauseButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Pause); var startPos = ImGui.GetCursorStartPos();
var playButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Play);
var menuButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars);
float pauseClusterWidth = Math.Max(pauseButtonSize.X, playButtonSize.X); var framePadding = ImGui.GetStyle().FramePadding;
float pauseClusterHeight = Math.Max(Math.Max(pauseButtonSize.Y, playButtonSize.Y), ImGui.GetFrameHeight()); var lineHeight = textSize.Y + framePadding.Y * 2;
float reservedSpacing = style.ItemSpacing.X * 1.6f;
float rightButtonWidth =
menuButtonSize.X +
pauseClusterWidth +
reservedSpacing +
GetRightSideExtraWidth();
float availableWidth = Math.Max(ImGui.GetContentRegionAvail().X - rightButtonWidth, 1f); var off = startPos.Y;
float textHeight = ImGui.GetFontSize(); var height = UiSharedService.GetWindowContentRegionHeight();
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 origin = ImGui.GetCursorStartPos(); if ((originalY + off) < -lineHeight || (originalY + off) > height)
var top = origin.Y + rowStartCursor.Y;
var bottom = top + totalHeight;
var visibleHeight = UiSharedService.GetWindowContentRegionHeight();
if (bottom < 0 || top > visibleHeight)
{ {
ImGui.SetCursorPos(new Vector2(rowStartCursor.X, rowStartCursor.Y + totalHeight)); ImGui.Dummy(new System.Numerics.Vector2(0f, lineHeight));
return; return;
} }
var drawList = ImGui.GetWindowDrawList(); var textPosY = originalY + pauseIconSize.Y / 2 - textSize.Y / 2;
var backgroundColor = new Vector4(0.10f, 0.10f, 0.14f, 0.95f); DrawLeftSide(textPosY, originalY);
var borderColor = new Vector4(0f, 0f, 0f, 0.9f); ImGui.SameLine();
float rounding = Math.Max(style.FrameRounding, 7f * ImGuiHelpers.GlobalScale); var posX = ImGui.GetCursorPosX();
var rightSide = DrawRightSide(textPosY, originalY);
var panelMin = rowStartScreen + new Vector2(0f, spacing.Y * 0.15f); DrawName(originalY, posX, rightSide);
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);
float leftReserved = GetLeftSideReservedWidth();
float nameStartX = rowStartCursor.X + padding.X + leftReserved;
var rightSide = DrawRightSide(buttonTop, buttonTop);
ImGui.SameLine(nameStartX);
ImGui.SetCursorPosY(textTop);
DrawName(textTop + padding.Y * 0.15f, nameStartX, rightSide);
ImGui.SetCursorPos(new Vector2(rowStartCursor.X, rowStartCursor.Y + totalHeight));
ImGui.SetCursorPosX(rowStartCursor.X);
} }
protected abstract void DrawLeftSide(float textPosY, float originalY); protected abstract void DrawLeftSide(float textPosY, float originalY);
protected abstract float DrawRightSide(float textPosY, float originalY); protected abstract float DrawRightSide(float textPosY, float originalY);
protected virtual float GetRightSideExtraWidth() => 0f;
protected virtual float GetLeftSideReservedWidth() => UiSharedService.GetIconSize(FontAwesomeIcon.Moon).X * 2f + ImGui.GetStyle().ItemSpacing.X * 1.5f;
private void DrawName(float originalY, float leftSide, float rightSide) private void DrawName(float originalY, float leftSide, float rightSide)
{ {
_displayHandler.DrawPairText(_id, _pair, leftSide, originalY, () => rightSide - leftSide); _displayHandler.DrawPairText(_id, _pair, leftSide, originalY, () => rightSide - leftSide);

View File

@@ -41,48 +41,6 @@ public class DrawUserPair : DrawPairBase
public bool IsVisible => _pair.IsVisible; public bool IsVisible => _pair.IsVisible;
public UserPairDto UserPair => _pair.UserPair!; 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;
}
width += spacingX * 1.2f;
return width;
}
protected override float GetLeftSideReservedWidth()
{
var style = ImGui.GetStyle();
float spacing = style.ItemSpacing.X;
float iconW = UiSharedService.GetIconSize(FontAwesomeIcon.Moon).X;
int icons = 1;
if (!(_pair.UserPair!.OwnPermissions.IsPaired() && _pair.UserPair!.OtherPermissions.IsPaired()))
icons++;
else if (_pair.UserPair!.OwnPermissions.IsPaused() || _pair.UserPair!.OtherPermissions.IsPaused())
icons++;
if (_pair.IsOnline && _pair.IsVisible)
icons++;
float iconsTotal = icons * iconW + Math.Max(0, icons - 1) * spacing;
float cushion = spacing * 0.6f;
return iconsTotal + cushion;
}
protected override void DrawLeftSide(float textPosY, float originalY) protected override void DrawLeftSide(float textPosY, float originalY)
{ {
var online = _pair.IsOnline; var online = _pair.IsOnline;
@@ -152,9 +110,7 @@ public class DrawUserPair : DrawPairBase
var barButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars); var barButtonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars);
var entryUID = _pair.UserData.AliasOrUID; var entryUID = _pair.UserData.AliasOrUID;
var spacingX = ImGui.GetStyle().ItemSpacing.X; var spacingX = ImGui.GetStyle().ItemSpacing.X;
var edgePadding = UiSharedService.GetCardContentPaddingX() + 6f * ImGuiHelpers.GlobalScale; var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth();
var rightEdgeGap = spacingX * 1.2f;
var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - edgePadding - rightEdgeGap;
var rightSidePos = windowEndX - barButtonSize.X; var rightSidePos = windowEndX - barButtonSize.X;
// Flyout Menu // Flyout Menu
@@ -171,12 +127,13 @@ public class DrawUserPair : DrawPairBase
ImGui.EndPopup(); ImGui.EndPopup();
} }
// Pause (mutual pairs only)
if (_pair.UserPair!.OwnPermissions.IsPaired() && _pair.UserPair!.OtherPermissions.IsPaired()) if (_pair.UserPair!.OwnPermissions.IsPaired() && _pair.UserPair!.OtherPermissions.IsPaired())
{ {
rightSidePos -= pauseIconSize.X + spacingX; rightSidePos -= pauseIconSize.X + spacingX;
ImGui.SameLine(rightSidePos); ImGui.SameLine(rightSidePos);
ImGui.SetCursorPosY(originalY); ImGui.SetCursorPosY(originalY);
if (pauseIcon == FontAwesomeIcon.Pause ? _uiSharedService.IconPauseButtonCentered() : _uiSharedService.IconButtonCentered(pauseIcon)) if (_uiSharedService.IconButton(pauseIcon))
{ {
var perm = _pair.UserPair!.OwnPermissions; var perm = _pair.UserPair!.OwnPermissions;
perm.SetPaused(!perm.IsPaused()); perm.SetPaused(!perm.IsPaused());

View File

@@ -30,6 +30,7 @@ internal sealed class GroupPanel
private readonly CompactUi _mainUi; private readonly CompactUi _mainUi;
private readonly PairManager _pairManager; private readonly PairManager _pairManager;
private readonly ChatService _chatService; private readonly ChatService _chatService;
private readonly MareConfigService _mareConfig;
private readonly ServerConfigurationManager _serverConfigurationManager; private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly CharaDataManager _charaDataManager; private readonly CharaDataManager _charaDataManager;
private readonly AutoDetectRequestService _autoDetectRequestService; private readonly AutoDetectRequestService _autoDetectRequestService;
@@ -74,7 +75,7 @@ internal sealed class GroupPanel
private string _syncShellToJoin = string.Empty; private string _syncShellToJoin = string.Empty;
public GroupPanel(CompactUi mainUi, UiSharedService uiShared, PairManager pairManager, ChatService chatServivce, public GroupPanel(CompactUi mainUi, UiSharedService uiShared, PairManager pairManager, ChatService chatServivce,
UidDisplayHandler uidDisplayHandler, ServerConfigurationManager serverConfigurationManager, UidDisplayHandler uidDisplayHandler, MareConfigService mareConfig, ServerConfigurationManager serverConfigurationManager,
CharaDataManager charaDataManager, AutoDetectRequestService autoDetectRequestService) CharaDataManager charaDataManager, AutoDetectRequestService autoDetectRequestService)
{ {
_mainUi = mainUi; _mainUi = mainUi;
@@ -82,6 +83,7 @@ internal sealed class GroupPanel
_pairManager = pairManager; _pairManager = pairManager;
_chatService = chatServivce; _chatService = chatServivce;
_uidDisplayHandler = uidDisplayHandler; _uidDisplayHandler = uidDisplayHandler;
_mareConfig = mareConfig;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_charaDataManager = charaDataManager; _charaDataManager = charaDataManager;
_autoDetectRequestService = autoDetectRequestService; _autoDetectRequestService = autoDetectRequestService;
@@ -91,7 +93,6 @@ internal sealed class GroupPanel
public void DrawSyncshells() public void DrawSyncshells()
{ {
using var fontScale = UiSharedService.PushFontScale(UiSharedService.ContentFontScale);
using (ImRaii.PushId("addsyncshell")) DrawAddSyncshell(); using (ImRaii.PushId("addsyncshell")) DrawAddSyncshell();
using (ImRaii.PushId("syncshelllist")) DrawSyncshellList(); using (ImRaii.PushId("syncshelllist")) DrawSyncshellList();
_mainUi.TransferPartHeight = ImGui.GetCursorPosY(); _mainUi.TransferPartHeight = ImGui.GetCursorPosY();
@@ -312,20 +313,19 @@ internal sealed class GroupPanel
int shellNumber = _serverConfigurationManager.GetShellNumberForGid(groupDto.GID); int shellNumber = _serverConfigurationManager.GetShellNumberForGid(groupDto.GID);
var name = groupDto.Group.Alias ?? 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]; _expandedGroupState[groupDto.GID] = !_expandedGroupState[groupDto.GID];
UiSharedService.DrawArrowToggle(ref expandedState, $"##syncshell-toggle-{groupDto.GID}");
if (expandedState != _expandedGroupState[groupDto.GID])
{
_expandedGroupState[groupDto.GID] = expandedState;
} }
ImGui.SameLine(0f, 6f * ImGuiHelpers.GlobalScale); ImGui.SameLine();
var textIsGid = true; var textIsGid = true;
string groupName = groupDto.GroupAliasOrGID; string groupName = groupDto.GroupAliasOrGID;
@@ -547,7 +547,7 @@ internal sealed class GroupPanel
bool hideOfflineUsers = pairsInGroup.Count > 1000; bool hideOfflineUsers = pairsInGroup.Count > 1000;
ImGui.Indent(20); ImGui.Indent(20);
if (expandedState) if (_expandedGroupState[groupDto.GID])
{ {
var sortedPairs = pairsInGroup var sortedPairs = pairsInGroup
.OrderByDescending(u => string.Equals(u.UserData.UID, groupDto.OwnerUID, StringComparison.Ordinal)) .OrderByDescending(u => string.Equals(u.UserData.UID, groupDto.OwnerUID, StringComparison.Ordinal))
@@ -614,9 +614,6 @@ internal sealed class GroupPanel
ImGui.Separator(); ImGui.Separator();
} }
ImGui.Unindent(20); ImGui.Unindent(20);
}, stretchWidth: true);
ImGuiHelpers.ScaledDummy(4f);
} }
private void DrawSyncShellButtons(GroupFullInfoDto groupDto, List<Pair> groupPairs) 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 isOwner = string.Equals(groupDto.OwnerUID, ApiController.UID, StringComparison.Ordinal);
var spacingX = ImGui.GetStyle().ItemSpacing.X; var spacingX = ImGui.GetStyle().ItemSpacing.X;
var cardPaddingX = UiSharedService.GetCardContentPaddingX(); var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth();
var windowEndX = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() - cardPaddingX - 6f * ImGuiHelpers.GlobalScale;
var pauseIcon = groupDto.GroupUserPermissions.IsPaused() ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; var pauseIcon = groupDto.GroupUserPermissions.IsPaused() ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
var pauseIconSize = _uiShared.GetIconButtonSize(pauseIcon); var pauseIconSize = _uiShared.GetIconButtonSize(pauseIcon);
@@ -832,22 +828,9 @@ internal sealed class GroupPanel
private void DrawSyncshellList() private void DrawSyncshellList()
{ {
float availableHeight = ImGui.GetContentRegionAvail().Y; var ySize = _mainUi.TransferPartHeight == 0
float ySize; ? 1
if (_mainUi.TransferPartHeight <= 0) : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _mainUi.TransferPartHeight - ImGui.GetCursorPosY();
{
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();
}
ImGui.BeginChild("list", new Vector2(_mainUi.WindowContentWidth, ySize), border: false); 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()) foreach (var entry in _pairManager.GroupPairs.OrderBy(g => g.Key.Group.AliasOrGID, StringComparer.OrdinalIgnoreCase).ToList())
{ {

View File

@@ -1,14 +1,10 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using MareSynchronos.API.Data.Extensions; using MareSynchronos.API.Data.Extensions;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.UI.Handlers; using MareSynchronos.UI.Handlers;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MareSynchronos.UI.Components; namespace MareSynchronos.UI.Components;
@@ -32,7 +28,7 @@ public class PairGroupsUi
_uiSharedService = uiSharedService; _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 // Only render those tags that actually have pairs in them, otherwise
// we can end up with a bunch of useless pair groups // we can end up with a bunch of useless pair groups
@@ -40,7 +36,7 @@ public class PairGroupsUi
var allUsers = onlineUsers.Concat(offlineUsers).ToList(); var allUsers = onlineUsers.Concat(offlineUsers).ToList();
if (typeof(T) == typeof(DrawUserPair)) 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 allArePaused = availablePairsInThisTag.All(pair => pair.UserPair!.OwnPermissions.IsPaused());
var pauseButton = allArePaused ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; var pauseButton = allArePaused ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
var flyoutMenuSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars); var flyoutMenuX = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars).X;
var pauseButtonSize = _uiSharedService.GetIconButtonSize(pauseButton); var pauseButtonX = _uiSharedService.GetIconButtonSize(pauseButton).X;
var windowX = ImGui.GetWindowContentRegionMin().X;
var windowWidth = UiSharedService.GetWindowContentRegionWidth();
var spacingX = ImGui.GetStyle().ItemSpacing.X; 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 (_uiSharedService.IconButton(pauseButton))
{ {
if (allArePaused) if (allArePaused)
@@ -77,8 +72,8 @@ public class PairGroupsUi
UiSharedService.AttachToolTip($"Pause pairing with all pairs in {tag}"); UiSharedService.AttachToolTip($"Pause pairing with all pairs in {tag}");
} }
var menuStart = Math.Max(pauseStart + pauseButtonSize.X + spacingX, currentX); var buttonDeleteOffset = windowX + windowWidth - flyoutMenuX;
ImGui.SameLine(menuStart); ImGui.SameLine(buttonDeleteOffset);
if (_uiSharedService.IconButton(FontAwesomeIcon.Bars)) if (_uiSharedService.IconButton(FontAwesomeIcon.Bars))
{ {
ImGui.OpenPopup("Group Flyout Menu"); 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; IEnumerable<DrawPairBase> usersInThisTag;
HashSet<string>? otherUidsTaggedWithTag = null; HashSet<string>? otherUidsTaggedWithTag = null;
@@ -113,25 +108,26 @@ public class PairGroupsUi
if (isSpecialTag && !usersInThisTag.Any()) return; if (isSpecialTag && !usersInThisTag.Any()) return;
UiSharedService.DrawCard($"pair-group-{tag}", () =>
{
DrawName(tag, isSpecialTag, visibleInThisTag, usersInThisTag.Count(), otherUidsTaggedWithTag?.Count); DrawName(tag, isSpecialTag, visibleInThisTag, usersInThisTag.Count(), otherUidsTaggedWithTag?.Count);
if (!isSpecialTag) if (!isSpecialTag)
{ {
using (ImRaii.PushId($"group-{tag}-buttons")) DrawButtons(tag, allUsers.Cast<DrawUserPair>().Where(p => otherUidsTaggedWithTag!.Contains(p.UID)).ToList()); using (ImRaii.PushId($"group-{tag}-buttons")) DrawButtons(tag, allUsers.Cast<DrawUserPair>().Where(p => otherUidsTaggedWithTag!.Contains(p.UID)).ToList());
} }
else
{
if (!_tagHandler.IsTagOpen(tag))
{
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); ImGui.Indent(20);
var indent = 18f * ImGuiHelpers.GlobalScale;
ImGui.Indent(indent);
DrawPairs(tag, usersInThisTag); DrawPairs(tag, usersInThisTag);
drawExtraContent?.Invoke(); ImGui.Unindent(20);
ImGui.Unindent(indent);
}, stretchWidth: true);
ImGuiHelpers.ScaledDummy(4f);
} }
private void DrawGroupMenu(string tag) private void DrawGroupMenu(string tag)
@@ -161,21 +157,17 @@ public class PairGroupsUi
}; };
string resultFolderName = !isSpecialTag ? $"{displayedName} ({visible}/{online}/{total} Pairs)" : $"{displayedName} ({online} Pairs)"; string resultFolderName = !isSpecialTag ? $"{displayedName} ({visible}/{online}/{total} Pairs)" : $"{displayedName} ({online} Pairs)";
bool isOpen = _tagHandler.IsTagOpen(tag); var icon = _tagHandler.IsTagOpen(tag) ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight;
bool previousState = isOpen; _uiSharedService.IconText(icon);
UiSharedService.DrawArrowToggle(ref isOpen, $"##group-toggle-{tag}"); if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
if (isOpen != previousState)
{ {
_tagHandler.SetTagOpen(tag, isOpen); ToggleTagOpen(tag);
} }
ImGui.SameLine(0f, 6f * ImGuiHelpers.GlobalScale); ImGui.SameLine();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(resultFolderName); ImGui.TextUnformatted(resultFolderName);
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{ {
bool newState = !_tagHandler.IsTagOpen(tag); ToggleTagOpen(tag);
_tagHandler.SetTagOpen(tag, newState);
isOpen = newState;
} }
if (!isSpecialTag && ImGui.IsItemHovered()) if (!isSpecialTag && ImGui.IsItemHovered())
@@ -194,11 +186,15 @@ public class PairGroupsUi
{ {
// These are all the OtherUIDs that are tagged with this tag // These are all the OtherUIDs that are tagged with this tag
_uidDisplayHandler.RenderPairList(availablePairsInThisCategory); _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) foreach (var tag in tagsWithPairsInThem)
{ {
if (_mareConfig.Current.ShowOfflineUsersSeparately) 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);
}
} }

View File

@@ -9,7 +9,6 @@ using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Numerics; using System.Numerics;
using System;
namespace MareSynchronos.UI; namespace MareSynchronos.UI;
@@ -21,7 +20,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly Dictionary<string, string[]> _texturesToConvert = new(StringComparer.Ordinal); private readonly Dictionary<string, string[]> _texturesToConvert = new(StringComparer.Ordinal);
private Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>? _cachedAnalysis; private Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>? _cachedAnalysis;
private CancellationTokenSource? _conversionCancellationTokenSource = new(); private CancellationTokenSource _conversionCancellationTokenSource = new();
private string _conversionCurrentFileName = string.Empty; private string _conversionCurrentFileName = string.Empty;
private int _conversionCurrentFileProgress = 0; private int _conversionCurrentFileProgress = 0;
private Task? _conversionTask; private Task? _conversionTask;
@@ -65,19 +64,6 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
} }
protected override void DrawInternal() protected override void DrawInternal()
{
DrawAnalysisContent();
}
public void DrawInline()
{
using (ImRaii.PushId("CharacterAnalysisInline"))
{
DrawAnalysisContent();
}
}
private void DrawAnalysisContent()
{ {
if (_conversionTask != null && !_conversionTask.IsCompleted) if (_conversionTask != null && !_conversionTask.IsCompleted)
{ {
@@ -88,7 +74,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
UiSharedService.TextWrapped("Current file: " + _conversionCurrentFileName); UiSharedService.TextWrapped("Current file: " + _conversionCurrentFileName);
if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel conversion")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel conversion"))
{ {
TryCancel(_conversionCancellationTokenSource); _conversionCancellationTokenSource.Cancel();
} }
UiSharedService.SetScaledWindowSize(500); UiSharedService.SetScaledWindowSize(500);
ImGui.EndPopup(); ImGui.EndPopup();
@@ -129,7 +115,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
if (isAnalyzing) if (isAnalyzing)
{ {
UiSharedService.ColorTextWrapped($"Analyzing {_characterAnalyzer.CurrentFile}/{_characterAnalyzer.TotalFiles}", UiSharedService.ColorTextWrapped($"Analyzing {_characterAnalyzer.CurrentFile}/{_characterAnalyzer.TotalFiles}",
UiSharedService.AccentColor); ImGuiColors.DalamudYellow);
if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel analysis")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel analysis"))
{ {
_characterAnalyzer.CancelAnalyze(); _characterAnalyzer.CancelAnalyze();
@@ -140,7 +126,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
if (needAnalysis) if (needAnalysis)
{ {
UiSharedService.ColorTextWrapped("Some entries in the analysis have file size not determined yet, press the button below to analyze your current data", UiSharedService.ColorTextWrapped("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)")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start analysis (missing entries)"))
{ {
_ = _characterAnalyzer.ComputeAnalysis(print: false); _ = _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(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize))));
ImGui.TextUnformatted("Total size (download size):"); ImGui.TextUnformatted("Total size (download size):");
ImGui.SameLine(); 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)))); ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.CompressedSize))));
if (needAnalysis && !isAnalyzing) if (needAnalysis && !isAnalyzing)
@@ -193,10 +179,6 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
ImGui.TextUnformatted($"Total modded model triangles: {UiSharedService.TrisToString(_cachedAnalysis.Sum(c => c.Value.Sum(f => f.Value.Triangles)))}"); ImGui.TextUnformatted($"Total modded model triangles: {UiSharedService.TrisToString(_cachedAnalysis.Sum(c => c.Value.Sum(f => f.Value.Triangles)))}");
ImGui.Separator(); ImGui.Separator();
{
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"); using var tabbar = ImRaii.TabBar("objectSelection");
foreach (var kvp in _cachedAnalysis) foreach (var kvp in _cachedAnalysis)
{ {
@@ -230,7 +212,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize))); ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
ImGui.TextUnformatted($"{kvp.Key} size (download size):"); ImGui.TextUnformatted($"{kvp.Key} size (download size):");
ImGui.SameLine(); ImGui.SameLine();
using (ImRaii.PushColor(ImGuiCol.Text, UiSharedService.AccentColor, needAnalysis)) using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis))
{ {
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize))); ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
if (needAnalysis && !isAnalyzing) if (needAnalysis && !isAnalyzing)
@@ -260,17 +242,15 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
_texturesToConvert.Clear(); _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"); using var fileTabBar = ImRaii.TabBar("fileTabs");
foreach (IGrouping<string, CharacterAnalyzer.FileDataEntry>? fileGroup in groupedfiles) foreach (IGrouping<string, CharacterAnalyzer.FileDataEntry>? fileGroup in groupedfiles)
{ {
string fileGroupText = fileGroup.Key + " [" + fileGroup.Count() + "]"; string fileGroupText = fileGroup.Key + " [" + fileGroup.Count() + "]";
var requiresCompute = fileGroup.Any(k => !k.IsComputed); var requiresCompute = fileGroup.Any(k => !k.IsComputed);
using var tabcol = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.Color(ImGuiColors.DalamudYellow), requiresCompute);
ImRaii.IEndObject fileTab; ImRaii.IEndObject fileTab;
using (var textcol = ImRaii.PushColor(ImGuiCol.Text, UiSharedService.Color(Vector4.One), using (var textcol = ImRaii.PushColor(ImGuiCol.Text, UiSharedService.Color(new(0, 0, 0, 1)),
requiresCompute && !string.Equals(_selectedFileTypeTab, fileGroup.Key, StringComparison.Ordinal))) requiresCompute && !string.Equals(_selectedFileTypeTab, fileGroup.Key, StringComparison.Ordinal)))
{ {
fileTab = ImRaii.TabItem(fileGroupText + "###" + fileGroup.Key); fileTab = ImRaii.TabItem(fileGroupText + "###" + fileGroup.Key);
@@ -303,7 +283,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
ImGui.Checkbox("Enable BC7 Conversion Mode", ref _enableBc7ConversionMode); ImGui.Checkbox("Enable BC7 Conversion Mode", ref _enableBc7ConversionMode);
if (_enableBc7ConversionMode) if (_enableBc7ConversionMode)
{ {
UiSharedService.ColorText("WARNING BC7 CONVERSION:", UiSharedService.AccentColor); UiSharedService.ColorText("WARNING BC7 CONVERSION:", ImGuiColors.DalamudYellow);
ImGui.SameLine(); ImGui.SameLine();
UiSharedService.ColorText("Converting textures to BC7 is irreversible!", UiSharedService.AccentColor); 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." + UiSharedService.ColorTextWrapped("- Converting textures to BC7 will reduce their size (compressed and uncompressed) drastically. It is recommended to be used for large (4k+) textures." +
@@ -311,11 +291,11 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
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 + "- 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 + "- 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." 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); , ImGuiColors.DalamudYellow);
if (_texturesToConvert.Count > 0 && _uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start conversion of " + _texturesToConvert.Count + " texture(s)")) if (_texturesToConvert.Count > 0 && _uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start conversion of " + _texturesToConvert.Count + " texture(s)"))
{ {
var conversionCts = EnsureFreshCts(ref _conversionCancellationTokenSource); _conversionCancellationTokenSource = _conversionCancellationTokenSource.CancelRecreate();
_conversionTask = _ipcManager.Penumbra.ConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, conversionCts.Token); _conversionTask = _ipcManager.Penumbra.ConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, _conversionCancellationTokenSource.Token);
} }
} }
} }
@@ -325,17 +305,14 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
fileTab.Dispose(); fileTab.Dispose();
} }
} }
} }
}
ImGui.Separator(); ImGui.Separator();
ImGui.TextUnformatted("Selected file:"); ImGui.TextUnformatted("Selected file:");
ImGui.SameLine(); ImGui.SameLine();
UiSharedService.ColorText(_selectedHash, UiSharedService.AccentColor); UiSharedService.ColorText(_selectedHash, ImGuiColors.DalamudYellow);
if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item)) if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item))
{ {
@@ -377,13 +354,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (disposing)
{
CancelAndDispose(ref _conversionCancellationTokenSource);
_conversionProgress.ProgressChanged -= ConversionProgress_ProgressChanged;
}
base.Dispose(disposing); base.Dispose(disposing);
_conversionProgress.ProgressChanged -= ConversionProgress_ProgressChanged;
} }
private void ConversionProgress_ProgressChanged(object? sender, (string, int) e) private void ConversionProgress_ProgressChanged(object? sender, (string, int) e)
@@ -462,8 +434,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (string.Equals(_selectedHash, item.Hash, StringComparison.Ordinal)) if (string.Equals(_selectedHash, item.Hash, StringComparison.Ordinal))
{ {
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(UiSharedService.AccentColor)); ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(ImGuiColors.DalamudYellow));
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(UiSharedService.AccentColor)); ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(ImGuiColors.DalamudYellow));
} }
ImGui.TextUnformatted(item.Hash); ImGui.TextUnformatted(item.Hash);
if (ImGui.IsItemClicked()) _selectedHash = item.Hash; if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
@@ -477,7 +449,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
ImGui.TextUnformatted(UiSharedService.ByteToString(item.OriginalSize)); ImGui.TextUnformatted(UiSharedService.ByteToString(item.OriginalSize));
if (ImGui.IsItemClicked()) _selectedHash = item.Hash; if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
ImGui.TableNextColumn(); 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)); ImGui.TextUnformatted(UiSharedService.ByteToString(item.CompressedSize));
if (ImGui.IsItemClicked()) _selectedHash = item.Hash; if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal)) 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)
{
}
}
} }

View File

@@ -22,6 +22,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase
private readonly FileDialogManager _fileDialogManager; private readonly FileDialogManager _fileDialogManager;
private readonly MareProfileManager _mareProfileManager; private readonly MareProfileManager _mareProfileManager;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly ServerConfigurationManager _serverConfigurationManager;
private bool _adjustedForScollBarsLocalProfile = false; private bool _adjustedForScollBarsLocalProfile = false;
private bool _adjustedForScollBarsOnlineProfile = false; private bool _adjustedForScollBarsOnlineProfile = false;
private string _descriptionText = string.Empty; private string _descriptionText = string.Empty;
@@ -33,6 +34,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase
public EditProfileUi(ILogger<EditProfileUi> logger, MareMediator mediator, public EditProfileUi(ILogger<EditProfileUi> logger, MareMediator mediator,
ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager, ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager,
ServerConfigurationManager serverConfigurationManager,
MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService) MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "Umbra Edit Profile###UmbraSyncEditProfileUI", performanceCollectorService) : base(logger, mediator, "Umbra Edit Profile###UmbraSyncEditProfileUI", performanceCollectorService)
{ {
@@ -45,6 +47,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase
_apiController = apiController; _apiController = apiController;
_uiSharedService = uiSharedService; _uiSharedService = uiSharedService;
_fileDialogManager = fileDialogManager; _fileDialogManager = fileDialogManager;
_serverConfigurationManager = serverConfigurationManager;
_mareProfileManager = mareProfileManager; _mareProfileManager = mareProfileManager;
Mediator.Subscribe<GposeStartMessage>(this, (_) => { _wasOpen = IsOpen; IsOpen = false; }); Mediator.Subscribe<GposeStartMessage>(this, (_) => { _wasOpen = IsOpen; IsOpen = false; });
@@ -61,16 +64,6 @@ public class EditProfileUi : WindowMediatorSubscriberBase
} }
protected override void DrawInternal() protected override void DrawInternal()
{
DrawProfileContent();
}
public void DrawInline()
{
DrawProfileContent();
}
private void DrawProfileContent()
{ {
_uiSharedService.BigText("Current Profile (as saved on server)"); _uiSharedService.BigText("Current Profile (as saved on server)");
ImGuiHelpers.ScaledDummy(new Vector2(0f, ImGui.GetStyle().ItemSpacing.Y / 2)); ImGuiHelpers.ScaledDummy(new Vector2(0f, ImGui.GetStyle().ItemSpacing.Y / 2));

View File

@@ -144,12 +144,12 @@ public partial class IntroUi : WindowMediatorSubscriberBase
} }
ImGui.Separator(); ImGui.Separator();
UiSharedService.SetFontScale(1.5f); ImGui.SetWindowFontScale(1.5f);
string readThis = "MERCI DE LIRE ATTENTIVEMENT"; string readThis = "MERCI DE LIRE ATTENTIVEMENT";
Vector2 textSize = ImGui.CalcTextSize(readThis); Vector2 textSize = ImGui.CalcTextSize(readThis);
ImGui.SetCursorPosX(ImGui.GetWindowSize().X / 2 - textSize.X / 2); ImGui.SetCursorPosX(ImGui.GetWindowSize().X / 2 - textSize.X / 2);
UiSharedService.ColorText(readThis, UiSharedService.AccentColor); UiSharedService.ColorText(readThis, UiSharedService.AccentColor);
UiSharedService.SetFontScale(1.0f); ImGui.SetWindowFontScale(1.0f);
ImGui.Separator(); ImGui.Separator();
UiSharedService.TextWrapped(""" UiSharedService.TextWrapped("""
Pour utiliser les services UmbraSync, vous devez être â de plus de 18 ans, plus de 21 ans dans certaines juridictions. Pour utiliser les services UmbraSync, vous devez être â de plus de 18 ans, plus de 21 ans dans certaines juridictions.

View File

@@ -16,22 +16,25 @@ namespace MareSynchronos.UI;
public class PopoutProfileUi : WindowMediatorSubscriberBase public class PopoutProfileUi : WindowMediatorSubscriberBase
{ {
private readonly MareProfileManager _mareProfileManager; private readonly MareProfileManager _mareProfileManager;
private readonly PairManager _pairManager;
private readonly ServerConfigurationManager _serverManager; private readonly ServerConfigurationManager _serverManager;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private Vector2 _lastMainPos = Vector2.Zero; private Vector2 _lastMainPos = Vector2.Zero;
private Vector2 _lastMainSize = Vector2.Zero; private Vector2 _lastMainSize = Vector2.Zero;
private byte[] _lastProfilePicture = []; private byte[] _lastProfilePicture = [];
private byte[] _lastSupporterPicture = [];
private Pair? _pair; private Pair? _pair;
private IDalamudTextureWrap? _supporterTextureWrap; private IDalamudTextureWrap? _supporterTextureWrap;
private IDalamudTextureWrap? _textureWrap; private IDalamudTextureWrap? _textureWrap;
public PopoutProfileUi(ILogger<PopoutProfileUi> logger, MareMediator mediator, UiSharedService uiSharedService, public PopoutProfileUi(ILogger<PopoutProfileUi> logger, MareMediator mediator, UiSharedService uiSharedService,
ServerConfigurationManager serverManager, MareConfigService mareConfigService, ServerConfigurationManager serverManager, MareConfigService mareConfigService,
MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService) : base(logger, mediator, "###UmbraSyncPopoutProfileUI", performanceCollectorService) MareProfileManager mareProfileManager, PairManager pairManager, PerformanceCollectorService performanceCollectorService) : base(logger, mediator, "###UmbraSyncPopoutProfileUI", performanceCollectorService)
{ {
_uiSharedService = uiSharedService; _uiSharedService = uiSharedService;
_serverManager = serverManager; _serverManager = serverManager;
_mareProfileManager = mareProfileManager; _mareProfileManager = mareProfileManager;
_pairManager = pairManager;
Flags = ImGuiWindowFlags.NoDecoration; Flags = ImGuiWindowFlags.NoDecoration;
Mediator.Subscribe<ProfilePopoutToggle>(this, (msg) => Mediator.Subscribe<ProfilePopoutToggle>(this, (msg) =>
@@ -39,6 +42,7 @@ public class PopoutProfileUi : WindowMediatorSubscriberBase
IsOpen = msg.Pair != null; IsOpen = msg.Pair != null;
_pair = msg.Pair; _pair = msg.Pair;
_lastProfilePicture = []; _lastProfilePicture = [];
_lastSupporterPicture = [];
_textureWrap?.Dispose(); _textureWrap?.Dispose();
_textureWrap = null; _textureWrap = null;
_supporterTextureWrap?.Dispose(); _supporterTextureWrap?.Dispose();

View File

@@ -52,8 +52,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
private readonly AccountRegistrationService _registerService; private readonly AccountRegistrationService _registerService;
private readonly ServerConfigurationManager _serverConfigurationManager; private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly UiSharedService _uiShared; private readonly UiSharedService _uiShared;
private readonly TypingIndicatorStateService _typingStateService;
private readonly ChatTypingDetectionService _chatTypingDetectionService;
private static readonly string DtrDefaultPreviewText = DtrEntry.DefaultGlyph + " 123"; private static readonly string DtrDefaultPreviewText = DtrEntry.DefaultGlyph + " 123";
private bool _deleteAccountPopupModalShown = false; private bool _deleteAccountPopupModalShown = false;
private string _lastTab = string.Empty; private string _lastTab = string.Empty;
@@ -82,9 +80,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
FileCompactor fileCompactor, ApiController apiController, FileCompactor fileCompactor, ApiController apiController,
IpcManager ipcManager, IpcProvider ipcProvider, CacheMonitor cacheMonitor, IpcManager ipcManager, IpcProvider ipcProvider, CacheMonitor cacheMonitor,
DalamudUtilService dalamudUtilService, AccountRegistrationService registerService, DalamudUtilService dalamudUtilService, AccountRegistrationService registerService,
AutoDetectSuppressionService autoDetectSuppressionService, AutoDetectSuppressionService autoDetectSuppressionService) : base(logger, mediator, "Umbra Settings", performanceCollector)
TypingIndicatorStateService typingIndicatorStateService,
ChatTypingDetectionService chatTypingDetectionService) : base(logger, mediator, "Umbra Settings", performanceCollector)
{ {
_configService = configService; _configService = configService;
_pairManager = pairManager; _pairManager = pairManager;
@@ -106,8 +102,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
_autoDetectSuppressionService = autoDetectSuppressionService; _autoDetectSuppressionService = autoDetectSuppressionService;
_fileCompactor = fileCompactor; _fileCompactor = fileCompactor;
_uiShared = uiShared; _uiShared = uiShared;
_typingStateService = typingIndicatorStateService;
_chatTypingDetectionService = chatTypingDetectionService;
AllowClickthrough = false; AllowClickthrough = false;
AllowPinning = false; AllowPinning = false;
_validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v); _validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v);
@@ -137,11 +131,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
DrawSettingsContent(); DrawSettingsContent();
} }
public void DrawInline()
{
DrawSettingsContent();
}
public override void OnClose() public override void OnClose()
{ {
_uiShared.EditTrackerPosition = false; _uiShared.EditTrackerPosition = false;
@@ -540,9 +529,9 @@ public class SettingsUi : WindowMediatorSubscriberBase
}, globalChatTypeIdx); }, globalChatTypeIdx);
_uiShared.DrawHelpText("FFXIV chat channel to output chat messages on."); _uiShared.DrawHelpText("FFXIV chat channel to output chat messages on.");
UiSharedService.SetFontScale(0.6f); ImGui.SetWindowFontScale(0.6f);
_uiShared.BigText("\"Chat 2\" Plugin Integration"); _uiShared.BigText("\"Chat 2\" Plugin Integration");
UiSharedService.SetFontScale(1.0f); ImGui.SetWindowFontScale(1.0f);
var extraChatTags = _configService.Current.ExtraChatTags; var extraChatTags = _configService.Current.ExtraChatTags;
if (ImGui.Checkbox("Tag messages as ExtraChat", ref extraChatTags)) if (ImGui.Checkbox("Tag messages as ExtraChat", ref extraChatTags))
@@ -583,9 +572,9 @@ public class SettingsUi : WindowMediatorSubscriberBase
if (shellEnabled) if (shellEnabled)
shellName = $"[{shellNumber}] {shellName}"; shellName = $"[{shellNumber}] {shellName}";
UiSharedService.SetFontScale(0.6f); ImGui.SetWindowFontScale(0.6f);
_uiShared.BigText(shellName); _uiShared.BigText(shellName);
UiSharedService.SetFontScale(1.0f); ImGui.SetWindowFontScale(1.0f);
using var pushIndent = ImRaii.PushIndent(); using var pushIndent = ImRaii.PushIndent();
@@ -1129,10 +1118,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
var useNameColors = _configService.Current.UseNameColors; var useNameColors = _configService.Current.UseNameColors;
var nameColors = _configService.Current.NameColors; var nameColors = _configService.Current.NameColors;
var autoPausedNameColors = _configService.Current.BlockedNameColors; var autoPausedNameColors = _configService.Current.BlockedNameColors;
var typingEnabled = _configService.Current.TypingIndicatorEnabled;
var typingIndicatorNameplates = _configService.Current.TypingIndicatorShowOnNameplates; var typingIndicatorNameplates = _configService.Current.TypingIndicatorShowOnNameplates;
var typingIndicatorPartyList = _configService.Current.TypingIndicatorShowOnPartyList; var typingIndicatorPartyList = _configService.Current.TypingIndicatorShowOnPartyList;
var typingShowSelf = _configService.Current.TypingIndicatorShowSelf;
if (ImGui.Checkbox("Coloriser les plaques de nom des paires", ref useNameColors)) if (ImGui.Checkbox("Coloriser les plaques de nom des paires", ref useNameColors))
{ {
_configService.Current.UseNameColors = useNameColors; _configService.Current.UseNameColors = useNameColors;
@@ -1160,16 +1147,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
} }
} }
if (ImGui.Checkbox("Activer le système d'indicateur de frappe", ref typingEnabled))
{
_configService.Current.TypingIndicatorEnabled = typingEnabled;
_configService.Save();
_chatTypingDetectionService.SoftRestart();
}
_uiShared.DrawHelpText("Active ou désactive complètement l'envoi/la réception et l'affichage des bulles de frappe.");
if (typingEnabled)
{
if (ImGui.Checkbox("Afficher la bulle de frappe sur les plaques", ref typingIndicatorNameplates)) if (ImGui.Checkbox("Afficher la bulle de frappe sur les plaques", ref typingIndicatorNameplates))
{ {
_configService.Current.TypingIndicatorShowOnNameplates = typingIndicatorNameplates; _configService.Current.TypingIndicatorShowOnNameplates = typingIndicatorNameplates;
@@ -1177,7 +1154,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
} }
_uiShared.DrawHelpText("Ajoute une bulle '...' sur la plaque des paires en train d'écrire."); _uiShared.DrawHelpText("Ajoute une bulle '...' sur la plaque des paires en train d'écrire.");
if (typingIndicatorNameplates) using (ImRaii.Disabled(!typingIndicatorNameplates))
{ {
using var indentTyping = ImRaii.PushIndent(); using var indentTyping = ImRaii.PushIndent();
var bubbleSize = _configService.Current.TypingIndicatorBubbleSize; var bubbleSize = _configService.Current.TypingIndicatorBubbleSize;
@@ -1198,6 +1175,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
_configService.Current.TypingIndicatorBubbleSize = selectedBubbleSize.Value; _configService.Current.TypingIndicatorBubbleSize = selectedBubbleSize.Value;
_configService.Save(); _configService.Save();
} }
}
if (ImGui.Checkbox("Tracer la frappe dans la liste de groupe", ref typingIndicatorPartyList)) if (ImGui.Checkbox("Tracer la frappe dans la liste de groupe", ref typingIndicatorPartyList))
{ {
@@ -1206,15 +1184,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
} }
_uiShared.DrawHelpText("Consigne dans les journaux quand une paire du groupe est en train d'écrire (bulle visuelle ultérieure)."); _uiShared.DrawHelpText("Consigne dans les journaux quand une paire du groupe est en train d'écrire (bulle visuelle ultérieure).");
if (ImGui.Checkbox("Afficher ma propre bulle", ref typingShowSelf))
{
_configService.Current.TypingIndicatorShowSelf = typingShowSelf;
_configService.Save();
}
_uiShared.DrawHelpText("Affiche votre propre bulle lorsque vous tapez (utile pour test/retour visuel).");
}
}
if (ImGui.Checkbox("Show separate Visible group", ref showVisibleSeparate)) if (ImGui.Checkbox("Show separate Visible group", ref showVisibleSeparate))
{ {
_configService.Current.ShowVisibleUsersSeparately = showVisibleSeparate; _configService.Current.ShowVisibleUsersSeparately = showVisibleSeparate;

View File

@@ -16,6 +16,7 @@ namespace MareSynchronos.UI;
public class StandaloneProfileUi : WindowMediatorSubscriberBase public class StandaloneProfileUi : WindowMediatorSubscriberBase
{ {
private readonly MareProfileManager _mareProfileManager; private readonly MareProfileManager _mareProfileManager;
private readonly PairManager _pairManager;
private readonly ServerConfigurationManager _serverManager; private readonly ServerConfigurationManager _serverManager;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private bool _adjustedForScrollBars = false; private bool _adjustedForScrollBars = false;
@@ -23,7 +24,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
private IDalamudTextureWrap? _textureWrap; private IDalamudTextureWrap? _textureWrap;
public StandaloneProfileUi(ILogger<StandaloneProfileUi> logger, MareMediator mediator, UiSharedService uiBuilder, 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) PerformanceCollectorService performanceCollector)
: base(logger, mediator, "Profile of " + pair.UserData.AliasOrUID + "##UmbraSyncStandaloneProfileUI" + pair.UserData.AliasOrUID, performanceCollector) : base(logger, mediator, "Profile of " + pair.UserData.AliasOrUID + "##UmbraSyncStandaloneProfileUI" + pair.UserData.AliasOrUID, performanceCollector)
{ {
@@ -31,6 +32,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
_serverManager = serverManager; _serverManager = serverManager;
_mareProfileManager = mareProfileManager; _mareProfileManager = mareProfileManager;
Pair = pair; Pair = pair;
_pairManager = pairManager;
Flags = ImGuiWindowFlags.NoResize | ImGuiWindowFlags.AlwaysAutoResize; Flags = ImGuiWindowFlags.NoResize | ImGuiWindowFlags.AlwaysAutoResize;
var spacing = ImGui.GetStyle().ItemSpacing; var spacing = ImGui.GetStyle().ItemSpacing;

View File

@@ -3,21 +3,15 @@ using Dalamud.Interface;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using System;
using MareSynchronos.API.Data.Enum; using MareSynchronos.API.Data.Enum;
using MareSynchronos.API.Data.Extensions; using MareSynchronos.API.Data.Extensions;
using MareSynchronos.API.Dto.Group; using MareSynchronos.API.Dto.Group;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.AutoDetect;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.Notifications;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Globalization; using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using MareSynchronos.MareConfiguration.Models;
namespace MareSynchronos.UI.Components.Popup; namespace MareSynchronos.UI.Components.Popup;
@@ -29,8 +23,6 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
private readonly List<string> _oneTimeInvites = []; private readonly List<string> _oneTimeInvites = [];
private readonly PairManager _pairManager; private readonly PairManager _pairManager;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly SyncshellDiscoveryService _syncshellDiscoveryService;
private readonly NotificationTracker _notificationTracker;
private List<BannedGroupUserDto> _bannedUsers = []; private List<BannedGroupUserDto> _bannedUsers = [];
private int _multiInvites; private int _multiInvites;
private string _newPassword; private string _newPassword;
@@ -38,43 +30,20 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
private Task<int>? _pruneTestTask; private Task<int>? _pruneTestTask;
private Task<int>? _pruneTask; private Task<int>? _pruneTask;
private int _pruneDays = 14; private int _pruneDays = 14;
private bool _autoDetectStateInitialized;
private bool _autoDetectStateLoading;
private bool _autoDetectToggleInFlight;
private bool _autoDetectVisible;
private bool _autoDetectPasswordDisabled;
private string? _autoDetectMessage;
private bool _autoDetectDesiredVisibility;
private int _adDurationHours = 2;
private bool _adRecurring = false;
private readonly bool[] _adWeekdays = new bool[7];
private int _adStartHour = 21;
private int _adStartMinute = 0;
private int _adEndHour = 23;
private int _adEndMinute = 0;
private const string AutoDetectTimeZone = "Europe/Paris";
public SyncshellAdminUI(ILogger<SyncshellAdminUI> logger, MareMediator mediator, ApiController apiController, public SyncshellAdminUI(ILogger<SyncshellAdminUI> logger, MareMediator mediator, ApiController apiController,
UiSharedService uiSharedService, PairManager pairManager, SyncshellDiscoveryService syncshellDiscoveryService, UiSharedService uiSharedService, PairManager pairManager, GroupFullInfoDto groupFullInfo, PerformanceCollectorService performanceCollectorService)
GroupFullInfoDto groupFullInfo, PerformanceCollectorService performanceCollectorService, NotificationTracker notificationTracker)
: base(logger, mediator, "Syncshell Admin Panel (" + groupFullInfo.GroupAliasOrGID + ")", performanceCollectorService) : base(logger, mediator, "Syncshell Admin Panel (" + groupFullInfo.GroupAliasOrGID + ")", performanceCollectorService)
{ {
GroupFullInfo = groupFullInfo; GroupFullInfo = groupFullInfo;
_apiController = apiController; _apiController = apiController;
_uiSharedService = uiSharedService; _uiSharedService = uiSharedService;
_pairManager = pairManager; _pairManager = pairManager;
_syncshellDiscoveryService = syncshellDiscoveryService;
_notificationTracker = notificationTracker;
_isOwner = string.Equals(GroupFullInfo.OwnerUID, _apiController.UID, StringComparison.Ordinal); _isOwner = string.Equals(GroupFullInfo.OwnerUID, _apiController.UID, StringComparison.Ordinal);
_isModerator = GroupFullInfo.GroupUserInfo.IsModerator(); _isModerator = GroupFullInfo.GroupUserInfo.IsModerator();
_newPassword = string.Empty; _newPassword = string.Empty;
_multiInvites = 30; _multiInvites = 30;
_pwChangeSuccess = true; _pwChangeSuccess = true;
_autoDetectVisible = groupFullInfo.AutoDetectVisible;
_autoDetectDesiredVisibility = _autoDetectVisible;
_autoDetectPasswordDisabled = groupFullInfo.PasswordTemporarilyDisabled;
Mediator.Subscribe<SyncshellAutoDetectStateChanged>(this, OnSyncshellAutoDetectStateChanged);
IsOpen = true; IsOpen = true;
SizeConstraints = new WindowSizeConstraints() SizeConstraints = new WindowSizeConstraints()
{ {
@@ -90,11 +59,6 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
if (!_isModerator && !_isOwner) return; if (!_isModerator && !_isOwner) return;
GroupFullInfo = _pairManager.Groups[GroupFullInfo.Group]; GroupFullInfo = _pairManager.Groups[GroupFullInfo.Group];
if (!_autoDetectToggleInFlight && !_autoDetectStateLoading)
{
_autoDetectVisible = GroupFullInfo.AutoDetectVisible;
_autoDetectPasswordDisabled = GroupFullInfo.PasswordTemporarilyDisabled;
}
using var id = ImRaii.PushId("syncshell_admin_" + GroupFullInfo.GID); using var id = ImRaii.PushId("syncshell_admin_" + GroupFullInfo.GID);
@@ -104,9 +68,6 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
ImGui.Separator(); ImGui.Separator();
var perm = GroupFullInfo.GroupPermissions; var perm = GroupFullInfo.GroupPermissions;
using var tabColor = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.AccentColor);
using var tabHoverColor = ImRaii.PushColor(ImGuiCol.TabHovered, UiSharedService.AccentHoverColor);
using var tabActiveColor = ImRaii.PushColor(ImGuiCol.TabActive, UiSharedService.AccentActiveColor);
using var tabbar = ImRaii.TabBar("syncshell_tab_" + GroupFullInfo.GID); using var tabbar = ImRaii.TabBar("syncshell_tab_" + GroupFullInfo.GID);
if (tabbar) if (tabbar)
@@ -402,13 +363,6 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
} }
mgmtTab.Dispose(); mgmtTab.Dispose();
var discoveryTab = ImRaii.TabItem("AutoDetect");
if (discoveryTab)
{
DrawAutoDetectTab();
}
discoveryTab.Dispose();
var permissionTab = ImRaii.TabItem("Permissions"); var permissionTab = ImRaii.TabItem("Permissions");
if (permissionTab) if (permissionTab)
{ {
@@ -494,283 +448,8 @@ 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);
}
using (ImRaii.Disabled(_autoDetectToggleInFlight || _autoDetectStateLoading))
{
if (ImGui.Checkbox("Afficher cette Syncshell dans l'AutoDetect", ref _autoDetectDesiredVisibility))
{
// Only change local desired state; sending is done via the validate button
}
}
_uiSharedService.DrawHelpText("Quand cette option est activée, le mot de passe devient inactif tant que la visibilité est maintenue.");
if (_autoDetectDesiredVisibility)
{
ImGuiHelpers.ScaledDummy(4);
ImGui.TextUnformatted("Options d'affichage AutoDetect");
ImGui.Separator();
// Recurring toggle first
ImGui.Checkbox("Affichage récurrent", ref _adRecurring);
_uiSharedService.DrawHelpText("Si activé, vous pouvez choisir les jours et une plage horaire récurrents. Si désactivé, seule la durée sera prise en compte.");
// Duration in hours (only when NOT recurring)
if (!_adRecurring)
{
ImGuiHelpers.ScaledDummy(4);
int duration = _adDurationHours;
ImGui.PushItemWidth(120 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("Durée (heures)", ref duration))
{
_adDurationHours = Math.Clamp(duration, 1, 240);
}
ImGui.PopItemWidth();
_uiSharedService.DrawHelpText("Combien de temps la Syncshell doit rester visible, en heures.");
}
ImGuiHelpers.ScaledDummy(4);
if (_adRecurring)
{
ImGuiHelpers.ScaledDummy(4);
ImGui.TextUnformatted("Jours de la semaine actifs :");
string[] daysFr = new[] { "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim" };
for (int i = 0; i < 7; i++)
{
ImGui.SameLine(i == 0 ? 0 : 0);
bool v = _adWeekdays[i];
if (ImGui.Checkbox($"##adwd{i}", ref v)) _adWeekdays[i] = v;
ImGui.SameLine();
ImGui.TextUnformatted(daysFr[i]);
if (i < 6) ImGui.SameLine();
}
ImGui.NewLine();
_uiSharedService.DrawHelpText("Sélectionnez les jours où l'affichage est autorisé (ex: jeudi et dimanche).");
ImGuiHelpers.ScaledDummy(4);
ImGui.TextUnformatted("Plage horaire (heure locale Europe/Paris) :");
ImGui.PushItemWidth(60 * ImGuiHelpers.GlobalScale);
ImGui.InputInt("Début heure", ref _adStartHour); ImGui.SameLine();
ImGui.InputInt("min", ref _adStartMinute);
_adStartHour = Math.Clamp(_adStartHour, 0, 23);
_adStartMinute = Math.Clamp(_adStartMinute, 0, 59);
ImGui.SameLine();
ImGui.TextUnformatted("→"); ImGui.SameLine();
ImGui.InputInt("Fin heure", ref _adEndHour); ImGui.SameLine();
ImGui.InputInt("min ", ref _adEndMinute);
_adEndHour = Math.Clamp(_adEndHour, 0, 23);
_adEndMinute = Math.Clamp(_adEndMinute, 0, 59);
ImGui.PopItemWidth();
_uiSharedService.DrawHelpText("Exemple : de 21h00 à 23h00. Le fuseau utilisé est Europe/Paris (avec changements été/hiver).");
}
}
if (_autoDetectPasswordDisabled && _autoDetectVisible)
{
UiSharedService.ColorTextWrapped("Le mot de passe est actuellement désactivé pendant la visibilité AutoDetect.", ImGuiColors.DalamudYellow);
}
ImGuiHelpers.ScaledDummy(6);
using (ImRaii.Disabled(_autoDetectToggleInFlight || _autoDetectStateLoading))
{
if (ImGui.Button("Valider et envoyer"))
{
_ = SubmitAutoDetectAsync();
}
ImGui.SameLine();
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.";
if (desiredVisibility)
{
PublishSyncshellPublicNotification();
}
}
catch (Exception ex)
{
_autoDetectMessage = $"Erreur lors de la mise à jour AutoDetect : {ex.Message}";
}
finally
{
_autoDetectToggleInFlight = false;
}
}
private async Task SubmitAutoDetectAsync()
{
if (_autoDetectToggleInFlight)
{
return;
}
_autoDetectToggleInFlight = true;
_autoDetectMessage = null;
try
{
// Duration always used when visible
int? duration = _autoDetectDesiredVisibility ? _adDurationHours : null;
// Scheduling fields only if recurring is enabled
int[]? weekdaysArr = null;
TimeSpan? start = null;
TimeSpan? end = null;
string? tz = null;
if (_autoDetectDesiredVisibility && _adRecurring)
{
List<int> weekdays = new();
for (int i = 0; i < 7; i++) if (_adWeekdays[i]) weekdays.Add(i);
weekdaysArr = weekdays.Count > 0 ? weekdays.ToArray() : Array.Empty<int>();
start = new TimeSpan(_adStartHour, _adStartMinute, 0);
end = new TimeSpan(_adEndHour, _adEndMinute, 0);
tz = AutoDetectTimeZone;
}
var ok = await _syncshellDiscoveryService.SetVisibilityAsync(
GroupFullInfo.GID,
_autoDetectDesiredVisibility,
duration,
weekdaysArr,
start,
end,
tz,
CancellationToken.None).ConfigureAwait(false);
if (!ok)
{
_autoDetectMessage = "Impossible d'envoyer les paramètres AutoDetect.";
return;
}
await EnsureAutoDetectStateAsync(true).ConfigureAwait(false);
_autoDetectMessage = _autoDetectDesiredVisibility
? "Paramètres AutoDetect envoyés. La Syncshell sera visible selon le planning défini."
: "La Syncshell n'est plus visible dans AutoDetect.";
if (_autoDetectDesiredVisibility)
{
PublishSyncshellPublicNotification();
}
}
catch (Exception ex)
{
_autoDetectMessage = $"Erreur lors de l'envoi des paramètres 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() public override void OnClose()
{ {
Mediator.Publish(new RemoveWindowMessage(this)); Mediator.Publish(new RemoveWindowMessage(this));
} }
private void PublishSyncshellPublicNotification()
{
try
{
var title = $"Syncshell publique: {GroupFullInfo.GroupAliasOrGID}";
var message = "La Syncshell est désormais visible via AutoDetect.";
Mediator.Publish(new DualNotificationMessage(title, message, NotificationType.Info, TimeSpan.FromSeconds(4)));
_notificationTracker.Upsert(NotificationEntry.SyncshellPublic(GroupFullInfo.GID, GroupFullInfo.GroupAliasOrGID));
}
catch
{
// swallow any notification errors to not break UI flow
}
}
} }

View File

@@ -7,7 +7,6 @@ using Dalamud.Interface.Utility;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using MareSynchronos.API.Data; using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Extensions; using MareSynchronos.API.Data.Extensions;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.MareConfiguration.Models; using MareSynchronos.MareConfiguration.Models;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
@@ -30,7 +29,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
private static readonly TimeSpan TypingDisplayDelay = TimeSpan.FromMilliseconds(500); private static readonly TimeSpan TypingDisplayDelay = TimeSpan.FromMilliseconds(500);
private static readonly TimeSpan TypingDisplayFade = TypingDisplayTime; private static readonly TimeSpan TypingDisplayFade = TypingDisplayTime;
private readonly ILogger<TypingIndicatorOverlay> _typedLogger; private readonly ILogger<TypingIndicatorOverlay> _logger;
private readonly MareConfigService _configService; private readonly MareConfigService _configService;
private readonly IGameGui _gameGui; private readonly IGameGui _gameGui;
private readonly ITextureProvider _textureProvider; private readonly ITextureProvider _textureProvider;
@@ -48,7 +47,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
TypingIndicatorStateService typingStateService, ApiController apiController) TypingIndicatorStateService typingStateService, ApiController apiController)
: base(logger, mediator, nameof(TypingIndicatorOverlay), performanceCollectorService) : base(logger, mediator, nameof(TypingIndicatorOverlay), performanceCollectorService)
{ {
_typedLogger = logger; _logger = logger;
_configService = configService; _configService = configService;
_gameGui = gameGui; _gameGui = gameGui;
_textureProvider = textureProvider; _textureProvider = textureProvider;
@@ -76,10 +75,6 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
if (!_clientState.IsLoggedIn) if (!_clientState.IsLoggedIn)
return; return;
var typingEnabled = _configService.Current.TypingIndicatorEnabled;
if (!typingEnabled)
return;
var showParty = _configService.Current.TypingIndicatorShowOnPartyList; var showParty = _configService.Current.TypingIndicatorShowOnPartyList;
var showNameplates = _configService.Current.TypingIndicatorShowOnNameplates; var showNameplates = _configService.Current.TypingIndicatorShowOnNameplates;
@@ -102,16 +97,14 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
} }
} }
private unsafe void DrawPartyIndicators(ImDrawListPtr drawList, IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate, MareSynchronos.API.Data.Enum.TypingScope Scope)> activeTypers, private unsafe void DrawPartyIndicators(ImDrawListPtr drawList, IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate)> activeTypers,
bool selfActive, DateTime now, DateTime selfStart, DateTime selfLast) bool selfActive, DateTime now, DateTime selfStart, DateTime selfLast)
{ {
var partyAddon = (AtkUnitBase*)_gameGui.GetAddonByName("_PartyList", 1).Address; var partyAddon = (AtkUnitBase*)_gameGui.GetAddonByName("_PartyList", 1).Address;
if (partyAddon == null || !partyAddon->IsVisible) if (partyAddon == null || !partyAddon->IsVisible)
return; return;
var showSelf = _configService.Current.TypingIndicatorShowSelf;
if (selfActive if (selfActive
&& showSelf
&& (now - selfStart) >= TypingDisplayDelay && (now - selfStart) >= TypingDisplayDelay
&& (now - selfLast) <= TypingDisplayFade) && (now - selfLast) <= TypingDisplayFade)
{ {
@@ -187,16 +180,14 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 0.9f))); ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 0.9f)));
} }
private unsafe void DrawNameplateIndicators(ImDrawListPtr drawList, IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate, MareSynchronos.API.Data.Enum.TypingScope Scope)> activeTypers, private unsafe void DrawNameplateIndicators(ImDrawListPtr drawList, IReadOnlyDictionary<string, (UserData User, DateTime FirstSeen, DateTime LastUpdate)> activeTypers,
bool selfActive, DateTime now, DateTime selfStart, DateTime selfLast) bool selfActive, DateTime now, DateTime selfStart, DateTime selfLast)
{ {
var iconWrap = _textureProvider.GetFromGameIcon(NameplateIconId).GetWrapOrEmpty(); var iconWrap = _textureProvider.GetFromGameIcon(NameplateIconId).GetWrapOrEmpty();
if (iconWrap == null || iconWrap.Handle == IntPtr.Zero) if (iconWrap == null || iconWrap.Handle == IntPtr.Zero)
return; return;
var showSelf = _configService.Current.TypingIndicatorShowSelf;
if (selfActive if (selfActive
&& showSelf
&& _clientState.LocalPlayer != null && _clientState.LocalPlayer != null
&& (now - selfStart) >= TypingDisplayDelay && (now - selfStart) >= TypingDisplayDelay
&& (now - selfLast) <= TypingDisplayFade) && (now - selfLast) <= TypingDisplayFade)
@@ -221,22 +212,11 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
var pairName = pair?.PlayerName ?? entry.User.AliasOrUID ?? string.Empty; var pairName = pair?.PlayerName ?? entry.User.AliasOrUID ?? string.Empty;
var pairIdent = pair?.Ident ?? string.Empty; var pairIdent = pair?.Ident ?? string.Empty;
var isPartyMember = IsPartyMember(objectId, pairName); var isPartyMember = IsPartyMember(objectId, pairName);
// Enforce party-only visibility when the scope is Party/CrossParty
if (entry.Scope is TypingScope.Party or TypingScope.CrossParty)
{
if (!isPartyMember)
{
_typedLogger.LogTrace("TypingIndicator: suppressed non-party bubble for {uid} due to scope={scope}", uid, entry.Scope);
continue;
}
}
var isRelevantMember = IsPlayerRelevant(pair, isPartyMember); var isRelevantMember = IsPlayerRelevant(pair, isPartyMember);
if (objectId != uint.MaxValue && objectId != 0 && TryDrawNameplateBubble(drawList, iconWrap, objectId)) if (objectId != uint.MaxValue && objectId != 0 && TryDrawNameplateBubble(drawList, iconWrap, objectId))
{ {
_typedLogger.LogTrace("TypingIndicator: drew nameplate bubble for {uid} (objectId={objectId}, scope={scope})", uid, objectId, entry.Scope); _logger.LogTrace("TypingIndicator: drew nameplate bubble for {uid} (objectId={objectId})", uid, objectId);
continue; continue;
} }
@@ -246,29 +226,22 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
if (!isRelevantMember && !isNearby) if (!isRelevantMember && !isNearby)
continue; continue;
// For Party/CrossParty scope, do not draw fallback world icon for non-party even if nearby
if (entry.Scope is TypingScope.Party or TypingScope.CrossParty)
{
if (!isPartyMember)
continue;
}
if (pair == null) 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}, scope={scope})", _logger.LogTrace("TypingIndicator: fallback draw for {uid} (objectId={objectId}, name={name}, ident={ident})",
uid, objectId, pairName, pairIdent, entry.Scope); uid, objectId, pairName, pairIdent);
if (hasWorldPosition) if (hasWorldPosition)
{ {
DrawWorldFallbackIcon(drawList, iconWrap, worldPos); 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 else
{ {
_typedLogger.LogTrace("TypingIndicator: could not resolve position for {uid}", uid); _logger.LogTrace("TypingIndicator: could not resolve position for {uid}", uid);
} }
} }
} }
@@ -345,6 +318,26 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
if (!iconVisible) if (!iconVisible)
{ {
sizeScaleFactor = 2.5f;
var anchor = rootPosition + iconLocalPosition + new Vector2(iconDimensions.X * 0.5f, 0f);
var distanceOffset = new Vector2(0f, -16f + distance) * scaleVector;
if (iconNode->Height == 24)
{
distanceOffset.Y += 16f * scaleY;
}
distanceOffset.Y += 64f * scaleY;
var referenceSize = GetConfiguredBubbleSize(scaleX * sizeScaleFactor, scaleY * sizeScaleFactor, false, TypingIndicatorBubbleSize.Small);
var manualOffset = new Vector2(referenceSize.X * 2.00f, referenceSize.Y * 2.00f);
var iconSizeHidden = GetConfiguredBubbleSize(scaleX * sizeScaleFactor, scaleY * sizeScaleFactor, false);
var center = anchor + distanceOffset + manualOffset;
var topLeft = center - (iconSizeHidden / 2f);
drawList.AddImage(textureWrap.Handle, topLeft, topLeft + iconSizeHidden, Vector2.Zero, Vector2.One,
ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 0.95f)));
return true; return true;
} }
@@ -400,7 +393,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
{ {
if (TryGetWorldPosition(objectId, out position)) if (TryGetWorldPosition(objectId, out position))
{ {
_typedLogger.LogTrace("TypingIndicator: resolved by objectId {objectId}", objectId); _logger.LogTrace("TypingIndicator: resolved by objectId {objectId}", objectId);
return true; return true;
} }
@@ -409,7 +402,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
var name = pair.PlayerName; var name = pair.PlayerName;
if (!string.IsNullOrEmpty(name) && TryGetWorldPositionByName(name!, out position)) 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; return true;
} }
@@ -419,7 +412,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
var cached = _dalamudUtil.FindPlayerByNameHash(ident); var cached = _dalamudUtil.FindPlayerByNameHash(ident);
if (!string.IsNullOrEmpty(cached.Name) && TryGetWorldPositionByName(cached.Name, out position)) 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; return true;
} }
@@ -429,7 +422,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
if (objRef != null) if (objRef != null)
{ {
position = objRef.Position; 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; return true;
} }
} }
@@ -439,7 +432,7 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
var alias = userData.AliasOrUID; var alias = userData.AliasOrUID;
if (!string.IsNullOrEmpty(alias) && TryGetWorldPositionByName(alias, out position)) 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; return true;
} }

View File

@@ -20,8 +20,6 @@ using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@@ -38,8 +36,6 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollbar |
ImGuiWindowFlags.NoScrollWithMouse; ImGuiWindowFlags.NoScrollWithMouse;
public const float ContentFontScale = 0.92f;
public static Vector4 AccentColor { get; set; } = ImGuiColors.DalamudViolet; 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 AccentHoverColor { get; set; } = new Vector4(0x3A / 255f, 0x15 / 255f, 0x50 / 255f, 1f);
public static Vector4 AccentActiveColor { get; set; } = AccentHoverColor; 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 Dictionary<string, object> _selectedComboItems = new(StringComparer.Ordinal);
private readonly ServerConfigurationManager _serverConfigurationManager; private readonly ServerConfigurationManager _serverConfigurationManager;
private bool _cacheDirectoryHasOtherFilesThanCache = false; private bool _cacheDirectoryHasOtherFilesThanCache = false;
private static readonly Stack<float> _fontScaleStack = new();
private static float _currentWindowFontScale = 1f;
private bool _cacheDirectoryIsValidPath = true; private bool _cacheDirectoryIsValidPath = true;
@@ -123,12 +117,8 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
{ {
e.OnPreBuild(tk => tk.AddDalamudAssetFont(Dalamud.DalamudAsset.NotoSansJpMedium, new() e.OnPreBuild(tk => tk.AddDalamudAssetFont(Dalamud.DalamudAsset.NotoSansJpMedium, new()
{ {
SizePx = 27, SizePx = 35,
GlyphRanges = [ GlyphRanges = [0x20, 0x7E, 0]
0x0020, 0x007E,
0x00A0, 0x017F,
0
]
})); }));
}); });
GameFont = _pluginInterface.UiBuilder.FontAtlas.NewGameFontHandle(new(GameFontFamilyAndSize.Axis12)); 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 bool CtrlPressed() => (GetKeyState(0xA2) & 0x8000) != 0 || (GetKeyState(0xA3) & 0x8000) != 0;
public static IDisposable PushFontScale(float scale) public static void DrawGrouped(Action imguiDrawAction, float rounding = 5f, float? expectedWidth = null)
{
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)
{ {
var cursorPos = ImGui.GetCursorPos(); var cursorPos = ImGui.GetCursorPos();
using (ImRaii.Group()) using (ImRaii.Group())
@@ -271,129 +230,11 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
imguiDrawAction.Invoke(); imguiDrawAction.Invoke();
} }
if (drawBorder)
{
ImGui.GetWindowDrawList().AddRect( ImGui.GetWindowDrawList().AddRect(
ImGui.GetItemRectMin() - ImGui.GetStyle().ItemInnerSpacing, ImGui.GetItemRectMin() - ImGui.GetStyle().ItemInnerSpacing,
ImGui.GetItemRectMax() + ImGui.GetStyle().ItemInnerSpacing, ImGui.GetItemRectMax() + ImGui.GetStyle().ItemInnerSpacing,
Color(ImGuiColors.DalamudGrey2), rounding); 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;
}
public static void DrawGroupedCenteredColorText(string text, Vector4 color, float? maxWidth = null) public static void DrawGroupedCenteredColorText(string text, Vector4 color, float? maxWidth = null)
{ {
@@ -542,100 +383,6 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
return result; return result;
} }
public bool IconButtonCentered(FontAwesomeIcon icon, float? height = null, float xOffset = 0f, float yOffset = 0f, bool square = false)
{
string text = icon.ToIconString();
ImGui.PushID($"centered-{text}");
Vector2 glyphSize;
using (IconFont.Push())
glyphSize = ImGui.CalcTextSize(text);
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float frameHeight = height ?? ImGui.GetFrameHeight();
float buttonWidth = square ? frameHeight : glyphSize.X + ImGui.GetStyle().FramePadding.X * 2f;
using var hoverColor = ImRaii.PushColor(ImGuiCol.ButtonHovered, AccentHoverColor);
using var activeColor = ImRaii.PushColor(ImGuiCol.ButtonActive, AccentActiveColor);
bool clicked = ImGui.Button(string.Empty, new Vector2(buttonWidth, frameHeight));
Vector2 pos = new Vector2(
cursorScreenPos.X + (buttonWidth - glyphSize.X) / 2f + xOffset,
cursorScreenPos.Y + frameHeight / 2f - glyphSize.Y / 2f + yOffset);
using (IconFont.Push())
drawList.AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), text);
ImGui.PopID();
return clicked;
}
public bool IconPauseButtonCentered(float? height = null)
{
ImGui.PushID("centered-pause-custom");
Vector2 glyphSize;
using (IconFont.Push())
glyphSize = ImGui.CalcTextSize(FontAwesomeIcon.Pause.ToIconString());
float frameHeight = height ?? ImGui.GetFrameHeight();
float buttonWidth = glyphSize.X + ImGui.GetStyle().FramePadding.X * 2f;
using var hoverColor = ImRaii.PushColor(ImGuiCol.ButtonHovered, AccentHoverColor);
using var activeColor = ImRaii.PushColor(ImGuiCol.ButtonActive, AccentActiveColor);
var drawList = ImGui.GetWindowDrawList();
var buttonTopLeft = ImGui.GetCursorScreenPos();
bool clicked = ImGui.Button(string.Empty, new Vector2(buttonWidth, frameHeight));
var textColor = ImGui.GetColorU32(ImGuiCol.Text);
float h = frameHeight * 0.55f; // bar height
float w = MathF.Max(1f, frameHeight * 0.16f); // bar width
float gap = MathF.Max(1f, w * 0.9f); // gap between bars
float total = 2f * w + gap;
float startX = buttonTopLeft.X + (buttonWidth - total) / 2f;
float startY = buttonTopLeft.Y + (frameHeight - h) / 2f;
float rounding = w * 0.35f;
drawList.AddRectFilled(new Vector2(startX, startY), new Vector2(startX + w, startY + h), textColor, rounding);
float rightX = startX + w + gap;
drawList.AddRectFilled(new Vector2(rightX, startY), new Vector2(rightX + w, startY + h), textColor, rounding);
ImGui.PopID();
return clicked;
}
public bool IconPlusButtonCentered(float? height = null)
{
ImGui.PushID("centered-plus-custom");
Vector2 glyphSize;
using (IconFont.Push())
glyphSize = ImGui.CalcTextSize(FontAwesomeIcon.Plus.ToIconString());
float frameHeight = height ?? ImGui.GetFrameHeight();
float buttonWidth = glyphSize.X + ImGui.GetStyle().FramePadding.X * 2f;
using var hoverColor = ImRaii.PushColor(ImGuiCol.ButtonHovered, AccentHoverColor);
using var activeColor = ImRaii.PushColor(ImGuiCol.ButtonActive, AccentActiveColor);
var drawList = ImGui.GetWindowDrawList();
var buttonTopLeft = ImGui.GetCursorScreenPos();
bool clicked = ImGui.Button(string.Empty, new Vector2(buttonWidth, frameHeight));
var color = ImGui.GetColorU32(ImGuiCol.Text);
float armThickness = MathF.Max(1f, frameHeight * 0.14f);
float crossSize = frameHeight * 0.55f; // total length of vertical/horizontal arms
float startX = buttonTopLeft.X + (buttonWidth - crossSize) / 2f;
float startY = buttonTopLeft.Y + (frameHeight - crossSize) / 2f;
float endX = startX + crossSize;
float endY = startY + crossSize;
float r = armThickness * 0.35f;
float hY1 = startY + (crossSize - armThickness) / 2f;
drawList.AddRectFilled(new Vector2(startX, hY1), new Vector2(endX, hY1 + armThickness), color, r);
float vX1 = startX + (crossSize - armThickness) / 2f;
drawList.AddRectFilled(new Vector2(vX1, startY), new Vector2(vX1 + armThickness, endY), color, r);
ImGui.PopID();
return clicked;
}
private bool IconTextButtonInternal(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, float? width = null, bool useAccentHover = true) private bool IconTextButtonInternal(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, float? width = null, bool useAccentHover = true)
{ {
int colorsPushed = 0; int colorsPushed = 0;
@@ -1028,9 +775,9 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
if (intro) if (intro)
{ {
SetFontScale(0.8f); ImGui.SetWindowFontScale(0.8f);
BigText("Mandatory Plugins"); BigText("Mandatory Plugins");
SetFontScale(1.0f); ImGui.SetWindowFontScale(1.0f);
} }
else else
{ {
@@ -1051,9 +798,9 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
if (intro) if (intro)
{ {
SetFontScale(0.8f); ImGui.SetWindowFontScale(0.8f);
BigText("Optional Addons"); 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."); UiSharedService.TextWrapped("These addons are not required for basic operation, but without them you may not see others as intended.");
} }
else else

View File

@@ -1,6 +1,6 @@
namespace MareSynchronos.Utils; 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[] _magicSignature = [137, 80, 78, 71, 13, 10, 26, 10];
private static readonly byte[] _IHDR = [(byte)'I', (byte)'H', (byte)'D', (byte)'R']; private static readonly byte[] _IHDR = [(byte)'I', (byte)'H', (byte)'D', (byte)'R'];

View File

@@ -4,6 +4,7 @@ using MareSynchronos.Services;
using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using MareSynchronos.WebAPI.SignalR; using MareSynchronos.WebAPI.SignalR;
using Microsoft.Extensions.Logging;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Reflection; using System.Reflection;
@@ -14,15 +15,17 @@ namespace MareSynchronos.WebAPI;
public sealed class AccountRegistrationService : IDisposable public sealed class AccountRegistrationService : IDisposable
{ {
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly ILogger<AccountRegistrationService> _logger;
private readonly ServerConfigurationManager _serverManager; private readonly ServerConfigurationManager _serverManager;
private static string GenerateSecretKey() private string GenerateSecretKey()
{ {
return Convert.ToHexString(SHA256.HashData(RandomNumberGenerator.GetBytes(64))); return Convert.ToHexString(SHA256.HashData(RandomNumberGenerator.GetBytes(64)));
} }
public AccountRegistrationService(ServerConfigurationManager serverManager) public AccountRegistrationService(ILogger<AccountRegistrationService> logger, ServerConfigurationManager serverManager)
{ {
_logger = logger;
_serverManager = serverManager; _serverManager = serverManager;
_httpClient = new( _httpClient = new(
new HttpClientHandler new HttpClientHandler

View File

@@ -17,16 +17,19 @@ namespace MareSynchronos.WebAPI.Files;
public sealed class FileUploadManager : DisposableMediatorSubscriberBase public sealed class FileUploadManager : DisposableMediatorSubscriberBase
{ {
private readonly FileCacheManager _fileDbManager; private readonly FileCacheManager _fileDbManager;
private readonly MareConfigService _mareConfigService;
private readonly FileTransferOrchestrator _orchestrator; private readonly FileTransferOrchestrator _orchestrator;
private readonly ServerConfigurationManager _serverManager; private readonly ServerConfigurationManager _serverManager;
private readonly Dictionary<string, DateTime> _verifiedUploadedHashes = new(StringComparer.Ordinal); private readonly Dictionary<string, DateTime> _verifiedUploadedHashes = new(StringComparer.Ordinal);
private CancellationTokenSource? _uploadCancellationTokenSource = new(); private CancellationTokenSource? _uploadCancellationTokenSource = new();
public FileUploadManager(ILogger<FileUploadManager> logger, MareMediator mediator, public FileUploadManager(ILogger<FileUploadManager> logger, MareMediator mediator,
MareConfigService mareConfigService,
FileTransferOrchestrator orchestrator, FileTransferOrchestrator orchestrator,
FileCacheManager fileDbManager, FileCacheManager fileDbManager,
ServerConfigurationManager serverManager) : base(logger, mediator) ServerConfigurationManager serverManager) : base(logger, mediator)
{ {
_mareConfigService = mareConfigService;
_orchestrator = orchestrator; _orchestrator = orchestrator;
_fileDbManager = fileDbManager; _fileDbManager = fileDbManager;
_serverManager = serverManager; _serverManager = serverManager;

View File

@@ -19,6 +19,6 @@ public class DownloadFileTransfer : FileTransfer
get => Dto.Size; get => Dto.Size;
} }
public long TotalRaw => Dto.Size; public long TotalRaw => 0; // XXX
private DownloadFileDto Dto => (DownloadFileDto)TransferDto; private DownloadFileDto Dto => (DownloadFileDto)TransferDto;
} }

View File

@@ -103,21 +103,6 @@ public partial class ApiController
await _mareHub!.SendAsync(nameof(UserSetTypingState), isTyping).ConfigureAwait(false); await _mareHub!.SendAsync(nameof(UserSetTypingState), isTyping).ConfigureAwait(false);
} }
public async Task UserSetTypingState(bool isTyping, MareSynchronos.API.Data.Enum.TypingScope scope)
{
CheckConnection();
try
{
await _mareHub!.SendAsync(nameof(UserSetTypingState), isTyping, scope).ConfigureAwait(false);
}
catch (Exception ex)
{
// fallback for older servers without scope support
Logger.LogDebug(ex, "UserSetTypingState(scope) not supported on server, falling back to legacy call");
await _mareHub!.SendAsync(nameof(UserSetTypingState), isTyping).ConfigureAwait(false);
}
}
private async Task PushCharacterDataInternal(CharacterData character, List<UserData> visibleCharacters) private async Task PushCharacterDataInternal(CharacterData character, List<UserData> visibleCharacters)
{ {
Logger.LogInformation("Pushing character data for {hash} to {charas}", character.DataHash.Value, string.Join(", ", visibleCharacters.Select(c => c.AliasOrUID))); Logger.LogInformation("Pushing character data for {hash} to {charas}", character.DataHash.Value, string.Join(", ", visibleCharacters.Select(c => c.AliasOrUID)));

View File

@@ -231,7 +231,7 @@ public partial class ApiController
public Task Client_GposeLobbyPushWorldData(UserData userData, WorldData worldData) 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))); ExecuteSafely(() => Mediator.Publish(new GPoseLobbyReceiveWorldData(userData, worldData)));
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -3,6 +3,8 @@ using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.WebAPI.SignalR.Utils; using MareSynchronos.WebAPI.SignalR.Utils;
using MessagePack;
using MessagePack.Resolvers;
using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -170,7 +172,25 @@ public class HubFactory : MediatorSubscriberBase
options.SkipNegotiation = hubConfig.SkipNegotiation && (transports == HttpTransportType.WebSockets); options.SkipNegotiation = hubConfig.SkipNegotiation && (transports == HttpTransportType.WebSockets);
options.Transports = transports; options.Transports = transports;
}) })
.AddJsonProtocol() .AddMessagePackProtocol(opt =>
{
var resolver = CompositeResolver.Create(StandardResolverAllowPrivate.Instance,
BuiltinResolver.Instance,
AttributeFormatterResolver.Instance,
// replace enum resolver
DynamicEnumAsStringResolver.Instance,
DynamicGenericResolver.Instance,
DynamicUnionResolver.Instance,
DynamicObjectResolver.Instance,
PrimitiveObjectResolver.Instance,
// final fallback(last priority)
StandardResolver.Instance);
opt.SerializerOptions =
MessagePackSerializerOptions.Standard
.WithCompression(MessagePackCompression.Lz4Block)
.WithResolver(resolver);
})
.WithAutomaticReconnect(new ForeverRetryPolicy(Mediator)) .WithAutomaticReconnect(new ForeverRetryPolicy(Mediator))
.ConfigureLogging(a => .ConfigureLogging(a =>
{ {