qstbak/Questionable/Questionable.Windows.QuestComponents/SavedPresetsComponent.cs
2026-08-19 13:19:57 +10:00

602 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Questionable.Controller;
using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
using Questionable.Windows.Utils;
namespace Questionable.Windows.QuestComponents;
internal sealed class SavedPresetsComponent
{
private sealed class PresetExportData
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<string> QuestIds { get; set; } = new List<string>();
}
private readonly QuestController _questController;
private readonly QuestFunctions _questFunctions;
private readonly QuestData _questData;
private readonly QuestRegistry _questRegistry;
private readonly Configuration _configuration;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly IChatGui _chatGui;
private readonly UiUtils _uiUtils;
private readonly QuestTooltipComponent _questTooltipComponent;
private readonly ILogger<SavedPresetsComponent> _logger;
private string _newPresetName = string.Empty;
private string _newPresetDescription = string.Empty;
private bool _showSaveDialog;
private string? _presetToDelete;
private string? _expandedPreset;
private string? _editingPresetKey;
private string _editPresetName = string.Empty;
private string _editPresetDescription = string.Empty;
private string? _lastClipboardText;
private PresetExportData? _clipboardPreset;
private List<PresetExportData>? _clipboardAllPresets;
private const string PresetClipboardPrefix = "qst:preset:";
private const string AllPresetsClipboardPrefix = "qst:presets:";
public SavedPresetsComponent(QuestController questController, QuestFunctions questFunctions, QuestData questData, QuestRegistry questRegistry, Configuration configuration, IDalamudPluginInterface pluginInterface, IChatGui chatGui, UiUtils uiUtils, QuestTooltipComponent questTooltipComponent, ILogger<SavedPresetsComponent> logger)
{
_questController = questController;
_questFunctions = questFunctions;
_questData = questData;
_questRegistry = questRegistry;
_configuration = configuration;
_pluginInterface = pluginInterface;
_chatGui = chatGui;
_uiUtils = uiUtils;
_questTooltipComponent = questTooltipComponent;
_logger = logger;
}
public void Draw()
{
DrawSaveSection();
UiThemeUtils.SectionSpacing();
using (ImRaii.ChildDisposable childDisposable = ImRaii.Child("SavedPresetsList", new Vector2(-1f, 300f), border: true, ImGuiWindowFlags.AlwaysVerticalScrollbar))
{
if ((bool)childDisposable)
{
DrawSavedPresets();
}
}
UiThemeUtils.SectionSpacing();
DrawBottomButtons();
}
private void DrawSaveSection()
{
int count = _questController.ManualPriorityQuests.Count;
float x = ImGui.GetContentRegionAvail().X;
float x2 = ImGui.GetStyle().ItemSpacing.X;
float width = (x - x2) / 2f;
if (_showSaveDialog)
{
ImGui.SetNextItemWidth(x);
ImGui.InputTextWithHint("##PresetName", "Preset name...", ref _newPresetName, 64);
ImGui.SetNextItemWidth(x);
ImGui.InputTextWithHint("##PresetDescription", "Description (optional)...", ref _newPresetDescription, 256);
using (ImRaii.Disabled(string.IsNullOrWhiteSpace(_newPresetName) || count == 0))
{
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Save, "Save", width))
{
SaveCurrentPreset();
}
}
ImGui.SameLine();
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Times, "Cancel", width))
{
_showSaveDialog = false;
_newPresetName = string.Empty;
_newPresetDescription = string.Empty;
}
return;
}
using (ImRaii.Disabled(count == 0))
{
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Plus, $"Save Current Queue ({count} quests)", x))
{
_showSaveDialog = true;
}
}
if (count == 0 && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("Add quests to the priority queue first.");
}
}
private void DrawSavedPresets()
{
Dictionary<string, Configuration.SavedQuestPreset> savedPresets = _configuration.General.SavedPresets;
if (savedPresets.Count == 0)
{
string[] array = new string[3] { "No saved presets yet.", "Save your current priority queue using the", "button above." };
float x = ImGui.GetContentRegionAvail().X;
using (ImRaii.PushColor(ImGuiCol.Text, UiThemeUtils.MutedTextColor))
{
string[] array2 = array;
foreach (string obj in array2)
{
float x2 = ImGui.CalcTextSize(obj).X;
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (x - x2) / 2f);
ImGui.TextUnformatted(obj);
}
return;
}
}
bool flag = true;
foreach (var (key, preset) in savedPresets.OrderBy((KeyValuePair<string, Configuration.SavedQuestPreset> keyValuePair2) => keyValuePair2.Value.Name))
{
if (!flag)
{
ImGui.Spacing();
}
flag = false;
DrawPresetEntry(key, preset);
}
if (_presetToDelete != null)
{
if (savedPresets.Remove(_presetToDelete))
{
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogInformation("Deleted saved preset '{PresetName}'", _presetToDelete);
}
_presetToDelete = null;
}
}
private void DrawPresetEntry(string key, Configuration.SavedQuestPreset preset)
{
using (ImRaii.PushId(key))
{
(Vector2 ContentStartPos, float AvailableWidth, ImDrawListPtr DrawList) tuple = UiThemeUtils.BeginCard();
Vector2 item = tuple.ContentStartPos;
float item2 = tuple.AvailableWidth;
ImDrawListPtr item3 = tuple.DrawList;
List<ElementId> availableQuests = GetAvailableQuests(preset.QuestIds);
int count = preset.QuestIds.Count;
string text = $"{count} {((count == 1) ? "quest" : "quests")}";
ImGui.GetCursorPosX();
float num = ImGui.GetContentRegionAvail().X - 12f;
bool flag = _expandedPreset == key;
float frameHeight = ImGui.GetFrameHeight();
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
using (ImRaii.PushColor(ImGuiCol.Button, Vector4.Zero))
{
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, UiThemeUtils.SubtleHoverColor))
{
using (ImRaii.PushColor(ImGuiCol.ButtonActive, UiThemeUtils.SubtlePressColor))
{
ImU8String label = new ImU8String(2, 1);
label.AppendLiteral("##");
label.AppendFormatted(key);
if (ImGui.Button(label, new Vector2(num, frameHeight)))
{
_expandedPreset = (flag ? null : key);
flag = _expandedPreset == key;
}
}
}
}
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
string text2 = (flag ? FontAwesomeIcon.CaretDown : FontAwesomeIcon.CaretRight).ToIconString();
Vector2 vector;
using (ImRaii.PushFont(UiBuilder.IconFont))
{
vector = ImGui.CalcTextSize(text2);
}
float y = cursorScreenPos.Y + (frameHeight - ImGui.GetTextLineHeight()) / 2f;
float y2 = cursorScreenPos.Y + (frameHeight - vector.Y) / 2f;
uint colorU = ImGui.GetColorU32(ImGuiCol.Text);
Vector2 vector2 = ImGui.CalcTextSize(text);
float x = ImGui.GetStyle().FramePadding.X;
float num2 = cursorScreenPos.X + x + vector.X + 6f;
float num3 = cursorScreenPos.X + num - vector2.X - x;
windowDrawList.AddText(UiBuilder.IconFont, ImGui.GetFontSize(), new Vector2(cursorScreenPos.X + x, y2), colorU, text2);
windowDrawList.AddText(new Vector2(num2, y), colorU, UiThemeUtils.TruncateToWidth(preset.Name, num3 - num2 - ImGui.GetStyle().ItemSpacing.X));
windowDrawList.AddText(new Vector2(num3, y), ImGui.ColorConvertFloat4ToU32(UiThemeUtils.MutedTextColor), text);
if (flag)
{
if (_editingPresetKey == key)
{
float x2 = ImGui.GetContentRegionAvail().X;
ImGui.SetNextItemWidth(x2);
ImGui.InputTextWithHint("##EditName", "Preset name...", ref _editPresetName, 64);
ImGui.SetNextItemWidth(x2);
ImGui.InputTextWithHint("##EditDesc", "Description (optional)...", ref _editPresetDescription, 256);
float width = (x2 - ImGui.GetStyle().ItemSpacing.X) / 2f;
using (ImRaii.Disabled(string.IsNullOrWhiteSpace(_editPresetName)))
{
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Save, "Save", width))
{
preset.Name = _editPresetName.Trim();
preset.Description = _editPresetDescription.Trim();
_pluginInterface.SavePluginConfig(_configuration);
_editingPresetKey = null;
}
}
ImGui.SameLine();
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Times, "Cancel", width))
{
_editingPresetKey = null;
}
}
else
{
using (ImRaii.PushColor(ImGuiCol.Text, UiThemeUtils.DimTextColor))
{
foreach (ElementId questId in preset.QuestIds)
{
if (_questData.TryGetQuestInfo(questId, out IQuestInfo questInfo))
{
UiThemeUtils.WrappedText(questInfo.Name);
if (ImGui.IsItemHovered())
{
_questTooltipComponent.Draw(questInfo);
}
}
else
{
UiThemeUtils.WrappedText(questId.ToString());
}
}
}
ImGui.Spacing();
float width2 = (num - ImGui.GetStyle().ItemSpacing.X * 3f) / 4f;
using (ImRaii.Disabled(availableQuests.Count == 0))
{
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Plus, "Add", width2))
{
AddPresetToPriority(availableQuests);
}
}
ImGui.SameLine();
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Upload, "Export", width2))
{
ExportPresetToClipboard(preset);
}
ImGui.SameLine();
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Pen, "Edit", width2))
{
_editingPresetKey = key;
_editPresetName = preset.Name;
_editPresetDescription = preset.Description;
}
ImGui.SameLine();
using (ImRaii.Disabled(!ImGui.IsKeyDown(ImGuiKey.ModCtrl)))
{
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Trash, "Delete", width2))
{
_presetToDelete = key;
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("Hold CTRL to delete");
}
}
}
UiThemeUtils.EndCard(item, item2, item3);
}
}
private void DrawBottomButtons()
{
PresetExportData presetExportData = ParseClipboardPreset();
List<PresetExportData> list = ParseClipboardAllPresets();
float x = ImGui.GetContentRegionAvail().X;
float x2 = ImGui.GetStyle().ItemSpacing.X;
float width = (x - x2) / 2f;
using (ImRaii.Disabled(presetExportData == null && list == null))
{
string text = ((list != null) ? $"Import ({list.Count})" : "Import");
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Download, text, width))
{
if (list != null)
{
ImportAllPresetsFromClipboard(list);
}
else if (presetExportData != null)
{
ImportPresetFromClipboard(presetExportData);
}
}
}
if (presetExportData == null && list == null && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("Copy a valid preset string to clipboard first.");
}
ImGui.SameLine();
int count = _configuration.General.SavedPresets.Count;
using (ImRaii.Disabled(count == 0))
{
if (UiThemeUtils.IconTextButton(FontAwesomeIcon.Upload, $"Export ({count})", width))
{
ExportAllPresetsToClipboard();
}
}
if (count == 0 && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
ImGui.SetTooltip("No saved presets to export.");
}
}
private void SaveCurrentPreset()
{
List<ElementId> list = _questController.ManualPriorityQuests.Select((Quest q) => q.Id).ToList();
string key = GeneratePresetKey(_newPresetName);
Configuration.SavedQuestPreset savedQuestPreset = new Configuration.SavedQuestPreset
{
Name = _newPresetName.Trim(),
Description = _newPresetDescription.Trim(),
QuestIds = list,
CreatedAt = DateTime.UtcNow
};
_configuration.General.SavedPresets[key] = savedQuestPreset;
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogInformation("Saved preset '{PresetName}' with {Count} quests", savedQuestPreset.Name, list.Count);
_chatGui.Print($"Saved preset '{savedQuestPreset.Name}' with {list.Count} quests.", "Questionable", 576);
_newPresetName = string.Empty;
_newPresetDescription = string.Empty;
_showSaveDialog = false;
}
private string GeneratePresetKey(string name)
{
string text = "user_" + name.Trim().ToUpperInvariant().Replace(' ', '_');
string text2 = text;
int num = 1;
while (_configuration.General.SavedPresets.ContainsKey(text2))
{
text2 = $"{text}_{num}";
num++;
}
return text2;
}
private void ExportPresetToClipboard(Configuration.SavedQuestPreset preset)
{
string s = JsonConvert.SerializeObject(new PresetExportData
{
Name = preset.Name,
Description = preset.Description,
QuestIds = preset.QuestIds.Select((ElementId x) => x.ToString()).ToList()
});
ImGui.SetClipboardText("qst:preset:" + Convert.ToBase64String(Encoding.UTF8.GetBytes(s)));
_chatGui.Print("Exported preset '" + preset.Name + "' to clipboard.", "Questionable", 576);
}
private void ExportAllPresetsToClipboard()
{
List<PresetExportData> list = _configuration.General.SavedPresets.Values.Select((Configuration.SavedQuestPreset preset) => new PresetExportData
{
Name = preset.Name,
Description = preset.Description,
QuestIds = preset.QuestIds.Select((ElementId x) => x.ToString()).ToList()
}).ToList();
string s = JsonConvert.SerializeObject(list);
ImGui.SetClipboardText("qst:presets:" + Convert.ToBase64String(Encoding.UTF8.GetBytes(s)));
_chatGui.Print($"Exported {list.Count} presets to clipboard.", "Questionable", 576);
}
private void RefreshClipboardPresets()
{
string text = ThrottledClipboard.GetText();
if (text == _lastClipboardText)
{
return;
}
_lastClipboardText = text;
_clipboardPreset = null;
_clipboardAllPresets = null;
try
{
if (text.StartsWith("qst:preset:", StringComparison.InvariantCulture))
{
string s = text.Substring("qst:preset:".Length);
string value = Encoding.UTF8.GetString(Convert.FromBase64String(s));
_clipboardPreset = JsonConvert.DeserializeObject<PresetExportData>(value);
}
else if (text.StartsWith("qst:presets:", StringComparison.InvariantCulture))
{
string s2 = text.Substring("qst:presets:".Length);
string value2 = Encoding.UTF8.GetString(Convert.FromBase64String(s2));
_clipboardAllPresets = JsonConvert.DeserializeObject<List<PresetExportData>>(value2);
}
}
catch (Exception exception)
{
_logger.LogDebug(exception, "Failed to parse presets from clipboard");
}
}
private PresetExportData? ParseClipboardPreset()
{
RefreshClipboardPresets();
return _clipboardPreset;
}
private List<PresetExportData>? ParseClipboardAllPresets()
{
RefreshClipboardPresets();
return _clipboardAllPresets;
}
private bool PresetNameExists(string name)
{
return _configuration.General.SavedPresets.Values.Any((Configuration.SavedQuestPreset p) => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase));
}
private void ImportPresetFromClipboard(PresetExportData exportData)
{
try
{
if (PresetNameExists(exportData.Name))
{
_chatGui.Print("Preset '" + exportData.Name + "' already exists, skipping.", "Questionable", 576);
return;
}
int skippedCount;
List<ElementId> list = ElementId.FromStrings(exportData.QuestIds, out skippedCount);
if (list.Count == 0)
{
_chatGui.PrintError("Preset '" + exportData.Name + "' contains no importable quests.", "Questionable", 576);
return;
}
string key = GeneratePresetKey(exportData.Name);
Configuration.SavedQuestPreset savedQuestPreset = new Configuration.SavedQuestPreset
{
Name = exportData.Name,
Description = exportData.Description,
QuestIds = list,
CreatedAt = DateTime.UtcNow
};
_configuration.General.SavedPresets[key] = savedQuestPreset;
_pluginInterface.SavePluginConfig(_configuration);
_logger.LogInformation("Imported preset '{PresetName}' with {Count} quests from clipboard, skipped {Skipped} unknown ids", savedQuestPreset.Name, list.Count, skippedCount);
string text = $"Imported preset '{savedQuestPreset.Name}' with {list.Count} quests.";
if (skippedCount > 0)
{
text += $" Skipped {skippedCount} unknown quest ids.";
}
_chatGui.Print(text, "Questionable", 576);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to import preset from clipboard");
_chatGui.PrintError("Failed to import preset: " + ex.Message, "Questionable", 576);
}
}
private void ImportAllPresetsFromClipboard(List<PresetExportData> allExportData)
{
try
{
int num = 0;
int num2 = 0;
int num3 = 0;
HashSet<string> hashSet = _configuration.General.SavedPresets.Values.Select((Configuration.SavedQuestPreset p) => p.Name).ToHashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (PresetExportData allExportDatum in allExportData)
{
if (hashSet.Contains(allExportDatum.Name))
{
num2++;
continue;
}
int skippedCount;
List<ElementId> list = ElementId.FromStrings(allExportDatum.QuestIds, out skippedCount);
num3 += skippedCount;
if (list.Count == 0)
{
num2++;
continue;
}
string key = GeneratePresetKey(allExportDatum.Name);
Configuration.SavedQuestPreset value = new Configuration.SavedQuestPreset
{
Name = allExportDatum.Name,
Description = allExportDatum.Description,
QuestIds = list,
CreatedAt = DateTime.UtcNow
};
_configuration.General.SavedPresets[key] = value;
num++;
}
if (num > 0)
{
_pluginInterface.SavePluginConfig(_configuration);
}
string text = $"Imported {num} presets from clipboard.";
if (num2 > 0)
{
text += $" Skipped {num2} (already exist or empty).";
}
if (num3 > 0)
{
text += $" Skipped {num3} unknown quest ids.";
}
_logger.LogInformation("Imported {Imported} presets from clipboard, skipped {Skipped} presets and {SkippedIds} unknown ids", num, num2, num3);
_chatGui.Print(text, "Questionable", 576);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to import presets from clipboard");
_chatGui.PrintError("Failed to import presets: " + ex.Message, "Questionable", 576);
}
}
private void AddPresetToPriority(List<ElementId> questIds)
{
int num = 0;
foreach (ElementId questId in questIds)
{
if (_questController.AddQuestPriority(questId))
{
num++;
}
}
if (num > 0)
{
_chatGui.Print($"Added {num} quests to priority queue.", "Questionable", 576);
}
_logger.LogInformation("Added {Count} quests from saved preset to priority list", num);
}
private List<ElementId> GetAvailableQuests(List<ElementId> questIds)
{
return (from questId in questIds
where _questFunctions.IsReadyToAcceptQuest(questId) || _questFunctions.IsQuestAccepted(questId)
where !_questController.ManualPriorityQuests.Any((Quest q) => q.Id.Equals(questId))
where _questRegistry.IsKnownQuest(questId)
select questId).ToList();
}
private List<ElementId> GetAlreadyPriorityQuests(List<ElementId> questIds)
{
return (from questId in questIds
where _questController.ManualPriorityQuests.Any((Quest q) => q.Id.Equals(questId))
where !_questFunctions.IsQuestComplete(questId)
select questId).ToList();
}
}