Changelog. (#3398)
This commit is contained in:
committed by
GitHub
parent
fdcbece63d
commit
cca23f2812
53
Content.Client/Changelog/ChangelogButton.cs
Normal file
53
Content.Client/Changelog/ChangelogButton.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Changelog
|
||||
{
|
||||
public sealed class ChangelogButton : Button
|
||||
{
|
||||
[Dependency] private readonly ChangelogManager _changelogManager = default!;
|
||||
|
||||
public ChangelogButton()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
OnPressed += OnOnPressed;
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
_changelogManager.NewChangelogEntriesChanged += UpdateStuff;
|
||||
UpdateStuff();
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
_changelogManager.NewChangelogEntriesChanged -= UpdateStuff;
|
||||
}
|
||||
|
||||
private void OnOnPressed(ButtonEventArgs obj)
|
||||
{
|
||||
new ChangelogWindow().OpenCentered();
|
||||
}
|
||||
|
||||
private void UpdateStuff()
|
||||
{
|
||||
if (_changelogManager.NewChangelogEntries)
|
||||
{
|
||||
Text = Loc.GetString("changelog-button-new-entries");
|
||||
StyleClasses.Add(StyleBase.ButtonCaution);
|
||||
}
|
||||
else
|
||||
{
|
||||
Text = Loc.GetString("changelog-button");
|
||||
StyleClasses.Remove(StyleBase.ButtonCaution);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
Content.Client/Changelog/ChangelogManager.cs
Normal file
124
Content.Client/Changelog/ChangelogManager.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Content.Client.Changelog
|
||||
{
|
||||
public sealed class ChangelogManager
|
||||
{
|
||||
// If you fork SS14, change this to have the changelog "last seen" date stored separately.
|
||||
public const string ForkId = "Wizards";
|
||||
|
||||
[Dependency] private readonly IResourceManager _resource = default!;
|
||||
|
||||
public bool NewChangelogEntries { get; private set; }
|
||||
public int LastReadId { get; private set; }
|
||||
public int MaxId { get; private set; }
|
||||
|
||||
public event Action? NewChangelogEntriesChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Ran when the user opens ("read") the changelog,
|
||||
/// stores the new ID to disk and clears <see cref="NewChangelogEntries"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="LastReadId"/> is NOT cleared
|
||||
/// since that's used in the changelog menu to show the "since you last read" bar.
|
||||
/// </remarks>
|
||||
public void SaveNewReadId()
|
||||
{
|
||||
NewChangelogEntries = false;
|
||||
NewChangelogEntriesChanged?.Invoke();
|
||||
|
||||
using var file = _resource.UserData.Create(new ResourcePath($"/changelog_last_seen_{ForkId}"));
|
||||
using var sw = new StreamWriter(file);
|
||||
|
||||
sw.Write(MaxId.ToString());
|
||||
}
|
||||
|
||||
public async void Initialize()
|
||||
{
|
||||
// Open changelog purely to compare to the last viewed date.
|
||||
var changelog = await LoadChangelog();
|
||||
|
||||
if (changelog.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MaxId = changelog.Max(c => c.Id);
|
||||
|
||||
var path = new ResourcePath($"/changelog_last_seen_{ForkId}");
|
||||
if (_resource.UserData.Exists(path))
|
||||
{
|
||||
LastReadId = int.Parse(_resource.UserData.ReadAllText(path));
|
||||
}
|
||||
|
||||
NewChangelogEntries = LastReadId < MaxId;
|
||||
|
||||
NewChangelogEntriesChanged?.Invoke();
|
||||
}
|
||||
|
||||
public Task<List<ChangelogEntry>> LoadChangelog()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var yamlData = _resource.ContentFileReadYaml(new ResourcePath("/Changelog/Changelog.yml"));
|
||||
|
||||
if (yamlData.Documents.Count == 0)
|
||||
return new List<ChangelogEntry>();
|
||||
|
||||
var serializer = YamlObjectSerializer.NewReader((YamlMappingNode) yamlData.Documents[0].RootNode);
|
||||
|
||||
return serializer.ReadDataField<List<ChangelogEntry>>("Entries");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public sealed class ChangelogEntry : IExposeData
|
||||
{
|
||||
public int Id { get; private set; }
|
||||
public string Author { get; private set; } = "";
|
||||
public DateTime Time { get; private set; }
|
||||
public List<ChangelogChange> Changes { get; private set; } = default!;
|
||||
|
||||
void IExposeData.ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
Id = serializer.ReadDataField<int>("id");
|
||||
Author = serializer.ReadDataField<string>("author");
|
||||
Time = DateTime.Parse(serializer.ReadDataField<string>("time"), null, DateTimeStyles.RoundtripKind);
|
||||
Changes = serializer.ReadDataField<List<ChangelogChange>>("changes");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ChangelogChange : IExposeData
|
||||
{
|
||||
public ChangelogLineType Type { get; private set; }
|
||||
public string Message { get; private set; } = "";
|
||||
|
||||
void IExposeData.ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
Type = serializer.ReadDataField<ChangelogLineType>("type");
|
||||
Message = serializer.ReadDataField<string>("message");
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChangelogLineType
|
||||
{
|
||||
Add,
|
||||
Remove,
|
||||
Fix,
|
||||
Tweak,
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Content.Client/Changelog/ChangelogWindow.xaml
Normal file
23
Content.Client/Changelog/ChangelogWindow.xaml
Normal file
@@ -0,0 +1,23 @@
|
||||
<changelog:ChangelogWindow xmlns="https://spacestation14.io"
|
||||
xmlns:cui="clr-namespace:Content.Client.UserInterface"
|
||||
xmlns:cuic="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:changelog="clr-namespace:Content.Client.Changelog"
|
||||
MinSize="500 400" MouseFilter="Stop">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<VBoxContainer>
|
||||
<HBoxContainer>
|
||||
<Label Margin="6 0 0 0" HorizontalExpand="True" Text="{Loc 'changelog-window-title'}" VAlign="Center"
|
||||
StyleClasses="LabelHeading" />
|
||||
<TextureButton Margin="0 0 8 0" Name="CloseButton" StyleClasses="windowCloseButton"
|
||||
VerticalAlignment="Center" />
|
||||
</HBoxContainer>
|
||||
<cuic:HighDivider />
|
||||
|
||||
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
|
||||
<VBoxContainer Name="ChangelogBody" />
|
||||
</ScrollContainer>
|
||||
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<Label Name="VersionLabel" HorizontalAlignment="Right" StyleClasses="LabelSubText" Margin="4 0" />
|
||||
</VBoxContainer>
|
||||
</changelog:ChangelogWindow>
|
||||
207
Content.Client/Changelog/ChangelogWindow.xaml.cs
Normal file
207
Content.Client/Changelog/ChangelogWindow.xaml.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Content.Client.Utility;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Client.Changelog.ChangelogManager;
|
||||
|
||||
namespace Content.Client.Changelog
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ChangelogWindow : BaseWindow
|
||||
{
|
||||
[Dependency] private readonly ChangelogManager _changelog = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
public ChangelogWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||
CloseButton.OnPressed += _ => Close();
|
||||
}
|
||||
|
||||
protected override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
|
||||
_changelog.SaveNewReadId();
|
||||
PopulateChangelog();
|
||||
}
|
||||
|
||||
private async void PopulateChangelog()
|
||||
{
|
||||
// Changelog is not kept in memory so load it again.
|
||||
var changelog = await _changelog.LoadChangelog();
|
||||
|
||||
var byDay = changelog
|
||||
.GroupBy(e => e.Time.ToLocalTime().Date)
|
||||
.OrderByDescending(c => c.Key);
|
||||
|
||||
var hasRead = _changelog.MaxId <= _changelog.LastReadId;
|
||||
foreach (var dayEntries in byDay)
|
||||
{
|
||||
var day = dayEntries.Key;
|
||||
|
||||
var groupedEntries = dayEntries
|
||||
.GroupBy(c => (c.Author, Read: c.Id <= _changelog.LastReadId))
|
||||
.OrderBy(c => c.Key.Read)
|
||||
.ThenBy(c => c.Key.Author);
|
||||
|
||||
string dayNice;
|
||||
var today = DateTime.Today;
|
||||
if (day == today)
|
||||
dayNice = Loc.GetString("changelog-today");
|
||||
else if (day == today.AddDays(-1))
|
||||
dayNice = Loc.GetString("changelog-yesterday");
|
||||
else
|
||||
dayNice = day.ToShortDateString();
|
||||
|
||||
ChangelogBody.AddChild(new Label
|
||||
{
|
||||
Text = dayNice,
|
||||
StyleClasses = {"LabelHeading"},
|
||||
Margin = new Thickness(4, 6, 0, 0)
|
||||
});
|
||||
|
||||
var first = true;
|
||||
|
||||
foreach (var groupedEntry in groupedEntries)
|
||||
{
|
||||
var (author, read) = groupedEntry.Key;
|
||||
|
||||
if (!first)
|
||||
{
|
||||
ChangelogBody.AddChild(new Control {Margin = new Thickness(4)});
|
||||
}
|
||||
|
||||
if (read && !hasRead)
|
||||
{
|
||||
hasRead = true;
|
||||
|
||||
var upArrow =
|
||||
_resourceCache.GetTexture("/Textures/Interface/Changelog/up_arrow.svg.192dpi.png");
|
||||
|
||||
var readDivider = new VBoxContainer();
|
||||
|
||||
var hBox = new HBoxContainer
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Children =
|
||||
{
|
||||
new TextureRect
|
||||
{
|
||||
Texture = upArrow,
|
||||
ModulateSelfOverride = Color.FromHex("#888"),
|
||||
TextureScale = (0.5f, 0.5f),
|
||||
Margin = new Thickness(4, 3),
|
||||
VerticalAlignment = VAlignment.Bottom
|
||||
},
|
||||
new Label
|
||||
{
|
||||
Align = Label.AlignMode.Center,
|
||||
Text = Loc.GetString("changelog-new-changes"),
|
||||
FontColorOverride = Color.FromHex("#888"),
|
||||
},
|
||||
new TextureRect
|
||||
{
|
||||
Texture = upArrow,
|
||||
ModulateSelfOverride = Color.FromHex("#888"),
|
||||
TextureScale = (0.5f, 0.5f),
|
||||
Margin = new Thickness(4, 3),
|
||||
VerticalAlignment = VAlignment.Bottom
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
readDivider.AddChild(hBox);
|
||||
readDivider.AddChild(new PanelContainer {StyleClasses = {"LowDivider"}});
|
||||
ChangelogBody.AddChild(readDivider);
|
||||
|
||||
if (first)
|
||||
readDivider.SetPositionInParent(ChangelogBody.ChildCount - 2);
|
||||
}
|
||||
|
||||
first = false;
|
||||
|
||||
var authorLabel = new RichTextLabel
|
||||
{
|
||||
Margin = new Thickness(6, 0, 0, 0),
|
||||
};
|
||||
authorLabel.SetMessage(
|
||||
FormattedMessage.FromMarkup(Loc.GetString("changelog-author-changed", ("author", author))));
|
||||
ChangelogBody.AddChild(authorLabel);
|
||||
|
||||
foreach (var change in groupedEntry.SelectMany(c => c.Changes))
|
||||
{
|
||||
var text = new RichTextLabel();
|
||||
text.SetMessage(FormattedMessage.FromMarkup(change.Message));
|
||||
ChangelogBody.AddChild(new HBoxContainer
|
||||
{
|
||||
Margin = new Thickness(14, 1, 10, 2),
|
||||
Children =
|
||||
{
|
||||
GetIcon(change.Type),
|
||||
text
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var version = typeof(ChangelogWindow).Assembly.GetName().Version ?? new Version(1, 0);
|
||||
VersionLabel.Text = Loc.GetString("changelog-version-tag", ("version", version.ToString()));
|
||||
}
|
||||
|
||||
private TextureRect GetIcon(ChangelogLineType type)
|
||||
{
|
||||
var (file, color) = type switch
|
||||
{
|
||||
ChangelogLineType.Add => ("plus.svg.192dpi.png", "#6ED18D"),
|
||||
ChangelogLineType.Remove => ("minus.svg.192dpi.png", "#D16E6E"),
|
||||
ChangelogLineType.Fix => ("bug.svg.192dpi.png", "#D1BA6E"),
|
||||
ChangelogLineType.Tweak => ("wrench.svg.192dpi.png", "#6E96D1"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
|
||||
};
|
||||
|
||||
return new TextureRect
|
||||
{
|
||||
Texture = _resourceCache.GetTexture(new ResourcePath($"/Textures/Interface/Changelog/{file}")),
|
||||
VerticalAlignment = VAlignment.Top,
|
||||
TextureScale = (0.5f, 0.5f),
|
||||
Margin = new Thickness(2, 4, 6, 2),
|
||||
ModulateSelfOverride = Color.FromHex(color)
|
||||
};
|
||||
}
|
||||
|
||||
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
|
||||
{
|
||||
return DragMode.Move;
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class ChangelogCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "changelog";
|
||||
public string Description => "Opens the changelog";
|
||||
public string Help => "Usage: changelog";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
new ChangelogWindow().OpenCentered();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user