about summary refs log tree commit diff
path: root/MatrixUtils.Web/Shared
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixUtils.Web/Shared')
-rw-r--r--MatrixUtils.Web/Shared/InlineUserItem.razor2
-rw-r--r--MatrixUtils.Web/Shared/MainLayout.razor.css1
-rw-r--r--MatrixUtils.Web/Shared/MxcAvatar.razor58
-rw-r--r--MatrixUtils.Web/Shared/MxcImage.razor28
-rw-r--r--MatrixUtils.Web/Shared/NavMenu.razor6
-rw-r--r--MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor102
-rw-r--r--MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor60
-rw-r--r--MatrixUtils.Web/Shared/RoomListItem.razor37
-rw-r--r--MatrixUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor2
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: {