Skip to content

Commit

Permalink
Merge pull request #1319 from CitiesSkylinesMods/limit-whats-new
Browse files Browse the repository at this point in the history
Refactor `WhatsNew.cs` and update the UI
  • Loading branch information
originalfoo authored Jan 26, 2022
2 parents d3e0e23 + 84745b5 commit 6428b92
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 131 deletions.
1 change: 1 addition & 0 deletions TLM/TLM/UI/WhatsNew/MarkupKeyword.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public enum MarkupKeyword {
Link,
Released,
Stable,
Meta,
New,
Mod,
Fixed,
Expand Down
145 changes: 86 additions & 59 deletions TLM/TLM/UI/WhatsNew/WhatsNew.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,26 @@ namespace TrafficManager.UI.WhatsNew {
using State;

public class WhatsNew {
private const string WHATS_NEW_FILE = "whats_new.txt";
private const string RESOURCES_PREFIX = "TrafficManager.Resources.";

// bump and update what's new changelogs when new features added
internal static readonly Version CurrentVersion = new Version(11,6,4,0);
internal static readonly Version CurrentVersion = new Version(11, 6, 4, 0);

internal static readonly Version PreviouslySeenVersion = GlobalConfig.Instance.Main.LastWhatsNewPanelVersion;

internal bool Shown => CurrentVersion == GlobalConfig.Instance.Main.LastWhatsNewPanelVersion;
public List<ChangelogEntry> Changelogs { get; private set; }
internal bool Shown => PreviouslySeenVersion >= CurrentVersion;

private const string WHATS_NEW_FILE = "whats_new.txt";
private const string RESOURCES_PREFIX = "TrafficManager.Resources.";

public WhatsNew() {
LoadChangelog();
LoadChangelogs();
}

public List<Changelog> Changelogs { get; private set; }

public static void OpenModal() {
UIView uiView = UIView.GetAView();
if (uiView) {
MarkAsShown();
WhatsNewPanel panel = uiView.AddUIComponent(typeof(WhatsNewPanel)) as WhatsNewPanel;
if (panel) {
Log.Info("Opened What's New panel!");
Expand All @@ -40,14 +44,13 @@ public static void OpenModal() {
}
}

public void MarkAsShown() {
public static void MarkAsShown() {
Log.Info($"What's New - mark as shown. Version {CurrentVersion}");
GlobalConfig.Instance.Main.LastWhatsNewPanelVersion = CurrentVersion;
GlobalConfig.WriteConfig();
}


private void LoadChangelog() {
private void LoadChangelogs() {
Log.Info("Loading What's New changelogs...");
string[] lines;
using (Stream st = Assembly.GetExecutingAssembly()
Expand All @@ -57,42 +60,47 @@ private void LoadChangelog() {
lines = sr.ReadToEnd().Split(new[] { "\n", "\r\n" }, StringSplitOptions.None);
}

Changelogs = ChangelogEntry.ParseChangelogs(lines);
Changelogs = ParseChangelogs(lines);
}
Log.Info($"Loaded {Changelogs.Count} What's New changelogs");
}
}

public class ChangelogEntry {
public Version Version { get; private set; }
public bool Stable { get; private set; }
[CanBeNull]
public string Link { get; private set; }
[CanBeNull]
public string Released { get; private set; }
public ChangeEntry[] ChangeEntries { get; private set; }

public static List<ChangelogEntry> ParseChangelogs(string[] lines) {
List<ChangelogEntry> entries = new List<ChangelogEntry>();
/// <summary>
/// Parses the changelogs in <c>whats_new.txt</c>.
/// </summary>
/// <param name="lines">The contents of <c>whats_new.txt</c>.</param>
/// <returns>A list of <see cref="Changelog"/>.</returns>
/// <exception cref="FormatException">
/// Version blocks must contain only one <c>[Version]</c> tag
/// and must end with a <c>[/Version]</c> tag.
/// </exception>
/// <exception cref="IndexOutOfRangeException">
/// Ensure each version block ends with <c>[/Version]</c>.
/// </exception>
/// <remarks><seealso cref="https://github.com/CitiesSkylinesMods/TMPE/wiki/Changelogs#whats-new-panel"/> .</remarks>
private static List<Changelog> ParseChangelogs(string[] lines) {
var changelogs = new List<Changelog>();
int i = 0;
var keywordStrings = WhatsNewMarkup.MarkupKeywordsString;

while (i < lines.Length) {
string line = lines[i];

if (TryParseKeyword(line, out MarkupKeyword lineKeyword) && lineKeyword == MarkupKeyword.VersionStart) {
ChangelogEntry changelog = new ChangelogEntry();
// read version
changelog.Version = new Version(lines[i++].Substring(keywordStrings[MarkupKeyword.VersionStart].Length).Trim());
if (TryParseKeyword(line, out MarkupKeyword lineKeyword, out string text)
&& lineKeyword == MarkupKeyword.VersionStart) {

//get next line keyword
TryParseKeyword(lines[i], out lineKeyword);
// parse to the end of version section
List<ChangeEntry> changeEntries = new List<ChangeEntry>();
var changelog = new Changelog() {
Version = new Version(text),
};
var items = new List<Changelog.Item>();

// Parse contents of [Version]..[/Version] block
TryParseKeyword(lines[++i], out lineKeyword, out text);
while (lineKeyword != MarkupKeyword.VersionEnd) {
string text = lines[i].Substring(keywordStrings[lineKeyword].Length).Trim();
Log._Debug($"Keyword {lineKeyword}, text: {text}");

// Log._Debug($"Keyword {lineKeyword}, Text: {text}");
switch (lineKeyword) {
// TODO: Should we also check for VersionStart keyword and throw an error if encountered? (a changelog block missing the [/Version])
case MarkupKeyword.VersionStart:
throw new FormatException($"whats_new.txt line {i}: Unexpected '[Version]' tag.");
case MarkupKeyword.Stable:
changelog.Stable = true;
break;
Expand All @@ -103,58 +111,77 @@ public static List<ChangelogEntry> ParseChangelogs(string[] lines) {
changelog.Released = text;
break;
case MarkupKeyword.Unknown:
//skip unknown entries
// skip unknown entries
Log.Warning($"whats_new.txt line {i}: Unrecognised entry '{line}'");
break;
default:
changeEntries.Add(
new ChangeEntry() {
items.Add(
new Changelog.Item() {
Keyword = lineKeyword,
Text = text
Text = text,
});
break;
}

i++;
TryParseKeyword(lines[i], out lineKeyword);
TryParseKeyword(lines[++i], out lineKeyword, out text);
}

changelog.ChangeEntries = changeEntries.ToArray();
Array.Sort(changelog.ChangeEntries, ChangeEntry.KeywordComparer);
entries.Add(changelog);
changelog.Items = items.ToArray();
Array.Sort(changelog.Items, Changelog.Item.KeywordComparer);
changelogs.Add(changelog);

// If user already seen this version, don't bother parsing remainder of file
if (changelog.Version <= PreviouslySeenVersion) {
break;
}
}

i++;
}

return entries;
return changelogs;
}

private static bool TryParseKeyword(string line, out MarkupKeyword keyword) {
if (!string.IsNullOrEmpty(line)) {
if(line.StartsWith("[") &&
WhatsNewMarkup.MarkupKeywords.TryGetValue(
line.Substring(0, line.IndexOf("]") + 1),
out keyword)) {
return true;
}
Log.Warning($"Couldn't parse line \"{line}\"");
private static bool TryParseKeyword(string line, out MarkupKeyword keyword, out string text) {
if ((!string.IsNullOrEmpty(line)) && line.StartsWith("[")) {
int pos = line.IndexOf("]") + 1;
string tag = line.Substring(0, pos);

keyword = tag.ToKeyword();
text = line.Substring(pos).Trim();

return keyword != MarkupKeyword.Unknown;
}

keyword = MarkupKeyword.Unknown;
text = line;
return false;
}
}

/// <summary>
/// Represents the changelog for a release (ie. <c>[Version]..[/Version]</c> block).
/// </summary>
public class Changelog {
public Version Version { get; set; }
public bool Stable { get; set; }
[CanBeNull]
public string Link { get; set; }
[CanBeNull]
public string Released { get; set; }
public Item[] Items { get; set; }

public struct ChangeEntry {
public struct Item {
public MarkupKeyword Keyword;
public string Text;

private sealed class KeywordRelationalComparer : IComparer<ChangeEntry> {
public int Compare(ChangeEntry x, ChangeEntry y) {
public static IComparer<Item> KeywordComparer { get; } = new KeywordRelationalComparer();

private sealed class KeywordRelationalComparer : IComparer<Item> {
public int Compare(Item x, Item y) {
return x.Keyword.CompareTo(y.Keyword);
}
}

public static IComparer<ChangeEntry> KeywordComparer { get; } = new KeywordRelationalComparer();
}
}
}
56 changes: 28 additions & 28 deletions TLM/TLM/UI/WhatsNew/WhatsNewMarkup.cs
Original file line number Diff line number Diff line change
@@ -1,63 +1,63 @@
namespace TrafficManager.UI.WhatsNew {
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using CSUtil.Commons;
using UnityEngine;

[SuppressMessage("Usage", "RAS0002:Readonly field for a non-readonly struct", Justification = "Not performance critical.")]
public static class WhatsNewMarkup {
public static readonly Dictionary<string, MarkupKeyword> MarkupKeywords = new() {
public static MarkupKeyword ToKeyword(this string token) {
if (MarkupKeywords.TryGetValue(token, out MarkupKeyword keyword)) {
return keyword;
}
return MarkupKeyword.Unknown;
}

public static Color32 ToColor(this MarkupKeyword keyword) {
return GetColor(keyword);
}

private static readonly Dictionary<string, MarkupKeyword> MarkupKeywords = new() {
{ "[Version]", MarkupKeyword.VersionStart },
{ "[/Version]", MarkupKeyword.VersionEnd },
{ "[Stable]", MarkupKeyword.Stable },
{ "[Link]", MarkupKeyword.Link },
{ "[Released]", MarkupKeyword.Released },
{ "[Meta]", MarkupKeyword.Meta },
{ "[New]", MarkupKeyword.New },
{ "[Mod]", MarkupKeyword.Mod },
{ "[Fixed]", MarkupKeyword.Fixed },
{ "[Updated]", MarkupKeyword.Updated },
{ "[Removed]", MarkupKeyword.Removed },
};

public static readonly Dictionary<MarkupKeyword, string> MarkupKeywordsString = new() {
{ MarkupKeyword.VersionStart, "[Version]" },
{ MarkupKeyword.VersionEnd, "[/Version]" },
{ MarkupKeyword.Stable, "[Stable]" },
{ MarkupKeyword.Link, "[Link]" },
{ MarkupKeyword.Released, "[Released]" },
{ MarkupKeyword.New, "[New]" },
{ MarkupKeyword.Mod, "[Mod]" },
{ MarkupKeyword.Fixed, "[Fixed]" },
{ MarkupKeyword.Updated, "[Updated]" },
{ MarkupKeyword.Removed, "[Removed]" },
};

public static readonly Color32 ModColor = new Color32(255, 196, 0, 255);
public static readonly Color32 FixedOrUpdatedColor = new Color32(3, 106, 225, 255);
public static readonly Color32 NewOrAddedColor = new Color32(40, 178, 72, 255);
public static readonly Color32 RemovedColor = new Color32(224, 61, 76, 255);
public static readonly Color32 VersionColor = new Color32(119, 69, 204, 255);
private static readonly Color32 Red = new (224, 61, 76, 255);
private static readonly Color32 Amber = new (255, 196, 0, 255);
private static readonly Color32 Green = new (40, 178, 72, 255);
private static readonly Color32 Blue = new (3, 106, 225, 255);
private static readonly Color32 Purple = new (119, 69, 204, 255);

public static Color32 GetColor(MarkupKeyword keyword) {
private static Color32 GetColor(MarkupKeyword keyword) {
switch (keyword) {
case MarkupKeyword.Fixed:
return FixedOrUpdatedColor;
return Blue;
case MarkupKeyword.New:
return NewOrAddedColor;
return Green;
case MarkupKeyword.Mod:
return ModColor;
return Amber;
case MarkupKeyword.Removed:
return RemovedColor;
return Red;
case MarkupKeyword.Updated:
return FixedOrUpdatedColor;
return Blue;
case MarkupKeyword.VersionStart:
case MarkupKeyword.VersionEnd:
case MarkupKeyword.Stable:
return VersionColor;
case MarkupKeyword.Meta:
return Purple;
default:
Log.Warning($"No custom color for markup keyword: {keyword}");
return Color.white;
}
}


}
}
Loading

0 comments on commit 6428b92

Please sign in to comment.