diff options
Diffstat (limited to 'MatrixUtils.Web/Pages/User')
-rw-r--r-- | MatrixUtils.Web/Pages/User/DMManager.razor | 62 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/User/DMSpace.razor | 86 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor | 11 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor | 128 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor | 242 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor | 191 | ||||
-rw-r--r-- | MatrixUtils.Web/Pages/User/Profile.razor | 134 |
7 files changed, 854 insertions, 0 deletions
diff --git a/MatrixUtils.Web/Pages/User/DMManager.razor b/MatrixUtils.Web/Pages/User/DMManager.razor new file mode 100644 index 0000000..df5cd6b --- /dev/null +++ b/MatrixUtils.Web/Pages/User/DMManager.razor @@ -0,0 +1,62 @@ +@page "/User/DirectMessages" +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Responses +@using MatrixUtils.Abstractions +<h3>Direct Messages</h3> +<hr/> + +@foreach (var (targetUser, rooms) in DMRooms) { + <div> + <InlineUserItem User="targetUser"></InlineUserItem> + @foreach (var room in rooms) { + <RoomListItem RoomInfo="room" LoadData="true"></RoomListItem> + } + </div> +} + +@code { + private string? _status; + private AuthenticatedHomeserverGeneric? Homeserver { get; set; } + private Dictionary<UserProfileResponse, List<RoomInfo>> DMRooms { get; set; } = new(); + + public string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + protected override async Task OnInitializedAsync() { + Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + if (Homeserver is null) return; + Status = "Loading global profile..."; + if (Homeserver.WhoAmI?.UserId is null) return; + + Status = "Loading DM list from account data..."; + var dms = await Homeserver.GetAccountDataAsync<Dictionary<string, List<string>>>("m.direct"); + DMRooms.Clear(); + foreach (var (userId, rooms) in dms) { + var roomList = new List<RoomInfo>(); + DMRooms.Add(await Homeserver.GetProfileAsync(userId), roomList); + foreach (var room in rooms) { + var roomInfo = new RoomInfo() { Room = Homeserver.GetRoom(room) }; + roomList.Add(roomInfo); + roomInfo.StateEvents.Add(new() { + Type = RoomNameEventContent.EventId, + TypedContent = new RoomNameEventContent() { + Name = await Homeserver.GetRoom(room).GetNameOrFallbackAsync(4) + }, + RoomId = room, Sender = null, EventId = null + }); + } + StateHasChanged(); + } + + StateHasChanged(); + Status = null; + + await base.OnInitializedAsync(); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/DMSpace.razor b/MatrixUtils.Web/Pages/User/DMSpace.razor new file mode 100644 index 0000000..3751629 --- /dev/null +++ b/MatrixUtils.Web/Pages/User/DMSpace.razor @@ -0,0 +1,86 @@ +@page "/User/DMSpace/Setup" +@using LibMatrix.Homeservers +@using LibMatrix +@using MatrixUtils.LibDMSpace +@using MatrixUtils.LibDMSpace.StateEvents +@using MatrixUtils.Web.Pages.User.DMSpaceStages +<h3>DM Space Management</h3> +<hr/> +<CascadingValue Value="@DmSpace"> + @switch (Stage) { + case -1: + <p>Initialising...</p> + break; + case 0: + <DMSpaceStage0/> + break; + case 1: + <DMSpaceStage1/> + break; + case 2: + <DMSpaceStage2/> + break; + case 3: + <DMSpaceStage3/> + break; + default: + <p>Stage is unknown value: @Stage!</p> + break; + } +</CascadingValue> + +@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 AuthenticatedHomeserverGeneric? Homeserver { get; set; } + + public DMSpaceConfiguration? DmSpaceConfiguration { get; set; } + + [Parameter] + public DMSpace? DmSpace { get; set; } + + protected override async Task OnInitializedAsync() { + if (NavigationManager.Uri.Contains("?stage=")) { + NavigationManager.NavigateTo("/User/DMSpace", true); + } + DmSpace = this; + Homeserver ??= await RMUStorage.GetCurrentSessionOrNavigate(); + if (Homeserver is null) return; + try { + DmSpaceConfiguration = await Homeserver.GetAccountDataAsync<DMSpaceConfiguration>("gay.rory.dm_space"); + var room = Homeserver.GetRoom(DmSpaceConfiguration.DMSpaceId); + await room.GetStateAsync<object>(DMSpaceInfo.EventId); + Stage = 1; + } + catch (MatrixException e) { + if (e.ErrorCode == "M_NOT_FOUND") { + Stage = 0; + DmSpaceConfiguration = new(); + } + else throw; + } + catch (Exception e) { + throw; + } + finally { + StateHasChanged(); + } + await base.OnInitializedAsync(); + } + + protected override async Task OnParametersSetAsync() { + StateHasChanged(); + await base.OnParametersSetAsync(); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor new file mode 100644 index 0000000..49fd5b4 --- /dev/null +++ b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor @@ -0,0 +1,11 @@ +<b> + <u>Welcome to the DM Space tool!</u> +</b> +<p>This wizard will help you set up a DM space.</p> +<p>This is useful for eg. sharing DM rooms across multiple accounts.</p> +<br/> +<LinkButton href="/User/DMSpace?stage=1">Get started</LinkButton> + +@code { + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor new file mode 100644 index 0000000..6131617 --- /dev/null +++ b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor @@ -0,0 +1,128 @@ +@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 +<b> + <u>DM Space setup tool - stage 1: Configure space</u> +</b> +<p>You will need a space to use for DM rooms.</p> +@if (DmSpace is not null) { + <p> + Selected space: + <InputSelect @bind-Value="DmSpace.DmSpaceConfiguration.DMSpaceId"> + @foreach (var (id, name) in spaces) { + <option value="@id">@name</option> + } + </InputSelect> + </p> + <p> + <InputCheckbox @bind-Value="DmSpaceInfo.LayerByUser"></InputCheckbox> + Create sub-spaces per user + </p> +} +else { + <b>Error: DmSpaceConfiguration is null!</b> +} + +<br/> +<LinkButton OnClick="@Execute">Next</LinkButton> + +@if (!string.IsNullOrWhiteSpace(Status)) { + <p>@Status</p> +} + +@code { + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private Dictionary<string, string> spaces = new() { { "", "New space" } }; + private string? _status; + + [CascadingParameter] + public DMSpace? DmSpace { get; set; } + + public DMSpaceInfo? DmSpaceInfo { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + } + + SemaphoreSlim _semaphoreSlim = new(1, 1); + protected override async Task OnParametersSetAsync() { + if (DmSpace is null) + return; + await _semaphoreSlim.WaitAsync(); + DmSpace.DmSpaceConfiguration ??= new(); + if (spaces.Count == 1) { + Status = "Looking for spaces..."; + var userRoomsEnum = DmSpace.Homeserver.GetJoinedRoomsByType("m.space"); + List<GenericRoom> 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.name); + + Status = "Done!"; + } + _semaphoreSlim.Release(); + await base.OnParametersSetAsync(); + } + + private async Task Execute() { + if (string.IsNullOrWhiteSpace(DmSpace.DmSpaceConfiguration.DMSpaceId)) { + var crr = CreateRoomRequest.CreatePrivate(DmSpace.Homeserver, "Direct Messages"); + crr.CreationContentBaseType.Type = "m.space"; + DmSpace.DmSpaceConfiguration.DMSpaceId = (await DmSpace.Homeserver.CreateRoom(crr)).RoomId; + } + await DmSpace.Homeserver!.SetAccountDataAsync(DMSpaceConfiguration.EventId, DmSpace.DmSpaceConfiguration); + var space = DmSpace.Homeserver.GetRoom(DmSpace.DmSpaceConfiguration.DMSpaceId); + await space.SendStateEventAsync(DMSpaceInfo.EventId, DmSpaceInfo); + + NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=2"); + } + + public async Task<(string id, string name)?> GetFeasibleSpaces(GenericRoom room) { + try { + var pls = await room.GetPowerLevelsAsync(); + if (!pls.UserHasStatePermission(DmSpace.Homeserver.WhoAmI.UserId, "m.space.child")) { + Console.WriteLine($"No permission to send m.space.child in {room.RoomId}..."); + return null; + } + var roomName = await room.GetNameAsync(); + Status = $"Found viable space: {roomName}"; + if (string.IsNullOrWhiteSpace(DmSpace.DmSpaceConfiguration.DMSpaceId)) { + try { + var dsi = await DmSpace.Homeserver.GetRoom(room.RoomId).GetStateOrNullAsync<DMSpaceInfo>(DMSpaceInfo.EventId) ?? new DMSpaceInfo(); + if (await room.GetStateOrNullAsync<DMSpaceInfo>(DMSpaceInfo.EventId) is not null && dsi is not null) { + DmSpace.DmSpaceConfiguration.DMSpaceId = room.RoomId; + DmSpaceInfo = dsi; + } + } + catch (MatrixException e) { + if (e.ErrorCode == "M_NOT_FOUND") Console.WriteLine($"{room.RoomId} is not a DM space."); + else throw; + } + } + return (room.RoomId, roomName); + } + 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; + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor new file mode 100644 index 0000000..5a53347 --- /dev/null +++ b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor @@ -0,0 +1,242 @@ +@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 +<b> + <u>DM Space setup tool - stage 2: Fix DM room attribution</u> +</b> +<p>This is just to make sure that your DMs are attributed to the right person!</p> + +@if (!string.IsNullOrWhiteSpace(Status)) { + <p>@Status</p> +} + +@if (DmSpace is not null) { + @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.Id)) { + <InlineUserItem User="@userId"></InlineUserItem> + @foreach (var roomInfo in room) { + <RoomListItem RoomInfo="@roomInfo"> + <LinkButton Round="true" OnClick="@(async () => DmToReassign = roomInfo)">Reassign</LinkButton> + </RoomListItem> + } + } +} +else { + <b>Error: DmSpaceConfiguration is null!</b> +} + +<br/> +<LinkButton OnClick="@Execute">Next</LinkButton> + +@{ + var _offset = 0; +} +@foreach (var (room, usersList) in duplicateDmRooms) { + <ModalWindow Title="Duplicate room found" X="_offset += 30" Y="_offset"> + <p>Found room assigned to multiple users: <RoomListItem RoomInfo="@room"></RoomListItem></p> + <p>Users:</p> + @foreach (var userProfileResponse in usersList) { + <LinkButton OnClick="@(() => SetRoomAssignment(room.Room.RoomId, userProfileResponse.Id))"> + <span>Assign to </span> + <InlineUserItem User="userProfileResponse"></InlineUserItem> + </LinkButton> + <br/> + } + </ModalWindow> +} + +@if (DmToReassign is not null) { + <ModalWindow Title="Re-assign DM" OnCloseClicked="@(() => DmToReassign = null)"> + <RoomListItem RoomInfo="@DmToReassign"></RoomListItem> + @foreach (var userProfileResponse in roomMembers[DmToReassign]) { + <LinkButton OnClick="@(() => SetRoomAssignment(DmToReassign.Room.RoomId, userProfileResponse.Id))"> + <span>Assign to </span> + <InlineUserItem User="userProfileResponse"></InlineUserItem> + </LinkButton> + <br/> + } + </ModalWindow> +} + +@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? DmSpace { get; set; } + + private Dictionary<UserProfileWithId, List<RoomInfo>> dmRooms { get; set; } = new(); + private Dictionary<RoomInfo, List<UserProfileWithId>> duplicateDmRooms { get; set; } = new(); + private Dictionary<RoomInfo, List<UserProfileWithId>> roomMembers { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + } + + SemaphoreSlim _semaphore = new(1, 1); + + protected override async Task OnParametersSetAsync() { + if (DmSpace is null) + return; + await _semaphore.WaitAsync(); + DmToReassign = null; + var hs = DmSpace.Homeserver; + Status = "Loading DM list from account data..."; + var dms = await DmSpace.Homeserver.GetAccountDataAsync<Dictionary<string, List<string>>>("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 DmSpace.Homeserver.SetAccountDataAsync("m.direct", dms); + dmRooms.Clear(); + + Status = "DM list optimised, fetching info..."; + var results = dms.Select(async x => { + var (userId, rooms) = x; + UserProfileWithId userProfile; + try { + var profile = await DmSpace.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<RoomInfo>(); + 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) { + dmRooms.Add(res.userProfile, res.roomList); + // Status = $"Listed {dmRooms.Count} users"; + } + _semaphore.Release(); + var duplicateDmRoomIds = new Dictionary<string, List<UserProfileWithId>>(); + foreach (var (user, rooms) in 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(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<RoomInfo> GetRoomInfo(GenericRoom room) { + var roomInfo = new RoomInfo() { + Room = room + }; + roomMembers[roomInfo] = new(); + 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[roomInfo].Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); + + if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) { + List<string> displayNames = new List<string>(); + foreach (var member in roomMembers[roomInfo]) + 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<List<RoomInfo>> GetRoomInfoForRooms(List<GenericRoom> 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 = DmSpace.Homeserver; + Status = "Loading DM list from account data..."; + var dms = await DmSpace.Homeserver.GetAccountDataAsync<Dictionary<string, List<string>>>("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 DmSpace.Homeserver.SetAccountDataAsync("m.direct", dms); + + duplicateDmRooms.RemoveAll((x, y) => x.Room.RoomId == roomId); + StateHasChanged(); + if (duplicateDmRooms.Count == 0) await OnParametersSetAsync(); + } + + private class UserProfileWithId : UserProfileResponse { + [JsonIgnore] + public string Id { get; set; } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor new file mode 100644 index 0000000..9307f6a --- /dev/null +++ b/MatrixUtils.Web/Pages/User/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 + +<b> + <u>DM Space setup tool - stage 3: Preview space layout</u> +</b> +<p>This gives you a preview of how your settings would impact layout!</p> + +@if (!string.IsNullOrWhiteSpace(Status)) { + <p>@Status</p> +} + +@if (DmSpace is not null) { + @if (dmSpaceInfo is not null && dmSpaceRoomInfo is not null) { + <p> + <InputCheckbox @bind-Value="dmSpaceInfo.LayerByUser"></InputCheckbox> + Create sub-spaces per user + </p> + @if (!dmSpaceInfo.LayerByUser) { + <RoomListItem RoomInfo="@dmSpaceRoomInfo"></RoomListItem> + @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.RoomName)) { + @foreach (var roomInfo in room) { + <div style="margin-left: 32px;"> + <RoomListItem RoomInfo="@roomInfo"></RoomListItem> + </div> + } + } + } + else { + <RoomListItem RoomInfo="@dmSpaceRoomInfo"></RoomListItem> + @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.RoomName)) { + <div style="margin-left: 32px;"> + <RoomListItem RoomInfo="@userId"></RoomListItem> + </div> + @foreach (var roomInfo in room) { + <div style="margin-left: 64px;"> + <RoomListItem RoomInfo="@roomInfo"></RoomListItem> + </div> + } + } + } + } + else { + <b>Error: dmSpaceInfo is null!</b> + } +} +else { + <b>Error: DmSpaceConfiguration is null!</b> +} + +<br/> +<LinkButton OnClick="@Execute">Next</LinkButton> + +@code { + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private string? _status; + + [CascadingParameter] + public DMSpace? DmSpace { get; set; } + + private Dictionary<RoomInfo, List<RoomInfo>> dmRooms { get; set; } = new(); + private DMSpaceInfo? dmSpaceInfo { get; set; } + private RoomInfo? dmSpaceRoomInfo { get; set; } + + protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + } + + SemaphoreSlim _semaphore = new(1, 1); + + protected override async Task OnParametersSetAsync() { + if (DmSpace is null) + return; + await _semaphore.WaitAsync(); + var hs = DmSpace.Homeserver; + var dmSpaceRoom = new DMSpaceRoom(hs, DmSpace.DmSpaceConfiguration.DMSpaceId); + 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 DmSpace.Homeserver.GetAccountDataAsync<Dictionary<string, List<string>>>("m.direct"); + dmRooms.Clear(); + + Status = "DM list optimised, fetching info..."; + var results = dms.Select(async x => { + var (userId, rooms) = x; + UserProfileWithId userProfile; + try { + var profile = await DmSpace.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<RoomInfo>(); + 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); + } + _semaphore.Release(); + Status = null; + await base.OnParametersSetAsync(); + } + + private async Task Execute() { + var hs = DmSpace.Homeserver; + var dmSpaceRoom = new DMSpaceRoom(hs, DmSpace.DmSpaceConfiguration.DMSpaceId); + NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); + } + + private async Task<RoomInfo> GetRoomInfo(GenericRoom room) { + var roomInfo = new RoomInfo() { + Room = room + }; + var roomMembers = new List<UserProfileWithId>(); + 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<string> displayNames = new List<string>(); + 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<List<RoomInfo>> GetRoomInfoForRooms(List<GenericRoom> 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 diff --git a/MatrixUtils.Web/Pages/User/Profile.razor b/MatrixUtils.Web/Pages/User/Profile.razor new file mode 100644 index 0000000..8cffaab --- /dev/null +++ b/MatrixUtils.Web/Pages/User/Profile.razor @@ -0,0 +1,134 @@ +@page "/User/Profile" +@using LibMatrix.Homeservers +@using LibMatrix.EventTypes.Spec.State +@using ArcaneLibs.Extensions +@using LibMatrix.Responses +<h3>Manage Profile - @Homeserver?.WhoAmI?.UserId</h3> +<hr/> + +@if (NewProfile is not null) { + <h4>Profile</h4> + <hr/> + <div> + <img src="@Homeserver.ResolveMediaUri(NewProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> + <div style="display: inline-block; vertical-align: middle;"> + <span>Display name: </span><FancyTextBox @bind-Value="@NewProfile.DisplayName"></FancyTextBox><br/> + <span>Avatar URL: </span><FancyTextBox @bind-Value="@NewProfile.AvatarUrl"></FancyTextBox> + <InputFile OnChange="@AvatarChanged"></InputFile><br/> + <LinkButton OnClick="@(() => UpdateProfile())">Update profile</LinkButton> + <LinkButton OnClick="@(() => UpdateProfile(true))">Update profile (restore room overrides)</LinkButton> + </div> + </div> + @if (!string.IsNullOrWhiteSpace(Status)) { + <p>@Status</p> + } + + <br/> + + @* <details> *@ + <h4>Room profiles<hr></h4> + + @foreach (var (roomId, roomProfile) in RoomProfiles.OrderBy(x => RoomNames.TryGetValue(x.Key, out var _name) ? _name : x.Key)) { + <details class="details-compact"> + <summary style="@(roomProfile.DisplayName == OldProfile.DisplayName && roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")">@(RoomNames.TryGetValue(roomId, out var name) ? name : roomId)</summary> + <img src="@Homeserver.ResolveMediaUri(roomProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> + <div style="display: inline-block; vertical-align: middle;"> + <span>Display name: </span><FancyTextBox BackgroundColor="@(roomProfile.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@roomProfile.DisplayName"></FancyTextBox><br/> + <span>Avatar URL: </span><FancyTextBox BackgroundColor="@(roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@roomProfile.AvatarUrl"></FancyTextBox> + <InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, roomId))"></InputFile><br/> + <LinkButton OnClick="@(() => UpdateRoomProfile(roomId))">Update profile</LinkButton> + </div> + <br/> + @if (!string.IsNullOrWhiteSpace(Status)) { + <p>@Status</p> + } + </details> + <br/> + } + // </details> +} + +@code { + private string? _status = null; + + private AuthenticatedHomeserverGeneric? Homeserver { get; set; } + private UserProfileResponse? NewProfile { get; set; } + private UserProfileResponse? OldProfile { get; set; } + + private string? Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private Dictionary<string, RoomMemberEventContent> RoomProfiles { get; set; } = new(); + private Dictionary<string, string> RoomNames { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + if (Homeserver is null) return; + Status = "Loading global profile..."; + if (Homeserver.WhoAmI?.UserId is null) return; + NewProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)); //.DeepClone(); + OldProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)); //.DeepClone(); + Status = "Loading room profiles..."; + var roomProfiles = Homeserver.GetRoomProfilesAsync(); + await foreach (var (roomId, roomProfile) in roomProfiles) { + // Status = $"Got profile for {roomId}..."; + RoomProfiles[roomId] = roomProfile; //.DeepClone(); + } + + StateHasChanged(); + Status = "Room profiles loaded, loading room names..."; + + var roomNameTasks = RoomProfiles.Keys.Select(x => Homeserver.GetRoom(x)).Select(async x => { + var name = await x.GetNameOrFallbackAsync(); + return new KeyValuePair<string, string?>(x.RoomId, name); + }).ToAsyncEnumerable(); + + await foreach (var (roomId, roomName) in roomNameTasks) { + // Status = $"Got room name for {roomId}: {roomName}"; + RoomNames[roomId] = roomName; + } + + StateHasChanged(); + Status = null; + + await base.OnInitializedAsync(); + } + + private async Task AvatarChanged(InputFileChangeEventArgs arg) { + var res = await Homeserver.UploadFile(arg.File.Name, arg.File.OpenReadStream(Int64.MaxValue), arg.File.ContentType); + Console.WriteLine(res); + NewProfile.AvatarUrl = res; + StateHasChanged(); + } + + private async Task UpdateProfile(bool restoreRoomProfiles = false) { + Status = "Busy processing global profile update, please do not leave this page..."; + StateHasChanged(); + await Homeserver.UpdateProfileAsync(NewProfile, restoreRoomProfiles); + Status = null; + StateHasChanged(); + await OnInitializedAsync(); + } + + private async Task RoomAvatarChanged(InputFileChangeEventArgs arg, string roomId) { + var res = await Homeserver.UploadFile(arg.File.Name, arg.File.OpenReadStream(Int64.MaxValue), arg.File.ContentType); + Console.WriteLine(res); + RoomProfiles[roomId].AvatarUrl = res; + StateHasChanged(); + } + + private async Task UpdateRoomProfile(string roomId) { + Status = "Busy processing room profile update, please do not leave this page..."; + StateHasChanged(); + var room = Homeserver.GetRoom(roomId); + await room.SendStateEventAsync("m.room.member", Homeserver.WhoAmI.UserId, RoomProfiles[roomId]); + Status = null; + StateHasChanged(); + } + +} \ No newline at end of file |