about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--MatrixRoomUtils.Core/AuthenticatedHomeServer.cs77
-rw-r--r--MatrixRoomUtils.Core/Filters/LocalRoomQueryFilter.cs28
-rw-r--r--MatrixRoomUtils.Core/Interfaces/IHomeServer.cs2
-rw-r--r--MatrixRoomUtils.Core/Interfaces/IStorageProvider.cs15
-rw-r--r--MatrixRoomUtils.Core/Responses/Admin/AdminRoomDeleteRequest.cs18
-rw-r--r--MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs12
-rw-r--r--MatrixRoomUtils.Core/RuntimeCache.cs2
-rw-r--r--MatrixRoomUtils.Web/Classes/LocalStorageProviderService.cs5
-rw-r--r--MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs10
-rw-r--r--MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor115
-rw-r--r--MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor.css0
-rw-r--r--MatrixRoomUtils.Web/Pages/ModalTest.razor102
-rw-r--r--MatrixRoomUtils.Web/Program.cs5
-rw-r--r--MatrixRoomUtils.Web/SessionStorageProviderService.cs1
-rw-r--r--MatrixRoomUtils.Web/Shared/ModalWindow.razor130
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/app.css5
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/jetbrains-mono.css133
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Bold.ttfbin0 -> 277828 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-BoldItalic.ttfbin0 -> 279832 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraBold.ttfbin0 -> 279404 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraBoldItalic.ttfbin0 -> 281616 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraLight.ttfbin0 -> 274144 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraLightItalic.ttfbin0 -> 274240 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Italic.ttfbin0 -> 276840 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Light.ttfbin0 -> 276452 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-LightItalic.ttfbin0 -> 277104 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Medium.ttfbin0 -> 273860 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-MediumItalic.ttfbin0 -> 276804 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Regular.ttfbin0 -> 273900 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-SemiBold.ttfbin0 -> 277092 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-SemiBoldItalic.ttfbin0 -> 279828 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Thin.ttfbin0 -> 270112 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ThinItalic.ttfbin0 -> 272984 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Bold.ttfbin0 -> 210988 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-BoldItalic.ttfbin0 -> 214132 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraBold.ttfbin0 -> 213372 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraBoldItalic.ttfbin0 -> 215456 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraLight.ttfbin0 -> 209072 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraLightItalic.ttfbin0 -> 209884 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Italic.ttfbin0 -> 211624 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Light.ttfbin0 -> 210840 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-LightItalic.ttfbin0 -> 212320 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Medium.ttfbin0 -> 208276 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-MediumItalic.ttfbin0 -> 211604 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Regular.ttfbin0 -> 208576 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-SemiBold.ttfbin0 -> 209864 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-SemiBoldItalic.ttfbin0 -> 214032 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Thin.ttfbin0 -> 206004 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ThinItalic.ttfbin0 -> 209124 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/variable/JetBrainsMono-Italic[wght].ttfbin0 -> 308888 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/variable/JetBrainsMono[wght].ttfbin0 -> 303144 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Bold.woff2bin0 -> 94588 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-BoldItalic.woff2bin0 -> 98152 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBold.woff2bin0 -> 93692 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBoldItalic.woff2bin0 -> 96864 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLight.woff2bin0 -> 92712 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLightItalic.woff2bin0 -> 95816 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Italic.woff2bin0 -> 95864 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Light.woff2bin0 -> 93856 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-LightItalic.woff2bin0 -> 97280 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Medium.woff2bin0 -> 93824 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-MediumItalic.woff2bin0 -> 97808 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Regular.woff2bin0 -> 92164 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBold.woff2bin0 -> 94472 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBoldItalic.woff2bin0 -> 98124 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Thin.woff2bin0 -> 90612 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ThinItalic.woff2bin0 -> 94316 bytes
-rw-r--r--MatrixRoomUtils.Web/wwwroot/index.html6
-rw-r--r--MatrixRoomUtils.sln.DotSettings.user6
69 files changed, 640 insertions, 32 deletions
diff --git a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
index ee6be72..7e5650f 100644
--- a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
+++ b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
@@ -3,6 +3,7 @@ using System.Net.Http.Json;
 using System.Text.Json;
 using System.Text.Json.Nodes;
 using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Filters;
 using MatrixRoomUtils.Core.Interfaces;
 using MatrixRoomUtils.Core.Responses;
 using MatrixRoomUtils.Core.Responses.Admin;
@@ -73,7 +74,7 @@ public class AuthenticatedHomeServer : IHomeServer {
 
         public HomeserverAdminApi(AuthenticatedHomeServer authenticatedHomeServer) => _authenticatedHomeServer = authenticatedHomeServer;
 
-        public async IAsyncEnumerable<AdminRoomListingResult.AdminRoomListingResultRoom> SearchRoomsAsync(int limit = int.MaxValue, string orderBy = "name", string dir = "f", string? searchTerm = null, string? contentSearch = null) {
+        public async IAsyncEnumerable<AdminRoomListingResult.AdminRoomListingResultRoom> SearchRoomsAsync(int limit = int.MaxValue, string orderBy = "name", string dir = "f", string? searchTerm = null, LocalRoomQueryFilter? localFilter = null) {
             AdminRoomListingResult? res = null;
             var i = 0;
             int? totalRooms = null;
@@ -89,16 +90,72 @@ public class AuthenticatedHomeServer : IHomeServer {
                 totalRooms ??= res?.TotalRooms;
                 Console.WriteLine(res.ToJson(false));
                 foreach (var room in res.Rooms) {
-                    if (contentSearch != null && !string.IsNullOrEmpty(contentSearch) &&
-                        !(
-                            room.Name?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true ||
-                            room.CanonicalAlias?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true ||
-                            room.Creator?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true
-                        )
-                       ) {
-                        totalRooms--;
-                        continue;
+                    if (localFilter != null) {
+                        if (!room.RoomId.Contains(localFilter.RoomIdContains)) {
+                            totalRooms--;
+                            continue;
+                        }
+                        if (!room.Name?.Contains(localFilter.NameContains) == true) {
+                            totalRooms--;
+                            continue;
+                        }
+                        if (!room.CanonicalAlias?.Contains(localFilter.CanonicalAliasContains) == true) {
+                            totalRooms--;
+                            continue;
+                        }
+                        if (!room.Version.Contains(localFilter.VersionContains)) {
+                            totalRooms--;
+                            continue;
+                        }
+                        if (!room.Creator.Contains(localFilter.CreatorContains)) {
+                            totalRooms--;
+                            continue;
+                        }
+                        if (!room.Encryption?.Contains(localFilter.EncryptionContains) == true) {
+                            totalRooms--;
+                            continue;
+                        }
+                        if (!room.JoinRules?.Contains(localFilter.JoinRulesContains) == true) {
+                            totalRooms--;
+                            continue;
+                        }
+                        if(!room.GuestAccess?.Contains(localFilter.GuestAccessContains) == true) {
+                            totalRooms--;
+                            continue;
+                        }
+                        if(!room.HistoryVisibility?.Contains(localFilter.HistoryVisibilityContains) == true) {
+                            totalRooms--;
+                            continue;
+                        }
+                        
+                        if(localFilter.CheckFederation && room.Federatable != localFilter.Federatable) {
+                            totalRooms--;
+                            continue;
+                        }
+                        if(localFilter.CheckPublic && room.Public != localFilter.Public) {
+                            totalRooms--;
+                            continue;
+                        }
+                        
+                        if(room.JoinedMembers < localFilter.JoinedMembersGreaterThan || room.JoinedMembers > localFilter.JoinedMembersLessThan) {
+                            totalRooms--;
+                            continue;
+                        }
+                        if(room.JoinedLocalMembers < localFilter.JoinedLocalMembersGreaterThan || room.JoinedLocalMembers > localFilter.JoinedLocalMembersLessThan) {
+                            totalRooms--;
+                            continue;
+                        }
                     }
+                    // if (contentSearch != null && !string.IsNullOrEmpty(contentSearch) &&
+                    //     !(
+                    //         room.Name?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true ||
+                    //         room.CanonicalAlias?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true ||
+                    //         room.Creator?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true
+                    //     )
+                    //    ) {
+                    //     totalRooms--;
+                    //     continue;
+                    // }
 
                     i++;
                     yield return room;
diff --git a/MatrixRoomUtils.Core/Filters/LocalRoomQueryFilter.cs b/MatrixRoomUtils.Core/Filters/LocalRoomQueryFilter.cs
new file mode 100644
index 0000000..65c7baa
--- /dev/null
+++ b/MatrixRoomUtils.Core/Filters/LocalRoomQueryFilter.cs
@@ -0,0 +1,28 @@
+namespace MatrixRoomUtils.Core.Filters;
+
+public class LocalRoomQueryFilter {
+    public string RoomIdContains { get; set; } = "";
+    public string NameContains { get; set; } = "";
+    public string CanonicalAliasContains { get; set; } = "";
+    public string VersionContains { get; set; } = "";
+    public string CreatorContains { get; set; } = "";
+    public string EncryptionContains { get; set; } = "";
+    public string JoinRulesContains { get; set; } = "";
+    public string GuestAccessContains { get; set; } = "";
+    public string HistoryVisibilityContains { get; set; } = "";
+    
+    public bool Federatable { get; set; } = true;
+    public bool Public { get; set; } = true;
+    
+    public int JoinedMembersGreaterThan { get; set; } = 0;
+    public int JoinedMembersLessThan { get; set; } = int.MaxValue;
+
+    public int JoinedLocalMembersGreaterThan { get; set; } = 0;
+    public int JoinedLocalMembersLessThan { get; set; } = int.MaxValue;
+    public int StateEventsGreaterThan { get; set; } = 0;
+    public int StateEventsLessThan { get; set; } = int.MaxValue;
+
+    
+    public bool CheckFederation { get; set; }
+    public bool CheckPublic { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
index 9a9ba7a..a808c3d 100644
--- a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
+++ b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
@@ -21,7 +21,7 @@ public class IHomeServer {
 
     private async Task<string> _resolveHomeserverFromWellKnown(string homeserver) {
         if (RuntimeCache.HomeserverResolutionCache.Count == 0) {
-            Console.WriteLine("No cached homeservers, resolving...");
+            // Console.WriteLine("No cached homeservers, resolving...");
             await Task.Delay(Random.Shared.Next(1000, 5000));
         }
 
diff --git a/MatrixRoomUtils.Core/Interfaces/IStorageProvider.cs b/MatrixRoomUtils.Core/Interfaces/IStorageProvider.cs
new file mode 100644
index 0000000..e1a066e
--- /dev/null
+++ b/MatrixRoomUtils.Core/Interfaces/IStorageProvider.cs
@@ -0,0 +1,15 @@
+public interface IStorageProvider {
+    // save 
+    public async Task SaveAll() {
+        Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement Save()!");
+    }
+
+    public async Task SaveObject<T>(string key, T value) {
+        Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement SaveObject<T>(key, value)!");
+    }
+
+    // delete
+    public async Task DeleteObject(string key) {
+        Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement DeleteObject(key)!");
+    }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/Admin/AdminRoomDeleteRequest.cs b/MatrixRoomUtils.Core/Responses/Admin/AdminRoomDeleteRequest.cs
new file mode 100644
index 0000000..5605329
--- /dev/null
+++ b/MatrixRoomUtils.Core/Responses/Admin/AdminRoomDeleteRequest.cs
@@ -0,0 +1,18 @@
+using System.Text.Json.Serialization;
+
+namespace MatrixRoomUtils.Core.Responses.Admin; 
+
+public class AdminRoomDeleteRequest {
+    [JsonPropertyName("new_room_user_id")]
+    public string? NewRoomUserId { get; set; }
+    [JsonPropertyName("room_name")]
+    public string? RoomName { get; set; }
+    [JsonPropertyName("block")]
+    public bool Block { get; set; }
+    [JsonPropertyName("purge")]
+    public bool Purge { get; set; }
+    [JsonPropertyName("message")]
+    public string? Message { get; set; }
+    [JsonPropertyName("force_purge")]
+    public bool ForcePurge { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs b/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs
index 37bb3ba..d6da859 100644
--- a/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs
+++ b/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs
@@ -23,10 +23,10 @@ public class AdminRoomListingResult {
         public string RoomId { get; set; }
 
         [JsonPropertyName("name")]
-        public string Name { get; set; }
+        public string? Name { get; set; }
 
         [JsonPropertyName("canonical_alias")]
-        public string CanonicalAlias { get; set; }
+        public string? CanonicalAlias { get; set; }
 
         [JsonPropertyName("joined_members")]
         public int JoinedMembers { get; set; }
@@ -41,7 +41,7 @@ public class AdminRoomListingResult {
         public string Creator { get; set; }
 
         [JsonPropertyName("encryption")]
-        public string Encryption { get; set; }
+        public string? Encryption { get; set; }
 
         [JsonPropertyName("federatable")]
         public bool Federatable { get; set; }
@@ -50,13 +50,13 @@ public class AdminRoomListingResult {
         public bool Public { get; set; }
 
         [JsonPropertyName("join_rules")]
-        public string JoinRules { get; set; }
+        public string? JoinRules { get; set; }
 
         [JsonPropertyName("guest_access")]
-        public string GuestAccess { get; set; }
+        public string? GuestAccess { get; set; }
 
         [JsonPropertyName("history_visibility")]
-        public string HistoryVisibility { get; set; }
+        public string? HistoryVisibility { get; set; }
 
         [JsonPropertyName("state_events")]
         public int StateEvents { get; set; }
diff --git a/MatrixRoomUtils.Core/RuntimeCache.cs b/MatrixRoomUtils.Core/RuntimeCache.cs
index a2fcf40..db71ee5 100644
--- a/MatrixRoomUtils.Core/RuntimeCache.cs
+++ b/MatrixRoomUtils.Core/RuntimeCache.cs
@@ -27,7 +27,7 @@ public class RuntimeCache {
 
     public static Dictionary<string, ObjectCache<object>> GenericResponseCache { get; set; } = new();
 
-    public static Action Save { get; set; } = () => { Console.WriteLine("RuntimeCache.Save() was called, but no callback was set!"); };
+    public static Task Save { get; set; } = new Task(() => { Console.WriteLine("RuntimeCache.Save() was called, but no callback was set!"); });
     public static Action<string, object> SaveObject { get; set; } = (key, value) => { Console.WriteLine($"RuntimeCache.SaveObject({key}, {value}) was called, but no callback was set!"); };
     public static Action<string> RemoveObject { get; set; } = key => { Console.WriteLine($"RuntimeCache.RemoveObject({key}) was called, but no callback was set!"); };
 }
diff --git a/MatrixRoomUtils.Web/Classes/LocalStorageProviderService.cs b/MatrixRoomUtils.Web/Classes/LocalStorageProviderService.cs
new file mode 100644
index 0000000..0c3deec
--- /dev/null
+++ b/MatrixRoomUtils.Web/Classes/LocalStorageProviderService.cs
@@ -0,0 +1,5 @@
+namespace MatrixRoomUtils.Web.Classes; 
+
+public class LocalStorageProviderService : IStorageProvider {
+    
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs b/MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs
index 4e7117d..b7da78a 100644
--- a/MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs
+++ b/MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs
@@ -10,11 +10,13 @@ public class LocalStorageWrapper {
     //some basic logic
     public static async Task InitialiseRuntimeVariables(ILocalStorageService localStorage) {
         //RuntimeCache stuff
-        async void Save() => await SaveToLocalStorage(localStorage);
-
+        async Task Save() => await SaveToLocalStorage(localStorage);
+        async Task SaveObject(string key, object obj) => await localStorage.SetItemAsync(key, obj);
+        async Task RemoveObject(string key) => await localStorage.RemoveItemAsync(key);
+        
         RuntimeCache.Save = Save;
-        RuntimeCache.SaveObject = async (key, obj) => await localStorage.SetItemAsync(key, obj);
-        RuntimeCache.RemoveObject = async key => await localStorage.RemoveItemAsync(key);
+        RuntimeCache.SaveObject = SaveObject;
+        RuntimeCache.RemoveObject = RemoveObject;
         if (RuntimeCache.LastUsedToken != null) {
             Console.WriteLine("Access token is not null, creating authenticated home server");
             Console.WriteLine($"Homeserver cache: {RuntimeCache.HomeserverResolutionCache.Count} entries");
diff --git a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
index a62362b..1fe13bd 100644
--- a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
+++ b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
@@ -1,12 +1,13 @@
 @page "/HSAdmin/RoomQuery"
 @using MatrixRoomUtils.Core.Extensions
+@using MatrixRoomUtils.Core.Filters
 @using MatrixRoomUtils.Core.Responses.Admin
+@using MatrixRoomUtils.Web.Shared.SimpleComponents
+
 <h3>Homeserver Administration - Room Query</h3>
 
 <label>Search name: </label>
 <InputText @bind-Value="SearchTerm"/><br/>
-<label>Search id/name/creator (slow!): </label>
-<InputText @bind-Value="ContentSearchTerm"/><br/>
 <label>Order by: </label>
 <select @bind="OrderBy">
     @foreach (var item in validOrderBy) {
@@ -15,11 +16,69 @@
 </select><br/>
 <label>Ascending: </label>
 <InputCheckbox @bind-Value="Ascending"/><br/>
+<details>
+    <summary>
+        <span>Local filtering (slow)</span>
+
+    </summary>
+    <div style="margin-left: 8px; margin-bottom: 8px;">
+        <u style="display: block;">String contains</u>
+        <span class="tile tile280">Room ID: <FancyTextBox @bind-Value="@Filter.RoomIdContains"></FancyTextBox></span>
+        <span class="tile tile280">Room name: <FancyTextBox @bind-Value="@Filter.NameContains"></FancyTextBox></span>
+        <span class="tile tile280">Canonical alias: <FancyTextBox @bind-Value="@Filter.CanonicalAliasContains"></FancyTextBox></span>
+        <span class="tile tile280">Creator: <FancyTextBox @bind-Value="@Filter.CreatorContains"></FancyTextBox></span>
+        <span class="tile tile280">Room version: <FancyTextBox @bind-Value="@Filter.VersionContains"></FancyTextBox></span>
+        <span class="tile tile280">Encryption algorithm: <FancyTextBox @bind-Value="@Filter.EncryptionContains"></FancyTextBox></span>
+        <span class="tile tile280">Join rules: <FancyTextBox @bind-Value="@Filter.JoinRulesContains"></FancyTextBox></span>
+        <span class="tile tile280">Guest access: <FancyTextBox @bind-Value="@Filter.GuestAccessContains"></FancyTextBox></span>
+        <span class="tile tile280">History visibility: <FancyTextBox @bind-Value="@Filter.HistoryVisibilityContains"></FancyTextBox></span>
+
+        <u style="display: block;">Optional checks</u>
+        <span class="tile tile150">
+            <InputCheckbox @bind-Value="@Filter.CheckFederation"></InputCheckbox> Is federated:
+            @if (Filter.CheckFederation) {
+                <InputCheckbox @bind-Value="@Filter.Federatable"></InputCheckbox>
+            }
+        </span>
+        <span class="tile tile150">
+            <InputCheckbox @bind-Value="@Filter.CheckPublic"></InputCheckbox> Is public:
+            @if (Filter.CheckPublic) {
+                <InputCheckbox @bind-Value="@Filter.Public"></InputCheckbox>
+            }
+        </span>
+
+        <u style="display: block;">Ranges</u>
+        <span class="tile center-children">
+            <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEventsGreaterThan"></InputNumber><span class="range-sep">state events</span><InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEventsLessThan"></InputNumber>
+        </span>
+        <span class="tile center-children">
+            <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembersGreaterThan"></InputNumber><span class="range-sep">members</span><InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembersLessThan"></InputNumber>
+        </span>
+        <span class="tile center-children">
+            <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedLocalMembersGreaterThan"></InputNumber><span class="range-sep">local members</span><InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedLocalMembersLessThan"></InputNumber>
+        </span>
+    </div>
+</details>
 <button class="btn btn-primary" @onclick="Search">Search</button>
 <br/>
 
 @if (Results.Count > 0) {
     <p>Found @Results.Count rooms</p>
+    <details>
+        <summary>TSV data (copy/paste)</summary>
+        <pre style="font-size: 0.6em;">
+            <table>
+                @foreach (var res in Results) {
+                    <tr>
+                        <td style="padding: 8px;">@res.RoomId@("\t")</td>
+                        <td style="padding: 8px;">@res.CanonicalAlias@("\t")</td>
+                        <td style="padding: 8px;">@res.Creator@("\t")</td>
+                        <td style="padding: 8px;">@res.Name</td>
+                    </tr>
+                }
+            </table>
+        </pre>
+    </details>
 }
 
 @foreach (var res in Results) {
@@ -27,20 +86,58 @@
         <RoomListItem RoomName="@res.Name" RoomId="@res.RoomId"></RoomListItem>
         <p>
             @if (!string.IsNullOrWhiteSpace(res.CanonicalAlias)) {
-                <span>@res.CanonicalAlias (@res.RoomId)</span><br/>
+                <span>@res.CanonicalAlias (@res.RoomId)</span>
+                <br/>
             }
             else {
-                <span>@res.RoomId</span><br/>
+                <span>@res.RoomId</span>
+                <br/>
             }
             @if (!string.IsNullOrWhiteSpace(res.Creator)) {
-                <span>Created by <InlineUserItem UserId="@res.Creator"></InlineUserItem></span><br/>
+                <span>Created by <InlineUserItem UserId="@res.Creator"></InlineUserItem></span>
+                <br/>
             }
         </p>
         <span>@res.StateEvents state events</span><br/>
         <span>@res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server</span>
+        <details>
+            <summary>Full result data</summary>
+            <pre>@res.ToJson(ignoreNull: true)</pre>
+        </details>
     </div>
 }
 
+<style>
+    .int-input {
+        width: 128px;
+    }
+    .tile {
+        display: inline-block;
+        padding: 4px;
+        border: 1px solid #ffffff22;
+    }
+    .tile280 {
+        min-width: 280px;
+    }
+    .tile150 {
+        min-width: 150px;
+    }
+    .range-sep { 
+        display: inline-block;
+        padding: 4px;
+        width: 150px;
+    }
+    .range-sep::before {
+        content: "@("<") ";
+    }
+    .range-sep::after {
+        content: " @("<")";
+    }
+    .center-children {
+        text-align: center;
+    }
+</style>
+
 @code {
 
     [Parameter]
@@ -52,10 +149,6 @@
     public string SearchTerm { get; set; }
 
     [Parameter]
-    [SupplyParameterFromQuery(Name = "content_search")]
-    public string ContentSearchTerm { get; set; }
-
-    [Parameter]
     [SupplyParameterFromQuery(Name = "ascending")]
     public bool Ascending { get; set; }
 
@@ -63,6 +156,8 @@
 
     private string Status { get; set; }
 
+    public LocalRoomQueryFilter Filter { get; set; } = new();
+
     protected override async Task OnParametersSetAsync() {
         if (Ascending == null)
             Ascending = true;
@@ -71,7 +166,7 @@
 
     private async Task Search() {
         Results.Clear();
-        var searchRooms = RuntimeCache.CurrentHomeServer.Admin.SearchRoomsAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", searchTerm: SearchTerm, contentSearch: ContentSearchTerm).GetAsyncEnumerator();
+        var searchRooms = RuntimeCache.CurrentHomeServer.Admin.SearchRoomsAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", searchTerm: SearchTerm, localFilter: Filter).GetAsyncEnumerator();
         while (await searchRooms.MoveNextAsync()) {
             var room = searchRooms.Current;
             Console.WriteLine("Hit: " + room.ToJson(false));
diff --git a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor.css b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor.css
diff --git a/MatrixRoomUtils.Web/Pages/ModalTest.razor b/MatrixRoomUtils.Web/Pages/ModalTest.razor
new file mode 100644
index 0000000..d031dc2
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/ModalTest.razor
@@ -0,0 +1,102 @@
+@page "/ModalTest"
+@inject IJSRuntime JsRuntime
+<h3>ModalTest</h3>
+
+@foreach (var (key, value) in _windowInfos) {
+   @* <ModalWindow X="@value.X" Y="@value.Y" Title="@value.Title">@value.Content</ModalWindow> *@
+}
+@for (int i = 0; i < 5; i++) {
+     var i1 = i;
+     <ModalWindow X="@Random.Shared.Next(1400)" Y="@Random.Shared.Next(1000)" Title="@("Window " + i1)" OnCloseClicked="() => OnCloseClicked(i1)">
+          @for (int j = 0; j < i1; j++) {
+              <h1>@j</h1>
+          } 
+     </ModalWindow>
+}
+
+@code {
+
+    private Dictionary<int, WindowInfo> _windowInfos = new Dictionary<int, WindowInfo>();
+
+    private class WindowInfo {
+        public double X;
+        public double Y;
+        public string Title;
+        public RenderFragment Content;
+    }
+
+    protected override async Task OnInitializedAsync() {
+        double _x = 2;
+        double _xv = 20;
+        double _y = 0;
+        double multiplier = 1;
+
+        await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+        //var rooms = await RuntimeCache.CurrentHomeServer.GetJoinedRooms();
+        
+        //rooms.ForEach(async room => {
+        //    var name = await room.GetNameAsync();
+        //    _windowInfos.Add(_windowInfos.Count, new WindowInfo() { X = _x, Y = _y, Title = name});
+        //    _x += 20;
+        //    _y += 20;
+        //    var dimension = await JsRuntime.InvokeAsync<WindowDimension>("getWindowDimensions");
+        //    if (_x > dimension.Width - 100) _x %= dimension.Width - 100;
+        //    if (_y > dimension.Height - 50) _y %= dimension.Height - 50;
+        //    StateHasChanged();
+        //});
+
+        for (int i = 0; i < 200; i++) {
+            var i1 = i;
+            _windowInfos.Add(_windowInfos.Count, new WindowInfo() {
+                X = _x,
+                Y = _y,
+                Title = "Win" + i1,
+                Content = builder => {
+                    builder.OpenComponent<ModalWindow>(0);
+                    builder.AddAttribute(1, "X", _x);
+                    builder.AddAttribute(2, "Y", _y);
+                    builder.AddAttribute(3, "Title", "Win" + i1);
+                    builder.AddAttribute(4, "ChildContent", (RenderFragment)(builder2 => {
+                        builder2.OpenElement(0, "h1");
+                        builder2.AddContent(1, "Hello " + i1);
+                        builder2.CloseElement();
+                    }));
+                    builder.CloseComponent();
+                }
+            });
+            //_x += _xv /= 1000/System.Math.Sqrt((double)_windowInfos.Count)*_windowInfos.Count.ToString().Length*multiplier;
+            _y += 20;
+	    _x += 20;
+            var dimension = await JsRuntime.InvokeAsync<WindowDimension>("getWindowDimensions");
+            if (_x > dimension.Width - 100) _x %= dimension.Width - 100;
+            if (_y > dimension.Height - 50) {
+                _y %= dimension.Height - 50;
+                _xv = 20;
+            }
+            if (
+                (_windowInfos.Count < 10 && _windowInfos.Count % 2 == 0) ||
+                (_windowInfos.Count < 100 && _windowInfos.Count % 10 == 0) ||
+                (_windowInfos.Count < 1000 && _windowInfos.Count % 50 == 0) ||
+                (_windowInfos.Count < 10000 && _windowInfos.Count % 100 == 0)
+                ) {
+                StateHasChanged();
+                await Task.Delay(25);
+            }
+            if(_windowInfos.Count > 750) multiplier = 2;
+            if(_windowInfos.Count > 1500) multiplier = 3;
+            
+        }
+
+        await base.OnInitializedAsync();
+    }
+
+    private void OnCloseClicked(int i1) {
+        Console.WriteLine("Close clicked on " + i1);
+    }
+
+    public class WindowDimension {
+        public int Width { get; set; }
+        public int Height { get; set; }
+    }
+
+}
diff --git a/MatrixRoomUtils.Web/Program.cs b/MatrixRoomUtils.Web/Program.cs
index 164f746..8ea8742 100644
--- a/MatrixRoomUtils.Web/Program.cs
+++ b/MatrixRoomUtils.Web/Program.cs
@@ -2,6 +2,7 @@ using System.Text.Json;
 using System.Text.Json.Serialization;
 using Blazored.LocalStorage;
 using MatrixRoomUtils.Web;
+using MatrixRoomUtils.Web.Classes;
 using Microsoft.AspNetCore.Components.Web;
 using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
 
@@ -20,4 +21,8 @@ builder.Services.AddBlazoredLocalStorage(config => {
     config.JsonSerializerOptions.WriteIndented = false;
 });
 
+builder.Services.AddSingleton<LocalStorageProviderService>();
+builder.Services.AddSingleton<SessionStorageProviderService>();
+builder.Services.AddSingleton<TieredStorage<LocalStorageProviderService, SessionStorageProviderService>>();
+
 await builder.Build().RunAsync();
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/SessionStorageProviderService.cs b/MatrixRoomUtils.Web/SessionStorageProviderService.cs
new file mode 100644
index 0000000..82372ff
--- /dev/null
+++ b/MatrixRoomUtils.Web/SessionStorageProviderService.cs
@@ -0,0 +1 @@
+public class SessionStorageProviderService { }
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/ModalWindow.razor b/MatrixRoomUtils.Web/Shared/ModalWindow.razor
new file mode 100644
index 0000000..75c2933
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/ModalWindow.razor
@@ -0,0 +1,130 @@
+<div class="r-modal" style="top: @(_y)px; left: @(_x)px;">
+    <div class="titlebar" @onmousedown="MouseDown" @onmouseup="MouseUp" @onmousemove="MouseMove" @onmouseleave="MouseMove">
+        <b class="title">@Title</b>
+        <button class="close" @onclick="OnCloseClicked">X</button>
+    </div>
+    <div class="content">
+        @ChildContent
+    </div>
+</div>
+
+<style>
+    .r-modal {
+        position: absolute;
+        width: fit-content;
+        height: fit-content;
+        z-index: 1000;
+    }
+    .r-modal:hover {
+        z-index: 1001;
+    }
+    
+    .r-modal > .titlebar {
+        position: absolute;
+        display: block;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 25px;
+        background-color: #000;
+        user-select: none;
+    }
+    
+    .r-modal > .titlebar > .title {
+        position: relative;
+        top: 0;
+        left: 0;
+        width: fit-content;
+        height: 100%;
+        line-height: 25px;
+        padding-left: 10px;
+        color: #fff;
+    }
+    
+    .r-modal > .titlebar > .close {
+        position: absolute;
+        top: 0;
+        right: 0;
+        width: 25px;
+        height: 100%;
+        line-height: 25px;
+        text-align: center;
+        color: #fff;
+        background-color: #111;
+        cursor: pointer;
+    }
+    
+    .r-modal > .content {
+        position: relative;
+        top: 25px;
+        left: 0;
+        width: fit-content;
+        height: fit-content;
+        min-width: 150px;
+        min-height: 5px;
+        max-width: 75vw;
+        max-height: 75vh;
+        overflow: auto;
+        background-color: #fff;
+    }
+</style>
+
+@code {
+    
+    [Parameter]
+    public RenderFragment? ChildContent { get; set; }
+    
+    [Parameter]
+    public string Title { get; set; } = "Untitled window";
+    
+    [Parameter]
+    public double X { get; set; } = 60;
+    
+    [Parameter]
+    public double Y { get; set; } = 60;
+    
+    [Parameter]
+    public Action OnCloseClicked { get; set; }
+    
+
+    private double _x = 60;
+    private double _y = 60;
+    
+    protected override void OnInitialized() {
+        _x = X;
+        _y = Y;
+    }
+
+    private void WindowDrag(DragEventArgs obj) {
+        Console.WriteLine("Drag: " + obj.ToJson());
+        
+        _x += obj.MovementX;
+        _y += obj.MovementY;
+        
+        StateHasChanged();
+    }
+
+    private bool isDragging = false;
+    private double dragX = 0;
+    private double dragY = 0;
+    private void MouseDown(MouseEventArgs obj) {
+        isDragging = true;
+        dragX = obj.ClientX;
+        dragY = obj.ClientY;
+    }
+
+    private void MouseUp(MouseEventArgs obj) {
+        isDragging = false;
+    }
+
+    private void MouseMove(MouseEventArgs obj) {
+        if (isDragging) {
+            _x += obj.ClientX - dragX;
+            _y += obj.ClientY - dragY;
+            dragX = obj.ClientX;
+            dragY = obj.ClientY;
+            StateHasChanged();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/wwwroot/css/app.css b/MatrixRoomUtils.Web/wwwroot/css/app.css
index 245566a..cb7ba63 100644
--- a/MatrixRoomUtils.Web/wwwroot/css/app.css
+++ b/MatrixRoomUtils.Web/wwwroot/css/app.css
@@ -1,4 +1,5 @@
 @import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
+@import url('jetbrains-mono/jetbrains-mono.css');
 
 article > h3:first-child {
     padding-top: 24px;
@@ -114,3 +115,7 @@ a, .btn-link {
 .loading-progress-text:after {
     content: var(--blazor-load-percentage-text, "Loading");
 }
+
+pre {
+    font-family: JetBrainsMono, var(--bs-font-monospace);
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/jetbrains-mono.css b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/jetbrains-mono.css
new file mode 100644
index 0000000..aa98602
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/jetbrains-mono.css
@@ -0,0 +1,133 @@
+/* source: https://gist.github.com/aasmpro/95776294ecf48bd7d0562504bad848ea */
+
+/* normal fonts */
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: normal;
+    font-weight: 100;
+    src: url("./ttf/JetBrainsMono-Thin.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-Thin.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: normal;
+    font-weight: 200;
+    src: url("./ttf/JetBrainsMono-ExtraLight.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-ExtraLight.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: normal;
+    font-weight: 300;
+    src: url("./ttf/JetBrainsMono-Light.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-Light.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: normal;
+    font-weight: 400;
+    src: url("./ttf/JetBrainsMono-Regular.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-Regular.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: normal;
+    font-weight: 500;
+    src: url("./ttf/JetBrainsMono-Medium.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-Medium.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: normal;
+    font-weight: 600;
+    src: url("./ttf/JetBrainsMono-SemiBold.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-SemiBold.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: normal;
+    font-weight: 700;
+    src: url("./ttf/JetBrainsMono-Bold.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-Bold.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: normal;
+    font-weight: 800;
+    src: url("./ttf/JetBrainsMono-ExtraBold.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-ExtraBold.woff2") format("woff2");
+}
+
+/* italic fonts */
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: italic;
+    font-weight: 100;
+    src: url("./ttf/JetBrainsMono-ThinItalic.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-ThinItalic.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: italic;
+    font-weight: 200;
+    src: url("./ttf/JetBrainsMono-ExtraLightItalic.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-ExtraLightItalic.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: italic;
+    font-weight: 300;
+    src: url("./ttf/JetBrainsMono-LightItalic.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-LightItalic.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: italic;
+    font-weight: 400;
+    src: url("./ttf/JetBrainsMono-Italic.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-Italic.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: italic;
+    font-weight: 500;
+    src: url("./ttf/JetBrainsMono-MediumItalic.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-MediumItalic.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: italic;
+    font-weight: 600;
+    src: url("./ttf/JetBrainsMono-SemiBoldItalic.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-SemiBoldItalic.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: italic;
+    font-weight: 700;
+    src: url("./ttf/JetBrainsMono-BoldItalic.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-BoldItalic.woff2") format("woff2");
+}
+
+@font-face {
+    font-family: JetBrainsMono;
+    font-style: italic;
+    font-weight: 800;
+    src: url("./ttf/JetBrainsMono-ExtraBoldItalic.ttf") format("truetype");
+    src: url("./webfonts/JetBrainsMono-ExtraBoldItalic.woff2") format("woff2");
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Bold.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Bold.ttf
new file mode 100644
index 0000000..8c93043
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Bold.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-BoldItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-BoldItalic.ttf
new file mode 100644
index 0000000..1ddf216
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-BoldItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraBold.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraBold.ttf
new file mode 100644
index 0000000..435d7a7
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraBold.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraBoldItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..79e616e
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraBoldItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraLight.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraLight.ttf
new file mode 100644
index 0000000..c131cbf
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraLight.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraLightItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraLightItalic.ttf
new file mode 100644
index 0000000..a768985
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ExtraLightItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Italic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Italic.ttf
new file mode 100644
index 0000000..ccc9d6a
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Italic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Light.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Light.ttf
new file mode 100644
index 0000000..15f15a2
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Light.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-LightItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-LightItalic.ttf
new file mode 100644
index 0000000..506208f
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-LightItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Medium.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Medium.ttf
new file mode 100644
index 0000000..9767115
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Medium.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-MediumItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-MediumItalic.ttf
new file mode 100644
index 0000000..415a9e3
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-MediumItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Regular.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Regular.ttf
new file mode 100644
index 0000000..dff66cc
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Regular.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-SemiBold.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-SemiBold.ttf
new file mode 100644
index 0000000..a70e69b
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-SemiBold.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-SemiBoldItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-SemiBoldItalic.ttf
new file mode 100644
index 0000000..968602e
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-SemiBoldItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Thin.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Thin.ttf
new file mode 100644
index 0000000..7dbe2ac
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-Thin.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ThinItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ThinItalic.ttf
new file mode 100644
index 0000000..c6ad6c2
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMono-ThinItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Bold.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Bold.ttf
new file mode 100644
index 0000000..f78f84f
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Bold.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-BoldItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-BoldItalic.ttf
new file mode 100644
index 0000000..9fb8c83
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-BoldItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraBold.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraBold.ttf
new file mode 100644
index 0000000..fe5be6a
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraBold.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraBoldItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..59fc980
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraBoldItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraLight.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraLight.ttf
new file mode 100644
index 0000000..6da7b75
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraLight.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraLightItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraLightItalic.ttf
new file mode 100644
index 0000000..5733efc
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ExtraLightItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Italic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Italic.ttf
new file mode 100644
index 0000000..4e9c380
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Italic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Light.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Light.ttf
new file mode 100644
index 0000000..0b79b0c
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Light.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-LightItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-LightItalic.ttf
new file mode 100644
index 0000000..b5e0842
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-LightItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Medium.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Medium.ttf
new file mode 100644
index 0000000..1454372
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Medium.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-MediumItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-MediumItalic.ttf
new file mode 100644
index 0000000..8d63c6c
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-MediumItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Regular.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Regular.ttf
new file mode 100644
index 0000000..70d2ec9
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Regular.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-SemiBold.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-SemiBold.ttf
new file mode 100644
index 0000000..ce60a88
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-SemiBold.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-SemiBoldItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-SemiBoldItalic.ttf
new file mode 100644
index 0000000..3b3f8f6
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-SemiBoldItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Thin.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Thin.ttf
new file mode 100644
index 0000000..bea837e
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-Thin.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ThinItalic.ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ThinItalic.ttf
new file mode 100644
index 0000000..f0bfed7
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/ttf/JetBrainsMonoNL-ThinItalic.ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/variable/JetBrainsMono-Italic[wght].ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/variable/JetBrainsMono-Italic[wght].ttf
new file mode 100644
index 0000000..5414835
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/variable/JetBrainsMono-Italic[wght].ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/variable/JetBrainsMono[wght].ttf b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/variable/JetBrainsMono[wght].ttf
new file mode 100644
index 0000000..b60e77f
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/variable/JetBrainsMono[wght].ttf
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Bold.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Bold.woff2
new file mode 100644
index 0000000..4917f43
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Bold.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-BoldItalic.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-BoldItalic.woff2
new file mode 100644
index 0000000..536d3f7
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-BoldItalic.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBold.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBold.woff2
new file mode 100644
index 0000000..8f88c54
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBold.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBoldItalic.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBoldItalic.woff2
new file mode 100644
index 0000000..d1478ba
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBoldItalic.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLight.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLight.woff2
new file mode 100644
index 0000000..b97239f
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLight.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLightItalic.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLightItalic.woff2
new file mode 100644
index 0000000..be01aac
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLightItalic.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Italic.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Italic.woff2
new file mode 100644
index 0000000..d60c270
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Italic.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Light.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Light.woff2
new file mode 100644
index 0000000..6538498
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Light.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-LightItalic.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-LightItalic.woff2
new file mode 100644
index 0000000..66ca3d2
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-LightItalic.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Medium.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Medium.woff2
new file mode 100644
index 0000000..669d04c
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Medium.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-MediumItalic.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-MediumItalic.woff2
new file mode 100644
index 0000000..80cfd15
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-MediumItalic.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Regular.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Regular.woff2
new file mode 100644
index 0000000..40da427
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Regular.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBold.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBold.woff2
new file mode 100644
index 0000000..5ead7b0
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBold.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBoldItalic.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBoldItalic.woff2
new file mode 100644
index 0000000..c5dd294
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBoldItalic.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Thin.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Thin.woff2
new file mode 100644
index 0000000..17270e4
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Thin.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ThinItalic.woff2 b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ThinItalic.woff2
new file mode 100644
index 0000000..a643215
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ThinItalic.woff2
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/index.html b/MatrixRoomUtils.Web/wwwroot/index.html
index f268ac4..9c9e7d0 100644
--- a/MatrixRoomUtils.Web/wwwroot/index.html
+++ b/MatrixRoomUtils.Web/wwwroot/index.html
@@ -36,6 +36,12 @@
             console.log("Element is not an HTMLElement", element);
         }
     }
+    function getWindowDimensions() {
+        return {
+            width: window.innerWidth,
+            height: window.innerHeight
+        };
+    }
 </script>
 <script src="_framework/blazor.webassembly.js"></script>
 </body>
diff --git a/MatrixRoomUtils.sln.DotSettings.user b/MatrixRoomUtils.sln.DotSettings.user
new file mode 100644
index 0000000..07d0701
--- /dev/null
+++ b/MatrixRoomUtils.sln.DotSettings.user
@@ -0,0 +1,6 @@
+<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/SweaWarningsMode/@EntryValue">ShowAndRun</s:String>
+	
+	
+	
+	</wpf:ResourceDictionary>
\ No newline at end of file