diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateBasicRoomInfoOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateBasicRoomInfoOptions.razor
new file mode 100644
index 0000000..c1ee202
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateBasicRoomInfoOptions.razor
@@ -0,0 +1,52 @@
+@using ArcaneLibs
+@using LibMatrix.Helpers
+<tr>
+ <td>Room name:</td>
+ <td>
+ <FancyTextBox @bind-Value="@roomBuilder.Name.Name"></FancyTextBox>
+ </td>
+</tr>
+<tr>
+ <td>Room alias:</td>
+ <td>
+ <InputLocalPart Sigil="#" ServerName="@Homeserver.ServerName" @bind-LocalPart="@roomBuilder.AliasLocalPart"></InputLocalPart>
+ </td>
+</tr>
+<tr>
+ <td>Room icon:</td>
+ <td>
+ @if (!string.IsNullOrWhiteSpace(roomBuilder.Avatar.Url)) {
+ <MxcAvatar Homeserver="Homeserver" MxcUri="@roomBuilder.Avatar.Url" Size="3" SizeUnit="em" Circular="true"/>
+ }
+ else {
+ <img class="avatar" style="height: 3em; width: 3em; border-radius: 50%;" src="@IdenticonGenerator.GenerateAsDataUri(Homeserver.WhoAmI.UserId)"/>
+ }
+ <div style="display: inline-block; vertical-align: middle;">
+ <FancyTextBox @bind-Value="@roomBuilder.Avatar.Url"></FancyTextBox>
+ <br/>
+ <SimpleFilePicker OnFilePicked="@RoomIconFilePicked"></SimpleFilePicker>
+ </div>
+ </td>
+</tr>
+
+@code {
+
+ [Parameter]
+ public required RoomBuilder roomBuilder { get; set; }
+
+ [Parameter]
+ public required Action PageStateHasChanged { get; set; }
+
+ [Parameter]
+ public AuthenticatedHomeserverGeneric Homeserver { get; set; }
+
+ private static readonly SvgIdenticonGenerator IdenticonGenerator = new();
+
+ private async Task RoomIconFilePicked(InputFileChangeEventArgs obj) {
+ var res = await Homeserver.UploadFile(obj.File.Name, obj.File.OpenReadStream(), obj.File.ContentType);
+ Console.WriteLine(res);
+ roomBuilder.Avatar.Url = res;
+ PageStateHasChanged();
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateCreateOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateCreateOptions.razor
new file mode 100644
index 0000000..3f4a73d
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateCreateOptions.razor
@@ -0,0 +1,92 @@
+@using Blazored.LocalStorage
+@using LibMatrix.Helpers
+@inject ILocalStorageService LocalStorage
+<tr>
+ <td>Room type:</td>
+ <td>
+ @if (RoomTypes.ContainsKey(roomBuilder.Type ?? "")) {
+ <InputSelect @bind-Value="@roomBuilder.Type">
+ @foreach (var type in RoomTypes) {
+ <option value="@type.Key">@type.Value</option>
+ }
+ <option value="custom">Custom ...</option>
+ </InputSelect>
+ }
+ else {
+ <FancyTextBox @bind-Value="@roomBuilder.Type"></FancyTextBox>
+ }
+
+ <span> version </span>
+ @if (Capabilities is null) {
+ <span style="color: #888;">Loading...</span>
+ }
+ else {
+ <InputSelect @bind-Value="@roomBuilder.Version">
+ @foreach (var version in Capabilities.Capabilities.RoomVersions!.Available!) {
+ <option value="@version.Key">@version.Key (@version.Value)</option>
+ }
+ </InputSelect>
+ }
+ </td>
+</tr>
+<tr>
+ <td style="vertical-align: top;">Allow attribution:</td>
+ <td>
+ <InputCheckbox @bind-Value="@AllowAttribution"/>
+ <span>Allow attribution to Rory&::MatrixUtils</span>
+ <LinkButton InlineText="true" OnClick="@(() => ShowAttributionInfo = true)">?</LinkButton>
+ </td>
+</tr>
+
+@if (ShowAttributionInfo) {
+ <ModalWindow Title="Allow attribution to Rory&::MatrixUtils"
+ OnCloseClicked="@(() => ShowAttributionInfo = false)">
+ <span>This will add the following to the room creation content:</span>
+ <br/>
+ <pre>{ "gay.rory.created_using": "Rory&::MatrixUtils (https://mru.rory.gay)" }</pre>
+ <span>This is not visible to users unless they manually inspect the room's create event source.</span>
+ </ModalWindow>
+}
+
+@code {
+
+ [Parameter]
+ public required RoomBuilder roomBuilder { get; set; }
+
+ [Parameter]
+ public required Action PageStateHasChanged { get; set; }
+
+ [Parameter]
+ public AuthenticatedHomeserverGeneric Homeserver { get; set; }
+
+ private AuthenticatedHomeserverGeneric.CapabilitiesResponse? Capabilities { get; set; }
+
+ private bool ShowAttributionInfo {
+ get;
+ set {
+ field = value;
+ StateHasChanged();
+ }
+ }
+
+ private bool AllowAttribution {
+ get;
+ set {
+ field = value;
+ _ = LocalStorage.SetItemAsync("rmu.room_create.allow_attribution", value);
+ }
+ } = true;
+
+ protected override async Task OnInitializedAsync() {
+ Capabilities = await Homeserver.GetCapabilitiesAsync();
+ roomBuilder.Version = Capabilities.Capabilities.RoomVersions!.Default;
+ AllowAttribution = await LocalStorage.GetItemAsync<bool?>("rmu.room_create.allow_attribution") ?? true;
+ }
+
+ private static Dictionary<string, string> RoomTypes { get; } = new() {
+ { "", "Room" },
+ { "m.space", "Space" },
+ { "support.feline.policy.lists.msc.v1", "Policy list" }
+ };
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateInitialStateOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateInitialStateOptions.razor
new file mode 100644
index 0000000..272bd8b
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateInitialStateOptions.razor
@@ -0,0 +1,52 @@
+@using System.Text.Json
+@using LibMatrix
+@using LibMatrix.Helpers
+<tr>
+ <td style="vertical-align: top;">Initial room state:</td>
+ <td>
+ @foreach (var (displayName, events) in new Dictionary<string, List<StateEvent>>() {
+ { "Important room state (before final access rules)", roomBuilder.ImportantState },
+ { "Additional room state (after final access rules)", roomBuilder.InitialState },
+ }) {
+ <details>
+
+ @code
+ {
+ // private static readonly string[] ImplementedStates = { "m.room.avatar", "m.room.history_visibility", "m.room.guest_access", "m.room.server_acl" };
+ }
+
+ @* <summary>@displayName: @events.Count(x => !ImplementedStates.Contains(x.Type)) events</summary> *@
+ <summary>@displayName: @events.Count events</summary>
+ <table>
+ @* @foreach (var initialState in events.Where(x => !ImplementedStates.Contains(x.Type))) { *@
+ @foreach (var initialState in events) {
+ <tr>
+ <td style="vertical-align: top;">
+ @(initialState.Type):
+ @if (!string.IsNullOrEmpty(initialState.StateKey)) {
+ <br/>
+ <span>(@initialState.StateKey)</span>
+ }
+
+ </td>
+ <td>
+ <pre>@JsonSerializer.Serialize(initialState.RawContent, new JsonSerializerOptions { WriteIndented = true })</pre>
+ </td>
+ </tr>
+ }
+ </table>
+ </details>
+ }
+ </td>
+</tr>
+
+@code {
+ [Parameter]
+ public required RoomBuilder roomBuilder { get; set; }
+
+ [Parameter]
+ public required Action PageStateHasChanged { get; set; }
+
+ [Parameter]
+ public AuthenticatedHomeserverGeneric Homeserver { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateMembershipOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateMembershipOptions.razor
new file mode 100644
index 0000000..5170d2d
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateMembershipOptions.razor
@@ -0,0 +1,55 @@
+@using ArcaneLibs.Extensions
+@using LibMatrix.Helpers
+<tr>
+ <td>Invited members:</td>
+ <td>
+ <details>
+ <summary>@roomBuilder.Invites.Count members</summary>
+ <LinkButton OnClickAsync="@InviteAllSessions" InlineText="true">Invite all logged in accounts</LinkButton>
+ @foreach (var member in roomBuilder.Invites) {
+ <UserListItem _homeserver="Homeserver" UserId="@member.Key"></UserListItem>
+ <span>: </span>
+ <FancyTextBox Value="@member.Value" ValueChanged="@(val => roomBuilder.Invites[member.Key] = val)"/>
+ }
+ </details>
+ </td>
+</tr>
+<tr>
+ <td>Banned members:</td>
+ <td>
+ <details>
+ <summary>@roomBuilder.Bans.Count members</summary>
+ @foreach (var member in roomBuilder.Bans) {
+ <UserListItem _homeserver="Homeserver" UserId="@member.Key"></UserListItem>
+ <span>: </span>
+ <FancyTextBox Value="@member.Value" ValueChanged="@(val => roomBuilder.Bans[member.Key] = val)"/>
+ }
+ </details>
+ </td>
+</tr>
+
+@code {
+
+ [Parameter]
+ public required RoomBuilder roomBuilder { get; set; }
+
+ [Parameter]
+ public required Action PageStateHasChanged { get; set; }
+
+ [Parameter]
+ public AuthenticatedHomeserverGeneric Homeserver { get; set; }
+
+ private async Task InviteAllSessions() {
+ var sessions = await sessionStore.GetAllSessions();
+ foreach (var session in sessions) {
+ if (roomBuilder.Invites.ContainsKey(session.Value.Auth.UserId) || session.Value.Auth.UserId == Homeserver!.WhoAmI.UserId) continue;
+ Console.WriteLine("Inviting " + session.Value.Auth.UserId);
+ roomBuilder.Invites.Add(session.Value.Auth.UserId, null);
+ Console.WriteLine("--");
+ }
+
+ Console.WriteLine("Got all sessions, invited: " + string.Join(", ", roomBuilder.Invites.Keys));
+ StateHasChanged();
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePermissionsOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePermissionsOptions.razor
new file mode 100644
index 0000000..ba28b82
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePermissionsOptions.razor
@@ -0,0 +1,123 @@
+@using ArcaneLibs.Extensions
+@using LibMatrix.Helpers
+<tr>
+ <td>Permissions:</td>
+ <details>
+ <summary>
+ @if (roomBuilder.Version is "org.matrix.hydra.11" or "12") {
+ <span>@(roomBuilder.AdditionalCreators.Count + 1) creators, </span>
+ }
+ <span>@roomBuilder.PowerLevels.Users.Count members, @roomBuilder.PowerLevels.Events.Count events</span>
+ </summary>
+
+ @if (roomBuilder.Version is "org.matrix.hydra.11" or "12") {
+ <span style="border-bottom: #444;">Creators:</span>
+ <br/>
+ <span>@Homeserver.WhoAmI.UserId (you - to change, visit <a href="/">the homepage</a>.)</span>
+ <br/>
+
+ <StringListEditor @bind-Items="@roomBuilder.AdditionalCreators"></StringListEditor>
+ <br/>
+ }
+
+ <span style="border-bottom: #444;">Events:</span><br/>
+ @foreach (var eventType in roomBuilder.PowerLevels.Events.Keys) {
+ var _event = eventType;
+ <tr>
+ <td>
+ <LinkButton InlineText="true" OnClick="@(() => {
+ roomBuilder.PowerLevels.Events.Remove(_event);
+ StateHasChanged();
+ })">-
+ </LinkButton>
+ <div style="display: inline-flex;">
+ <FancyTextBox Formatter="@GetPermissionFriendlyName"
+ Value="@_event"
+ ValueChanged="val => { roomBuilder.PowerLevels.Events.ChangeKey(_event, val); }">
+ </FancyTextBox>
+ <span>:</span>
+ </div>
+ </td>
+ <td>
+ <input type="number" value="@roomBuilder.PowerLevels.Events[_event]"
+ @oninput="val => { roomBuilder.PowerLevels.Events[_event] = int.Parse(val.Value.ToString()); }"
+ @onfocusout="@(() => { roomBuilder.PowerLevels.Events = roomBuilder.PowerLevels.Events.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); })"/>
+ </td>
+ </tr>
+ }
+ <tr>
+ <td>
+ <LinkButton InlineText="true" OnClick="@(() => {
+ roomBuilder.PowerLevels.Events[""] = 0;
+ StateHasChanged();
+ })">+
+ </LinkButton>
+ </td>
+ </tr>
+
+ <span style="border-bottom: #444;">Users:</span><br/>
+ @foreach (var user in roomBuilder.PowerLevels.Users.Keys) {
+ var _user = user;
+ <tr>
+ <td>
+ <LinkButton InlineText="true" OnClick="@(() => {
+ roomBuilder.PowerLevels.Users.Remove(_user);
+ StateHasChanged();
+ })">-
+ </LinkButton>
+ <div style="display: inline-flex;">
+ <FancyTextBox Value="@_user"
+ ValueChanged="val => { roomBuilder.PowerLevels.Users.ChangeKey(_user, val); }">
+ </FancyTextBox>
+ <span>:</span>
+ </div>
+ </td>
+ <td>
+ <input type="number" value="@roomBuilder.PowerLevels.Users[_user]"
+ @oninput="val => { roomBuilder.PowerLevels.Users[_user] = int.Parse(val.Value.ToString()); }"
+ @onfocusout="@(() => { roomBuilder.PowerLevels.Users = roomBuilder.PowerLevels.Users.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); })"/>
+ </td>
+ </tr>
+ }
+ <tr>
+ <td>
+ <LinkButton InlineText="true" OnClick="@(() => {
+ roomBuilder.PowerLevels.Users[""] = 0;
+ StateHasChanged();
+ })">+
+ </LinkButton>
+ </td>
+ </tr>
+ </details>
+</tr>
+
+
+@code {
+
+ [Parameter]
+ public required RoomBuilder roomBuilder { get; set; }
+
+ [Parameter]
+ public required Action PageStateHasChanged { get; set; }
+
+ [Parameter]
+ public AuthenticatedHomeserverGeneric Homeserver { get; set; }
+
+ 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",
+ "org.matrix.msc4284.policy" => "Change policy server",
+ "m.room.guest_access" => "Change guest access",
+ _ => key
+ };
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePrivacyOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePrivacyOptions.razor
new file mode 100644
index 0000000..76f61c4
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePrivacyOptions.razor
@@ -0,0 +1,70 @@
+@using LibMatrix.Helpers
+<tr>
+ <td style="padding-top: 16px;">Join rules:</td>
+ <td style="padding-top: 16px;">
+ <InputSelect @bind-Value="@roomBuilder.JoinRules.JoinRuleValue">
+ <option value="public">Anyone can join</option>
+ <option value="invite">Invite only</option>
+ <option value="knock">Ask to join</option>
+ <option value="restricted">Invite only (or mutual room)</option>
+ <option value="knock_restricted">Ask to join (or mutual room)</option>
+ </InputSelect>
+ </td>
+</tr>
+<tr>
+ <td>History visibility:</td>
+ <td>
+ <InputSelect @bind-Value="@roomBuilder.HistoryVisibility.HistoryVisibility">
+ <option value="invited">Since invite</option>
+ <option value="joined">Since join</option>
+ <option value="shared">Since room creation (members only)</option>
+ <option value="world_readable">World readable (everyone)</option>
+ </InputSelect>
+ </td>
+</tr>
+<tr>
+ <td>Guest access:</td>
+ <td>
+ <InputCheckbox @bind-Value="roomBuilder.GuestAccess.IsGuestAccessEnabled"/>
+ <span>Allow guests to join</span>
+ <LinkButton InlineText="true" href="https://spec.matrix.org/v1.15/client-server-api/#guest-access" target="_blank">?</LinkButton>
+ </td>
+</tr>
+<tr>
+ <td>Server ACLs:</td>
+ <td>
+ @if (roomBuilder.ServerAcls?.Allow is null) {
+ <p>No allow rules exist!</p>
+ <LinkButton OnClick="@(() => { roomBuilder.ServerAcls!.Allow = ["*"]; })">Create sane defaults</LinkButton>
+ }
+ else {
+ <details>
+ <summary>@(roomBuilder.ServerAcls.Allow?.Count) allow rules</summary>
+ <StringListEditor @bind-Items="@roomBuilder.ServerAcls.Allow"></StringListEditor>
+ </details>
+ }
+ @if (roomBuilder.ServerAcls?.Deny is null) {
+ <p>No deny rules exist!</p>
+ <LinkButton OnClick="@(() => { roomBuilder.ServerAcls!.Deny = []; })">Create sane defaults</LinkButton>
+ }
+ else {
+ <details>
+ <summary>@(roomBuilder.ServerAcls.Deny?.Count) deny rules</summary>
+ <StringListEditor @bind-Items="@roomBuilder.ServerAcls.Deny"></StringListEditor>
+ </details>
+ }
+ </td>
+</tr>
+
+@code {
+
+ [Parameter]
+ public required RoomBuilder roomBuilder { get; set; }
+
+ [Parameter]
+ public required Action PageStateHasChanged { get; set; }
+
+ [Parameter]
+ public AuthenticatedHomeserverGeneric Homeserver { get; set; }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateUpgradeOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateUpgradeOptions.razor
new file mode 100644
index 0000000..3e8c3dd
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateUpgradeOptions.razor
@@ -0,0 +1,33 @@
+@using LibMatrix.Helpers
+<tr>
+ <td>Room upgrade options</td>
+ <td>
+ <details>
+ <summary>Upgrading from @roomUpgrade.OldRoom.RoomId</summary>
+ <InputCheckbox @bind-Value="@roomUpgrade.UpgradeOptions.InviteMembers"></InputCheckbox>
+ <span>Invite members</span>
+ <br/>
+ <InputCheckbox @bind-Value="@roomUpgrade.UpgradeOptions.InvitePowerlevelUsers"></InputCheckbox>
+ <span>Invite users with powerlevels</span>
+ <br/>
+ <InputCheckbox @bind-Value="@roomUpgrade.UpgradeOptions.MigrateBans"></InputCheckbox>
+ <span>Copy bans (do not use with moderation bots!)</span>
+ <br/>
+ <LinkButton OnClickAsync="@(async () => {
+ await roomUpgrade.ImportAsync();
+ PageStateHasChanged();
+ })">Apply
+ </LinkButton>
+ </details>
+ </td>
+</tr>
+
+@code {
+
+ [Parameter]
+ public required RoomUpgradeBuilder roomUpgrade { get; set; }
+
+ [Parameter]
+ public required Action PageStateHasChanged { get; set; }
+
+}
\ No newline at end of file
|