diff --git a/MatrixUtils.Web/Shared/ActivityGraph.razor b/MatrixUtils.Web/Shared/ActivityGraph.razor
new file mode 100644
index 0000000..51fb539
--- /dev/null
+++ b/MatrixUtils.Web/Shared/ActivityGraph.razor
@@ -0,0 +1,148 @@
+@using System.Drawing
+@using System.Runtime.InteropServices
+@using System.Diagnostics
+
+@if (Data is { Count: > 0 })
+{
+ @* 12*5=60 *@
+ <div style="display: grid; grid-template-columns: 35px repeat(60, 1.5em); grid-template-rows: 1.5em repeat(7, 1.5em); gap: 0;">
+ @* row 0: month labels with colspan *@
+ @* @foreach (var month in Enumerable.Range(1, 12)) *@
+ @* { *@
+ @* <div style="grid-row: 1; grid-column: @((int)(month * 4.3) + 1);"> *@
+ @* <span aria-hidden="true">@(new DateTime(2021, month, 1).ToString("MMM")[..3])</span> *@
+ @* </div> *@
+ @* } *@
+
+ @* column 0: day labels *@
+ @* @for (var i = 0; i < 7; i++) *@
+ @* { *@
+ @* <div style="text-align: left; grid-column: 1; grid-row: @(i + 2)"> *@
+ @* @(((DayOfWeek)i).ToString()[..3]) *@
+ @* </div> *@
+ @* } *@
+
+
+ <div style="grid-row: 1; grid-column: 5;">Jan</div>
+ <div style="grid-row: 1; grid-column: 9;">Feb</div>
+ <div style="grid-row: 1; grid-column: 13;">Mar</div>
+ <div style="grid-row: 1; grid-column: 18;">Apr</div>
+ <div style="grid-row: 1; grid-column: 22;">May</div>
+ <div style="grid-row: 1; grid-column: 26;">Jun</div>
+ <div style="grid-row: 1; grid-column: 31;">Jul</div>
+ <div style="grid-row: 1; grid-column: 35;">Aug</div>
+ <div style="grid-row: 1; grid-column: 39;">Sep</div>
+ <div style="grid-row: 1; grid-column: 44;">Oct</div>
+ <div style="grid-row: 1; grid-column: 48;">Nov</div>
+ <div style="grid-row: 1; grid-column: 52;">Dec</div>
+ <div style="text-align: left; grid-column: 1; grid-row: 2">Sun</div>
+ <div style="text-align: left; grid-column: 1; grid-row: 3">Mon</div>
+ <div style="text-align: left; grid-column: 1; grid-row: 4">Tue</div>
+ <div style="text-align: left; grid-column: 1; grid-row: 5">Wed</div>
+ <div style="text-align: left; grid-column: 1; grid-row: 6">Thu</div>
+ <div style="text-align: left; grid-column: 1; grid-row: 7">Fri</div>
+ <div style="text-align: left; grid-column: 1; grid-row: 8">Sat</div>
+
+
+ @* pad activity cell dates... *@
+ <div style="grid-column: 2; grid-row: 2 / span @((int)(new DateOnly(Data.Keys.First().Year, 1, 1).DayOfWeek));"></div>
+
+ @* the actual activity cells *@
+
+ @code{
+ bool needsBorder = false;
+ }
+
+ @for (DateOnly date = new DateOnly(Data.Keys.First().Year, 1, 1); date <= new DateOnly(Data.Keys.First().Year, 1, 1).AddYears(1).AddDays(-1); date = date.AddDays(1))
+ {
+ var hasData = Data.TryGetValue(date, out var color);
+ var needsTopBorder = date.Day == 1 && date.Month != 1 && date.DayOfWeek != DayOfWeek.Sunday;
+ if (date.DayOfWeek == DayOfWeek.Sunday)
+ needsBorder = date.AddDays(7).Day <= 7 && date.Month != 12;
+ var needsLeftBorder = date.Day <= 7;
+
+ <div class="activity-cell-container"
+ style="grid-row: @((int)date.DayOfWeek + 2); border-@(needsLeftBorder ? "left" : "right"): @(needsBorder ? "2px solid white" : "none"); border-top: @(needsTopBorder ? "2px solid white" : "none");">
+ @if (hasData)
+ {
+ <div class="activity-cell"
+ style="background-color: rgb(@(color.R / GlobalMax.R * 255), @(color.G / GlobalMax.G * 255), @(color.B / GlobalMax.B * 255));"
+ title="@($"{color.R} {RLabel}, {color.G} {GLabel}, and {color.B} {BLabel} on {date.ToString("D")}")">
+ </div>
+ }
+ else
+ {
+ <div class="activity-cell"
+ title="@($"No data on {date.ToString("D")}")">
+ </div>
+ }
+ </div>
+ }
+ </div>
+}
+
+
+@code {
+ private Dictionary<DateOnly, RGB> _data = new();
+ private RGB? _globalMax = null;
+
+ [Parameter]
+ public Dictionary<DateOnly, RGB> Data
+ {
+ get => _data;
+ set
+ {
+ // var sw = Stopwatch.StartNew();
+ if (value is not { Count: > 0 }) return;
+ // Console.WriteLine($"Recalculating activity graph ({value.Count} datapoints)...");
+
+
+ // var year = (int)value.Keys.Average(x => x.Year);
+ // value = value
+ // .Where(x => x.Key.Year == year)
+ // .OrderBy(x => x.Key)
+ // .ToDictionary(x => x.Key, x => x.Value);
+
+ _data = value;
+ // Console.WriteLine($"Recalculated activity graph in {sw.Elapsed}");
+ // StateHasChanged();
+ }
+ }
+
+ [Parameter]
+ public RGB GlobalMax
+ {
+ get
+ {
+ if (_globalMax is not null) return _globalMax.Value;
+ if (Data is not { Count: > 0 }) return new RGB() { R = 255, G = 255, B = 255 };
+ return new RGB()
+ {
+ R = Data.Values.Max(x => x.R),
+ G = Data.Values.Max(x => x.G),
+ B = Data.Values.Max(x => x.B)
+ };
+ }
+ set => _globalMax = value;
+ }
+
+ [Parameter] public string RLabel { get; set; } = "R";
+ [Parameter] public string GLabel { get; set; } = "G";
+ [Parameter] public string BLabel { get; set; } = "B";
+
+ [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * 3, Pack = 1)]
+ public struct RGB()
+ {
+ public float R = 0;
+ public float G = 0;
+ public float B = 0;
+
+ public RGB(float r, float g, float b) : this()
+ {
+ R = r;
+ G = g;
+ B = b;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Shared/ActivityGraph.razor.css b/MatrixUtils.Web/Shared/ActivityGraph.razor.css
new file mode 100644
index 0000000..d8e543c
--- /dev/null
+++ b/MatrixUtils.Web/Shared/ActivityGraph.razor.css
@@ -0,0 +1,16 @@
+.activity-cell-container {
+ width: 100%;
+ height: 100%;
+ align-content: center;
+ justify-content: center;
+}
+
+.activity-cell {
+ width: 85%;
+ height: 85%;
+ border-radius: 5px;
+}
+
+.day-label {
+ grid-column: 1;
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Shared/MainLayout.razor b/MatrixUtils.Web/Shared/MainLayout.razor
index d8bf411..41c3d69 100644
--- a/MatrixUtils.Web/Shared/MainLayout.razor
+++ b/MatrixUtils.Web/Shared/MainLayout.razor
@@ -8,8 +8,8 @@
<main>
<div class="top-row px-4">
<PortableDevTools></PortableDevTools>
- <a href="https://cgit.rory.gay/matrix/MatrixRoomUtils.git/" target="_blank">Git</a>
- <a href="https://matrix.to/#/%23mru%3Arory.gay?via=rory.gay&via=matrix.org&via=feline.support" target="_blank">Matrix</a>
+ <a style="color: #ccc; text-decoration: underline" href="https://cgit.rory.gay/matrix/MatrixRoomUtils.git/" target="_blank">Git</a>
+ <a style="color: #ccc; text-decoration: underline" href="https://matrix.to/#/%23mru%3Arory.gay?via=rory.gay&via=matrix.org&via=feline.support" target="_blank">Matrix</a>
</div>
<article class="Content px-4">
diff --git a/MatrixUtils.Web/Shared/MxcImage.razor b/MatrixUtils.Web/Shared/MxcImage.razor
index f31c19f..e651c3f 100644
--- a/MatrixUtils.Web/Shared/MxcImage.razor
+++ b/MatrixUtils.Web/Shared/MxcImage.razor
@@ -30,6 +30,7 @@
StateHasChanged();
}
}
+
[Parameter]
public RemoteHomeserver? Homeserver { get; set; }
@@ -41,7 +42,7 @@
}
}
- private string StyleString => $"{Style} {(Circular ? "border-radius: 50%;" : "")} {(Width.HasValue ? $"width: {Width}px;" : "")} {(Height.HasValue ? $"height: {Height}px;" : "")}";
+ private string StyleString => $"{Style} {(Circular ? "border-radius: 50%;" : "")} {(Width.HasValue ? $"width: {Width}px;" : "")} {(Height.HasValue ? $"height: {Height}px;" : "")} object-fit: cover;";
private static readonly string Prefix = "mxc://";
private static readonly int PrefixLength = Prefix.Length;
diff --git a/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor b/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
index 9c481e3..6954990 100644
--- a/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
+++ b/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
@@ -48,9 +48,7 @@
if (Breadcrumbs.Contains(room.RoomId)) continue;
var roomInfo = KnownRooms.FirstOrDefault(x => x.Room.RoomId == room.RoomId);
if (roomInfo is null) {
- roomInfo = new RoomInfo() {
- Room = room
- };
+ roomInfo = new RoomInfo(room);
KnownRooms.Add(roomInfo);
}
if(joinedRooms.Any(x=>x.RoomId == room.RoomId))
diff --git a/MatrixUtils.Web/Shared/UserListItem.razor b/MatrixUtils.Web/Shared/UserListItem.razor
index 525296e..daa9c9e 100644
--- a/MatrixUtils.Web/Shared/UserListItem.razor
+++ b/MatrixUtils.Web/Shared/UserListItem.razor
@@ -2,8 +2,9 @@
@using LibMatrix.EventTypes.Spec.State
@using LibMatrix.Homeservers
@using LibMatrix.Responses
+@using ArcaneLibs
<div style="background-color: #ffffff11; border-radius: 25px; margin: 8px; width: fit-Content;">
- <img style="@(ChildContent is not null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%;" src="@(string.IsNullOrWhiteSpace(User?.AvatarUrl) ? "https://api.dicebear.com/6.x/identicon/svg?seed=" + UserId : User.AvatarUrl)"/>
+ <img style="@(ChildContent is not null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%;" src="@(string.IsNullOrWhiteSpace(User?.AvatarUrl) ? _identiconGenerator.GenerateAsDataUri(UserId) : User.AvatarUrl)"/>
<span style="vertical-align: middle; margin-right: 8px; border-radius: 75px;">@User?.DisplayName</span>
<div style="display: inline-block;">
@@ -27,6 +28,8 @@
private AuthenticatedHomeserverGeneric _homeserver = null!;
+ private SvgIdenticonGenerator _identiconGenerator = new();
+
protected override async Task OnInitializedAsync() {
_homeserver = await RMUStorage.GetCurrentSessionOrNavigate();
if (_homeserver is null) return;
@@ -35,6 +38,7 @@
if (UserId == null) {
throw new ArgumentNullException(nameof(UserId));
}
+
User = await _homeserver.GetProfileAsync(UserId);
}
|