muffin v6.12
This commit is contained in:
parent
cfb4dea47e
commit
c8197297b2
58 changed files with 40038 additions and 58059 deletions
|
@ -0,0 +1,567 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Plugin;
|
||||
using Questionable.Data;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
using Questionable.Validation;
|
||||
|
||||
namespace Questionable.Windows.QuestComponents;
|
||||
|
||||
internal sealed class ValidationDetailsRenderer
|
||||
{
|
||||
private sealed record JsonValidationError
|
||||
{
|
||||
public string Path { get; init; } = string.Empty;
|
||||
|
||||
public List<string> Messages { get; init; } = new List<string>();
|
||||
}
|
||||
|
||||
private readonly QuestData _questData;
|
||||
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
|
||||
private readonly Dictionary<int, ValidationIssue> _storedIssues = new Dictionary<int, ValidationIssue>();
|
||||
|
||||
private readonly Dictionary<int, bool> _openDetailWindows = new Dictionary<int, bool>();
|
||||
|
||||
private static readonly Regex JsonPropertyPathRegex = new Regex("#/([^:]*)", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex UnicodeEscapeRegex = new Regex("\\\\u([0-9A-Fa-f]{4})", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex JsonEscapeRegex = new Regex("\\\\(.)", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex ConsecutiveQuotesRegex = new Regex("\"{2,}", RegexOptions.Compiled);
|
||||
|
||||
public ValidationDetailsRenderer(QuestData questData, IDalamudPluginInterface pluginInterface)
|
||||
{
|
||||
_questData = questData;
|
||||
_pluginInterface = pluginInterface;
|
||||
}
|
||||
|
||||
public static string CleanJsonText(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return text;
|
||||
}
|
||||
text = UnicodeEscapeRegex.Replace(text, (Match match) => int.TryParse(match.Groups[1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result) ? ((char)result).ToString() : match.Value);
|
||||
text = JsonEscapeRegex.Replace(text, (Match match) => match.Groups[1].Value switch
|
||||
{
|
||||
"\"" => "\"",
|
||||
"\\" => "\\",
|
||||
"/" => "/",
|
||||
"b" => "\b",
|
||||
"f" => "\f",
|
||||
"n" => "\n",
|
||||
"r" => "\r",
|
||||
"t" => "\t",
|
||||
_ => match.Value,
|
||||
});
|
||||
if (text.Contains("\"\"", StringComparison.Ordinal))
|
||||
{
|
||||
text = ConsecutiveQuotesRegex.Replace(text, "\"");
|
||||
text = text.Replace("Expected \"\"", "Expected \"\"", StringComparison.Ordinal);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
public void OpenDetails(ValidationIssue issue, int index)
|
||||
{
|
||||
_storedIssues[index] = issue;
|
||||
_openDetailWindows[index] = true;
|
||||
}
|
||||
|
||||
public void DrawDetailWindows()
|
||||
{
|
||||
List<int> list = new List<int>();
|
||||
foreach (KeyValuePair<int, bool> item in _openDetailWindows.ToList())
|
||||
{
|
||||
if (item.Value && _storedIssues.TryGetValue(item.Key, out ValidationIssue value))
|
||||
{
|
||||
string obj = $"Validation Details##{item.Key}";
|
||||
bool open = true;
|
||||
ImGui.SetNextWindowSize(new Vector2(800f, 600f), ImGuiCond.FirstUseEver);
|
||||
ImGui.SetNextWindowSizeConstraints(new Vector2(500f, 300f), new Vector2(1200f, 800f));
|
||||
if (ImGui.Begin(obj, ref open))
|
||||
{
|
||||
DrawIssueDetails(value);
|
||||
ImGui.End();
|
||||
}
|
||||
if (!open)
|
||||
{
|
||||
list.Add(item.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (int item2 in list)
|
||||
{
|
||||
_openDetailWindows.Remove(item2);
|
||||
_storedIssues.Remove(item2);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawIssueDetails(ValidationIssue issue)
|
||||
{
|
||||
Vector4 color = ((issue.Severity == EIssueSeverity.Error) ? ImGuiColors.DalamudRed : ImGuiColors.DalamudOrange);
|
||||
FontAwesomeIcon icon = ((issue.Severity == EIssueSeverity.Error) ? FontAwesomeIcon.ExclamationTriangle : FontAwesomeIcon.InfoCircle);
|
||||
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, color))
|
||||
{
|
||||
ImGui.TextUnformatted(icon.ToIconString());
|
||||
}
|
||||
}
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, color))
|
||||
{
|
||||
ImU8String text = new ImU8String(2, 2);
|
||||
text.AppendFormatted(issue.Severity);
|
||||
text.AppendLiteral(": ");
|
||||
text.AppendFormatted(issue.Type);
|
||||
ImGui.Text(text);
|
||||
}
|
||||
ImGui.Separator();
|
||||
if (issue.ElementId != null)
|
||||
{
|
||||
IQuestInfo questInfo = _questData.GetQuestInfo(issue.ElementId);
|
||||
ImU8String text = new ImU8String(10, 2);
|
||||
text.AppendLiteral("Quest: ");
|
||||
text.AppendFormatted(issue.ElementId);
|
||||
text.AppendLiteral(" - ");
|
||||
text.AppendFormatted(questInfo.Name);
|
||||
ImGui.Text(text);
|
||||
}
|
||||
else if (issue.AlliedSociety != EAlliedSociety.None)
|
||||
{
|
||||
ImU8String text = new ImU8String(16, 1);
|
||||
text.AppendLiteral("Allied Society: ");
|
||||
text.AppendFormatted(issue.AlliedSociety);
|
||||
ImGui.Text(text);
|
||||
}
|
||||
if (issue.Sequence.HasValue)
|
||||
{
|
||||
ImU8String text = new ImU8String(10, 1);
|
||||
text.AppendLiteral("Sequence: ");
|
||||
text.AppendFormatted(issue.Sequence);
|
||||
ImGui.Text(text);
|
||||
}
|
||||
if (issue.Step.HasValue)
|
||||
{
|
||||
ImU8String text = new ImU8String(6, 1);
|
||||
text.AppendLiteral("Step: ");
|
||||
text.AppendFormatted(issue.Step);
|
||||
ImGui.Text(text);
|
||||
}
|
||||
ImGui.Separator();
|
||||
ImGui.Text("Description:");
|
||||
string description = CleanJsonText(issue.Description ?? "(no description)");
|
||||
if (issue.Type == EIssueType.QuestDisabled && issue.ElementId == null)
|
||||
{
|
||||
DrawDisabledTribesDetails(description);
|
||||
}
|
||||
else if (issue.Type == EIssueType.InvalidJsonSchema)
|
||||
{
|
||||
DrawEnhancedJsonSchemaDetails(description);
|
||||
}
|
||||
else if (issue.Type == EIssueType.InvalidJsonSyntax)
|
||||
{
|
||||
DrawJsonSyntaxErrorDetails(description);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawGenericDetails(description);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawJsonSyntaxErrorDetails(string description)
|
||||
{
|
||||
string[] array = description.Split('\n');
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
string text = array[i].Trim();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
ImGui.Spacing();
|
||||
}
|
||||
else if (text.StartsWith("JSON parsing error", StringComparison.Ordinal))
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
else if (text.StartsWith("This usually indicates", StringComparison.Ordinal) || text.StartsWith("Please check", StringComparison.Ordinal))
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.HealerGreen))
|
||||
{
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
else if (text.StartsWith("\ufffd ", StringComparison.Ordinal))
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow))
|
||||
{
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDisabledTribesDetails(string description)
|
||||
{
|
||||
string[] array = description.Split(':', 2);
|
||||
if (array.Length < 2)
|
||||
{
|
||||
ImGui.TextWrapped(description);
|
||||
return;
|
||||
}
|
||||
ImGui.TextWrapped(array[0]);
|
||||
ImGui.Spacing();
|
||||
List<string> list = (from x in array[1].Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
select x.Trim() into x
|
||||
where !string.IsNullOrEmpty(x)
|
||||
select x).Distinct().ToList();
|
||||
if (list.Count == 0)
|
||||
{
|
||||
ImGui.TextWrapped("(no disabled quests listed)");
|
||||
return;
|
||||
}
|
||||
ImGui.Text("Disabled Quests:");
|
||||
ImGui.Indent();
|
||||
Vector4[] array2 = new Vector4[6]
|
||||
{
|
||||
ImGuiColors.TankBlue,
|
||||
ImGuiColors.HealerGreen,
|
||||
ImGuiColors.DPSRed,
|
||||
ImGuiColors.ParsedGreen,
|
||||
ImGuiColors.ParsedBlue,
|
||||
ImGuiColors.DalamudViolet
|
||||
};
|
||||
for (int num = 0; num < list.Count; num++)
|
||||
{
|
||||
string value = list[num];
|
||||
Vector4 color = array2[num % array2.Length];
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, color))
|
||||
{
|
||||
if (ElementId.TryFromString(value, out ElementId elementId) && elementId != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
IQuestInfo questInfo = _questData.GetQuestInfo(elementId);
|
||||
ImU8String text = new ImU8String(5, 2);
|
||||
text.AppendLiteral("\ufffd ");
|
||||
text.AppendFormatted(value);
|
||||
text.AppendLiteral(" - ");
|
||||
text.AppendFormatted(questInfo.Name);
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ImU8String text = new ImU8String(18, 1);
|
||||
text.AppendLiteral("\ufffd ");
|
||||
text.AppendFormatted(value);
|
||||
text.AppendLiteral(" (unknown quest)");
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImU8String text = new ImU8String(2, 1);
|
||||
text.AppendLiteral("\ufffd ");
|
||||
text.AppendFormatted(value);
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui.Unindent();
|
||||
}
|
||||
|
||||
private void DrawEnhancedJsonSchemaDetails(string description)
|
||||
{
|
||||
if (description.Split('\n').Length == 0)
|
||||
{
|
||||
ImGui.TextWrapped("No validation details available.");
|
||||
return;
|
||||
}
|
||||
List<JsonValidationError> list = ParseJsonValidationErrors(description);
|
||||
if (list.Count > 0)
|
||||
{
|
||||
ImGui.Text("JSON Schema Validation Errors:");
|
||||
ImGui.Spacing();
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
DrawJsonValidationError(list[i], i);
|
||||
if (i < list.Count - 1)
|
||||
{
|
||||
ImGui.Separator();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawSimpleJsonSchemaDetails(description);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawJsonValidationError(JsonValidationError error, int index)
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow))
|
||||
{
|
||||
ImU8String text = new ImU8String(8, 1);
|
||||
text.AppendLiteral("Error #");
|
||||
text.AppendFormatted(index + 1);
|
||||
text.AppendLiteral(":");
|
||||
ImGui.Text(text);
|
||||
}
|
||||
ImGui.Indent(12f);
|
||||
if (!string.IsNullOrEmpty(error.Path))
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text("Location:");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedBlue))
|
||||
{
|
||||
ImGui.TextWrapped(FormatJsonPath(error.Path));
|
||||
}
|
||||
}
|
||||
if (error.Messages.Count > 0)
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text((error.Messages.Count == 1) ? "Issue:" : "Issues:");
|
||||
foreach (string message in error.Messages)
|
||||
{
|
||||
ImGui.Indent(12f);
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
string text2 = CleanJsonText(message);
|
||||
if (string.Equals(text2, "validation failed", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
text2 = "JSON schema validation failed - check that all properties match the expected format and values";
|
||||
}
|
||||
ImU8String text = new ImU8String(2, 1);
|
||||
text.AppendLiteral("\ufffd ");
|
||||
text.AppendFormatted(text2);
|
||||
ImGui.TextWrapped(text);
|
||||
ImGui.Unindent(12f);
|
||||
}
|
||||
}
|
||||
}
|
||||
List<string> validationSuggestions = GetValidationSuggestions(error);
|
||||
if (validationSuggestions.Count > 0)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.HealerGreen))
|
||||
{
|
||||
ImGui.Text("Suggestions:");
|
||||
foreach (string item in validationSuggestions)
|
||||
{
|
||||
ImGui.Indent(12f);
|
||||
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow))
|
||||
{
|
||||
ImGui.Text(FontAwesomeIcon.Lightbulb.ToIconString());
|
||||
}
|
||||
}
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.HealerGreen))
|
||||
{
|
||||
ImGui.TextWrapped(item);
|
||||
ImGui.Unindent(12f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui.Unindent(12f);
|
||||
}
|
||||
|
||||
private static void DrawSimpleJsonSchemaDetails(string description)
|
||||
{
|
||||
string[] array = description.Split('\n');
|
||||
foreach (string text in array)
|
||||
{
|
||||
if (text.StartsWith("JSON Validation failed:", StringComparison.Ordinal))
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
else if (text.StartsWith(" - ", StringComparison.Ordinal))
|
||||
{
|
||||
int num = text.IndexOf(':', 3);
|
||||
if (num > 0)
|
||||
{
|
||||
string path = text.Substring(3, num - 3).Trim();
|
||||
string text2 = CleanJsonText(text.Substring(num + 1).Trim());
|
||||
if (string.Equals(text2, "validation failed", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
text2 = "Schema validation failed - check property format and values";
|
||||
}
|
||||
ImGui.Text("\ufffd");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedBlue))
|
||||
{
|
||||
ImGui.Text(FormatJsonPath(path));
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(":");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
ImGui.TextWrapped(text2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
ImGui.TextWrapped(CleanJsonText(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextWrapped(CleanJsonText(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<JsonValidationError> ParseJsonValidationErrors(string description)
|
||||
{
|
||||
List<JsonValidationError> list = new List<JsonValidationError>();
|
||||
string[] array = description.Split('\n');
|
||||
foreach (string text in array)
|
||||
{
|
||||
if (!text.StartsWith(" - ", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int num = text.IndexOf(':', 3);
|
||||
if (num > 0)
|
||||
{
|
||||
string path = text.Substring(3, num - 3).Trim();
|
||||
List<string> messages = (from m in text.Substring(num + 1).Trim().Split(';', StringSplitOptions.RemoveEmptyEntries)
|
||||
select CleanJsonText(m.Trim()) into m
|
||||
where !string.IsNullOrEmpty(m)
|
||||
select m).ToList();
|
||||
list.Add(new JsonValidationError
|
||||
{
|
||||
Path = path,
|
||||
Messages = messages
|
||||
});
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static string FormatJsonPath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return "<root>";
|
||||
}
|
||||
Match match = JsonPropertyPathRegex.Match(path);
|
||||
if (match.Success)
|
||||
{
|
||||
string value = match.Groups[1].Value;
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return "<root>";
|
||||
}
|
||||
return value.Replace('/', '.');
|
||||
}
|
||||
if (!(path == "<root>"))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return "<root>";
|
||||
}
|
||||
|
||||
private static List<string> GetValidationSuggestions(JsonValidationError error)
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
foreach (string message in error.Messages)
|
||||
{
|
||||
string text = message.ToUpperInvariant();
|
||||
if (text.Contains("REQUIRED", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Add the missing required property to your JSON.");
|
||||
}
|
||||
else if (text.Contains("TYPE", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Check that the property value has the correct data type (string, number, boolean, etc.).");
|
||||
}
|
||||
else if (text.Contains("ENUM", StringComparison.Ordinal) || text.Contains("ALLOWED VALUES", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Use one of the allowed enumeration values for this property.");
|
||||
}
|
||||
else if (text.Contains("FORMAT", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Ensure the property value follows the expected format.");
|
||||
}
|
||||
else if (text.Contains("MINIMUM", StringComparison.Ordinal) || text.Contains("MAXIMUM", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Check that numeric values are within the allowed range.");
|
||||
}
|
||||
else if (text.Contains("ADDITIONAL", StringComparison.Ordinal) && text.Contains("NOT ALLOWED", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Remove any extra properties that are not defined in the schema.");
|
||||
}
|
||||
else if (text.Contains("VALIDATION FAILED", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Review the JSON structure and ensure all properties match the expected schema format.");
|
||||
}
|
||||
}
|
||||
return list.Distinct().ToList();
|
||||
}
|
||||
|
||||
private static void DrawGenericDetails(string description)
|
||||
{
|
||||
string[] array = description.Split('\n');
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
string text = array[i].Trim();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
ImGui.Spacing();
|
||||
}
|
||||
else if (text.StartsWith("Error:", StringComparison.Ordinal) || text.StartsWith("Invalid", StringComparison.Ordinal) || text.StartsWith("Missing", StringComparison.Ordinal))
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
else if (text.Contains(':', StringComparison.Ordinal))
|
||||
{
|
||||
int num = text.IndexOf(':', StringComparison.Ordinal);
|
||||
string text2 = text.Substring(0, num);
|
||||
string text3 = text.Substring(num + 1).TrimStart();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedBlue))
|
||||
{
|
||||
ImGui.Text(text2 + ":");
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.TextWrapped(text3);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue