diff --git a/.idea/.idea.MatrixUtils/.idea/vcs.xml b/.idea/.idea.MatrixUtils/.idea/vcs.xml
index 94a25f7..df05d42 100644
--- a/.idea/.idea.MatrixUtils/.idea/vcs.xml
+++ b/.idea/.idea.MatrixUtils/.idea/vcs.xml
@@ -2,5 +2,8 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
+ <mapping directory="$PROJECT_DIR$/LibMatrix" vcs="Git" />
+ <mapping directory="$PROJECT_DIR$/LibMatrix/ArcaneLibs" vcs="Git" />
+ <mapping directory="$PROJECT_DIR$/MxApiExtensions" vcs="Git" />
</component>
</project>
\ No newline at end of file
diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj
index 5d584c9..e61ef31 100644
--- a/Benchmarks/Benchmarks.csproj
+++ b/Benchmarks/Benchmarks.csproj
@@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
+ <PackageReference Include="BenchmarkDotNet" Version="0.15.2" />
</ItemGroup>
</Project>
diff --git a/LibMatrix b/LibMatrix
-Subproject e16e9f3093fab575f5f9323248e7b19fa6d5456
+Subproject fcad35734ffe635a85e27349ff09bc035f26806
diff --git a/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj b/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj
index 24401ab..e69774b 100644
--- a/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj
+++ b/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj
@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.1" />
+ <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.7" />
</ItemGroup>
<ItemGroup>
diff --git a/MatrixUtils.Web/Classes/RmuSessionStore.cs b/MatrixUtils.Web/Classes/RmuSessionStore.cs
index 746f68a..9df8837 100644
--- a/MatrixUtils.Web/Classes/RmuSessionStore.cs
+++ b/MatrixUtils.Web/Classes/RmuSessionStore.cs
@@ -1,5 +1,3 @@
-using System.Text.Json;
-using System.Text.Json.Nodes;
using LibMatrix;
using LibMatrix.Homeservers;
using LibMatrix.Services;
diff --git a/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs b/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs
index 7078308..215ad14 100644
--- a/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs
+++ b/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs
@@ -82,10 +82,8 @@ public class DefaultRoomCreationTemplate : IRoomCreationTemplate {
//TODO: re-implement this
}
},
- CreationContent = new JsonObject {
- {
- "type", null
- }
+ CreationContent = new() {
+ { "type", null }
}
};
-}
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj
index aa9f37f..f418340 100644
--- a/MatrixUtils.Web/MatrixUtils.Web.csproj
+++ b/MatrixUtils.Web/MatrixUtils.Web.csproj
@@ -39,11 +39,11 @@
<ItemGroup>
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0"/>
<PackageReference Include="Blazored.SessionStorage" Version="2.4.0"/>
- <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" />
- <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.1" PrivateAssets="all" />
- <PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="9.0.2" />
- <PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="9.0.1" />
- <PackageReference Include="SpawnDev.BlazorJS.WebWorkers" Version="2.5.39" />
+ <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.7" />
+ <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.7" PrivateAssets="all" />
+ <PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="9.0.7" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="9.0.7" />
+ <PackageReference Include="SpawnDev.BlazorJS.WebWorkers" Version="2.17.1" />
</ItemGroup>
<ItemGroup>
diff --git a/MatrixUtils.Web/Pages/Dev/DevUtilities.razor b/MatrixUtils.Web/Pages/Dev/DevUtilities.razor
index 3b2d533..f6392a4 100644
--- a/MatrixUtils.Web/Pages/Dev/DevUtilities.razor
+++ b/MatrixUtils.Web/Pages/Dev/DevUtilities.razor
@@ -1,5 +1,8 @@
@page "/Dev/Utilities"
@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Spec.Ephemeral
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
+@using LibMatrix.Helpers
@using MatrixUtils.Abstractions
<h3>Debug Tools</h3>
@@ -14,7 +17,7 @@ else {
<summary>Room List</summary>
@foreach (var roomId in Rooms) {
<a style="color: unset; text-decoration: unset;" href="/RoomStateViewer/@roomId.Replace('.', '~')">
- <RoomListItem RoomInfo="@(new RoomInfo(hs.GetRoom(roomId)))" LoadData="true"></RoomListItem>
+ <RoomListItem Homeserver="hs" RoomInfo="@(new RoomInfo(hs.GetRoom(roomId)))" LoadData="true"></RoomListItem>
</a>
}
</details>
@@ -61,6 +64,7 @@ else {
StateHasChanged();
return;
}
+
if (res.Content.Headers.ContentType.MediaType == "application/json")
GetRequestResult = $"Error: {res.StatusCode}\n" + (await res.Content.ReadFromJsonAsync<object>()).ToJson();
else
@@ -69,7 +73,32 @@ else {
catch (Exception e) {
GetRequestResult = $"Error: {e}";
}
+
StateHasChanged();
}
+ private async Task TestRoomBuilder() {
+ var rb = new RoomBuilder() {
+ HistoryVisibility = new RoomHistoryVisibilityEventContent() { HistoryVisibility = RoomHistoryVisibilityEventContent.HistoryVisibilityTypes.Shared },
+ ImportantState = [
+ new() {
+ RawContent = new() {
+ ["type"] = "m.room.name",
+ ["name"] = "Test Room"
+ }
+ },
+ new() {
+ Type = "test",
+ TypedContent = new PresenceEventContent() {
+ Presence = "online",
+ LastActiveAgo = 0,
+ }
+ },
+
+ ]
+ };
+
+ await rb.Create(hs);
+ }
+
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor b/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor
index 1906dd8..c636c56 100644
--- a/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor
+++ b/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor
@@ -8,7 +8,7 @@
<h3>Known Homeserver List</h3>
<hr/>
-<span>Room ID: <FancyTextBox @bind-Value="@RoomId"/><LinkButton OnClick="@Execute">Execute</LinkButton></span>
+<span>Room ID: <FancyTextBox @bind-Value="@RoomId"/><LinkButton OnClickAsync="@Execute">Execute</LinkButton></span>
<span>Stats:</span><br/>
<span>Server count: @entries.Count</span><br/>
diff --git a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor
index 03d3ad1..21b0972 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor
@@ -1,5 +1,6 @@
@page "/HSAdmin"
@using ArcaneLibs.Extensions
+@using LibMatrix.Responses.Federation
<h3>Homeserver Admininistration</h3>
<hr/>
@@ -11,6 +12,7 @@ else {
<h4>Synapse tools</h4>
<hr/>
<a href="/HSAdmin/Synapse/RoomQuery">Query rooms</a><br/>
+ <a href="/HSAdmin/Synapse/UserQuery">Query users</a><br/>
<a href="/HSAdmin/Synapse/BlockMedia">Block media</a><br/>
<a href="/HSAdmin/Synapse/BackgroundJobs">View running background jobs</a><br/>
}
diff --git a/MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor b/MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor
index 87600c6..ec2ec54 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor
@@ -3,7 +3,7 @@
@using LibMatrix.Responses
<h3>Manage external profiles</h3>
-<LinkButton OnClick="AddAllLocalProfiles">Add local sessions</LinkButton>
+<LinkButton OnClickAsync="AddAllLocalProfiles">Add local sessions</LinkButton>
@foreach(var p in ExternalProfiles)
{
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor
index d07ff08..594ff35 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor
@@ -24,13 +24,13 @@
<pre>@MxcUri?.ToJson(ignoreNull: true)</pre>
@if (Event is not null) {
- <LinkButton OnClick="@RedactAllEvents">Redact all messages</LinkButton>
+ <LinkButton OnClickAsync="@RedactAllEvents">Redact all messages</LinkButton>
}
@if (Event?.Sender?.Split(':', 2)[1] == Homeserver?.ServerName) {
<p>User is a local user!</p>
- <LinkButton OnClick="@DeactivateUser">Deactivate User</LinkButton>
- <LinkButton OnClick="@QuarantineMediaByUser">Quarantine all media</LinkButton>
+ <LinkButton OnClickAsync="@DeactivateUser">Deactivate User</LinkButton>
+ <LinkButton OnClickAsync="@QuarantineMediaByUser">Quarantine all media</LinkButton>
}
}
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor
index 3b3acac..999e331 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor
@@ -50,7 +50,7 @@
<br/>
</details>
- <LinkButton OnClick="@DeleteRoom">Execute</LinkButton>
+ <LinkButton OnClickAsync="@DeleteRoom">Execute</LinkButton>
}
@code {
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor
index 3e38ee2..5e45e5b 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor
@@ -70,7 +70,7 @@
}
</p>
<p>
- <LinkButton OnClick="@(() => DeleteRoom(room))">Delete room</LinkButton>
+ <LinkButton OnClickAsync="@(() => DeleteRoom(room))">Delete room</LinkButton>
<LinkButton target="_blank" href="@($"/HSAdmin/Synapse/ResyncState?roomId={room.RoomId}&via={room.RoomId.Split(':', 2)[1]}")">Resync state</LinkButton>
</p>
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor
index f3faafa..c2446a2 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor
@@ -16,7 +16,7 @@
<span>Via: </span>
<InputText @bind-Value="@Via"></InputText>
<br/>
- <LinkButton OnClick="@Execute">Execute</LinkButton>
+ <LinkButton OnClickAsync="@Execute">Execute</LinkButton>
}
@if (Executing) {
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor
index 3e38ee2..54ac800 100644
--- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor
+++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor
@@ -1,8 +1,6 @@
-@page "/HSAdmin/Synapse/RoomQuery"
-@using System.Diagnostics.CodeAnalysis
+@page "/HSAdmin/Synapse/UserQuery"
@using Microsoft.AspNetCore.WebUtilities
@using ArcaneLibs.Extensions
-@using LibMatrix
@using LibMatrix.EventTypes.Spec.State.RoomInfo
@using LibMatrix.Homeservers.Extensions.NamedCaches
@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters
@@ -11,7 +9,7 @@
@using MatrixUtils.Web.Pages.HSAdmin.Synapse.Components.RoomQuery
@inject ILogger<RoomQuery> Logger
-<h3>Homeserver Administration - Room Query</h3>
+<h3>Homeserver Administration - User Query</h3>
<label>Search name: </label>
<InputText @bind-Value="SearchTerm"/><br/>
@@ -27,7 +25,7 @@
<summary>
<span>Local filtering (slow)</span>
</summary>
- <SynapseRoomQueryFilter Filter="@Filter"/>
+ @* <SynapseRoomQueryFilter Filter="@Filter"/> *@
</details>
<button class="btn btn-primary" @onclick="Search">Search</button>
<br/>
@@ -51,120 +49,45 @@
@* </details> *@
}
-@foreach (var room in Results) {
+@foreach (var user in Results) {
<div class="room-list-item">
- @* <RoomListItem RoomName="@res.Name" RoomId="@res.RoomId"></RoomListItem> *@
<p>
- @if (!string.IsNullOrWhiteSpace(room.CanonicalAlias)) {
- <span>@room.CanonicalAlias - </span>
- }
- <span>@room.RoomId</span>
- @if (!string.IsNullOrWhiteSpace(room.Name)) {
- <span> (@room.Name)</span>
+ <span>@user.Name</span>
+ @if (!string.IsNullOrWhiteSpace(user.DisplayName)) {
+ <span> (@user.DisplayName)</span>
}
<br/>
-
- @if (!string.IsNullOrWhiteSpace(room.Creator)) {
- <span>Created by @room.Creator</span>
- <br/>
- }
</p>
<p>
- <LinkButton OnClick="@(() => DeleteRoom(room))">Delete room</LinkButton>
- <LinkButton target="_blank" href="@($"/HSAdmin/Synapse/ResyncState?roomId={room.RoomId}&via={room.RoomId.Split(':', 2)[1]}")">Resync state</LinkButton>
+ <LinkButton OnClickAsync="@(() => Login(user))">Log in</LinkButton>
+ @* <LinkButton OnClickAsync="@(() => DeleteRoom(user))">Delete room</LinkButton> *@
+ @* <LinkButton target="_blank" href="@($"/HSAdmin/Synapse/ResyncState?roomId={user.RoomId}&via={user.RoomId.Split(':', 2)[1]}")">Resync state</LinkButton> *@
+
</p>
@{
List<string?> flags = [];
- if (true || room.JoinedLocalMembers > 0) {
- flags.Add(room.JoinRules switch {
- "public" => "Public",
- "invite" => "Invite only",
- "knock" => "Knock",
- "restricted" => "Restricted",
- "knock_restricted" => "Knock + restricted",
- // TODO: default?
- null => null,
- "" => null,
- _ => "unknown join rule: " + room.JoinRules
- });
-
- if (!string.IsNullOrWhiteSpace(room.Encryption)) flags.Add("encrypted");
- if (!room.Federatable) flags.Add("unfederated");
+ if (user.IsGuest == true) flags.Add("guest");
+ if (user.Admin == true) flags.Add("admin");
+ if (user.Deactivated == true) flags.Add("deactivated");
+ if (user.Erased == true) flags.Add("erased");
+ if (user.ShadowBanned == true) flags.Add("shadow banned");
+ if (user.Locked == true) flags.Add("locked");
+ if (user.Approved == true) flags.Add("approved");
- flags.Add(room.HistoryVisibility switch {
- "world_readable" => "world readable history",
- "shared" => "shared history",
- "invited" => "history since invite",
- "joined" => "history since join",
- // TODO: default?
- null => null,
- "" => null,
- _ => "unknown history setting: " + room.HistoryVisibility
- });
+ if (!string.IsNullOrWhiteSpace(user.UserType)) flags.Add($"type=\"{user.UserType}\"");
- flags.Add(room.GuestAccess switch {
- "can_join" => "guests allowed",
- "forbidden" => null,
- // TODO: default?
- null => null,
- "" => null,
- _ => "unknown guest access: " + room.GuestAccess,
- });
-
- flags = flags.Where(x => x != null).ToList();
- }
+ flags = flags.Where(x => x != null).ToList();
}
<span>@string.Join(", ", flags)</span>
- @if (room.JoinedLocalMembers == 0 && flags.Count > 0) {
- <span> at the time of leaving</span>
- }
<br/>
- <span>@room.StateEvents state events, room version @(room.Version ?? "1")</span><br/>
- @if (room.TombstoneEvent is not null) {
- var tombstoneContent = room.TombstoneEvent.ContentAs<RoomTombstoneEventContent>()!;
- <span>Room is tombstoned! Target room: @tombstoneContent.ReplacementRoom, message: @tombstoneContent.Body</span><br/>
- }
-
- @{
- var memberSummary = room.MemberSummary;
- if (room.LocalMembers is not null) {
- memberSummary += $": {string.Join(", ", room.LocalMembers)}";
- }
- }
- <span>@memberSummary</span>
<details>
<summary>Full result data</summary>
- <pre>@room.ToJson(ignoreNull: true)</pre>
+ <pre>@user.ToJson(ignoreNull: true)</pre>
</details>
</div>
}
-@* *@
-@* @if (DeleteRequest.HasValue) { *@
-@* <ModalWindow MinWidth="600" Title="@("Delete " + DeleteRequest.Value.RoomId)" OnCloseClicked="@(() => { DeleteRequest = null; })"> *@
-@* *@
-@* </ModalWindow> *@
-@* } *@
-
-@* @foreach (var (roomId, status) in DeleteStatuses) { *@
-@* <ModalWindow Title="@("Delete status for " + roomId)" MinWidth="600"> *@
-@* <pre>@status.ToJson()</pre> *@
-@* </ModalWindow> *@
-@* } *@
-
-@foreach (var (roomId, deleteRequest) in DeleteRequests) {
- <ModalWindow Title="@($"Delete room {roomId}")" OnCloseClicked="@(() => {
- DeleteRequests.Remove(roomId);
- StateHasChanged();
- })">
- <SynapseRoomShutdownWindowContent Context="deleteRequest" Homeserver="Homeserver"/>
- </ModalWindow>
-}
-
-<style>
-
-</style>
@code {
@@ -180,17 +103,11 @@
[SupplyParameterFromQuery(Name = "ascending")]
public bool Ascending { get; set; } = true;
- private List<RoomInfo> Results { get; set; } = new();
+ private List<SynapseAdminUserListResult.SynapseAdminUserListResultUser> Results { get; set; } = new();
private AuthenticatedHomeserverSynapse Homeserver { get; set; } = null!;
- private SynapseAdminLocalRoomQueryFilter Filter { get; set; } = new();
-
- private Dictionary<string, SynapseRoomShutdownWindowContent.RoomShutdownContext> DeleteRequests { get; set; } = [];
-
- // private Dictionary<string, SynapseAdminRoomDeleteStatus> DeleteStatuses { get; set; } = new();
-
- private NamedCache<SynapseRoomShutdownWindowContent.RoomShutdownContext> TaskMap { get; set; } = null!;
+ private SynapseAdminLocalUserQueryFilter Filter { get; set; } = new();
protected override async Task OnInitializedAsync() {
var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
@@ -200,8 +117,6 @@
}
Homeserver = synapse;
- TaskMap = new NamedCache<SynapseRoomShutdownWindowContent.RoomShutdownContext>(Homeserver, "gay.rory.matrixutils.synapse_room_shutdown_tasks");
- DeleteRequests = (await TaskMap.ReadCacheMapAsync()).Where(x => x.Value.DeleteId is not null).ToDictionary();
StateHasChanged();
}
@@ -212,59 +127,59 @@
foreach (var (key, value) in QueryHelpers.ParseQuery(new Uri(NavigationManager.Uri).Query)) {
switch (key) {
- case "RoomIdContains":
- Filter.RoomIdContains = value[0]!;
- break;
- case "NameContains":
- Filter.NameContains = value[0]!;
- break;
- case "CanonicalAliasContains":
- Filter.CanonicalAliasContains = value[0]!;
- break;
- case "VersionContains":
- Filter.VersionContains = value[0]!;
- break;
- case "CreatorContains":
- Filter.CreatorContains = value[0]!;
- break;
- case "EncryptionContains":
- Filter.EncryptionContains = value[0]!;
- break;
- case "JoinRulesContains":
- Filter.JoinRulesContains = value[0]!;
- break;
- case "GuestAccessContains":
- Filter.GuestAccessContains = value[0]!;
- break;
- case "HistoryVisibilityContains":
- Filter.HistoryVisibilityContains = value[0]!;
- break;
- case "Federatable":
- Filter.Federatable = bool.Parse(value[0]!);
- Filter.CheckFederation = true;
- break;
- case "Public":
- Filter.Public = value[0] == "true";
- Filter.CheckPublic = true;
- break;
- case "JoinedMembersGreaterThan":
- Filter.JoinedMembersGreaterThan = int.Parse(value[0]!);
- break;
- case "JoinedMembersLessThan":
- Filter.JoinedMembersLessThan = int.Parse(value[0]!);
- break;
- case "JoinedLocalMembersGreaterThan":
- Filter.JoinedLocalMembersGreaterThan = int.Parse(value[0]!);
- break;
- case "JoinedLocalMembersLessThan":
- Filter.JoinedLocalMembersLessThan = int.Parse(value[0]!);
- break;
- case "StateEventsGreaterThan":
- Filter.StateEventsGreaterThan = int.Parse(value[0]!);
- break;
- case "StateEventsLessThan":
- Filter.StateEventsLessThan = int.Parse(value[0]!);
- break;
+ // case "RoomIdContains":
+ // Filter.RoomIdContains = value[0]!;
+ // break;
+ // case "NameContains":
+ // Filter.NameContains = value[0]!;
+ // break;
+ // case "CanonicalAliasContains":
+ // Filter.CanonicalAliasContains = value[0]!;
+ // break;
+ // case "VersionContains":
+ // Filter.VersionContains = value[0]!;
+ // break;
+ // case "CreatorContains":
+ // Filter.CreatorContains = value[0]!;
+ // break;
+ // case "EncryptionContains":
+ // Filter.EncryptionContains = value[0]!;
+ // break;
+ // case "JoinRulesContains":
+ // Filter.JoinRulesContains = value[0]!;
+ // break;
+ // case "GuestAccessContains":
+ // Filter.GuestAccessContains = value[0]!;
+ // break;
+ // case "HistoryVisibilityContains":
+ // Filter.HistoryVisibilityContains = value[0]!;
+ // break;
+ // case "Federatable":
+ // Filter.Federatable = bool.Parse(value[0]!);
+ // Filter.CheckFederation = true;
+ // break;
+ // case "Public":
+ // Filter.Public = value[0] == "true";
+ // Filter.CheckPublic = true;
+ // break;
+ // case "JoinedMembersGreaterThan":
+ // Filter.JoinedMembersGreaterThan = int.Parse(value[0]!);
+ // break;
+ // case "JoinedMembersLessThan":
+ // Filter.JoinedMembersLessThan = int.Parse(value[0]!);
+ // break;
+ // case "JoinedLocalMembersGreaterThan":
+ // Filter.JoinedLocalMembersGreaterThan = int.Parse(value[0]!);
+ // break;
+ // case "JoinedLocalMembersLessThan":
+ // Filter.JoinedLocalMembersLessThan = int.Parse(value[0]!);
+ // break;
+ // case "StateEventsGreaterThan":
+ // Filter.StateEventsGreaterThan = int.Parse(value[0]!);
+ // break;
+ // case "StateEventsLessThan":
+ // Filter.StateEventsLessThan = int.Parse(value[0]!);
+ // break;
case "Execute":
execute = true;
break;
@@ -282,28 +197,11 @@
private async Task Search() {
Results.Clear();
- var searchRooms = Homeserver.Admin.SearchRoomsAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", searchTerm: SearchTerm, localFilter: Filter).GetAsyncEnumerator();
+ var searchRooms = Homeserver.Admin.SearchUsersAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", localFilter: Filter).GetAsyncEnumerator();
while (await searchRooms.MoveNextAsync()) {
var room = searchRooms.Current;
- var roomInfo = new RoomInfo {
- RoomId = room.RoomId,
- Name = room.Name,
- CanonicalAlias = room.CanonicalAlias,
- Creator = room.Creator,
- Version = room.Version,
- Encryption = room.Encryption,
- Federatable = room.Federatable,
- Public = room.Public,
- JoinRules = room.JoinRules,
- GuestAccess = room.GuestAccess,
- HistoryVisibility = room.HistoryVisibility,
- StateEvents = room.StateEvents,
- JoinedMembers = room.JoinedMembers,
- JoinedLocalMembers = room.JoinedLocalMembers
- };
-
- Results.Add(roomInfo);
+ Results.Add(room);
if ((Results.Count <= 200 && Results.Count % 10 == 0) || Results.Count % 1000 == 0) {
StateHasChanged();
@@ -314,120 +212,32 @@
StateHasChanged();
- var getLocalMembersTasks = Results
- .Where(x => x.JoinedLocalMembers is > 0 and < 100)
- .Select(async r => {
- var members = (await Homeserver.Admin.GetRoomMembersAsync(r.RoomId)).Members.Where(x => x.EndsWith(":" + Homeserver.ServerName)).ToList();
- r.LocalMembers = members;
- }
- );
- await Task.WhenAll(getLocalMembersTasks);
-
- var getTombstoneTasks = Results
- .Select(async r => {
- var state = await Homeserver.Admin.GetRoomStateAsync(r.RoomId, type: "m.room.tombstone");
- var tombstone = state.Events.FirstOrDefault(x => x is { StateKey: "", Type: "m.room.tombstone" });
- if (tombstone is { } tombstoneEvent) {
- r.TombstoneEvent = tombstoneEvent;
- }
- });
- await Task.WhenAll(getTombstoneTasks);
-
StateHasChanged();
}
- Task DeleteRoom(RoomInfo room) {
- DeleteRequests.TryAdd(room.RoomId, new() { RoomId = room.RoomId, RoomDetails = room, DeleteRequest = new() { Block = true, Purge = true, ForcePurge = false } });
- StateHasChanged();
-
- return Task.CompletedTask;
- }
-
- //
- // private async Task DeleteRoom() {
- // if (DeleteRequest is { } deleteRequest) {
- // var media = await Homeserver.Admin.GetRoomMediaAsync(deleteRequest.RoomId);
- // if (deleteRequest.DeleteRequest.QuarantineRemoteMedia) {
- // foreach (var remoteMedia in media.Remote) {
- // await Homeserver.Admin.QuarantineMediaById(remoteMedia);
- // }
- // }
- //
- // if (deleteRequest.DeleteRequest.DeleteRemoteMedia) {
- // foreach (var remoteMedia in media.Remote) {
- // await Homeserver.Admin.DeleteMediaById(remoteMedia);
- // }
- // }
- // else if (deleteRequest.DeleteRequest.QuarantineLocalMedia) {
- // foreach (var localMedia in media.Local) {
- // await Homeserver.Admin.QuarantineMediaById(localMedia);
- // }
- // }
- //
- // var deleteId = await Homeserver.Admin.DeleteRoom(deleteRequest.RoomId, deleteRequest.DeleteRequest, waitForCompletion: false);
- // DeleteRequest = null;
- // List<string> alreadyCleanedUsers = [];
- // while (true) {
- // var status = await Homeserver.Admin.GetRoomDeleteStatus(deleteId.DeleteId);
- // DeleteStatuses[deleteRequest.RoomId] = status;
- // StateHasChanged();
- // await Task.Delay(5000);
- // if (status.Status == "complete") {
- // DeleteStatuses.Remove(deleteRequest.RoomId);
- // StateHasChanged();
- // break;
- // }
- //
- // if (status.Status == "failed") {
- // deleteId = await Homeserver.Admin.DeleteRoom(deleteRequest.RoomId, deleteRequest.DeleteRequest, waitForCompletion: false);
- // }
- //
- // var newCleanedUsers = status.ShutdownRoom?.KickedUsers?.Except(alreadyCleanedUsers).ToList();
- // if (newCleanedUsers is not null) {
- // alreadyCleanedUsers.AddRange(newCleanedUsers);
- // foreach (var user in newCleanedUsers) {
- // if (deleteRequest.DeleteRequest.SuspendLocalUsers) {
- // // await Homeserver.Admin.(user);
- // }
- //
- // if (deleteRequest.DeleteRequest.QuarantineLocalUserMedia) {
- // await Homeserver.Admin.QuarantineMediaByUserId(user);
- // }
- //
- // if (deleteRequest.DeleteRequest.DeleteLocalUserMedia) {
- // var userMedia = Homeserver.Admin.GetUserMediaEnumerableAsync(user);
- // await foreach (var mediaEntry in userMedia) {
- // await Homeserver.Admin.DeleteMediaById(mediaEntry.MediaId);
- // }
- // }
- // }
- // }
- // }
- // }
- // }
-
private readonly Dictionary<string, string> validOrderBy = new() {
- { "name", "Room name" },
- { "canonical_alias", "Main alias address" },
- { "joined_members", "Number of members (reversed)" },
- { "joined_local_members", "Number of local members (reversed)" },
- { "version", "Room version" },
- { "creator", "Creator of the room" },
- { "encryption", "End-to-end encryption algorithm" },
- { "federatable", "Is room federated" },
- { "public", "Visibility in room list" },
- { "join_rules", "Join rules" },
- { "guest_access", "Guest access" },
- { "history_visibility", "Visibility of history" },
- { "state_events", "Number of state events" }
+ { "name", "User name" },
+ { "is_guest", "Guest status" },
+ { "admin", "Admin status" },
+ { "user_type", "User type" },
+ { "deactivated", "Deactivation status" },
+ { "shadow_banned", "Shadow banned status" },
+ { "displayname", "Display name" },
+ { "avatar_url", "Avatar URL" },
+ { "creation_ts", "Creation time" },
+ { "last_seen_ts", "Last activity" },
};
- private class RoomInfo : SynapseAdminRoomListResult.SynapseAdminRoomListResultRoom {
- public List<string>? LocalMembers { get; set; }
- public StateEventResponse? TombstoneEvent { get; set; }
+ private async Task Login(SynapseAdminUserListResult.SynapseAdminUserListResultUser user) {
+ var loginResult = await Homeserver.Admin.LoginUserAsync(user.Name, TimeSpan.FromDays(1));
+ await sessionStore.AddSession(new() {
+ AccessToken = loginResult.AccessToken,
+ DeviceId = loginResult.DeviceId,
+ UserId = loginResult.UserId,
+ Homeserver = Homeserver.ServerName,
+ Proxy = Homeserver.Proxy
+ });
- [field: AllowNull, MaybeNull]
- public string MemberSummary => field ??= $"{JoinedMembers} members, of which {JoinedLocalMembers} are on this server";
}
}
diff --git a/MatrixUtils.Web/Pages/Index.razor b/MatrixUtils.Web/Pages/Index.razor
index 509c634..82ee0f2 100644
--- a/MatrixUtils.Web/Pages/Index.razor
+++ b/MatrixUtils.Web/Pages/Index.razor
@@ -4,6 +4,7 @@
@using LibMatrix
@using ArcaneLibs
@using System.Diagnostics
+@using LibMatrix.Responses.Federation
<PageTitle>Index</PageTitle>
@@ -58,9 +59,9 @@ Small collection of tools to do not-so-everyday things.
</td>
<td>
<p>
- <LinkButton OnClick="@(() => ManageUser(session.SessionId))">Manage</LinkButton>
- <LinkButton OnClick="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
- <LinkButton OnClick="@(() => RemoveUser(session.SessionId, true))">Log out</LinkButton>
+ <LinkButton OnClickAsync="@(() => ManageUser(session.SessionId))">Manage</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemoveUser(session.SessionId, true))">Log out</LinkButton>
</p>
</td>
</tr>
@@ -89,7 +90,7 @@ Small collection of tools to do not-so-everyday things.
</p>
</td>
<td>
- <LinkButton OnClick="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
</td>
</tr>
}
@@ -118,10 +119,10 @@ Small collection of tools to do not-so-everyday things.
</p>
</td>
<td>
- <LinkButton OnClick="@(() => Task.Run(() => NavigationManager.NavigateTo($"/InvalidSession?ctx={session.SessionId}")))">Re-login</LinkButton>
+ <LinkButton OnClickAsync="@(() => Task.Run(() => NavigationManager.NavigateTo($"/InvalidSession?ctx={session.SessionId}")))">Re-login</LinkButton>
</td>
<td>
- <LinkButton OnClick="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
</td>
</tr>
}
diff --git a/MatrixUtils.Web/Pages/InvalidSession.razor b/MatrixUtils.Web/Pages/InvalidSession.razor
index 1ec99f6..f86d112 100644
--- a/MatrixUtils.Web/Pages/InvalidSession.razor
+++ b/MatrixUtils.Web/Pages/InvalidSession.razor
@@ -8,14 +8,14 @@
@if (_auth is not null) {
<p>It appears that the affected user is @_auth.UserId (@_auth.DeviceId) on @_auth.Homeserver!</p>
- <LinkButton OnClick="@(OpenRefreshDialog)">Refresh token</LinkButton>
- <LinkButton OnClick="@(RemoveUser)">Remove</LinkButton>
+ <LinkButton OnClickAsync="@(OpenRefreshDialog)">Refresh token</LinkButton>
+ <LinkButton OnClickAsync="@(RemoveUser)">Remove</LinkButton>
@if (_showRefreshDialog) {
<ModalWindow MinWidth="300" X="275" Y="300" Title="@($"Password for {_auth.UserId}")">
<FancyTextBox IsPassword="true" @bind-Value="@_password"></FancyTextBox>
<br/>
- <LinkButton OnClick="TryLogin">Log in</LinkButton>
+ <LinkButton OnClickAsync="TryLogin">Log in</LinkButton>
@if (_loginException is not null) {
<pre style="color: red;">@_loginException.RawContent</pre>
}
diff --git a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor
index 8831dd1..56c8cfe 100644
--- a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor
+++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor
@@ -1,7 +1,7 @@
@using ClientContext = MatrixUtils.Web.Pages.Labs.Client.Index.ClientContext
@* user header and room list *@
@foreach (var room in Data.SyncWrapper.Rooms) {
- <LinkButton OnClick="@(async () => Data.SelectedRoom = room)" Color="@(Data.SelectedRoom == room ? "#FF00FF" : "")">
+ <LinkButton OnClickAsync="@(async () => Data.SelectedRoom = room)" Color="@(Data.SelectedRoom == room ? "#FF00FF" : "")">
@room.RoomName
</LinkButton>
<br/>
diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor
index a974a8f..360548d 100644
--- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor
+++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor
@@ -28,8 +28,8 @@
</p>
<br/>
- <LinkButton OnClick="@Disband" Color="#FF0000">Disband</LinkButton>
- <LinkButton OnClick="@Execute">Next</LinkButton>
+ <LinkButton OnClickAsync="@Disband" Color="#FF0000">Disband</LinkButton>
+ <LinkButton OnClickAsync="@Execute">Next</LinkButton>
}
else {
<p>Discovering spaces, please wait...</p>
diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor
index 9f31647..25d1629 100644
--- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor
+++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor
@@ -31,7 +31,7 @@ else {
}
<br/>
-<LinkButton OnClick="@Execute">Next</LinkButton>
+<LinkButton OnClickAsync="@Execute">Next</LinkButton>
@{
var _offset = 0;
@@ -41,7 +41,7 @@ else {
<p>Found room assigned to multiple users: <RoomListItem RoomInfo="@room"></RoomListItem></p>
<p>Users:</p>
@foreach (var userProfileResponse in usersList) {
- <LinkButton OnClick="@(() => SetRoomAssignment(room.Room.RoomId, userProfileResponse.Id))">
+ <LinkButton OnClickAsync="@(() => SetRoomAssignment(room.Room.RoomId, userProfileResponse.Id))">
<span>Assign to </span>
<InlineUserItem User="userProfileResponse"></InlineUserItem>
</LinkButton>
@@ -54,7 +54,7 @@ else {
<ModalWindow Title="Re-assign DM" OnCloseClicked="@(() => DmToReassign = null)">
<RoomListItem RoomInfo="@DmToReassign"></RoomListItem>
@foreach (var userProfileResponse in roomMembers[DmToReassign]) {
- <LinkButton OnClick="@(() => SetRoomAssignment(DmToReassign.Room.RoomId, userProfileResponse.Id))">
+ <LinkButton OnClickAsync="@(() => SetRoomAssignment(DmToReassign.Room.RoomId, userProfileResponse.Id))">
<span>Assign to </span>
<InlineUserItem User="userProfileResponse"></InlineUserItem>
</LinkButton>
diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor
index e5a8b22..2bed22e 100644
--- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor
+++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor
@@ -59,7 +59,7 @@ else {
}
<br/>
-<LinkButton OnClick="@Execute">Next</LinkButton>
+<LinkButton OnClickAsync="@Execute">Next</LinkButton>
@code {
diff --git a/MatrixUtils.Web/Pages/LoginPage.razor b/MatrixUtils.Web/Pages/LoginPage.razor
index 88577a2..38ede74 100644
--- a/MatrixUtils.Web/Pages/LoginPage.razor
+++ b/MatrixUtils.Web/Pages/LoginPage.razor
@@ -22,8 +22,8 @@
<FancyTextBox @bind-Value="@newRecordInput.Proxy"></FancyTextBox>
</span>
<br/>
-<LinkButton OnClick="@AddRecord">Add account to queue</LinkButton>
-<LinkButton OnClick="@(() => Login(newRecordInput))">Log in</LinkButton>
+<LinkButton OnClickAsync="@AddRecord">Add account to queue</LinkButton>
+<LinkButton OnClickAsync="@(() => Login(newRecordInput))">Log in</LinkButton>
<br/>
<br/>
@@ -44,7 +44,7 @@
<FancyTextBox @bind-Value="@newRecordInput.Proxy"></FancyTextBox>
</span>
<br/>
-<LinkButton OnClick="@(() => AddWithAccessToken(newRecordInput))">Add session</LinkButton>
+<LinkButton OnClickAsync="@(() => AddWithAccessToken(newRecordInput))">Add session</LinkButton>
<br/>
<br/>
@@ -101,7 +101,7 @@
}
</table>
<br/>
-<LinkButton OnClick="@LoginAll">Log in</LinkButton>
+<LinkButton OnClickAsync="@LoginAll">Log in</LinkButton>
@code {
diff --git a/MatrixUtils.Web/Pages/Rooms/Create2.razor b/MatrixUtils.Web/Pages/Rooms/Create2.razor
new file mode 100644
index 0000000..19f6fbc
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/Create2.razor
@@ -0,0 +1,159 @@
+@page "/Rooms/Create2"
+@using System.Text.Json
+@using System.Reflection
+@using ArcaneLibs
+@using ArcaneLibs.Extensions
+@using Blazored.LocalStorage
+@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
+@using LibMatrix.Helpers
+@using LibMatrix.Responses
+@using MatrixUtils.Web.Classes.RoomCreationTemplates
+@using MatrixUtils.Web.Pages.Rooms.RoomCreateComponents
+@inject ILogger<Create2> logger
+@* @* ReSharper disable once RedundantUsingDirective - Must not remove this, Rider marks this as "unused" when it's not */ *@
+
+<h3>Room Manager - Create Room</h3>
+
+@if (Ready) {
+ <style>
+ table.table-top-first-tr tr td:first-child {
+ vertical-align: top;
+ }
+ </style>
+ <table class="table-top-first-tr">
+ @if (roomBuilder is RoomUpgradeBuilder roomUpgrade) {
+ <RoomCreateUpgradeOptions roomUpgrade="@roomUpgrade" PageStateHasChanged="@StateHasChanged"/>
+ }
+ else {
+ @* <tr style="padding-bottom: 16px;"> *@
+ @* <td>Preset:</td> *@
+ @* <td> *@
+ @* @if (Presets is null) { *@
+ @* <p style="color: red;">Presets is null!</p> *@
+ @* } *@
+ @* else { *@
+ @* <p style="color: red;">Support for presets is currently disabled!</p> *@
+ @* $1$ <InputSelect @bind-Value="@RoomPreset"> #1# *@
+ @* $1$ @foreach (var createRoomRequest in Presets) { #1# *@
+ @* $1$ <option value="@createRoomRequest.Key">@createRoomRequest.Key</option> #1# *@
+ @* $1$ } #1# *@
+ @* $1$ </InputSelect> #1# *@
+ @* } *@
+ @* </td> *@
+ @* </tr> *@
+ }
+ <RoomCreateBasicRoomInfoOptions roomBuilder="@roomBuilder" PageStateHasChanged="@StateHasChanged" Homeserver="@Homeserver"/>
+ <RoomCreateCreateOptions roomBuilder="@roomBuilder" PageStateHasChanged="@StateHasChanged" Homeserver="@Homeserver"/>
+ <RoomCreatePrivacyOptions roomBuilder="@roomBuilder" PageStateHasChanged="@StateHasChanged" Homeserver="@Homeserver"/>
+ <RoomCreatePermissionsOptions roomBuilder="@roomBuilder" PageStateHasChanged="@StateHasChanged" Homeserver="@Homeserver"/>
+ <RoomCreateMembershipOptions roomBuilder="@roomBuilder" PageStateHasChanged="@StateHasChanged" Homeserver="@Homeserver"/>
+ @* Initial states, should remain at bottom *@
+ </table>
+ <LinkButton OnClickAsync="@CreateRoom">Create room</LinkButton>
+ <br/>
+ <div
+ style="position: fixed; top: 56px; right: 0; width: fit-content; max-width: 25%; height: calc(100vh - 56px); overflow: auto; background-color: #2c3054; padding-right: 32px; border-left: 1px solid #ccc;">
+ <details open>
+ <summary>RoomBuilder state</summary>
+ <InputCheckbox @bind-Value="@ShowNullInState"/>
+ <span>Show null values</span><br/>
+ <pre>
+ @roomBuilder.ToJson(ignoreNull: !ShowNullInState)
+ </pre>
+ </details>
+ </div>
+}
+@if (_matrixException is not null) {
+ <ModalWindow Title="@("Matrix exception: " + _matrixException.ErrorCode)">
+ <pre>
+ @_matrixException.Message
+ </pre>
+ </ModalWindow>
+}
+
+@code {
+
+#region State
+
+ [Parameter, SupplyParameterFromQuery(Name = "previousRoomId")]
+ public string? PreviousRoomId { get; set; }
+
+ private bool ShowNullInState { get; set; }
+
+ private bool Ready { get; set; }
+
+ private RoomBuilder roomBuilder { get; set; } = new();
+
+ private AuthenticatedHomeserverGeneric? Homeserver { get; set; }
+
+ private MatrixException? _matrixException { get; set; }
+
+#endregion
+
+#region Presets
+
+ private Dictionary<string, CreateRoomRequest>? Presets { get; set; } = new();
+ // private string RoomPreset {
+ // get => Presets.ContainsValue(roomBuilder) ? Presets.First(x => x.Value == roomBuilder).Key : "Not a preset";
+ // set {
+ // roomBuilder = Presets[value];
+ // JsonChanged();
+ // StateHasChanged();
+ // }
+ // }
+
+#endregion
+
+ protected override async Task OnInitializedAsync() {
+ Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
+ if (Homeserver is null) return;
+ if (!string.IsNullOrWhiteSpace(PreviousRoomId)) {
+ roomBuilder = new RoomUpgradeBuilder(Homeserver.GetRoom(PreviousRoomId));
+ }
+
+ roomBuilder.ServerAcls.Allow = ["*"];
+ roomBuilder.ServerAcls.Deny = [];
+
+ // foreach (var x in Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsClass && !x.IsAbstract && x.GetInterfaces().Contains(typeof(IRoomCreationTemplate))).ToList()) {
+ // 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";
+
+ Ready = true;
+ StateHasChanged();
+ if (roomBuilder is RoomUpgradeBuilder roomUpgrade) {
+ await roomUpgrade.ImportAsync().ConfigureAwait(false);
+ StateHasChanged();
+ }
+ }
+
+ protected override bool ShouldRender() {
+ if (roomBuilder.Type == "")
+ roomBuilder.Type = null; // Reset to null if empty, so it doesn't get sent as an empty string
+ var result = base.ShouldRender();
+ logger.LogInformation("ShouldRender: " + result);
+ return result;
+ }
+
+ private async Task CreateRoom() {
+ Console.WriteLine("Create room");
+ Console.WriteLine(roomBuilder.ToJson());
+ roomBuilder.AdditionalCreationContent["gay.rory.created_using"] = "Rory&::MatrixUtils (https://mru.rory.gay)";
+ try {
+ var newRoom = await roomBuilder.Create(Homeserver);
+ }
+ catch (MatrixException e) {
+ _matrixException = e;
+ }
+ }
+
+}
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
index 5f70187..cdb5894 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
@@ -8,190 +8,118 @@
@using System.Reflection
@using ArcaneLibs.Attributes
@using LibMatrix.EventTypes
-@using LibMatrix.EventTypes.Common
@using LibMatrix.EventTypes.Interop.Draupnir
@using LibMatrix.EventTypes.Spec.State.RoomInfo
-
-@using MatrixUtils.Web.Shared.PolicyEditorComponents
@using SpawnDev.BlazorJS.WebWorkers
+@using MatrixUtils.Web.Pages.Rooms.PolicyListComponents
@inject WebWorkerService WebWorkerService
+@inject ILogger<PolicyList> logger
-<h3>Policy list editor - Editing @(RoomName ?? RoomId)</h3>
-@if (!string.IsNullOrWhiteSpace(DraupnirShortcode)) {
- <span style="margin-right: 2em;">Shortcode: @DraupnirShortcode</span>
-}
-@if (!string.IsNullOrWhiteSpace(RoomAlias)) {
- <span>Alias: @RoomAlias</span>
-}
-<hr/>
-@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@
-<LinkButton OnClick="@(() => {
- CurrentlyEditingEvent = new() { Type = "", RawContent = new() };
- return Task.CompletedTask;
- })">Create new policy
-</LinkButton>
-<LinkButton OnClick="@(() => {
- MassCreatePolicies = true;
- return Task.CompletedTask;
- })">Create many new policies
-</LinkButton>
-<LinkButton OnClick="@LoadStatesAsync">Refresh</LinkButton>
-
-@if (Loading) {
- <p>Loading...</p>
-}
-else if (PolicyEventsByType is not { Count: > 0 }) {
- <p>No policies yet</p>
+@if (!IsInitialised) {
+ <p>Connecting to homeserver...</p>
}
else {
- var renderSw = Stopwatch.StartNew();
- var renderTotalSw = Stopwatch.StartNew();
- @foreach (var (type, value) in PolicyEventsByType) {
- <p>
- @(GetValidPolicyEventsByType(type).Count) active,
- @(GetInvalidPolicyEventsByType(type).Count) invalid,
- @(GetRemovedPolicyEventsByType(type).Count) removed
- (@value.Count total)
- @(GetPolicyTypeName(type).ToLower())
- </p>
- }
-
- Console.WriteLine($"Rendered header in {renderSw.GetElapsedAndRestart()}");
-
- var renderSw2 = Stopwatch.StartNew();
- IOrderedEnumerable<Type> policiesByType = KnownPolicyTypes.Where(t => GetPolicyEventsByType(t).Count > 0).OrderByDescending(t => GetPolicyEventsByType(t).Count);
- Console.WriteLine($"Ordered policy types by count in {renderSw2.GetElapsedAndRestart()}");
-
- foreach (var type in policiesByType) {
- <details>
- <summary>
- <span>
- @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies")
- </span>
- <hr style="margin: revert;"/>
- </summary>
- <table class="table table-striped table-hover">
- @{
- var renderSw3 = Stopwatch.StartNew();
- var policies = GetValidPolicyEventsByType(type);
- var invalidPolicies = GetInvalidPolicyEventsByType(type);
- // enumerate all properties with friendly name
- var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
- .Where(x => (x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyNameOrNull()) is not null)
- .Where(x => x.GetCustomAttribute<TableHideAttribute>() is null)
- .ToFrozenSet();
- var propNames = props.Select(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName()!).ToFrozenSet();
-
- var proxySafeProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
- .Where(x => props.Any(y => y.Name == x.Name))
- .ToFrozenSet();
- Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}");
- Console.WriteLine($"Filtered policies and got properties in {renderSw3.GetElapsedAndRestart()}");
- }
- <thead>
- <tr>
- @foreach (var name in propNames) {
- <th>@name</th>
- }
- <th>Actions</th>
- </tr>
- </thead>
- <tbody style="border-width: 1px;">
- @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue<string>())) {
- <tr>
- @{
- var typedContent = policy.TypedContent!;
- }
- @foreach (var prop in proxySafeProps ?? Enumerable.Empty<PropertyInfo>()) {
- if (prop.Name == "Entity") {
- <td>@TruncateMxid((string)prop.GetGetMethod()?.Invoke(typedContent, null)!)</td>
- }
- else {
- <td>@prop.GetGetMethod()?.Invoke(typedContent, null)</td>
- }
- }
- <td>
- <div style="display: ruby;">
- @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) {
- <LinkButton OnClick="@(() => {
- CurrentlyEditingEvent = policy;
- return Task.CompletedTask;
- })">Edit
- </LinkButton>
- <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Remove</LinkButton>
- @if (policy.IsLegacyType) {
- <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton>
- }
-
- @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.Type)) {
- <LinkButton OnClick="@(() => {
- ServerPolicyToMakePermanent = policy;
- return Task.CompletedTask;
- })">Make permanent
- </LinkButton>
- @if (CurrentUserIsDraupnir) {
- <LinkButton Color="@(ActiveKicks.ContainsKey(policy) ? "#FF0000" : null)" OnClick="@(() => DraupnirKickMatching(policy))">Kick
- users @(ActiveKicks.TryGetValue(policy, out var kick) ? $"({kick})" : null)
- </LinkButton>
- }
- }
- }
- else {
- <p>No permission to modify</p>
- }
- </div>
- </td>
- </tr>
- }
- </tbody>
- </table>
- <details>
- <summary>
- <u>
- @("Invalid " + GetPolicyTypeName(type).ToLower())
- </u>
- </summary>
- <table class="table table-striped table-hover">
- <thead>
- <tr>
- <th>State key</th>
- <th>Json contents</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var policy in invalidPolicies) {
- <tr>
- <td>@policy.StateKey</td>
- <td>
- <pre>@policy.RawContent.ToJson(true, false)</pre>
- </td>
- </tr>
- }
- </tbody>
- </table>
- </details>
- </details>
+ <PolicyListEditorHeader Room="@Room" ReloadStateAsync="@(() => LoadStateAsync(true))"></PolicyListEditorHeader>
+ @if (Loading) {
+ <p>Loading...</p>
}
+ // else if (PolicyEventsByType is not { Count: > 0 }) {
+ @* <p>No policies yet</p> *@
+ // }
+ else {
+ var renderSw = Stopwatch.StartNew();
+ var renderTotalSw = Stopwatch.StartNew();
+ @foreach (var value in PolicyCollections.Values.OrderByDescending(x => x.TotalCount)) {
+ <p>
+ @value.ActivePolicies.Count active,
+ @value.RemovedPolicies.Count removed
+ (@value.TotalCount total)
+ @value.Name.ToLower()
+ </p>
+ }
- Console.WriteLine($"Rendered policies in {renderSw.GetElapsedAndRestart()}");
- Console.WriteLine($"Rendered in {renderTotalSw.Elapsed}");
-}
+ // logger.LogInformation($"Rendered header in {renderSw.GetElapsedAndRestart()}");
-@if (CurrentlyEditingEvent is not null) {
- <PolicyEditorModal PolicyEvent="@CurrentlyEditingEvent" OnClose="@(() => CurrentlyEditingEvent = null)" OnSave="@(e => UpdatePolicyAsync(e))"></PolicyEditorModal>
-}
+ // var renderSw2 = Stopwatch.StartNew();
+ // IOrderedEnumerable<Type> policiesByType = KnownPolicyTypes.Where(t => GetPolicyEventsByType(t).Count > 0).OrderByDescending(t => GetPolicyEventsByType(t).Count);
+ // logger.LogInformation($"Ordered policy types by count in {renderSw2.GetElapsedAndRestart()}");
-@if (ServerPolicyToMakePermanent is not null) {
- <ModalWindow Title="Make policy permanent">
-
- </ModalWindow>
-}
+ foreach (var collection in PolicyCollections.Values.OrderByDescending(x => x.ActivePolicies.Count)) {
+ <PolicyListCategoryComponent PolicyCollection="@collection" Room="@Room"></PolicyListCategoryComponent>
+ }
-@if (MassCreatePolicies) {
- <MassPolicyEditorModal Room="@Room" OnClose="@(() => MassCreatePolicies = false)" OnSaved="@(() => {
- MassCreatePolicies = false;
- _ = LoadStatesAsync();
- })"></MassPolicyEditorModal>
+ // foreach (var type in policiesByType) {
+ @* foreach (var type in (List<Type>) []) { *@
+ @* <details> *@
+ @* <summary> *@
+ @* <span> *@
+ @* @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies") *@
+ @* </span> *@
+ @* <hr style="margin: revert;"/> *@
+ @* </summary> *@
+ @* <table class="table table-striped table-hover table-bordered align-middle"> *@
+ @* @{ *@
+ @* var renderSw3 = Stopwatch.StartNew(); *@
+ @* var policies = GetValidPolicyEventsByType(type); *@
+ @* var invalidPolicies = GetInvalidPolicyEventsByType(type); *@
+ @* // enumerate all properties with friendly name *@
+ @* var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) *@
+ @* .Where(x => (x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyNameOrNull()) is not null) *@
+ @* .Where(x => x.GetCustomAttribute<TableHideAttribute>() is null) *@
+ @* .ToFrozenSet(); *@
+ @* var propNames = props.Select(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName()!).ToFrozenSet(); *@
+ @* *@
+ @* var proxySafeProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) *@
+ @* .Where(x => props.Any(y => y.Name == x.Name)) *@
+ @* .ToFrozenSet(); *@
+ @* logger.LogInformation($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}"); *@
+ @* logger.LogInformation($"Filtered policies and got properties in {renderSw3.GetElapsedAndRestart()}"); *@
+ @* } *@
+ @* <thead> *@
+ @* <tr> *@
+ @* @foreach (var name in propNames) { *@
+ @* <th>@name</th> *@
+ @* } *@
+ @* <th>Actions</th> *@
+ @* </tr> *@
+ @* </thead> *@
+ @* <tbody> *@
+ @* @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue<string>())) { *@
+ @* <PolicyListRowComponent PolicyInfo="@policy" Room="@Room"></PolicyListRowComponent> *@
+ @* } *@
+ @* </tbody> *@
+ @* </table> *@
+ @* <details> *@
+ @* <summary> *@
+ @* <u> *@
+ @* @("Invalid " + GetPolicyTypeName(type).ToLower()) *@
+ @* </u> *@
+ @* </summary> *@
+ @* <table class="table table-striped table-hover"> *@
+ @* <thead> *@
+ @* <tr> *@
+ @* <th>State key</th> *@
+ @* <th>Json contents</th> *@
+ @* </tr> *@
+ @* </thead> *@
+ @* <tbody> *@
+ @* @foreach (var policy in invalidPolicies) { *@
+ @* <tr> *@
+ @* <td>@policy.StateKey</td> *@
+ @* <td> *@
+ @* <pre>@policy.RawContent.ToJson(true, false)</pre> *@
+ @* </td> *@
+ @* </tr> *@
+ @* } *@
+ @* </tbody> *@
+ @* </table> *@
+ @* </details> *@
+ @* </details> *@
+ // }
+
+ // logger.LogInformation($"Rendered policies in {renderSw.GetElapsedAndRestart()}");
+ logger.LogInformation($"Rendered in {renderTotalSw.Elapsed}");
+ }
}
@code {
@@ -202,6 +130,7 @@ else {
private const bool Debug = false;
#endif
+ private bool IsInitialised { get; set; } = false;
private bool Loading { get; set; } = true;
[Parameter]
@@ -209,14 +138,6 @@ else {
private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new();
- private StateEventResponse? CurrentlyEditingEvent {
- get;
- set {
- field = value;
- StateHasChanged();
- }
- }
-
public StateEventResponse? ServerPolicyToMakePermanent {
get;
set {
@@ -225,22 +146,12 @@ else {
}
}
- private AuthenticatedHomeserverGeneric Homeserver { get; set; }
- private GenericRoom Room { get; set; }
- private RoomPowerLevelEventContent PowerLevels { get; set; }
+ private AuthenticatedHomeserverGeneric Homeserver { get; set; } = null!;
+ private GenericRoom Room { get; set; } = null!;
+ private RoomPowerLevelEventContent PowerLevels { get; set; } = null!;
public bool CurrentUserIsDraupnir { get; set; }
- public string? RoomName { get; set; }
- public string? RoomAlias { get; set; }
- public string? DraupnirShortcode { get; set; }
- public Dictionary<StateEventResponse, int> ActiveKicks { get; set; } = [];
- public bool MassCreatePolicies {
- get;
- set {
- field = value;
- StateHasChanged();
- }
- }
+ public Dictionary<StateEventResponse, int> ActiveKicks { get; set; } = [];
protected override async Task OnInitializedAsync() {
var sw = Stopwatch.StartNew();
@@ -248,64 +159,234 @@ else {
Homeserver = (await sessionStore.GetCurrentHomeserver(navigateOnFailure: true))!;
if (Homeserver is null) return;
Room = Homeserver.GetRoom(RoomId!);
+ IsInitialised = true;
+ StateHasChanged();
await Task.WhenAll(
Task.Run(async () => { PowerLevels = (await Room.GetPowerLevelsAsync())!; }),
- Task.Run(async () => { DraupnirShortcode = (await Room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode; }),
- Task.Run(async () => { RoomAlias = (await Room.GetCanonicalAliasAsync())?.Alias; }),
- Task.Run(async () => { RoomName = await Room.GetNameOrFallbackAsync(); }),
Task.Run(async () => { CurrentUserIsDraupnir = (await Homeserver.GetAccountDataOrNullAsync<object>(DraupnirProtectedRoomsData.EventId)) is not null; })
);
StateHasChanged();
- await LoadStatesAsync();
- Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!");
+ await LoadStateAsync(firstLoad: true);
+ Loading = false;
+ logger.LogInformation($"Policy list editor initialized in {sw.Elapsed}!");
}
- private async Task LoadStatesAsync() {
+ private async Task LoadStateAsync(bool firstLoad = false) {
+ var sw = Stopwatch.StartNew();
+ // Loading = true;
+ // var states = Room.GetFullStateAsync();
+ var states = await Room.GetFullStateAsListAsync();
+ // PolicyEventsByType.Clear();
+
+ logger.LogInformation($"LoadStatesAsync: Loaded state in {sw.Elapsed}");
+
+ foreach (var type in KnownPolicyTypes) {
+ if (!PolicyCollections.ContainsKey(type)) {
+ var filterPropSw = Stopwatch.StartNew();
+ // enumerate all properties with friendly name
+ var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(x => (x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyNameOrNull()) is not null)
+ .Where(x => x.GetCustomAttribute<TableHideAttribute>() is null)
+ .ToFrozenSet();
+
+ var proxySafeProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(x => props.Any(y => y.Name == x.Name))
+ .ToFrozenDictionary(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName(), x => x);
+ logger.LogInformation($"{proxySafeProps?.Count} proxy safe props found in {type.FullName} ({filterPropSw.Elapsed})");
+ PolicyCollections.Add(type, new() {
+ Name = type.GetFriendlyNamePluralOrNull() ?? type.FullName ?? type.Name,
+ ActivePolicies = [],
+ RemovedPolicies = [],
+ PropertiesToDisplay = proxySafeProps
+ });
+ }
+ }
+
+ var count = 0;
+ var parseSw = Stopwatch.StartNew();
+ foreach (var evt in states) {
+ var sw2 = Stopwatch.StartNew();
+ var mappedType = evt.MappedType;
+ logger.LogInformation($"Processing state #{count++:000000} {evt.Type} @ {sw.Elapsed} (took {parseSw.Elapsed:c} so far to process)");
+ if (!mappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue;
+
+ var collection = PolicyCollections[mappedType];
+
+ var key = (evt.Type, evt.StateKey!);
+ var policyInfo = new PolicyCollection.PolicyInfo {
+ Policy = evt,
+ MadeRedundantBy = []
+ };
+ if (evt.RawContent is null or { Count: 0 } || string.IsNullOrWhiteSpace(evt.RawContent?["recommendation"]?.GetValue<string>())) {
+ collection.ActivePolicies.Remove(key);
+ if (!collection.RemovedPolicies.TryAdd(key, policyInfo)) {
+ if (StateEvent.Equals(collection.RemovedPolicies[key].Policy, evt)) continue;
+ collection.RemovedPolicies[key] = policyInfo;
+ }
+ }
+ else {
+ collection.RemovedPolicies.Remove(key);
+ if (!collection.ActivePolicies.TryAdd(key, policyInfo)) {
+ if (StateEvent.Equals(collection.ActivePolicies[key].Policy, evt)) continue;
+ collection.ActivePolicies[key] = policyInfo;
+ }
+ }
+ }
+
+ logger.LogInformation($"LoadStatesAsync: Processed state in {sw.Elapsed}");
+ foreach (var collection in PolicyCollections) {
+ logger.LogInformation($"Policy collection {collection.Key.FullName} has {collection.Value.ActivePolicies.Count} active and {collection.Value.RemovedPolicies.Count} removed policies.");
+ }
+
+ logger.LogInformation("LoadStatesAsync: Scanning for redundant policies...");
+
+ Loading = false;
+ var allPolicies = PolicyCollections.Values
+ .SelectMany(x => x.ActivePolicies.Values)
+ .Select(x => (x, x.Policy.TypedContent as PolicyRuleEventContent))
+ .ToList();
+ var wildcardPolicies = allPolicies
+ .Where(x => x.Item2!.IsGlobRule() || x.Item2 is ServerPolicyRuleEventContent)
+ .ToList();
+ Console.WriteLine($"Got {allPolicies.Count} total policies, {wildcardPolicies.Count} wildcard policies.");
+ int i = 0;
+ int hits = 0;
+ int redundant = 0;
+ int duplicates = 0;
+ foreach (var policy in allPolicies) {
+ if (policy.Item2 is null) continue;
+ var matchingPolicies = wildcardPolicies
+ .Where(x =>
+ !StateEvent.TypeKeyPairMatches(policy.x.Policy, x.x.Policy)
+ && x.Item2!.EntityMatches(policy.Item2.Entity!)
+ )
+ .ToList();
+
+ if (matchingPolicies.Count > 0) {
+ logger.LogInformation($"{i} Got {matchingPolicies.Count} hits for {policy.x.Policy.RawContent.ToJson()}: {matchingPolicies.Select(x => x.x.Policy.RawContent).ToJson()}");
+ foreach (var match in matchingPolicies) {
+ policy.x.MadeRedundantBy.Add(match.x.Policy);
+ }
+
+ hits++;
+ redundant += matchingPolicies.Count;
+
+ if (hits % 5 == 0)
+ StateHasChanged();
+ }
+ else logger.LogInformation("Sleeping...");
+ await Task.Delay(1);
+ i++;
+ }
+
+ i = 0;
+ foreach (var policy in allPolicies) {
+ if (policy.Item2 is null) continue;
+ var matchingPolicies = allPolicies
+ .Where(x =>
+ !StateEvent.TypeKeyPairMatches(policy.x.Policy, x.x.Policy)
+ && x.Item2!.Entity == policy.Item2.Entity!
+ )
+ .ToList();
+
+ if (matchingPolicies.Count > 0) {
+ logger.LogInformation($"{i} Got {matchingPolicies.Count} duplicates for {policy.x.Policy.RawContent.ToJson()}: {matchingPolicies.Select(x => x.x.Policy.RawContent).ToJson()}");
+ foreach (var match in matchingPolicies) {
+ policy.x.MadeRedundantBy.Add(match.x.Policy);
+ }
+
+ hits++;
+ redundant += matchingPolicies.Count;
+
+ if (hits % 5 == 0)
+ StateHasChanged();
+ }
+ else logger.LogInformation("Sleeping...");
+ await Task.Delay(1);
+ i++;
+ }
+
+ logger.LogInformation($"LoadStatesAsync: Found {hits} ({redundant} redundant, {duplicates} duplicates) redundant policies in {sw.Elapsed}");
+ StateHasChanged();
+ }
+
+ // the old one:
+ private async Task LoadStatesAsync(bool firstLoad = false) {
+ await LoadStateAsync(firstLoad);
+ return;
var sw = Stopwatch.StartNew();
Loading = true;
// var states = Room.GetFullStateAsync();
var states = await Room.GetFullStateAsListAsync();
// PolicyEventsByType.Clear();
- Console.WriteLine($"LoadStatesAsync: Loaded state in {sw.Elapsed}");
+ logger.LogInformation($"LoadStatesAsync: Loaded state in {sw.Elapsed}");
- foreach (var state in states) {
- if (state is null) continue;
- if (!state.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue;
+ foreach (var type in KnownPolicyTypes) {
+ if (!PolicyEventsByType.ContainsKey(type))
+ PolicyEventsByType.Add(type, new List
+ <StateEventResponse>(16000));
+ }
- if (!PolicyEventsByType.ContainsKey(state.MappedType)) PolicyEventsByType.Add(state.MappedType, new());
+ int count = 0;
+ foreach (var state in states) {
+ var _spsw = Stopwatch.StartNew();
+ TimeSpan e1, e2, e3, e4, e5, e6, t;
+ if (!state.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue;
+ e1 = _spsw.Elapsed;
var targetPolicies = PolicyEventsByType[state.MappedType];
+ e2 = _spsw.Elapsed;
+ if (!firstLoad && targetPolicies.FirstOrDefault(x => StateEvent.TypeKeyPairMatches(x, state)) is { } evt) {
+ e3 = _spsw.Elapsed;
+ if (StateEvent.Equals(evt, state)) {
+ if (count % 100 == 0) {
+ await Task.Delay(10);
+ await Task.Yield();
+ }
- if (targetPolicies.FirstOrDefault(x => StateEvent.TypeKeyPairMatches(x, state)) is { } evt) {
- if (StateEvent.Equals(evt, state)) continue;
+ e4 = _spsw.Elapsed;
+ logger.LogInformation($"[E] LoadStatesAsync: Processed state #{count++:000000} {state.Type} @ {sw.Elapsed} (e1={e1:c}, e2={e2:c}, e3={e3:c}, e4={e4:c}, e5={TimeSpan.Zero:c},t={_spsw.Elapsed:c})");
+ continue;
+ }
+
+ e4 = _spsw.Elapsed;
targetPolicies.Remove(evt);
+ e5 = _spsw.Elapsed;
+ targetPolicies.Add(state);
+ e6 = _spsw.Elapsed;
+ t = _spsw.Elapsed;
+ logger.LogInformation($"[M] LoadStatesAsync: Processed state #{count++:000000} {state.Type} @ {sw.Elapsed} (e1={e1:c}, e2={e2:c}, e3={e3:c}, e4={e4:c}, e5={e5:c}, e6={e6:c},t={t:c})");
+ }
+ else {
targetPolicies.Add(state);
+ t = _spsw.Elapsed;
+ logger.LogInformation($"[N] LoadStatesAsync: Processed state #{count++:000000} {state.Type} @ {sw.Elapsed} (e1={e1:c}, e2={e2:c}, e3={TimeSpan.Zero:c}, e4={TimeSpan.Zero:c}, e5={TimeSpan.Zero:c}, e6={TimeSpan.Zero:c}, t={t:c})");
}
- else targetPolicies.Add(state);
+
+ // await Task.Delay(10);
+ // await Task.Yield();
}
- Console.WriteLine($"LoadStatesAsync: Processed state in {sw.Elapsed}");
+ logger.LogInformation($"LoadStatesAsync: Processed state in {sw.Elapsed}");
Loading = false;
StateHasChanged();
await Task.Delay(10);
await Task.Yield();
- Console.WriteLine($"LoadStatesAsync: yield finished in {sw.Elapsed}");
+ logger.LogInformation($"LoadStatesAsync: yield finished in {sw.Elapsed}");
}
- // private List<StateEventResponse> AllPolicies { get; set; } = [];
-
private List<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : [];
- private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
- .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
-
- private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
- .Where(x => x.RawContent is { Count: > 0 } && string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
-
- private List<StateEventResponse> GetRemovedPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
- .Where(x => x.RawContent is null or { Count: 0 }).ToList();
+ // private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ // .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
+ //
+ // private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ // .Where(x => x.RawContent is { Count: > 0 } && string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
+ //
+ // private List<StateEventResponse> GetRemovedPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ // .Where(x => x.RawContent is null or { Count: 0 }).ToList();
private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull()
?? type.GetCustomAttributes<MatrixEventAttribute>()
@@ -313,23 +394,6 @@ else {
private string GetPolicyTypeName(Type type) => GetPolicyTypeNameOrNull(type) ?? type.Name;
- private async Task RemovePolicyAsync(StateEventResponse policyEvent) {
- await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, new { });
- PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent);
- await LoadStatesAsync();
- }
-
- private async Task UpdatePolicyAsync(StateEventResponse policyEvent) {
- await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, policyEvent.RawContent);
- CurrentlyEditingEvent = null;
- await LoadStatesAsync();
- }
-
- private async Task UpgradePolicyAsync(StateEventResponse policyEvent) {
- policyEvent.RawContent["gay.rory.matrixutils.upgraded_from_type"] = policyEvent.Type;
- await LoadStatesAsync();
- }
-
private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
// event types, unnamed
@@ -339,153 +403,28 @@ else {
private static Dictionary<Type, string[]> PolicyTypeIds = KnownPolicyTypes
.ToDictionary(x => x, x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName).ToArray());
- private static string TruncateMxid(string mxid) {
- var parts = mxid.Split(':', 2);
- if (parts[0].Length > 50)
- parts[0] = parts[0][..50] + "[...]";
-
- if (parts is [_, { Length: > 50 }])
- parts[1] = parts[1][..50] + "[...]";
-
- return parts.Length == 1 ? parts[0] : $"{parts[0]}:{parts[1]}";
- }
-
- private struct PolicyStats {
- public int Active { get; set; }
- public int Invalid { get; set; }
- public int Removed { get; set; }
- }
-
-#region Draupnir interop
-
- private SemaphoreSlim ss = new(16, 16);
-
- private async Task DraupnirKickMatching(StateEventResponse policy) {
- try {
- var content = policy.TypedContent! as PolicyRuleEventContent;
- if (content is null) return;
- if (string.IsNullOrWhiteSpace(content.Entity)) return;
-
- var data = await Homeserver.GetAccountDataAsync<DraupnirProtectedRoomsData>(DraupnirProtectedRoomsData.EventId);
- var rooms = data.Rooms.Select(Homeserver.GetRoom).ToList();
+ Dictionary<Type, PolicyCollection> PolicyCollections { get; set; } = new();
- ActiveKicks.Add(policy, rooms.Count);
- StateHasChanged();
- await Task.Delay(500);
-
- // for (int i = 0; i < 12; i++) {
- // _ = WebWorkerService.TaskPool.Invoke(WasteCpu);
- // }
-
- // static async Task runKicks(string roomId, PolicyRuleEventContent content) {
- // Console.WriteLine($"Checking {roomId}...");
- // // Console.WriteLine($"Checking {room.RoomId}...");
- // //
- // // try {
- // // var members = await room.GetMembersListAsync();
- // // foreach (var member in members) {
- // // var membership = member.ContentAs<RoomMemberEventContent>();
- // // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue;
- // // if (membership?.Membership is "leave" or "ban") continue;
- // //
- // // if (content.EntityMatches(member.StateKey!))
- // // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given");
- // // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)");
- // // }
- // // }
- // // finally {
- // // Console.WriteLine($"Finished checking {room.RoomId}...");
- // // }
- // }
- //
- // try {
- // var tasks = rooms.Select(room => WebWorkerService.TaskPool.Invoke(runKicks, room.RoomId, content)).ToList();
- //
- // await Task.WhenAll(tasks);
- // }
- // catch (Exception e) {
- // Console.WriteLine(e);
- // }
-
- await NastyInternalsPleaseIgnore.ExecuteKickWithWasmWorkers(WebWorkerService, Homeserver, policy, data.Rooms);
- // await Task.Run(async () => {
- // foreach (var room in rooms) {
- // try {
- // Console.WriteLine($"Checking {room.RoomId}...");
- // var members = await room.GetMembersListAsync();
- // foreach (var member in members) {
- // var membership = member.ContentAs<RoomMemberEventContent>();
- // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue;
- // if (membership?.Membership is "leave" or "ban") continue;
- //
- // if (content.EntityMatches(member.StateKey!))
- // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given");
- // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)");
- // }
- // ActiveKicks[policy]--;
- // StateHasChanged();
- // }
- // finally {
- // Console.WriteLine($"Finished checking {room.RoomId}...");
- // }
- // }
- // });
- }
- finally {
- ActiveKicks.Remove(policy);
- StateHasChanged();
- await Task.Delay(500);
- }
- }
+ public struct PolicyCollection {
+ public required string Name { get; init; }
+ public int TotalCount => ActivePolicies.Count + RemovedPolicies.Count;
-#region Nasty, nasty internals, please ignore!
+ public required Dictionary<(string Type, string StateKey), PolicyInfo> ActivePolicies { get; set; }
- private static class NastyInternalsPleaseIgnore {
- public static async Task ExecuteKickWithWasmWorkers(WebWorkerService workerService, AuthenticatedHomeserverGeneric hs, StateEventResponse evt, List<string> roomIds) {
- try {
- // var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal, hs.WellKnownUris.Client, hs.AccessToken, roomId, content.Entity)).ToList();
- var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal2, hs.WellKnownUris, hs.AccessToken, roomId, evt)).ToList();
- // workerService.TaskPool.Invoke(ExecuteKickInternal, hs.BaseUrl, hs.AccessToken, roomIds, content.Entity);
- await Task.WhenAll(tasks);
- }
- catch (Exception e) {
- Console.WriteLine(e);
- }
- }
-
- private static async Task ExecuteKickInternal(string homeserverBaseUrl, string accessToken, string roomId, string entity) {
- try {
- Console.WriteLine("args: " + string.Join(", ", homeserverBaseUrl, accessToken, roomId, entity));
- Console.WriteLine($"Checking {roomId}...");
- var hs = new AuthenticatedHomeserverGeneric(homeserverBaseUrl, new() { Client = homeserverBaseUrl }, null, accessToken);
- Console.WriteLine($"Got HS...");
- var room = hs.GetRoom(roomId);
- Console.WriteLine($"Got room...");
- var members = await room.GetMembersListAsync();
- Console.WriteLine($"Got members...");
- // foreach (var member in members) {
- // var membership = member.ContentAs<RoomMemberEventContent>();
- // if (member.StateKey == hs.WhoAmI.UserId) continue;
- // if (membership?.Membership is "leave" or "ban") continue;
- //
- // if (entity == member.StateKey)
- // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given");
- // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)");
- // }
- }
- catch (Exception e) {
- Console.WriteLine(e);
- }
- }
+ // public Dictionary<(string Type, string StateKey), StateEventResponse> InvalidPolicies { get; set; }
+ public required Dictionary<(string Type, string StateKey), PolicyInfo> RemovedPolicies { get; set; }
+ public required FrozenDictionary<string, PropertyInfo> PropertiesToDisplay { get; set; }
- private async static Task ExecuteKickInternal2(HomeserverResolverService.WellKnownUris wellKnownUris, string accessToken, string roomId, StateEventResponse policy) {
- Console.WriteLine($"Checking {roomId}...");
- Console.WriteLine(policy.EventId);
+ public struct PolicyInfo {
+ public required StateEventResponse Policy { get; init; }
+ public required List<StateEventResponse> MadeRedundantBy { get; set; }
}
}
-#endregion
-
-#endregion
+ // private struct PolicyStats {
+ // public int Active { get; set; }
+ // public int Invalid { get; set; }
+ // public int Removed { get; set; }
+ // }
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs
new file mode 100644
index 0000000..0106c6e
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs
@@ -0,0 +1,144 @@
+using LibMatrix;
+using LibMatrix.EventTypes.Interop.Draupnir;
+using LibMatrix.EventTypes.Spec.State.Policy;
+using LibMatrix.Homeservers;
+using LibMatrix.Services;
+using SpawnDev.BlazorJS.WebWorkers;
+
+namespace MatrixUtils.Web.Pages.Rooms;
+
+public partial class PolicyList {
+
+#region Draupnir interop
+
+ private SemaphoreSlim ss = new(16, 16);
+
+ private async Task DraupnirKickMatching(StateEventResponse policy) {
+ try {
+ var content = policy.TypedContent! as PolicyRuleEventContent;
+ if (content is null) return;
+ if (string.IsNullOrWhiteSpace(content.Entity)) return;
+
+ var data = await Homeserver.GetAccountDataAsync<DraupnirProtectedRoomsData>(DraupnirProtectedRoomsData.EventId);
+ var rooms = data.Rooms.Select(Homeserver.GetRoom).ToList();
+
+ ActiveKicks.Add(policy, rooms.Count);
+ StateHasChanged();
+ await Task.Delay(500);
+
+ // for (int i = 0; i < 12; i++) {
+ // _ = WebWorkerService.TaskPool.Invoke(WasteCpu);
+ // }
+
+ // static async Task runKicks(string roomId, PolicyRuleEventContent content) {
+ // Console.WriteLine($"Checking {roomId}...");
+ // // Console.WriteLine($"Checking {room.RoomId}...");
+ // //
+ // // try {
+ // // var members = await room.GetMembersListAsync();
+ // // foreach (var member in members) {
+ // // var membership = member.ContentAs<RoomMemberEventContent>();
+ // // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue;
+ // // if (membership?.Membership is "leave" or "ban") continue;
+ // //
+ // // if (content.EntityMatches(member.StateKey!))
+ // // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given");
+ // // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)");
+ // // }
+ // // }
+ // // finally {
+ // // Console.WriteLine($"Finished checking {room.RoomId}...");
+ // // }
+ // }
+ //
+ // try {
+ // var tasks = rooms.Select(room => WebWorkerService.TaskPool.Invoke(runKicks, room.RoomId, content)).ToList();
+ //
+ // await Task.WhenAll(tasks);
+ // }
+ // catch (Exception e) {
+ // Console.WriteLine(e);
+ // }
+
+ await NastyInternalsPleaseIgnore.ExecuteKickWithWasmWorkers(WebWorkerService, Homeserver, policy, data.Rooms);
+ // await Task.Run(async () => {
+ // foreach (var room in rooms) {
+ // try {
+ // Console.WriteLine($"Checking {room.RoomId}...");
+ // var members = await room.GetMembersListAsync();
+ // foreach (var member in members) {
+ // var membership = member.ContentAs<RoomMemberEventContent>();
+ // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue;
+ // if (membership?.Membership is "leave" or "ban") continue;
+ //
+ // if (content.EntityMatches(member.StateKey!))
+ // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given");
+ // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)");
+ // }
+ // ActiveKicks[policy]--;
+ // StateHasChanged();
+ // }
+ // finally {
+ // Console.WriteLine($"Finished checking {room.RoomId}...");
+ // }
+ // }
+ // });
+ }
+ finally {
+ ActiveKicks.Remove(policy);
+ StateHasChanged();
+ await Task.Delay(500);
+ }
+ }
+
+#region Nasty, nasty internals, please ignore!
+
+ private static class NastyInternalsPleaseIgnore {
+ public static async Task ExecuteKickWithWasmWorkers(WebWorkerService workerService, AuthenticatedHomeserverGeneric hs, StateEventResponse evt, List<string> roomIds) {
+ try {
+ // var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal, hs.WellKnownUris.Client, hs.AccessToken, roomId, content.Entity)).ToList();
+ var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal2, hs.WellKnownUris, hs.AccessToken, roomId, evt)).ToList();
+ // workerService.TaskPool.Invoke(ExecuteKickInternal, hs.BaseUrl, hs.AccessToken, roomIds, content.Entity);
+ await Task.WhenAll(tasks);
+ }
+ catch (Exception e) {
+ Console.WriteLine(e);
+ }
+ }
+
+ private static async Task ExecuteKickInternal(string homeserverBaseUrl, string accessToken, string roomId, string entity) {
+ try {
+ Console.WriteLine("args: " + string.Join(", ", homeserverBaseUrl, accessToken, roomId, entity));
+ Console.WriteLine($"Checking {roomId}...");
+ var hs = new AuthenticatedHomeserverGeneric(homeserverBaseUrl, new() { Client = homeserverBaseUrl }, null, accessToken);
+ Console.WriteLine($"Got HS...");
+ var room = hs.GetRoom(roomId);
+ Console.WriteLine($"Got room...");
+ var members = await room.GetMembersListAsync();
+ Console.WriteLine($"Got members...");
+ // foreach (var member in members) {
+ // var membership = member.ContentAs<RoomMemberEventContent>();
+ // if (member.StateKey == hs.WhoAmI.UserId) continue;
+ // if (membership?.Membership is "leave" or "ban") continue;
+ //
+ // if (entity == member.StateKey)
+ // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given");
+ // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)");
+ // }
+ }
+ catch (Exception e) {
+ Console.WriteLine(e);
+ }
+ }
+
+ private async static Task ExecuteKickInternal2(HomeserverResolverService.WellKnownUris wellKnownUris, string accessToken, string roomId, StateEventResponse policy) {
+ Console.WriteLine($"Checking {roomId}...");
+ Console.WriteLine(policy.EventId);
+ }
+ }
+
+#endregion
+
+#endregion
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css
deleted file mode 100644
index afe9fb0..0000000
--- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css
+++ /dev/null
@@ -1,9 +0,0 @@
-th {
- border-width: 1px;
-}
-
-table {
- width: fit-content;
- border-width: 1px;
- vertical-align: middle;
-}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor
index 982fc5a..5d5bb5d 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor
@@ -15,7 +15,7 @@
<h3>Policy list editor - Editing @RoomId</h3>
<hr/>
@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@
-<LinkButton OnClick="@(() => { CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; return Task.CompletedTask; })">Create new policy</LinkButton>
+<LinkButton OnClickAsync="@(() => { CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; return Task.CompletedTask; })">Create new policy</LinkButton>
@if (Loading) {
<p>Loading...</p>
@@ -71,16 +71,16 @@ else {
}
<div style="display: ruby;">
@if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) {
- <LinkButton OnClick="@(() => { CurrentlyEditingEvent = policy; return Task.CompletedTask; })">Edit</LinkButton>
- <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Remove</LinkButton>
+ <LinkButton OnClickAsync="@(() => { CurrentlyEditingEvent = policy; return Task.CompletedTask; })">Edit</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemovePolicyAsync(policy))">Remove</LinkButton>
@if (policy.IsLegacyType) {
- <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton>
}
@if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.EventId)) {
- <LinkButton OnClick="@(() => { ServerPolicyToMakePermanent = policy; return Task.CompletedTask; })">Make permanent (wildcard)</LinkButton>
+ <LinkButton OnClickAsync="@(() => { ServerPolicyToMakePermanent = policy; return Task.CompletedTask; })">Make permanent (wildcard)</LinkButton>
@if (CurrentUserIsDraupnir) {
- <LinkButton OnClick="@(() => UpgradePolicyAsync(policy))">Kick matching users</LinkButton>
+ <LinkButton OnClickAsync="@(() => UpgradePolicyAsync(policy))">Kick matching users</LinkButton>
}
}
else {
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
new file mode 100644
index 0000000..f818b62
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
@@ -0,0 +1,66 @@
+@using ArcaneLibs.Extensions
+@using LibMatrix.RoomTypes
+<details>
+ <summary>
+ <span>
+ @($"{PolicyCollection.Name}: {PolicyCollection.TotalCount} policies")
+ </span>
+ <hr style="margin: revert;"/>
+ </summary>
+ <table class="table table-striped table-hover table-bordered align-middle">
+ <thead>
+ <tr>
+ @foreach (var name in PolicyCollection.PropertiesToDisplay!.Keys) {
+ <th>@name</th>
+ }
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var policy in PolicyCollection.ActivePolicies.Values.OrderBy(x => x.Policy.RawContent?["entity"]?.GetValue<string>())) {
+ <PolicyListRowComponent PolicyInfo="@policy" PolicyCollection="@PolicyCollection" Room="@Room"></PolicyListRowComponent>
+ }
+ </tbody>
+ </table>
+ <details>
+ <summary>
+ <u>
+ @("Invalid " + PolicyCollection.Name.ToLower())
+ </u>
+ </summary>
+ <table class="table table-striped table-hover table-bordered align-middle">
+ <thead>
+ <tr>
+ <th>State key</th>
+ <th>Json contents</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var policy in PolicyCollection.RemovedPolicies.Values) {
+ <tr>
+ <td>@policy.Policy.StateKey</td>
+ <td>
+ <pre>@policy.Policy.RawContent.ToJson(true, false)</pre>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </details>
+</details>
+
+@code {
+
+ [Parameter]
+ public required PolicyList.PolicyCollection PolicyCollection { get; set; }
+
+ [Parameter]
+ public required GenericRoom Room { get; set; }
+
+ protected override bool ShouldRender() {
+ // if (PolicyCollection is null) return false;
+
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor
new file mode 100644
index 0000000..e82f17d
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor
@@ -0,0 +1,65 @@
+@using LibMatrix
+@using LibMatrix.EventTypes.Common
+@using LibMatrix.RoomTypes
+@using MatrixUtils.Web.Shared.PolicyEditorComponents
+<h3>Policy list editor - Editing @(RoomName ?? Room.RoomId)</h3>
+@if (!string.IsNullOrWhiteSpace(DraupnirShortcode)) {
+ <span style="margin-right: 2em;">Shortcode: @DraupnirShortcode</span>
+}
+@if (!string.IsNullOrWhiteSpace(RoomAlias)) {
+ <span>Alias: @RoomAlias</span>
+}
+<hr/>
+@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@
+<LinkButton OnClickAsync="@(() => {
+ CurrentlyEditingEvent = new() { Type = "", RawContent = new() };
+ return Task.CompletedTask;
+ })">Create new policy
+</LinkButton>
+<LinkButton OnClickAsync="@(() => {
+ MassCreatePolicies = true;
+ return Task.CompletedTask;
+ })">Create many new policies
+</LinkButton>
+<LinkButton OnClickAsync="@(() => ReloadStateAsync())">Refresh</LinkButton>
+
+@if (CurrentlyEditingEvent is not null) {
+ <PolicyEditorModal PolicyEvent="@CurrentlyEditingEvent" OnClose="@(() => CurrentlyEditingEvent = null)" OnSaveAsync="@UpdatePolicyAsync"></PolicyEditorModal>
+}
+
+@if (MassCreatePolicies) {
+ <MassPolicyEditorModal Room="@Room" OnClose="@(() => MassCreatePolicies = false)" OnSaved="@(() => {
+ MassCreatePolicies = false;
+ // _ = LoadStatesAsync();
+ })"></MassPolicyEditorModal>
+}
+
+@code {
+ [Parameter]
+ public required GenericRoom Room { get; set; }
+
+ [Parameter]
+ public required Func<Task> ReloadStateAsync { get; set; }
+
+ private string? RoomName { get; set; }
+ private string? RoomAlias { get; set; }
+ private string? DraupnirShortcode { get; set; }
+
+ private StateEventResponse? CurrentlyEditingEvent { get; set { field = value; StateHasChanged(); } }
+ private bool MassCreatePolicies { get; set { field = value; StateHasChanged(); } }
+
+ protected override async Task OnInitializedAsync() {
+ await Task.WhenAll(
+ Task.Run(async () => { DraupnirShortcode = (await Room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode; }),
+ Task.Run(async () => { RoomAlias = (await Room.GetCanonicalAliasAsync())?.Alias; }),
+ Task.Run(async () => { RoomName = await Room.GetNameOrFallbackAsync(); })
+ );
+
+ StateHasChanged();
+ }
+
+ private async Task UpdatePolicyAsync(StateEventResponse evt) {
+ Console.WriteLine("UpdatePolicyAsync in PolicyListEditorHeader not yet implementeD!");
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor
new file mode 100644
index 0000000..11de82c
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor
@@ -0,0 +1,167 @@
+@using System.Reflection
+@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State.Policy
+@using LibMatrix.RoomTypes
+@using MatrixUtils.Web.Shared.PolicyEditorComponents
+
+@if (_isInitialized && IsVisible) {
+ <tr id="@PolicyInfo.Policy.EventId">
+ @foreach (var prop in PolicyCollection.PropertiesToDisplay.Values) {
+ if (prop.Name == "Entity") {
+ <td>
+ <span>@TruncateMxid(TypedContent.Entity)</span>
+ @foreach (var dup in PolicyInfo.MadeRedundantBy) {
+ <br/>
+ <span>Also matched by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId)">@TruncateMxid(dup.RawContent["entity"].GetValue<string>())</a></span>
+ }
+ </td>
+ }
+ else {
+ <td>@prop.GetGetMethod()?.Invoke(TypedContent, null)</td>
+ }
+ }
+ <td>
+ <div style="display: flex; flex-direction: row; gap: 0.5em;">
+ @* @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, Policy.Type)) { *@
+ @if (true) {
+ <LinkButton OnClickAsync="@(() => {
+ IsEditing = true;
+ return Task.CompletedTask;
+ })">Edit
+ </LinkButton>
+ <LinkButton OnClickAsync="@RemovePolicyAsync">Remove</LinkButton>
+ @if (Policy.IsLegacyType) {
+ <LinkButton OnClickAsync="@RemovePolicyAsync">Update policy type</LinkButton>
+ }
+
+ @if (TypedContent.Entity?.StartsWith("@*:", StringComparison.Ordinal) == true) {
+ <LinkButton OnClickAsync="@ConvertToAclAsync">Convert to ACL</LinkButton>
+ }
+
+ @* @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(Policy.Type)) { *@
+ @* <LinkButton OnClickAsync="@(() => { *@
+ @* ServerPolicyToMakePermanent = Policy; *@
+ @* return Task.CompletedTask; *@
+ @* })">Make permanent *@
+ @* </LinkButton> *@
+ @* @if (CurrentUserIsDraupnir) { *@
+ @* <LinkButton Color="@(ActiveKicks.ContainsKey(Policy) ? "#FF0000" : null)" OnClick="@(() => DraupnirKickMatching(Policy))">Kick *@
+ @* users @(ActiveKicks.TryGetValue(Policy, out var kick) ? $"({kick})" : null) *@
+ @* </LinkButton> *@
+ @* } *@
+ // }
+ }
+ else {
+ <p>No permission to modify</p>
+ }
+ </div>
+ </td>
+ </tr>
+
+ @if (IsEditing) {
+ <PolicyEditorModal PolicyEvent="@Policy" OnClose="@(() => IsEditing = false)" OnSaveAsync="@UpdatePolicyAsync"></PolicyEditorModal>
+ }
+ @* TODO: Implement ability to turn ACLs into wildcards *@
+ @*@if (ServerPolicyToMakePermanent is not null) {
+ <ModalWindow Title="Make policy permanent">
+
+ </ModalWindow>
+ }*@
+}
+
+
+
+@code {
+
+ [Parameter]
+ public PolicyList.PolicyCollection.PolicyInfo PolicyInfo { get; set; }
+
+ [Parameter]
+ public GenericRoom Room { get; set; } = null!;
+
+ [Parameter]
+ public required PolicyList.PolicyCollection PolicyCollection { get; set; }
+
+ private StateEventResponse Policy => PolicyInfo.Policy;
+
+ private bool IsEditing {
+ get;
+ set {
+ field = value;
+ _isDirty = true;
+ StateHasChanged();
+ }
+ }
+
+ public bool IsVisible {
+ get;
+ set {
+ field = value;
+ _isDirty = true;
+ }
+ } = true;
+
+ private PolicyRuleEventContent TypedContent { get; set; }
+
+ private bool _isDirty = true;
+ private bool _isInitialized;
+
+ protected override bool ShouldRender() => _isDirty;
+
+ protected override void OnParametersSet() {
+ TypedContent = Policy.TypedContent as PolicyRuleEventContent ?? throw new InvalidOperationException("Policy must have a typed content of type PolicyRuleEventContent.");
+ _isDirty = true;
+ _isInitialized = true;
+ // Console.WriteLine($"ParametersSet {Policy.StateKey}");
+ }
+
+ private static string TruncateMxid(string? mxid) {
+ if (string.IsNullOrWhiteSpace(mxid)) return mxid;
+ var parts = mxid.Split(':', 2);
+ if (parts[0].Length > 50)
+ parts[0] = parts[0][..50] + "[...]";
+
+ if (parts is [_, { Length: > 50 }])
+ parts[1] = parts[1][..50] + "[...]";
+
+ return parts.Length == 1 ? parts[0] : $"{parts[0]}:{parts[1]}";
+ }
+
+ private async Task RemovePolicyAsync() {
+ await Room.SendStateEventAsync(Policy.Type, Policy.StateKey, new { });
+ IsVisible = false;
+ StateHasChanged();
+ // PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent);
+ // await LoadStatesAsync();
+ }
+
+ private async Task UpdatePolicyAsync(StateEventResponse evt) {
+ await Room.SendStateEventAsync(Policy.Type, Policy.StateKey, Policy.RawContent);
+ // CurrentlyEditingEvent = null;
+ // await LoadStatesAsync();
+ }
+
+ private async Task UpgradePolicyAsync() {
+ Policy.RawContent["gay.rory.matrixutils.upgraded_from_type"] = Policy.Type;
+ // await LoadStatesAsync();
+ }
+
+ private async Task ConvertToAclAsync() {
+ if (Policy.RawContent.ContainsKey("entity")) {
+ var newContent = Policy.ContentAs<ServerPolicyRuleEventContent>();
+ newContent!.Entity = newContent.Entity!.Replace("@*:", "");
+ await Room.SendStateEventAsync(ServerPolicyRuleEventContent.EventId, newContent.GetDraupnir2StateKey(), newContent);
+ await Room.SendStateEventAsync(Policy.Type, Policy.StateKey!, new { });
+ IsVisible = false;
+ StateHasChanged();
+ }
+ else {
+ throw new InvalidOperationException("Policy event must contain an 'entity' field to convert to ACL.");
+ }
+ }
+
+ private string Anchor(string anchor) {
+ return $"{NavigationManager.Uri.Split('#')[0]}#{anchor}";
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor
index 2903ab8..6df56ba 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor
@@ -1,12 +1,24 @@
@page "/PolicyLists"
+@using ArcaneLibs
@using ArcaneLibs.Extensions
@using LibMatrix
@using LibMatrix.EventTypes
@using LibMatrix.EventTypes.Common
@using LibMatrix.EventTypes.Spec.State.Policy
+@using LibMatrix.Helpers
+@using LibMatrix.Responses
@using LibMatrix.RoomTypes
@inject ILogger<Index> logger
-<h3>Policy lists </h3> @* <LinkButton href="/Rooms/Create">Create new policy list</LinkButton> *@
+<h3>
+ <span>Policy lists </span>
+ <LinkButton OnClickAsync="@(() => {
+ ShowPolicyListCreationWindow = true;
+ return Task.CompletedTask;
+ })">
+ <span class="oi oi-plus" aria-hidden="true"> Create</span>
+ </LinkButton>
+</h3>
+
@if (!string.IsNullOrWhiteSpace(Status)) {
<p>@Status</p>
@@ -16,22 +28,17 @@
}
<hr/>
-<table>
+<table class="table table-striped table-hover table-bordered align-middle" aria-busy="@isLoading">
<thead>
<tr>
- <th/>
<th>Room name</th>
<th>Policies</th>
+ <th/>
</tr>
</thead>
<tbody>
@foreach (var room in Rooms.OrderByDescending(x => x.PolicyCounts.Sum(y => y.Value))) {
<tr>
- <td>
- <LinkButton href="@($"/Rooms/{room.Room.RoomId}/Policies")">
- <span class="oi oi-pencil" aria-hidden="true"></span>
- </LinkButton>
- </td>
<td style="padding-right: 24px;">
<span>@room.RoomName</span>
@if (room.IsLegacy) {
@@ -50,11 +57,44 @@
<span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Server) ?? 0) server policies</span><br/>
<span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Room) ?? 0) room policies</span><br/>
</td>
+ <td>
+ <LinkButton href="@($"/Rooms/{room.Room.RoomId}/Policies")">
+ <span class="oi oi-pencil" aria-hidden="true"> View/edit policies</span>
+ </LinkButton>
+ </td>
</tr>
}
</tbody>
</table>
+@if (ShowPolicyListCreationWindow && Homeserver != null) {
+ <ModalWindow Title="New policy list">
+ @if (!string.IsNullOrWhiteSpace(_roomBuilder.Avatar.Url)) {
+ <MxcAvatar Homeserver="@Homeserver" MxcUri="@_roomBuilder.Avatar.Url" Circular="true" Size="4" SizeUnit="em"/>
+ }
+ else {
+ <img class="avatar" style="height: 4em; width: 4em; border-radius: 50%;" src="@IdenticonGenerator.GenerateAsDataUri(Homeserver.WhoAmI.UserId)"/>
+ }
+ <div style="display: inline-block; vertical-align: middle; padding-left: 1em;">
+ <FancyTextBox @bind-Value="@_roomBuilder.Name.Name"></FancyTextBox>
+ <br/>
+ <span>#</span>
+ <FancyTextBox @bind-Value="@_roomBuilder.AliasLocalPart"></FancyTextBox>
+ <span>:@Homeserver!.ServerName</span>
+ <br/>
+ <FancyTextBox @bind-Value="@_roomBuilder.Avatar.Url"></FancyTextBox>
+ <InputFile OnChange="@RoomIconFilePicked"></InputFile>
+ </div>
+ <br/>
+
+ <span>Bot shortcode: </span>
+ <FancyTextBox @bind-Value="@_shortcodeEvent.Shortcode"></FancyTextBox>
+ <br/>
+ <LinkButton OnClickAsync="@CreatePolicyList">Create</LinkButton>
+
+ </ModalWindow>
+}
+
@code {
private List<RoomInfo> Rooms { get; } = [];
@@ -65,44 +105,39 @@
Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (Homeserver is null) return;
+ isLoading = true;
Status = "Fetching rooms...";
-
- var userEventTypes = EventContent.GetMatchingEventTypes<UserPolicyRuleEventContent>();
- var serverEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>();
- var roomEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>();
- var knownPolicyTypes = (List<string>) [..userEventTypes, ..serverEventTypes, ..roomEventTypes];
-
- List<GenericRoom> roomsByType = [];
+ List<Task> _tasks = [];
await foreach (var room in Homeserver.GetJoinedRoomsByType("support.feline.policy.lists.msc.v1")) {
- roomsByType.Add(room);
+ // roomsByType.Add(room);
Status2 = $"Found {room.RoomId} (MSC3784)...";
+ _tasks.Add(Task.Run(async () => {
+ Rooms.Add(await RoomInfo.FromRoom(room));
+ StateHasChanged();
+ }));
}
- List<Task<RoomInfo>> tasks = roomsByType.Select(async room => {
- Status2 = $"Fetching room {room.RoomId}...";
- return await RoomInfo.FromRoom(room);
- }).ToList();
+ await Task.WhenAll(_tasks);
- var results = tasks.ToAsyncEnumerable();
- await foreach (var result in results) {
- Rooms.Add(result);
- StateHasChanged();
- }
+ isLoading = false;
+ Status = "";
+ Status2 = "";
+ }
+ private async Task ScanLegacyLists() {
+ isLoading = true;
Status = "Searching for legacy lists...";
-
var rooms = (await Homeserver.GetJoinedRooms())
.Where(x => !Rooms.Any(y => y.Room.RoomId == x.RoomId))
.Select(async room => {
var state = await room.GetFullStateAsListAsync();
var policies = state
- .Where(x => knownPolicyTypes.Contains(x.Type))
+ .Where(x => PolicyRoom.SpecPolicyEventTypes.Contains(x.Type))
.ToList();
if (policies.Count == 0) return null;
Status2 = $"Found legacy list {room.RoomId}...";
return await RoomInfo.FromRoom(room, state, true);
- })
- .ToAsyncEnumerable();
+ }).ToAsyncEnumerable();
await foreach (var room in rooms) {
if (room is not null) {
@@ -110,32 +145,36 @@
StateHasChanged();
}
}
-
+
+ isLoading = false;
Status = "";
Status2 = "";
- await base.OnInitializedAsync();
}
- private string _status;
-
- public string Status {
- get => _status;
+ private string? Status {
+ get;
set {
- _status = value;
+ field = value;
StateHasChanged();
}
}
- private string _status2;
-
- public string Status2 {
- get => _status2;
+ private string? Status2 {
+ get;
set {
- _status2 = value;
+ field = value;
StateHasChanged();
}
}
+ private bool ShowPolicyListCreationWindow {
+ get;
+ set {
+ field = value;
+ StateHasChanged();
+ }
+ } = true;
+
private class RoomInfo {
public GenericRoom Room { get; set; }
public string RoomName { get; set; }
@@ -149,11 +188,6 @@
Server
}
- private static readonly List<string> userEventTypes = EventContent.GetMatchingEventTypes<UserPolicyRuleEventContent>();
- private static readonly List<string> serverEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>();
- private static readonly List<string> roomEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>();
- private static readonly List<string> allKnownPolicyTypes = [..userEventTypes, ..serverEventTypes, ..roomEventTypes];
-
public static async Task<RoomInfo> FromRoom(GenericRoom room, List<StateEventResponse>? state = null, bool legacy = false) {
state ??= await room.GetFullStateAsListAsync();
return new RoomInfo() {
@@ -165,12 +199,40 @@
?? room.RoomId,
Shortcode = (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode,
PolicyCounts = new() {
- { PolicyType.User, state.Count(x => userEventTypes.Contains(x.Type)) },
- { PolicyType.Server, state.Count(x => serverEventTypes.Contains(x.Type)) },
- { PolicyType.Room, state.Count(x => roomEventTypes.Contains(x.Type)) }
+ { PolicyType.User, state.Count(x => PolicyRoom.UserPolicyEventTypes.Contains(x.Type)) },
+ { PolicyType.Server, state.Count(x => PolicyRoom.ServerPolicyEventTypes.Contains(x.Type)) },
+ { PolicyType.Room, state.Count(x => PolicyRoom.RoomPolicyEventTypes.Contains(x.Type)) }
}
};
}
}
+ private readonly RoomBuilder _roomBuilder = new() {
+ Type = "support.feline.policy.lists.msc.v1",
+ Name = new() { Name = "New policy list" },
+ AliasLocalPart = "policies"
+ };
+
+ private readonly MjolnirShortcodeEventContent _shortcodeEvent = new() {
+ Shortcode = "policy-list"
+ };
+
+ private bool isLoading = true;
+
+ 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;
+ StateHasChanged();
+ }
+
+ private async Task CreatePolicyList() {
+ var room = await _roomBuilder.Create(Homeserver!);
+ Status = $"Created policy list {room.RoomId} ({room.GetNameAsync()})";
+ await room.SendStateEventAsync(MjolnirShortcodeEventContent.EventId, _shortcodeEvent);
+ NavigationManager.NavigateTo($"/Rooms/{room.RoomId}/Policies");
+ }
+
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css
deleted file mode 100644
index f9b5b3f..0000000
--- a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css
+++ /dev/null
@@ -1,6 +0,0 @@
-table, th, td {
- border-width: 1px;
-}
-td {
- padding: 8px;
-}
\ No newline at end of file
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
diff --git a/MatrixUtils.Web/Pages/Rooms/Timeline.razor b/MatrixUtils.Web/Pages/Rooms/Timeline.razor
index 108581c..2af819b 100644
--- a/MatrixUtils.Web/Pages/Rooms/Timeline.razor
+++ b/MatrixUtils.Web/Pages/Rooms/Timeline.razor
@@ -3,6 +3,7 @@
@using LibMatrix
@using LibMatrix.EventTypes.Spec
@using LibMatrix.EventTypes.Spec.State.RoomInfo
+@using LibMatrix.Responses
<h3>RoomManagerTimeline</h3>
<hr/>
<p>Loaded @Events.Count events...</p>
diff --git a/MatrixUtils.Web/Pages/ServerInfo.razor b/MatrixUtils.Web/Pages/ServerInfo.razor
index 8dd7907..3da93f2 100644
--- a/MatrixUtils.Web/Pages/ServerInfo.razor
+++ b/MatrixUtils.Web/Pages/ServerInfo.razor
@@ -1,6 +1,7 @@
@page "/ServerInfo/{Homeserver}"
@using LibMatrix.Responses
@using ArcaneLibs.Extensions
+@using LibMatrix.Responses.Federation
<h3>Server info for @Homeserver</h3>
<hr/>
@if (ServerVersionResponse is not null) {
diff --git a/MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor
index 319c9e7..cb56a40 100644
--- a/MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor
@@ -5,10 +5,13 @@
<span>Room ID: </span>
<InputText @bind-Value="@RoomId"></InputText>
<br/>
-<span>Via server: </span>
-<InputText @bind-Value="@Server"></InputText>
+<span>Via server(s), comma separated: </span>
+<InputText @bind-Value="@Servers"></InputText>
<br/>
-<LinkButton OnClick="@Join">Join</LinkButton>
+<span>Unblock room (Synapse): </span>
+<InputCheckbox @bind-Value="@Unblock"></InputCheckbox>
+<br/>
+<LinkButton OnClickAsync="@Join">Join</LinkButton>
<br/><br/>
@foreach (var line in Log) {
<pre>@line</pre>
@@ -21,9 +24,12 @@
[Parameter, SupplyParameterFromQuery(Name = "roomId")]
public string? RoomId { get; set; }
-
+
[Parameter, SupplyParameterFromQuery(Name = "via")]
- public string? Server { get; set; }
+ public string? Servers { get; set; }
+
+ [Parameter, SupplyParameterFromQuery(Name = "unblock")]
+ public bool Unblock { get; set; } = false;
protected override async Task OnInitializedAsync() {
hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
@@ -39,14 +45,19 @@
if (string.IsNullOrWhiteSpace(RoomId)) return;
var room = hs.GetRoom(RoomId);
Log.Add("Got room object...");
-
- if (hs is AuthenticatedHomeserverSynapse synapse) {
- await synapse.Admin.BlockRoom(RoomId, false);
- Log.Add($"Synapse: unblocked room");
+
+ if (Unblock && hs is AuthenticatedHomeserverSynapse synapse) {
+ try {
+ await synapse.Admin.BlockRoom(RoomId, false);
+ Log.Add($"Synapse: unblocked room");
+ }
+ catch (Exception e) {
+ Log.Add($"Synapse: failed to unblock room: {e}");
+ }
}
-
+
try {
- await room.JoinAsync([Server], checkIfAlreadyMember: false);
+ await room.JoinAsync(Servers?.Split(','), checkIfAlreadyMember: false);
Log.Add("Joined room!");
}
catch (Exception e) {
diff --git a/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor
index 7844331..c40fa0b 100644
--- a/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor
@@ -5,7 +5,7 @@
<span>Room ID: </span>
<InputText @bind-Value="@RoomId"></InputText>
<br/>
-<LinkButton OnClick="@Leave">Leave</LinkButton>
+<LinkButton OnClickAsync="@Leave">Leave</LinkButton>
<br/><br/>
@foreach (var line in Log) {
<p>@line</p>
diff --git a/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor
index 0943216..b0f7dbf 100644
--- a/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor
@@ -17,7 +17,7 @@
</details>
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
@foreach (var line in Enumerable.Reverse(log)) {
<p>@line</p>
diff --git a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
index 5c238b3..fcdb3d0 100644
--- a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
@@ -11,7 +11,7 @@
<p>Users: </p>
<InputTextArea @bind-Value="@UserIdString"></InputTextArea>
<br/>
-<InputText @bind-Value="@ImportFromRoomId"></InputText><LinkButton OnClick="@DoImportFromRoomId">Import from room (ID)</LinkButton>
+<InputText @bind-Value="@ImportFromRoomId"></InputText><LinkButton OnClickAsync="@DoImportFromRoomId">Import from room (ID)</LinkButton>
<details>
<summary>Rooms to be searched (@rooms.Count)</summary>
@@ -21,7 +21,7 @@
}
</details>
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
<details>
diff --git a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/InviteCounter.razor
index 2313884..16a3853 100644
--- a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor
+++ b/MatrixUtils.Web/Pages/Tools/InviteCounter.razor
@@ -9,7 +9,7 @@
<br/>
<span>Room ID: </span>
<InputText @bind-Value="@roomId"></InputText>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<br/>
diff --git a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor
index a252e6b..5b0f510 100644
--- a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor
+++ b/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor
@@ -7,7 +7,7 @@
<br/>
<span>Users:</span>
<InputTextArea @bind-Value="@roomId"></InputTextArea>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<br/>
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor
index b0d5a65..1ff97c8 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor
@@ -53,7 +53,7 @@
</div>
}
<br/>
-<LinkButton OnClick="@Apply">Apply</LinkButton>
+<LinkButton OnClickAsync="@Apply">Apply</LinkButton>
@code {
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor
index ea39c9a..9b0266c 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor
@@ -49,7 +49,7 @@
</div>
}
<br/>
-<LinkButton OnClick="@Apply">Apply</LinkButton>
+<LinkButton OnClickAsync="@Apply">Apply</LinkButton>
@code {
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor
index 9e70687..69a9048 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor
@@ -49,7 +49,7 @@
</div>
}
<br/>
-<LinkButton OnClick="@Apply">Apply</LinkButton>
+<LinkButton OnClickAsync="@Apply">Apply</LinkButton>
@code {
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor
index b62cf57..1fd0ff6 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor
@@ -14,10 +14,10 @@
<p>Users (regex): </p>
<InputTextArea @bind-Value="@UserIdString"></InputTextArea>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
-<LinkButton OnClick="RemoveKicks">Remove kicks</LinkButton>
-<LinkButton OnClick="RemoveBans">Remove bans</LinkButton>
+<LinkButton OnClickAsync="RemoveKicks">Remove kicks</LinkButton>
+<LinkButton OnClickAsync="RemoveBans">Remove bans</LinkButton>
<br/>
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor
index 5c5946f..ac68e3d 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor
@@ -9,7 +9,7 @@
<br/>
<span>Room ID: </span>
<InputText @bind-Value="@roomId"></InputText>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<br/>
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor
index 8fdad84..605890d 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor
@@ -8,7 +8,7 @@
<br/>
<span>Users:</span>
<InputTextArea @bind-Value="@roomId"></InputTextArea>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<br/>
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
index 1ec3cd0..11c4a80 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
@@ -16,7 +16,7 @@
<br/>
<span>Room ID: </span>
<InputText @bind-Value="@RoomId"></InputText>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<p>
<span><InputCheckbox @bind-Value="ChronologicalOrder"/>Chronological order</span>
<span><InputCheckbox @bind-Value="DoDisambiguate"/>Enable extended filters</span>
@@ -30,17 +30,17 @@
<span><InputCheckbox @bind-Value="ShowBans"/> bans</span>
</p>
<p>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = false;
StateHasChanged();
})">Hide all
</LinkButton>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = true;
StateHasChanged();
})">Show all
</LinkButton>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
ShowJoins ^= true;
ShowLeaves ^= true;
ShowKnocks ^= true;
@@ -129,17 +129,17 @@
</p>
<p>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = false;
StateHasChanged();
})">Un-disambiguate all
</LinkButton>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = true;
StateHasChanged();
})">Disambiguate all
</LinkButton>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
DisambiguateProfileUpdates ^= true;
DisambiguateKicks ^= true;
DisambiguateUnbans ^= true;
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor
index 31fde0b..ee77532 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor
@@ -8,13 +8,13 @@
<p>Set A: </p>
<InputText @bind-Value="@ImportSetASpaceId"></InputText>
-<LinkButton OnClick="@(() => AppendSet(ImportSetASpaceId, RoomsA))">Append Set A</LinkButton>
+<LinkButton OnClickAsync="@(() => AppendSet(ImportSetASpaceId, RoomsA))">Append Set A</LinkButton>
<p>Set B: </p>
<InputText @bind-Value="@ImportSetBSpaceId"></InputText>
-<LinkButton OnClick="@(() => AppendSet(ImportSetBSpaceId, RoomsB))">Append Set B</LinkButton>
+<LinkButton OnClickAsync="@(() => AppendSet(ImportSetBSpaceId, RoomsB))">Append Set B</LinkButton>
<br/>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<br/>
<details>
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor
index c3cc09c..f39a2eb 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor
@@ -11,7 +11,7 @@
<InputTextArea @bind-Value="@UserIdString"></InputTextArea>
<br/>
<InputText @bind-Value="@ImportFromRoomId"></InputText>
-<LinkButton OnClick="@DoImportFromRoomId">Import from room (ID)</LinkButton>
+<LinkButton OnClickAsync="@DoImportFromRoomId">Import from room (ID)</LinkButton>
<details>
<summary>Rooms to be searched (@rooms.Count)</summary>
@@ -21,7 +21,7 @@
}
</details>
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
<details>
diff --git a/MatrixUtils.Web/Pages/Tools/Room/DropPowerlevel.razor b/MatrixUtils.Web/Pages/Tools/Room/DropPowerlevel.razor
new file mode 100644
index 0000000..208cd19
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/Room/DropPowerlevel.razor
@@ -0,0 +1,51 @@
+@page "/Tools/Room/DropPowerlevel"
+@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
+<h3>DropPowerlevel</h3>
+<hr/>
+
+<span>User ID: </span><FancyTextBox @bind-Value="@UserId"/><br/>
+<span>Room ID: </span><FancyTextBox @bind-Value="@RoomId"/><br/>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
+
+<pre>@Result</pre>
+
+@code {
+ private AuthenticatedHomeserverGeneric? Homeserver { get; set; } = null!;
+
+ [Parameter, SupplyParameterFromQuery(Name = "RoomId")]
+ public string RoomId { get; set; } = "";
+
+ [Parameter, SupplyParameterFromQuery(Name = "UserId")]
+ public string UserId { get; set; } = "";
+
+ private string Result { get; set; } = "";
+
+ protected override async Task OnInitializedAsync() {
+ Homeserver = await sessionStore.GetCurrentHomeserver();
+ Result = "I am: " + Homeserver.WhoAmI.ToJson() + "\n";
+ StateHasChanged();
+ }
+
+ private async Task Execute() {
+ try {
+ if (Homeserver is not AuthenticatedHomeserverGeneric hs) {
+ Result = "Not authenticated";
+ return;
+ }
+
+ var room = hs.GetRoom(RoomId);
+
+ var powerlevels = await room.GetPowerLevelsAsync();
+ powerlevels.Users.Remove(UserId);
+ Result = (await room.SendStateEventAsync(RoomPowerLevelEventContent.EventId, powerlevels)).ToJson();
+ }
+ catch (Exception e) {
+ Result = e.Message;
+ }
+ finally {
+ StateHasChanged();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor b/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor
index 5d5ca20..d6ae945 100644
--- a/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor
+++ b/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor
@@ -10,7 +10,7 @@
<p><InputCheckbox @bind-Value="@ChangeKnocking"/> Change knock access: <InputCheckbox @bind-Value="@Knocking"/></p>
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
<br/>
diff --git a/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor
index e5ffd5b..b893970 100644
--- a/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor
+++ b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor
@@ -12,7 +12,7 @@
}
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
@foreach (var line in Enumerable.Reverse(log)) {
<p>@line</p>
diff --git a/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor
index c373a37..748f2fb 100644
--- a/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor
@@ -13,7 +13,7 @@
}
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
@foreach (var line in Enumerable.Reverse(log)) {
<p>@line</p>
diff --git a/MatrixUtils.Web/Pages/User/Profile.razor b/MatrixUtils.Web/Pages/User/Profile.razor
index ccd3e7b..290b92a 100644
--- a/MatrixUtils.Web/Pages/User/Profile.razor
+++ b/MatrixUtils.Web/Pages/User/Profile.razor
@@ -15,8 +15,8 @@
<span>Display name: </span><FancyTextBox @bind-Value="@NewProfile.DisplayName"></FancyTextBox><br/>
<span>Avatar URL: </span><FancyTextBox @bind-Value="@NewProfile.AvatarUrl"></FancyTextBox>
<InputFile OnChange="@AvatarChanged"></InputFile><br/>
- <LinkButton OnClick="@(() => UpdateProfile())">Update profile</LinkButton>
- <LinkButton OnClick="@(() => UpdateProfile(true))">Update profile (restore room overrides)</LinkButton>
+ <LinkButton OnClickAsync="@(() => UpdateProfile())">Update profile</LinkButton>
+ <LinkButton OnClickAsync="@(() => UpdateProfile(true))">Update profile (restore room overrides)</LinkButton>
</div>
</div>
@if (!string.IsNullOrWhiteSpace(Status)) {
@@ -44,7 +44,7 @@
<span>Display name: </span><FancyTextBox BackgroundColor="@(room.OwnMembership.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@room.OwnMembership.DisplayName"></FancyTextBox><br/>
<span>Avatar URL: </span><FancyTextBox BackgroundColor="@(room.OwnMembership.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@room.OwnMembership.AvatarUrl"></FancyTextBox>
<InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, room.Room.RoomId))"></InputFile><br/>
- <LinkButton OnClick="@(() => UpdateRoomProfile(room.Room.RoomId))">Update profile</LinkButton>
+ <LinkButton OnClickAsync="@(() => UpdateRoomProfile(room.Room.RoomId))">Update profile</LinkButton>
</div>
<br/>
@if (!string.IsNullOrWhiteSpace(Status)) {
diff --git a/MatrixUtils.Web/Shared/InputLocalPart.razor b/MatrixUtils.Web/Shared/InputLocalPart.razor
new file mode 100644
index 0000000..8f34377
--- /dev/null
+++ b/MatrixUtils.Web/Shared/InputLocalPart.razor
@@ -0,0 +1,50 @@
+<div style="display: inline-flex;">
+ @if (!string.IsNullOrWhiteSpace(Label)) {
+ <label>@Label</label>
+ }
+ <span>@Sigil</span>
+ <FancyTextBox @bind-Value="@LocalPart"></FancyTextBox>
+ <span>:</span>
+ @if (ServerNameChanged is not null) {
+ <FancyTextBox @bind-Value="@ServerName"></FancyTextBox>
+ }
+ else {
+ <span>@ServerName</span>
+ }
+</div>
+
+@code {
+
+ [Parameter]
+ public string? Label { get; set; }
+
+ [Parameter]
+ public required string Sigil { get; set; }
+
+ [Parameter]
+ public string? LocalPart {
+ get;
+ set {
+ if (field == value) return;
+ field = value;
+ LocalPartChanged.InvokeAsync(value);
+ }
+ }
+
+ [Parameter]
+ public EventCallback<string> LocalPartChanged { get; set; }
+
+ [Parameter]
+ public string? ServerName {
+ get;
+ set {
+ if (field == value) return;
+ field = value;
+ ServerNameChanged?.InvokeAsync(value);
+ }
+ }
+
+ [Parameter]
+ public EventCallback<string>? ServerNameChanged { get; set; }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
index f7bb200..99dbbc3 100644
--- a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
+++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
@@ -36,15 +36,15 @@
@* </pre> *@
@* </details> *@
@if (!VerifyIntent) {
- <LinkButton OnClick="@(() => {
- OnClose.Invoke();
- return Task.CompletedTask;
- })"> Cancel
+ <LinkButton OnClickAsync="@(() => {
+ OnClose.Invoke();
+ return Task.CompletedTask;
+ })"> Cancel
</LinkButton>
- <LinkButton OnClick="@(() => {
- _ = Save();
- return Task.CompletedTask;
- })"> Save
+ <LinkButton OnClickAsync="@(() => {
+ _ = Save();
+ return Task.CompletedTask;
+ })"> Save
</LinkButton>
@if (!string.IsNullOrWhiteSpace(Response)) {
<pre style="color: red;">@Response</pre>
@@ -63,14 +63,9 @@
VerifyIntent = false;
Response = null;
StateHasChanged();
- return Task.CompletedTask;
})">No
</LinkButton>
- <LinkButton Color="#FF0000" OnClick="@(() => {
- _ = Save(force: true);
- return Task.CompletedTask;
- })"> Yes
- </LinkButton>
+ <LinkButton Color="#FF0000" OnClick="@(() => { _ = Save(force: true); })">Yes</LinkButton>
}
</ModalWindow>
diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor
index 5819bee..0205e16 100644
--- a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor
+++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor
@@ -6,7 +6,7 @@
@using System.Collections.Frozen
@using LibMatrix.EventTypes
<ModalWindow Title="@((string.IsNullOrWhiteSpace(PolicyEvent.EventId) ? "Creating new " : "Editing ") + (PolicyEvent.MappedType.GetFriendlyNameOrNull()?.ToLower() ?? "event"))"
- OnCloseClicked="@OnClose" X="60" Y="60" MinWidth="300">
+ OnCloseClickedAsync="@InvokeOnClose" X="60" Y="60" MinWidth="300">
@if (string.IsNullOrWhiteSpace(PolicyEvent.EventId)) {
<span>Policy type:</span>
<select @bind="@MappedType">
@@ -52,7 +52,12 @@
else {
switch (Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType) {
case Type t when t == typeof(string):
- <FancyTextBox Value="@(getter?.Invoke(PolicyData, null) as string)" ValueChanged="@((string e) => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"></FancyTextBox>
+ <FancyTextBox Value="@(getter?.Invoke(PolicyData, null) as string)" ValueChanged="@((string 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) {
@@ -61,13 +66,22 @@
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>
+ <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>
+ <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>
}
}
@@ -88,8 +102,8 @@
@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>
+ <LinkButton OnClickAsync="@InvokeOnClose">Cancel</LinkButton>
+ <LinkButton OnClickAsync="@InvokeOnSave">Save</LinkButton>
}
else {
<p>Policy data is null</p>
@@ -111,10 +125,32 @@
}
[Parameter]
- public required Action OnClose { get; set; }
+ public Action? OnClose { get; set; }
[Parameter]
- public required Action<StateEventResponse> OnSave { get; set; }
+ public Func<Task>? OnCloseAsync { get; set; }
+
+ private async Task InvokeOnClose() {
+ if (OnClose is not null)
+ OnClose.Invoke();
+
+ if (OnCloseAsync is not null)
+ await OnCloseAsync.Invoke();
+ }
+
+ [Parameter]
+ public Action<StateEventResponse>? OnSave { get; set; }
+
+ [Parameter]
+ public Func<StateEventResponse, Task>? OnSaveAsync { get; set; }
+
+ private async Task InvokeOnSave() {
+ if (OnSave is not null)
+ OnSave.Invoke(PolicyEvent);
+
+ if (OnSaveAsync is not null)
+ await OnSaveAsync.Invoke(PolicyEvent);
+ }
public PolicyRuleEventContent? PolicyData { get; set; }
diff --git a/MatrixUtils.Web/Shared/UserListItem.razor b/MatrixUtils.Web/Shared/UserListItem.razor
index 5084807..fd2fdec 100644
--- a/MatrixUtils.Web/Shared/UserListItem.razor
+++ b/MatrixUtils.Web/Shared/UserListItem.razor
@@ -1,7 +1,12 @@
@using LibMatrix.Responses
@using ArcaneLibs
<div style="background-color: #ffffff11; border-radius: 25px; margin: 8px; width: fit-Content;">
- <img style="@(ChildContent is not null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%;" src="@(string.IsNullOrWhiteSpace(User?.AvatarUrl) ? _identiconGenerator.GenerateAsDataUri(UserId) : User.AvatarUrl)"/>
+ @if (!string.IsNullOrWhiteSpace(User?.AvatarUrl)) {
+ <MxcAvatar Homeserver="@_homeserver" Size="32" Circular="true" MxcUri="@User.AvatarUrl"/>
+ }
+ else {
+ <img style="@(ChildContent is not null ? "vertical-align: baseline;" : "") width: 32px; height: 32px; border-radius: 50%;" src="@_identiconGenerator.GenerateAsDataUri(UserId)"/>
+ }
<span style="vertical-align: middle; margin-right: 8px; border-radius: 75px;">@User?.DisplayName</span>
<div style="display: inline-block;">
@@ -26,7 +31,7 @@
[Parameter]
public AuthenticatedHomeserverGeneric _homeserver { get; set; }
- private SvgIdenticonGenerator _identiconGenerator = new();
+ private static SvgIdenticonGenerator _identiconGenerator = new();
protected override async Task OnInitializedAsync() {
// _homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
@@ -37,7 +42,14 @@
throw new ArgumentNullException(nameof(UserId));
}
- User = await _homeserver.GetProfileAsync(UserId);
+ try {
+ User = await _homeserver.GetProfileAsync(UserId);
+ }
+ catch (Exception) {
+ User = new() {
+ DisplayName = UserId
+ };
+ }
}
await base.OnInitializedAsync();
diff --git a/MatrixUtils.sln b/MatrixUtils.sln
index 6b33671..889232b 100644
--- a/MatrixUtils.sln
+++ b/MatrixUtils.sln
@@ -1,6 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-#
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Web", "MatrixUtils.Web\MatrixUtils.Web.csproj", "{D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Web.Server", "MatrixUtils.Web.Server\MatrixUtils.Web.Server.csproj", "{F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}"
@@ -71,132 +70,407 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Web.Ssr", "Matr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Web.Ssr.Client", "MatrixUtils.Web.Ssr\MatrixUtils.Web.Ssr.Client\MatrixUtils.Web.Ssr.Client.csproj", "{335DB9D5-FEEE-45E3-B76A-057D8BB48412}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Federation", "LibMatrix\LibMatrix.Federation\LibMatrix.Federation.csproj", "{8F154875-96EE-4BE5-8456-F5EBB2516C1C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.FederationTest", "LibMatrix\Utilities\LibMatrix.FederationTest\LibMatrix.FederationTest.csproj", "{960CC2DF-BB1A-4164-A895-834F81B3A113}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Debug|x64.Build.0 = Debug|Any CPU
+ {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Debug|x86.Build.0 = Debug|Any CPU
{D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Release|x64.ActiveCfg = Release|Any CPU
+ {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Release|x64.Build.0 = Release|Any CPU
+ {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Release|x86.ActiveCfg = Release|Any CPU
+ {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Release|x86.Build.0 = Release|Any CPU
{F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Debug|x64.Build.0 = Debug|Any CPU
+ {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Debug|x86.Build.0 = Debug|Any CPU
{F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Release|x64.ActiveCfg = Release|Any CPU
+ {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Release|x64.Build.0 = Release|Any CPU
+ {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Release|x86.ActiveCfg = Release|Any CPU
+ {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Release|x86.Build.0 = Release|Any CPU
{27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|x64.Build.0 = Debug|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|x86.Build.0 = Debug|Any CPU
{27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|Any CPU.ActiveCfg = Release|Any CPU
{27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|Any CPU.Build.0 = Release|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|x64.ActiveCfg = Release|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|x64.Build.0 = Release|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|x86.ActiveCfg = Release|Any CPU
+ {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|x86.Build.0 = Release|Any CPU
{EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Debug|x64.Build.0 = Debug|Any CPU
+ {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Debug|x86.Build.0 = Debug|Any CPU
{EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Release|x64.ActiveCfg = Release|Any CPU
+ {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Release|x64.Build.0 = Release|Any CPU
+ {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Release|x86.ActiveCfg = Release|Any CPU
+ {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Release|x86.Build.0 = Release|Any CPU
{FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|x64.Build.0 = Debug|Any CPU
+ {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|x86.Build.0 = Debug|Any CPU
{FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|x64.ActiveCfg = Release|Any CPU
+ {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|x64.Build.0 = Release|Any CPU
+ {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|x86.ActiveCfg = Release|Any CPU
+ {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|x86.Build.0 = Release|Any CPU
{EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Debug|x64.Build.0 = Debug|Any CPU
+ {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Debug|x86.Build.0 = Debug|Any CPU
{EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Release|x64.ActiveCfg = Release|Any CPU
+ {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Release|x64.Build.0 = Release|Any CPU
+ {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Release|x86.ActiveCfg = Release|Any CPU
+ {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Release|x86.Build.0 = Release|Any CPU
{CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Debug|x64.Build.0 = Debug|Any CPU
+ {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Debug|x86.Build.0 = Debug|Any CPU
{CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Release|x64.ActiveCfg = Release|Any CPU
+ {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Release|x64.Build.0 = Release|Any CPU
+ {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Release|x86.ActiveCfg = Release|Any CPU
+ {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Release|x86.Build.0 = Release|Any CPU
{0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Debug|x64.Build.0 = Debug|Any CPU
+ {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Debug|x86.Build.0 = Debug|Any CPU
{0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Release|x64.ActiveCfg = Release|Any CPU
+ {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Release|x64.Build.0 = Release|Any CPU
+ {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Release|x86.ActiveCfg = Release|Any CPU
+ {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Release|x86.Build.0 = Release|Any CPU
{48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Debug|x64.Build.0 = Debug|Any CPU
+ {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Debug|x86.Build.0 = Debug|Any CPU
{48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Release|x64.ActiveCfg = Release|Any CPU
+ {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Release|x64.Build.0 = Release|Any CPU
+ {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Release|x86.ActiveCfg = Release|Any CPU
+ {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Release|x86.Build.0 = Release|Any CPU
{CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Debug|x64.Build.0 = Debug|Any CPU
+ {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Debug|x86.Build.0 = Debug|Any CPU
{CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Release|x64.ActiveCfg = Release|Any CPU
+ {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Release|x64.Build.0 = Release|Any CPU
+ {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Release|x86.ActiveCfg = Release|Any CPU
+ {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Release|x86.Build.0 = Release|Any CPU
{ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Debug|x64.Build.0 = Debug|Any CPU
+ {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Debug|x86.Build.0 = Debug|Any CPU
{ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Release|x64.ActiveCfg = Release|Any CPU
+ {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Release|x64.Build.0 = Release|Any CPU
+ {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Release|x86.ActiveCfg = Release|Any CPU
+ {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Release|x86.Build.0 = Release|Any CPU
{D6315791-949B-4501-AA95-50516DE899C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6315791-949B-4501-AA95-50516DE899C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D6315791-949B-4501-AA95-50516DE899C1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D6315791-949B-4501-AA95-50516DE899C1}.Debug|x64.Build.0 = Debug|Any CPU
+ {D6315791-949B-4501-AA95-50516DE899C1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D6315791-949B-4501-AA95-50516DE899C1}.Debug|x86.Build.0 = Debug|Any CPU
{D6315791-949B-4501-AA95-50516DE899C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6315791-949B-4501-AA95-50516DE899C1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D6315791-949B-4501-AA95-50516DE899C1}.Release|x64.ActiveCfg = Release|Any CPU
+ {D6315791-949B-4501-AA95-50516DE899C1}.Release|x64.Build.0 = Release|Any CPU
+ {D6315791-949B-4501-AA95-50516DE899C1}.Release|x86.ActiveCfg = Release|Any CPU
+ {D6315791-949B-4501-AA95-50516DE899C1}.Release|x86.Build.0 = Release|Any CPU
{03466515-77CC-49E4-90E5-9A21EDD0A644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03466515-77CC-49E4-90E5-9A21EDD0A644}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {03466515-77CC-49E4-90E5-9A21EDD0A644}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {03466515-77CC-49E4-90E5-9A21EDD0A644}.Debug|x64.Build.0 = Debug|Any CPU
+ {03466515-77CC-49E4-90E5-9A21EDD0A644}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {03466515-77CC-49E4-90E5-9A21EDD0A644}.Debug|x86.Build.0 = Debug|Any CPU
{03466515-77CC-49E4-90E5-9A21EDD0A644}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03466515-77CC-49E4-90E5-9A21EDD0A644}.Release|Any CPU.Build.0 = Release|Any CPU
+ {03466515-77CC-49E4-90E5-9A21EDD0A644}.Release|x64.ActiveCfg = Release|Any CPU
+ {03466515-77CC-49E4-90E5-9A21EDD0A644}.Release|x64.Build.0 = Release|Any CPU
+ {03466515-77CC-49E4-90E5-9A21EDD0A644}.Release|x86.ActiveCfg = Release|Any CPU
+ {03466515-77CC-49E4-90E5-9A21EDD0A644}.Release|x86.Build.0 = Release|Any CPU
{0336306C-285A-4810-9253-5C5F0373992E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0336306C-285A-4810-9253-5C5F0373992E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0336306C-285A-4810-9253-5C5F0373992E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0336306C-285A-4810-9253-5C5F0373992E}.Debug|x64.Build.0 = Debug|Any CPU
+ {0336306C-285A-4810-9253-5C5F0373992E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0336306C-285A-4810-9253-5C5F0373992E}.Debug|x86.Build.0 = Debug|Any CPU
{0336306C-285A-4810-9253-5C5F0373992E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0336306C-285A-4810-9253-5C5F0373992E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0336306C-285A-4810-9253-5C5F0373992E}.Release|x64.ActiveCfg = Release|Any CPU
+ {0336306C-285A-4810-9253-5C5F0373992E}.Release|x64.Build.0 = Release|Any CPU
+ {0336306C-285A-4810-9253-5C5F0373992E}.Release|x86.ActiveCfg = Release|Any CPU
+ {0336306C-285A-4810-9253-5C5F0373992E}.Release|x86.Build.0 = Release|Any CPU
{D7E5B226-114C-4747-9277-A4D6341A16FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7E5B226-114C-4747-9277-A4D6341A16FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7E5B226-114C-4747-9277-A4D6341A16FE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D7E5B226-114C-4747-9277-A4D6341A16FE}.Debug|x64.Build.0 = Debug|Any CPU
+ {D7E5B226-114C-4747-9277-A4D6341A16FE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D7E5B226-114C-4747-9277-A4D6341A16FE}.Debug|x86.Build.0 = Debug|Any CPU
{D7E5B226-114C-4747-9277-A4D6341A16FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7E5B226-114C-4747-9277-A4D6341A16FE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7E5B226-114C-4747-9277-A4D6341A16FE}.Release|x64.ActiveCfg = Release|Any CPU
+ {D7E5B226-114C-4747-9277-A4D6341A16FE}.Release|x64.Build.0 = Release|Any CPU
+ {D7E5B226-114C-4747-9277-A4D6341A16FE}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7E5B226-114C-4747-9277-A4D6341A16FE}.Release|x86.Build.0 = Release|Any CPU
{D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Debug|x64.Build.0 = Debug|Any CPU
+ {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Debug|x86.Build.0 = Debug|Any CPU
{D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Release|x64.ActiveCfg = Release|Any CPU
+ {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Release|x64.Build.0 = Release|Any CPU
+ {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Release|x86.ActiveCfg = Release|Any CPU
+ {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Release|x86.Build.0 = Release|Any CPU
{FA6A9923-419A-40E1-8A32-30DD906E5025}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA6A9923-419A-40E1-8A32-30DD906E5025}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FA6A9923-419A-40E1-8A32-30DD906E5025}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FA6A9923-419A-40E1-8A32-30DD906E5025}.Debug|x64.Build.0 = Debug|Any CPU
+ {FA6A9923-419A-40E1-8A32-30DD906E5025}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FA6A9923-419A-40E1-8A32-30DD906E5025}.Debug|x86.Build.0 = Debug|Any CPU
{FA6A9923-419A-40E1-8A32-30DD906E5025}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA6A9923-419A-40E1-8A32-30DD906E5025}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FA6A9923-419A-40E1-8A32-30DD906E5025}.Release|x64.ActiveCfg = Release|Any CPU
+ {FA6A9923-419A-40E1-8A32-30DD906E5025}.Release|x64.Build.0 = Release|Any CPU
+ {FA6A9923-419A-40E1-8A32-30DD906E5025}.Release|x86.ActiveCfg = Release|Any CPU
+ {FA6A9923-419A-40E1-8A32-30DD906E5025}.Release|x86.Build.0 = Release|Any CPU
{43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Debug|x64.Build.0 = Debug|Any CPU
+ {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Debug|x86.Build.0 = Debug|Any CPU
{43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Release|x64.ActiveCfg = Release|Any CPU
+ {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Release|x64.Build.0 = Release|Any CPU
+ {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Release|x86.ActiveCfg = Release|Any CPU
+ {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Release|x86.Build.0 = Release|Any CPU
{CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Debug|x64.Build.0 = Debug|Any CPU
+ {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Debug|x86.Build.0 = Debug|Any CPU
{CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Release|x64.ActiveCfg = Release|Any CPU
+ {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Release|x64.Build.0 = Release|Any CPU
+ {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Release|x86.ActiveCfg = Release|Any CPU
+ {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Release|x86.Build.0 = Release|Any CPU
{DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Debug|x64.Build.0 = Debug|Any CPU
+ {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Debug|x86.Build.0 = Debug|Any CPU
{DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Release|x64.ActiveCfg = Release|Any CPU
+ {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Release|x64.Build.0 = Release|Any CPU
+ {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Release|x86.ActiveCfg = Release|Any CPU
+ {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Release|x86.Build.0 = Release|Any CPU
{7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Debug|x64.Build.0 = Debug|Any CPU
+ {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Debug|x86.Build.0 = Debug|Any CPU
{7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Release|x64.ActiveCfg = Release|Any CPU
+ {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Release|x64.Build.0 = Release|Any CPU
+ {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Release|x86.ActiveCfg = Release|Any CPU
+ {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Release|x86.Build.0 = Release|Any CPU
{D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Debug|x64.Build.0 = Debug|Any CPU
+ {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Debug|x86.Build.0 = Debug|Any CPU
{D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Release|x64.ActiveCfg = Release|Any CPU
+ {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Release|x64.Build.0 = Release|Any CPU
+ {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Release|x86.Build.0 = Release|Any CPU
{72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Debug|x64.Build.0 = Debug|Any CPU
+ {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Debug|x86.Build.0 = Debug|Any CPU
{72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Release|x64.ActiveCfg = Release|Any CPU
+ {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Release|x64.Build.0 = Release|Any CPU
+ {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Release|x86.ActiveCfg = Release|Any CPU
+ {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Release|x86.Build.0 = Release|Any CPU
{CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Debug|x64.Build.0 = Debug|Any CPU
+ {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Debug|x86.Build.0 = Debug|Any CPU
{CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Release|x64.ActiveCfg = Release|Any CPU
+ {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Release|x64.Build.0 = Release|Any CPU
+ {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Release|x86.ActiveCfg = Release|Any CPU
+ {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Release|x86.Build.0 = Release|Any CPU
{3BD05B05-86DE-4680-A7A0-5A326E41E776}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3BD05B05-86DE-4680-A7A0-5A326E41E776}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Debug|x64.Build.0 = Debug|Any CPU
+ {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Debug|x86.Build.0 = Debug|Any CPU
{3BD05B05-86DE-4680-A7A0-5A326E41E776}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3BD05B05-86DE-4680-A7A0-5A326E41E776}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Release|x64.ActiveCfg = Release|Any CPU
+ {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Release|x64.Build.0 = Release|Any CPU
+ {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Release|x86.ActiveCfg = Release|Any CPU
+ {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Release|x86.Build.0 = Release|Any CPU
{98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Debug|x64.Build.0 = Debug|Any CPU
+ {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Debug|x86.Build.0 = Debug|Any CPU
{98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Release|Any CPU.Build.0 = Release|Any CPU
+ {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Release|x64.ActiveCfg = Release|Any CPU
+ {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Release|x64.Build.0 = Release|Any CPU
+ {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Release|x86.ActiveCfg = Release|Any CPU
+ {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Release|x86.Build.0 = Release|Any CPU
{44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Debug|x64.Build.0 = Debug|Any CPU
+ {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Debug|x86.Build.0 = Debug|Any CPU
{44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Release|Any CPU.Build.0 = Release|Any CPU
+ {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Release|x64.ActiveCfg = Release|Any CPU
+ {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Release|x64.Build.0 = Release|Any CPU
+ {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Release|x86.ActiveCfg = Release|Any CPU
+ {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Release|x86.Build.0 = Release|Any CPU
{CEECE820-1BA9-4E29-8668-25967B3E712B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CEECE820-1BA9-4E29-8668-25967B3E712B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CEECE820-1BA9-4E29-8668-25967B3E712B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CEECE820-1BA9-4E29-8668-25967B3E712B}.Debug|x64.Build.0 = Debug|Any CPU
+ {CEECE820-1BA9-4E29-8668-25967B3E712B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CEECE820-1BA9-4E29-8668-25967B3E712B}.Debug|x86.Build.0 = Debug|Any CPU
{CEECE820-1BA9-4E29-8668-25967B3E712B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CEECE820-1BA9-4E29-8668-25967B3E712B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CEECE820-1BA9-4E29-8668-25967B3E712B}.Release|x64.ActiveCfg = Release|Any CPU
+ {CEECE820-1BA9-4E29-8668-25967B3E712B}.Release|x64.Build.0 = Release|Any CPU
+ {CEECE820-1BA9-4E29-8668-25967B3E712B}.Release|x86.ActiveCfg = Release|Any CPU
+ {CEECE820-1BA9-4E29-8668-25967B3E712B}.Release|x86.Build.0 = Release|Any CPU
{35F510FD-98FC-4760-A53B-9176A53A33A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35F510FD-98FC-4760-A53B-9176A53A33A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Debug|x64.Build.0 = Debug|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Debug|x86.Build.0 = Debug|Any CPU
{35F510FD-98FC-4760-A53B-9176A53A33A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35F510FD-98FC-4760-A53B-9176A53A33A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Release|x64.ActiveCfg = Release|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Release|x64.Build.0 = Release|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Release|x86.ActiveCfg = Release|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Release|x86.Build.0 = Release|Any CPU
{335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Debug|x64.Build.0 = Debug|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Debug|x86.Build.0 = Debug|Any CPU
{335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Release|Any CPU.ActiveCfg = Release|Any CPU
{335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Release|Any CPU.Build.0 = Release|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Release|x64.ActiveCfg = Release|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Release|x64.Build.0 = Release|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Release|x86.ActiveCfg = Release|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Release|x86.Build.0 = Release|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Debug|x64.Build.0 = Debug|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Debug|x86.Build.0 = Debug|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Release|x64.ActiveCfg = Release|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Release|x64.Build.0 = Release|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Release|x86.ActiveCfg = Release|Any CPU
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C}.Release|x86.Build.0 = Release|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Debug|x64.Build.0 = Debug|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Debug|x86.Build.0 = Debug|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Release|Any CPU.Build.0 = Release|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Release|x64.ActiveCfg = Release|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Release|x64.Build.0 = Release|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Release|x86.ActiveCfg = Release|Any CPU
+ {960CC2DF-BB1A-4164-A895-834F81B3A113}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{84BE90C4-2FDE-4A48-B154-58926EF24846} = {933DC8A6-8B1F-46BF-9046-4B636AA46469}
@@ -223,5 +497,7 @@ Global
{3BD05B05-86DE-4680-A7A0-5A326E41E776} = {0641F1C8-8518-4C67-B385-832745C063FD}
{98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53} = {0641F1C8-8518-4C67-B385-832745C063FD}
{44BFB1AD-62FB-4B5B-A5A8-E7D04D731684} = {0641F1C8-8518-4C67-B385-832745C063FD}
+ {8F154875-96EE-4BE5-8456-F5EBB2516C1C} = {933DC8A6-8B1F-46BF-9046-4B636AA46469}
+ {960CC2DF-BB1A-4164-A895-834F81B3A113} = {80828C75-9C5B-442F-86A4-8CE9D85E811C}
EndGlobalSection
EndGlobal
|