Update 0.1.8 - Fix interface & ajout syncshell perma/temp
This commit is contained in:
2
MareAPI
2
MareAPI
Submodule MareAPI updated: 7a48ca9823...fa9b7bce43
@@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>UmbraSync</AssemblyName>
|
<AssemblyName>UmbraSync</AssemblyName>
|
||||||
<RootNamespace>UmbraSync</RootNamespace>
|
<RootNamespace>UmbraSync</RootNamespace>
|
||||||
<Version>0.1.7.0</Version>
|
<Version>0.1.8.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -210,9 +210,16 @@ public sealed class PairManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
public void SetGroupInfo(GroupInfoDto dto)
|
public void SetGroupInfo(GroupInfoDto dto)
|
||||||
{
|
{
|
||||||
_allGroups[dto.Group].Group = dto.Group;
|
if (!_allGroups.TryGetValue(dto.Group, out var groupInfo))
|
||||||
_allGroups[dto.Group].Owner = dto.Owner;
|
{
|
||||||
_allGroups[dto.Group].GroupPermissions = dto.GroupPermissions;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
groupInfo.Group = dto.Group;
|
||||||
|
groupInfo.Owner = dto.Owner;
|
||||||
|
groupInfo.GroupPermissions = dto.GroupPermissions;
|
||||||
|
groupInfo.IsTemporary = dto.IsTemporary;
|
||||||
|
groupInfo.ExpiresAt = dto.ExpiresAt;
|
||||||
|
|
||||||
RecreateLazy();
|
RecreateLazy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<IpcCallerMare>();
|
collection.AddSingleton<IpcCallerMare>();
|
||||||
collection.AddSingleton<IpcManager>();
|
collection.AddSingleton<IpcManager>();
|
||||||
collection.AddSingleton<NotificationService>();
|
collection.AddSingleton<NotificationService>();
|
||||||
|
collection.AddSingleton<TemporarySyncshellNotificationService>();
|
||||||
|
|
||||||
collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName));
|
collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||||
collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName));
|
collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||||
@@ -203,6 +204,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddHostedService(p => p.GetRequiredService<ConfigurationSaveService>());
|
collection.AddHostedService(p => p.GetRequiredService<ConfigurationSaveService>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<MareMediator>());
|
collection.AddHostedService(p => p.GetRequiredService<MareMediator>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<NotificationService>());
|
collection.AddHostedService(p => p.GetRequiredService<NotificationService>());
|
||||||
|
collection.AddHostedService(p => p.GetRequiredService<TemporarySyncshellNotificationService>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<FileCacheManager>());
|
collection.AddHostedService(p => p.GetRequiredService<FileCacheManager>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<ConfigurationMigrator>());
|
collection.AddHostedService(p => p.GetRequiredService<ConfigurationMigrator>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<DalamudUtilService>());
|
collection.AddHostedService(p => p.GetRequiredService<DalamudUtilService>());
|
||||||
|
|||||||
225
MareSynchronos/Services/TemporarySyncshellNotificationService.cs
Normal file
225
MareSynchronos/Services/TemporarySyncshellNotificationService.cs
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Threading;
|
||||||
|
using MareSynchronos.API.Dto.Group;
|
||||||
|
using MareSynchronos.MareConfiguration.Models;
|
||||||
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
|
using MareSynchronos.Services.Mediator;
|
||||||
|
using MareSynchronos.WebAPI;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Services;
|
||||||
|
|
||||||
|
public sealed class TemporarySyncshellNotificationService : MediatorSubscriberBase, IHostedService
|
||||||
|
{
|
||||||
|
private static readonly int[] NotificationThresholdMinutes = [30, 15, 5, 1];
|
||||||
|
private readonly ApiController _apiController;
|
||||||
|
private readonly PairManager _pairManager;
|
||||||
|
private readonly Lock _stateLock = new();
|
||||||
|
private readonly Dictionary<string, TrackedGroup> _trackedGroups = new(StringComparer.Ordinal);
|
||||||
|
private CancellationTokenSource? _loopCts;
|
||||||
|
private Task? _loopTask;
|
||||||
|
|
||||||
|
public TemporarySyncshellNotificationService(ILogger<TemporarySyncshellNotificationService> logger, MareMediator mediator, PairManager pairManager, ApiController apiController)
|
||||||
|
: base(logger, mediator)
|
||||||
|
{
|
||||||
|
_pairManager = pairManager;
|
||||||
|
_apiController = apiController;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_loopCts = new CancellationTokenSource();
|
||||||
|
Mediator.Subscribe<ConnectedMessage>(this, _ => ResetTrackedGroups());
|
||||||
|
Mediator.Subscribe<DisconnectedMessage>(this, _ => ResetTrackedGroups());
|
||||||
|
_loopTask = Task.Run(() => MonitorLoopAsync(_loopCts.Token), _loopCts.Token);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Mediator.UnsubscribeAll(this);
|
||||||
|
if (_loopCts == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_loopCts.Cancel();
|
||||||
|
if (_loopTask != null)
|
||||||
|
{
|
||||||
|
await _loopTask.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loopTask = null;
|
||||||
|
_loopCts.Dispose();
|
||||||
|
_loopCts = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task MonitorLoopAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
var delay = TimeSpan.FromSeconds(30);
|
||||||
|
while (!ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CheckGroups();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogDebug(ex, "Failed to check temporary syncshell expirations");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(delay, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckGroups()
|
||||||
|
{
|
||||||
|
var nowUtc = DateTime.UtcNow;
|
||||||
|
var groupsSnapshot = _pairManager.Groups.Values.ToList();
|
||||||
|
var notifications = new List<NotificationPayload>();
|
||||||
|
var expiredGroups = new List<GroupFullInfoDto>();
|
||||||
|
var seenTemporaryGids = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
using (var guard = _stateLock.EnterScope())
|
||||||
|
{
|
||||||
|
foreach (var group in groupsSnapshot)
|
||||||
|
{
|
||||||
|
if (!group.IsTemporary || group.ExpiresAt == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_apiController.UID) || !string.Equals(group.OwnerUID, _apiController.UID, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gid = group.Group.GID;
|
||||||
|
seenTemporaryGids.Add(gid);
|
||||||
|
var expiresAtUtc = NormalizeToUtc(group.ExpiresAt.Value);
|
||||||
|
var remaining = expiresAtUtc - nowUtc;
|
||||||
|
|
||||||
|
if (!_trackedGroups.TryGetValue(gid, out var state))
|
||||||
|
{
|
||||||
|
state = new TrackedGroup(expiresAtUtc);
|
||||||
|
_trackedGroups[gid] = state;
|
||||||
|
}
|
||||||
|
else if (state.ExpiresAtUtc != expiresAtUtc)
|
||||||
|
{
|
||||||
|
state.UpdateExpiresAt(expiresAtUtc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
_trackedGroups.Remove(gid);
|
||||||
|
expiredGroups.Add(group);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.LastRemaining.HasValue)
|
||||||
|
{
|
||||||
|
state.UpdateRemaining(remaining);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousRemaining = state.LastRemaining.Value;
|
||||||
|
|
||||||
|
foreach (var thresholdMinutes in NotificationThresholdMinutes)
|
||||||
|
{
|
||||||
|
var threshold = TimeSpan.FromMinutes(thresholdMinutes);
|
||||||
|
if (previousRemaining > threshold && remaining <= threshold)
|
||||||
|
{
|
||||||
|
notifications.Add(new NotificationPayload(group, thresholdMinutes, expiresAtUtc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.UpdateRemaining(remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
var toRemove = _trackedGroups.Keys.Where(k => !seenTemporaryGids.Contains(k)).ToList();
|
||||||
|
foreach (var gid in toRemove)
|
||||||
|
{
|
||||||
|
_trackedGroups.Remove(gid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var expiredGroup in expiredGroups)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Temporary syncshell {gid} expired locally; removing", expiredGroup.Group.GID);
|
||||||
|
_pairManager.RemoveGroup(expiredGroup.Group);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var notification in notifications)
|
||||||
|
{
|
||||||
|
PublishNotification(notification.Group, notification.ThresholdMinutes, notification.ExpiresAtUtc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PublishNotification(GroupFullInfoDto group, int thresholdMinutes, DateTime expiresAtUtc)
|
||||||
|
{
|
||||||
|
string displayName = string.IsNullOrWhiteSpace(group.GroupAlias) ? group.Group.GID : group.GroupAlias!;
|
||||||
|
string threshold = thresholdMinutes == 1 ? "1 minute" : $"{thresholdMinutes} minutes";
|
||||||
|
string expiresLocal = expiresAtUtc.ToLocalTime().ToString("t", CultureInfo.CurrentCulture);
|
||||||
|
|
||||||
|
string message = $"La Syncshell temporaire \"{displayName}\" sera supprimee dans {threshold} (a {expiresLocal}).";
|
||||||
|
Mediator.Publish(new NotificationMessage("Syncshell temporaire", message, NotificationType.Warning, TimeSpan.FromSeconds(6)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateTime NormalizeToUtc(DateTime expiresAt)
|
||||||
|
{
|
||||||
|
return expiresAt.Kind switch
|
||||||
|
{
|
||||||
|
DateTimeKind.Utc => expiresAt,
|
||||||
|
DateTimeKind.Local => expiresAt.ToUniversalTime(),
|
||||||
|
_ => DateTime.SpecifyKind(expiresAt, DateTimeKind.Utc)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetTrackedGroups()
|
||||||
|
{
|
||||||
|
using (var guard = _stateLock.EnterScope())
|
||||||
|
{
|
||||||
|
_trackedGroups.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TrackedGroup
|
||||||
|
{
|
||||||
|
public TrackedGroup(DateTime expiresAtUtc)
|
||||||
|
{
|
||||||
|
ExpiresAtUtc = expiresAtUtc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime ExpiresAtUtc { get; private set; }
|
||||||
|
public TimeSpan? LastRemaining { get; private set; }
|
||||||
|
|
||||||
|
public void UpdateExpiresAt(DateTime expiresAtUtc)
|
||||||
|
{
|
||||||
|
ExpiresAtUtc = expiresAtUtc;
|
||||||
|
LastRemaining = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateRemaining(TimeSpan remaining)
|
||||||
|
{
|
||||||
|
LastRemaining = remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record NotificationPayload(GroupFullInfoDto Group, int ThresholdMinutes, DateTime ExpiresAtUtc);
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ using MareSynchronos.Services.ServerConfiguration;
|
|||||||
using MareSynchronos.UI.Components;
|
using MareSynchronos.UI.Components;
|
||||||
using MareSynchronos.UI.Handlers;
|
using MareSynchronos.UI.Handlers;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -54,6 +55,20 @@ internal sealed class GroupPanel
|
|||||||
private bool _showModalCreateGroup;
|
private bool _showModalCreateGroup;
|
||||||
private bool _showModalEnterPassword;
|
private bool _showModalEnterPassword;
|
||||||
private string _newSyncShellAlias = string.Empty;
|
private string _newSyncShellAlias = string.Empty;
|
||||||
|
private bool _createIsTemporary = false;
|
||||||
|
private int _tempSyncshellDurationHours = 24;
|
||||||
|
private readonly int[] _temporaryDurationOptions = new[]
|
||||||
|
{
|
||||||
|
1,
|
||||||
|
12,
|
||||||
|
24,
|
||||||
|
48,
|
||||||
|
72,
|
||||||
|
96,
|
||||||
|
120,
|
||||||
|
144,
|
||||||
|
168
|
||||||
|
};
|
||||||
private string _syncShellPassword = string.Empty;
|
private string _syncShellPassword = string.Empty;
|
||||||
private string _syncShellToJoin = string.Empty;
|
private string _syncShellToJoin = string.Empty;
|
||||||
|
|
||||||
@@ -111,6 +126,8 @@ internal sealed class GroupPanel
|
|||||||
_lastCreatedGroup = null;
|
_lastCreatedGroup = null;
|
||||||
_errorGroupCreate = false;
|
_errorGroupCreate = false;
|
||||||
_newSyncShellAlias = string.Empty;
|
_newSyncShellAlias = string.Empty;
|
||||||
|
_createIsTemporary = false;
|
||||||
|
_tempSyncshellDurationHours = 24;
|
||||||
_errorGroupCreateMessage = string.Empty;
|
_errorGroupCreateMessage = string.Empty;
|
||||||
_showModalCreateGroup = true;
|
_showModalCreateGroup = true;
|
||||||
ImGui.OpenPopup("Create Syncshell");
|
ImGui.OpenPopup("Create Syncshell");
|
||||||
@@ -153,28 +170,98 @@ internal sealed class GroupPanel
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginPopupModal("Create Syncshell", ref _showModalCreateGroup, UiSharedService.PopupWindowFlags))
|
if (ImGui.BeginPopupModal("Create Syncshell", ref _showModalCreateGroup, UiSharedService.PopupWindowFlags))
|
||||||
|
{
|
||||||
|
UiSharedService.TextWrapped("Choisissez le type de Syncshell à créer.");
|
||||||
|
bool showPermanent = !_createIsTemporary;
|
||||||
|
if (ImGui.RadioButton("Permanente", showPermanent))
|
||||||
|
{
|
||||||
|
_createIsTemporary = false;
|
||||||
|
}
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.RadioButton("Temporaire", _createIsTemporary))
|
||||||
|
{
|
||||||
|
_createIsTemporary = true;
|
||||||
|
_newSyncShellAlias = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_createIsTemporary)
|
||||||
{
|
{
|
||||||
UiSharedService.TextWrapped("Donnez un nom à votre Syncshell (optionnel) puis créez-la.");
|
UiSharedService.TextWrapped("Donnez un nom à votre Syncshell (optionnel) puis créez-la.");
|
||||||
ImGui.SetNextItemWidth(-1);
|
ImGui.SetNextItemWidth(-1);
|
||||||
ImGui.InputTextWithHint("##syncshellalias", "Nom du Syncshell", ref _newSyncShellAlias, 50);
|
ImGui.InputTextWithHint("##syncshellalias", "Nom du Syncshell", ref _newSyncShellAlias, 50);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_newSyncShellAlias = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_createIsTemporary)
|
||||||
|
{
|
||||||
|
UiSharedService.TextWrapped("Durée maximale d'une Syncshell temporaire : 7 jours.");
|
||||||
|
if (_tempSyncshellDurationHours > 168) _tempSyncshellDurationHours = 168;
|
||||||
|
for (int i = 0; i < _temporaryDurationOptions.Length; i++)
|
||||||
|
{
|
||||||
|
var option = _temporaryDurationOptions[i];
|
||||||
|
var isSelected = _tempSyncshellDurationHours == option;
|
||||||
|
string label = option switch
|
||||||
|
{
|
||||||
|
>= 24 when option % 24 == 0 => option == 24 ? "24h" : $"{option / 24}j",
|
||||||
|
_ => option + "h"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ImGui.RadioButton(label, isSelected))
|
||||||
|
{
|
||||||
|
_tempSyncshellDurationHours = option;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a new line after every 3 buttons
|
||||||
|
if ((i + 1) % 3 == 0)
|
||||||
|
{
|
||||||
|
ImGui.NewLine();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiresLocal = DateTime.Now.AddHours(_tempSyncshellDurationHours);
|
||||||
|
UiSharedService.TextWrapped($"Expiration le {expiresLocal:g} (heure locale).");
|
||||||
|
}
|
||||||
|
|
||||||
UiSharedService.TextWrapped("Appuyez sur le bouton ci-dessous pour créer une nouvelle Syncshell.");
|
UiSharedService.TextWrapped("Appuyez sur le bouton ci-dessous pour créer une nouvelle Syncshell.");
|
||||||
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||||
if (ImGui.Button("Create Syncshell"))
|
if (ImGui.Button("Create Syncshell"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var aliasInput = string.IsNullOrWhiteSpace(_newSyncShellAlias) ? null : _newSyncShellAlias.Trim();
|
if (_createIsTemporary)
|
||||||
_lastCreatedGroup = ApiController.GroupCreate(aliasInput).Result;
|
|
||||||
if (_lastCreatedGroup != null)
|
|
||||||
{
|
{
|
||||||
_newSyncShellAlias = string.Empty;
|
var expiresAtUtc = DateTime.UtcNow.AddHours(_tempSyncshellDurationHours);
|
||||||
|
_lastCreatedGroup = ApiController.GroupCreateTemporary(expiresAtUtc).Result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var aliasInput = string.IsNullOrWhiteSpace(_newSyncShellAlias) ? null : _newSyncShellAlias.Trim();
|
||||||
|
_lastCreatedGroup = ApiController.GroupCreate(aliasInput).Result;
|
||||||
|
if (_lastCreatedGroup != null)
|
||||||
|
{
|
||||||
|
_newSyncShellAlias = string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_lastCreatedGroup = null;
|
_lastCreatedGroup = null;
|
||||||
_errorGroupCreate = true;
|
_errorGroupCreate = true;
|
||||||
_errorGroupCreateMessage = ex.Message;
|
if (ex.Message.Contains("name is already in use", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_errorGroupCreateMessage = "Le nom de la Syncshell est déjà utilisé.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_errorGroupCreateMessage = ex.Message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +283,11 @@ internal sealed class GroupPanel
|
|||||||
ImGui.SetClipboardText(_lastCreatedGroup.Password);
|
ImGui.SetClipboardText(_lastCreatedGroup.Password);
|
||||||
}
|
}
|
||||||
UiSharedService.TextWrapped("You can change the Syncshell password later at any time.");
|
UiSharedService.TextWrapped("You can change the Syncshell password later at any time.");
|
||||||
|
if (_lastCreatedGroup.IsTemporary && _lastCreatedGroup.ExpiresAt != null)
|
||||||
|
{
|
||||||
|
var expiresLocal = _lastCreatedGroup.ExpiresAt.Value.ToLocalTime();
|
||||||
|
UiSharedService.TextWrapped($"Cette Syncshell expirera le {expiresLocal:g} (heure locale).");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_errorGroupCreate)
|
if (_errorGroupCreate)
|
||||||
@@ -263,11 +355,13 @@ internal sealed class GroupPanel
|
|||||||
if (!string.Equals(_editGroupEntry, groupDto.GID, StringComparison.Ordinal))
|
if (!string.Equals(_editGroupEntry, groupDto.GID, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
var shellConfig = _serverConfigurationManager.GetShellConfigForGid(groupDto.GID);
|
var shellConfig = _serverConfigurationManager.GetShellConfigForGid(groupDto.GID);
|
||||||
if (!_mareConfig.Current.DisableSyncshellChat && shellConfig.Enabled)
|
var totalMembers = pairsInGroup.Count + 1;
|
||||||
{
|
var connectedMembers = pairsInGroup.Count(p => p.IsOnline) + 1;
|
||||||
ImGui.TextUnformatted($"[{shellNumber}]");
|
var maxCapacity = ApiController.ServerInfo.MaxGroupUserCount;
|
||||||
UiSharedService.AttachToolTip("Chat command prefix: /ss" + shellNumber);
|
ImGui.TextUnformatted($"{connectedMembers}/{totalMembers}");
|
||||||
}
|
UiSharedService.AttachToolTip("Membres connectés / membres totaux" + Environment.NewLine +
|
||||||
|
$"Capacité maximale : {maxCapacity}" + Environment.NewLine +
|
||||||
|
"Syncshell ID: " + groupDto.Group.GID);
|
||||||
if (textIsGid) ImGui.PushFont(UiBuilder.MonoFont);
|
if (textIsGid) ImGui.PushFont(UiBuilder.MonoFont);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(groupName);
|
ImGui.TextUnformatted(groupName);
|
||||||
@@ -275,6 +369,20 @@ internal sealed class GroupPanel
|
|||||||
UiSharedService.AttachToolTip("Left click to switch between GID display and comment" + Environment.NewLine +
|
UiSharedService.AttachToolTip("Left click to switch between GID display and comment" + Environment.NewLine +
|
||||||
"Right click to change comment for " + groupName + Environment.NewLine + Environment.NewLine
|
"Right click to change comment for " + groupName + Environment.NewLine + Environment.NewLine
|
||||||
+ "Users: " + (pairsInGroup.Count + 1) + ", Owner: " + groupDto.OwnerAliasOrUID);
|
+ "Users: " + (pairsInGroup.Count + 1) + ", Owner: " + groupDto.OwnerAliasOrUID);
|
||||||
|
if (groupDto.IsTemporary)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
UiSharedService.ColorText("(Temp)", ImGuiColors.DalamudOrange);
|
||||||
|
if (groupDto.ExpiresAt != null)
|
||||||
|
{
|
||||||
|
var tempExpireLocal = groupDto.ExpiresAt.Value.ToLocalTime();
|
||||||
|
UiSharedService.AttachToolTip($"Expire le {tempExpireLocal:g}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UiSharedService.AttachToolTip("Syncshell temporaire");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||||
{
|
{
|
||||||
var prevState = textIsGid;
|
var prevState = textIsGid;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using Dalamud.Interface;
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.MareConfiguration.Configurations;
|
using MareSynchronos.MareConfiguration.Configurations;
|
||||||
using MareSynchronos.PlayerData.Pairs;
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
@@ -15,6 +16,7 @@ namespace MareSynchronos.UI;
|
|||||||
|
|
||||||
public sealed class DtrEntry : IDisposable, IHostedService
|
public sealed class DtrEntry : IDisposable, IHostedService
|
||||||
{
|
{
|
||||||
|
public const string DefaultGlyph = "\u25CB";
|
||||||
private enum DtrStyle
|
private enum DtrStyle
|
||||||
{
|
{
|
||||||
Default,
|
Default,
|
||||||
@@ -196,7 +198,8 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
|||||||
{
|
{
|
||||||
var style = (DtrStyle)styleNum;
|
var style = (DtrStyle)styleNum;
|
||||||
|
|
||||||
return style switch {
|
return style switch
|
||||||
|
{
|
||||||
DtrStyle.Style1 => $"\xE039 {text}",
|
DtrStyle.Style1 => $"\xE039 {text}",
|
||||||
DtrStyle.Style2 => $"\xE0BC {text}",
|
DtrStyle.Style2 => $"\xE0BC {text}",
|
||||||
DtrStyle.Style3 => $"\xE0BD {text}",
|
DtrStyle.Style3 => $"\xE0BD {text}",
|
||||||
@@ -206,7 +209,7 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
|||||||
DtrStyle.Style7 => $"\xE05D {text}",
|
DtrStyle.Style7 => $"\xE05D {text}",
|
||||||
DtrStyle.Style8 => $"\xE03C{text}",
|
DtrStyle.Style8 => $"\xE03C{text}",
|
||||||
DtrStyle.Style9 => $"\xE040 {text} \xE041",
|
DtrStyle.Style9 => $"\xE040 {text} \xE041",
|
||||||
_ => $"\uE044 {text}"
|
_ => DefaultGlyph + " " + text
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ 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 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;
|
||||||
private bool? _notesSuccessfullyApplied = null;
|
private bool? _notesSuccessfullyApplied = null;
|
||||||
@@ -1049,13 +1050,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
_configService.Save();
|
_configService.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SetNextItemWidth(250 * ImGuiHelpers.GlobalScale);
|
DrawDtrStyleCombo();
|
||||||
_uiShared.DrawCombo("Server Info Bar style", Enumerable.Range(0, DtrEntry.NumStyles), (i) => DtrEntry.RenderDtrStyle(i, "123"),
|
|
||||||
(i) =>
|
|
||||||
{
|
|
||||||
_configService.Current.DtrStyle = i;
|
|
||||||
_configService.Save();
|
|
||||||
}, _configService.Current.DtrStyle);
|
|
||||||
|
|
||||||
if (ImGui.Checkbox("Color-code the Server Info Bar entry according to status", ref useColorsInDtr))
|
if (ImGui.Checkbox("Color-code the Server Info Bar entry according to status", ref useColorsInDtr))
|
||||||
{
|
{
|
||||||
@@ -1941,12 +1936,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.EndDisabled();
|
ImGui.EndDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Chat"))
|
|
||||||
{
|
|
||||||
DrawChatConfig();
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Advanced"))
|
if (ImGui.BeginTabItem("Advanced"))
|
||||||
{
|
{
|
||||||
DrawAdvanced();
|
DrawAdvanced();
|
||||||
@@ -1957,6 +1946,38 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawDtrStyleCombo()
|
||||||
|
{
|
||||||
|
var styleIndex = _configService.Current.DtrStyle;
|
||||||
|
string previewText = styleIndex == 0 ? DtrDefaultPreviewText : DtrEntry.RenderDtrStyle(styleIndex, "123");
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth(250 * ImGuiHelpers.GlobalScale);
|
||||||
|
bool comboOpen = ImGui.BeginCombo("Server Info Bar style", previewText);
|
||||||
|
|
||||||
|
if (comboOpen)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < DtrEntry.NumStyles; i++)
|
||||||
|
{
|
||||||
|
string label = i == 0 ? DtrDefaultPreviewText : DtrEntry.RenderDtrStyle(i, "123");
|
||||||
|
bool isSelected = i == styleIndex;
|
||||||
|
if (ImGui.Selectable(label, isSelected))
|
||||||
|
{
|
||||||
|
_configService.Current.DtrStyle = i;
|
||||||
|
_configService.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelected)
|
||||||
|
{
|
||||||
|
ImGui.SetItemDefaultFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void UiSharedService_GposeEnd()
|
private void UiSharedService_GposeEnd()
|
||||||
{
|
{
|
||||||
IsOpen = _wasOpen;
|
IsOpen = _wasOpen;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"Author": "SirConstance",
|
"Author": "Keda",
|
||||||
"Name": "UmbraSync",
|
"Name": "UmbraSync",
|
||||||
"Punchline": "Share your true self.",
|
"Punchline": "Parce que nous le valons bien.",
|
||||||
"Description": "This plugin will synchronize your Penumbra mods and current Glamourer state with other paired clients automatically.",
|
"Description": "Ce plugin synchronisera automatiquement vos mods Penumbra et l'état actuel de Glamourer avec les autres clients appairés.",
|
||||||
"InternalName": "UmbraSync",
|
"InternalName": "UmbraSync",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"Tags": [
|
"Tags": [
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MareSynchronos.API.Data;
|
using System;
|
||||||
|
using MareSynchronos.API.Data;
|
||||||
using MareSynchronos.API.Dto.Group;
|
using MareSynchronos.API.Dto.Group;
|
||||||
using MareSynchronos.WebAPI.SignalR.Utils;
|
using MareSynchronos.WebAPI.SignalR.Utils;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
@@ -60,6 +61,12 @@ public partial class ApiController
|
|||||||
return await _mareHub!.InvokeAsync<GroupPasswordDto>(nameof(GroupCreate), string.IsNullOrWhiteSpace(alias) ? null : alias.Trim()).ConfigureAwait(false);
|
return await _mareHub!.InvokeAsync<GroupPasswordDto>(nameof(GroupCreate), string.IsNullOrWhiteSpace(alias) ? null : alias.Trim()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<GroupPasswordDto> GroupCreateTemporary(DateTime expiresAtUtc)
|
||||||
|
{
|
||||||
|
CheckConnection();
|
||||||
|
return await _mareHub!.InvokeAsync<GroupPasswordDto>(nameof(GroupCreateTemporary), expiresAtUtc).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<string>> GroupCreateTempInvite(GroupDto group, int amount)
|
public async Task<List<string>> GroupCreateTempInvite(GroupDto group, int amount)
|
||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
|
|||||||
Reference in New Issue
Block a user