about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-03-10 07:41:43 +0100
committerRory& <root@rory.gay>2025-03-10 07:41:43 +0100
commit8838a3b20ba95bca34954b6ec828991adb028d4d (patch)
tree4fb6d6efdb04107e10daf8dc311894c3f6050b34
parentChanges (diff)
downloadModerationClient-8838a3b20ba95bca34954b6ec828991adb028d4d.tar.xz
Various work
-rw-r--r--.idea/.idea.ModerationClient/.idea/avalonia.xml2
-rw-r--r--.idea/.idea.ModerationClient/.idea/vcs.xml1
-rw-r--r--FilesystemBenchmark/FilesystemBenchmark.csproj2
m---------LibMatrix0
-rw-r--r--ModerationClient.sln22
-rw-r--r--ModerationClient/App.axaml.cs4
-rw-r--r--ModerationClient/Models/SpaceTreeNodes/RoomNode.cs5
-rw-r--r--ModerationClient/ModerationClient.csproj5
-rw-r--r--ModerationClient/Properties/launchSettings.json26
-rw-r--r--ModerationClient/Services/ClientContainer.cs34
-rw-r--r--ModerationClient/Services/CommandLineConfiguration.cs8
-rw-r--r--ModerationClient/Services/MatrixAuthenticationService.cs12
-rw-r--r--ModerationClient/Services/StatusBarService.cs18
-rw-r--r--ModerationClient/ViewLocator.cs5
-rw-r--r--ModerationClient/ViewModels/ClientViewModel.cs128
-rw-r--r--ModerationClient/ViewModels/EventViewModel.cs9
-rw-r--r--ModerationClient/ViewModels/LoginViewModel.cs13
-rw-r--r--ModerationClient/ViewModels/RoomViewModel.cs113
-rw-r--r--ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs8
-rw-r--r--ModerationClient/Views/MainWindow/ClientView.axaml9
-rw-r--r--ModerationClient/Views/MainWindow/EventView.axaml10
-rw-r--r--ModerationClient/Views/MainWindow/EventView.axaml.cs11
-rw-r--r--ModerationClient/Views/MainWindow/LoginView.axaml4
-rw-r--r--ModerationClient/Views/MainWindow/MainWindow.axaml.cs6
-rw-r--r--ModerationClient/Views/MainWindow/RoomView.axaml53
-rw-r--r--ModerationClient/Views/MainWindow/RoomView.axaml.cs11
-rw-r--r--ModerationClient/Views/MainWindow/StatusBar.axaml10
-rw-r--r--ModerationClient/Views/MainWindow/StatusBar.axaml.cs15
-rw-r--r--ModerationClient/Views/UserManagementWindow.axaml2
-rw-r--r--ModerationClient/Views/UserManagementWindow.axaml.cs17
-rw-r--r--ModerationClient/avatar.png0
-rw-r--r--Test/Test.csproj2
32 files changed, 483 insertions, 82 deletions
diff --git a/.idea/.idea.ModerationClient/.idea/avalonia.xml b/.idea/.idea.ModerationClient/.idea/avalonia.xml

index b045202..e613ece 100644 --- a/.idea/.idea.ModerationClient/.idea/avalonia.xml +++ b/.idea/.idea.ModerationClient/.idea/avalonia.xml
@@ -9,9 +9,11 @@ <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/ClientView.axaml" value="ModerationClient/ModerationClient.csproj" /> + <entry key="ModerationClient/Views/MainWindow/EventView.axaml" value="ModerationClient/ModerationClient.csproj" /> <entry key="ModerationClient/Views/MainWindow/LoginView.axaml" value="ModerationClient/ModerationClient.csproj" /> <entry key="ModerationClient/Views/MainWindow/MainWindow.axaml" value="ModerationClient/ModerationClient.csproj" /> <entry key="ModerationClient/Views/MainWindow/NotificationPopupWindow.axaml" value="ModerationClient/ModerationClient.csproj" /> + <entry key="ModerationClient/Views/MainWindow/RoomView.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> diff --git a/.idea/.idea.ModerationClient/.idea/vcs.xml b/.idea/.idea.ModerationClient/.idea/vcs.xml
index 94a25f7..d6f86ef 100644 --- a/.idea/.idea.ModerationClient/.idea/vcs.xml +++ b/.idea/.idea.ModerationClient/.idea/vcs.xml
@@ -2,5 +2,6 @@ <project version="4"> <component name="VcsDirectoryMappings"> <mapping directory="$PROJECT_DIR$" vcs="Git" /> + <mapping directory="$PROJECT_DIR$/SynapseDataMiner/Resources" vcs="Git" /> </component> </project> \ No newline at end of file diff --git a/FilesystemBenchmark/FilesystemBenchmark.csproj b/FilesystemBenchmark/FilesystemBenchmark.csproj
index bb0af83..3094aff 100644 --- a/FilesystemBenchmark/FilesystemBenchmark.csproj +++ b/FilesystemBenchmark/FilesystemBenchmark.csproj
@@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <LangVersion>preview</LangVersion> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> diff --git a/LibMatrix b/LibMatrix -Subproject a8d20e9d57857296e4600f44807893f4dcad72d +Subproject 1ec9feb19e9dbf57c57628226b5130d222d59ec diff --git a/ModerationClient.sln b/ModerationClient.sln
index cff05e1..f8c6daa 100644 --- a/ModerationClient.sln +++ b/ModerationClient.sln
@@ -45,12 +45,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "LibMa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "LibMatrix\ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{F9BA709C-DA10-4416-A07D-C89293FB0D24}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLib.Tests", "LibMatrix\ArcaneLibs\ArcaneLib.Tests\ArcaneLib.Tests.csproj", "{046D35DF-E1F2-41DA-94D3-80CF960C100A}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{BC6C910C-79C7-42D3-ABD9-8B9ED9121AE5}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilesystemBenchmark", "FilesystemBenchmark\FilesystemBenchmark.csproj", "{6F3FA65D-5661-4840-B421-BF64CD4DBBD2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SynapseDataMiner", "SynapseDataMiner\SynapseDataMiner.csproj", "{E50928A5-AC3E-4918-9850-ED91B4E32BAA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Tests", "LibMatrix\ArcaneLibs\ArcaneLibs.Tests\ArcaneLibs.Tests.csproj", "{7CB33E73-7CB8-4503-BB7B-1DDA7F2739C9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -129,10 +131,6 @@ Global {F9BA709C-DA10-4416-A07D-C89293FB0D24}.Debug|Any CPU.Build.0 = Debug|Any CPU {F9BA709C-DA10-4416-A07D-C89293FB0D24}.Release|Any CPU.ActiveCfg = Release|Any CPU {F9BA709C-DA10-4416-A07D-C89293FB0D24}.Release|Any CPU.Build.0 = Release|Any CPU - {046D35DF-E1F2-41DA-94D3-80CF960C100A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {046D35DF-E1F2-41DA-94D3-80CF960C100A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {046D35DF-E1F2-41DA-94D3-80CF960C100A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {046D35DF-E1F2-41DA-94D3-80CF960C100A}.Release|Any CPU.Build.0 = Release|Any CPU {BC6C910C-79C7-42D3-ABD9-8B9ED9121AE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BC6C910C-79C7-42D3-ABD9-8B9ED9121AE5}.Debug|Any CPU.Build.0 = Debug|Any CPU {BC6C910C-79C7-42D3-ABD9-8B9ED9121AE5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -141,6 +139,14 @@ Global {6F3FA65D-5661-4840-B421-BF64CD4DBBD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {6F3FA65D-5661-4840-B421-BF64CD4DBBD2}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F3FA65D-5661-4840-B421-BF64CD4DBBD2}.Release|Any CPU.Build.0 = Release|Any CPU + {E50928A5-AC3E-4918-9850-ED91B4E32BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E50928A5-AC3E-4918-9850-ED91B4E32BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E50928A5-AC3E-4918-9850-ED91B4E32BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E50928A5-AC3E-4918-9850-ED91B4E32BAA}.Release|Any CPU.Build.0 = Release|Any CPU + {7CB33E73-7CB8-4503-BB7B-1DDA7F2739C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CB33E73-7CB8-4503-BB7B-1DDA7F2739C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CB33E73-7CB8-4503-BB7B-1DDA7F2739C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CB33E73-7CB8-4503-BB7B-1DDA7F2739C9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {6FDD7CD7-4526-410B-858D-35FA8292317C} = {C5771E60-0DA8-42AC-B54B-94C91B5B719B} @@ -163,10 +169,8 @@ Global {3ACB4445-D0FD-438B-B9C0-531A012D484D} = {D7A5A7D3-4D9E-4B15-9C48-5E75A91483CF} {277ED4E3-5987-450C-B82F-67AD0AAA19F1} = {D7A5A7D3-4D9E-4B15-9C48-5E75A91483CF} {F9BA709C-DA10-4416-A07D-C89293FB0D24} = {D7A5A7D3-4D9E-4B15-9C48-5E75A91483CF} - {046D35DF-E1F2-41DA-94D3-80CF960C100A} = {D7A5A7D3-4D9E-4B15-9C48-5E75A91483CF} + {7CB33E73-7CB8-4503-BB7B-1DDA7F2739C9} = {D7A5A7D3-4D9E-4B15-9C48-5E75A91483CF} EndGlobalSection GlobalSection(RiderSharedRunConfigurations) = postSolution - File = .run\local\ModerationClient (Apothecary).run.xml - File = .run\local\ModerationClient (matrix.org).run.xml EndGlobalSection EndGlobal diff --git a/ModerationClient/App.axaml.cs b/ModerationClient/App.axaml.cs
index b15c0fa..7addc19 100644 --- a/ModerationClient/App.axaml.cs +++ b/ModerationClient/App.axaml.cs
@@ -70,11 +70,15 @@ public partial class App : Application { // Register views services.AddTransient<LoginView>(); services.AddTransient<ClientView>(); + services.AddTransient<RoomView>(); + services.AddTransient<EventView>(); // Register ViewModels services.AddTransient<MainWindowViewModel>(); services.AddTransient<ClientViewModel>(); services.AddTransient<UserManagementViewModel>(); + services.AddTransient<RoomViewModel>(); + services.AddTransient<EventViewModel>(); if (cfg.TestConfiguration is not null) { services.AddSingleton(cfg.TestConfiguration); diff --git a/ModerationClient/Models/SpaceTreeNodes/RoomNode.cs b/ModerationClient/Models/SpaceTreeNodes/RoomNode.cs
index 76d5aa9..98b923a 100644 --- a/ModerationClient/Models/SpaceTreeNodes/RoomNode.cs +++ b/ModerationClient/Models/SpaceTreeNodes/RoomNode.cs
@@ -1,4 +1,6 @@ +using System.Collections.ObjectModel; using ArcaneLibs; +using LibMatrix; namespace ModerationClient.Models.SpaceTreeNodes; @@ -11,4 +13,7 @@ public class RoomNode : NotifyPropertyChanged { get => _name; set => SetField(ref _name, value); } + + public ObservableCollection<StateEventResponse> Timeline { get; } = new(); + public ObservableCollection<StateEventResponse> State { get; } = new(); } \ No newline at end of file diff --git a/ModerationClient/ModerationClient.csproj b/ModerationClient/ModerationClient.csproj
index c64d0c3..bf95720 100644 --- a/ModerationClient/ModerationClient.csproj +++ b/ModerationClient/ModerationClient.csproj
@@ -1,11 +1,12 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <Nullable>enable</Nullable> <BuiltInComInteropSupport>true</BuiltInComInteropSupport> <ApplicationManifest>app.manifest</ApplicationManifest> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> + <LangVersion>preview</LangVersion> </PropertyGroup> <ItemGroup> diff --git a/ModerationClient/Properties/launchSettings.json b/ModerationClient/Properties/launchSettings.json new file mode 100644
index 0000000..997e294 --- /dev/null +++ b/ModerationClient/Properties/launchSettings.json
@@ -0,0 +1,26 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Default": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + + } + }, + "Development": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + }, + "Local config": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Local" + } + } + } +} diff --git a/ModerationClient/Services/ClientContainer.cs b/ModerationClient/Services/ClientContainer.cs
index fa3abef..957e3cc 100644 --- a/ModerationClient/Services/ClientContainer.cs +++ b/ModerationClient/Services/ClientContainer.cs
@@ -1,8 +1,40 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + namespace ModerationClient.Services; public class ClientContainer { - public ClientContainer(MatrixAuthenticationService authService, CommandLineConfiguration cfg) + private readonly ILogger<ClientContainer> _logger; + private readonly MatrixAuthenticationService _authService; + private readonly CommandLineConfiguration _cfg; + + public ClientContainer(ILogger<ClientContainer> logger, MatrixAuthenticationService authService, CommandLineConfiguration cfg) { + _logger = logger; + _authService = authService; + _cfg = cfg; + } + + private bool _isRunning = false; + + public void EnsureRunning() + { + if (_isRunning) return; + _isRunning = true; + _ = Task.Run(Run).ContinueWith(t => { + if (t.IsFaulted) + { + _logger.LogError(t.Exception, "Error in client container task"); + } + return _isRunning = false; + }); + } + + private async Task Run() { + Console.WriteLine("Running client view model loop..."); + ArgumentNullException.ThrowIfNull(_authService.Homeserver, nameof(_authService.Homeserver)); } } \ No newline at end of file diff --git a/ModerationClient/Services/CommandLineConfiguration.cs b/ModerationClient/Services/CommandLineConfiguration.cs
index 4f7da2d..fcb5072 100644 --- a/ModerationClient/Services/CommandLineConfiguration.cs +++ b/ModerationClient/Services/CommandLineConfiguration.cs
@@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Text.Json; +using System.Text.Json.Serialization; using ArcaneLibs; using ArcaneLibs.Extensions; @@ -35,10 +37,12 @@ public record CommandLineConfiguration { List<string> args = new(); if (Profile != current.Profile) args.AddRange(["--profile", Profile]); if (IsTemporary) args.Add("--temporary"); - if (Math.Abs(Scale - 1f) > float.Epsilon) args.AddRange(["--scale", Scale.ToString()]); + if (Math.Abs(Scale - 1f) > float.Epsilon) args.AddRange(["--scale", Scale.ToString(CultureInfo.InvariantCulture)]); if (ProfileDirectory != current.ProfileDirectory) args.AddRange(["--profile-dir", ProfileDirectory]); if (!string.IsNullOrWhiteSpace(_loginData) && _loginData != current.LoginData) args.AddRange(["--login-data", _loginData!]); if (TestConfiguration is not null && TestConfiguration != current.TestConfiguration) args.AddRange(["--test-config", TestConfiguration!.ToJson()]); + + Console.WriteLine("Serialised CommandLineConfiguration: " + string.Join(" ", args)); return args.ToArray(); } public static CommandLineConfiguration FromSerialised(string[] args) { @@ -85,11 +89,13 @@ public record CommandLineConfiguration { } } + [JsonIgnore] private string? testConfiguration { get => TestConfiguration?.ToJson(); init => TestConfiguration = value is null ? null : JsonSerializer.Deserialize<TestConfig>(value); } + [JsonIgnore] public TestConfig? TestConfiguration { get; init; } public class TestConfig { diff --git a/ModerationClient/Services/MatrixAuthenticationService.cs b/ModerationClient/Services/MatrixAuthenticationService.cs
index 46ec067..e4fb99b 100644 --- a/ModerationClient/Services/MatrixAuthenticationService.cs +++ b/ModerationClient/Services/MatrixAuthenticationService.cs
@@ -28,6 +28,7 @@ public class MatrixAuthenticationService(ILogger<MatrixAuthenticationService> lo if (login is null) return; try { Homeserver = await hsProvider.GetAuthenticatedWithToken(login.Homeserver, login.AccessToken); + await Homeserver.UpdateProfilePropertyAsync("meow", "h"); IsLoggedIn = true; } catch (MatrixException e) { @@ -35,10 +36,15 @@ public class MatrixAuthenticationService(ILogger<MatrixAuthenticationService> lo } } - public async Task LoginAsync(string username, string password) { + public async Task LoginAsync(string username, string password, string? homeserver = null) { Directory.CreateDirectory(Util.ExpandPath($"{cfg.ProfileDirectory}")!); - var mxidParts = username.Split(':', 2); - var res = await hsProvider.Login(mxidParts[1], username, password); + + if (string.IsNullOrWhiteSpace(homeserver)) { + var mxidParts = username.Split(':', 2); + homeserver = mxidParts[1]; + } + + var res = await hsProvider.Login(homeserver, username, password); await File.WriteAllTextAsync(Path.Combine(cfg.ProfileDirectory, "login.json"), res.ToJson()); await LoadProfileAsync(); diff --git a/ModerationClient/Services/StatusBarService.cs b/ModerationClient/Services/StatusBarService.cs
index 57aff21..f1d7223 100644 --- a/ModerationClient/Services/StatusBarService.cs +++ b/ModerationClient/Services/StatusBarService.cs
@@ -1,4 +1,5 @@ using System; +using System.Collections.ObjectModel; using ArcaneLibs; namespace ModerationClient.Services; @@ -16,4 +17,21 @@ public class StatusBarService : NotifyPropertyChanged { get => _isBusy; set => SetField(ref _isBusy, value); } + + public ObservableCollection<Progress> ProgressBars { get; } = new(); + + + public class Progress : NotifyPropertyChanged { + public Progress(int total) { + Total = total; + } + + public int Total { get; } + public int Current { get; private set; } + + public void Increment() { + Current++; + OnPropertyChanged(nameof(Current)); + } + } } \ No newline at end of file diff --git a/ModerationClient/ViewLocator.cs b/ModerationClient/ViewLocator.cs
index 3de8107..73ceef4 100644 --- a/ModerationClient/ViewLocator.cs +++ b/ModerationClient/ViewLocator.cs
@@ -9,14 +9,15 @@ namespace ModerationClient; public class ViewLocator : IDataTemplate { public Control? Build(object? data) { try { + Console.WriteLine($"ViewLocator: Searching viewmodel for {data?.GetType().FullName ?? "null"}"); if (data is null) return null; - + var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); Console.WriteLine($"ViewLocator: Locating {name} for {data.GetType().FullName}"); var type = Type.GetType(name); Console.WriteLine($"ViewLocator: Got {type?.FullName ?? "null"}"); - + if (type != null) { var control = (Control)App.Current.Services.GetRequiredService(type); Console.WriteLine($"ViewLocator: Created {control.GetType().FullName}"); diff --git a/ModerationClient/ViewModels/ClientViewModel.cs b/ModerationClient/ViewModels/ClientViewModel.cs
index fb3681e..ab4f2da 100644 --- a/ModerationClient/ViewModels/ClientViewModel.cs +++ b/ModerationClient/ViewModels/ClientViewModel.cs
@@ -6,14 +6,18 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using ArcaneLibs; using ArcaneLibs.Collections; +using ArcaneLibs.Extensions; using LibMatrix; using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Helpers; using LibMatrix.Responses; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using ModerationClient.Models.SpaceTreeNodes; using ModerationClient.Services; @@ -40,60 +44,111 @@ public partial class ClientViewModel : ViewModelBase { private SpaceNode? _currentSpace; private readonly SpaceNode _allRoomsNode; private string _status = "Loading..."; + private RoomNode? _currentRoom; public ObservableCollection<SpaceNode> DisplayedSpaces { get; } = []; public ObservableDictionary<string, RoomNode> AllRooms { get; } = new(); public SpaceNode DirectMessages { get; } - public bool Paused { get; set; } = false; + public bool Paused { get; set; } = true; + + public string Status { + get => _status + " " + DateTime.Now; + set => SetProperty(ref _status, value); + } public SpaceNode CurrentSpace { get => _currentSpace ?? _allRoomsNode; set => SetProperty(ref _currentSpace, value); } - public string Status { - get => _status + " " + DateTime.Now; - set => SetProperty(ref _status, value); + public RoomNode? CurrentRoom { + get => _currentRoom; + set { + if (SetProperty(ref _currentRoom, value)) OnPropertyChanged(nameof(CurrentRoomViewModel)); + } } + public RoomViewModel? CurrentRoomViewModel => CurrentRoom is not null ? new RoomViewModel(_authService.Homeserver!, CurrentRoom) : null; + public async Task Run() { Console.WriteLine("Running client view model loop..."); ArgumentNullException.ThrowIfNull(_authService.Homeserver, nameof(_authService.Homeserver)); // var sh = new SyncStateResolver(_authService.Homeserver, _logger, storageProvider: new FileStorageProvider(Path.Combine(_cfg.ProfileDirectory, "syncCache"))); var store = new FileStorageProvider(Path.Combine(_cfg.ProfileDirectory, "syncCache")); + var mediaCache = new FileStorageProvider(Path.Combine(_cfg.ProfileDirectory, "mediaCache")); Console.WriteLine($"Sync store at {store.TargetPath}"); - var sh = new SyncHelper(_authService.Homeserver, _logger, storageProvider: store) { + var sh = new SyncHelper(_authService.Homeserver, new NullLogger<SyncHelper>(), storageProvider: store) { // MinimumDelay = TimeSpan.FromSeconds(1) }; Console.WriteLine("Sync helper created."); + var unoptimised = await sh.GetUnoptimisedStoreCount(); // this is slow, so we cache //optimise - we create a new scope here to make ssr go out of scope - // if((await sh.GetUnoptimisedStoreCount()) > 1000) - { + if (unoptimised > 100) { Console.WriteLine("RUN - Optimising sync store..."); Status = "Optimising sync store, please wait..."; var ssr = new SyncStateResolver(_authService.Homeserver, _logger, storageProvider: store); Console.WriteLine("Created sync state resolver..."); Status = "Optimising sync store, please wait... Creating new snapshot..."; - await ssr.OptimiseStore(); + await ssr.OptimiseStore((remaining, total) => { + if (remaining % 25 == 0) + Status = $"Optimising sync store, please wait... {remaining}/{total} remaining..."; + }); Status = "Optimising sync store, please wait... Deleting old intermediate snapshots..."; await ssr.RemoveOldSnapshots(); + for (int gen = 0; gen < GC.MaxGeneration; gen++) { + Status = $"Collecting garbage #{gen}: {Util.BytesToString(GC.GetGCMemoryInfo().TotalCommittedBytes)}"; + await Task.Delay(1000); + } + + Status = $"Collecting garbage: {GC.GetTotalMemory(true) / 1024 / 1024}MB"; + await Task.Delay(1000); } - var unoptimised = await sh.GetUnoptimisedStoreCount(); // this is slow, so we cache + GC.Collect(); + unoptimised = 0; Status = "Doing initial sync..."; + var currentSyncRes = "init"; + var lastGc = DateTime.Now; await foreach (var res in sh.EnumerateSyncAsync()) { - Program.Beep((short)250, 0); + var sw = Stopwatch.StartNew(); + //log thing + + // Program.Beep((short)250, 0); Status = $"Processing sync... {res.NextBatch}"; + + foreach (var (key, value) in res.Rooms?.Leave ?? []) { + List<StateEventResponse> events = [..value.Timeline?.Events ?? [], ..value.State?.Events ?? []]; + var ownEvents = events.Where(x => x is { StateKey: "@emma:rory.gay", Type: RoomMemberEventContent.EventId }).ToList(); + foreach (var evt in ownEvents) { + var ct = evt.ContentAs<RoomMemberEventContent>()!; + Console.WriteLine($"Room {key,60} removed: {evt.Type} {evt.StateKey} by {evt.Sender,80}, membership: {ct.Membership,6}, reason: {ct.Reason}"); + } + } + + if (res.Rooms?.Leave is { Count: > 0 }) + Console.WriteLine($"Processed {res.Rooms?.Leave?.Count} left rooms"); + // await Task.Delay(10000); + + var applySw = Stopwatch.StartNew(); await ApplySyncChanges(res); + applySw.Stop(); Program.Beep(0, 0); + if (DateTime.Now - lastGc > TimeSpan.FromMinutes(1)) { + lastGc = DateTime.Now; + GC.Collect(); + } + + Console.WriteLine($"Processed sync {currentSyncRes} in {sw.ElapsedMilliseconds}ms (applied in: {applySw.ElapsedMilliseconds}ms)"); if (Paused) { Status = "Sync loop interrupted... Press pause/break to resume."; while (Paused) await Task.Delay(1000); } - else Status = $"Syncing... {unoptimised++} unoptimised sync responses..."; + else Status = $"Syncing... {unoptimised++} unoptimised sync responses, last={currentSyncRes}..."; + + currentSyncRes = res.NextBatch; } } @@ -113,7 +168,7 @@ public partial class ClientViewModel : ViewModelBase { } if (room.Value.State?.Events is not null) { - var nameEvent = room.Value.State!.Events!.FirstOrDefault(x => x.Type == "m.room.name" && x.StateKey == ""); + var nameEvent = room.Value.State!.Events!.FirstOrDefault(x => x is { Type: "m.room.name", StateKey: "" }); if (nameEvent is not null) AllRooms[room.Key].Name = (nameEvent?.TypedContent as RoomNameEventContent)?.Name ?? ""; } @@ -124,36 +179,24 @@ public partial class ClientViewModel : ViewModelBase { // Status = $"Getting room name for {room.Key}..."; // AllRooms[room.Key].Name = await _authService.Homeserver!.GetRoom(room.Key).GetNameOrFallbackAsync(); } - } - await AwaitTasks(tasks, "Waiting for {0}/{1} tasks while applying room changes..."); + if (room.Value.Timeline?.Events is not null) { + foreach (var evt in room.Value.Timeline!.Events!) { + AllRooms[room.Key].Timeline.Add(evt); + } + } - return; - - List<string> handledRoomIds = []; - var spaces = newSync.Rooms?.Join? - .Where(x => x.Value.State?.Events is not null) - .Where(x => x.Value.State!.Events!.Any(y => y.Type == "m.room.create" && (y.TypedContent as RoomCreateEventContent)!.Type == "m.space")) - .ToList(); - Console.WriteLine("spaces: " + spaces.Count); - var nonRootSpaces = spaces - .Where(x => spaces.Any(x => x.Value.State!.Events!.Any(y => y.Type == "m.space.child" && y.StateKey == x.Key))) - .ToDictionary(); - - var rootSpaces = spaces - .Where(x => !nonRootSpaces.ContainsKey(x.Key)) - .ToDictionary(); - // var rootSpaces = spaces - // .Where(x=>!spaces.Any(x=>x.Value.State!.Events!.Any(y=>y.Type == "m.space.child" && y.StateKey == x.Key))) - // .ToList(); - - foreach (var (roomId, room) in rootSpaces) { - var space = new SpaceNode { Name = (room.State!.Events!.First(x => x.Type == "m.room.name")!.TypedContent as RoomNameEventContent).Name }; - DisplayedSpaces.Add(space); - handledRoomIds.Add(roomId); + if (room.Value.State?.Events is not null) { + foreach (var evt in room.Value.State!.Events!) { + AllRooms[room.Key].State.Add(evt); + } + } } + + await AwaitTasks(tasks, "Waiting for {0}/{1} tasks while applying room changes..."); } + private ExpiringSemaphoreCache<UserProfileResponse> _profileCache = new(); private async Task ApplyDirectMessagesChanges(StateEventResponse evt) { _logger.LogCritical("Direct messages updated!"); var dms = evt.RawContent.Deserialize<Dictionary<string, string[]?>>(); @@ -163,8 +206,10 @@ public partial class ClientViewModel : ViewModelBase { var space = DirectMessages.ChildSpaces.FirstOrDefault(x => x.RoomID == userId); if (space is null) { space = new SpaceNode { Name = userId, RoomID = userId }; - tasks.Add(_authService.Homeserver!.GetProfileAsync(userId) - .ContinueWith(r => space.Name = string.IsNullOrWhiteSpace(r.Result?.DisplayName) ? userId : r.Result.DisplayName)); + // tasks.Add(_authService.Homeserver!.GetProfileAsync(userId) + // .ContinueWith(r => space.Name = string.IsNullOrWhiteSpace(r.Result.DisplayName) ? userId : r.Result.DisplayName)); + tasks.Add(_profileCache.GetOrAdd(userId, async () => await _authService.Homeserver!.GetProfileAsync(userId), TimeSpan.FromMinutes(5)) + .ContinueWith(r => string.IsNullOrWhiteSpace(r.Result.DisplayName) ? userId : r.Result.DisplayName)); DirectMessages.ChildSpaces.Add(space); } @@ -191,9 +236,10 @@ public partial class ClientViewModel : ViewModelBase { int total = tasks.Count; while (tasks.Any(x => !x.IsCompleted)) { int incomplete = tasks.Count(x => !x.IsCompleted); - Program.Beep((short)MathUtil.Map(incomplete, 0, total, 20, 7500), 5); + // Program.Beep((short)MathUtil.Map(incomplete, 0, total, 20, 7500), 5); // Program.Beep(0, 0); - Status = string.Format(message, incomplete, total); + if (incomplete < 10 || incomplete % (total / 10) == 0) + Status = string.Format(message, incomplete, total); await Task.WhenAny(tasks); tasks.RemoveAll(x => x.IsCompleted); } diff --git a/ModerationClient/ViewModels/EventViewModel.cs b/ModerationClient/ViewModels/EventViewModel.cs new file mode 100644
index 0000000..fe5de32 --- /dev/null +++ b/ModerationClient/ViewModels/EventViewModel.cs
@@ -0,0 +1,9 @@ +namespace ModerationClient.ViewModels; + +public class EventViewModel : ViewModelBase { + + public EventViewModel() { + + } + +} \ No newline at end of file diff --git a/ModerationClient/ViewModels/LoginViewModel.cs b/ModerationClient/ViewModels/LoginViewModel.cs
index 32f0d6e..34c8d28 100644 --- a/ModerationClient/ViewModels/LoginViewModel.cs +++ b/ModerationClient/ViewModels/LoginViewModel.cs
@@ -1,13 +1,15 @@ using System; using System.Threading.Tasks; +using LibMatrix.Services; using ModerationClient.Services; namespace ModerationClient.ViewModels; -public partial class LoginViewModel(MatrixAuthenticationService authService) : ViewModelBase +public partial class LoginViewModel(MatrixAuthenticationService authService, HomeserverResolverService hsResolver) : ViewModelBase { private Exception? _exception; public string Username { get; set; } + public string? Homeserver { get; set; } public string Password { get; set; } public Exception? Exception { @@ -15,6 +17,15 @@ public partial class LoginViewModel(MatrixAuthenticationService authService) : V private set => SetProperty(ref _exception, value); } + public async Task ResolveHomeserverAsync() { + try { + string[] parts = Username.Split(':', 2); + Homeserver = (await hsResolver.ResolveHomeserverFromWellKnown(Username, enableServer: false)).Client; + } catch (Exception e) { + Console.WriteLine(e); + } + } + public async Task LoginAsync() { try { Exception = null; diff --git a/ModerationClient/ViewModels/RoomViewModel.cs b/ModerationClient/ViewModels/RoomViewModel.cs new file mode 100644
index 0000000..c18b842 --- /dev/null +++ b/ModerationClient/ViewModels/RoomViewModel.cs
@@ -0,0 +1,113 @@ +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.Specialized; +using System.ComponentModel; +using System.IO; +using System.Linq; +using ArcaneLibs; +using Avalonia; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using LibMatrix; +using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.Homeservers; +using ModerationClient.Models.SpaceTreeNodes; + +namespace ModerationClient.ViewModels; + +public class RoomViewModel : ViewModelBase { + public RoomViewModel(AuthenticatedHomeserverGeneric homeserver, RoomNode room) { + Homeserver = homeserver; + Room = room; + room.Timeline.CollectionChanged += (_, args) => { + foreach (var obj in args.NewItems ?? ImmutableList<object>.Empty) { + if (obj is StateEventResponse evt) { + if (evt.Type == "m.room.member") + Users = room.State.Where(x => x.Type == "m.room.member").Select(x => { + var tc = x.TypedContent as RoomMemberEventContent; + return new RoomMember(homeserver) { + AvatarUrl = tc.AvatarUrl, + DisplayName = tc.DisplayName, + UserId = x.StateKey! + }; + }).ToFrozenSet(); + } + } + }; + + Users = room.State.Where(x => x.Type == "m.room.member" && x.ContentAs<RoomMemberEventContent>()?.Membership == "join").Select(x => { + var tc = x.TypedContent as RoomMemberEventContent; + return new RoomMember(homeserver) { + AvatarUrl = tc.AvatarUrl, + DisplayName = tc.DisplayName, + UserId = x.StateKey! + }; + }).ToFrozenSet(); + } + + public AuthenticatedHomeserverGeneric Homeserver { get; } + public RoomNode Room { get; } + public Bitmap? RoomIconBitmap { get; } + public FrozenSet<RoomMember> Users { get; private set; } = FrozenSet<RoomMember>.Empty; +} + +public class RoomMember(AuthenticatedHomeserverGeneric homeserver) : NotifyPropertyChanged { + public string UserId { get; set; } + public string DisplayName { get; set; } + private static readonly string MediaDir = Path.Combine("/tmp", "ModerationClient", "media"); + + public string AvatarUrl { + get; + set { + field = value; + + if(!Directory.Exists(MediaDir)) + Directory.CreateDirectory(MediaDir); + + var fsPath = Path.Combine(MediaDir, AvatarUrl.Replace("/", "_")); + Console.WriteLine($"Avatar path for {UserId}: {fsPath}"); + if (File.Exists(fsPath)) { + UserAvatarBitmap = new Bitmap(fsPath); + Console.WriteLine($"Avatar bitmap for {UserId} loaded: {UserAvatarBitmap.GetHashCode()}, Path: {fsPath}"); + return; + } + + homeserver.GetMediaStreamAsync(field).ContinueWith(async streamTask => { + try { + await using var stream = await streamTask; + Console.WriteLine($"Avatar stream for {UserId} received: {stream.GetHashCode()}"); + // await using var ms = new MemoryStream(); + // await stream.CopyToAsync(ms); + // var fs = new FileStream("avatar.png", FileMode.Create); + // ms.Seek(0, SeekOrigin.Begin); + // Console.WriteLine($"Avatar stream for {UserId} copied: {ms.GetHashCode()}"); + // var bm = new Bitmap(ms); + // Console.WriteLine($"Avatar bitmap for {UserId} loaded: {bm.GetHashCode()}"); + // var sbm = bm.CreateScaledBitmap(new(32, 32)); + // Console.WriteLine($"Avatar bitmap for {UserId} loaded: {sbm.GetHashCode()}"); + // UserAvatarBitmap = sbm; + // Console.WriteLine($"Bitmap for {UserId} set to {UserAvatarBitmap.GetHashCode()}"); + + var fs = new FileStream(fsPath, FileMode.Create); + await stream.CopyToAsync(fs); + fs.Close(); + UserAvatarBitmap = new Bitmap(fsPath); + Console.WriteLine($"Avatar bitmap for {UserId} loaded: {UserAvatarBitmap.GetHashCode()}, Path: {fsPath}"); + } + catch (Exception e) { + Console.WriteLine($"Failed to load avatar for {UserId}: {e}"); + UserAvatarBitmap = new Bitmap(PixelFormat.Rgba8888, AlphaFormat.Unpremul, 1, PixelSize.Empty, Vector.Zero, 0); + } + }); + // UserAvatarBitmap = new Bitmap(); + } + } + + public Bitmap UserAvatarBitmap { + get; + private set => SetField(ref field, value); + } +} \ No newline at end of file diff --git a/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs b/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs
index 90020d6..0e99298 100644 --- a/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs +++ b/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs
@@ -25,7 +25,7 @@ public partial class UserManagementViewModel : ViewModelBase { private readonly MatrixAuthenticationService _authService; private readonly CommandLineConfiguration _cfg; private string _status = "Loading..."; - public ObservableCollection<User> Users { get; set; } = []; + public ObservableCollection<SynapseAdminUser> Users { get; set; } = []; public string Status { get => _status + " " + DateTime.Now; @@ -34,7 +34,7 @@ public partial class UserManagementViewModel : ViewModelBase { public async Task Run() { Users.Clear(); - Status = "Doing initial sync..."; + Status = "Fetching user list..."; if (_authService.Homeserver is not AuthenticatedHomeserverSynapse synapse) { Console.WriteLine("This client only supports Synapse homeservers."); return; @@ -43,12 +43,12 @@ public partial class UserManagementViewModel : ViewModelBase { await foreach (var user in synapse.Admin.SearchUsersAsync(chunkLimit: 100)) { Program.Beep(250, 1); Console.WriteLine("USERMANAGER GOT USER: " + user.ToJson(indent:false, ignoreNull: true)); - Users.Add(JsonSerializer.Deserialize<User>(user.ToJson())!); + Users.Add(JsonSerializer.Deserialize<SynapseAdminUser>(user.ToJson())!); } Console.WriteLine("Done."); } } -public class User : SynapseAdminUserListResult.SynapseAdminUserListResultUser { +public class SynapseAdminUser : SynapseAdminUserListResult.SynapseAdminUserListResultUser { } \ No newline at end of file diff --git a/ModerationClient/Views/MainWindow/ClientView.axaml b/ModerationClient/Views/MainWindow/ClientView.axaml
index e0cd4e0..6db948a 100644 --- a/ModerationClient/Views/MainWindow/ClientView.axaml +++ b/ModerationClient/Views/MainWindow/ClientView.axaml
@@ -19,11 +19,6 @@ </Grid.ColumnDefinitions> <TreeView Grid.Column="0" Background="#202020" ItemsSource="{CompiledBinding DisplayedSpaces}" SelectedItem="{CompiledBinding CurrentSpace}"> <TreeView.ItemTemplate> - <!-- <TreeView.Styles> --> - <!-- <Style Selector="TreeViewItem"> --> - <!-- <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> --> - <!-- </Style> --> - <!-- </TreeView.Styles> --> <TreeDataTemplate ItemsSource="{Binding ChildSpaces}" DataType="spaceTreeNodes:SpaceNode"> <TextBlock Text="{Binding Name}" Height="20" /> </TreeDataTemplate> @@ -31,7 +26,7 @@ </TreeView> <GridSplitter Grid.Column="1" Background="Black" ResizeDirection="Columns" /> <!-- <Rectangle Grid.Column="2" Fill="Green" /> --> - <ListBox Grid.Column="2" Background="#242424" ItemsSource="{CompiledBinding CurrentSpace.ChildRooms}"> + <ListBox Grid.Column="2" Background="#242424" ItemsSource="{CompiledBinding CurrentSpace.ChildRooms}" SelectedItem="{CompiledBinding CurrentRoom}"> <ListBox.ItemTemplate> <DataTemplate DataType="spaceTreeNodes:RoomNode"> <TextBlock Text="{CompiledBinding Name}" Height="20" /> @@ -39,7 +34,7 @@ </ListBox.ItemTemplate> </ListBox> <GridSplitter Grid.Column="3" Background="Black" ResizeDirection="Columns" /> - <Rectangle Grid.Column="4" Fill="#282828" /> + <ContentControl Grid.Column="4" Content="{CompiledBinding CurrentRoomViewModel}" /> </Grid> <Grid Grid.Row="1" ColumnDefinitions="Auto, *, Auto" Background="Black"> <Label Grid.Column="2" Content="{CompiledBinding Status}" /> diff --git a/ModerationClient/Views/MainWindow/EventView.axaml b/ModerationClient/Views/MainWindow/EventView.axaml new file mode 100644
index 0000000..1815430 --- /dev/null +++ b/ModerationClient/Views/MainWindow/EventView.axaml
@@ -0,0 +1,10 @@ +<UserControl xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:libMatrix="clr-namespace:LibMatrix;assembly=LibMatrix" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + x:Class="ModerationClient.Views.EventView" + x:DataType="libMatrix:StateEventResponse"> + Welcome to Avalonia! +</UserControl> diff --git a/ModerationClient/Views/MainWindow/EventView.axaml.cs b/ModerationClient/Views/MainWindow/EventView.axaml.cs new file mode 100644
index 0000000..f07103f --- /dev/null +++ b/ModerationClient/Views/MainWindow/EventView.axaml.cs
@@ -0,0 +1,11 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ModerationClient.Views; + +public partial class EventView : UserControl { + public EventView() { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/ModerationClient/Views/MainWindow/LoginView.axaml b/ModerationClient/Views/MainWindow/LoginView.axaml
index 5dc6533..e5fcb84 100644 --- a/ModerationClient/Views/MainWindow/LoginView.axaml +++ b/ModerationClient/Views/MainWindow/LoginView.axaml
@@ -14,6 +14,10 @@ <TextBox MinWidth="250" Text="{Binding Username, Mode=TwoWay}" /> </StackPanel> <StackPanel Orientation="Horizontal"> + <Label Width="100">Homeserver</Label> + <TextBox MinWidth="250" Text="{Binding Homeserver, Mode=TwoWay}" /> + </StackPanel> + <StackPanel Orientation="Horizontal"> <Label Width="100">Password</Label> <MaskedTextBox MinWidth="250" PasswordChar="*" Text="{Binding Password, Mode=TwoWay}" /> </StackPanel> diff --git a/ModerationClient/Views/MainWindow/MainWindow.axaml.cs b/ModerationClient/Views/MainWindow/MainWindow.axaml.cs
index cc3534d..b632b87 100644 --- a/ModerationClient/Views/MainWindow/MainWindow.axaml.cs +++ b/ModerationClient/Views/MainWindow/MainWindow.axaml.cs
@@ -22,7 +22,7 @@ namespace ModerationClient.Views.MainWindow; public partial class MainWindow : Window { private readonly CommandLineConfiguration _cfg; - public MainWindow(CommandLineConfiguration cfg, MainWindowViewModel dataContext, IHostApplicationLifetime appLifetime) { + public MainWindow(CommandLineConfiguration cfg, MainWindowViewModel dataContext, IHostApplicationLifetime appLifetime, HomeserverResolverService hsResolver) { _cfg = cfg; InitializeComponent(); DataContext = dataContext; @@ -33,7 +33,7 @@ public partial class MainWindow : Window { dataContext.CurrentViewModel = dataContext.AuthService.IsLoggedIn ? App.Current.Host.Services.GetRequiredService<ClientViewModel>() - : new LoginViewModel(dataContext.AuthService); + : new LoginViewModel(dataContext.AuthService, hsResolver); if (!dataContext.AuthService.IsLoggedIn) { dataContext.AuthService.PropertyChanged += (sender, args) => { @@ -45,7 +45,7 @@ public partial class MainWindow : Window { // window.Show(); } else { - dataContext.CurrentViewModel = new LoginViewModel(dataContext.AuthService); + dataContext.CurrentViewModel = new LoginViewModel(dataContext.AuthService, hsResolver); } } }; diff --git a/ModerationClient/Views/MainWindow/RoomView.axaml b/ModerationClient/Views/MainWindow/RoomView.axaml new file mode 100644
index 0000000..cd74b0d --- /dev/null +++ b/ModerationClient/Views/MainWindow/RoomView.axaml
@@ -0,0 +1,53 @@ +<UserControl xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:viewModels="clr-namespace:ModerationClient.ViewModels" + xmlns:libMatrix="clr-namespace:LibMatrix;assembly=LibMatrix" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + x:Class="ModerationClient.Views.RoomView" + x:DataType="viewModels:RoomViewModel"> + <Grid RowDefinitions="32,*"> + <Grid> + <Image Source="{CompiledBinding RoomIconBitmap}"></Image> + <Label Content="{CompiledBinding Room.Name}" /> + </Grid> + <Grid Grid.Row="1" Grid.ColumnDefinitions="*,1,200"> + <!-- timeline --> + <ScrollViewer Grid.Column="0" Background="#222222"> + <ItemsControl ItemsSource="{CompiledBinding Room.Timeline}"> + <ItemsControl.ItemTemplate> + <DataTemplate DataType="libMatrix:StateEventResponse"> + <Grid ColumnDefinitions="32, *"> + <Image></Image> + <Grid Grid.Column="1" RowDefinitions="*,*"> + <TextBlock Text="{CompiledBinding Sender}" /> + <TextBlock Grid.Row="1" Text="{CompiledBinding RawContent}" /> + </Grid> + </Grid> + </DataTemplate> + </ItemsControl.ItemTemplate> + </ItemsControl> + </ScrollViewer> + <GridSplitter Grid.Column="1" Background="Black" ResizeDirection="Columns" /> + <!-- members --> + <ScrollViewer Grid.Column="2" Background="#222222"> + <ItemsControl ItemsSource="{CompiledBinding Users}"> + <ItemsControl.ItemTemplate> + <DataTemplate DataType="viewModels:RoomMember"> + <Grid ColumnDefinitions="32,*" Margin="0,4"> + <Image Source="{CompiledBinding UserAvatarBitmap}" Height="32" Width="32" Margin="0,0,4,0" /> + <Grid Grid.Column="1" RowDefinitions="*,*"> + <TextBlock Text="{CompiledBinding DisplayName}" /> + <TextBlock Grid.Row="1" Text="{CompiledBinding UserId}" FontSize="10" FontStyle="Italic" /> + </Grid> + + <WrapPanel Children=""></WrapPanel> + </Grid> + </DataTemplate> + </ItemsControl.ItemTemplate> + </ItemsControl> + </ScrollViewer> + </Grid> + </Grid> +</UserControl> \ No newline at end of file diff --git a/ModerationClient/Views/MainWindow/RoomView.axaml.cs b/ModerationClient/Views/MainWindow/RoomView.axaml.cs new file mode 100644
index 0000000..aedd8e9 --- /dev/null +++ b/ModerationClient/Views/MainWindow/RoomView.axaml.cs
@@ -0,0 +1,11 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ModerationClient.Views; + +public partial class RoomView : UserControl { + public RoomView() { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/ModerationClient/Views/MainWindow/StatusBar.axaml b/ModerationClient/Views/MainWindow/StatusBar.axaml new file mode 100644
index 0000000..d299278 --- /dev/null +++ b/ModerationClient/Views/MainWindow/StatusBar.axaml
@@ -0,0 +1,10 @@ +<UserControl xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + x:Class="ModerationClient.Views.MainWindow.StatusBar"> + <Grid ColumnDefinitions="*,*,*"> + + </Grid> +</UserControl> \ No newline at end of file diff --git a/ModerationClient/Views/MainWindow/StatusBar.axaml.cs b/ModerationClient/Views/MainWindow/StatusBar.axaml.cs new file mode 100644
index 0000000..78a74ca --- /dev/null +++ b/ModerationClient/Views/MainWindow/StatusBar.axaml.cs
@@ -0,0 +1,15 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using ModerationClient.Services; + +namespace ModerationClient.Views.MainWindow; + +public partial class StatusBar : UserControl { + public StatusBarService StatusBarService { get; } + + public StatusBar(StatusBarService statusBarService) { + StatusBarService = statusBarService; + InitializeComponent(); + } +} \ No newline at end of file diff --git a/ModerationClient/Views/UserManagementWindow.axaml b/ModerationClient/Views/UserManagementWindow.axaml
index ef93517..688b687 100644 --- a/ModerationClient/Views/UserManagementWindow.axaml +++ b/ModerationClient/Views/UserManagementWindow.axaml
@@ -37,7 +37,7 @@ <ContentControl DataContext="{CompiledBinding Path=CurrentViewModel}"> <ItemsControl ItemsSource="{ReflectionBinding Users}"> <ItemsControl.ItemTemplate> - <DataTemplate DataType="vm:User"> + <DataTemplate DataType="vm:SynapseAdminUser"> <StackPanel Orientation="Vertical"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{CompiledBinding Name}" /> diff --git a/ModerationClient/Views/UserManagementWindow.axaml.cs b/ModerationClient/Views/UserManagementWindow.axaml.cs
index 2d2dfb4..f5b4d56 100644 --- a/ModerationClient/Views/UserManagementWindow.axaml.cs +++ b/ModerationClient/Views/UserManagementWindow.axaml.cs
@@ -1,4 +1,5 @@ using System; +using System.IO; using ArcaneLibs.Extensions; using Avalonia; using Avalonia.Controls; @@ -64,7 +65,7 @@ public partial class UserManagementWindow : Window { dataContext.CurrentViewModel = App.Current.Host.Services.GetRequiredService<ClientViewModel>(); } else { - dataContext.CurrentViewModel = new LoginViewModel(dataContext.AuthService); + Close(); } } }; @@ -118,7 +119,7 @@ public partial class UserManagementWindow : Window { return; } - if (button.Tag is not User user) { + if (button.Tag is not SynapseAdminUser user) { Console.WriteLine("WARN: Tag is not User, ignoring PuppetButtonClicked!"); return; } @@ -129,8 +130,14 @@ public partial class UserManagementWindow : Window { } 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()); + // var currentProcess = System.Diagnostics.Process.GetCurrentProcess().MainModule!.FileName; + // if(Path.GetFileName(currentProcess) == "dotnet") { + // Console.WriteLine("WARN: Running in dotnet, trying again...!"); + // // get the path to the current executable + // var path = System.Reflection.Assembly.GetEntryAssembly()!.Location; + // Console.WriteLine(path); + // return; + // } + System.Diagnostics.Process.Start("steam-run", ["dotnet", System.Reflection.Assembly.GetEntryAssembly()!.Location, "--", ..(_cfg with { IsTemporary = true, LoginData = puppet.ToJson() }).Serialise()]); } } \ No newline at end of file diff --git a/ModerationClient/avatar.png b/ModerationClient/avatar.png new file mode 100644
index 0000000..e69de29 --- /dev/null +++ b/ModerationClient/avatar.png
diff --git a/Test/Test.csproj b/Test/Test.csproj
index 39961c1..fa7b068 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj
@@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup>