diff options
author | Rory& <root@rory.gay> | 2024-08-08 02:57:34 +0200 |
---|---|---|
committer | Rory& <root@rory.gay> | 2024-08-08 03:02:10 +0200 |
commit | df5fe7c86e41235f99a9b0d69519a18581eddd5e (patch) | |
tree | d7ae3b2c9b98ffcf4fbe4613091dfc7db7e2c62b /ModerationClient | |
parent | List rooms (diff) | |
download | ModerationClient-df5fe7c86e41235f99a9b0d69519a18581eddd5e.tar.xz |
Further work
Diffstat (limited to 'ModerationClient')
14 files changed, 427 insertions, 94 deletions
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 |