diff --git a/LibMatrix b/LibMatrix
-Subproject bf2da30c7ae9d4c15a5e22f3ee0b1bae2ca66e4
+Subproject b7dbc011e0eee55c011623d2747e517436d0410
diff --git a/MatrixUtils.Abstractions/RoomInfo.cs b/MatrixUtils.Abstractions/RoomInfo.cs
index 84a5940..0cd4dc1 100644
--- a/MatrixUtils.Abstractions/RoomInfo.cs
+++ b/MatrixUtils.Abstractions/RoomInfo.cs
@@ -13,42 +13,55 @@ public class RoomInfo : NotifyPropertyChanged {
public ObservableCollection<StateEventResponse?> StateEvents { get; } = new();
public async Task<StateEventResponse?> GetStateEvent(string type, string stateKey = "") {
- var @event = StateEvents.FirstOrDefault(x => x.Type == type && x.StateKey == stateKey);
+ var @event = StateEvents.FirstOrDefault(x => x?.Type == type && x.StateKey == stateKey);
if (@event is not null) return @event;
- @event = new StateEventResponse {
- RoomId = Room.RoomId,
- Type = type,
- StateKey = stateKey,
- Sender = null, //TODO implement
- EventId = null
- };
- // if (Room is null) return null;
+ // @event = new StateEventResponse {
+ // RoomId = Room.RoomId,
+ // Type = type,
+ // StateKey = stateKey,
+ // Sender = null, //TODO implement
+ // EventId = null
+ // };
+ // // if (Room is null) return null;
+ // try {
+ // @event.RawContent = await Room.GetStateAsync<JsonObject>(type, stateKey);
+ // }
+ // catch (MatrixException e) {
+ // if (e is { ErrorCode: "M_NOT_FOUND" }) {
+ // if (type == "m.room.name")
+ // @event = new() {
+ // Type = type,
+ // StateKey = stateKey,
+ // TypedContent = new RoomNameEventContent() {
+ // Name = await Room.GetNameOrFallbackAsync()
+ // },
+ // //TODO implement
+ // RoomId = null,
+ // Sender = null,
+ // EventId = null
+ // };
+ // else
+ // @event.RawContent = default!;
+ // }
+ // else {
+ // throw;
+ // }
+ // }
+ // catch (Exception e) {
+ // await Task.Delay(1000);
+ // return await GetStateEvent(type, stateKey);
+ // }
+
try {
- @event.RawContent = await Room.GetStateAsync<JsonObject>(type, stateKey);
+ @event = await Room.GetStateEventOrNullAsync(type, stateKey);
+ StateEvents.Add(@event);
}
- catch (MatrixException e) {
- if (e is { ErrorCode: "M_NOT_FOUND" }) {
- if (type == "m.room.name")
- @event = new() {
- Type = type,
- StateKey = stateKey,
- TypedContent = new RoomNameEventContent() {
- Name = await Room.GetNameOrFallbackAsync()
- },
- //TODO implement
- RoomId = null,
- Sender = null,
- EventId = null
- };
- else
- @event.RawContent = default!;
- }
- else {
- throw;
- }
+ catch (Exception e) {
+ Console.Error.WriteLine(e);
+ await Task.Delay(1000);
+ return await GetStateEvent(type, stateKey);
}
- StateEvents.Add(@event);
return @event;
}
@@ -81,11 +94,14 @@ public class RoomInfo : NotifyPropertyChanged {
private string? _roomCreator;
public string? DefaultRoomName { get; set; }
+ public string? OverrideRoomType { get; set; }
+ public string? RoomType => OverrideRoomType ?? CreationEventContent?.Type;
public RoomInfo() {
StateEvents.CollectionChanged += (_, args) => {
if (args.NewItems is { Count: > 0 })
- foreach (StateEventResponse newState in args.NewItems) { // TODO: switch statement benchmark?
+ foreach (StateEventResponse? newState in args.NewItems) { // TODO: switch statement benchmark?
+ if(newState is null) continue;
if (newState.Type == RoomNameEventContent.EventId && newState.TypedContent is RoomNameEventContent roomNameContent)
RoomName = roomNameContent.Name;
else if (newState is { Type: RoomAvatarEventContent.EventId, TypedContent: RoomAvatarEventContent roomAvatarContent })
diff --git a/MatrixUtils.Web/Classes/RMUStorageWrapper.cs b/MatrixUtils.Web/Classes/RMUStorageWrapper.cs
index 31e7734..fa79268 100644
--- a/MatrixUtils.Web/Classes/RMUStorageWrapper.cs
+++ b/MatrixUtils.Web/Classes/RMUStorageWrapper.cs
@@ -12,7 +12,7 @@ public class RMUStorageWrapper(TieredStorageService storageService, HomeserverPr
}
public async Task<UserAuth?> GetCurrentToken() {
- var currentToken = await storageService.DataStorageProvider.LoadObjectAsync<UserAuth>("token");
+ var currentToken = await storageService.DataStorageProvider.LoadObjectAsync<UserAuth>("rmu.token");
var allTokens = await GetAllTokens();
if (allTokens is null or { Count: 0 }) {
await SetCurrentToken(null);
@@ -94,5 +94,32 @@ public class RMUStorageWrapper(TieredStorageService storageService, HomeserverPr
await storageService.DataStorageProvider.SaveObjectAsync("rmu.tokens", tokens);
}
- public async Task SetCurrentToken(UserAuth? auth) => await storageService.DataStorageProvider.SaveObjectAsync("token", auth);
+ public async Task SetCurrentToken(UserAuth? auth) => await storageService.DataStorageProvider.SaveObjectAsync("rmu.token", auth);
+
+ public async Task MigrateFromMRU() {
+ var dsp = storageService.DataStorageProvider!;
+ if(await dsp.ObjectExistsAsync("token")) {
+ var oldToken = await dsp.LoadObjectAsync<UserAuth>("token");
+ if (oldToken != null) {
+ await dsp.SaveObjectAsync("rmu.token", oldToken);
+ await dsp.DeleteObjectAsync("tokens");
+ }
+ }
+
+ if(await dsp.ObjectExistsAsync("tokens")) {
+ var oldTokens = await dsp.LoadObjectAsync<List<UserAuth>>("tokens");
+ if (oldTokens != null) {
+ await dsp.SaveObjectAsync("rmu.tokens", oldTokens);
+ await dsp.DeleteObjectAsync("tokens");
+ }
+ }
+
+ if(await dsp.ObjectExistsAsync("mru.tokens")) {
+ var oldTokens = await dsp.LoadObjectAsync<List<UserAuth>>("mru.tokens");
+ if (oldTokens != null) {
+ await dsp.SaveObjectAsync("rmu.tokens", oldTokens);
+ await dsp.DeleteObjectAsync("mru.tokens");
+ }
+ }
+ }
}
diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj
index d5977a0..515b235 100644
--- a/MatrixUtils.Web/MatrixUtils.Web.csproj
+++ b/MatrixUtils.Web/MatrixUtils.Web.csproj
@@ -9,6 +9,7 @@
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<UseBlazorWebAssembly>true</UseBlazorWebAssembly>
+ <BlazorEnableCompression>false</BlazorEnableCompression>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<!-- <RunAOTCompilation>true</RunAOTCompilation>-->
</PropertyGroup>
diff --git a/MatrixUtils.Web/Pages/Index.razor b/MatrixUtils.Web/Pages/Index.razor
index 9c1bab6..e1cb60e 100644
--- a/MatrixUtils.Web/Pages/Index.razor
+++ b/MatrixUtils.Web/Pages/Index.razor
@@ -20,7 +20,7 @@ Small collection of tools to do not-so-everyday things.
var _auth = session.UserAuth;
<tr class="user-entry">
<td>
- <img class="avatar" src="@session.UserInfo.AvatarUrl"/>
+ <img class="avatar" src="@session.UserInfo.AvatarUrl" crossorigin="anonymous"/>
</td>
<td class="user-info">
<p>
@@ -108,6 +108,16 @@ Small collection of tools to do not-so-everyday things.
_sessions.Clear();
_offlineSessions.Clear();
var tokens = await RMUStorage.GetAllTokens();
+ if (tokens is not { Count: > 0 }) {
+ Console.WriteLine("No tokens found, trying migration from MRU...");
+ await RMUStorage.MigrateFromMRU();
+ tokens = await RMUStorage.GetAllTokens();
+ if (tokens is not { Count: > 0 }) {
+ Console.WriteLine("No tokens found");
+ return;
+ }
+ }
+
var profileTasks = tokens.Select(async token => {
UserInfo userInfo = new();
AuthenticatedHomeserverGeneric hs;
@@ -119,14 +129,13 @@ Small collection of tools to do not-so-everyday things.
if (e.ErrorCode != "M_UNKNOWN_TOKEN") throw;
NavigationManager.NavigateTo("/InvalidSession?ctx=" + token.AccessToken);
return;
-
}
catch (Exception e) {
logger.LogError(e, $"Failed to instantiate AuthenticatedHomeserver for {token.ToJson()}, homeserver may be offline?", token.UserId);
_offlineSessions.Add(token);
return;
}
-
+
Console.WriteLine($"Got hs for {token.ToJson()}");
var roomCountTask = hs.GetJoinedRooms();
@@ -143,8 +152,8 @@ Small collection of tools to do not-so-everyday things.
ServerVersion = await (hs.FederationClient?.GetServerVersionAsync() ?? Task.FromResult<ServerVersionResponse?>(null)),
Homeserver = hs
});
- });
- Console.WriteLine("Waiting for profile tasks");
+ }).ToList();
+ Console.WriteLine($"Waiting for {profileTasks.Count} profile tasks");
await Task.WhenAll(profileTasks);
Console.WriteLine("Done waiting for profile tasks");
await base.OnInitializedAsync();
@@ -177,7 +186,6 @@ Small collection of tools to do not-so-everyday things.
await OnInitializedAsync();
}
-
private async Task SwitchSession(UserAuth auth) {
Console.WriteLine($"Switching to {auth.Homeserver} {auth.UserId} via {auth.Proxy}");
await RMUStorage.SetCurrentToken(auth);
diff --git a/MatrixUtils.Web/Pages/Rooms/Index.razor b/MatrixUtils.Web/Pages/Rooms/Index.razor
index 0ec9487..170f489 100644
--- a/MatrixUtils.Web/Pages/Rooms/Index.razor
+++ b/MatrixUtils.Web/Pages/Rooms/Index.razor
@@ -18,7 +18,13 @@
<RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" @bind-StillFetching="RenderContents"></RoomList>
@code {
- private ObservableCollection<RoomInfo> Rooms { get; } = new();
+
+ private ObservableCollection<RoomInfo> _rooms = new();
+ private ObservableCollection<RoomInfo> Rooms {
+ get => _rooms;
+ set => _rooms = value;
+ }
+
private UserProfileResponse GlobalProfile { get; set; }
private AuthenticatedHomeserverGeneric? Homeserver { get; set; }
@@ -97,25 +103,29 @@
if (Homeserver is null) return;
var rooms = await Homeserver.GetJoinedRooms();
// SemaphoreSlim _semaphore = new(160, 160);
+ GlobalProfile = await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId);
- var roomTasks = rooms.Select(async room => {
- RoomInfo ri;
- // await _semaphore.WaitAsync();
- ri = new() { Room = room };
- await Task.WhenAll((filter.Room?.State?.Types ?? []).Select(x => ri.GetStateEvent(x)));
- return ri;
- }).ToAsyncEnumerable();
-
- await foreach (var room in roomTasks) {
- Rooms.Add(room);
- StateHasChanged();
- // await Task.Delay(50);
- // _semaphore.Release();
+ Rooms = new ObservableCollection<RoomInfo>(rooms.Select(x => new RoomInfo() { Room = x }));
+ foreach (var stateType in filter.Room?.State?.Types ?? []) {
+ var tasks = Rooms.Select(async room => {
+ try {
+
+ await room.GetStateEvent(stateType);
+ }
+ catch (Exception e) {
+ Console.WriteLine($"Failed to get state event {stateType} for room {room.Room.RoomId}: {e}");
+ }
+ });
+ await Task.WhenAll(tasks);
+ Status = $"Fetched all {stateType} events...";
+ // StateHasChanged();
}
+
- if (rooms.Count >= 150) RenderContents = true;
-
- GlobalProfile = await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId);
+ RenderContents = true;
+ Status = "Initial fetch done! Starting initial sync...";
+ // StateHasChanged();
+ await Task.Delay(1000);
syncHelper = new SyncHelper(Homeserver, logger) {
Timeout = 30000,
Filter = filter,
@@ -147,7 +157,7 @@
Console.WriteLine($"Queue no longer empty after {renderTimeSw.Elapsed}!");
- int maxUpdates = 10;
+ int maxUpdates = 50;
isInitialSync = false;
while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) {
var (roomId, roomData) = queueEntry;
@@ -176,6 +186,8 @@
else {
Console.WriteLine($"QueueWorker: could not merge state for {room.Room.RoomId} as new data contains no state events!");
}
+
+ await Task.Delay(100);
}
Console.WriteLine($"QueueWorker: {queue.Count} entries left in queue, {maxUpdates} maxUpdates left, RenderContents: {RenderContents}");
Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue...";
@@ -212,9 +224,10 @@
}
private Queue<KeyValuePair<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure>> queue = new();
+
private async Task RunSyncLoop(SyncHelper syncHelper) {
- Status = "Initial syncing...";
+ // Status = "Initial syncing...";
Console.WriteLine("starting sync");
var syncs = syncHelper.EnumerateSyncAsync();
diff --git a/MatrixUtils.Web/Pages/Tools/ToolsIndex.razor b/MatrixUtils.Web/Pages/Tools/Index.razor
index f4092d7..f1e04a3 100644
--- a/MatrixUtils.Web/Pages/Tools/ToolsIndex.razor
+++ b/MatrixUtils.Web/Pages/Tools/Index.razor
@@ -6,3 +6,5 @@
<a href="/Tools/MassRoomJoin">Join room across all session</a><br/>
<a href="/Tools/MediaLocator">Locate lost media</a><br/>
<a href="/Tools/SpaceDebug">Debug space relationships</a><br/>
+<a href="/Tools/MigrateRoom">Migrate users from a split room to a new room</a><br/>
+<a href="/Tools/LeaveRoom">Leave room by ID</a><br/>
diff --git a/MatrixUtils.Web/Pages/Tools/LeaveRoom.razor b/MatrixUtils.Web/Pages/Tools/LeaveRoom.razor
new file mode 100644
index 0000000..b5df05f
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/LeaveRoom.razor
@@ -0,0 +1,56 @@
+@page "/Tools/LeaveRoom"
+@using System.Diagnostics
+@using ArcaneLibs.Extensions
+@using LibMatrix.Homeservers
+@using LibMatrix.RoomTypes
+@using System.Collections.ObjectModel
+<h3>Leave room</h3>
+<hr/>
+<span>Room ID: </span>
+<InputText @bind-Value="@RoomId"></InputText>
+<br/>
+<LinkButton OnClick="@Leave">Leave</LinkButton>
+<br/><br/>
+@foreach (var line in Log) {
+ <p>@line</p>
+}
+@code {
+ AuthenticatedHomeserverGeneric? hs { get; set; }
+ ObservableCollection<string> Log { get; set; } = new ObservableCollection<string>();
+ [Parameter, SupplyParameterFromQuery(Name = "roomId")]
+ public string? RoomId { get; set; }
+
+ protected override async Task OnInitializedAsync() {
+ hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ if (hs is null) return;
+ Log.CollectionChanged += (sender, args) => StateHasChanged();
+
+ StateHasChanged();
+ Console.WriteLine("Rerendered!");
+ await base.OnInitializedAsync();
+ }
+
+ private async Task Leave() {
+ if(string.IsNullOrWhiteSpace(RoomId)) return;
+ var room = hs.GetRoom(RoomId);
+ Log.Add("Got room object...");
+ try {
+ await room.LeaveAsync();
+ Log.Add("Left room!");
+ }
+ catch (Exception e) {
+ Log.Add(e.ToString());
+ }
+
+ try {
+ await room.ForgetAsync();
+ Log.Add("Forgot room!");
+ }
+ catch (Exception e) {
+ Log.Add(e.ToString());
+ }
+
+ Log.Add("Done!");
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Shared/MainLayout.razor b/MatrixUtils.Web/Shared/MainLayout.razor
index 92194b2..d8bf411 100644
--- a/MatrixUtils.Web/Shared/MainLayout.razor
+++ b/MatrixUtils.Web/Shared/MainLayout.razor
@@ -16,4 +16,6 @@
@Body
</article>
</main>
-</div>
\ No newline at end of file
+</div>
+
+<UpdateAvailableDetector/>
\ No newline at end of file
diff --git a/MatrixUtils.Web/Shared/MxcImage.razor b/MatrixUtils.Web/Shared/MxcImage.razor
index fb8c248..f31c19f 100644
--- a/MatrixUtils.Web/Shared/MxcImage.razor
+++ b/MatrixUtils.Web/Shared/MxcImage.razor
@@ -1,4 +1,4 @@
-<img class="@Class" src="@ResolvedUri" style="@Style"/>
+<img src="@ResolvedUri" style="@StyleString"/>
@code {
private string _mxcUri;
private string _style;
@@ -13,9 +13,14 @@
UriHasChanged(value);
}
}
+ [Parameter]
+ public bool Circular { get; set; }
- //mxcuri binding
+ [Parameter]
+ public int? Width { get; set; }
+ [Parameter]
+ public int? Height { get; set; }
[Parameter]
public string Style {
@@ -36,8 +41,18 @@
}
}
+ private string StyleString => $"{Style} {(Circular ? "border-radius: 50%;" : "")} {(Width.HasValue ? $"width: {Width}px;" : "")} {(Height.HasValue ? $"height: {Height}px;" : "")}";
+
+ private static readonly string Prefix = "mxc://";
+ private static readonly int PrefixLength = Prefix.Length;
+
private async Task UriHasChanged(string value) {
- var uri = value[5..].Split('/');
+ if (!value.StartsWith(Prefix)) {
+ Console.WriteLine($"UriHasChanged: {value} does not start with {Prefix}, passing as resolved URI!!!");
+ ResolvedUri = value;
+ return;
+ }
+ var uri = value[PrefixLength..].Split('/');
Console.WriteLine($"UriHasChanged: {value} {uri[0]}");
if (Homeserver is null) {
Console.WriteLine($"Homeserver is null, creating new remotehomeserver for {uri[0]}");
@@ -47,7 +62,7 @@
Console.WriteLine($"ResolvedUri: {ResolvedUri}");
}
- [Parameter]
- public string Class { get; set; }
+ // [Parameter]
+ // public string Class { get; set; }
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Shared/RoomList.razor b/MatrixUtils.Web/Shared/RoomList.razor
index ed443dd..2ab3cef 100644
--- a/MatrixUtils.Web/Shared/RoomList.razor
+++ b/MatrixUtils.Web/Shared/RoomList.razor
@@ -20,9 +20,24 @@ else {
}
@code {
+ private ObservableCollection<RoomInfo> _rooms;
[Parameter]
- public ObservableCollection<RoomInfo> Rooms { get; set; }
+ public ObservableCollection<RoomInfo> Rooms {
+ get => _rooms;
+ set {
+ if(_rooms != value)
+ value.CollectionChanged += (_, args) => {
+ foreach (RoomInfo item in args.NewItems??(object[])[]) {
+ item.PropertyChanged += (_, args2) => {
+ if (args2.PropertyName == nameof(item.CreationEventContent))
+ StateHasChanged();
+ };
+ }
+ };
+ _rooms = value;
+ }
+ }
[Parameter]
public UserProfileResponse? GlobalProfile { get; set; }
@@ -33,65 +48,21 @@ else {
[Parameter]
public EventCallback<bool> StillFetchingChanged { get; set; }
- private Dictionary<string, List<RoomInfo>> RoomsWithTypes => Rooms is null ? new() : Rooms.GroupBy(x => GetRoomTypeName(x.CreationEventContent?.Type)).ToDictionary(x => x.Key, x => x.ToList());
-
- private bool hooked;
- protected override async Task OnParametersSetAsync() {
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
- if (hs is null) return;
- if (!hooked) {
- Rooms.CollectionChanged += (_, args) => {
- foreach (RoomInfo item in args.NewItems) {
- item.PropertyChanged += (_, args2) => {
- // Console.WriteLine(args2);
-
- if (args2.PropertyName == nameof(item.CreationEventContent))
- StateHasChanged();
- };
- }
- };
- hooked = true;
- }
-
- // GlobalProfile ??= await hs.GetProfileAsync(hs.WhoAmI.UserId);
-
- await base.OnParametersSetAsync();
- }
+
+ private Dictionary<string, List<RoomInfo>> RoomsWithTypes => Rooms is null ? new() : Rooms.GroupBy(x => GetRoomTypeName(x.RoomType)).ToDictionary(x => x.Key, x => x.ToList());
private string GetRoomTypeName(string? roomType) => roomType switch {
null => "Room",
"m.space" => "Space",
"msc3588.stories.stories-room" => "Story room",
- "support.feline.policy.lists.msc.v1" => "MSC3784 Policy list (v1)",
+ "support.feline.policy.lists.msc.v1" => "MSC3784 policy list (v1)",
+ // custom names
+ "gay.rory.moderation_bot.policy_room" => "Rory&::ModerationBot policy room",
+ "gay.rory.moderation_bot.log_room" => "Rory&::ModerationBot log room",
+ "gay.rory.moderation_bot.control_room" => "Rory&::ModerationBot control room",
+ // fallback
+ "gay.rory.rmu.fallback.policy_list" => "\"Legacy\" policy list (unmarked room)",
_ => roomType
- };
-
- // private static SemaphoreSlim _semaphoreSlim = new(8, 8);
-
- // private async Task ProcessRoom(RoomInfo room) {
- // await _semaphoreSlim.WaitAsync();
- // string roomType;
- // try {
- // var createEvent = (await room.GetStateEvent("m.room.create")).TypedContent as RoomCreateEventContent;
- // roomType = GetRoomTypeName(createEvent.Type);
- //
- // if (roomType == "Room") {
- // var mjolnirData = await room.GetStateEvent("org.matrix.mjolnir.shortcode");
- // if (mjolnirData?.RawContent?.ToJson(ignoreNull: true) is not null and not "{}")
- // roomType = "Legacy policy room";
- // }
- // }
- // catch (MatrixException e) {
- // roomType = $"Error: {e.ErrorCode}";
- // }
- //
- // // if (!RoomsWithTypes.ContainsKey(roomType)) {
- // // RoomsWithTypes.Add(roomType, new List<RoomInfo>());
- // // }
- // // RoomsWithTypes[roomType].Add(room);
- //
- // StateHasChanged();
- // _semaphoreSlim.Release();
- // }
+ };
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor b/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
index 3d0070f..4b24c18 100644
--- a/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
+++ b/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
@@ -16,6 +16,7 @@
<LinkButton href="@($"/Rooms/{room.Room.RoomId}/Timeline")">View timeline</LinkButton>
<LinkButton href="@($"/Rooms/{room.Room.RoomId}/State/View")">View state</LinkButton>
<LinkButton href="@($"/Rooms/{room.Room.RoomId}/State/Edit")">Edit state</LinkButton>
+ <LinkButton href="@($"/Tools/LeaveRoom?roomId={room.Room.RoomId}")" Color="#FF0000">Leave room</LinkButton>
@if (room.CreationEventContent?.Type == "m.space") {
<RoomListSpace Space="@room"></RoomListSpace>
@@ -49,15 +50,5 @@
: RoomConstants.DangerousRoomVersions.Contains(roomVersionContent.RoomVersion) ? 2
: roomVersionContent.RoomVersion != RoomConstants.RecommendedRoomVersion ? 1 : 0;
}
-
- public static string GetRoomTypeName(string roomType) {
- return roomType switch {
- null => "Room",
- "m.space" => "Space",
- "org.matrix.mjolnir.policy" => "Policy room",
-
- _ => roomType
- };
- }
}
diff --git a/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor b/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
index 895d642..9c481e3 100644
--- a/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
+++ b/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
@@ -1,15 +1,23 @@
@using System.Collections.ObjectModel
@using MatrixUtils.Abstractions
-<MatrixUtils.Web.Shared.SimpleComponents.LinkButton href="@($"/Rooms/{Space.Room.RoomId}/Space")">Manage space</MatrixUtils.Web.Shared.SimpleComponents.LinkButton>
+<LinkButton href="@($"/Rooms/{Space.Room.RoomId}/Space")">Manage space</LinkButton>
<br/>
<details @ontoggle="SpaceChildrenOpened">
<summary>@Children.Count children</summary>
@if (_shouldRenderChildren) {
<p>Breadcrumb: @Breadcrumbs</p>
+ <p>Joined:</p>
<div style="margin-left: 8px;">
<RoomList Rooms="Children"></RoomList>
</div>
+ <p>Unjoined:</p>
+ @foreach (var room in Unjoined) {
+ <p>@room.Room.RoomId</p>
+ }
+ @* <div style="margin-left: 8px;"> *@
+ @* <RoomList Rooms="Children"></RoomList> *@
+ @* </div> *@
}
</details>
@@ -28,11 +36,14 @@
}
private ObservableCollection<RoomInfo> Children { get; set; } = new();
+ private Collection<RoomInfo> Unjoined { get; set; } = new();
protected override async Task OnInitializedAsync() {
if (Breadcrumbs == null) throw new ArgumentNullException(nameof(Breadcrumbs));
await Task.Delay(Random.Shared.Next(1000, 10000));
var rooms = Space.Room.AsSpace.GetChildrenAsync();
+ var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var joinedRooms = await hs.GetJoinedRooms();
await foreach (var room in rooms) {
if (Breadcrumbs.Contains(room.RoomId)) continue;
var roomInfo = KnownRooms.FirstOrDefault(x => x.Room.RoomId == room.RoomId);
@@ -42,7 +53,9 @@
};
KnownRooms.Add(roomInfo);
}
- Children.Add(roomInfo);
+ if(joinedRooms.Any(x=>x.RoomId == room.RoomId))
+ Children.Add(roomInfo);
+ else Unjoined.Add(roomInfo);
}
await base.OnInitializedAsync();
}
diff --git a/MatrixUtils.Web/Shared/RoomListItem.razor b/MatrixUtils.Web/Shared/RoomListItem.razor
index 1046dd1..2e7a372 100644
--- a/MatrixUtils.Web/Shared/RoomListItem.razor
+++ b/MatrixUtils.Web/Shared/RoomListItem.razor
@@ -17,7 +17,7 @@
</span>
<span class="centerVertical noLeftPadding">-></span>
}
- <MxcImage Class="avatar32" MxcUri="@RoomInfo.RoomIcon" Style="@(ChildContent is not null ? "vertical-align: middle;" : "")"/>
+ <MxcImage class="avatar32" MxcUri="@RoomInfo.RoomIcon" Style="@(ChildContent is not null ? "vertical-align: middle;" : "")"/>
<div class="inlineBlock">
<span class="centerVertical">@RoomInfo.RoomName</span>
@if (ChildContent is not null) {
diff --git a/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor b/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor
new file mode 100644
index 0000000..5197a6f
--- /dev/null
+++ b/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor
@@ -0,0 +1,38 @@
+@* Source: https://whuysentruit.medium.com/blazor-wasm-pwa-adding-a-new-update-available-notification-d9f65c4ad13 *@
+@inject IJSRuntime _jsRuntime
+
+@if (_newVersionAvailable)
+{
+ <button type="button" class="btn btn-warning shadow floating-update-button" onclick="window.location.reload()">
+ A new version of the application is available. Click here to reload.
+ </button>
+}
+
+@code {
+
+ private bool _newVersionAvailable = false;
+
+ protected override async Task OnInitializedAsync()
+ {
+ await RegisterForUpdateAvailableNotification();
+ }
+
+ private async Task RegisterForUpdateAvailableNotification()
+ {
+ await _jsRuntime.InvokeAsync<object>(
+ identifier: "registerForUpdateAvailableNotification",
+ DotNetObjectReference.Create(this),
+ nameof(OnUpdateAvailable));
+ }
+
+ [JSInvokable(nameof(OnUpdateAvailable))]
+ public Task OnUpdateAvailable()
+ {
+ _newVersionAvailable = true;
+
+ StateHasChanged();
+
+ return Task.CompletedTask;
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor.css b/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor.css
new file mode 100644
index 0000000..32bff09
--- /dev/null
+++ b/MatrixUtils.Web/Shared/UpdateAvailableDetector.razor.css
@@ -0,0 +1,15 @@
+.floating-update-button {
+ position: fixed;
+
+ right: 2rem;
+ bottom: 2rem;
+
+ padding: 1rem 1.5rem;
+
+ animation: fadein 2s ease-out;
+}
+
+@keyframes fadein {
+ from { right: -100%; }
+ to { right: 2rem; }
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/wwwroot/index.html b/MatrixUtils.Web/wwwroot/index.html
index a40f38c..5182193 100644
--- a/MatrixUtils.Web/wwwroot/index.html
+++ b/MatrixUtils.Web/wwwroot/index.html
@@ -59,7 +59,8 @@
}
</script>
<script src="_framework/blazor.webassembly.js"></script>
- <script>navigator.serviceWorker.register('service-worker.js');</script>
+<!-- <script>navigator.serviceWorker.register('service-worker.js');</script>-->
+ <script src="sw-registrator.js"></script>
</body>
</html>
diff --git a/MatrixUtils.Web/wwwroot/service-worker.published.js b/MatrixUtils.Web/wwwroot/service-worker.published.js
index 003e3e7..9219755 100644
--- a/MatrixUtils.Web/wwwroot/service-worker.published.js
+++ b/MatrixUtils.Web/wwwroot/service-worker.published.js
@@ -8,8 +8,10 @@ self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
const cacheNamePrefix = 'offline-cache-';
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
-const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ];
-const offlineAssetsExclude = [ /^service-worker\.js$/ ];
+const offlineAssetsInclude = [// Standard resources
+ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/, /* Extra known-static paths */
+ /\/_matrix\/media\/.{2}\/download\//, /api\.dicebear\.com\/6\.x\/identicon\/svg/];
+const offlineAssetsExclude = [/^service-worker\.js$/];
// Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'.
const base = "/";
@@ -19,11 +21,14 @@ const manifestUrlList = self.assetsManifest.assets.map(asset => new URL(asset.ur
async function onInstall(event) {
console.info('Service worker: Install');
+ // Activate the new service worker as soon as the old one is retired.
+ self.skipWaiting();
+
// Fetch and cache all matching items from the assets manifest
const assetsRequests = self.assetsManifest.assets
.filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
.filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
- .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' }));
+ .map(asset => new Request(asset.url, {integrity: asset.hash, cache: 'no-cache'}));
await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
}
@@ -43,12 +48,32 @@ async function onFetch(event) {
// For all navigation requests, try to serve index.html from cache,
// unless that request is for an offline resource.
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
- const shouldServeIndexHtml = event.request.mode === 'navigate'
- && !manifestUrlList.some(url => url === event.request.url);
+ const shouldServeIndexHtml = event.request.mode === 'navigate' && !manifestUrlList.some(url => url === event.request.url);
const request = shouldServeIndexHtml ? 'index.html' : event.request;
+ const shouldCache = offlineAssetsInclude.some(pattern => pattern.test(request.url));
+
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
+ let exception;
+ let fetched;
+ if (!cachedResponse && shouldCache) {
+ console.log("Service worker caching: fetching ", request.url)
+ try {
+ fetched = true;
+ await cache.add(request);
+ cachedResponse = await cache.match(request);
+ } catch (e) {
+ exception = e;
+ console.error("cache.add error: ", e, request.url)
+ }
+ }
+ let consoleLog = {
+ fetched, shouldCache, request, exception, cachedResponse, url: request.url,
+ }
+ Object.keys(consoleLog).forEach(key => consoleLog[key] == null && delete consoleLog[key])
+ if(consoleLog.exception)
+ console.log("Service worker caching: ", consoleLog)
}
return cachedResponse || fetch(event.request);
diff --git a/MatrixUtils.Web/wwwroot/sw-registrator.js b/MatrixUtils.Web/wwwroot/sw-registrator.js
new file mode 100644
index 0000000..94b96b2
--- /dev/null
+++ b/MatrixUtils.Web/wwwroot/sw-registrator.js
@@ -0,0 +1,41 @@
+// source: https://whuysentruit.medium.com/blazor-wasm-pwa-adding-a-new-update-available-notification-d9f65c4ad13
+
+window.updateAvailable = new Promise((resolve, reject) => {
+ if (!('serviceWorker' in navigator)) {
+ const errorMessage = `This browser doesn't support service workers`;
+ console.error(errorMessage);
+ reject(errorMessage);
+ return;
+ }
+
+ navigator.serviceWorker.register('/service-worker.js')
+ .then(registration => {
+ console.info(`Service worker registration successful (scope: ${registration.scope})`);
+
+ // detect updates every minute
+ setInterval(() => {
+ registration.update();
+ }, 5 * 1000); // 60000ms -> check each minute
+
+ registration.onupdatefound = () => {
+ const installingServiceWorker = registration.installing;
+ installingServiceWorker.onstatechange = () => {
+ if (installingServiceWorker.state === 'installed') {
+ resolve(!!navigator.serviceWorker.controller);
+ }
+ }
+ };
+ })
+ .catch(error => {
+ console.error('Service worker registration failed with error:', error);
+ reject(error);
+ });
+});
+
+window.registerForUpdateAvailableNotification = (caller, methodName) => {
+ window.updateAvailable.then(isUpdateAvailable => {
+ if (isUpdateAvailable) {
+ caller.invokeMethodAsync(methodName).then();
+ }
+ });
+};
\ No newline at end of file
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index f7117b1..4c60728 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -11,4 +11,4 @@ BASE_DIR=`pwd`
rm -rf **/bin/Release
cd MatrixUtils.Web
dotnet publish -c Release
-rsync -raP bin/Release/net8.0/publish/wwwroot/ rory.gay:/data/nginx/html_mru/
\ No newline at end of file
+rsync -raP bin/Release/net8.0/publish/wwwroot/ rory.gay:/data/nginx/html_mru/
|