From 41c5a84dacfd036b8d8f01f72226ac5a519995e3 Mon Sep 17 00:00:00 2001 From: Rory& Date: Tue, 14 May 2024 17:49:09 +0200 Subject: Organise tools somewhat, set proper icons for nav menu --- MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor | 104 +++++++++ .../Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor | 11 + .../Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor | 151 +++++++++++++ .../Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor | 240 +++++++++++++++++++++ .../Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor | 191 ++++++++++++++++ 5 files changed, 697 insertions(+) create mode 100644 MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor create mode 100644 MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor create mode 100644 MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor create mode 100644 MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor create mode 100644 MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor (limited to 'MatrixUtils.Web/Pages/Labs/DMSpace') diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor new file mode 100644 index 0000000..c0dc8a6 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor @@ -0,0 +1,104 @@ +@page "/Labs/DMSpace/Setup" +@using LibMatrix +@using LibMatrix.Responses +@using MatrixUtils.Abstractions +@using MatrixUtils.LibDMSpace +@using MatrixUtils.LibDMSpace.StateEvents +@using MatrixUtils.Web.Pages.Labs.DMSpace.DMSpaceStages +@using System.Text.Json.Serialization +

DM Space Management

+
+ + @switch (Stage) { + case -1: +

Initialising...

+ break; + case 0: + + break; + case 1: + + break; + case 2: + + break; + case 3: + + break; + default: +

Stage is unknown value: @Stage!

+ break; + } +
+ +@code { + private int _stage = -1; + + [Parameter, SupplyParameterFromQuery(Name = "stage")] + public int Stage { + get => _stage; + set { + _stage = value; + Console.WriteLine($"Stage is now {value}"); + StateHasChanged(); + } + } + + + public DMSpace? DMSpaceRootPage { get; set; } + + protected override async Task OnInitializedAsync() { + if (NavigationManager.Uri.Contains("?stage=")) { + NavigationManager.NavigateTo(NavigationManager.Uri.Replace("stage=", ""), true); //"/User/DMSpace/Setup" + } + DMSpaceRootPage = this; + SetupData.Homeserver ??= await RMUStorage.GetCurrentSessionOrNavigate(); + if (SetupData.Homeserver is null) return; + try { + SetupData.DmSpaceConfiguration = await SetupData.Homeserver.GetAccountDataAsync("gay.rory.dm_space"); + var room = SetupData.Homeserver.GetRoom(SetupData.DmSpaceConfiguration.DMSpaceId); + await room.GetStateAsync(DMSpaceInfo.EventId); + Stage = 1; + } + catch (MatrixException e) { + if (e.ErrorCode is "M_NOT_FOUND" or "M_FORBIDDEN") { + Stage = 0; + SetupData.DmSpaceConfiguration = new(); + } + else throw; + } + finally { + StateHasChanged(); + } + await base.OnInitializedAsync(); + } + + protected override async Task OnParametersSetAsync() { + StateHasChanged(); + await base.OnParametersSetAsync(); + } + + public DMSpaceSetupData SetupData { get; set; } = new(); + + public class DMSpaceSetupData { + + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + + public DMSpaceConfiguration? DmSpaceConfiguration { get; set; } + + public DMSpaceInfo? DmSpaceInfo { get; set; } = new(); + + public Dictionary? Spaces; + + public Dictionary>? DMRooms; + + public RoomInfo? DMSpaceRoomInfo { get; set; } + + + public class UserProfileWithId : UserProfileResponse { + [JsonIgnore] + public string Id { get; set; } + } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor new file mode 100644 index 0000000..5f6508c --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor @@ -0,0 +1,11 @@ + + Welcome to the DM Space tool! + +

This wizard will help you set up a DM space.

+

This is useful for eg. sharing DM rooms across multiple accounts.

+
+Get started + +@code { + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor new file mode 100644 index 0000000..2176467 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor @@ -0,0 +1,151 @@ +@using LibMatrix.Homeservers +@using LibMatrix.RoomTypes +@using LibMatrix +@using LibMatrix.Responses +@using MatrixUtils.LibDMSpace +@using MatrixUtils.LibDMSpace.StateEvents +@using Microsoft.Extensions.Primitives +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State +@using MatrixUtils.Abstractions + + DM Space setup tool - stage 1: Configure space + +

You will need a space to use for DM rooms.

+@if (SetupData is not null) { + if (SetupData.Spaces is not null) { +

+ Selected space: + + + @foreach (var (id, roomInfo) in SetupData.Spaces) { + + } + +

+

+ + Create sub-spaces per user +

+ +
+ Disband + Next + } + else { +

Discovering spaces, please wait...

+ } +} +else { + Error: Setup data is null! +} + + +@if (!string.IsNullOrWhiteSpace(Status)) { +

@Status

+} + +@code { + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private string? _status; + + [CascadingParameter] + public DMSpace.DMSpaceSetupData SetupData { get; set; } + + SemaphoreSlim _semaphoreSlim = new(1, 1); + + protected override async Task OnInitializedAsync() { + if (SetupData is null) + return; + + await _semaphoreSlim.WaitAsync(); + + Dictionary spaces = []; + SetupData.DmSpaceConfiguration ??= new(); + + Status = "Looking for spaces..."; + var userRoomsEnum = SetupData.Homeserver!.GetJoinedRoomsByType("m.space"); + + List userRooms = new(); + await foreach (var room in userRoomsEnum) { + userRooms.Add(room); + } + + var roomChecks = userRooms.Select(GetFeasibleSpaces).ToAsyncEnumerable(); + await foreach (var room in roomChecks) + if (room.HasValue) + spaces.TryAdd(room.Value.id, room.Value.roomInfo); + + SetupData.Spaces = spaces; + + Status = "Done!"; + _semaphoreSlim.Release(); + await base.OnParametersSetAsync(); + } + + private async Task Execute() { + if (string.IsNullOrWhiteSpace(SetupData!.DmSpaceConfiguration!.DMSpaceId)) { + var createRoomRequest = CreateRoomRequest.CreatePrivate(SetupData.Homeserver!, "Direct Messages"); + createRoomRequest.CreationContentBaseType.Type = "m.space"; + SetupData.DmSpaceConfiguration.DMSpaceId = (await SetupData.Homeserver!.CreateRoom(createRoomRequest)).RoomId; + } + + await SetupData.Homeserver!.SetAccountDataAsync(DMSpaceConfiguration.EventId, SetupData.DmSpaceConfiguration); + var space = SetupData.Homeserver.GetRoom(SetupData.DmSpaceConfiguration.DMSpaceId); + await space.SendStateEventAsync(DMSpaceInfo.EventId, SetupData.DmSpaceInfo); + SetupData.DMSpaceRoomInfo = new RoomInfo(space); + await SetupData.DMSpaceRoomInfo.FetchAllStateAsync(); + + NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=2"); + } + + public async Task<(string id, RoomInfo roomInfo)?> GetFeasibleSpaces(GenericRoom room) { + try { + var ri = new RoomInfo(room); + + await foreach(var evt in room.GetFullStateAsync()) + ri.StateEvents.Add(evt); + + var powerLevels = (await ri.GetStateEvent(RoomPowerLevelEventContent.EventId)).TypedContent as RoomPowerLevelEventContent; + if (!powerLevels.UserHasStatePermission(SetupData.Homeserver.WhoAmI.UserId, SpaceChildEventContent.EventId)) { + Console.WriteLine($"No permission to send m.space.child in {room.RoomId}..."); + return null; + } + + Status = $"Found viable space: {ri.RoomName}"; + if (!string.IsNullOrWhiteSpace(SetupData.DmSpaceConfiguration!.DMSpaceId)) { + if (await room.GetStateOrNullAsync(DMSpaceInfo.EventId) is { } dsi) { + SetupData.DmSpaceConfiguration.DMSpaceId = room.RoomId; + SetupData.DmSpaceInfo = dsi; + Console.WriteLine(dsi.ToJson(ignoreNull: true)); + } + } + + if (ri.RoomName == room.RoomId) + ri.RoomName = await room.GetNameOrFallbackAsync(); + + return (room.RoomId, ri); + } + catch (MatrixException e) { + if (e.ErrorCode == "M_NOT_FOUND") Console.WriteLine($"m.room.power_levels does not exist in {room.RoomId}!!!"); + else throw; + } + + return null; + } + + private async Task Disband() { + var space = new DMSpaceRoom(SetupData.Homeserver, SetupData.DmSpaceConfiguration.DMSpaceId); + await space.DisbandDMSpace(); + NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor new file mode 100644 index 0000000..a70e9c5 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor @@ -0,0 +1,240 @@ +@using LibMatrix.Homeservers +@using LibMatrix.RoomTypes +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +@using MatrixUtils.LibDMSpace +@using MatrixUtils.LibDMSpace.StateEvents +@using ArcaneLibs.Extensions +@using System.Text.Json.Serialization +@using MatrixUtils.Abstractions + + DM Space setup tool - stage 2: Fix DM room attribution + +

This is just to make sure that your DMs are attributed to the right person!

+ +@if (!string.IsNullOrWhiteSpace(Status)) { +

@Status

+} + +@if (SetupData is not null) { + if (SetupData.DMRooms is { Count: > 0 }) { + @foreach (var (userId, room) in SetupData.DMRooms.OrderBy(x => x.Key.Id)) { + + @foreach (var roomInfo in room) { + + Reassign + + } + } + } + else { +

DM room list is loading, please wait...

+ } +} +else { + Error: DMSpaceRootPage is null! +} + +
+Next + +@{ + var _offset = 0; +} +@foreach (var (room, usersList) in duplicateDmRooms) { + +

Found room assigned to multiple users:

+

Users:

+ @foreach (var userProfileResponse in usersList) { + + Assign to + + +
+ } +
+} + +@if (DmToReassign is not null) { + + + @foreach (var userProfileResponse in roomMembers[DmToReassign]) { + + Assign to + + +
+ } +
+} + +@code { + + private string newMxid { get; set; } = ""; + + private RoomInfo? DmToReassign { + get => _dmToReassign; + set { + _dmToReassign = value; + StateHasChanged(); + } + } + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private string? _status; + private RoomInfo? _dmToReassign; + + [CascadingParameter] + public DMSpace.DMSpaceSetupData SetupData { get; set; } + + private Dictionary> duplicateDmRooms { get; set; } = new(); + private Dictionary> roomMembers { get; set; } = new(); + + SemaphoreSlim _semaphore = new(1, 1); + + protected override async Task OnInitializedAsync() { + if (SetupData is null) + return; + await _semaphore.WaitAsync(); + DmToReassign = null; + var hs = SetupData.Homeserver; + Status = "Loading DM list from account data..."; + var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); + Status = "Optimising DM list from account data..."; + var joinedRooms = (await hs.GetJoinedRooms()).Select(x => x.RoomId).ToList(); + foreach (var (user, rooms) in dms) { + for (var i = rooms.Count - 1; i >= 0; i--) { + var roomId = rooms[i]; + if (!joinedRooms.Contains(roomId)) + rooms.RemoveAt(i); + } + + dms[user] = rooms.Distinct().ToList(); + } + + dms.RemoveAll((x, y) => y is { Count: 0 }); + await SetupData.Homeserver.SetAccountDataAsync("m.direct", dms); + + Status = "DM list optimised, fetching info..."; + + SetupData.DMRooms = new Dictionary>(); + + var results = dms.Select(async x => { + var (userId, rooms) = x; + DMSpace.DMSpaceSetupData.UserProfileWithId userProfile; + try { + var profile = await SetupData.Homeserver.GetProfileAsync(userId); + userProfile = new() { + AvatarUrl = profile.AvatarUrl, + Id = userId, + DisplayName = profile.DisplayName + }; + } + catch { + userProfile = new() { + AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", + DisplayName = userId, + Id = userId + }; + } + + var roomList = new List(); + var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); + await foreach (var result in tasks) + roomList.Add(result); + return (userProfile, roomList); + // StateHasChanged(); + }).ToAsyncEnumerable(); + await foreach (var res in results) { + SetupData.DMRooms.Add(res.userProfile, res.roomList); + // Status = $"Listed {dmRooms.Count} users"; + } + + _semaphore.Release(); + var duplicateDmRoomIds = new Dictionary>(); + foreach (var (user, rooms) in SetupData.DMRooms) { + foreach (var roomInfo in rooms) { + if (!duplicateDmRoomIds.ContainsKey(roomInfo.Room.RoomId)) + duplicateDmRoomIds.Add(roomInfo.Room.RoomId, new()); + duplicateDmRoomIds[roomInfo.Room.RoomId].Add(user); + } + } + + duplicateDmRoomIds.RemoveAll((x, y) => y.Count == 1); + foreach (var (roomId, users) in duplicateDmRoomIds) { + duplicateDmRooms.Add(SetupData.DMRooms.First(x => x.Value.Any(x => x.Room.RoomId == roomId)).Value.First(x => x.Room.RoomId == roomId), users); + } + + // StateHasChanged(); + Status = null; + await base.OnParametersSetAsync(); + } + + private async Task Execute() { + NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); + } + + private async Task GetRoomInfo(GenericRoom room) { + var roomInfo = new RoomInfo(room); + await roomInfo.FetchAllStateAsync(); + roomMembers[roomInfo] = new(); + // roomInfo.CreationEventContent = await room.GetCreateEventAsync(); + + if(roomInfo.RoomName == room.RoomId) + try { + roomInfo.RoomName = await room.GetNameOrFallbackAsync(); + } + catch { } + + var membersEnum = room.GetMembersEnumerableAsync(true); + await foreach (var member in membersEnum) + if (member.TypedContent is RoomMemberEventContent memberEvent) + roomMembers[roomInfo].Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); + + try { + string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; + if (room is not null) + roomInfo.RoomIcon = roomIcon; + } + catch { } + + return roomInfo; + } + + private async Task> GetRoomInfoForRooms(List rooms) { + var tasks = rooms.Select(GetRoomInfo).ToList(); + await Task.WhenAll(tasks); + return tasks.Select(x => x.Result).ToList(); + } + + private async Task SetRoomAssignment(string roomId, string userId) { + var hs = SetupData.Homeserver; + Status = "Loading DM list from account data..."; + var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); + Status = "Updating DM list from account data..."; + + foreach (var (user, rooms) in dms) { + rooms.RemoveAll(x => x == roomId); + dms[user] = rooms.Distinct().ToList(); + } + + if (!dms.ContainsKey(userId)) + dms.Add(userId, new()); + dms[userId].Add(roomId); + dms.RemoveAll((x, y) => y is { Count: 0 }); + await SetupData.Homeserver.SetAccountDataAsync("m.direct", dms); + + duplicateDmRooms.RemoveAll((x, y) => x.Room.RoomId == roomId); + StateHasChanged(); + if (duplicateDmRooms.Count == 0) await OnParametersSetAsync(); + } + +} diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor new file mode 100644 index 0000000..865e956 --- /dev/null +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor @@ -0,0 +1,191 @@ +@using LibMatrix.Homeservers +@using LibMatrix.RoomTypes +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +@using MatrixUtils.LibDMSpace +@using MatrixUtils.LibDMSpace.StateEvents +@using ArcaneLibs.Extensions +@using System.Text.Json.Serialization +@using MatrixUtils.Abstractions + + + DM Space setup tool - stage 3: Preview space layout + +

This gives you a preview of how your settings would impact layout!

+ +@if (!string.IsNullOrWhiteSpace(Status)) { +

@Status

+} + +@if (SetupData is not null) { + @if (SetupData.DMSpaceRoomInfo is not null) { +

+ + Create sub-spaces per user +

+ @if (!SetupData.DmSpaceInfo.LayerByUser) { + + @foreach (var (userId, room) in SetupData.DMRooms.OrderBy(x => x.Key.DisplayName)) { + @foreach (var roomInfo in room) { +
+ +
+ } + } + } + else { + + @foreach (var (user, room) in SetupData.DMRooms.OrderBy(x => x.Key.DisplayName)) { +
+ @{ + RoomInfo fakeRoom = new(SetupData.DMSpaceRoomInfo.Room) { + RoomName = user.DisplayName ?? user.Id, + RoomIcon = user.AvatarUrl + }; + } + +
+ @foreach (var roomInfo in room) { +
+ +
+ } + } + } + } + else { + Error: SetupData.DMSpaceRoomInfo is null! + } +} +else { + Error: DMSpaceRootPageConfiguration is null! +} + +
+Next + +@code { + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private string? _status; + + [CascadingParameter] + public DMSpace.DMSpaceSetupData SetupData { get; set; } + + SemaphoreSlim _semaphore = new(1, 1); + + protected override async Task OnInitializedAsync() { + if (SetupData is null) + return; + await _semaphore.WaitAsync(); + var hs = SetupData.Homeserver; + // var dmSpaceRoom = new DMSpaceRoom(hs, SetupData.DmSpaceConfiguration.DMSpaceId); + // SetupData. + // dmSpaceRoomInfo = new() { + // RoomName = await dmSpaceRoom.GetNameAsync(), + // CreationEventContent = await dmSpaceRoom.GetCreateEventAsync(), + // RoomIcon = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", + // Room = dmSpaceRoom + // }; + // dmSpaceInfo = await dmSpaceRoom.GetDMSpaceInfo(); + // Status = "Loading DM list from account data..."; + // var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); + + Status = "DM list optimised, fetching info..."; + // var results = dms.Select(async x => { + // var (userId, rooms) = x; + // UserProfileWithId userProfile; + // try { + // var profile = await SetupData.Homeserver.GetProfileAsync(userId); + // userProfile = new() { + // AvatarUrl = profile.AvatarUrl, + // Id = userId, + // DisplayName = profile.DisplayName + // }; + // } + // catch { + // userProfile = new() { + // AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", + // DisplayName = userId, + // Id = userId + // }; + // } + // var roomList = new List(); + // var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); + // await foreach (var result in tasks) + // roomList.Add(result); + // return (userProfile, roomList); + // }).ToAsyncEnumerable(); + // await foreach (var res in results) { + // dmRooms.Add(new RoomInfo() { + // Room = dmSpaceRoom, + // RoomIcon = res.userProfile.AvatarUrl, + // RoomName = res.userProfile.DisplayName, + // CreationEventContent = await dmSpaceRoom.GetCreateEventAsync() + // }, res.roomList); + // } + await SetupData.DMSpaceRoomInfo!.FetchAllStateAsync(); + _semaphore.Release(); + Status = null; + await base.OnParametersSetAsync(); + } + + private async Task Execute() { + var hs = SetupData.Homeserver; + var dmSpaceRoom = new DMSpaceRoom(hs, SetupData.DmSpaceConfiguration!.DMSpaceId!); + await dmSpaceRoom.ImportNativeDMs(); + NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); + } + + private async Task GetRoomInfo(GenericRoom room) { + var roomInfo = new RoomInfo(room); + var roomMembers = new List(); + roomInfo.CreationEventContent = await room.GetCreateEventAsync(); + try { + roomInfo.RoomName = await room.GetNameAsync(); + } + catch { } + + var membersEnum = room.GetMembersEnumerableAsync(true); + await foreach (var member in membersEnum) + if (member.TypedContent is RoomMemberEventContent memberEvent) + roomMembers.Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); + + if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) { + List displayNames = new List(); + foreach (var member in roomMembers) + if (!string.IsNullOrWhiteSpace(member.DisplayName)) + displayNames.Add(member.DisplayName); + roomInfo.RoomName = string.Join(", ", displayNames); + } + + try { + string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; + if (room is not null) + roomInfo.RoomIcon = roomIcon; + } + catch { } + + return roomInfo; + } + + private async Task> GetRoomInfoForRooms(List rooms) { + var tasks = rooms.Select(GetRoomInfo).ToList(); + await Task.WhenAll(tasks); + return tasks.Select(x => x.Result).ToList(); + } + + private class UserProfileWithId : UserProfileResponse { + [JsonIgnore] + public string Id { get; set; } + } + +} \ No newline at end of file -- cgit 1.5.1