diff --git a/MatrixRoomUtils.Core/Room.cs b/MatrixRoomUtils.Core/Room.cs
index a867c0c..2d6dc46 100644
--- a/MatrixRoomUtils.Core/Room.cs
+++ b/MatrixRoomUtils.Core/Room.cs
@@ -3,6 +3,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using System.Web;
using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.RoomTypes;
namespace MatrixRoomUtils.Core;
@@ -12,6 +13,8 @@ public class Room {
public Room(HttpClient httpClient, string roomId) {
_httpClient = httpClient;
RoomId = roomId;
+ if(GetType() != typeof(SpaceRoom))
+ AsSpace = new SpaceRoom(_httpClient, RoomId);
}
public string RoomId { get; set; }
@@ -137,6 +140,16 @@ public class Room {
return res.Value.Deserialize<CreateEvent>() ?? new CreateEvent();
}
+
+ public async Task<string?> GetRoomType() {
+ var res = await GetStateAsync("m.room.create");
+ if (!res.HasValue) return null;
+ if (res.Value.TryGetProperty("type", out var type)) return type.GetString();
+ return null;
+ }
+
+
+ public SpaceRoom AsSpace;
}
public class MessagesResponse {
diff --git a/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs b/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs
new file mode 100644
index 0000000..e8d4823
--- /dev/null
+++ b/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs
@@ -0,0 +1,23 @@
+using System.Text.Json;
+using MatrixRoomUtils.Core.Extensions;
+
+namespace MatrixRoomUtils.Core.RoomTypes;
+
+public class SpaceRoom : Room {
+ public SpaceRoom(HttpClient httpClient, string roomId) : base(httpClient, roomId) { }
+
+ public async Task<List<Room>> GetRoomsAsync(bool includeRemoved = false) {
+ var rooms = new List<Room>();
+ var state = await GetStateAsync("");
+ if (state != null) {
+ var states = state.Value.Deserialize<StateEventResponse<object>[]>()!;
+ foreach (var stateEvent in states.Where(x => x.Type == "m.space.child")) {
+ var roomId = stateEvent.StateKey;
+ if(stateEvent.Content.ToJson() != "{}" || includeRemoved)
+ rooms.Add(await RuntimeCache.CurrentHomeServer.GetRoom(roomId));
+ }
+ }
+
+ return rooms;
+ }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/FileUploadTest.razor b/MatrixRoomUtils.Web/FileUploadTest.razor
index 478a3e4..cbdb5ce 100644
--- a/MatrixRoomUtils.Web/FileUploadTest.razor
+++ b/MatrixRoomUtils.Web/FileUploadTest.razor
@@ -6,7 +6,7 @@
@code {
- private async void FilePicked(InputFileChangeEventArgs obj) {
+ private async Task FilePicked(InputFileChangeEventArgs obj) {
Console.WriteLine("FilePicked");
Console.WriteLine(obj.File.Name);
Console.WriteLine(obj.File.Size);
diff --git a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
index c76452f..e1511c4 100644
--- a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
+++ b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
@@ -7,17 +7,17 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Blazored.LocalStorage" Version="4.3.0"/>
- <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.3"/>
- <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.3" PrivateAssets="all"/>
+ <PackageReference Include="Blazored.LocalStorage" Version="4.3.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.3" />
+ <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.3" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\MatrixRoomUtils.Core\MatrixRoomUtils.Core.csproj"/>
+ <ProjectReference Include="..\MatrixRoomUtils.Core\MatrixRoomUtils.Core.csproj" />
</ItemGroup>
<ItemGroup>
- <Folder Include="Shared\TimelineComponents\TimelineMessageComponents"/>
+ <Folder Include="Shared\TimelineComponents\TimelineMessageComponents" />
</ItemGroup>
</Project>
diff --git a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
index e6f95c7..a62362b 100644
--- a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
+++ b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
@@ -18,19 +18,26 @@
<button class="btn btn-primary" @onclick="Search">Search</button>
<br/>
+@if (Results.Count > 0) {
+ <p>Found @Results.Count rooms</p>
+}
+
@foreach (var res in Results) {
<div style="background-color: #ffffff11; border-radius: 0.5em; display: block; margin-top: 4px; padding: 4px;">
<RoomListItem RoomName="@res.Name" RoomId="@res.RoomId"></RoomListItem>
<p>
- @res.CanonicalAlias
+ @if (!string.IsNullOrWhiteSpace(res.CanonicalAlias)) {
+ <span>@res.CanonicalAlias (@res.RoomId)</span><br/>
+ }
+ else {
+ <span>@res.RoomId</span><br/>
+ }
@if (!string.IsNullOrWhiteSpace(res.Creator)) {
- <span>
- , created by <InlineUserItem UserId="@res.Creator"></InlineUserItem>
- </span>
+ <span>Created by <InlineUserItem UserId="@res.Creator"></InlineUserItem></span><br/>
}
</p>
- <p>@res.StateEvents state events</p>
- <p>@res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server</p>
+ <span>@res.StateEvents state events</span><br/>
+ <span>@res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server</span>
</div>
}
@@ -41,11 +48,11 @@
public string? OrderBy { get; set; }
[Parameter]
- [SupplyParameterFromQuery(Name = "search_term")]
+ [SupplyParameterFromQuery(Name = "name_search")]
public string SearchTerm { get; set; }
[Parameter]
- [SupplyParameterFromQuery(Name = "content_search_term")]
+ [SupplyParameterFromQuery(Name = "content_search")]
public string ContentSearchTerm { get; set; }
[Parameter]
@@ -69,6 +76,8 @@
var room = searchRooms.Current;
Console.WriteLine("Hit: " + room.ToJson(false));
Results.Add(room);
+ if (Results.Count % 10 == 0)
+ StateHasChanged();
}
}
diff --git a/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerTimeline.razor b/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerTimeline.razor
index 2db7cab..b90cc09 100644
--- a/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerTimeline.razor
+++ b/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerTimeline.razor
@@ -1,4 +1,4 @@
-@page "/RoomManager/Timeline/{RoomId}"
+@page "/Rooms/{RoomId}/Timeline"
@using MatrixRoomUtils.Web.Shared.TimelineComponents
<h3>RoomManagerTimeline</h3>
<hr/>
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
new file mode 100644
index 0000000..17551c9
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
@@ -0,0 +1,22 @@
+@page "/Rooms"
+<h3>Room list</h3>
+
+@if (Rooms != null) {
+ <RoomList Rooms="Rooms"></RoomList>
+}
+
+
+@code {
+
+ private List<Room> Rooms { get; set; }
+
+ protected override async Task OnInitializedAsync()
+ {
+ await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+
+ Rooms = await RuntimeCache.CurrentHomeServer.GetJoinedRooms();
+
+ await base.OnInitializedAsync();
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/RoomList.razor b/MatrixRoomUtils.Web/Shared/RoomList.razor
new file mode 100644
index 0000000..ca93fa6
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/RoomList.razor
@@ -0,0 +1,56 @@
+@using MatrixRoomUtils.Web.Shared.RoomListComponents;
+<p>@Rooms.Count rooms total, @RoomsWithTypes.Sum(x=>x.Value.Count) fetched so far...</p>
+@foreach (var category in RoomsWithTypes.OrderBy(x => x.Value.Count)) {
+ <RoomListCategory Category="@category"></RoomListCategory>
+}
+
+@code {
+
+ [Parameter]
+ public List<Room> Rooms { get; set; }
+
+ Dictionary<string, List<Room>> RoomsWithTypes = new();
+
+ protected override async Task OnInitializedAsync() {
+ if (RoomsWithTypes.Any()) return;
+
+ await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+
+ var tasks = Rooms.Select(AddRoom);
+ await Task.WhenAll(tasks);
+
+ await base.OnInitializedAsync();
+ }
+
+ private string GetRoomTypeName(string? roomType) => roomType switch {
+ "m.space" => "Space",
+ "msc3588.stories.stories-room" => "Story room",
+ null => "Room",
+ _ => roomType
+ };
+
+
+ private static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(8, 8);
+ private async Task AddRoom(Room room) {
+ await _semaphoreSlim.WaitAsync();
+ var roomType = GetRoomTypeName(await room.GetRoomType());
+
+ if (roomType == "Room") {
+ var shortcodeState = await room.GetStateAsync("org.matrix.mjolnir.shortcode");
+ if (shortcodeState.HasValue) roomType = "Legacy policy room";
+ }
+
+ if (!RoomsWithTypes.ContainsKey(roomType)) {
+ RoomsWithTypes.Add(roomType, new List<Room>());
+ }
+ RoomsWithTypes[roomType].Add(room);
+
+ // if (RoomsWithTypes.Count % 10 == 0)
+ StateHasChanged();
+ await Task.Delay(100);
+ _semaphoreSlim.Release();
+ }
+
+ private bool _isSpaceChildrenOpen = false;
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/RoomList.razor.css b/MatrixRoomUtils.Web/Shared/RoomList.razor.css
new file mode 100644
index 0000000..a159305
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/RoomList.razor.css
@@ -0,0 +1,8 @@
+.room-list-item {
+ background-color: #ffffff11;
+ border-radius: 0.5em;
+ display: block;
+ margin-top: 4px;
+ padding: 4px;
+ width: fit-content;
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
new file mode 100644
index 0000000..ecdcc68
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
@@ -0,0 +1,24 @@
+<details>
+ <summary>@roomType (@rooms.Count)</summary>
+ @foreach (var room in rooms) {
+ <div class="room-list-item">
+ <RoomListItem Room="@room" ShowOwnProfile="@(roomType == "Room")"></RoomListItem>
+ <MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton href="@($"/Rooms/{room.RoomId}/Timeline")">View timeline</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton>
+
+ @if (roomType == "Space") {
+ <RoomListSpace Space="@room"></RoomListSpace>
+ }
+ </div>
+ }
+</details>
+<br/>
+
+@code {
+
+ [Parameter]
+ public KeyValuePair<string, List<Room>> Category { get; set; }
+
+ private string roomType => Category.Key;
+ private List<Room> rooms => Category.Value;
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
new file mode 100644
index 0000000..c90ae8f
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
@@ -0,0 +1,42 @@
+<LinkButton href="@($"/Rooms/{Space.RoomId}/Space")">Manage space</LinkButton>
+
+<br/>
+<details @ontoggle="SpaceChildrenOpened">
+ <summary>@Children.Count children</summary>
+ @if (_shouldRenderChildren) {
+ <p>Breadcrumb: @Breadcrumbs</p>
+ <div style="margin-left: 8px;">
+ <RoomList Rooms="Children"></RoomList>
+ </div>
+ }
+</details>
+
+@code {
+
+ [Parameter]
+ public Room Space { get; set; }
+
+ [Parameter, CascadingParameter]
+ public string? Breadcrumbs {
+ get => _breadcrumbs + Space.RoomId;
+ set => _breadcrumbs = value;
+ }
+
+ private List<Room> Children { get; set; } = new();
+
+ protected override async Task OnInitializedAsync() {
+ if (Breadcrumbs == null) throw new ArgumentNullException(nameof(Breadcrumbs));
+ Children = (await Space.AsSpace.GetRoomsAsync()).Where(x => !Breadcrumbs.Contains(x.RoomId)).ToList();
+ await base.OnInitializedAsync();
+ }
+
+ private bool _shouldRenderChildren = false;
+ private string? _breadcrumbs;
+
+ private async Task SpaceChildrenOpened() {
+ if (_shouldRenderChildren) return;
+ _shouldRenderChildren = true;
+ Console.WriteLine($"[RoomList] Rendering children of {Space.RoomId}");
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/RoomListItem.razor b/MatrixRoomUtils.Web/Shared/RoomListItem.razor
index f58ab3a..6dc0683 100644
--- a/MatrixRoomUtils.Web/Shared/RoomListItem.razor
+++ b/MatrixRoomUtils.Web/Shared/RoomListItem.razor
@@ -2,7 +2,7 @@
@using System.Text.Json
<div class="roomListItem" style="background-color: #ffffff11; border-radius: 25px; margin: 8px; width: fit-Content; @(hasDangerousRoomVersion ? "border: red 4px solid;" : hasOldRoomVersion ? "border: #FF0 1px solid;" : "")">
@if (ShowOwnProfile) {
- <img class="imageUnloaded @(string.IsNullOrWhiteSpace(profileAvatar) ? "" : "imageLoaded")" style="@(ChildContent != null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%; @(hasCustomProfileAvatar ? "border-color: red; border-width: 3px; border-style: dashed;" : "")" src="@(profileAvatar ?? "/icon-192.png")" @onload="Callback"/>
+ <img class="imageUnloaded @(string.IsNullOrWhiteSpace(profileAvatar) ? "" : "imageLoaded")" style="@(ChildContent != null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%; @(hasCustomProfileAvatar ? "border-color: red; border-width: 3px; border-style: dashed;" : "")" src="@(profileAvatar ?? "/icon-192.png")" />
<span style="vertical-align: middle; margin-right: 8px; border-radius: 75px; @(hasCustomProfileName ? "background-color: red;" : "")">@(profileName ?? "Loading...")</span>
<span style="vertical-align: middle; padding-right: 8px; padding-left: 0px;">-></span>
}
@@ -51,7 +51,7 @@
await _semaphoreSlim.WaitAsync();
- var hs = RuntimeCache.CurrentHomeServer; //await new AuthenticatedHomeServer(RuntimeCache.CurrentHomeServer.UserId, RuntimeCache.CurrentHomeServer.AccessToken, RuntimeCache.CurrentHomeServer.HomeServerDomain).Configure();
+ var hs = RuntimeCache.CurrentHomeServer;
if (Room == null) {
if (RoomId == null) {
@@ -119,6 +119,4 @@
await LocalStorageWrapper.SaveCacheToLocalStorage(LocalStorage);
}
- private void Callback(ProgressEventArgs obj) => Console.WriteLine("prog: " + obj.ToJson(false));
-
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor b/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor
index d17d0de..966c44d 100644
--- a/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor
+++ b/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor
@@ -3,7 +3,7 @@
<input autofocus type="@(IsPassword ? "password" : "text")" @bind="Value" @onfocusout="() => { isVisible = false; ValueChanged.InvokeAsync(Value); }" @ref="elementToFocus"/>
}
else {
- <span tabindex="0" style="border-bottom: #ccc solid 1px; height: 1.4em; display: inline-block; @(string.IsNullOrEmpty(Value) ? "min-width: 50px;" : "")" @onfocusin="() => isVisible = true">@(Formatter?.Invoke(Value) ?? (IsPassword ? string.Join("", Value.Select(x => '*')) : Value))</span>
+ <span class="fancy-textbox-inline" tabindex="0" style="@(string.IsNullOrEmpty(Value) ? "min-width: 50px;" : "")" @onfocusin="() => isVisible = true">@(Formatter?.Invoke(Value) ?? (IsPassword ? string.Join("", Value.Select(x => '*')) : Value))</span>
}
@code {
diff --git a/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor.css b/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor.css
new file mode 100644
index 0000000..01b2c6f
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor.css
@@ -0,0 +1,5 @@
+.fancy-textbox-inline {
+ border-bottom: #ccc solid 1px;
+ height: 1.4em;
+ display: inline-block;
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/SimpleComponents/LinkButton.razor b/MatrixRoomUtils.Web/Shared/SimpleComponents/LinkButton.razor
new file mode 100644
index 0000000..8c9e73b
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/SimpleComponents/LinkButton.razor
@@ -0,0 +1,13 @@
+<a href="@href" class="btn btn-primary">
+ @ChildContent
+</a>
+
+@code {
+
+ [Parameter]
+ public string href { get; set; }
+
+ [Parameter]
+ public RenderFragment ChildContent { get; set; }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/_Imports.razor b/MatrixRoomUtils.Web/_Imports.razor
index 837780e..155837b 100644
--- a/MatrixRoomUtils.Web/_Imports.razor
+++ b/MatrixRoomUtils.Web/_Imports.razor
@@ -11,13 +11,18 @@
@using MatrixRoomUtils.Web
@using MatrixRoomUtils.Web.Classes
@using MatrixRoomUtils.Web.Shared
+@using MatrixRoomUtils.Core.RoomTypes
+@using MatrixRoomUtils.Core.Extensions
+
+@using LinkButton = MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton
+
+
@inject ILocalStorageService LocalStorage
@inject NavigationManager NavigationManager
@code
{
-
protected override async Task OnInitializedAsync() {
if (!RuntimeCache.WasLoaded) {
await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
diff --git a/MatrixRoomUtils.sln.DotSettings b/MatrixRoomUtils.sln.DotSettings
index 4ac5b79..3da5982 100644
--- a/MatrixRoomUtils.sln.DotSettings
+++ b/MatrixRoomUtils.sln.DotSettings
@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+ <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AsyncVoidMethod/@EntryIndexedValue">ERROR</s:String>
<s:String x:Key="/Default/CustomTools/CustomToolsData/@EntryValue"></s:String></wpf:ResourceDictionary>
\ No newline at end of file
|