diff options
Diffstat (limited to 'MatrixUtils.Web/Shared')
-rw-r--r-- | MatrixUtils.Web/Shared/InlineUserItem.razor | 2 | ||||
-rw-r--r-- | MatrixUtils.Web/Shared/MainLayout.razor.css | 1 | ||||
-rw-r--r-- | MatrixUtils.Web/Shared/MxcAvatar.razor | 58 | ||||
-rw-r--r-- | MatrixUtils.Web/Shared/MxcImage.razor | 28 | ||||
-rw-r--r-- | MatrixUtils.Web/Shared/NavMenu.razor | 6 | ||||
-rw-r--r-- | MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor | 102 | ||||
-rw-r--r-- | MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor | 60 | ||||
-rw-r--r-- | MatrixUtils.Web/Shared/RoomListItem.razor | 37 | ||||
-rw-r--r-- | MatrixUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor | 2 |
9 files changed, 245 insertions, 51 deletions
diff --git a/MatrixUtils.Web/Shared/InlineUserItem.razor b/MatrixUtils.Web/Shared/InlineUserItem.razor index 9c6608a..c7f16f0 100644 --- a/MatrixUtils.Web/Shared/InlineUserItem.razor +++ b/MatrixUtils.Web/Shared/InlineUserItem.razor @@ -59,7 +59,7 @@ } - ProfileAvatar ??= Homeserver.ResolveMediaUri(User.AvatarUrl); + // ProfileAvatar ??= Homeserver.ResolveMediaUri(User.AvatarUrl); ProfileName ??= User.DisplayName; _semaphoreSlim.Release(); diff --git a/MatrixUtils.Web/Shared/MainLayout.razor.css b/MatrixUtils.Web/Shared/MainLayout.razor.css index 01a5066..924393b 100644 --- a/MatrixUtils.Web/Shared/MainLayout.razor.css +++ b/MatrixUtils.Web/Shared/MainLayout.razor.css @@ -57,6 +57,7 @@ main { .sidebar { width: 250px; + min-width: 250px; height: 100vh; position: sticky; top: 0; diff --git a/MatrixUtils.Web/Shared/MxcAvatar.razor b/MatrixUtils.Web/Shared/MxcAvatar.razor new file mode 100644 index 0000000..09ea790 --- /dev/null +++ b/MatrixUtils.Web/Shared/MxcAvatar.razor @@ -0,0 +1,58 @@ +@using System.Security +@using System.Security.Cryptography +@using Blazored.SessionStorage.JsonConverters +<StreamedImage Stream="@_stream" style="@StyleString"/> + +@code { + private string _mxcUri; + private string _style; + private Stream _stream; + + [Parameter] + public string MxcUri { + get => _mxcUri ?? ""; + set { + if(_mxcUri == value) return; + _mxcUri = value; + UriHasChanged(value); + } + } + + [Parameter] + public bool Circular { get; set; } + + [Parameter] + public int Size { get; set; } = 48; + + [Parameter] + public string SizeUnit { get; set; } = "px"; + + [Parameter] + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + + private string StyleString => $"{(Circular ? "border-radius: 50%;" : "")} width: {Size}{SizeUnit}; height: {Size}{SizeUnit}; object-fit: cover;"; + + private static readonly string Prefix = "mxc://"; + private static readonly int PrefixLength = Prefix.Length; + + private async Task UriHasChanged(string value) { + if (!value.StartsWith(Prefix)) { + // Console.WriteLine($"UriHasChanged: {value} does not start with {Prefix}, passing as resolved URI!!!"); + // ResolvedUri = value; + return; + } + + if (Homeserver is null) { + Console.WriteLine("Homeserver is required for MxcAvatar"); + return; + } + + var uri = value[PrefixLength..].Split('/'); + // Console.WriteLine($"UriHasChanged: {value} {uri[0]}"); + var url = $"/_matrix/media/v3/download/{uri[0]}/{uri[1]}"; + Console.WriteLine($"ResolvedUri: {url}"); + _stream = await Homeserver.ClientHttpClient.GetStreamAsync(url); + StateHasChanged(); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/MxcImage.razor b/MatrixUtils.Web/Shared/MxcImage.razor index e651c3f..e7cb2e0 100644 --- a/MatrixUtils.Web/Shared/MxcImage.razor +++ b/MatrixUtils.Web/Shared/MxcImage.razor @@ -31,7 +31,7 @@ } } - [Parameter] + [CascadingParameter, Parameter] public RemoteHomeserver? Homeserver { get; set; } private string ResolvedUri { @@ -48,19 +48,19 @@ private static readonly int PrefixLength = Prefix.Length; private async Task UriHasChanged(string value) { - 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]}"); - Homeserver = await hsProvider.GetRemoteHomeserver(uri[0]); - } - ResolvedUri = Homeserver.ResolveMediaUri(value); - Console.WriteLine($"ResolvedUri: {ResolvedUri}"); + // 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]}"); + // Homeserver = await hsProvider.GetRemoteHomeserver(uri[0]); + // } + // ResolvedUri = Homeserver.ResolveMediaUri(value); + // Console.WriteLine($"ResolvedUri: {ResolvedUri}"); } // [Parameter] diff --git a/MatrixUtils.Web/Shared/NavMenu.razor b/MatrixUtils.Web/Shared/NavMenu.razor index 770a246..7371e66 100644 --- a/MatrixUtils.Web/Shared/NavMenu.razor +++ b/MatrixUtils.Web/Shared/NavMenu.razor @@ -37,6 +37,12 @@ </div> <div class="nav-item px-3"> + <NavLink class="nav-link" href="PolicyLists"> + <span class="oi oi-ban" aria-hidden="true"></span> Manage policy lists + </NavLink> + </div> + + <div class="nav-item px-3"> <NavLink class="nav-link" href="User/Profile"> <span class="oi oi-person" aria-hidden="true"></span> Manage profile </NavLink> diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor new file mode 100644 index 0000000..11ba18a --- /dev/null +++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor @@ -0,0 +1,102 @@ +@using LibMatrix.EventTypes.Spec.State.Policy +@using System.Reflection +@using ArcaneLibs.Attributes +@using LibMatrix +@using System.Collections.Frozen +@using LibMatrix.EventTypes +@using LibMatrix.RoomTypes +<ModalWindow Title="@("Creating many new " + (PolicyTypes.ContainsKey(MappedType??"") ? PolicyTypes[MappedType!].GetFriendlyNamePluralOrNull()?.ToLower() ?? PolicyTypes[MappedType!].Name : "event"))" + OnCloseClicked="@OnClose" X="60" Y="60" MinWidth="600"> + <span>Policy type:</span> + <select @bind="@MappedType"> + <option>Select a value</option> + @foreach (var (type, mappedType) in PolicyTypes) { + <option value="@type">@mappedType.GetFriendlyName().ToLower()</option> + } + </select><br/> + + <span>Reason:</span> + <FancyTextBox @bind-Value="@Reason"></FancyTextBox><br/> + + <span>Recommendation:</span> + <FancyTextBox @bind-Value="@Recommendation"></FancyTextBox><br/> + + <span>Entities:</span><br/> + <InputTextArea @bind-Value="@Users" style="width: 500px;"></InputTextArea><br/> + + + @* <details> *@ + @* <summary>JSON data</summary> *@ + @* <pre> *@ + @* $1$ @PolicyEvent.ToJson(true, true) #1# *@ + @* </pre> *@ + @* </details> *@ + <LinkButton OnClick="@(() => { OnClose.Invoke(); return Task.CompletedTask; })"> Cancel </LinkButton> + <LinkButton OnClick="@(() => { _ = Save(); return Task.CompletedTask; })"> Save </LinkButton> + +</ModalWindow> + +@code { + + [Parameter] + public required Action OnClose { get; set; } + + [Parameter] + public required Action OnSaved { get; set; } + + [Parameter] + public required GenericRoom Room { get; set; } + + public string Recommendation { get; set; } = "m.ban"; + public string Reason { get; set; } = "spam"; + public string Users { get; set; } = ""; + + private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); + + private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes + .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); + + private string? MappedType { get; set; } + + private async Task Save() { + try { + await DoActualSave(); + } + catch (Exception e) { + Console.WriteLine($"Failed to save: {e}"); + } + } + + private async Task DoActualSave() { + Console.WriteLine($"Saving ---"); + Console.WriteLine($"Users = {Users}"); + var users = Users.Split("\n").Select(x => x.Trim()).Where(x => x.StartsWith('@')).ToList(); + var tasks = users.Select(x => ExecuteBan(Room, x)).ToList(); + await Task.WhenAll(tasks); + + OnSaved.Invoke(); + } + + private async Task ExecuteBan(GenericRoom room, string entity) { + bool success = false; + while (!success) { + try { + var content = Activator.CreateInstance(PolicyTypes[MappedType!]) as PolicyRuleEventContent; + content.Recommendation = Recommendation; + content.Reason = Reason; + content.Entity = entity; + await room.SendStateEventAsync(MappedType!, content.GetDraupnir2StateKey(), content); + success = true; + } + catch (MatrixException e) { + if (e is not { ErrorCode: MatrixException.ErrorCodes.M_FORBIDDEN }) throw; + Console.WriteLine(e); + } + catch (Exception e) { + //ignored + Console.WriteLine(e); + } + } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor index 1bd00d1..fc536c0 100644 --- a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor +++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor @@ -35,39 +35,61 @@ </thead> <tbody> @foreach (var prop in props) { + var isNullable = Nullable.GetUnderlyingType(prop.PropertyType) is not null; <tr> <td style="padding-right: 8px;"> <span>@prop.GetFriendlyName()</span> - @if (Nullable.GetUnderlyingType(prop.PropertyType) is not null) { + @if (Nullable.GetUnderlyingType(prop.PropertyType) is null) { <span style="color: red;">*</span> } </td> @{ var getter = prop.GetGetMethod(); var setter = prop.GetSetMethod(); - } - @switch (Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType) { - case Type t when t == typeof(string): - <FancyTextBox Value="@(getter?.Invoke(PolicyData, null) as string)" ValueChanged="@(e => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"></FancyTextBox> - break; - default: - <p style="color: red;">Unsupported type: @prop.PropertyType</p> - break; + if (getter is null) { + <p style="color: red;">Missing property getter: @prop.Name</p> + } + else { + switch (Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType) { + case Type t when t == typeof(string): + <FancyTextBox Value="@(getter?.Invoke(PolicyData, null) as string)" ValueChanged="@(e => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"></FancyTextBox> + break; + case Type t when t == typeof(DateTime): + if (!isNullable) { + <InputDate TValue="DateTime" Value="@(getter?.Invoke(PolicyData, null) as DateTime? ?? new DateTime())" ValueChanged="@(e => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"></InputDate> + } + else { + var value = getter?.Invoke(PolicyData, null) as DateTime?; + if (value is null) { + <button @onclick="() => { setter?.Invoke(PolicyData, [DateTime.Now]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); }">Add value</button> + } + else { + var notNullValue = Nullable.GetValueRefOrDefaultRef(ref value); + Console.WriteLine($"Value: {value?.ToString() ?? "null"}"); + <InputDate TValue="DateTime" ValueExpression="@(() => notNullValue)" ValueChanged="@(e => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"></InputDate> + <button @onclick="() => { setter?.Invoke(PolicyData, [null]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); }">Remove value</button> + } + } + + break; + default: + <p style="color: red;">Unsupported type: @prop.PropertyType</p> + break; + } + } } </tr> } </tbody> </table> - <br/> - <pre> - @PolicyEvent.ToJson(true, false) - </pre> + <details> + <summary>JSON data</summary> + <pre> + @PolicyEvent.ToJson(true, true) + </pre> + </details> <LinkButton OnClick="@(() => { OnClose.Invoke(); return Task.CompletedTask; })"> Cancel </LinkButton> <LinkButton OnClick="@(() => { OnSave.Invoke(PolicyEvent); return Task.CompletedTask; })"> Save </LinkButton> - @* <span>Target entity: </span> *@ - @* <FancyTextBox @bind-Value="@policyData.Entity"></FancyTextBox><br/> *@ - @* <span>Reason: </span> *@ - @* <FancyTextBox @bind-Value="@policyData.Reason"></FancyTextBox> *@ } else { <p>Policy data is null</p> @@ -102,7 +124,7 @@ .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); private StateEventResponse? _policyEvent; - + private string? MappedType { get => _policyEvent?.Type; set { @@ -110,9 +132,9 @@ PolicyEvent.Type = value; PolicyEvent.TypedContent ??= Activator.CreateInstance(PolicyTypes[value]) as PolicyRuleEventContent; PolicyData = PolicyEvent.TypedContent as PolicyRuleEventContent; + PolicyData.Recommendation ??= "m.ban"; } } } - } \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/RoomListItem.razor b/MatrixUtils.Web/Shared/RoomListItem.razor index bfaa900..248cb59 100644 --- a/MatrixUtils.Web/Shared/RoomListItem.razor +++ b/MatrixUtils.Web/Shared/RoomListItem.razor @@ -7,13 +7,15 @@ <div class="roomListItem @(HasDangerousRoomVersion ? "dangerousRoomVersion" : HasOldRoomVersion ? "oldRoomVersion" : "")" id="@RoomInfo.Room.RoomId"> @if (OwnMemberState != null) { @* Class="@("avatar32" + (OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? " highlightChange" : "") + (ChildContent is not null ? " vcenter" : ""))" *@ - <MxcImage Homeserver="hs" Circular="true" Height="32" Width="32" MxcUri="@(OwnMemberState.AvatarUrl ?? GlobalProfile.AvatarUrl)"/> + @* <MxcImage Homeserver="hs" Circular="true" Height="32" Width="32" MxcUri="@(OwnMemberState.AvatarUrl ?? GlobalProfile.AvatarUrl)"/> *@ + <MxcAvatar Homeserver="Homeserver" Circular="true" Size="32" MxcUri="@(OwnMemberState.AvatarUrl ?? GlobalProfile.AvatarUrl)"/> <span class="centerVertical border75 @(OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? "highlightChange" : "")"> @(OwnMemberState?.DisplayName ?? GlobalProfile?.DisplayName ?? "Loading...") </span> <span class="centerVertical noLeftPadding">-></span> } - <MxcImage Circular="true" Height="32" Width="32" MxcUri="@RoomInfo.RoomIcon" Style="@(ChildContent is not null ? "vertical-align: middle;" : "")"/> + @* <MxcImage Circular="true" Height="32" Width="32" MxcUri="@RoomInfo.RoomIcon" Style="@(ChildContent is not null ? "vertical-align: middle;" : "")"/> *@ + <MxcAvatar Homeserver="Homeserver" Circular="true" Size="32" MxcUri="@RoomInfo.RoomIcon"/> <div class="inlineBlock"> <span class="centerVertical">@RoomInfo.RoomName</span> @if (ChildContent is not null) { @@ -42,8 +44,6 @@ else { } } - - [Parameter] public bool ShowOwnProfile { get; set; } = false; @@ -61,6 +61,9 @@ else { OnParametersSetAsync(); } } + + [Parameter] + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } private bool HasOldRoomVersion { get; set; } = false; private bool HasDangerousRoomVersion { get; set; } = false; @@ -68,20 +71,19 @@ else { private static SemaphoreSlim _semaphoreSlim = new(8); private RoomInfo? _roomInfo; private bool _loadData = false; - private static AuthenticatedHomeserverGeneric? hs { get; set; } private bool _hooked; - + private async Task RoomInfoChanged() { RoomInfo.PropertyChanged += async (_, a) => { if (a.PropertyName == nameof(RoomInfo.CreationEventContent)) { await CheckRoomVersion(); } - + StateHasChanged(); }; } - + // protected override async Task OnParametersSetAsync() { // if (RoomInfo != null) { // if (!_hooked) { @@ -127,21 +129,24 @@ else { protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - hs ??= await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; + // hs ??= await RMUStorage.GetCurrentSessionOrNavigate(); + // if (hs is null) return; + if (Homeserver is null) { + Console.WriteLine($"RoomListItem called without homeserver"); + } await CheckRoomVersion(); } private async Task LoadOwnProfile() { if (!ShowOwnProfile) return; try { - // OwnMemberState ??= (await RoomInfo.GetStateEvent("m.room.member", hs.UserId)).TypedContent as RoomMemberEventContent; - GlobalProfile ??= await hs.GetProfileAsync(hs.UserId); + // OwnMemberState ??= (await RoomInfo.GetStateEvent("m.room.member", hs.UserId)).TypedContent as RoomMemberEventContent; + GlobalProfile ??= await Homeserver.GetProfileAsync(Homeserver.UserId); } catch (MatrixException e) { if (e is { ErrorCode: "M_FORBIDDEN" }) { - Console.WriteLine($"Failed to get profile for {hs.UserId}: {e.Message}"); + Console.WriteLine($"Failed to get profile for {Homeserver.UserId}: {e.Message}"); ShowOwnProfile = false; } else { @@ -151,8 +156,8 @@ else { } private async Task CheckRoomVersion() { - if (RoomInfo?.CreationEventContent is null) return; - + if (RoomInfo?.CreationEventContent is null) return; + var ce = RoomInfo.CreationEventContent; if (int.TryParse(ce.RoomVersion, out var rv)) { if (rv < 10) @@ -163,7 +168,7 @@ else { if (RoomConstants.DangerousRoomVersions.Contains(ce.RoomVersion)) { HasDangerousRoomVersion = true; - // RoomName = "Dangerous room: " + RoomName; + // RoomName = "Dangerous room: " + RoomName; } } diff --git a/MatrixUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor b/MatrixUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor index 81956b0..98b5a6d 100644 --- a/MatrixUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor +++ b/MatrixUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor @@ -15,7 +15,7 @@ } case "m.image": { <i>@currentEventContent.Body</i><br/> - <img src="@Homeserver.ResolveMediaUri(currentEventContent.Url)"> + @* <img src="@Homeserver.ResolveMediaUri(currentEventContent.Url)"> *@ break; } default: { |