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>
|