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: {
|