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";
+ }
+ }
+
+ }
+
+
|