From df5fe7c86e41235f99a9b0d69519a18581eddd5e Mon Sep 17 00:00:00 2001 From: Rory& Date: Thu, 8 Aug 2024 02:57:34 +0200 Subject: Further work --- .gitignore | 21 ++-- .idea/.idea.ModerationClient/.idea/avalonia.xml | 2 + .run/local/.gitkeep | 0 LibMatrix | 2 +- ModerationClient.sln | 6 +- ModerationClient/App.axaml.cs | 33 ++--- ModerationClient/ModerationClient.csproj | 14 ++- .../Services/CommandLineConfiguration.cs | 83 +++++++++---- ModerationClient/ViewModels/ClientViewModel.cs | 14 ++- ModerationClient/ViewModels/MainWindowViewModel.cs | 28 +++-- .../UserManagement/UserManagementViewModel.cs | 63 ++++++++++ ModerationClient/Views/ClientView.axaml | 42 ------- ModerationClient/Views/ClientView.axaml.cs | 27 ---- ModerationClient/Views/LoginView.axaml | 25 ---- ModerationClient/Views/LoginView.axaml.cs | 18 --- ModerationClient/Views/MainWindow.axaml | 42 ------- ModerationClient/Views/MainWindow.axaml.cs | 94 -------------- ModerationClient/Views/MainWindow/ClientView.axaml | 42 +++++++ .../Views/MainWindow/ClientView.axaml.cs | 27 ++++ ModerationClient/Views/MainWindow/LoginView.axaml | 23 ++++ .../Views/MainWindow/LoginView.axaml.cs | 18 +++ ModerationClient/Views/MainWindow/MainWindow.axaml | 39 ++++++ .../Views/MainWindow/MainWindow.axaml.cs | 124 +++++++++++++++++++ ModerationClient/Views/UserManagementWindow.axaml | 59 +++++++++ .../Views/UserManagementWindow.axaml.cs | 136 +++++++++++++++++++++ 25 files changed, 657 insertions(+), 325 deletions(-) create mode 100644 .run/local/.gitkeep create mode 100644 ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs delete mode 100644 ModerationClient/Views/ClientView.axaml delete mode 100644 ModerationClient/Views/ClientView.axaml.cs delete mode 100644 ModerationClient/Views/LoginView.axaml delete mode 100644 ModerationClient/Views/LoginView.axaml.cs delete mode 100644 ModerationClient/Views/MainWindow.axaml delete mode 100644 ModerationClient/Views/MainWindow.axaml.cs create mode 100644 ModerationClient/Views/MainWindow/ClientView.axaml create mode 100644 ModerationClient/Views/MainWindow/ClientView.axaml.cs create mode 100644 ModerationClient/Views/MainWindow/LoginView.axaml create mode 100644 ModerationClient/Views/MainWindow/LoginView.axaml.cs create mode 100644 ModerationClient/Views/MainWindow/MainWindow.axaml create mode 100644 ModerationClient/Views/MainWindow/MainWindow.axaml.cs create mode 100644 ModerationClient/Views/UserManagementWindow.axaml create mode 100644 ModerationClient/Views/UserManagementWindow.axaml.cs 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 @@ + + diff --git a/.run/local/.gitkeep b/.run/local/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/LibMatrix b/LibMatrix index 3b48824..bba7333 160000 --- a/LibMatrix +++ b/LibMatrix @@ -1 +1 @@ -Subproject commit 3b488242050bbc0521d846bd31cb6ea59b8d4e38 +Subproject commit bba7333ee6581a92bbbc7479d72325e704fe7fa6 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 { - /// - /// Gets the current instance in use - /// - public new static App Current => (App)Application.Current; - - /// - /// Gets the instance to resolve application services. - /// + 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(); ConfigureServices(builder.Services); - // builder.Services.AddHostedService(); 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() - // }; desktop.MainWindow = Host.Services.GetRequiredService(); desktop.Exit += (sender, args) => { Host.StopAsync(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult(); @@ -59,19 +46,16 @@ public partial class App : Application { await Host.StartAsync(); } - /// - /// Configures the services for the application. - /// private static IServiceProvider ConfigureServices(IServiceCollection services) { services.AddRoryLibMatrixServices(new() { AppName = "ModerationClient", }); - services.AddSingleton(); + services.AddSingleton(CommandLineConfiguration.FromProcessArgs()); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(x => { - var cmdLine = x.GetRequiredService(); + services.AddSingleton(s => { + var cmdLine = s.GetRequiredService(); 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(); + services.AddTransient(); + + // Register views services.AddTransient(); services.AddTransient(); + // Register ViewModels services.AddTransient(); + services.AddTransient(); 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 @@ - - LoginWindow.axaml + + UserManagementWindow.axaml Code - + + MainWindow.axaml + Code + + ClientView.axaml Code + + LoginView.axaml + Code + 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 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 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 DisplayedSpaces { get; } = []; public ObservableDictionary 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 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 _logger; + private readonly MatrixAuthenticationService _authService; + private readonly CommandLineConfiguration _cfg; + private string _status = "Loading..."; + public ObservableCollection 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.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/ClientView.axaml deleted file mode 100644 index 0ed8021..0000000 --- a/ModerationClient/Views/ClientView.axaml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ModerationClient/Views/ClientView.axaml.cs b/ModerationClient/Views/ClientView.axaml.cs deleted file mode 100644 index 894e807..0000000 --- a/ModerationClient/Views/ClientView.axaml.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace ModerationClient.Views; - -public partial class ClientView : UserControl { - - public ClientView() { - InitializeComponent(); - - // PropertyChanged += (_, e) => { - // switch (e.Property.Name) { - // case nameof(Width): { - // //make sure all columns fit - // var grid = this.LogicalChildren.OfType().FirstOrDefault(); - // if(grid is null) { - // Console.WriteLine("Failed to find Grid in ClientView"); - // return; - // } - // Console.WriteLine($"ClientView width changed to {Width}"); - // var columns = grid.ColumnDefinitions; - // break; - // } - // } - // }; - } -} diff --git a/ModerationClient/Views/LoginView.axaml b/ModerationClient/Views/LoginView.axaml deleted file mode 100644 index 10e97c6..0000000 --- a/ModerationClient/Views/LoginView.axaml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ModerationClient/Views/LoginView.axaml.cs b/ModerationClient/Views/LoginView.axaml.cs deleted file mode 100644 index 5e84ace..0000000 --- a/ModerationClient/Views/LoginView.axaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Markup.Xaml; -using ModerationClient.ViewModels; - -namespace ModerationClient.Views; - -public partial class LoginView : UserControl { - public LoginView() { - InitializeComponent(); - } - - // ReSharper disable once AsyncVoidMethod - private async void Login(object? _, RoutedEventArgs __) { - await (DataContext as LoginViewModel ?? throw new InvalidCastException("LoginView did not receive LoginViewModel?")).LoginAsync(); - } -} diff --git a/ModerationClient/Views/MainWindow.axaml b/ModerationClient/Views/MainWindow.axaml deleted file mode 100644 index 1c2b396..0000000 --- a/ModerationClient/Views/MainWindow.axaml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ModerationClient/Views/MainWindow.axaml.cs b/ModerationClient/Views/MainWindow.axaml.cs deleted file mode 100644 index 884e90c..0000000 --- a/ModerationClient/Views/MainWindow.axaml.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Diagnostics; -using Avalonia.Input; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using ModerationClient.Services; -using ModerationClient.ViewModels; - -namespace ModerationClient.Views; - -public partial class MainWindow : Window { - public MainWindow(CommandLineConfiguration cfg, MainWindowViewModel dataContext, IHostApplicationLifetime appLifetime) { - InitializeComponent(); - DataContext = dataContext; - _ = dataContext.AuthService.LoadProfileAsync(); - 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(Height): - case nameof(Width): { - if (DataContext is not MainWindowViewModel viewModel) { - Console.WriteLine("WARN: MainWindowViewModel is null, ignoring height/width change!"); - return; - } - - // Console.WriteLine("height/width changed"); - viewModel.Scale = viewModel.Scale; - break; - } - } - }; - 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(); - } - else { - dataContext.CurrentViewModel = new LoginViewModel(dataContext.AuthService); - } - } - }; - dataContext.MainWindow = this; - 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"); - } - } -} \ No newline at end of file diff --git a/ModerationClient/Views/MainWindow/ClientView.axaml b/ModerationClient/Views/MainWindow/ClientView.axaml new file mode 100644 index 0000000..ba030e4 --- /dev/null +++ b/ModerationClient/Views/MainWindow/ClientView.axaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ModerationClient/Views/MainWindow/ClientView.axaml.cs b/ModerationClient/Views/MainWindow/ClientView.axaml.cs new file mode 100644 index 0000000..894e807 --- /dev/null +++ b/ModerationClient/Views/MainWindow/ClientView.axaml.cs @@ -0,0 +1,27 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ModerationClient.Views; + +public partial class ClientView : UserControl { + + public ClientView() { + InitializeComponent(); + + // PropertyChanged += (_, e) => { + // switch (e.Property.Name) { + // case nameof(Width): { + // //make sure all columns fit + // var grid = this.LogicalChildren.OfType().FirstOrDefault(); + // if(grid is null) { + // Console.WriteLine("Failed to find Grid in ClientView"); + // return; + // } + // Console.WriteLine($"ClientView width changed to {Width}"); + // var columns = grid.ColumnDefinitions; + // break; + // } + // } + // }; + } +} diff --git a/ModerationClient/Views/MainWindow/LoginView.axaml b/ModerationClient/Views/MainWindow/LoginView.axaml new file mode 100644 index 0000000..5dc6533 --- /dev/null +++ b/ModerationClient/Views/MainWindow/LoginView.axaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ModerationClient/Views/MainWindow/LoginView.axaml.cs b/ModerationClient/Views/MainWindow/LoginView.axaml.cs new file mode 100644 index 0000000..5e84ace --- /dev/null +++ b/ModerationClient/Views/MainWindow/LoginView.axaml.cs @@ -0,0 +1,18 @@ +using System; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using ModerationClient.ViewModels; + +namespace ModerationClient.Views; + +public partial class LoginView : UserControl { + public LoginView() { + InitializeComponent(); + } + + // ReSharper disable once AsyncVoidMethod + private async void Login(object? _, RoutedEventArgs __) { + await (DataContext as LoginViewModel ?? throw new InvalidCastException("LoginView did not receive LoginViewModel?")).LoginAsync(); + } +} diff --git a/ModerationClient/Views/MainWindow/MainWindow.axaml b/ModerationClient/Views/MainWindow/MainWindow.axaml new file mode 100644 index 0000000..ef13553 --- /dev/null +++ b/ModerationClient/Views/MainWindow/MainWindow.axaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ModerationClient/Views/MainWindow/MainWindow.axaml.cs b/ModerationClient/Views/MainWindow/MainWindow.axaml.cs new file mode 100644 index 0000000..01027c1 --- /dev/null +++ b/ModerationClient/Views/MainWindow/MainWindow.axaml.cs @@ -0,0 +1,124 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Diagnostics; +using Avalonia.Input; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using ModerationClient.Services; +using ModerationClient.ViewModels; + +namespace ModerationClient.Views.MainWindow; + +public partial class MainWindow : Window { + public MainWindow(CommandLineConfiguration cfg, MainWindowViewModel dataContext, IHostApplicationLifetime appLifetime) { + InitializeComponent(); + DataContext = dataContext; + _ = 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() { + 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(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(); + var window = App.Current.Host.Services.GetRequiredService(); + window.Show(); + } + 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.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(); + 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) { + + } + } + } +} \ 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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(); + } + 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 -- cgit 1.4.1