Rework mod import UX

This commit is contained in:
Exter-N 2026-01-27 04:32:40 +01:00 committed by Ottermandias
parent bbc090085b
commit 34234927d8
6 changed files with 179 additions and 15 deletions

2
Luna

@ -1 +1 @@
Subproject commit 384b437a8cf16b4b1a51da43a4837d8fcc2116bd
Subproject commit 14544263ad78d95d96235eab597a7cb0a76015eb

View file

@ -19,6 +19,7 @@ public partial class TexToolsImporter : IDisposable
private readonly string _tmpFile;
private readonly IEnumerable<FileInfo> _modPackFiles;
private readonly int _previousModPackCount;
private readonly int _modPackCount;
private FileStream? _tmpFileStream;
private StreamDisposer? _streamDisposer;
@ -37,8 +38,16 @@ public partial class TexToolsImporter : IDisposable
public TexToolsImporter(int count, IEnumerable<FileInfo> modPackFiles, Action<FileInfo, DirectoryInfo?, Exception?> handler,
Configuration config, DuplicateManager duplicates, ModNormalizer modNormalizer, ModManager modManager, FileCompactor compactor,
MigrationManager migrationManager)
MigrationManager migrationManager, TexToolsImporter? previous)
{
if (previous is not null)
{
if (previous.State is not ImporterState.Done)
throw new ArgumentException($"The previous {nameof(TexToolsImporter)} must have completed its job.");
_previousModPackCount = previous.ExtractedMods.Count;
}
_baseDirectory = modManager.BasePath;
_tmpFile = Path.Combine(_baseDirectory.FullName, TempFileName);
_modPackFiles = modPackFiles;
@ -48,14 +57,16 @@ public partial class TexToolsImporter : IDisposable
_modManager = modManager;
_compactor = compactor;
_migrationManager = migrationManager;
_modPackCount = count;
ExtractedMods = new List<(FileInfo, DirectoryInfo?, Exception?)>(count);
_modPackCount = count + _previousModPackCount;
ExtractedMods = new List<(FileInfo, DirectoryInfo?, Exception?)>(count + _previousModPackCount);
_token = _cancellation.Token;
if (previous is not null)
ExtractedMods.AddRange(previous.ExtractedMods);
Task.Run(ImportFiles, _token)
.ContinueWith(_ => CloseStreams(), TaskScheduler.Default)
.ContinueWith(_ =>
{
foreach (var (file, dir, error) in ExtractedMods)
foreach (var (file, dir, error) in ExtractedMods.Skip(_previousModPackCount))
handler(file, dir, error);
}, TaskScheduler.Default);
}
@ -83,7 +94,7 @@ public partial class TexToolsImporter : IDisposable
private void ImportFiles()
{
State = ImporterState.None;
_currentModPackIdx = 0;
_currentModPackIdx = _previousModPackCount;
foreach (var file in _modPackFiles)
{
_currentModDirectory = null;

View file

@ -18,6 +18,24 @@ public partial class TexToolsImporter
private string _currentOptionName = string.Empty;
private string _currentFileName = string.Empty;
public (string Text, float Progress, bool Ended, bool Successful) ComputeNotificationData()
{
if (_modPackCount is 0)
return ("Nothing to extract.", 1.0f, true, true);
if (_modPackCount == _currentModPackIdx)
{
var success = ExtractedMods.Count(t => t.Error == null);
return ($"Successfully extracted {success} / {ExtractedMods.Count} files.", 1.0f, true, success == ExtractedMods.Count);
}
if (State is ImporterState.DeduplicatingFiles)
return ($"Deduplicating {_currentModName}...", 1.0f, false, true);
return ($"Extracting {_currentModName}...", _currentNumFiles > 0 ? _currentFileIdx / (float)_currentNumFiles : 0.0f, false, true);
}
public bool DrawProgressInfo(Vector2 size)
{
if (_modPackCount is 0)

View file

@ -1,6 +1,7 @@
using Dalamud.Interface.ImGuiNotification;
using Luna;
using Penumbra.Import;
using Penumbra.Import.Structs;
using Penumbra.Mods.Editor;
using Penumbra.Services;
@ -31,7 +32,7 @@ public class ModImportManager(
public void TryUnpacking()
{
if (Importing || !_modsToUnpack.TryDequeue(out var newMods))
if (Importing && _import!.State is not ImporterState.Done || !_modsToUnpack.TryDequeue(out var newMods))
return;
var files = newMods.Where(s =>
@ -49,7 +50,7 @@ public class ModImportManager(
return;
_import = new TexToolsImporter(files.Length, files, AddNewMod, config, duplicates, modNormalizer, modManager, compactor,
migrationManager);
migrationManager, _import);
}
public bool Importing

View file

@ -1,22 +1,37 @@
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.ImGuiNotification.EventArgs;
using ImSharp;
using Luna;
using Penumbra.Import.Structs;
using Penumbra.Mods.Manager;
using MessageService = Penumbra.Services.MessageService;
namespace Penumbra.UI;
/// <summary> Draw the progress information for import. </summary>
public sealed class ImportPopup : Window
public sealed class ImportPopup : Window, INotificationAwareMessage
{
public const string WindowLabel = "Penumbra Import Status";
private readonly ModImportManager _modImportManager;
private readonly MessageService _messageService;
private static readonly Vector2 OneHalf = Vector2.One / 2;
private IActiveNotification? _notification;
private string _notificationMessage = string.Empty;
private float _notificationProgress = 1.0f;
private bool _notificationEnded = true;
private bool _notificationSuccessful = true;
private bool _openPopup = false;
public bool HasNotification
=> _notification is not null;
public bool WasDrawn { get; private set; }
public bool PopupWasDrawn { get; private set; }
public ImportPopup(ModImportManager modImportManager)
public ImportPopup(ModImportManager modImportManager, MessageService messageService)
: base(WindowLabel,
WindowFlags.NoCollapse
| WindowFlags.NoDecoration
@ -30,6 +45,7 @@ public sealed class ImportPopup : Window
| WindowFlags.NoTitleBar, true)
{
_modImportManager = modImportManager;
_messageService = messageService;
DisableWindowSounds = true;
IsOpen = true;
RespectCloseHotkey = false;
@ -58,8 +74,26 @@ public sealed class ImportPopup : Window
if (!_modImportManager.IsImporting(out var import))
return;
if (!Im.Popup.IsOpen("##PenumbraImportPopup"u8))
(_notificationMessage, _notificationProgress, _notificationEnded, _notificationSuccessful) = import.ComputeNotificationData();
_notification?.Title = NotificationTitle;
_notification?.Type = NotificationType;
_notification?.Content = _notificationMessage;
_notification?.Progress = _notificationProgress;
_notification?.UserDismissable = _notificationEnded;
if (_openPopup)
{
Im.Popup.Open("##PenumbraImportPopup"u8);
_openPopup = false;
}
if (!Im.Popup.IsOpen("##PenumbraImportPopup"u8))
{
if (_notification is null)
_messageService.AddMessage(this, false, true, false);
return;
}
var display = Im.Io.DisplaySize;
var height = Math.Max(display.Y / 4, 15 * Im.Style.FrameHeightWithSpacing);
@ -78,10 +112,109 @@ public sealed class ImportPopup : Window
terminate = true;
}
if (import.State != ImporterState.Done)
{
if (Im.Button("Continue in the Background"u8,
new Vector2((Im.ContentRegion.Available.X - Im.GetStyle().ItemSpacing.X) * 0.5f, 0.0f)))
Im.Popup.CloseCurrent();
Im.Line.Same();
}
terminate |= import.State == ImporterState.Done
? Im.Button("Close"u8, -Vector2.UnitX)
: import.DrawCancelButton(-Vector2.UnitX);
if (terminate)
_modImportManager.ClearImport();
}
#region Luna Message implementation
private NotificationType NotificationType
=> (_notificationEnded, _notificationSuccessful) switch
{
(false, _) => NotificationType.Info,
(true, true) => NotificationType.Success,
(true, false) => NotificationType.Error,
};
private string NotificationTitle
=> (_notificationEnded, _notificationSuccessful) switch
{
(false, _) => "Importing mods",
(true, true) => "Successfully imported mods",
(true, false) => "Failed to import some mods",
};
NotificationType IMessage.NotificationType
=> NotificationType;
string IMessage.NotificationMessage
=> _notificationMessage;
TimeSpan IMessage.NotificationDuration
=> TimeSpan.MaxValue;
string IMessage.NotificationTitle
=> NotificationTitle;
string IMessage.LogMessage
=> string.Empty;
SeString IMessage.ChatMessage
=> SeString.Empty;
StringU8 IMessage.StoredMessage
=> StringU8.Empty;
StringU8 IMessage.StoredTooltip
=> StringU8.Empty;
void IMessage.OnNotificationActions(INotificationDrawArgs args)
{
if (_notificationEnded)
{
if (Im.Button("Open Report"u8, -Vector2.UnitX))
{
_openPopup = true;
_notification?.DismissNow();
}
}
else
{
if (Im.Button("Show Details"u8, new Vector2((Im.ContentRegion.Available.X - Im.GetStyle().ItemSpacing.X) * 0.5f, 0.0f)))
{
_openPopup = true;
_notification?.DismissNow();
}
Im.Line.Same();
if (_modImportManager.IsImporting(out var import) && import.DrawCancelButton(-Vector2.UnitX))
{
_modImportManager.ClearImport();
_notification?.DismissNow();
}
}
}
void INotificationAwareMessage.OnNotificationCreated(IActiveNotification notification)
{
var previousNotification = _notification;
_notification = notification;
previousNotification?.DismissNow();
notification.Progress = _notificationProgress;
notification.UserDismissable = _notificationEnded;
notification.Dismiss += OnNotificationDismissed;
}
private void OnNotificationDismissed(INotificationDismissArgs args)
{
if (args.Notification != _notification)
return;
_notification = null;
if (!_openPopup && !PopupWasDrawn)
_modImportManager.ClearImport();
}
#endregion
}

View file

@ -350,10 +350,11 @@ public sealed class DebugTab : Window, ITab<TabType>
if (table)
{
var importing = _modImporter.IsImporting(out var importer);
table.DrawDataPair("Is Importing"u8, importing.ToString());
table.DrawDataPair("Importer State"u8, (importer?.State ?? ImporterState.None).ToString());
table.DrawDataPair("Import Window Was Drawn"u8, _importPopup.WasDrawn.ToString());
table.DrawDataPair("Import Popup Was Drawn"u8, _importPopup.PopupWasDrawn.ToString());
table.DrawDataPair("Is Importing"u8, importing.ToString());
table.DrawDataPair("Importer State"u8, (importer?.State ?? ImporterState.None).ToString());
table.DrawDataPair("Import Notification Exists"u8, _importPopup.HasNotification.ToString());
table.DrawDataPair("Import Window Was Drawn"u8, _importPopup.WasDrawn.ToString());
table.DrawDataPair("Import Popup Was Drawn"u8, _importPopup.PopupWasDrawn.ToString());
table.DrawColumn("Import Batches"u8);
table.NextColumn();
foreach (var (index, batch) in _modImporter.ModBatches.Index())