diff --git a/.gitignore b/.gitignore
index 984ec54..a6807bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,19 +1,12 @@
+# Dotnet build outputs
**/bin/
**/obj/
-MatrixRoomUtils/
-MatrixRoomUtils.Web/wwwroot/MRU.tar.xz
-/src/
-*.tar.xz
-matrix-sync.json
-/patches/
-MatrixRoomUtils.Bot/bot_data/
-appsettings.Local*.json
-nixpkgs/
+
+# User files
*.DotSettings.user
*.patch
+/patches/
-test.tsv
-test-proxy.tsv
-homeservers.txt
-LoginPayload.txt
-LoginPayload.txt.old
+# Local files:
+appsettings.Local*.json
+.run/local/*.run.xml
\ No newline at end of file
diff --git a/.idea/.idea.ModerationClient/.idea/avalonia.xml b/.idea/.idea.ModerationClient/.idea/avalonia.xml
index 203dd5a..6bc9c45 100644
--- a/.idea/.idea.ModerationClient/.idea/avalonia.xml
+++ b/.idea/.idea.ModerationClient/.idea/avalonia.xml
@@ -8,6 +8,8 @@
<entry key="ModerationClient/Views/LoginView.axaml" value="ModerationClient/ModerationClient.csproj" />
<entry key="ModerationClient/Views/LoginWindow.axaml" value="ModerationClient/ModerationClient.csproj" />
<entry key="ModerationClient/Views/MainWindow.axaml" value="ModerationClient/ModerationClient.csproj" />
+ <entry key="ModerationClient/Views/MainWindow/NotificationPopupWindow.axaml" value="ModerationClient/ModerationClient.csproj" />
+ <entry key="ModerationClient/Views/UserManagementWindow.axaml" value="ModerationClient/ModerationClient.csproj" />
<entry key="ModerationClient/Windows/LoginWindow.axaml" value="ModerationClient/ModerationClient.csproj" />
</map>
</option>
diff --git a/.run/local/.gitkeep b/.run/local/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/.run/local/.gitkeep
diff --git a/LibMatrix b/LibMatrix
-Subproject 3b488242050bbc0521d846bd31cb6ea59b8d4e3
+Subproject bba7333ee6581a92bbbc7479d72325e704fe7fa
diff --git a/ModerationClient.sln b/ModerationClient.sln
index c04d45e..c23825d 100644
--- a/ModerationClient.sln
+++ b/ModerationClient.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-#
+#
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModerationClient", "ModerationClient\ModerationClient.csproj", "{18FFCA62-0CA3-4B24-8708-8EE5C5BBDE41}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LibMatrix", "LibMatrix", "{C5771E60-0DA8-42AC-B54B-94C91B5B719B}"
@@ -153,4 +153,8 @@ Global
{F9BA709C-DA10-4416-A07D-C89293FB0D24} = {D7A5A7D3-4D9E-4B15-9C48-5E75A91483CF}
{046D35DF-E1F2-41DA-94D3-80CF960C100A} = {D7A5A7D3-4D9E-4B15-9C48-5E75A91483CF}
EndGlobalSection
+ GlobalSection(RiderSharedRunConfigurations) = postSolution
+ File = .run\local\ModerationClient (Apothecary) test.run.xml
+ File = .run\local\ModerationClient (Apothecary).run.xml
+ EndGlobalSection
EndGlobal
diff --git a/ModerationClient/App.axaml.cs b/ModerationClient/App.axaml.cs
index db584de..c44b5a2 100644
--- a/ModerationClient/App.axaml.cs
+++ b/ModerationClient/App.axaml.cs
@@ -2,7 +2,6 @@ using System;
using System.IO;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using LibMatrix.Services;
@@ -13,18 +12,12 @@ using Microsoft.Extensions.Hosting;
using ModerationClient.Services;
using ModerationClient.ViewModels;
using ModerationClient.Views;
+using ModerationClient.Views.MainWindow;
namespace ModerationClient;
public partial class App : Application {
- /// <summary>
- /// Gets the current <see cref="App"/> instance in use
- /// </summary>
- public new static App Current => (App)Application.Current;
-
- /// <summary>
- /// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
- /// </summary>
+ public new static App Current => Application.Current as App ?? throw new InvalidOperationException("Application.Current is null");
public IServiceProvider Services => Host.Services;
public IHost Host { get; private set; }
@@ -38,16 +31,10 @@ public partial class App : Application {
var builder = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder(Environment.GetCommandLineArgs());
builder.Services.AddTransient<MainWindowViewModel>();
ConfigureServices(builder.Services);
- // builder.Services.AddHostedService<HostedBackgroundService>();
Host = builder.Build();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
- // Line below is needed to remove Avalonia data validation.
- // Without this line you will get duplicate validations from both Avalonia and CT
BindingPlugins.DataValidators.RemoveAt(0);
- // desktop.MainWindow = new MainWindow {
- // DataContext = Host.Services.GetRequiredService<MainWindowViewModel>()
- // };
desktop.MainWindow = Host.Services.GetRequiredService<MainWindow>();
desktop.Exit += (sender, args) => {
Host.StopAsync(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult();
@@ -59,19 +46,16 @@ public partial class App : Application {
await Host.StartAsync();
}
- /// <summary>
- /// Configures the services for the application.
- /// </summary>
private static IServiceProvider ConfigureServices(IServiceCollection services) {
services.AddRoryLibMatrixServices(new() {
AppName = "ModerationClient",
});
- services.AddSingleton<CommandLineConfiguration>();
+ services.AddSingleton<CommandLineConfiguration>(CommandLineConfiguration.FromProcessArgs());
services.AddSingleton<MatrixAuthenticationService>();
services.AddSingleton<ModerationClientConfiguration>();
- services.AddSingleton<TieredStorageService>(x => {
- var cmdLine = x.GetRequiredService<CommandLineConfiguration>();
+ services.AddSingleton<TieredStorageService>(s => {
+ var cmdLine = s.GetRequiredService<CommandLineConfiguration>();
return new TieredStorageService(
cacheStorageProvider: new FileStorageProvider(Directory.CreateTempSubdirectory($"modcli-{cmdLine.Profile}").FullName),
dataStorageProvider: new FileStorageProvider(Directory.CreateTempSubdirectory($"modcli-{cmdLine.Profile}").FullName)
@@ -79,12 +63,17 @@ public partial class App : Application {
}
);
- // Register views
+ // Register windows
services.AddSingleton<MainWindow>();
+ services.AddTransient<UserManagementWindow>();
+
+ // Register views
services.AddTransient<LoginView>();
services.AddTransient<ClientView>();
+
// Register ViewModels
services.AddTransient<ClientViewModel>();
+ services.AddTransient<UserManagementViewModel>();
return services.BuildServiceProvider();
}
diff --git a/ModerationClient/ModerationClient.csproj b/ModerationClient/ModerationClient.csproj
index 84adbc3..9876af9 100644
--- a/ModerationClient/ModerationClient.csproj
+++ b/ModerationClient/ModerationClient.csproj
@@ -37,14 +37,22 @@
</ItemGroup>
<ItemGroup>
- <Compile Update="Views\LoginView.axaml.cs">
- <DependentUpon>LoginWindow.axaml</DependentUpon>
+ <Compile Update="Views\UserManagementWindow.axaml.cs">
+ <DependentUpon>UserManagementWindow.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
- <Compile Update="Views\ClientView.axaml.cs">
+ <Compile Update="Views\MainWindow\MainWindow.axaml.cs">
+ <DependentUpon>MainWindow.axaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Update="Views\MainWindow\ClientView.axaml.cs">
<DependentUpon>ClientView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
+ <Compile Update="Views\MainWindow\LoginView.axaml.cs">
+ <DependentUpon>LoginView.axaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
</ItemGroup>
<ItemGroup>
diff --git a/ModerationClient/Services/CommandLineConfiguration.cs b/ModerationClient/Services/CommandLineConfiguration.cs
index 4debd5c..63c3691 100644
--- a/ModerationClient/Services/CommandLineConfiguration.cs
+++ b/ModerationClient/Services/CommandLineConfiguration.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using ArcaneLibs;
@@ -6,47 +7,75 @@ using Microsoft.Extensions.Logging;
namespace ModerationClient.Services;
-public class CommandLineConfiguration {
- public CommandLineConfiguration(ILogger<CommandLineConfiguration> logger) {
- var args = Environment.GetCommandLineArgs();
- logger.LogInformation("Command line arguments: " + string.Join(", ", args));
+[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public record CommandLineConfiguration {
+ private readonly string? _loginData;
+
+ public static CommandLineConfiguration FromProcessArgs() {
+ // logger.LogInformation("Command line arguments: " + string.Join(", ", Environment.GetCommandLineArgs()));
+ CommandLineConfiguration cfg = FromSerialised(Environment.GetCommandLineArgs());
+
+ if (string.IsNullOrWhiteSpace(cfg.ProfileDirectory))
+ cfg = cfg with {
+ ProfileDirectory = cfg.IsTemporary
+ ? Directory.CreateTempSubdirectory("ModerationClient-tmp").FullName
+ : Util.ExpandPath($"$HOME/.local/share/ModerationClient/{cfg.Profile}")
+ };
+
+ // logger.LogInformation("Profile directory: " + cfg.ProfileDirectory);
+ Directory.CreateDirectory(cfg.ProfileDirectory);
+ if (!string.IsNullOrWhiteSpace(cfg.LoginData)) {
+ File.WriteAllText(Path.Combine(cfg.ProfileDirectory, "login.json"), cfg.LoginData);
+ }
+ return cfg;
+ }
+
+ public string[] Serialise() {
+ List<string> args = new();
+ if (Profile != "default") args.AddRange(["--profile", Profile]);
+ if (IsTemporary) args.Add("--temporary");
+ if (Math.Abs(Scale - 1f) > float.Epsilon) args.AddRange(["--scale", Scale.ToString()]);
+ if (ProfileDirectory != Util.ExpandPath("$HOME/.local/share/ModerationClient/default")) args.AddRange(["--profile-dir", ProfileDirectory]);
+ if (!string.IsNullOrWhiteSpace(_loginData)) args.AddRange(["--login-data", _loginData!]);
+ return args.ToArray();
+ }
+
+ public static CommandLineConfiguration FromSerialised(string[] args) {
+ CommandLineConfiguration cfg = new();
for (var i = 0; i < args.Length; i++) {
- logger.LogInformation("Processing argument: " + args[i]);
switch (args[i]) {
case "--profile":
- case "-p":
- if (args.Length <= i + 1 || args[i + 1].StartsWith("-")) {
- throw new ArgumentException("No profile specified");
- }
-
- Profile = args[++i];
- logger.LogInformation("Set profile to: " + Profile);
+ cfg = cfg with { Profile = args[++i] };
break;
case "--temporary":
- IsTemporary = true;
- logger.LogInformation("Using temporary profile");
+ cfg = cfg with { IsTemporary = true };
break;
case "--profile-dir":
- ProfileDirectory = args[++i];
+ cfg = cfg with { ProfileDirectory = args[++i] };
break;
case "--scale":
- Scale = float.Parse(args[++i]);
+ cfg = cfg with { Scale = float.Parse(args[++i]) };
+ break;
+ case "--login-data":
+ cfg = cfg with { LoginData = args[++i] };
break;
}
}
- if (string.IsNullOrWhiteSpace(ProfileDirectory))
- ProfileDirectory = IsTemporary
- ? Directory.CreateTempSubdirectory("ModerationClient-tmp").FullName
- : Util.ExpandPath($"$HOME/.local/share/ModerationClient/{Profile}");
-
- logger.LogInformation("Profile directory: " + ProfileDirectory);
- Directory.CreateDirectory(ProfileDirectory);
+ return cfg;
}
- public string Profile { get; private set; } = "default";
- public bool IsTemporary { get; private set; }
+ public string Profile { get; init; } = "default";
+ public bool IsTemporary { get; init; }
- public string ProfileDirectory { get; private set; }
- public float Scale { get; private set; } = 1f;
+ public string ProfileDirectory { get; init; }
+ public float Scale { get; init; } = 1f;
+
+ public string? LoginData {
+ get => _loginData;
+ init {
+ Console.WriteLine("Setting login data: " + value);
+ _loginData = value;
+ }
+ }
}
\ No newline at end of file
diff --git a/ModerationClient/ViewModels/ClientViewModel.cs b/ModerationClient/ViewModels/ClientViewModel.cs
index 1e287ec..312b46a 100644
--- a/ModerationClient/ViewModels/ClientViewModel.cs
+++ b/ModerationClient/ViewModels/ClientViewModel.cs
@@ -30,6 +30,7 @@ public partial class ClientViewModel : ViewModelBase {
private readonly CommandLineConfiguration _cfg;
private SpaceNode? _currentSpace;
private readonly SpaceNode _allRoomsNode;
+ private string _status = "Loading...";
public ObservableCollection<SpaceNode> DisplayedSpaces { get; } = [];
public ObservableDictionary<string, RoomNode> AllRooms { get; } = new();
@@ -38,12 +39,22 @@ public partial class ClientViewModel : ViewModelBase {
set => SetProperty(ref _currentSpace, value);
}
+ public string Status {
+ get => _status + " " + DateTime.Now;
+ set => SetProperty(ref _status, value);
+ }
+
public async Task Run() {
+ Status = "Interrupted.";
+ return;
+ Status = "Doing initial sync...";
var sh = new SyncStateResolver(_authService.Homeserver, _logger, storageProvider: new FileStorageProvider(Path.Combine(_cfg.ProfileDirectory, "syncCache")));
// var res = await sh.SyncAsync();
//await sh.OptimiseStore();
while (true) {
+ // Status = "Syncing...";
var res = await sh.ContinueAsync();
+ Status = $"Processing sync... {res.next.NextBatch}";
await ApplySpaceChanges(res.next);
//OnPropertyChanged(nameof(CurrentSpace));
//OnPropertyChanged(nameof(CurrentSpace.ChildRooms));
@@ -52,6 +63,7 @@ public partial class ClientViewModel : ViewModelBase {
// GC.Collect(i, GCCollectionMode.Forced, blocking: true);
// GC.WaitForPendingFinalizers();
// }
+ Status = "Syncing...";
}
}
@@ -72,7 +84,7 @@ public partial class ClientViewModel : ViewModelBase {
}
}
- // await Task.WhenAll(tasks);
+ await Task.WhenAll(tasks);
return;
diff --git a/ModerationClient/ViewModels/MainWindowViewModel.cs b/ModerationClient/ViewModels/MainWindowViewModel.cs
index 01ec6d6..be64de4 100644
--- a/ModerationClient/ViewModels/MainWindowViewModel.cs
+++ b/ModerationClient/ViewModels/MainWindowViewModel.cs
@@ -1,14 +1,16 @@
using System;
+using Avalonia;
using ModerationClient.Services;
using ModerationClient.Views;
namespace ModerationClient.ViewModels;
public partial class MainWindowViewModel(MatrixAuthenticationService authService, CommandLineConfiguration cfg) : ViewModelBase {
- public MainWindow? MainWindow { get; set; }
-
+ // public MainWindow? MainWindow { get; set; }
+
private float _scale = 1.0f;
private ViewModelBase _currentViewModel = new LoginViewModel(authService);
+ private Size _physicalSize = new Size(300, 220);
public ViewModelBase CurrentViewModel {
get => _currentViewModel;
@@ -21,13 +23,23 @@ public partial class MainWindowViewModel(MatrixAuthenticationService authService
public float Scale {
get => _scale;
set {
- SetProperty(ref _scale, (float)Math.Round(value, 2));
- OnPropertyChanged(nameof(ChildTargetWidth));
- OnPropertyChanged(nameof(ChildTargetHeight));
+ if (SetProperty(ref _scale, (float)Math.Round(value, 2))) {
+ OnPropertyChanged(nameof(ChildTargetWidth));
+ OnPropertyChanged(nameof(ChildTargetHeight));
+ }
}
}
- public int ChildTargetWidth => (int)(MainWindow?.Width / Scale ?? 1);
- public int ChildTargetHeight => (int)(MainWindow?.Height / Scale ?? 1);
-
+ public int ChildTargetWidth => (int)(PhysicalSize.Width / Scale);
+ public int ChildTargetHeight => (int)(PhysicalSize.Height / Scale);
+
+ public Size PhysicalSize {
+ get => _physicalSize;
+ set {
+ if (SetProperty(ref _physicalSize, value)) {
+ OnPropertyChanged(nameof(ChildTargetWidth));
+ OnPropertyChanged(nameof(ChildTargetHeight));
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs b/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs
new file mode 100644
index 0000000..7a2ad63
--- /dev/null
+++ b/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using ArcaneLibs.Collections;
+using ArcaneLibs.Extensions;
+using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.Helpers;
+using LibMatrix.Homeservers;
+using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses;
+using LibMatrix.Responses;
+using MatrixUtils.Abstractions;
+using Microsoft.Extensions.Logging;
+using ModerationClient.Services;
+
+namespace ModerationClient.ViewModels;
+
+public partial class UserManagementViewModel : ViewModelBase {
+ public UserManagementViewModel(ILogger<UserManagementViewModel> logger, MatrixAuthenticationService authService, CommandLineConfiguration cfg) {
+ _logger = logger;
+ _authService = authService;
+ _cfg = cfg;
+ _ = Task.Run(Run).ContinueWith(x=>x.Exception?.Handle(y=> {
+ Console.WriteLine(y);
+ return true;
+ }));
+ }
+
+ private readonly ILogger<UserManagementViewModel> _logger;
+ private readonly MatrixAuthenticationService _authService;
+ private readonly CommandLineConfiguration _cfg;
+ private string _status = "Loading...";
+ public ObservableCollection<User> Users { get; set; } = [];
+
+ public string Status {
+ get => _status + " " + DateTime.Now;
+ set => SetProperty(ref _status, value);
+ }
+
+ public async Task Run() {
+ Users.Clear();
+ Status = "Doing initial sync...";
+ if (_authService.Homeserver is not AuthenticatedHomeserverSynapse synapse) {
+ Console.WriteLine("This client only supports Synapse homeservers.");
+ return;
+ }
+
+ await foreach (var user in synapse.Admin.SearchUsersAsync(chunkLimit: 100)) {
+ Console.WriteLine("USERMANAGER GOT USER: " + user.ToJson(indent:false, ignoreNull: true));
+ Users.Add(JsonSerializer.Deserialize<User>(user.ToJson())!);
+ }
+ Console.WriteLine("Done.");
+ }
+}
+
+public class User : AdminUserListResult.AdminUserListResultUser {
+
+}
\ No newline at end of file
diff --git a/ModerationClient/Views/ClientView.axaml b/ModerationClient/Views/MainWindow/ClientView.axaml
index 0ed8021..ba030e4 100644
--- a/ModerationClient/Views/ClientView.axaml
+++ b/ModerationClient/Views/MainWindow/ClientView.axaml
@@ -7,7 +7,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ModerationClient.Views.ClientView"
x:DataType="viewModels:ClientViewModel">
- <Grid Width="{Binding $parent.Width}" Height="{Binding $parent.Height}" RowDefinitions="*,20">
+ <Grid Width="{Binding $parent.Width}" Height="{Binding $parent.Height}" RowDefinitions="*, Auto">
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="128" MinWidth="16" />
@@ -19,7 +19,7 @@
<TreeView Grid.Column="0" Background="Red" ItemsSource="{CompiledBinding DisplayedSpaces}" SelectedItem="{CompiledBinding CurrentSpace}">
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding ChildSpaces}">
- <TextBlock Text="{Binding Name}" />
+ <TextBlock Text="{Binding Name}" Height="20" />
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
@@ -28,15 +28,15 @@
<ListBox Grid.Column="2" Background="Green" ItemsSource="{CompiledBinding CurrentSpace.ChildRooms}">
<ListBox.ItemTemplate>
<DataTemplate DataType="viewModels:RoomNode">
- <Label Content="{CompiledBinding Name}" />
+ <TextBlock Text="{CompiledBinding Name}" Height="20" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<GridSplitter Grid.Column="3" Background="Black" ResizeDirection="Columns" />
<Rectangle Grid.Column="4" Fill="Blue" />
</Grid>
- <Grid Grid.Row="1" ColumnDefinitions="Auto, *, Auto">
- <Label Grid.Column="2">Text here</Label>
+ <Grid Grid.Row="1" ColumnDefinitions="Auto, *, Auto" Background="Black">
+ <Label Grid.Column="2" Content="{CompiledBinding Status}" />
</Grid>
</Grid>
</UserControl>
\ No newline at end of file
diff --git a/ModerationClient/Views/ClientView.axaml.cs b/ModerationClient/Views/MainWindow/ClientView.axaml.cs
index 894e807..894e807 100644
--- a/ModerationClient/Views/ClientView.axaml.cs
+++ b/ModerationClient/Views/MainWindow/ClientView.axaml.cs
diff --git a/ModerationClient/Views/LoginView.axaml b/ModerationClient/Views/MainWindow/LoginView.axaml
index 10e97c6..5dc6533 100644
--- a/ModerationClient/Views/LoginView.axaml
+++ b/ModerationClient/Views/MainWindow/LoginView.axaml
@@ -7,8 +7,6 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ModerationClient.Views.LoginView"
x:DataType="viewModels:LoginViewModel">
- <!-- DataContext="{Binding $self}"> -->
- <!-- DataContext="{Binding $self}"> -->
<StackPanel>
<Label>Log in</Label>
<StackPanel Orientation="Horizontal">
diff --git a/ModerationClient/Views/LoginView.axaml.cs b/ModerationClient/Views/MainWindow/LoginView.axaml.cs
index 5e84ace..5e84ace 100644
--- a/ModerationClient/Views/LoginView.axaml.cs
+++ b/ModerationClient/Views/MainWindow/LoginView.axaml.cs
diff --git a/ModerationClient/Views/MainWindow.axaml b/ModerationClient/Views/MainWindow/MainWindow.axaml
index 1c2b396..ef13553 100644
--- a/ModerationClient/Views/MainWindow.axaml
+++ b/ModerationClient/Views/MainWindow/MainWindow.axaml
@@ -5,20 +5,19 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:ModerationClient.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
- x:Class="ModerationClient.Views.MainWindow"
+ x:Class="ModerationClient.Views.MainWindow.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="ModerationClient"
- Width="640" Height="480">
-
- <Design.DataContext>
- <!-- This only sets the DataContext for the previewer in an IDE,
+ Width="1280" Height="720">
+ <!-- <Design.DataContext> -->
+ <!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
- <vm:MainWindowViewModel />
- </Design.DataContext>
+ <!-- <vm:MainWindowViewModel /> -->
+ <!-- </Design.DataContext> -->
- <StackPanel>
- <Grid ColumnDefinitions="Auto, *, Auto">
+ <Grid RowDefinitions="Auto, *">
+ <Grid Grid.Row="0" ColumnDefinitions="Auto, *, Auto" x:Name="TopPanel">
<StackPanel Orientation="Horizontal" Grid.Column="0">
<Label Content="{CompiledBinding Scale}" />
<Label>x</Label>
@@ -29,14 +28,12 @@
</StackPanel>
<Label Grid.Column="2">Press '?' for keybinds</Label>
</Grid>
- <Viewbox>
+ <Viewbox Grid.Row="1">
<ContentControl
Width="{CompiledBinding ChildTargetWidth}"
- Height="{CompiledBinding ChildTargetHeight}"
Background="#222222"
+ Height="{CompiledBinding ChildTargetHeight}"
Content="{CompiledBinding CurrentViewModel}" />
</Viewbox>
-
- </StackPanel>
-
+ </Grid>
</Window>
\ No newline at end of file
diff --git a/ModerationClient/Views/MainWindow.axaml.cs b/ModerationClient/Views/MainWindow/MainWindow.axaml.cs
index 884e90c..01027c1 100644
--- a/ModerationClient/Views/MainWindow.axaml.cs
+++ b/ModerationClient/Views/MainWindow/MainWindow.axaml.cs
@@ -8,13 +8,17 @@ using Microsoft.Extensions.Hosting;
using ModerationClient.Services;
using ModerationClient.ViewModels;
-namespace ModerationClient.Views;
+namespace ModerationClient.Views.MainWindow;
public partial class MainWindow : Window {
public MainWindow(CommandLineConfiguration cfg, MainWindowViewModel dataContext, IHostApplicationLifetime appLifetime) {
InitializeComponent();
DataContext = dataContext;
- _ = dataContext.AuthService.LoadProfileAsync();
+ _ = dataContext.AuthService.LoadProfileAsync().ContinueWith(x => {
+ if (x.IsFaulted) {
+ Console.WriteLine("Failed to load profile: " + x.Exception);
+ }
+ });
Console.WriteLine("mainwnd");
#if DEBUG
this.AttachDevTools(new DevToolsOptions() {
@@ -25,31 +29,43 @@ public partial class MainWindow : Window {
PropertyChanged += (sender, args) => {
// Console.WriteLine($"MainWindow PropertyChanged: {args.Property.Name} ({args.OldValue} -> {args.NewValue})");
switch (args.Property.Name) {
- case nameof(Height):
- case nameof(Width): {
+ case nameof(ClientSize): {
if (DataContext is not MainWindowViewModel viewModel) {
- Console.WriteLine("WARN: MainWindowViewModel is null, ignoring height/width change!");
+ Console.WriteLine("WARN: MainWindowViewModel is null, ignoring ClientSize change!");
return;
}
- // Console.WriteLine("height/width changed");
- viewModel.Scale = viewModel.Scale;
+ viewModel.PhysicalSize = new Size(ClientSize.Width, ClientSize.Height - TopPanel.Bounds.Height);
break;
}
}
};
+
+ TopPanel.PropertyChanged += (_, args) => {
+ if (args.Property.Name == nameof(Visual.Bounds)) {
+ if (DataContext is not MainWindowViewModel viewModel) {
+ Console.WriteLine("WARN: MainWindowViewModel is null, ignoring TopPanel.Bounds change!");
+ return;
+ }
+
+ viewModel.PhysicalSize = new Size(ClientSize.Width, ClientSize.Height - TopPanel.Bounds.Height);
+ }
+ };
+
dataContext.AuthService.PropertyChanged += (sender, args) => {
if (args.PropertyName == nameof(MatrixAuthenticationService.IsLoggedIn)) {
if (dataContext.AuthService.IsLoggedIn) {
// dataContext.CurrentViewModel = new ClientViewModel(dataContext.AuthService);
dataContext.CurrentViewModel = App.Current.Host.Services.GetRequiredService<ClientViewModel>();
+ var window = App.Current.Host.Services.GetRequiredService<UserManagementWindow>();
+ window.Show();
}
else {
dataContext.CurrentViewModel = new LoginViewModel(dataContext.AuthService);
}
}
};
- dataContext.MainWindow = this;
+
dataContext.Scale = cfg.Scale;
Width *= cfg.Scale;
Height *= cfg.Scale;
@@ -84,11 +100,25 @@ public partial class MainWindow : Window {
viewModel.Scale = 5.0f;
}
}
- else if (e.Key == Key.K && e.KeyModifiers == KeyModifiers.Control) {
- if(viewModel.CurrentViewModel is ClientViewModel clientViewModel) {
- Console.WriteLine("QuickSwitcher invoked");
+ else if (e.KeyModifiers == KeyModifiers.Control) {
+ if (e.Key == Key.K) {
+ if (viewModel.CurrentViewModel is ClientViewModel clientViewModel) {
+ Console.WriteLine("QuickSwitcher invoked");
+ }
+ else Console.WriteLine("WARN: CurrentViewModel is not ClientViewModel, ignoring Quick Switcher");
+ }
+ else if (e.Key == Key.U ) {
+ Console.WriteLine("UserManagementWindow invoked");
+ var window = App.Current.Host.Services.GetRequiredService<UserManagementWindow>();
+ window.Show();
+ }
+ else if (e.Key == Key.F5) {
+ Console.WriteLine("Launching new process");
+ System.Diagnostics.Process.Start(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName, Environment.GetCommandLineArgs());
+ }
+ else if (e.Key == Key.F9) {
+
}
- else Console.WriteLine("WARN: CurrentViewModel is not ClientViewModel, ignoring Quick Switcher");
}
}
}
\ No newline at end of file
diff --git a/ModerationClient/Views/UserManagementWindow.axaml b/ModerationClient/Views/UserManagementWindow.axaml
new file mode 100644
index 0000000..ef93517
--- /dev/null
+++ b/ModerationClient/Views/UserManagementWindow.axaml
@@ -0,0 +1,59 @@
+<Window xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:vm="using:ModerationClient.ViewModels"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:views="clr-namespace:ModerationClient.Views"
+ xmlns:responses="clr-namespace:LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses;assembly=LibMatrix"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="ModerationClient.Views.UserManagementWindow"
+ x:DataType="vm:MainWindowViewModel"
+ Icon="/Assets/avalonia-logo.ico"
+ Title="ModerationClient"
+ Width="640" Height="480">
+ <!-- <Design.DataContext> -->
+ <!-- This only sets the DataContext for the previewer in an IDE,
+ to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
+ <!-- <vm:MainWindowViewModel /> -->
+ <!-- </Design.DataContext> -->
+
+ <Grid RowDefinitions="Auto, *">
+ <Grid Grid.Row="0" ColumnDefinitions="Auto, *, Auto" x:Name="TopPanel">
+ <StackPanel Orientation="Horizontal" Grid.Column="0">
+ <Label Content="{CompiledBinding Scale}" />
+ <Label>x</Label>
+ <Rectangle Width="32" />
+ <Label Content="{CompiledBinding ChildTargetWidth}" />
+ <Label>x</Label>
+ <Label Content="{CompiledBinding ChildTargetHeight}" />
+ </StackPanel>
+ <Label Grid.Column="2">Press '?' for keybinds</Label>
+ </Grid>
+ <Viewbox Grid.Row="1">
+ <ScrollViewer
+ Width="{CompiledBinding ChildTargetWidth}"
+ Background="#222222"
+ Height="{CompiledBinding ChildTargetHeight}">
+ <ContentControl DataContext="{CompiledBinding Path=CurrentViewModel}">
+ <ItemsControl ItemsSource="{ReflectionBinding Users}">
+ <ItemsControl.ItemTemplate>
+ <DataTemplate DataType="vm:User">
+ <StackPanel Orientation="Vertical">
+ <StackPanel Orientation="Horizontal">
+ <TextBlock Text="{CompiledBinding Name}" />
+ <TextBlock Text="{CompiledBinding DisplayName}" />
+ </StackPanel>
+ <StackPanel Orientation="Horizontal">
+ <Button Tag="{CompiledBinding .}" Click="PuppetButtonClicked">Puppet</Button>
+ <!-- <Button>Terminate</Button> -->
+ </StackPanel>
+ </StackPanel>
+ </DataTemplate>
+ </ItemsControl.ItemTemplate>
+ </ItemsControl>
+ </ContentControl>
+ </ScrollViewer>
+ </Viewbox>
+ </Grid>
+
+</Window>
\ No newline at end of file
diff --git a/ModerationClient/Views/UserManagementWindow.axaml.cs b/ModerationClient/Views/UserManagementWindow.axaml.cs
new file mode 100644
index 0000000..2d2dfb4
--- /dev/null
+++ b/ModerationClient/Views/UserManagementWindow.axaml.cs
@@ -0,0 +1,136 @@
+using System;
+using ArcaneLibs.Extensions;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Diagnostics;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using LibMatrix.Homeservers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using ModerationClient.Services;
+using ModerationClient.ViewModels;
+
+namespace ModerationClient.Views;
+
+public partial class UserManagementWindow : Window {
+ private readonly CommandLineConfiguration _cfg;
+ private readonly MatrixAuthenticationService _auth;
+
+ public UserManagementWindow(CommandLineConfiguration cfg, MainWindowViewModel dataContext, IHostApplicationLifetime appLifetime,
+ UserManagementViewModel userManagementViewModel, MatrixAuthenticationService auth) {
+ _cfg = cfg;
+ _auth = auth;
+ InitializeComponent();
+ DataContext = dataContext;
+ dataContext.CurrentViewModel = userManagementViewModel;
+ Console.WriteLine("mainwnd");
+#if DEBUG
+ this.AttachDevTools(new DevToolsOptions() {
+ ShowAsChildWindow = true,
+ LaunchView = DevToolsViewKind.LogicalTree,
+ });
+#endif
+ PropertyChanged += (sender, args) => {
+ // Console.WriteLine($"MainWindow PropertyChanged: {args.Property.Name} ({args.OldValue} -> {args.NewValue})");
+ switch (args.Property.Name) {
+ case nameof(ClientSize): {
+ if (DataContext is not MainWindowViewModel viewModel) {
+ Console.WriteLine("WARN: MainWindowViewModel is null, ignoring ClientSize change!");
+ return;
+ }
+
+ viewModel.PhysicalSize = new Size(ClientSize.Width, ClientSize.Height - TopPanel.Bounds.Height);
+ break;
+ }
+ }
+ };
+
+ TopPanel.PropertyChanged += (_, args) => {
+ if (args.Property.Name == nameof(TopPanel.Bounds)) {
+ if (DataContext is not MainWindowViewModel viewModel) {
+ Console.WriteLine("WARN: MainWindowViewModel is null, ignoring TopPanel.Bounds change!");
+ return;
+ }
+
+ viewModel.PhysicalSize = new Size(ClientSize.Width, ClientSize.Height - TopPanel.Bounds.Height);
+ }
+ };
+
+ dataContext.AuthService.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == nameof(MatrixAuthenticationService.IsLoggedIn)) {
+ if (dataContext.AuthService.IsLoggedIn) {
+ // dataContext.CurrentViewModel = new ClientViewModel(dataContext.AuthService);
+ dataContext.CurrentViewModel = App.Current.Host.Services.GetRequiredService<ClientViewModel>();
+ }
+ else {
+ dataContext.CurrentViewModel = new LoginViewModel(dataContext.AuthService);
+ }
+ }
+ };
+
+ dataContext.Scale = cfg.Scale;
+ Width *= cfg.Scale;
+ Height *= cfg.Scale;
+
+ appLifetime.ApplicationStopping.Register(() => {
+ Console.WriteLine("ApplicationStopping triggered");
+ Close();
+ });
+ }
+
+ protected override void OnKeyDown(KeyEventArgs e) => OnKeyDown(this, e);
+
+ private void OnKeyDown(object? _, KeyEventArgs e) {
+ if (DataContext is not MainWindowViewModel viewModel) {
+ Console.WriteLine($"WARN: DataContext is {DataContext?.GetType().Name ?? "null"}, ignoring key press!");
+ return;
+ }
+
+ // Console.WriteLine("MainWindow KeyDown: " + e.Key);
+ if (e.Key == Key.Escape) {
+ viewModel.Scale = 1.0f;
+ }
+ else if (e.Key == Key.F1) {
+ viewModel.Scale -= 0.1f;
+ if (viewModel.Scale < 0.1f) {
+ viewModel.Scale = 0.1f;
+ }
+ }
+ else if (e.Key == Key.F2) {
+ viewModel.Scale += 0.1f;
+ if (viewModel.Scale > 5.0f) {
+ viewModel.Scale = 5.0f;
+ }
+ }
+ else if (e.Key == Key.K && e.KeyModifiers == KeyModifiers.Control) {
+ if (viewModel.CurrentViewModel is ClientViewModel clientViewModel) {
+ Console.WriteLine("QuickSwitcher invoked");
+ }
+ else Console.WriteLine("WARN: CurrentViewModel is not ClientViewModel, ignoring Quick Switcher");
+ }
+ }
+
+ // ReSharper disable once AsyncVoidMethod
+ private async void PuppetButtonClicked(object? sender, RoutedEventArgs e) {
+ if (e.Source is not Button button) {
+ Console.WriteLine("WARN: Source is not Button, ignoring PuppetButtonClicked!");
+ return;
+ }
+
+ if (button.Tag is not User user) {
+ Console.WriteLine("WARN: Tag is not User, ignoring PuppetButtonClicked!");
+ return;
+ }
+
+ if (_auth.Homeserver is not AuthenticatedHomeserverSynapse synapse) {
+ Console.WriteLine("WARN: Homeserver is not Synapse, ignoring PuppetButtonClicked!");
+ return;
+ }
+
+ var puppet = await synapse.Admin.LoginUserAsync(user.Name, TimeSpan.FromMinutes(5));
+
+ System.Diagnostics.Process.Start(System.Diagnostics.Process.GetCurrentProcess().MainModule!.FileName,
+ (_cfg with { IsTemporary = true, LoginData = puppet.ToJson() }).Serialise());
+ }
+}
\ No newline at end of file
|