diff --git a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateEditorPage.razor b/MatrixRoomUtils.Web/Pages/RoomState/RoomStateEditorPage.razor
new file mode 100644
index 0000000..638d728
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/RoomState/RoomStateEditorPage.razor
@@ -0,0 +1,170 @@
+@page "/RoomStateViewer/{RoomId}/Edit"
+@using System.Net.Http.Headers
+@using System.Text.Json
+@inject ILocalStorageService LocalStorage
+@inject NavigationManager NavigationManager
+<h3>Room state editor - Editing @RoomId</h3>
+<hr/>
+
+<p>@status</p>
+
+<input type="checkbox" id="showAll" @bind="ShowMembershipEvents"/> Show member events
+<br/>
+<InputSelect @bind-Value="shownStateKey">
+ <option value="">-- State key --</option>
+ @foreach (var stateEvent in FilteredEvents.Where(x => x.state_key != "").Select(x => x.state_key).Distinct().OrderBy(x => x))
+ {
+ <option value="@stateEvent">@stateEvent</option>
+ Console.WriteLine(stateEvent);
+ }
+</InputSelect>
+<br/>
+<InputSelect @bind-Value="shownType">
+ <option value="">-- Type --</option>
+ @foreach (var stateEvent in FilteredEvents.Where(x => x.state_key != shownStateKey).Select(x => x.type).Distinct().OrderBy(x => x))
+ {
+ <option value="@stateEvent">@stateEvent</option>
+ }
+</InputSelect>
+<br/>
+
+<textarea @bind="shownEventJson" style="width: 100%; height: fit-content;"></textarea>
+
+<LogView></LogView>
+
+@code {
+ //get room list
+ // - sync withroom list filter
+ // type = support.feline.msc3784
+ //support.feline.policy.lists.msc.v1
+
+ [Parameter]
+ public string? RoomId { get; set; }
+
+ public List<StateEvent> FilteredEvents { get; set; } = new();
+ public List<StateEvent> Events { get; set; } = new();
+ public string status = "";
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (!RuntimeCache.WasLoaded) await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+ await base.OnInitializedAsync();
+ if (RuntimeCache.CurrentHomeServer != null)
+ {
+ NavigationManager.NavigateTo("/Login");
+ return;
+ }
+ RoomId = RoomId.Replace('~', '.');
+ await LoadStatesAsync();
+ Console.WriteLine("Policy list editor initialized!");
+ }
+
+ private DateTime _lastUpdate = DateTime.Now;
+
+ private async Task LoadStatesAsync()
+ {
+ int StateLoaded = 0;
+ using var client = new HttpClient();
+ //TODO: can this be improved?
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeCache.CurrentHomeServer.AccessToken);
+ var response = await client.GetAsync($"{RuntimeCache.CurrentHomeServer.FullHomeServerDomain}/_matrix/client/r0/rooms/{RoomId}/state");
+ // var response = await client.GetAsync($"http://localhost:5117/matrix-hq-state.json");
+ //var _events = await response.Content.ReadFromJsonAsync<Queue<StateEventStruct>>();
+ var _data = await response.Content.ReadAsStreamAsync();
+ var __events = JsonSerializer.DeserializeAsyncEnumerable<StateEvent>(_data);
+ await foreach (var _ev in __events)
+ {
+ var e = new StateEvent()
+ {
+ type = _ev.type,
+ state_key = _ev.state_key,
+ origin_server_ts = _ev.origin_server_ts,
+ content = _ev.content
+ };
+ Events.Add(e);
+ if (string.IsNullOrEmpty(e.state_key))
+ {
+ FilteredEvents.Add(e);
+ }
+ StateLoaded++;
+ if ((DateTime.Now - _lastUpdate).TotalMilliseconds > 100)
+ {
+ _lastUpdate = DateTime.Now;
+ status = $"Loaded {StateLoaded} state events";
+ StateHasChanged();
+ await Task.Delay(0);
+ }
+ }
+
+ StateHasChanged();
+ }
+
+ private async Task RebuildFilteredData()
+ {
+ status = "Rebuilding filtered data...";
+ StateHasChanged();
+ await Task.Delay(1);
+ var _FilteredEvents = Events;
+ if (!ShowMembershipEvents)
+ _FilteredEvents = _FilteredEvents.Where(x => x.type != "m.room.member").ToList();
+
+ status = "Done, rerendering!";
+ StateHasChanged();
+ await Task.Delay(1);
+ FilteredEvents = _FilteredEvents;
+
+ if(_shownType != null)
+ shownEventJson = _FilteredEvents.Where(x => x.type == _shownType).First().content.ToJson(indent: true, ignoreNull: true);
+
+ StateHasChanged();
+ }
+
+
+ public struct PreRenderedStateEvent
+ {
+ public string content { get; set; }
+ public long origin_server_ts { get; set; }
+ public string state_key { get; set; }
+ public string type { get; set; }
+ // public string sender { get; set; }
+ // public string event_id { get; set; }
+ // public string user_id { get; set; }
+ // public string replaces_state { get; set; }
+ }
+
+ public bool ShowMembershipEvents
+ {
+ get => _showMembershipEvents;
+ set
+ {
+ _showMembershipEvents = value;
+ RebuildFilteredData();
+ }
+ }
+
+ private bool _showMembershipEvents;
+ private string _shownStateKey;
+ private string _shownType;
+
+ private string shownStateKey
+ {
+ get => _shownStateKey;
+ set
+ {
+ _shownStateKey = value;
+ RebuildFilteredData();
+ }
+ }
+
+ private string shownType
+ {
+ get => _shownType;
+ set
+ {
+ _shownType = value;
+ RebuildFilteredData();
+ }
+ }
+
+ private string shownEventJson { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateRoomList.razor b/MatrixRoomUtils.Web/Pages/RoomState/RoomStateRoomList.razor
new file mode 100644
index 0000000..ba1b0ec
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/RoomState/RoomStateRoomList.razor
@@ -0,0 +1,36 @@
+@page "/RoomStateViewer"
+@inject ILocalStorageService LocalStorage
+@inject NavigationManager NavigationManager
+<h3>Room state viewer - Room list</h3>
+<hr/>
+@if (Rooms.Count == 0)
+{
+ <p>You are not in any rooms!</p>
+ @* <p>Loading progress: @checkedRoomCount/@totalRoomCount</p> *@
+}
+else
+{
+ @foreach (var room in Rooms)
+ {
+ <a style="color: unset; text-decoration: unset;" href="/RoomStateViewer/@room.Replace('.','~')"><RoomListItem RoomId="@room"></RoomListItem></a>
+ }
+ <div style="margin-bottom: 4em;"></div>
+}
+
+<LogView></LogView>
+
+@code {
+ public List<string> Rooms { get; set; } = new();
+ protected override async Task OnInitializedAsync()
+ {
+ if (!RuntimeCache.WasLoaded) await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+ await base.OnInitializedAsync();
+ if (RuntimeCache.CurrentHomeServer == null)
+ {
+ NavigationManager.NavigateTo("/Login");
+ return;
+ }
+ Rooms = (await RuntimeCache.CurrentHomeServer.GetJoinedRooms()).Select(x=>x.RoomId).ToList();
+ Console.WriteLine("Fetched joined rooms!");
+ }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateViewerPage.razor b/MatrixRoomUtils.Web/Pages/RoomState/RoomStateViewerPage.razor
new file mode 100644
index 0000000..0f40cb9
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/RoomState/RoomStateViewerPage.razor
@@ -0,0 +1,169 @@
+@page "/RoomStateViewer/{RoomId}"
+@using System.Net.Http.Headers
+@using System.Text.Json
+@using MatrixRoomUtils.Core.Extensions
+@inject ILocalStorageService LocalStorage
+@inject NavigationManager NavigationManager
+<h3>Room state viewer - Viewing @RoomId</h3>
+<hr/>
+
+<p>@status</p>
+
+<input type="checkbox" id="showAll" @bind="ShowMembershipEvents"/> Show member events
+
+<table class="table table-striped table-hover" style="width: fit-content;">
+ <thead>
+ <tr>
+ <th scope="col">Type</th>
+ <th scope="col">Content</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var stateEvent in FilteredEvents.Where(x => x.state_key == "").OrderBy(x => x.origin_server_ts))
+ {
+ <tr>
+ <td>@stateEvent.type</td>
+ <td style="max-width: fit-content;">
+ <pre>@stateEvent.content</pre>
+ </td>
+ </tr>
+ }
+ </tbody>
+</table>
+
+@foreach (var group in FilteredEvents.GroupBy(x => x.state_key).OrderBy(x => x.Key).Where(x => x.Key != ""))
+{
+ <details>
+ <summary>@group.Key</summary>
+ <table class="table table-striped table-hover" style="width: fit-content;">
+ <thead>
+ <tr>
+ <th scope="col">Type</th>
+ <th scope="col">Content</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var stateEvent in group.OrderBy(x => x.origin_server_ts))
+ {
+ <tr>
+ <td>@stateEvent.type</td>
+ <td style="max-width: fit-content;">
+ <pre>@stateEvent.content</pre>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </details>
+}
+
+<LogView></LogView>
+
+@code {
+ //get room list
+ // - sync withroom list filter
+ // type = support.feline.msc3784
+ //support.feline.policy.lists.msc.v1
+
+ [Parameter]
+ public string? RoomId { get; set; }
+
+ public List<PreRenderedStateEvent> FilteredEvents { get; set; } = new();
+ public List<PreRenderedStateEvent> Events { get; set; } = new();
+ public string status = "";
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (!RuntimeCache.WasLoaded) await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+ await base.OnInitializedAsync();
+ if (RuntimeCache.CurrentHomeServer == null)
+ {
+ NavigationManager.NavigateTo("/Login");
+ return;
+ }
+ RoomId = RoomId.Replace('~', '.');
+ await LoadStatesAsync();
+ Console.WriteLine("Policy list editor initialized!");
+ }
+ private DateTime _lastUpdate = DateTime.Now;
+
+ private async Task LoadStatesAsync()
+ {
+ int StateLoaded = 0;
+ //TODO: can we improve this?
+ using var client = new HttpClient();
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeCache.CurrentHomeServer.AccessToken);
+ var response = await client.GetAsync($"{RuntimeCache.CurrentHomeServer.FullHomeServerDomain}/_matrix/client/r0/rooms/{RoomId}/state");
+ // var response = await client.GetAsync($"http://localhost:5117/matrix-hq-state.json");
+ //var _events = await response.Content.ReadFromJsonAsync<Queue<StateEventStruct>>();
+ var _data = await response.Content.ReadAsStreamAsync();
+ var __events = JsonSerializer.DeserializeAsyncEnumerable<StateEventStruct>(_data);
+ await foreach (var _ev in __events)
+ {
+ var e = new PreRenderedStateEvent()
+ {
+ type = _ev.type,
+ state_key = _ev.state_key,
+ origin_server_ts = _ev.origin_server_ts,
+ content = _ev.content.ToJson(indent: true, ignoreNull: true),
+ };
+ Events.Add(e);
+ if (string.IsNullOrEmpty(e.state_key))
+ {
+ FilteredEvents.Add(e);
+ }
+ StateLoaded++;
+ if ((DateTime.Now - _lastUpdate).TotalMilliseconds > 100)
+ {
+ _lastUpdate = DateTime.Now;
+ status = $"Loaded {StateLoaded} state events";
+ StateHasChanged();
+ await Task.Delay(0);
+ }
+
+ }
+
+ StateHasChanged();
+ }
+
+ private async Task RebuildFilteredData()
+ {
+ status = "Rebuilding filtered data...";
+ StateHasChanged();
+ await Task.Delay(1);
+ var _FilteredEvents = Events;
+ if (!ShowMembershipEvents)
+ _FilteredEvents = _FilteredEvents.Where(x => x.type != "m.room.member").ToList();
+
+ status = "Done, rerendering!";
+ StateHasChanged();
+ await Task.Delay(1);
+ FilteredEvents = _FilteredEvents;
+ StateHasChanged();
+ }
+
+
+ public struct PreRenderedStateEvent
+ {
+ public string content { get; set; }
+ public long origin_server_ts { get; set; }
+ public string state_key { get; set; }
+ public string type { get; set; }
+ // public string sender { get; set; }
+ // public string event_id { get; set; }
+ // public string user_id { get; set; }
+ // public string replaces_state { get; set; }
+ }
+
+ public bool ShowMembershipEvents
+ {
+ get => _showMembershipEvents;
+ set
+ {
+ _showMembershipEvents = value;
+ RebuildFilteredData();
+ }
+ }
+
+ private bool _showMembershipEvents;
+}
\ No newline at end of file
|