Skip to content

Commit afd615c

Browse files
Added an "About" page
1 parent cc9ce65 commit afd615c

File tree

8 files changed

+274
-10
lines changed

8 files changed

+274
-10
lines changed

GuiStack/ApplicationInfo.cs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* Copyright © Vincent Bengtsson & Contributors 2022-2024
7+
* https://github.com/Visual-Vincent/GuiStack
8+
*/
9+
10+
using System;
11+
using System.Collections.Generic;
12+
using System.IO;
13+
using System.Linq;
14+
using System.Reflection;
15+
16+
namespace GuiStack
17+
{
18+
/// <summary>
19+
/// A static class containing information about the application.
20+
/// </summary>
21+
public static class ApplicationInfo
22+
{
23+
/// <summary>
24+
/// A record representing a license to a third-party resource.
25+
/// </summary>
26+
/// <param name="Name">The name of the third-party resource.</param>
27+
/// <param name="LicenseText">The text of the resource's copyright license.</param>
28+
public record class ThirdPartyLicense(string Name, string LicenseText);
29+
30+
#pragma warning disable IDE0001 // Simplify name
31+
internal static readonly Type ProgramClass = typeof(global::GuiStack.Program); // Deliberate choice to ensure we always refer to GuiStack
32+
#pragma warning restore IDE0001
33+
34+
/// <summary>
35+
/// The URL to the project that this application is part of.
36+
/// </summary>
37+
public const string ProjectUrl = "https://github.com/Visual-Vincent/GuiStack";
38+
39+
/// <summary>
40+
/// The assembly that this application resides in.
41+
/// </summary>
42+
public static readonly Assembly MainAssembly = ProgramClass.Assembly;
43+
44+
/// <summary>
45+
/// The copyright information of the application.
46+
/// </summary>
47+
public static readonly string Copyright = ProgramClass.Assembly.GetCustomAttributes<AssemblyCopyrightAttribute>().FirstOrDefault()?.Copyright;
48+
49+
/// <summary>
50+
/// The application description.
51+
/// </summary>
52+
public static readonly string Description =
53+
MainAssembly.GetCustomAttributes<AssemblyDescriptionAttribute>().FirstOrDefault()?.Description ??
54+
MainAssembly.GetCustomAttributes<AssemblyTitleAttribute>().FirstOrDefault()?.Title;
55+
56+
/// <summary>
57+
/// The copyright license of the application.
58+
/// </summary>
59+
public static readonly string License = GetStringResource("LICENSE.txt");
60+
61+
/// <summary>
62+
/// The collection of copyright licenses of third-party resources used by the application.
63+
/// </summary>
64+
public static readonly IReadOnlyCollection<ThirdPartyLicense> ThirdPartyLicenses = ((Func<IReadOnlyCollection<ThirdPartyLicense>>)(() => {
65+
var licenses = new List<ThirdPartyLicense>();
66+
var resources = MainAssembly.GetManifestResourceNames();
67+
68+
foreach(var resource in resources)
69+
{
70+
if(!resource.StartsWith($"{nameof(GuiStack)}.Licenses", StringComparison.OrdinalIgnoreCase))
71+
continue;
72+
73+
var resourceName = resource.Remove(0, nameof(GuiStack).Length + 1);
74+
var thirdPartyName = resource.Remove(0, $"{nameof(GuiStack)}.Licenses".Length + 1);
75+
var license = GetStringResource(resourceName);
76+
77+
if(license == null)
78+
continue;
79+
80+
var extension = Path.GetExtension(thirdPartyName);
81+
82+
switch(extension.ToLower())
83+
{
84+
case ".txt":
85+
case ".md":
86+
thirdPartyName = thirdPartyName.Remove(thirdPartyName.Length - extension.Length);
87+
break;
88+
}
89+
90+
licenses.Add(new ThirdPartyLicense(thirdPartyName, license));
91+
}
92+
93+
return licenses.OrderBy(l => l.Name).ToArray();
94+
})).Invoke();
95+
96+
/// <summary>
97+
/// Returns an embedded resource string, or null if none was found.
98+
/// </summary>
99+
/// <param name="resourceName">The name of the resource to fetch (case sensitive).</param>
100+
internal static string GetStringResource(string resourceName)
101+
{
102+
using var resourceStream = MainAssembly.GetManifestResourceStream($"{nameof(GuiStack)}.{resourceName}");
103+
104+
if(resourceStream == null)
105+
return null;
106+
107+
using var streamReader = new StreamReader(resourceStream);
108+
109+
return streamReader.ReadToEnd();
110+
}
111+
}
112+
}

GuiStack/GuiStack.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
</ItemGroup>
1717

1818
<ItemGroup>
19-
<None Update="Licenses\*">
19+
<EmbeddedResource Include="Licenses\*">
2020
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
21-
</None>
21+
</EmbeddedResource>
2222
</ItemGroup>
2323

2424
<ItemGroup>
25-
<Content Include="..\LICENSE.txt" Link="LICENSE.txt">
25+
<EmbeddedResource Include="..\LICENSE.txt" Link="LICENSE.txt">
2626
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
27-
</Content>
27+
</EmbeddedResource>
2828
<Content Include="..\NOTICE.txt" Link="NOTICE.txt">
2929
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3030
</Content>

GuiStack/Pages/About.cshtml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
@*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* Copyright © Vincent Bengtsson & Contributors 2022-2024
7+
* https://github.com/Visual-Vincent/GuiStack
8+
*@
9+
10+
@page
11+
@using GuiStack.Pages
12+
@model AboutModel
13+
@{
14+
ViewData["Title"] = "About";
15+
}
16+
17+
<h1 class="text-center" style="font-size: 30pt; margin-bottom: 0px">GuiStack</h1>
18+
19+
<p class="text-center" style="font-size: 18pt; margin: 24px 0px">
20+
@AboutModel.Description
21+
</p>
22+
23+
<p class="text-center" style="margin-bottom: 0px">
24+
@ApplicationInfo.Copyright
25+
</p>
26+
27+
<p class="text-center" style="margin-top: 8px; margin-bottom: 40px">
28+
<a href="@ApplicationInfo.ProjectUrl">@ApplicationInfo.ProjectUrl</a>
29+
</p>
30+
31+
<div class="gs-expander">
32+
<div class="gs-expander-header">Copyright License</div>
33+
<div class="gs-expander-body">
34+
<pre><code>@ApplicationInfo.License</code></pre>
35+
</div>
36+
</div>
37+
38+
<hr style="margin: 2em"/>
39+
40+
<h2>Third-party content licenses</h2>
41+
42+
@foreach(var license in ApplicationInfo.ThirdPartyLicenses)
43+
{
44+
<div class="gs-expander">
45+
<div class="gs-expander-header">@license.Name</div>
46+
<div class="gs-expander-body">
47+
<pre><code>@license.LicenseText</code></pre>
48+
</div>
49+
</div>
50+
}

GuiStack/Pages/About.cshtml.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* Copyright © Vincent Bengtsson & Contributors 2022-2024
7+
* https://github.com/Visual-Vincent/GuiStack
8+
*/
9+
10+
using System;
11+
using System.Text.RegularExpressions;
12+
using Microsoft.AspNetCore.Mvc.RazorPages;
13+
14+
namespace GuiStack.Pages
15+
{
16+
public class AboutModel : PageModel
17+
{
18+
public static readonly string Description = Regex.Replace(ApplicationInfo.Description, @"^GuiStack\s+-\s+", "");
19+
20+
public void OnGet()
21+
{
22+
}
23+
}
24+
}

GuiStack/Pages/Shared/_Layout.cshtml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@
4545
</header>
4646

4747
<nav class="gs-navbar">
48-
<a href="/" data-selected-regex="^/$">Dashboard</a>
49-
<a href="/S3" data-selected-regex="^/S3(?:/|\?|$)">S3</a>
50-
<a href="/SQS" data-selected-regex="^/SQS(?:/|\?|$)">SQS</a>
51-
<a href="/SNS" data-selected-regex="^/SNS(?:/|\?|$)">SNS</a>
48+
<a href="/" data-selected-regex="^/$">Dashboard</a>
49+
<a href="/S3" data-selected-regex="^/S3(?:/|\?|$)">S3</a>
50+
<a href="/SQS" data-selected-regex="^/SQS(?:/|\?|$)">SQS</a>
51+
<a href="/SNS" data-selected-regex="^/SNS(?:/|\?|$)">SNS</a>
52+
<a href="/About" data-selected-regex="^/About(?:/|\?|$)">About</a>
5253
</nav>
5354

5455
<main style="overflow: auto">

GuiStack/wwwroot/css/site.css

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ body
1515
margin: 8px;
1616
}
1717

18+
hr
19+
{
20+
border: 1px inset #555555;
21+
}
22+
1823
a, a:visited
1924
{
2025
color: #00E0FF;
@@ -215,7 +220,8 @@ a.gs-button:active { background: #005577; }
215220
width: 20%;
216221
}
217222

218-
.gs-navbar > a
223+
.gs-navbar > a,
224+
.gs-expander > .gs-expander-header
219225
{
220226
display: block;
221227
background: linear-gradient(0deg, #373737, #52555A);
@@ -226,7 +232,8 @@ a.gs-button:active { background: #005577; }
226232
text-decoration: none;
227233
}
228234

229-
.gs-navbar > a.selected
235+
.gs-navbar > a.selected,
236+
.gs-expander.expanded > .gs-expander-header
230237
{
231238
background: linear-gradient(180deg, #303030, #4d5054);
232239
box-shadow: inset rgba(0, 0, 0, 0.69) 0px 0px 4px 0px;
@@ -251,6 +258,69 @@ a.gs-button:active { background: #005577; }
251258
margin-top: 0px;
252259
}
253260

261+
.gs-expander
262+
{
263+
border: 1px solid rgba(0, 0, 0, 0.25);
264+
border-radius: 12px;
265+
overflow: hidden;
266+
}
267+
268+
.gs-expander > .gs-expander-header
269+
{
270+
display: block;
271+
position: relative;
272+
padding: 10px 12px;
273+
border-bottom: none;
274+
cursor: pointer;
275+
}
276+
277+
.gs-expander > .gs-expander-header::after
278+
{
279+
display: block;
280+
position: absolute;
281+
content: "\25BC";
282+
font-family: initial;
283+
font-weight: normal;
284+
top: calc(50% + 1px);
285+
right: 12px;
286+
transform: translateY(-50%);
287+
}
288+
289+
.gs-expander.expanded > .gs-expander-header::after
290+
{
291+
content: "\25B2";
292+
}
293+
294+
.gs-expander > .gs-expander-body
295+
{
296+
display: block;
297+
overflow: auto;
298+
transition: max-height 0.5s ease-out, padding-top 0.5s ease-out, padding-bottom 0.5s ease-out;
299+
background: #35363A;
300+
padding: 16px;
301+
box-sizing: border-box;
302+
box-shadow: inset 0px 3px 5px rgba(0, 0, 0, 0.6);
303+
}
304+
305+
.gs-expander > .gs-expander-body > pre
306+
{
307+
margin-top: 0px;
308+
margin-bottom: 0px;
309+
}
310+
311+
.gs-expander.expanded > .gs-expander-body
312+
{
313+
max-height: 100vh;
314+
}
315+
316+
.gs-expander:not(.expanded) > .gs-expander-body
317+
{
318+
max-height: 0px !important;
319+
padding-top: 0px !important;
320+
padding-bottom: 0px !important;
321+
overflow: hidden !important;
322+
}
323+
254324
.gs-dashboard
255325
{
256326
display: grid;

GuiStack/wwwroot/js/site.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ function gsevent_InfoTable_ToggleButton_Click(event)
269269
table.classList.toggle("expanded");
270270
}
271271

272+
function gsevent_Expander_Click(event)
273+
{
274+
var expander = event.currentTarget.parentElement;
275+
expander.classList.toggle("expanded");
276+
}
277+
272278
function gsevent_TableItem_Click(event)
273279
{
274280
var table = gs_GetParentTable(event.currentTarget, true);
@@ -420,6 +426,7 @@ $(document).ready(function() {
420426

421427
$("table.gs-selector-table > tr").click(gsevent_TableItem_Click);
422428
$("table.gs-selector-table > tbody > tr").click(gsevent_TableItem_Click);
429+
$(".gs-expander > .gs-expander-header").click(gsevent_Expander_Click)
423430
});
424431

425432
/*

screenshots/dashboard.png

1.14 KB
Loading

0 commit comments

Comments
 (0)