diff --git a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
index 502fe5b..be085c1 100644
--- a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
+++ b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
@@ -1,7 +1,9 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
+using System.Text.Json.Nodes;
using MatrixRoomUtils.Core.Interfaces;
+using MatrixRoomUtils.Core.Responses;
namespace MatrixRoomUtils.Core;
@@ -72,6 +74,17 @@ public class AuthenticatedHomeServer : IHomeServer
}
+ public async Task<Room> CreateRoom(CreateRoomRequest creationEvent)
+ {
+ var res = await _httpClient.PostAsJsonAsync("/_matrix/client/r0/createRoom", creationEvent);
+ if (!res.IsSuccessStatusCode)
+ {
+ Console.WriteLine($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
+ throw new InvalidDataException($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
+ }
+
+ return await GetRoom((await res.Content.ReadFromJsonAsync<JsonObject>())!["room_id"]!.ToString()!);
+ }
@@ -83,12 +96,6 @@ public class AuthenticatedHomeServer : IHomeServer
{
_authenticatedHomeServer = authenticatedHomeServer;
}
-
-
-
-
-
-
-
}
+
}
diff --git a/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs b/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs
new file mode 100644
index 0000000..cce71dd
--- /dev/null
+++ b/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs
@@ -0,0 +1,15 @@
+namespace MatrixRoomUtils.Core.Extensions;
+
+public static class DictionaryExtensions
+{
+ public static bool ChangeKey<TKey, TValue>(this IDictionary<TKey, TValue> dict,
+ TKey oldKey, TKey newKey)
+ {
+ TValue value;
+ if (!dict.Remove(oldKey, out value))
+ return false;
+
+ dict[newKey] = value; // or dict.Add(newKey, value) depending on ur comfort
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
index 9f7bfee..bae55e7 100644
--- a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
+++ b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
@@ -93,7 +93,7 @@ public class IHomeServer
{
while (_profileCache[mxid] == null)
{
- Console.WriteLine($"Waiting for profile cache for {mxid}, currently {_profileCache[mxid]?.ToJson()} within {_profileCache.Count} profiles...");
+ Console.WriteLine($"Waiting for profile cache for {mxid}, currently {_profileCache[mxid]?.ToJson() ?? "null"} within {_profileCache.Count} profiles...");
await Task.Delay(Random.Shared.Next(50, 500));
}
return _profileCache[mxid];
diff --git a/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
index 6949b1a..5df99f7 100644
--- a/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
+++ b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
@@ -2,6 +2,7 @@ using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
+using MatrixRoomUtils.Core.Extensions;
namespace MatrixRoomUtils.Core.Responses;
@@ -26,6 +27,22 @@ public class CreateRoomRequest
/// For use only when you can't use the CreationContent property
/// </summary>
+ public StateEvent this[string event_type, string event_key = ""]
+ {
+ get => InitialState.First(x => x.Type == event_type && x.StateKey == event_key);
+ set
+ {
+ var stateEvent = InitialState.FirstOrDefault(x => x.Type == event_type && x.StateKey == event_key);
+ if (stateEvent == null)
+ {
+ InitialState.Add(value);
+ }
+ else
+ {
+ InitialState[InitialState.IndexOf(stateEvent)] = value;
+ }
+ }
+ }
//extra properties
[JsonIgnore]
@@ -116,52 +133,118 @@ public class CreateRoomRequest
}
}
- [JsonIgnore]
- public string GuestAccess
+ // [JsonIgnore]
+ // public string GuestAccess
+ // {
+ // get
+ // {
+ // var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.guest_access");
+ // if (stateEvent == null)
+ // {
+ // InitialState.Add(new StateEvent()
+ // {
+ // Type = "m.room.guest_access",
+ // Content = new JsonObject()
+ // {
+ // ["guest_access"] = "can_join"
+ // }
+ // });
+ // return "can_join";
+ // }
+ //
+ // return stateEvent.ContentAsJsonNode["guest_access"].GetValue<string>();
+ // }
+ // set
+ // {
+ // var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.guest_access");
+ // if (stateEvent == null)
+ // {
+ // InitialState.Add(new StateEvent()
+ // {
+ // Type = "m.room.guest_access",
+ // Content = new JsonObject()
+ // {
+ // ["guest_access"] = value
+ // }
+ // });
+ // }
+ // else
+ // {
+ // var v = stateEvent.ContentAsJsonNode;
+ // v["guest_access"] = value;
+ // stateEvent.ContentAsJsonNode = v;
+ // }
+ // }
+ // }
+
+ public ServerACL ServerACLs
{
get
{
- var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.guest_access");
+ var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.server_acl");
if (stateEvent == null)
{
InitialState.Add(new StateEvent()
{
- Type = "m.room.guest_access",
+ Type = "m.room.server_acl",
Content = new JsonObject()
{
- ["guest_access"] = "can_join"
+ ["allow"] = new JsonArray()
+ {
+ "*"
+ },
+ ["deny"] = new JsonArray()
}
});
- return "can_join";
+ return new ServerACL()
+ {
+ Allow = new List<string>()
+ {
+ "*"
+ },
+ Deny = new List<string>(),
+ AllowIpLiterals = true
+ };
}
-
- return stateEvent.ContentAsJsonNode["guest_access"].GetValue<string>();
+ return new ServerACL()
+ {
+ Allow = JsonSerializer.Deserialize<List<string>>(stateEvent.ContentAsJsonNode["allow"]),
+ Deny = JsonSerializer.Deserialize<List<string>>(stateEvent.ContentAsJsonNode["deny"]),
+ AllowIpLiterals = true
+ };
}
set
{
- var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.guest_access");
+ Console.WriteLine($"Setting server acl to {value.ToJson()}");
+ var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.server_acl");
if (stateEvent == null)
{
InitialState.Add(new StateEvent()
{
- Type = "m.room.guest_access",
+ Type = "m.room.server_acl",
Content = new JsonObject()
{
- ["guest_access"] = value
+ ["allow"] = JsonArray.Parse(JsonSerializer.Serialize(value.Allow)),
+ ["deny"] = JsonArray.Parse(JsonSerializer.Serialize(value.Deny))
+ ["allow_ip_literals"] = value.AllowIpLiterals
}
});
}
else
{
var v = stateEvent.ContentAsJsonNode;
- v["guest_access"] = value;
+ v["allow"] = JsonArray.Parse(JsonSerializer.Serialize(value.Allow));
+ v["deny"] = JsonArray.Parse(JsonSerializer.Serialize(value.Deny));
+ v["allow_ip_literals"] = value.AllowIpLiterals;
stateEvent.ContentAsJsonNode = v;
+ Console.WriteLine($"v={v.ToJson()}");
+ Console.WriteLine($"stateEvent.ContentAsJsonNode={stateEvent.ContentAsJsonNode.ToJson()}");
}
}
}
- [JsonIgnore] public CreationContentBaseType _creationContentBaseType;
+ [JsonIgnore] public CreationContentBaseType _creationContentBaseType;
public CreateRoomRequest() => _creationContentBaseType = new(this);
@@ -214,4 +297,11 @@ public class PowerLevelEvent
public class NotificationsPL
{
[JsonPropertyName("room")] public int Room { get; set; } = 50;
+}
+
+public class ServerACL
+{
+ [JsonPropertyName("allow")] public List<string> Allow { get; set; } // = null!;
+ [JsonPropertyName("deny")] public List<string> Deny { get; set; } // = null!;
+ [JsonPropertyName("allow_ip_literals")] public bool AllowIpLiterals { get; set; } // = false;
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEvent.cs b/MatrixRoomUtils.Core/StateEvent.cs
index 2201587..f98d963 100644
--- a/MatrixRoomUtils.Core/StateEvent.cs
+++ b/MatrixRoomUtils.Core/StateEvent.cs
@@ -6,15 +6,12 @@ namespace MatrixRoomUtils.Core;
public class StateEvent
{
- [JsonPropertyName("content")]
- public dynamic Content { get; set; } = new{};
- [JsonPropertyName("state_key")]
- public string? StateKey { get; set; }
- [JsonPropertyName("type")]
- public string Type { get; set; }
- [JsonPropertyName("replaces_state")]
- public string? ReplacesState { get; set; }
-
+ [JsonPropertyName("content")] public dynamic Content { get; set; } = new { };
+
+ [JsonPropertyName("state_key")] public string StateKey { get; set; } = "";
+ [JsonPropertyName("type")] public string Type { get; set; }
+ [JsonPropertyName("replaces_state")] public string? ReplacesState { get; set; }
+
//extra properties
[JsonIgnore]
public JsonNode ContentAsJsonNode
@@ -22,17 +19,24 @@ public class StateEvent
get => JsonSerializer.SerializeToNode(Content);
set => Content = value;
}
+
+ public StateEvent<T> As<T>() where T : class
+ {
+ return (StateEvent<T>)this;
+ }
}
public class StateEvent<T> : StateEvent where T : class
{
- public new T content { get; set; }
-
-
- [JsonIgnore]
- public new JsonNode ContentAsJsonNode
+ public StateEvent()
{
- get => JsonSerializer.SerializeToNode(Content);
- set => Content = value.Deserialize<T>();
+ //import base content if not an empty object
+ if (base.Content.GetType() == typeof(T))
+ {
+ Console.WriteLine($"StateEvent<{typeof(T)}> created with base content of type {base.Content.GetType()}. Importing base content.");
+ Content = base.Content;
+ }
}
+ [JsonPropertyName("content")]
+ public T Content { get; set; }
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs b/MatrixRoomUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs
new file mode 100644
index 0000000..2bba01c
--- /dev/null
+++ b/MatrixRoomUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs
@@ -0,0 +1,110 @@
+using MatrixRoomUtils.Core;
+using MatrixRoomUtils.Core.Responses;
+
+namespace MatrixRoomUtils.Web.Classes.RoomCreationTemplates;
+
+public class DefaultRoomCreationTemplate : IRoomCreationTemplate
+{
+ public string Name => "Default";
+ public CreateRoomRequest CreateRoomRequest
+ {
+ get
+ {
+ return new()
+ {
+ Name = "My new room",
+ RoomAliasName = "myroom",
+ InitialState = new()
+ {
+ new()
+ {
+ Type = "m.room.history_visibility",
+ Content = new
+ {
+ history_visibility = "world_readable"
+ }
+ },
+new StateEvent<Pages.RoomManager.RoomManagerCreateRoom.GuestAccessContent>
+{
+ Type = "m.room.guest_access",
+ Content = new()
+ {
+ GuestAccess = "can_join"
+ }
+},
+ new()
+ {
+ Type = "m.room.join_rules",
+ Content = new
+ {
+ join_rule = "public"
+ }
+ },
+ new()
+ {
+ Type = "m.room.server_acl",
+ Content = new
+ {
+ allow = new[] { "*" },
+ deny = Array.Empty<string>(),
+ allow_ip_literals = false
+ }
+ },
+ new()
+ {
+ Type = "m.room.avatar",
+ Content = new
+ {
+ url = "mxc://feline.support/UKNhEyrVsrAbYteVvZloZcFj"
+ }
+ }
+ },
+ Visibility = "public",
+ PowerLevelContentOverride = new()
+ {
+ UsersDefault = 0,
+ EventsDefault = 100,
+ StateDefault = 50,
+ Invite = 0,
+ Redact = 50,
+ Kick = 50,
+ Ban = 50,
+ NotificationsPl = new()
+ {
+ Room = 50
+ },
+ Events = new()
+ {
+ { "im.vector.modular.widgets", 50 },
+ { "io.element.voice_broadcast_info", 50 },
+ { "m.reaction", 100 },
+ { "m.room.avatar", 50 },
+ { "m.room.canonical_alias", 50 },
+ { "m.room.encryption", 100 },
+ { "m.room.history_visibility", 100 },
+ { "m.room.name", 50 },
+ { "m.room.pinned_events", 50 },
+ { "m.room.power_levels", 100 },
+ { "m.room.redaction", 100 },
+ { "m.room.server_acl", 100 },
+ { "m.room.tombstone", 100 },
+ { "m.room.topic", 50 },
+ { "m.space.child", 50 },
+ { "org.matrix.msc3401.call", 50 },
+ { "org.matrix.msc3401.call.member", 50 }
+ },
+ Users = new()
+ {
+ { RuntimeCache.CurrentHomeServer.UserId, 100 },
+ },
+ },
+ CreationContent = new()
+ {
+ {
+ "type", null
+ }
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Classes/RoomCreationTemplates/IRoomCreationTemplate.cs b/MatrixRoomUtils.Web/Classes/RoomCreationTemplates/IRoomCreationTemplate.cs
new file mode 100644
index 0000000..7f84dc4
--- /dev/null
+++ b/MatrixRoomUtils.Web/Classes/RoomCreationTemplates/IRoomCreationTemplate.cs
@@ -0,0 +1,9 @@
+using MatrixRoomUtils.Core.Responses;
+
+namespace MatrixRoomUtils.Web.Classes.RoomCreationTemplates;
+
+public interface IRoomCreationTemplate
+{
+ public CreateRoomRequest CreateRoomRequest { get; }
+ public string Name { get; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/LoginPage.razor b/MatrixRoomUtils.Web/Pages/LoginPage.razor
index 9fcedd1..c46dc9a 100644
--- a/MatrixRoomUtils.Web/Pages/LoginPage.razor
+++ b/MatrixRoomUtils.Web/Pages/LoginPage.razor
@@ -1,41 +1,21 @@
@page "/Login"
@using System.Text.Json
@using MatrixRoomUtils.Core.Authentication
+@using MatrixRoomUtils.Web.Shared.SimpleComponents
@inject ILocalStorageService LocalStorage
@inject IJSRuntime JsRuntime
<h3>Login</h3>
<hr/>
<span>
- <label>@@</label>
- @if (inputVisible.username)
- {
- <input autofocus @bind="newRecordInput.username" @onfocusout="() => inputVisible.username = false" @ref="elementToFocus"/>
- }
- else
- {
- <span tabindex="0" style="border-bottom: #ccc solid 1px; min-width: 50px; display: inline-block; height: 1.4em;" @onfocusin="() => inputVisible.username = true">@newRecordInput.username</span>
- }
- <label>:</label>
- @if (inputVisible.homeserver)
- {
- <input autofocus @bind="newRecordInput.homeserver" @onfocusout="() => inputVisible.homeserver = false" @ref="elementToFocus"/>
- }
- else
- {
- <span tabindex="0" style="border-bottom: #ccc solid 1px; min-width: 50px; display: inline-block; margin-left: 2px; height: 1.4em;" @onfocusin="() => inputVisible.homeserver = true">@newRecordInput.homeserver</span>
- }
+ <span>@@</span><!--
+ --><FancyTextBox @bind-Value="@newRecordInput.username"></FancyTextBox><!--
+ --><span>:</span><!--
+ --><FancyTextBox @bind-Value="@newRecordInput.homeserver"></FancyTextBox>
</span>
<span style="display: block;">
<label>Password:</label>
- @if (inputVisible.password)
- {
- <input autofocus="true" @bind="newRecordInput.password" @onfocusout="() => inputVisible.password = false" @ref="elementToFocus" type="password"/>
- }
- else
- {
- <span tabindex="0" style="border-bottom: #ccc solid 1px; min-width: 50px; display: inline-block; height: 1.4em;" @onfocusin="() => inputVisible.password = true">@string.Join("", newRecordInput.password.Select(x => '*'))</span>
- }
+ <FancyTextBox @bind-Value="@newRecordInput.password" IsPassword="true"></FancyTextBox>
</span>
<button @onclick="AddRecord">Add account to queue</button>
<br/>
@@ -63,7 +43,6 @@
@code {
List<(string homeserver, string username, string password)> records = new();
(string homeserver, string username, string password) newRecordInput = ("", "", "");
- (bool homeserver, bool username, bool password) inputVisible = (false, false, false);
async Task Login()
{
@@ -106,14 +85,6 @@
}
}
-
- private ElementReference elementToFocus;
-
- protected override async Task OnAfterRenderAsync(bool firstRender)
- {
- await JsRuntime.InvokeVoidAsync("BlazorFocusElement", elementToFocus);
- }
-
private void AddRecord()
{
records.Add(newRecordInput);
diff --git a/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerCreateRoom.razor b/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerCreateRoom.razor
index 8cbbca6..c43e276 100644
--- a/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerCreateRoom.razor
+++ b/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerCreateRoom.razor
@@ -2,12 +2,21 @@
@using System.Text.Json
@using MatrixRoomUtils.Core.Extensions
@using MatrixRoomUtils.Core.Responses
+@using MatrixRoomUtils.Web.Shared.SimpleComponents
+@using System.Reflection
@using System.Runtime.Intrinsics.X86
+@using System.Text.Json.Serialization
+@using MatrixRoomUtils.Web.Classes.RoomCreationTemplates
<h3>Room Manager - Create Room</h3>
@* <pre Contenteditable="true" @onkeypress="@JsonChanged" ="JsonString">@JsonString</pre> *@
-<table>
- <tr >
+<style>
+ table.table-top-first-tr tr td:first-child {
+ vertical-align: top;
+ }
+</style>
+<table class="table-top-first-tr">
+ <tr>
<td style="padding-bottom: 16px;">Preset:</td>
<td style="padding-bottom: 16px;">
<InputSelect @bind-Value="@RoomPreset">
@@ -15,105 +24,184 @@
{
<option value="@createRoomRequest.Key">@createRoomRequest.Key</option>
}
- @* <option value="private_chat">Private chat</option> *@
- @* <option value="trusted_private_chat">Trusted private chat</option> *@
- @* <option value="public_chat">Public chat</option> *@
- </InputSelect>
- </td>
- </tr>
- <tr>
- <td>Room name:</td>
- <td>
- <InputText @bind-Value="@creationEvent.Name"></InputText>
- </td>
- </tr>
- <tr>
- <td>Room alias (localpart):</td>
- <td>
- <InputText @bind-Value="@creationEvent.RoomAliasName"></InputText>
- </td>
- </tr>
- <tr>
- <td>Room type:</td>
- <td>
- <InputSelect @bind-Value="@creationEvent._creationContentBaseType.Type">
- <option value="">Room</option>
- <option value="m.space">Space</option>
- </InputSelect>
- <InputText @bind-Value="@creationEvent._creationContentBaseType.Type"></InputText>
- </td>
- </tr>
- <tr>
- <td style="padding-top: 16px;">History visibility:</td>
- <td style="padding-top: 16px;">
- <InputSelect @bind-Value="@creationEvent.HistoryVisibility">
- <option value="invited">Invited</option>
- <option value="joined">Joined</option>
- <option value="shared">Shared</option>
- <option value="world_readable">World readable</option>
- </InputSelect>
- </td>
- </tr>
- <tr>
- <td>Guest access:</td>
- <td>
- <InputSelect @bind-Value="@creationEvent.GuestAccess">
- <option value="can_join">Can join</option>
- <option value="forbidden">Forbidden</option>
</InputSelect>
</td>
</tr>
+ @if (creationEvent != null)
+ {
+ <tr>
+ <td>Room name:</td>
+ <td>
+ <FancyTextBox @bind-Value="@creationEvent.Name"></FancyTextBox>
+ </td>
+ </tr>
+ <tr>
+ <td>Room alias (localpart):</td>
+ <td>
+ <FancyTextBox @bind-Value="@creationEvent.RoomAliasName"></FancyTextBox>
+ </td>
+ </tr>
+ <tr>
+ <td>Room type:</td>
+ <td>
+ <InputSelect @bind-Value="@creationEvent._creationContentBaseType.Type">
+ <option value="">Room</option>
+ <option value="m.space">Space</option>
+ </InputSelect>
+ <FancyTextBox @bind-Value="@creationEvent._creationContentBaseType.Type"></FancyTextBox>
+ </td>
+ </tr>
+ <tr>
+ <td style="padding-top: 16px;">History visibility:</td>
+ <td style="padding-top: 16px;">
+ <InputSelect @bind-Value="@creationEvent.HistoryVisibility">
+ <option value="invited">Invited</option>
+ <option value="joined">Joined</option>
+ <option value="shared">Shared</option>
+ <option value="world_readable">World readable</option>
+ </InputSelect>
+ </td>
+ </tr>
+ <tr>
+ <td>Guest access:</td>
+ <td>
+@code
+{
+ bool test { get; set; } = true;
+ GuestAccessContent a => creationEvent["m.room.guest_access"].As<GuestAccessContent>().Content;
+}
+<ToggleSlider @bind-Value="a.IsGuestAccessEnabled">@(a.IsGuestAccessEnabled ? "Guests can join" : "Guests cannot join")</ToggleSlider>
+ @* <InputSelect @bind-Value="@creationEvent.GuestAccess"> *@
+ @* <option value="can_join">Can join</option> *@
+ @* <option value="forbidden">Forbidden</option> *@
+ @* </InputSelect> *@
+ </td>
+ </tr>
- <tr>
- <td>Room icon:</td>
- <td>
- <img src="@RuntimeCache.CurrentHomeServer?.ResolveMediaUri(creationEvent.RoomIcon ?? "")" style="max-width: 100px; max-height: 100px; border-radius: 50%;"/>
- @* <InputText @bind-Value="@creationEvent.RoomIcon"></InputText> *@
- </td>
-
- </tr>
+ <tr>
+ <td>Room icon:</td>
+ <td>
+ <img src="@RuntimeCache.CurrentHomeServer?.ResolveMediaUri(creationEvent.RoomIcon ?? "")" style="width: 128px; height: 128px; border-radius: 50%;"/>
+ <div style=" display: inline-block;
+ vertical-align: middle;">
+ <FancyTextBox @bind-Value="@creationEvent.RoomIcon"></FancyTextBox><br/>
+ <InputFile OnChange="RoomIconFilePicked"></InputFile>
+ </div>
- <tr>
- <td style="vertical-align: top;">Initial states:</td>
- <td>
+ </td>
+ </tr>
+ <tr>
+ <td>Permissions:</td>
<details>
- @code{
-
- private static readonly string[] ImplementedStates = { "m.room.avatar", "m.room.history_visibility", "m.room.guest_access", };
-
+ <summary>@creationEvent.PowerLevelContentOverride.Users.Count members</summary>
+ @foreach (var user in creationEvent.PowerLevelContentOverride.Events.Keys)
+ {
+ var _event = user;
+ <tr>
+ <td><FancyTextBox Formatter="@GetPermissionFriendlyName" Value="@_event" ValueChanged="val => { creationEvent.PowerLevelContentOverride.Events.ChangeKey(_event, val); }"></FancyTextBox>:</td>
+ <td>
+ <input type="number" value="@creationEvent.PowerLevelContentOverride.Events[_event]" @oninput="val => { creationEvent.PowerLevelContentOverride.Events[_event] = int.Parse(val.Value.ToString()); }" @onfocusout="() => { creationEvent.PowerLevelContentOverride.Events = creationEvent.PowerLevelContentOverride.Events.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); }"/>
+ </td>
+ </tr>
+ }
+ @foreach (var user in creationEvent.PowerLevelContentOverride.Users.Keys)
+ {
+ var _user = user;
+ <tr>
+ <td><FancyTextBox Value="@_user" ValueChanged="val => { creationEvent.PowerLevelContentOverride.Users.ChangeKey(_user, val); creationEvent.PowerLevelContentOverride.Users = creationEvent.PowerLevelContentOverride.Users.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); }"></FancyTextBox>:</td>
+ <td>
+ <input type="number" value="@creationEvent.PowerLevelContentOverride.Users[_user]" @oninput="val => { creationEvent.PowerLevelContentOverride.Users[_user] = int.Parse(val.Value.ToString()); }"/>
+ </td>
+ </tr>
}
- <summary>@creationEvent.InitialState.Count(x => !ImplementedStates.Contains(x.Type)) custom states</summary>
- <table>
- @foreach (var initialState in creationEvent.InitialState.Where(x => !ImplementedStates.Contains(x.Type)))
- {
- <tr>
- <td style="vertical-align: top;">@(initialState.Type):</td>
-
- <td>
- <pre>@JsonSerializer.Serialize(initialState.Content, new JsonSerializerOptions { WriteIndented = true })</pre>
- </td>
- </tr>
- }
- </table>
</details>
- <details>
- <summary>@creationEvent.InitialState.Count initial states</summary>
- <table>
- @foreach (var initialState in creationEvent.InitialState.Where(x => !new[] { "m.room.avatar", "m.room.history_visibility" }.Contains(x.Type)))
+ </tr>
+ <tr>
+ <td>Server ACLs:</td>
+ <td>
+ <details>
+ <summary>@(creationEvent.ServerACLs.Allow.Count) allow rules</summary>
+ <StringListEditor ItemsChanged="OverwriteWrappedProperties" Items="@ServerACLAllowRules"></StringListEditor>
+ </details>
+ <details>
+ <summary>@creationEvent.ServerACLs.Deny.Count deny rules</summary>
+ <StringListEditor ItemsChanged="OverwriteWrappedProperties" Items="@ServerACLDenyRules"></StringListEditor>
+ </details>
+ </td>
+ </tr>
+
+ <tr>
+ <td>Invited members:</td>
+ <td>
+ <details>
+ <summary>@creationEvent.InitialState.Count(x => x.Type == "m.room.member") members</summary>
+ <button @onclick="() => { RuntimeCache.LoginSessions.Select(x => x.Value.LoginResponse.UserId).ToList().ForEach(InviteMember); }">Invite all logged in accounts</button>
+ @foreach (var member in creationEvent.InitialState.Where(x => x.Type == "m.room.member" && x.StateKey != RuntimeCache.CurrentHomeServer.UserId))
{
- <tr>
- <td style="vertical-align: top;">@(initialState.Type):</td>
+ <UserListItem UserId="@member.StateKey"></UserListItem>
+ }
+ </details>
+ </td>
+ </tr>
+
+ @* Initial states, should remain at bottom? *@
+
+ <tr>
+ <td style="vertical-align: top;">Initial states:</td>
+ <td>
+ <details>
+
+ @code{
+
+ private static readonly string[] ImplementedStates = { "m.room.avatar", "m.room.history_visibility", "m.room.guest_access", "m.room.server_acl" };
- <td>
- <pre>@JsonSerializer.Serialize(initialState.Content, new JsonSerializerOptions { WriteIndented = true })</pre>
- </td>
- </tr>
}
- </table>
- </details>
- </td>
- </tr>
+
+ <summary>@creationEvent.InitialState.Count(x => !ImplementedStates.Contains(x.Type)) custom states</summary>
+ <table>
+ @foreach (var initialState in creationEvent.InitialState.Where(x => !ImplementedStates.Contains(x.Type)))
+ {
+ <tr>
+ <td style="vertical-align: top;">
+ @(initialState.Type):
+ @if (!string.IsNullOrEmpty(initialState.StateKey))
+ {
+ <br/>
+ <span>(@initialState.StateKey)</span>
+ }
+ </td>
+
+ <td>
+ <pre>@JsonSerializer.Serialize(initialState.Content, new JsonSerializerOptions { WriteIndented = true })</pre>
+ </td>
+ </tr>
+ }
+ </table>
+ </details>
+ <details>
+ <summary>@creationEvent.InitialState.Count initial states</summary>
+ <table>
+ @foreach (var initialState in creationEvent.InitialState)
+ {
+ var _state = initialState;
+ <tr>
+ <td style="vertical-align: top;">
+ <span>@(_state.Type):</span><br/>
+ <button @onclick="() => { creationEvent.InitialState.Remove(_state); StateHasChanged(); }">Remove</button>
+ </td>
+
+ <td>
+ <pre>@JsonSerializer.Serialize(_state.Content, new JsonSerializerOptions { WriteIndented = true })</pre>
+ </td>
+ </tr>
+ }
+ </table>
+ </details>
+ </td>
+ </tr>
+ }
</table>
+<button @onclick="CreateRoom">Create room</button>
<br/>
<details>
<summary>Creation JSON</summary>
@@ -123,22 +211,14 @@
</details>
<details open>
<summary>Creation JSON (with null values)</summary>
- <EditablePre @bind-Value="@JsonString" oninput="@JsonChanged"></EditablePre>
+ <pre>
+ @creationEvent.ToJson()
+ </pre>
</details>
@code {
- private string JsonString
- {
- get => creationEvent.ToJson();
- set
- {
- creationEvent = JsonSerializer.Deserialize<CreateRoomRequest>(value);
- JsonChanged();
- }
- }
-
private string RoomPreset
{
get
@@ -153,6 +233,8 @@
{
creationEvent = Presets[value];
JsonChanged();
+ OverwriteWrappedPropertiesFromEvent();
+ creationEvent.PowerLevelContentOverride.Events = creationEvent.PowerLevelContentOverride.Events.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);
}
}
@@ -166,123 +248,87 @@
{
await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
- creationEvent = Presets["Default room"] = new CreateRoomRequest
+ //creationEvent = Presets["Default room"] =
+ foreach (var x in Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsClass && !x.IsAbstract && x.GetInterfaces().Contains(typeof(IRoomCreationTemplate))).ToList())
{
- Name = "My new room",
- RoomAliasName = "myroom",
- InitialState = new()
- {
- new()
- {
- Type = "m.room.history_visibility",
- Content = new
- {
- history_visibility = "world_readable"
- }
- },
- new()
- {
- Type = "m.room.guest_access",
- Content = new
- {
- guest_access = "can_join"
- }
- },
- new()
- {
- Type = "m.room.join_rules",
- Content = new
- {
- join_rule = "public"
- }
- },
- new()
- {
- Type = "m.room.server_acl",
- Content = new
- {
- allow = new[] { "*" },
- deny = new[]
- {
- "midov.pl",
- "qoto.org",
- "matrix.kiwifarms.net",
- "plan9.rocks",
- "thisisjoes.site",
- "konqi.work",
- "austinhuang.lol",
- "arcticfox.ems.host",
- "*.thisisjoes.site",
- "*.abuser.eu",
- "*.austinhuang.lol"
- },
- allow_ip_literals = false
- }
- },
- new()
- {
- Type = "m.room.avatar",
- Content = new
- {
- url = "mxc://feline.support/UKNhEyrVsrAbYteVvZloZcFj"
- }
- }
- },
- Visibility = "public",
- PowerLevelContentOverride = new()
- {
- UsersDefault = 0,
- EventsDefault = 100,
- StateDefault = 50,
- Invite = 0,
- Redact = 50,
- Kick = 50,
- Ban = 50,
- NotificationsPl = new()
- {
- Room = 50
- },
- Events = new()
- {
- { "im.vector.modular.widgets", 50 },
- { "io.element.voice_broadcast_info", 50 },
- { "m.reaction", 100 },
- { "m.room.avatar", 50 },
- { "m.room.canonical_alias", 50 },
- { "m.room.encryption", 100 },
- { "m.room.history_visibility", 100 },
- { "m.room.name", 50 },
- { "m.room.pinned_events", 50 },
- { "m.room.power_levels", 100 },
- { "m.room.redaction", 100 },
- { "m.room.server_acl", 100 },
- { "m.room.tombstone", 100 },
- { "m.room.topic", 50 },
- { "m.space.child", 50 },
- { "org.matrix.msc3401.call", 50 },
- { "org.matrix.msc3401.call.member", 50 }
- },
- Users = new()
- {
- { "@alicia:rory.gay", 100 },
- { "@emma:rory.gay", 100 },
- { "@root:rory.gay", 100 },
- { "@rory:rory.gay", 100 }
- },
- },
- CreationContent = new()
- {
- { "type", null }
- }
- };
+ Console.WriteLine($"Found room creation template in class: {x.FullName}");
+ var instance = (IRoomCreationTemplate)Activator.CreateInstance(x);
+ Presets[instance.Name] = instance.CreateRoomRequest;
+ }
+ Presets = Presets.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);
+ if (!Presets.ContainsKey("Default"))
+ {
+ Console.WriteLine($"No default room found in {Presets.Count} presets: {string.Join(", ", Presets.Keys)}");
+ }
+ else RoomPreset = "Default";
await base.OnInitializedAsync();
}
private void JsonChanged()
{
- Console.WriteLine(JsonString);
+ Console.WriteLine(creationEvent.ToJson());
+ }
+
+
+ //wrappers
+ private List<string> ServerACLAllowRules { get; set; } = new();
+ private List<string> ServerACLDenyRules { get; set; } = new();
+
+ private void OverwriteWrappedPropertiesFromEvent()
+ {
+ Console.WriteLine("Overwriting wrapped properties from event");
+ ServerACLAllowRules = creationEvent.ServerACLs.Allow;
+ ServerACLDenyRules = creationEvent.ServerACLs.Deny;
+ }
+
+ private async Task OverwriteWrappedProperties()
+ {
+ Console.WriteLine("Overwriting wrapped properties");
+ Console.WriteLine($"Allow: {ServerACLAllowRules.Count}: {string.Join(", ", ServerACLAllowRules)}");
+ Console.WriteLine($"Deny: {ServerACLDenyRules.Count}: {string.Join(", ", ServerACLDenyRules)}");
+ creationEvent.ServerACLs = new()
+ {
+ Allow = ServerACLAllowRules,
+ Deny = ServerACLDenyRules,
+ AllowIpLiterals = creationEvent.ServerACLs.AllowIpLiterals
+ };
+
+ StateHasChanged();
+ }
+
+ private async Task RoomIconFilePicked(InputFileChangeEventArgs obj)
+ {
+ var res = await RuntimeCache.CurrentHomeServer.UploadFile(obj.File.Name, obj.File.OpenReadStream(), obj.File.ContentType);
+ Console.WriteLine(res);
+ creationEvent.RoomIcon = res;
+ StateHasChanged();
+ }
+
+ private async Task CreateRoom()
+ {
+ Console.WriteLine("Create room");
+ Console.WriteLine(creationEvent.ToJson());
+ creationEvent.CreationContent.Add("rory.gay.created_using", "Rory&::MatrixRoomUtils (https://mru.rory.gay)");
+ //creationEvent.CreationContent.Add();
+ var id = await RuntimeCache.CurrentHomeServer.CreateRoom(creationEvent);
+ // NavigationManager.NavigateTo($"/RoomManager/{id.RoomId.Replace('.','~')}");
+ }
+
+ private void InviteMember(string mxid)
+ {
+ if (!creationEvent.InitialState.Any(x => x.Type == "m.room.member" && x.StateKey == mxid) && RuntimeCache.CurrentHomeServer.UserId != mxid)
+ creationEvent.InitialState.Add(new()
+ {
+ Type = "m.room.member",
+ StateKey = mxid,
+ Content = new
+ {
+ membership = "invite",
+ reason = "Automatically invited at room creation time."
+ }
+ });
}
@@ -295,4 +341,32 @@
_ => key
};
-}
\ No newline at end of file
+ private string GetPermissionFriendlyName(string key) => key switch {
+ "m.reaction" => "Send reaction",
+ "m.room.avatar" => "Change room icon",
+ "m.room.canonical_alias" => "Change room alias",
+ "m.room.encryption" => "Enable encryption",
+ "m.room.history_visibility" => "Change history visibility",
+ "m.room.name" => "Change room name",
+ "m.room.power_levels" => "Change power levels",
+ "m.room.tombstone" => "Upgrade room",
+ "m.room.topic" => "Change room topic",
+ "m.room.pinned_events" => "Pin events",
+ "m.room.server_acl" => "Change server ACLs",
+ _ => key
+ };
+ public class GuestAccessContent
+ {
+ [JsonPropertyName("guest_access")]
+ public string GuestAccess { get; set; }
+
+ public bool IsGuestAccessEnabled
+ {
+ get => GuestAccess == "can_join";
+ set => GuestAccess = value ? "can_join" : "forbidden";
+ }
+ }
+
+ }
+
+
diff --git a/MatrixRoomUtils.Web/Shared/SimpleComponents/DictionaryEditor.razor b/MatrixRoomUtils.Web/Shared/SimpleComponents/DictionaryEditor.razor
new file mode 100644
index 0000000..4d90c57
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/SimpleComponents/DictionaryEditor.razor
@@ -0,0 +1,41 @@
+@using MatrixRoomUtils.Core.Extensions
+<table>
+ @foreach(var i in Items.Keys)
+ {
+ var key = i;
+ <input value="@Items[key]" @oninput="(obj) => inputChanged(obj, key)">
+ <button @onclick="() => { Items.Remove(key); ItemsChanged.InvokeAsync(); }">Remove</button>
+ <br/>
+ }
+</table>
+<button @onclick="() => { Items.Add(string.Empty, default); ItemsChanged.InvokeAsync(); }">Add</button>
+
+@code {
+
+ [Parameter]
+ public Dictionary<string, object> Items { get; set; } = new();
+
+ [Parameter, EditorRequired]
+ public EventCallback ItemsChanged { get; set; }
+
+ [Parameter]
+ public Func<string,string>? KeyFormatter { get; set; }
+
+ [Parameter]
+ public Action? OnFocusLost { get; set; }
+
+
+ protected override Task OnInitializedAsync()
+ {
+ Console.WriteLine($"DictionaryEditor initialized with {Items.Count} items: {Items.ToJson()}");
+ return base.OnInitializedAsync();
+ }
+
+ private void inputChanged(ChangeEventArgs obj, string key)
+ {
+ Console.WriteLine($"StringListEditor inputChanged {key} {obj.Value}");
+ Items[key] = obj.Value.ToString();
+ ItemsChanged.InvokeAsync();
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor b/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor
new file mode 100644
index 0000000..9c325a7
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/SimpleComponents/FancyTextBox.razor
@@ -0,0 +1,35 @@
+@inject IJSRuntime JsRuntime
+@if (isVisible)
+{
+ <input autofocus @bind="Value" @onfocusout="() => { isVisible = false; ValueChanged.InvokeAsync(Value); }" @ref="elementToFocus"/>
+}
+else
+{
+ <span tabindex="0" style="border-bottom: #ccc solid 1px; height: 1.4em; display: inline-block; @(string.IsNullOrEmpty(Value) ? "min-width: 50px;" : "")" @onfocusin="() => isVisible = true">@(Formatter?.Invoke(Value) ?? (IsPassword ? string.Join("", Value.Select(x=>'*')) : Value))</span>
+}
+
+@code {
+
+ [Parameter]
+ public string Value { get; set; }
+
+ [Parameter]
+ public bool IsPassword { get; set; } = false;
+
+ [Parameter]
+ public EventCallback<string> ValueChanged { get; set; }
+
+ [Parameter]
+ public Func<string?, string>? Formatter { get; set; }
+
+
+ private bool isVisible { get; set; } = false;
+
+ private ElementReference elementToFocus;
+
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ await JsRuntime.InvokeVoidAsync("BlazorFocusElement", elementToFocus);
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/SimpleComponents/StringListEditor.razor b/MatrixRoomUtils.Web/Shared/SimpleComponents/StringListEditor.razor
new file mode 100644
index 0000000..fe3a938
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/SimpleComponents/StringListEditor.razor
@@ -0,0 +1,31 @@
+@for (int i = 0; i < Items.Count; i++)
+{
+ var self = i;
+ <button @onclick="() => { Items.RemoveAt(self); ItemsChanged.InvokeAsync(); }">Remove</button>
+ <FancyTextBox Value="@Items[self]" ValueChanged="@(obj => inputChanged(obj, self))"/>
+ <br/>
+}
+<button @onclick="() => { Items.Add(string.Empty); ItemsChanged.InvokeAsync(); }">Add</button>
+
+@code {
+
+ [Parameter]
+ public List<string> Items { get; set; } = new List<string>();
+
+ [Parameter, EditorRequired]
+ public EventCallback ItemsChanged { get; set; }
+
+ protected override Task OnInitializedAsync()
+ {
+ Console.WriteLine($"StringListEditor initialized with {Items.Count} items: {string.Join(",", Items)}");
+ return base.OnInitializedAsync();
+ }
+
+ private void inputChanged(string obj, int i)
+ {
+ Console.WriteLine($"StringListEditor inputChanged {i} {obj}");
+ Items[i] = obj;
+ ItemsChanged.InvokeAsync();
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/SimpleComponents/ToggleSlider.razor b/MatrixRoomUtils.Web/Shared/SimpleComponents/ToggleSlider.razor
new file mode 100644
index 0000000..49a363d
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/SimpleComponents/ToggleSlider.razor
@@ -0,0 +1,70 @@
+<input type="checkbox"/><span>@ChildContent</span>
+
+<div class="container">
+ <label class="switch" for="checkbox">
+ <input type="checkbox" id="checkbox" @bind="Value"/>
+ <div class="slider round"></div>
+ </label>
+</div>
+
+<style>
+ .switch {
+ display: inline-block;
+ height: 16px;
+ position: relative;
+ width: 32px;
+ }
+
+ .switch input {
+ display:none;
+ }
+
+ .slider {
+ background-color: #ccc;
+ bottom: 0;
+ cursor: pointer;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ transition: .4s;
+ }
+
+ .slider:before {
+ background-color: #fff;
+ bottom: -5px;
+ content: "";
+ height: 26px;
+ left: -8px;
+ position: absolute;
+ transition: .4s;
+ width: 26px;
+ }
+
+ input:checked + .slider {
+ background-color: #66bb6a;
+ }
+
+ input:checked + .slider:before {
+ transform: translateX(24px);
+ }
+
+ .slider.round {
+ border-radius: 24px;
+ }
+
+ .slider.round:before {
+ border-radius: 50%;
+ }
+</style>
+
+@code {
+ [Parameter]
+ public RenderFragment? ChildContent { get; set; }
+
+ [Parameter]
+ public bool Value { get; set; }
+ [Parameter]
+ public EventCallback<bool> ValueChanged { get; set; }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/UserListItem.razor b/MatrixRoomUtils.Web/Shared/UserListItem.razor
new file mode 100644
index 0000000..ae1fcd1
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/UserListItem.razor
@@ -0,0 +1,62 @@
+@using MatrixRoomUtils.Core.Responses
+<div style="background-color: #ffffff11; border-radius: 25px; margin: 8px; width: fit-Content;">
+ <img style="@(ChildContent != null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%;" src="@profileAvatar"/>
+ <span style="vertical-align: middle; margin-right: 8px; border-radius: 75px;">@profileName</span>
+
+ <div style="display: inline-block;">
+ @if (ChildContent != null)
+ {
+ @ChildContent
+ }
+ </div>
+
+</div>
+
+@code {
+
+ [Parameter]
+ public RenderFragment? ChildContent { get; set; }
+
+ [Parameter]
+ public ProfileResponse User { get; set; }
+
+ [Parameter]
+ public string UserId { get; set; }
+
+ private string profileAvatar { get; set; } = "/icon-192.png";
+ private string profileName { get; set; } = "Loading...";
+
+
+ private static SemaphoreSlim _semaphoreSlim = new(128);
+
+ protected override async Task OnInitializedAsync()
+ {
+ await base.OnInitializedAsync();
+ await LocalStorageWrapper.LoadFromLocalStorage(LocalStorage);
+
+ await _semaphoreSlim.WaitAsync();
+
+ var hs = await new AuthenticatedHomeServer(RuntimeCache.CurrentHomeServer.UserId, RuntimeCache.CurrentHomeServer.AccessToken, RuntimeCache.CurrentHomeServer.HomeServerDomain).Configure();
+
+ if (User == null)
+ {
+ if (UserId == null)
+ {
+ throw new ArgumentNullException(nameof(UserId));
+ }
+ User = await hs.GetProfile(UserId);
+ }
+ else
+ {
+ // UserId = User.;
+ }
+
+ profileAvatar = RuntimeCache.CurrentHomeServer.ResolveMediaUri(User.AvatarUrl);
+ profileName = User.DisplayName;
+
+ _semaphoreSlim.Release();
+ if (Random.Shared.Next(100) == 1)
+ await LocalStorageWrapper.SaveCacheToLocalStorage(LocalStorage);
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/wwwroot/index.html b/MatrixRoomUtils.Web/wwwroot/index.html
index 028e56b..eeac69b 100644
--- a/MatrixRoomUtils.Web/wwwroot/index.html
+++ b/MatrixRoomUtils.Web/wwwroot/index.html
@@ -32,7 +32,7 @@
if (element instanceof HTMLElement) {
console.log(element);
element.focus();
- } else {
+ } else if (element.__internalId) {
console.log("Element is not an HTMLElement", element);
}
}
|