about summary refs log tree commit diff
path: root/MatrixUtils.Web/Pages/Rooms/Index2Components
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixUtils.Web/Pages/Rooms/Index2Components')
-rw-r--r--MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor27
-rw-r--r--MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css15
-rw-r--r--MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor53
-rw-r--r--MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor198
-rw-r--r--MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css0
-rw-r--r--MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor202
6 files changed, 495 insertions, 0 deletions
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor
new file mode 100644
index 0000000..4216824
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor
@@ -0,0 +1,27 @@
+@using MatrixUtils.Abstractions
+<div class="spaceListItem" onclick="@ToggleSpace">
+    <MxcImage Circular="true" Height="32" Width="32" Homeserver="Space.Room.Homeserver" MxcUri="@Space.RoomIcon"></MxcImage>
+    <span class="spaceNameEllipsis">@Space.RoomName</span>
+</div>
+
+@code {
+
+    [Parameter]
+    public RoomInfo Space { get; set; }
+
+    [Parameter]
+    public List<RoomInfo> OpenedSpaces { get; set; }
+    
+    protected override Task OnInitializedAsync() {
+        Space.PropertyChanged += (sender, args) => { StateHasChanged(); };
+        return base.OnInitializedAsync();
+    }
+
+    public void ToggleSpace() {
+        if (OpenedSpaces.Contains(Space)) {
+            OpenedSpaces.Remove(Space);
+        } else {
+            OpenedSpaces.Add(Space);
+        }
+    }
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css
new file mode 100644
index 0000000..c174567
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css
@@ -0,0 +1,15 @@
+.spaceNameEllipsis {
+    padding-left: 8px;
+    display: inline-block;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    vertical-align: middle;
+    width: calc(100% - 38px);
+}
+
+.spaceListItem {
+    display: block;
+    width: 100%;
+    height: 50px;
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor
new file mode 100644
index 0000000..f4cf849
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor
@@ -0,0 +1,53 @@
+@using MatrixUtils.Abstractions
+@using System.Security.Cryptography
+@using ArcaneLibs.Extensions
+<h3>RoomsIndex2MainTab</h3>
+
+<div>
+    <div class="row">
+        <div class="col-3" style="background-color: #ffffff66;">
+            <LinkButton>Uncategorised rooms</LinkButton>
+            @foreach (var space in Data.Rooms.Where(x => x.RoomType == "m.space")) {
+                <div style="@("width: 100%; height: 50px; background-color: #" + RandomNumberGenerator.GetBytes(3).Append((byte)0x11).ToArray().AsHexString().Replace(" ",""))">
+                    <p>@space.RoomName</p>
+                </div>
+            }
+        </div>
+        <div class="col-9" style="background-color: #ff00ff66;">
+            <p>omae wa mou shindeiru</p>
+        </div>
+    </div>
+</div>
+
+@code {
+
+    [CascadingParameter]
+    public Index2.RoomListViewData Data { get; set; } = null!;
+
+    protected override async Task OnInitializedAsync() {
+        Data.Rooms.CollectionChanged += (sender, args) => {
+            DebouncedStateHasChanged();
+            if (args.NewItems is { Count: > 0 })
+                foreach (var newItem in args.NewItems) {
+                    (newItem as RoomInfo).PropertyChanged += (sender, args) => { DebouncedStateHasChanged(); };
+                }
+        };
+        await base.OnInitializedAsync();
+    }
+
+    //debounce StateHasChanged, we dont want to reredner on every key stroke
+
+    private CancellationTokenSource _debounceCts = new CancellationTokenSource();
+
+    private async Task DebouncedStateHasChanged() {
+        _debounceCts.Cancel();
+        _debounceCts = new CancellationTokenSource();
+        try {
+            await Task.Delay(100, _debounceCts.Token);
+            Console.WriteLine("DebouncedStateHasChanged - Calling StateHasChanged!");
+            StateHasChanged();
+        }
+        catch (TaskCanceledException) { }
+    }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor
new file mode 100644
index 0000000..2b7c5ac
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor
@@ -0,0 +1,198 @@
+@using MatrixUtils.Abstractions
+@using System.Security.Cryptography
+@using ArcaneLibs.Extensions
+@using System.ComponentModel
+@using System.Diagnostics
+@using LibMatrix.EventTypes.Spec.State
+@using MatrixUtils.Web.Pages.Rooms.Index2Components.MainTabComponents
+@using Microsoft.AspNetCore.Components.Rendering
+<h3>RoomsIndex2MainTab</h3>
+
+@* <div> *@
+@*     <div class="row"> *@
+@*         <div class="col-3" style="background-color: #ffffff66;"> *@
+@*             <LinkButton>Uncategorised rooms</LinkButton> *@
+@*             @foreach (var space in GetTopLevelSpaces()) { *@
+@*                 <a style="@("display:block; width: 100%; height: 50px; background-color: #" + RandomNumberGenerator.GetBytes(3).Append((byte)0x11).ToArray().AsHexString().Replace(" ", ""))"> *@
+@*                     <div style="vertical-align: middle;"> *@
+@*                         <div style="overflow:hidden; text-overflow: ellipsis; white-space: nowrap; ">@space.RoomName</div> *@
+@*                     </div> *@
+@*                 </a> *@
+@*             } *@
+@*         </div> *@
+@*         <div class="col-9" style="background-color: #ff00ff66;"> *@
+@*             <p>Placeholder for rooms list...</p> *@
+@*         </div> *@
+@*     </div> *@
+@* </div> *@
+
+<div>
+    <div class="row">
+        <div class="col-3" style="background-color: #ffffff22;">
+            <LinkButton>Uncategorised rooms</LinkButton>
+            @foreach (var space in GetTopLevelSpaces()) {
+                @RecursingSpaceChildren(space)
+            }
+        </div>
+        <div class="col-9" style="background-color: #ff00ff66;">
+            <p>Placeholder for rooms list...</p>
+        </div>
+    </div>
+</div>
+
+
+@code {
+
+    [CascadingParameter]
+    public Index2.RoomListViewData Data { get; set; } = null!;
+
+    protected override async Task OnInitializedAsync() {
+        Data.Rooms.CollectionChanged += (sender, args) => {
+            DebouncedStateHasChanged();
+            if (args.NewItems is { Count: > 0 })
+                foreach (var newItem in args.NewItems) {
+                    (newItem as RoomInfo).PropertyChanged += OnRoomListChanged;
+                    (newItem as RoomInfo).StateEvents.CollectionChanged += (sender, args) => { DebouncedStateHasChanged(); };
+                }
+        };
+        foreach (var newItem in Data.Rooms) {
+            newItem.PropertyChanged += OnRoomListChanged;
+            newItem.StateEvents.CollectionChanged += (sender, args) => { DebouncedStateHasChanged(); };
+        }
+
+        await base.OnInitializedAsync();
+        StateHasChanged();
+    }
+
+    private void OnRoomListChanged(object? sender, PropertyChangedEventArgs e) {
+        if (e.PropertyName == "RoomName" || e.PropertyName == "RoomType")
+            DebouncedStateHasChanged();
+    }
+
+    private CancellationTokenSource _debounceCts = new CancellationTokenSource();
+
+    private async Task DebouncedStateHasChanged() {
+        _debounceCts.Cancel();
+        _debounceCts = new CancellationTokenSource();
+        try {
+            Console.WriteLine("DebouncedStateHasChanged - Waiting 50ms...");
+            await Task.Delay(50, _debounceCts.Token);
+            Console.WriteLine("DebouncedStateHasChanged - Calling StateHasChanged!");
+            StateHasChanged();
+        }
+        catch (TaskCanceledException) { }
+    }
+
+    private List<RoomInfo> GetTopLevelSpaces() {
+        var spaces = Data.Rooms.Where(x => x.RoomType == "m.space").OrderBy(x => x.RoomName).ToList();
+        var allSpaceChildEvents = spaces.SelectMany(x => x.StateEvents.Where(y =>
+            y.Type == SpaceChildEventContent.EventId &&
+            y.RawContent!.Count > 0
+        )).ToList();
+
+        Console.WriteLine($"Child count: {allSpaceChildEvents.Count}");
+
+        spaces.RemoveAll(x => allSpaceChildEvents.Any(y => y.StateKey == x.Room.RoomId));
+
+        if (allSpaceChildEvents.Count == 0) {
+            Console.WriteLine("No space children found, returning nothing...");
+            return [];
+        }
+
+        return spaces.ToList();
+    }
+
+    private List<RoomInfo> GetSpaceChildren(RoomInfo space) {
+        var childEvents = space.StateEvents.Where(x =>
+            x.Type == SpaceChildEventContent.EventId &&
+            x.RawContent!.Count > 0
+        ).ToList();
+        var children = childEvents.Select(x => Data.Rooms.FirstOrDefault(y => y.Room.RoomId == x.StateKey)).Where(x => x is not null).ToList();
+        return children;
+    }
+
+    private List<RoomInfo> GetSpaceChildSpaces(RoomInfo space) {
+        var children = GetSpaceChildren(space);
+        var childSpaces = children.Where(x => x.RoomType == "m.space").ToList();
+        return childSpaces;
+    }
+
+    private RoomInfo? SelectedSpace { get; set; }
+    private List<RoomInfo> OpenedSpaces { get; set; } = new List<RoomInfo>();
+
+    private RenderFragment RecursingSpaceChildren(RoomInfo space, List<RoomInfo>? parents = null, int depth = 0) {
+        parents ??= [];
+        var totalSw = Stopwatch.StartNew();
+        var children = GetSpaceChildSpaces(space);
+
+        var randomColor = RandomNumberGenerator.GetBytes(3).Append((byte)0x33).ToArray().AsHexString().Replace(" ", "");
+        var isExpanded = OpenedSpaces.Contains(space);
+
+        // Console.WriteLine($"RecursingSpaceChildren::FetchData - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}");
+
+        // var renderSw = Stopwatch.StartNew();
+        var rf = new RenderFragment(builder => {
+            builder.OpenElement(0, "div");
+            //space list entry render fragment
+            // builder.AddContent(1, SpaceListEntry(space));
+            builder.OpenComponent<MainTabSpaceItem>(1);
+            builder.AddAttribute(2, "Space", space);
+            builder.AddAttribute(2, "OpenedSpaces", OpenedSpaces);
+            builder.CloseComponent();
+            builder.CloseElement();
+            //space children render fragment
+            if (isExpanded) {
+                builder.OpenElement(2, "div");
+                builder.AddAttribute(3, "style", "padding-left: 10px;");
+                foreach (var child in children) {
+                    builder.AddContent(4, RecursingSpaceChildren(child, parents.Append(space).ToList(), depth + 1));
+                }
+
+                builder.CloseElement();
+            }
+        });
+
+        // Console.WriteLine($"RecursingSpaceChildren::Render - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {renderSw.Elapsed}");
+        if (totalSw.ElapsedMilliseconds > 20)
+            Console.WriteLine($"RecursingSpaceChildren::Total - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}");
+        // Console.WriteLine($"RecursingSpaceChildren::Total - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}");
+        return rf;
+    }
+
+    // private RenderFragment SpaceListEntry(RoomInfo space) {
+    //     return builder => {
+    //         {
+    //             builder.OpenElement(0, "div");
+    //             builder.AddAttribute(1, "style", "display: block; width: 100%; height: 50px;");
+    //             builder.AddAttribute(2, "onclick", EventCallback.Factory.Create(this, () => {
+    //                 if (OpenedSpaces.Contains(space)) {
+    //                     OpenedSpaces.Remove(space);
+    //                 }
+    //                 else {
+    //                     OpenedSpaces.Add(space);
+    //                 }
+    //
+    //                 StateHasChanged();
+    //             }));
+    //             {
+    //                 builder.OpenComponent<MxcImage>(5);
+    //                 builder.AddAttribute(6, "Homeserver", Data.Homeserver);
+    //                 builder.AddAttribute(7, "MxcUri", space.RoomIcon);
+    //                 builder.AddAttribute(8, "Circular", true);
+    //                 builder.AddAttribute(9, "Width", 32);
+    //                 builder.AddAttribute(10, "Height", 32);
+    //                 builder.CloseComponent();
+    //             }
+    //             {
+    //                 // room name, ellipsized
+    //                 builder.OpenElement(11, "span");
+    //                 builder.AddAttribute(12, "class", "spaceNameEllipsis");
+    //                 builder.AddContent(13, space.RoomName);
+    //                 builder.CloseElement();
+    //             }
+    //             builder.CloseElement();
+    //         }
+    //     };
+    // }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor
new file mode 100644
index 0000000..bbc63eb
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor
@@ -0,0 +1,202 @@
+@using LibMatrix.Helpers
+@using LibMatrix.Responses
+@using MatrixUtils.Abstractions
+@using System.Diagnostics
+@using System.Diagnostics.CodeAnalysis
+@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.Extensions
+@using LibMatrix.Utilities
+@using System.Collections.ObjectModel
+@using ArcaneLibs
+@inject ILogger<RoomsIndex2SyncContainer> logger
+<pre>RoomsIndex2SyncContainer</pre>
+@foreach (var (name, value) in _statusList) {
+    <pre>[@name] @value.Status</pre>
+}
+
+@code {
+
+    [Parameter]
+    public Index2.RoomListViewData Data { get; set; } = null!;
+
+    private SyncHelper syncHelper;
+
+    private Queue<KeyValuePair<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure>> queue = new();
+
+    private ObservableCollection<(string name, ObservableStatus value)> _statusList = new();
+
+    protected override async Task OnInitializedAsync() {
+        _statusList.CollectionChanged += (sender, args) => {
+            StateHasChanged();
+            if (args.NewItems is { Count: > 0 })
+                foreach (var item in args.NewItems) {
+                    if (item is not (string name, ObservableStatus value)) continue;
+                    value.PropertyChanged += (sender, args) => {
+                        if(value.Show) StateHasChanged();
+                    };
+                }
+        };
+
+        while (Data.Homeserver is null) {
+            await Task.Delay(100);
+        }
+
+        await SetUpSync();
+    }
+
+    private async Task SetUpSync() {
+        var status = await GetOrAddStatus("Main");
+        var syncHelpers = new Dictionary<string, SyncHelper>() {
+            ["Main"] = new SyncHelper(Data.Homeserver, logger) {
+                Timeout = 30000,
+                FilterId = await Data.Homeserver.GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetBasicRoomInfo),
+                // MinimumDelay = TimeSpan.FromMilliseconds(5000)
+            }
+        };
+        status.Status = "Initial sync... Checking server filter capability...";
+        var syncRes = await syncHelpers["Main"].SyncAsync();
+        if (!syncRes.Rooms?.Join?.Any(x => x.Value.State?.Events?.Any(y => y.Type == SpaceChildEventContent.EventId) ?? false) ?? true) {
+            status.Status = "Initial sync indicates that server supports filters, starting helpers!";
+            syncHelpers.Add("SpaceRelations", new SyncHelper(Data.Homeserver, logger) {
+                Timeout = 30000,
+                FilterId = await Data.Homeserver.GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetSpaceRelations),
+                // MinimumDelay = TimeSpan.FromMilliseconds(5000)
+            });
+
+            syncHelpers.Add("Profile", new SyncHelper(Data.Homeserver, logger) {
+                Timeout = 30000,
+                FilterId = await Data.Homeserver.GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetOwnMemberEvents),
+                // MinimumDelay = TimeSpan.FromMilliseconds(5000)
+            });
+        }
+        else status.Status = "Initial sync indicates that server does not support filters, continuing without extra filters!";
+
+        await HandleSyncResponse(syncRes);
+
+        //  profileSyncHelper = new SyncHelper(Homeserver, logger) {
+        //     Timeout = 10000,
+        //     Filter = profileUpdateFilter,
+        //     MinimumDelay = TimeSpan.FromMilliseconds(5000)
+        //  };
+        // profileUpdateFilter.Room.State.Senders.Add(Homeserver.WhoAmI.UserId);
+        RunQueueProcessor();
+        foreach (var helper in syncHelpers) {
+            Console.WriteLine($"Starting sync loop for {helper.Key}");
+            RunSyncLoop(helper.Value, helper.Key);
+        }
+    }
+
+    private async Task RunQueueProcessor() {
+        var status = await GetOrAddStatus("QueueProcessor");
+        var statusd = await GetOrAddStatus("QueueProcessor/D", show: false);
+        while (true) {
+            await Task.Delay(1000);
+            try {
+                var renderTimeSw = Stopwatch.StartNew();
+                while (queue.Count == 0) {
+                    var delay = 1000;
+                    Console.WriteLine("Queue is empty, waiting...");
+                    // Status2 = $"Queue is empty, waiting for {delay}ms...";
+                    await Task.Delay(delay);
+                }
+
+                status.Status = $"Queue no longer empty after {renderTimeSw.Elapsed}!";
+                renderTimeSw.Restart();
+
+                int maxUpdates = 5000;
+                while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) {
+                    var (roomId, roomData) = queueEntry;
+                    statusd.Status = $"Dequeued room {roomId}";
+                    RoomInfo room;
+
+                    if (Data.Rooms.Any(x => x.Room.RoomId == roomId)) {
+                        room = Data.Rooms.First(x => x.Room.RoomId == roomId);
+                        statusd.Status = $"{roomId} already known with {room.StateEvents?.Count ?? 0} state events";
+                    }
+                    else {
+                        statusd.Status = $"Eencountered new room {roomId}!";
+                        room = new RoomInfo(Data.Homeserver!.GetRoom(roomId), roomData.State?.Events);
+                        Data.Rooms.Add(room);
+                    }
+
+                    if (roomData.State?.Events is { Count: > 0 })
+                        room.StateEvents!.MergeStateEventLists(roomData.State.Events);
+                    else {
+                        statusd.Status = $"Could not merge state for {room.Room.RoomId} as new data contains no state events!";
+                    }
+
+                    // await Task.Delay(10);
+                }
+
+                status.Status = $"Got {Data.Rooms.Count} rooms so far! {queue.Count} entries left in processing queue... Parsed last response in {renderTimeSw.Elapsed}";
+
+                // RenderContents |= queue.Count == 0;
+                // await Task.Delay(Data.Rooms.Count);
+            }
+            catch (Exception e) {
+                Console.WriteLine("QueueWorker exception: " + e);
+            }
+        }
+    }
+
+    private async Task RunSyncLoop(SyncHelper syncHelper, string name = "Unknown") {
+        var status = await GetOrAddStatus($"SYNC/{name}");
+        status.Status = $"Initial syncing...";
+
+        var syncs = syncHelper.EnumerateSyncAsync();
+        await foreach (var sync in syncs) {
+            var sw = Stopwatch.StartNew();
+            status.Status = $"[{DateTime.Now}] Got {Data.Rooms.Count} rooms so far! {sync.Rooms?.Join?.Count ?? 0} new updates!";
+
+            await HandleSyncResponse(sync);
+            status.Status += $"\nProcessed sync in {sw.ElapsedMilliseconds}ms, queue length: {queue.Count}";
+        }
+    }
+
+    private async Task HandleSyncResponse(SyncResponse? sync) {
+        if (sync?.Rooms?.Join is { Count: > 0 })
+            foreach (var joinedRoom in sync.Rooms.Join)
+                queue.Enqueue(joinedRoom);
+
+        if (sync.Rooms.Leave is { Count: > 0 })
+            foreach (var leftRoom in sync.Rooms.Leave)
+                if (Data.Rooms.Any(x => x.Room.RoomId == leftRoom.Key))
+                    Data.Rooms.Remove(Data.Rooms.First(x => x.Room.RoomId == leftRoom.Key));
+    }
+
+    private SemaphoreSlim _syncLock = new(1, 1);
+
+    private async Task<ObservableStatus> GetOrAddStatus(string name, bool show = true, bool log = true) {
+        await _syncLock.WaitAsync();
+        try {
+            if (_statusList.Any(x => x.name == name))
+                return _statusList.First(x => x.name == name).value;
+            var status = new ObservableStatus() {
+                Name = name,
+                Log = log,
+                Show = show
+            };
+            _statusList.Add((name, status));
+            return status;
+        }
+        finally {
+            _syncLock.Release();
+        }
+    }
+
+    private class ObservableStatus : NotifyPropertyChanged {
+        private string _status = "Initialising...";
+        public string Name { get; set; } = "Unknown";
+        public bool Show { get; set; } = true;
+        public bool Log { get; set; } = true;
+
+        public string Status {
+            get => _status;
+            set {
+                if(SetField(ref _status, value) && Log)
+                    Console.WriteLine($"[{Name}]: {value}");
+            }
+        }
+    }
+
+}
\ No newline at end of file