Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create In-Guidebook Errors #28942

Merged
merged 4 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Content.Client/Guidebook/Controls/GuidebookError.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Margin="5 5 5 5"
MinHeight="200">
<PanelContainer HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="2" BorderColor="White" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical">

<PanelContainer>
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="0 0 0 1" BackgroundColor="DarkRed" BorderColor="Black" />
</PanelContainer.PanelOverride>
<Label Margin="5" StyleClasses="bold" Text="{Loc 'guidebook-parser-error'}" />
</PanelContainer>

<OutputPanel Margin="5" MinHeight="75" VerticalExpand="True" Name="Original">
<OutputPanel.StyleBoxOverride>
<gfx:StyleBoxFlat BorderThickness="0 0 0 1" BorderColor="Gray"
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
ContentMarginBottomOverride="3" ContentMarginTopOverride="3" />
</OutputPanel.StyleBoxOverride>
</OutputPanel>

<Collapsible Margin="5" MinHeight="75" VerticalExpand="True">
<CollapsibleHeading Title="{Loc 'guidebook-error-message' }" />
<CollapsibleBody VerticalExpand="True">
<OutputPanel Name="Error" VerticalExpand="True" MinHeight="100">
<OutputPanel.StyleBoxOverride>
<gfx:StyleBoxFlat
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
ContentMarginBottomOverride="3" ContentMarginTopOverride="3" />
</OutputPanel.StyleBoxOverride>
</OutputPanel>
</CollapsibleBody>
</Collapsible>

</BoxContainer>
</PanelContainer>
</BoxContainer>
23 changes: 23 additions & 0 deletions Content.Client/Guidebook/Controls/GuidebookError.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;

namespace Content.Client.Guidebook.Controls;

[UsedImplicitly] [GenerateTypedNameReferences]
public sealed partial class GuidebookError : BoxContainer
{
public GuidebookError()
{
RobustXamlLoader.Load(this);
}

public GuidebookError(string original, string? error) : this()
{
Original.AddText(original);

if (error is not null)
Error.AddText(error);
}
}
55 changes: 30 additions & 25 deletions Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Controls.FancyTree;
using Content.Client.UserInterface.Systems.Info;
using Content.Shared.CCVar;
using Content.Shared.Guidebook;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;

Expand All @@ -18,15 +16,18 @@ namespace Content.Client.Guidebook.Controls;
[GenerateTypedNameReferences]
public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
{
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!;

private Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> _entries = new();

private readonly ISawmill _sawmill;

public GuidebookWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sawmill = Logger.GetSawmill("Guidebook");

Tree.OnSelectedItemChanged += OnSelectionChanged;

Expand All @@ -36,6 +37,20 @@ public GuidebookWindow()
};
}

public void HandleClick(string link)
{
if (!_entries.TryGetValue(link, out var entry))
return;

if (Tree.TryGetIndexFromMetadata(entry, out var index))
{
Tree.ExpandParentEntries(index.Value);
Tree.SetSelectedIndex(index);
}
else
ShowGuide(entry);
}

private void OnSelectionChanged(TreeItem? item)
{
if (item != null && item.Metadata is GuideEntry entry)
Expand Down Expand Up @@ -71,8 +86,9 @@ private void ShowGuide(GuideEntry entry)

if (!_parsingMan.TryAddMarkup(EntryContainer, file.ReadToEnd()))
{
EntryContainer.AddChild(new Label() { Text = "ERROR: Failed to parse document." });
Logger.Error($"Failed to parse contents of guide document {entry.Id}.");
// The guidebook will automatically display the in-guidebook error if it fails

_sawmill.Error($"Failed to parse contents of guide document {entry.Id}.");
}
}

Expand Down Expand Up @@ -124,8 +140,10 @@ private IEnumerable<GuideEntry> GetSortedEntries(List<ProtoId<GuideEntryPrototyp

entry.Children = sortedChildren;
}

entries.ExceptWith(entry.Children);
}

rootEntries = entries.ToList();
}

Expand All @@ -135,21 +153,25 @@ private IEnumerable<GuideEntry> GetSortedEntries(List<ProtoId<GuideEntryPrototyp
.ThenBy(rootEntry => Loc.GetString(rootEntry.Name));
}

private void RepopulateTree(List<ProtoId<GuideEntryPrototype>>? roots = null, ProtoId<GuideEntryPrototype>? forcedRoot = null)
private void RepopulateTree(List<ProtoId<GuideEntryPrototype>>? roots = null,
ProtoId<GuideEntryPrototype>? forcedRoot = null)
{
Tree.Clear();

HashSet<ProtoId<GuideEntryPrototype>> addedEntries = new();

TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
var parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
foreach (var entry in GetSortedEntries(roots))
{
AddEntry(entry.Id, parent, addedEntries);
}

Tree.SetAllExpanded(true);
}

private TreeItem? AddEntry(ProtoId<GuideEntryPrototype> id, TreeItem? parent, HashSet<ProtoId<GuideEntryPrototype>> addedEntries)
private TreeItem? AddEntry(ProtoId<GuideEntryPrototype> id,
TreeItem? parent,
HashSet<ProtoId<GuideEntryPrototype>> addedEntries)
{
if (!_entries.TryGetValue(id, out var entry))
return null;
Expand Down Expand Up @@ -179,22 +201,6 @@ private void RepopulateTree(List<ProtoId<GuideEntryPrototype>>? roots = null, Pr
return item;
}

public void HandleClick(string link)
{
if (!_entries.TryGetValue(link, out var entry))
return;

if (Tree.TryGetIndexFromMetadata(entry, out var index))
{
Tree.ExpandParentEntries(index.Value);
Tree.SetSelectedIndex(index);
}
else
{
ShowGuide(entry);
}
}

private void HandleFilter()
{
var emptySearch = SearchBar.Text.Trim().Length == 0;
Expand All @@ -208,6 +214,5 @@ private void HandleFilter()
element.SetHiddenState(true, SearchBar.Text.Trim());
}
}

}
}
79 changes: 53 additions & 26 deletions Content.Client/Guidebook/DocumentParsingManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using Content.Client.Guidebook.Controls;
using Content.Client.Guidebook.Richtext;
using Content.Shared.Guidebook;
using Pidgin;
Expand All @@ -7,6 +8,7 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Sandboxing;
using Robust.Shared.Utility;
using static Pidgin.Parser;

namespace Content.Client.Guidebook;
Expand All @@ -22,8 +24,10 @@ public sealed partial class DocumentParsingManager
[Dependency] private readonly ISandboxHelper _sandboxHelper = default!;

private readonly Dictionary<string, Parser<char, Control>> _tagControlParsers = new();
private Parser<char, Control> _tagParser = default!;
private Parser<char, Control> _controlParser = default!;

private ISawmill _sawmill = default!;
private Parser<char, Control> _tagParser = default!;
public Parser<char, IEnumerable<Control>> ControlParser = default!;

public void Initialize()
Expand All @@ -32,14 +36,17 @@ public void Initialize()
.Assert(_tagControlParsers.ContainsKey, tag => $"unknown tag: {tag}")
.Bind(tag => _tagControlParsers[tag]);

_controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser).Before(SkipWhitespaces);
_controlParser = OneOf(_tagParser, TryHeaderControl, ListControlParser, TextControlParser)
.Before(SkipWhitespaces);

foreach (var typ in _reflectionManager.GetAllChildren<IDocumentTag>())
{
_tagControlParsers.Add(typ.Name, CreateTagControlParser(typ.Name, typ, _sandboxHelper));
}

ControlParser = SkipWhitespaces.Then(_controlParser.Many());

_sawmill = Logger.GetSawmill("Guidebook");
}

public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId, bool log = true)
Expand Down Expand Up @@ -68,37 +75,57 @@ public bool TryAddMarkup(Control control, string text, bool log = true)
}
catch (Exception e)
{
if (log)
Logger.Error($"Encountered error while generating markup controls: {e}");
_sawmill.Error($"Encountered error while generating markup controls: {e}");

control.AddChild(new GuidebookError(text, e.ToStringBetter()));

return false;
}

return true;
}

private Parser<char, Control> CreateTagControlParser(string tagId, Type tagType, ISandboxHelper sandbox) => Map(
(args, controls) =>
{
var tag = (IDocumentTag) sandbox.CreateInstance(tagType);
if (!tag.TryParseTag(args, out var control))
{
Logger.Error($"Failed to parse {tagId} args");
return new Control();
}
private Parser<char, Control> CreateTagControlParser(string tagId, Type tagType, ISandboxHelper sandbox)
{
return Map(
(args, controls) =>
{
try
{
var tag = (IDocumentTag) sandbox.CreateInstance(tagType);
if (!tag.TryParseTag(args, out var control))
{
_sawmill.Error($"Failed to parse {tagId} args");
return new GuidebookError(args.ToString() ?? tagId, $"Failed to parse {tagId} args");
}

foreach (var child in controls)
{
control.AddChild(child);
}
return control;
},
ParseTagArgs(tagId),
TagContentParser(tagId)).Labelled($"{tagId} control");
foreach (var child in controls)
{
control.AddChild(child);
}

return control;
}
catch (Exception e)
{
var output = args.Aggregate(string.Empty,
(current, pair) => current + $"{pair.Key}=\"{pair.Value}\" ");

_sawmill.Error($"Tag: {tagId} \n Arguments: {output}/>");
return new GuidebookError($"Tag: {tagId}\nArguments: {output}", e.ToString());
}
},
ParseTagArgs(tagId),
TagContentParser(tagId))
.Labelled($"{tagId} control");
}

// Parse a bunch of controls until we encounter a matching closing tag.
private Parser<char, IEnumerable<Control>> TagContentParser(string tag) =>
OneOf(
Try(ImmediateTagEnd).ThenReturn(Enumerable.Empty<Control>()),
TagEnd.Then(_controlParser.Until(TryTagTerminator(tag)).Labelled($"{tag} children"))
);
private Parser<char, IEnumerable<Control>> TagContentParser(string tag)
{
return OneOf(
Try(ImmediateTagEnd).ThenReturn(Enumerable.Empty<Control>()),
TagEnd.Then(_controlParser.Until(TryTagTerminator(tag)).Labelled($"{tag} children"))
);
}
}
Loading
Loading