Add support for multiple changelog files, add admin changelog (#20849)

This commit is contained in:
DrSmugleaf
2023-10-12 15:45:04 -07:00
committed by GitHub
parent 4159b7b5e8
commit d6575eb556
10 changed files with 402 additions and 168 deletions

View File

@@ -130,5 +130,13 @@ namespace Content.Client.Administration.Managers
return null; return null;
} }
public AdminData? GetAdminData(bool includeDeAdmin = false)
{
if (_player.LocalPlayer is { Session: { } session })
return GetAdminData(session, includeDeAdmin);
return null;
}
} }
} }

View File

@@ -1,5 +1,4 @@
using System; using Content.Shared.Administration;
using Content.Shared.Administration;
namespace Content.Client.Administration.Managers namespace Content.Client.Administration.Managers
{ {
@@ -13,6 +12,15 @@ namespace Content.Client.Administration.Managers
/// </summary> /// </summary>
event Action AdminStatusUpdated; event Action AdminStatusUpdated;
/// <summary>
/// Gets the admin data for the client, if they are an admin.
/// </summary>
/// <param name="includeDeAdmin">
/// Whether to return admin data for admins that are current de-adminned.
/// </param>
/// <returns><see langword="null" /> if the player is not an admin.</returns>
AdminData? GetAdminData(bool includeDeAdmin = false);
/// <summary> /// <summary>
/// Checks whether the local player is an admin. /// Checks whether the local player is an admin.
/// </summary> /// </summary>
@@ -52,5 +60,17 @@ namespace Content.Client.Administration.Managers
bool CanAdminMenu(); bool CanAdminMenu();
void Initialize(); void Initialize();
/// <summary>
/// Checks if the client is an admin.
/// </summary>
/// <param name="includeDeAdmin">
/// Whether to return admin data for admins that are current de-adminned.
/// </param>
/// <returns>true if the player is an admin, false otherwise.</returns>
bool IsAdmin(bool includeDeAdmin = false)
{
return GetAdminData(includeDeAdmin) != null;
}
} }
} }

View File

@@ -1,29 +1,29 @@
using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.ContentPack; using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown; using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Client.Changelog namespace Content.Client.Changelog
{ {
public sealed partial class ChangelogManager public sealed partial class ChangelogManager : IPostInjectInit
{ {
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IResourceManager _resource = default!; [Dependency] private readonly IResourceManager _resource = default!;
[Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly IConfigurationManager _configManager = default!;
private const string SawmillName = "changelog";
public const string MainChangelogName = "Changelog";
private ISawmill _sawmill = default!;
public bool NewChangelogEntries { get; private set; } public bool NewChangelogEntries { get; private set; }
public int LastReadId { get; private set; } public int LastReadId { get; private set; }
public int MaxId { get; private set; } public int MaxId { get; private set; }
@@ -51,17 +51,39 @@ namespace Content.Client.Changelog
public async void Initialize() public async void Initialize()
{ {
// Open changelog purely to compare to the last viewed date. // Open changelog purely to compare to the last viewed date.
var changelog = await LoadChangelog(); var changelogs = await LoadChangelog();
UpdateChangelogs(changelogs);
}
if (changelog.Count == 0) private void UpdateChangelogs(List<Changelog> changelogs)
{
if (changelogs.Count == 0)
{ {
return; return;
} }
MaxId = changelog.Max(c => c.Id); var mainChangelogs = changelogs.Where(c => c.Name == MainChangelogName).ToArray();
if (mainChangelogs.Length == 0)
{
_sawmill.Error($"No changelog file found in Resources/Changelog with name {MainChangelogName}");
return;
}
var changelog = changelogs[0];
if (mainChangelogs.Length > 1)
{
_sawmill.Error($"More than one file found in Resource/Changelog with name {MainChangelogName}");
}
if (changelog.Entries.Count == 0)
{
return;
}
MaxId = changelog.Entries.Max(c => c.Id);
var path = new ResPath($"/changelog_last_seen_{_configManager.GetCVar(CCVars.ServerId)}"); var path = new ResPath($"/changelog_last_seen_{_configManager.GetCVar(CCVars.ServerId)}");
if(_resource.UserData.TryReadAllText(path, out var lastReadIdText)) if (_resource.UserData.TryReadAllText(path, out var lastReadIdText))
{ {
LastReadId = int.Parse(lastReadIdText); LastReadId = int.Parse(lastReadIdText);
} }
@@ -71,20 +93,74 @@ namespace Content.Client.Changelog
NewChangelogEntriesChanged?.Invoke(); NewChangelogEntriesChanged?.Invoke();
} }
public Task<List<ChangelogEntry>> LoadChangelog() public Task<List<Changelog>> LoadChangelog()
{ {
return Task.Run(() => return Task.Run(() =>
{ {
var yamlData = _resource.ContentFileReadYaml(new ("/Changelog/Changelog.yml")); var changelogs = new List<Changelog>();
var directory = new ResPath("/Changelog");
foreach (var file in _resource.ContentFindFiles(new ResPath("/Changelog/")))
{
if (file.Directory != directory || file.Extension != "yml")
continue;
var yamlData = _resource.ContentFileReadYaml(file);
if (yamlData.Documents.Count == 0) if (yamlData.Documents.Count == 0)
return new List<ChangelogEntry>(); continue;
var node = (MappingDataNode)yamlData.Documents[0].RootNode.ToDataNode(); var node = yamlData.Documents[0].RootNode.ToDataNodeCast<MappingDataNode>();
return _serialization.Read<List<ChangelogEntry>>(node["Entries"], notNullableOverride: true); var changelog = _serialization.Read<Changelog>(node, notNullableOverride: true);
if (string.IsNullOrWhiteSpace(changelog.Name))
changelog.Name = file.FilenameWithoutExtension;
changelogs.Add(changelog);
}
changelogs.Sort((a, b) => a.Order.CompareTo(b.Order));
return changelogs;
}); });
} }
public void PostInject()
{
_sawmill = _logManager.GetSawmill(SawmillName);
}
[DataDefinition]
public sealed partial class Changelog
{
/// <summary>
/// The name to use for this changelog.
/// If left unspecified, the name of the file is used instead.
/// Used during localization to find the user-displayed name of this changelog.
/// </summary>
[DataField("Name")]
public string Name = string.Empty;
/// <summary>
/// The individual entries in this changelog.
/// These are not kept around in memory in the changelog manager.
/// </summary>
[DataField("Entries")]
public List<ChangelogEntry> Entries = new();
/// <summary>
/// Whether or not this changelog will be displayed as a tab to non-admins.
/// These are still loaded by all clients, but not shown if they aren't an admin,
/// as they do not contain sensitive data and are publicly visible on GitHub.
/// </summary>
[DataField("AdminOnly")]
public bool AdminOnly;
/// <summary>
/// Used when ordering the changelog tabs for the user to see.
/// Larger numbers are displayed later, smaller numbers are displayed earlier.
/// </summary>
[DataField("Order")]
public int Order;
}
[DataDefinition] [DataDefinition]
public sealed partial class ChangelogEntry : ISerializationHooks public sealed partial class ChangelogEntry : ISerializationHooks
{ {
@@ -108,7 +184,7 @@ namespace Content.Client.Changelog
} }
[DataDefinition] [DataDefinition]
public sealed partial class ChangelogChange : ISerializationHooks public sealed partial class ChangelogChange
{ {
[DataField("type")] [DataField("type")]
public ChangelogLineType Type { get; private set; } public ChangelogLineType Type { get; private set; }

View File

@@ -0,0 +1,9 @@
<controls:ChangelogTab
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.Changelog">
<BoxContainer Orientation="Vertical">
<ScrollContainer Margin="5" VerticalExpand="True" HScrollEnabled="False">
<BoxContainer Orientation="Vertical" Name="ChangelogBody" />
</ScrollContainer>
</BoxContainer>
</controls:ChangelogTab>

View File

@@ -0,0 +1,175 @@
using System.Linq;
using System.Numerics;
using Content.Client.Resources;
using Content.Client.Stylesheets;
using Robust.Client.AutoGenerated;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
using static Content.Client.Changelog.ChangelogManager;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Changelog;
[GenerateTypedNameReferences]
public sealed partial class ChangelogTab : Control
{
[Dependency] private readonly ChangelogManager _changelog = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
public bool AdminOnly;
public ChangelogTab()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void PopulateChangelog(ChangelogManager.Changelog changelog)
{
var byDay = changelog.Entries
.GroupBy(e => e.Time.ToLocalTime().Date)
.OrderByDescending(c => c.Key);
var hasRead = changelog.Name != MainChangelogName ||
_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 = { StyleBase.StyleClassLabelHeading },
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 BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalAlignment = HAlignment.Center,
Children =
{
new TextureRect
{
Texture = upArrow,
ModulateSelfOverride = Color.FromHex("#888"),
TextureScale = new Vector2(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 = new Vector2(0.5f, 0.5f),
Margin = new Thickness(4, 3),
VerticalAlignment = VAlignment.Bottom
}
}
};
readDivider.AddChild(hBox);
readDivider.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } });
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 BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Margin = new Thickness(14, 1, 10, 2),
Children =
{
GetIcon(change.Type),
text
}
});
}
}
}
}
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 ResPath($"/Textures/Interface/Changelog/{file}")),
VerticalAlignment = VAlignment.Top,
TextureScale = new Vector2(0.5f, 0.5f),
Margin = new Thickness(2, 4, 6, 2),
ModulateSelfOverride = Color.FromHex(color)
};
}
}

View File

@@ -3,15 +3,10 @@
Title="{Loc 'changelog-window-title'}" Title="{Loc 'changelog-window-title'}"
MinSize="500 400" MinSize="500 400"
SetSize="500 400"> SetSize="500 400">
<PanelContainer StyleClasses="AngleRect" /> <PanelContainer StyleClasses="AngleRect" />
<BoxContainer Orientation="Vertical"> <BoxContainer Orientation="Vertical">
<TabContainer Name="Tabs" Access="Public" HorizontalExpand="True" VerticalExpand="True" />
<ScrollContainer Margin="5" VerticalExpand="True" HScrollEnabled="False">
<BoxContainer Orientation="Vertical" Name="ChangelogBody" />
</ScrollContainer>
<PanelContainer StyleClasses="LowDivider" /> <PanelContainer StyleClasses="LowDivider" />
<Label Name="VersionLabel" HorizontalAlignment="Right" StyleClasses="LabelSubText" Margin="4 0" /> <Label Name="VersionLabel" Access="Public" HorizontalAlignment="Right" StyleClasses="LabelSubText" Margin="4 0" />
</BoxContainer> </BoxContainer>
</ui:ChangelogWindow> </ui:ChangelogWindow>

View File

@@ -1,28 +1,22 @@
using System.Linq; using System.Linq;
using System.Numerics; using Content.Client.Administration.Managers;
using Content.Client.Resources;
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.EscapeMenu; using Content.Client.UserInterface.Systems.EscapeMenu;
using Content.Shared.Administration; using Content.Shared.Administration;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.Utility;
using static Content.Client.Changelog.ChangelogManager;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Changelog namespace Content.Client.Changelog
{ {
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class ChangelogWindow : FancyWindow public sealed partial class ChangelogWindow : FancyWindow
{ {
[Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly ChangelogManager _changelog = default!; [Dependency] private readonly ChangelogManager _changelog = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
public ChangelogWindow() public ChangelogWindow()
{ {
@@ -39,154 +33,84 @@ namespace Content.Client.Changelog
PopulateChangelog(); PopulateChangelog();
} }
protected override void EnteredTree()
{
base.EnteredTree();
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
}
protected override void ExitedTree()
{
base.ExitedTree();
_adminManager.AdminStatusUpdated -= OnAdminStatusUpdated;
}
private void OnAdminStatusUpdated()
{
TabsUpdated();
}
private async void PopulateChangelog() private async void PopulateChangelog()
{ {
// Changelog is not kept in memory so load it again. // Changelog is not kept in memory so load it again.
var changelog = await _changelog.LoadChangelog(); var changelogs = await _changelog.LoadChangelog();
var byDay = changelog Tabs.DisposeAllChildren();
.GroupBy(e => e.Time.ToLocalTime().Date)
.OrderByDescending(c => c.Key);
var hasRead = _changelog.MaxId <= _changelog.LastReadId; var i = 0;
foreach (var dayEntries in byDay) foreach (var changelog in changelogs)
{ {
var day = dayEntries.Key; var tab = new ChangelogTab { AdminOnly = changelog.AdminOnly };
tab.PopulateChangelog(changelog);
var groupedEntries = dayEntries Tabs.AddChild(tab);
.GroupBy(c => (c.Author, Read: c.Id <= _changelog.LastReadId)) Tabs.SetTabTitle(i++, Loc.GetString($"changelog-tab-title-{changelog.Name}"));
.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 = { StyleBase.StyleClassLabelHeading },
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 BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalAlignment = HAlignment.Center,
Children =
{
new TextureRect
{
Texture = upArrow,
ModulateSelfOverride = Color.FromHex("#888"),
TextureScale = new Vector2(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 = new Vector2(0.5f, 0.5f),
Margin = new Thickness(4, 3),
VerticalAlignment = VAlignment.Bottom
}
}
};
readDivider.AddChild(hBox);
readDivider.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } });
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 BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Margin = new Thickness(14, 1, 10, 2),
Children =
{
GetIcon(change.Type),
text
}
});
}
}
} }
var version = typeof(ChangelogWindow).Assembly.GetName().Version ?? new Version(1, 0); var version = typeof(ChangelogWindow).Assembly.GetName().Version ?? new Version(1, 0);
VersionLabel.Text = Loc.GetString("changelog-version-tag", ("version", version.ToString())); VersionLabel.Text = Loc.GetString("changelog-version-tag", ("version", version.ToString()));
TabsUpdated();
} }
private TextureRect GetIcon(ChangelogLineType type) private void TabsUpdated()
{ {
var (file, color) = type switch var tabs = Tabs.Children.OfType<ChangelogTab>().ToArray();
{ var isAdmin = _adminManager.IsAdmin(true);
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 var visibleTabs = 0;
int? firstVisible = null;
for (var i = 0; i < tabs.Length; i++)
{ {
Texture = _resourceCache.GetTexture(new ResPath($"/Textures/Interface/Changelog/{file}")), var tab = tabs[i];
VerticalAlignment = VAlignment.Top,
TextureScale = new Vector2(0.5f, 0.5f), if (!tab.AdminOnly || isAdmin)
Margin = new Thickness(2, 4, 6, 2), {
ModulateSelfOverride = Color.FromHex(color) Tabs.SetTabVisible(i, true);
}; tab.Visible = true;
visibleTabs++;
firstVisible ??= i;
}
else
{
Tabs.SetTabVisible(i, false);
tab.Visible = false;
}
}
Tabs.TabsVisible = visibleTabs > 1;
// Current tab became invisible, select the first one that is visible
if (!Tabs.GetTabVisible(Tabs.CurrentTab) && firstVisible != null)
{
Tabs.CurrentTab = firstVisible.Value;
}
// We are only displaying one tab, hide its header
if (!Tabs.TabsVisible && firstVisible != null)
{
Tabs.SetTabVisible(firstVisible.Value, false);
}
} }
} }

View File

@@ -4,7 +4,6 @@ using Robust.Client.Graphics;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.StylesheetHelpers; using static Robust.Client.UserInterface.StylesheetHelpers;
namespace Content.Client.Stylesheets namespace Content.Client.Stylesheets
@@ -62,6 +61,14 @@ namespace Content.Client.Stylesheets
var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png"); var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png");
var tabContainerPanel = new StyleBoxTexture();
tabContainerPanel.SetPatchMargin(StyleBox.Margin.All, 2);
var tabContainerBoxActive = new StyleBoxFlat {BackgroundColor = new Color(64, 64, 64)};
tabContainerBoxActive.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
var tabContainerBoxInactive = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 32)};
tabContainerBoxInactive.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
Stylesheet = new Stylesheet(BaseRules.Concat(new StyleRule[] Stylesheet = new Stylesheet(BaseRules.Concat(new StyleRule[]
{ {
Element<Label>().Class(StyleClassLabelHeading) Element<Label>().Class(StyleClassLabelHeading)
@@ -179,6 +186,14 @@ namespace Content.Client.Stylesheets
Element<Label>().Class(OptionButton.StyleClassOptionButton) Element<Label>().Class(OptionButton.StyleClassOptionButton)
.Prop(Label.StylePropertyAlignMode, Label.AlignMode.Center), .Prop(Label.StylePropertyAlignMode, Label.AlignMode.Center),
// TabContainer
new StyleRule(new SelectorElement(typeof(TabContainer), null, null, null),
new[]
{
new StyleProperty(TabContainer.StylePropertyPanelStyleBox, tabContainerPanel),
new StyleProperty(TabContainer.StylePropertyTabStyleBox, tabContainerBoxActive),
new StyleProperty(TabContainer.StylePropertyTabStyleBoxInactive, tabContainerBoxInactive),
}),
}).ToList()); }).ToList());
} }

View File

@@ -0,0 +1,9 @@
Name: Admin
AdminOnly: true
Order: 1
Entries:
- author: DrSmugleaf
changes:
- {message: 'Created the admin changelog.', type: Add}
id: 1
time: '2023-10-08T04:26:00.0000000+00:00'

View File

@@ -9,3 +9,6 @@ changelog-version-tag = version v{ $version }
changelog-button = Changelog changelog-button = Changelog
changelog-button-new-entries = Changelog (new!) changelog-button-new-entries = Changelog (new!)
changelog-tab-title-Changelog = Changelog
changelog-tab-title-Admin = Admin