about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2024-07-09 00:18:44 +0200
committerRory& <root@rory.gay>2024-07-09 00:18:44 +0200
commit63677f70bf0663ef3fae47b4fce64d9de7f428f4 (patch)
treed7fba0f181c45fee61952e3d01b67d5c2c9a9a92
parentAdd basic issues, abstract project loading to component in web (diff)
downloadBugMine-github/master.tar.xz
Older changes HEAD github/master master
-rw-r--r--BugMine.DevTools.CLI/Worker.cs31
-rw-r--r--BugMine.Sdk/BugMineClient.cs1
-rw-r--r--BugMine.Sdk/BugMineIssue.cs4
-rw-r--r--BugMine.Sdk/BugMineProject.cs6
-rw-r--r--BugMine.Sdk/Events/State/BugMineRoomMetadata.cs2
-rw-r--r--BugMine.Sdk/Events/State/ProjectInfo.cs2
-rw-r--r--BugMine.Sdk/Events/Timeline/BugMineIssueComment.cs4
-rw-r--r--BugMine.Sdk/Events/Timeline/BugMineIssueData.cs6
-rw-r--r--BugMine.Web/Components/ScopedContainers/IssueContainer.razor101
-rw-r--r--BugMine.Web/Components/ScopedContainers/ProjectContainer.razor (renamed from BugMine.Web/Components/ProjectContainer.razor)39
-rw-r--r--BugMine.Web/Pages/Projects/Boards/Index.razor19
-rw-r--r--BugMine.Web/Pages/Projects/Issues/NewIssue.razor13
-rw-r--r--BugMine.Web/Pages/Projects/Issues/ViewIssue.razor24
-rw-r--r--BugMine.Web/Pages/Projects/NewProject.razor4
-rw-r--r--BugMine.Web/Pages/Projects/ViewProject.razor53
-rw-r--r--BugMine.Web/Pages/Projects/ViewProject.razor.css8
m---------LibMatrix0
17 files changed, 274 insertions, 43 deletions
diff --git a/BugMine.DevTools.CLI/Worker.cs b/BugMine.DevTools.CLI/Worker.cs
index 4b3d3f4..93e31c9 100644
--- a/BugMine.DevTools.CLI/Worker.cs
+++ b/BugMine.DevTools.CLI/Worker.cs
@@ -36,6 +36,7 @@ public class Worker(ILogger<Worker> logger, HomeserverProviderService hsProvider
                               4) Get room count
                               5) Summarize all projects
                               6) Mass create regular rooms
+                              7) Create issues in project7
 
                               L) Logout
                               Q) Quit
@@ -88,6 +89,34 @@ public class Worker(ILogger<Worker> logger, HomeserverProviderService hsProvider
                     Console.WriteLine($"Created 1000 rooms in {sw.Elapsed}");
                     break;
                 }
+                case ConsoleKey.D7: {
+                    Console.Write("Slug: ");
+                    var slug = Console.ReadLine();
+                    var project = await Client.GetProject(slug);
+                    if (project == null) {
+                        Console.WriteLine("Project not found.");
+                        break;
+                    }
+                    Console.Write("Count: ");
+                    if (!int.TryParse(Console.ReadLine(), out var count)) {
+                        Console.WriteLine("Invalid count.");
+                        break;
+                    }
+                    await Task.WhenAll(Enumerable.Range(0,count).Select(async _ => {
+                        await project.CreateIssue(new() {
+                            Name = Guid.NewGuid().ToString()[..8]
+                        });
+                    }));
+                    break;
+                }
+                case ConsoleKey.D8: {
+                    await foreach (var board in Client.GetProjects(ignoreInvalidBoards: true).WithCancellation(stoppingToken)) {
+                        await foreach (var issue in board.GetIssues().WithCancellation(stoppingToken)) {
+                            Console.WriteLine(issue.Data.TypedContent);
+                        }
+                    }
+                    break;
+                }
                 case ConsoleKey.L: {
                     File.Delete("auth.json");
                     await ExecuteAsync(stoppingToken);
@@ -145,7 +174,7 @@ public class Worker(ILogger<Worker> logger, HomeserverProviderService hsProvider
 
     private async Task<string> SummarizeProject(BugMineProject project) {
         string result = $"Project: {project.Info.Name}, slug: {project.ProjectSlug}, metadata: {project.Metadata.ToJson(indent: false)}";
-        await foreach (var issue in project.GetIssues()) {
+        await foreach (var issue in project.GetIssues(chunkLimit:50000)) {
             // Console.WriteLine($" - {issue.Data.RawContent.ToJson(indent: false)}");
             result += $"\n - {issue.Data.RawContent.ToJson(indent: false)}";
         }
diff --git a/BugMine.Sdk/BugMineClient.cs b/BugMine.Sdk/BugMineClient.cs
index d55ff4d..a012dde 100644
--- a/BugMine.Sdk/BugMineClient.cs
+++ b/BugMine.Sdk/BugMineClient.cs
@@ -1,6 +1,7 @@
 using System.Text.Json.Serialization;
 using System.Text.RegularExpressions;
 using ArcaneLibs.Extensions;
+using BugMine.Sdk.Events.State;
 using BugMine.Web.Classes.Exceptions;
 using LibMatrix;
 using LibMatrix.Homeservers;
diff --git a/BugMine.Sdk/BugMineIssue.cs b/BugMine.Sdk/BugMineIssue.cs
index 6cf3409..7b01b32 100644
--- a/BugMine.Sdk/BugMineIssue.cs
+++ b/BugMine.Sdk/BugMineIssue.cs
@@ -4,8 +4,8 @@ using LibMatrix.RoomTypes;
 namespace BugMine.Web.Classes;
 
 public class BugMineIssue(GenericRoom room, StateEventResponse data) {
-    public GenericRoom Room { get; } = room;
-    public StateEventResponse Data { get; } = data;
+    public GenericRoom Room => room ?? throw new ArgumentNullException(nameof(room));
+    public StateEventResponse Data => data ?? throw new ArgumentNullException(nameof(data));
     // public async IAsyncEnumerable<StateEventResponse> GetRelatedEventsAsync() {
     //     
     // }
diff --git a/BugMine.Sdk/BugMineProject.cs b/BugMine.Sdk/BugMineProject.cs
index 1f56659..453b597 100644
--- a/BugMine.Sdk/BugMineProject.cs
+++ b/BugMine.Sdk/BugMineProject.cs
@@ -1,4 +1,6 @@
 using ArcaneLibs.Extensions;
+using BugMine.Sdk.Events.State;
+using BugMine.Sdk.Events.Timeline;
 using LibMatrix.RoomTypes;
 
 namespace BugMine.Web.Classes;
@@ -37,8 +39,8 @@ public class BugMineProject(GenericRoom room) {
         return new BugMineIssue(Room, evt);
     }
 
-    public async IAsyncEnumerable<BugMineIssue> GetIssues() {
-        await foreach (var evt in room.GetRelatedEventsAsync(Metadata.RoomCreationEventId, "gay.rory.bugmine.issue", BugMineIssueData.EventId)) {
+    public async IAsyncEnumerable<BugMineIssue> GetIssues(int chunkLimit = 250) {
+        await foreach (var evt in room.GetRelatedEventsAsync(Metadata.RoomCreationEventId, "gay.rory.bugmine.issue", BugMineIssueData.EventId, chunkLimit: chunkLimit)) {
             yield return new BugMineIssue(Room, evt);
         }
     }
diff --git a/BugMine.Sdk/Events/State/BugMineRoomMetadata.cs b/BugMine.Sdk/Events/State/BugMineRoomMetadata.cs
index 734fd37..5fafa5a 100644
--- a/BugMine.Sdk/Events/State/BugMineRoomMetadata.cs
+++ b/BugMine.Sdk/Events/State/BugMineRoomMetadata.cs
@@ -1,6 +1,6 @@
 using LibMatrix.EventTypes;
 
-namespace BugMine.Web.Classes;
+namespace BugMine.Sdk.Events.State;
 
 [MatrixEvent(EventName = EventId)]
 public class BugMineRoomMetadata : EventContent {
diff --git a/BugMine.Sdk/Events/State/ProjectInfo.cs b/BugMine.Sdk/Events/State/ProjectInfo.cs
index 2d15bff..b1ec2d9 100644
--- a/BugMine.Sdk/Events/State/ProjectInfo.cs
+++ b/BugMine.Sdk/Events/State/ProjectInfo.cs
@@ -1,6 +1,6 @@
 using LibMatrix.EventTypes;
 
-namespace BugMine.Web.Classes;
+namespace BugMine.Sdk.Events.State;
 
 [MatrixEvent(EventName = EventId)]
 public class ProjectInfo : EventContent {
diff --git a/BugMine.Sdk/Events/Timeline/BugMineIssueComment.cs b/BugMine.Sdk/Events/Timeline/BugMineIssueComment.cs
index 50c73a1..782439a 100644
--- a/BugMine.Sdk/Events/Timeline/BugMineIssueComment.cs
+++ b/BugMine.Sdk/Events/Timeline/BugMineIssueComment.cs
@@ -1,10 +1,10 @@
 using LibMatrix.EventTypes;
 
-namespace BugMine.Web.Classes;
+namespace BugMine.Sdk.Events.Timeline;
 
 [MatrixEvent(EventName = EventId)]
 public class BugMineIssueComment : TimelineEventContent {
-    public const string EventId = "gay.rory.bugmine.comment";
+    public const string EventId = "gay.rory.bugmine.issue.comment";
     public string Comment { get; set; }
     public string Author { get; set; }
     public DateTime Timestamp { get; set; }
diff --git a/BugMine.Sdk/Events/Timeline/BugMineIssueData.cs b/BugMine.Sdk/Events/Timeline/BugMineIssueData.cs
index 480102a..859add3 100644
--- a/BugMine.Sdk/Events/Timeline/BugMineIssueData.cs
+++ b/BugMine.Sdk/Events/Timeline/BugMineIssueData.cs
@@ -1,8 +1,10 @@
+using BugMine.Sdk.Events.State;
+using BugMine.Web.Classes;
 using LibMatrix.EventTypes;
 
-namespace BugMine.Web.Classes;
+namespace BugMine.Sdk.Events.Timeline;
 
-[MatrixEvent(EventName = ProjectInfo.EventId)]
+[MatrixEvent(EventName = EventId)]
 public class BugMineIssueData : TimelineEventContent {
     public const string EventId = "gay.rory.bugmine.issue";
     public string Name { get; set; }
diff --git a/BugMine.Web/Components/ScopedContainers/IssueContainer.razor b/BugMine.Web/Components/ScopedContainers/IssueContainer.razor
new file mode 100644
index 0000000..dac3e97
--- /dev/null
+++ b/BugMine.Web/Components/ScopedContainers/IssueContainer.razor
@@ -0,0 +1,101 @@
+@using System.Text.Json.Serialization
+@using ArcaneLibs.Extensions
+@using BugMine.Web.Classes.Exceptions
+@using LibMatrix
+@inject ILogger<ProjectContainer> Logger
+
+@if (Constants.Debug) {
+    <details>
+        <summary>IssueContainer Debug info - Debug build, here be dragons!</summary>
+        <p>IssueContainer debug info:</p>
+        @* <pre>Slug: @ProjectSlug</pre> *@
+        @* <pre>Progress: @Progress.ToString()</pre> *@
+        @* @if (ProjectContext is null) { *@
+            @* <pre>ProjectContext is null!</pre> *@
+        @* } *@
+        @* else { *@
+            @* <details> *@
+                @* <summary>Context json dump</summary> *@
+                @* <pre>@ProjectContext.ToJson()</pre> *@
+            @* </details> *@
+            @* @if (ProjectContext?.Project?.Room is not null) { *@
+                @* <LinkButton OnClick="@ProjectContext.Project.Room.PermanentlyBrickRoomAsync">Dispose room</LinkButton> *@
+            @* } *@
+        @* } *@
+    </details>
+
+    <hr/>
+}
+@if (ProjectContext?.Client is null) {
+    <p>Authenticating</p>
+}
+else {
+    <CascadingValue Value="ProjectContext">
+        @ChildContent
+    </CascadingValue>
+}
+
+@code {
+    private Status? _progress = Status.Loading;
+
+    [Parameter]
+    public string IssueId { get; set; } = null!;
+
+    [Parameter, CascadingParameter]
+    public ProjectContainer.ProjectContainerContext? ProjectContext { get; set; }
+
+    [Parameter]
+    public IssueContainerContext? IssueContext { get; set; }
+
+    [Parameter]
+    public RenderFragment ChildContent { get; set; }
+
+    [Parameter]
+    public Func<Task>? Loaded { get; set; }
+
+    private Status? Progress {
+        get => _progress;
+        set {
+            _progress = value;
+            StateHasChanged();
+        }
+    }
+
+    protected override async Task OnInitializedAsync() {
+        if (ProjectContext is null) {
+            Logger.LogError("ProjectContext is null");
+            ProjectContext = new();
+        }
+
+        if (ProjectContext.Project != null) {
+            Logger.LogWarning("ProjectContext.Project is not null");
+        }
+
+        ProjectContext.Client ??= await BugMineStorage.GetCurrentSessionOrNavigate();
+        if (ProjectContext.Client == null) {
+            return;
+        }
+
+        Progress = Status.Loading;
+
+        
+        Progress = Status.Done;
+        if (Loaded != null) {
+            await Loaded.Invoke();
+        }
+
+        StateHasChanged();
+    }
+
+    public class IssueContainerContext {
+        public ProjectContainer.ProjectContainerContext ProjectContext { get; set; }
+    }
+
+    private enum Status {
+        Loading,
+        NotInRoom,
+        RoomNotFound,
+        Done
+    }
+
+}
\ No newline at end of file
diff --git a/BugMine.Web/Components/ProjectContainer.razor b/BugMine.Web/Components/ScopedContainers/ProjectContainer.razor
index f7621be..44578a7 100644
--- a/BugMine.Web/Components/ProjectContainer.razor
+++ b/BugMine.Web/Components/ScopedContainers/ProjectContainer.razor
@@ -5,22 +5,24 @@
 @inject ILogger<ProjectContainer> Logger
 
 @if (Constants.Debug) {
-    <p>Debug, beware: here be dragons!</p>
-    <p>ProjectContainer debug info:</p>
-    <pre>Slug: @ProjectSlug</pre>
-    <pre>Progress: @Progress.ToString()</pre>
-    @if (ProjectContext is null) {
-        <pre>ProjectContext is null!</pre>
-    }
-    else {
-        <details>
-            <summary>Context json dump</summary>
-            <pre>@ProjectContext.ToJson()</pre>
-        </details>
-        @if (ProjectContext?.Project?.Room is not null) {
-            <LinkButton OnClick="@ProjectContext.Project.Room.PermanentlyBrickRoomAsync">Dispose room</LinkButton>
+    <details>
+        <summary>ProjectContainer Debug info - Debug build, here be dragons!</summary>
+        <p>ProjectContainer debug info:</p>
+        <pre>Slug: @ProjectSlug</pre>
+        <pre>Progress: @Progress.ToString()</pre>
+        @if (ProjectContext is null) {
+            <pre>ProjectContext is null!</pre>
         }
-    }
+        else {
+            <details>
+                <summary>Context json dump</summary>
+                <pre>@ProjectContext.ToJson()</pre>
+            </details>
+            @if (ProjectContext?.Project?.Room is not null) {
+                <LinkButton OnClick="@ProjectContext.Project.Room.PermanentlyBrickRoomAsync">Dispose room</LinkButton>
+            }
+        }
+    </details>
 
     <hr/>
 }
@@ -42,7 +44,9 @@ else if (ProjectContext?.Project is null) {
     }
 }
 else {
-    @ChildContent
+    <CascadingValue Value="ProjectContext">
+        @ChildContent
+    </CascadingValue>
 }
 
 @code {
@@ -56,7 +60,7 @@ else {
 
     [Parameter]
     public RenderFragment ChildContent { get; set; }
-    
+
     [Parameter]
     public Func<Task>? Loaded { get; set; }
 
@@ -105,7 +109,6 @@ else {
         if (Loaded != null) {
             await Loaded.Invoke();
         }
-        
 
         StateHasChanged();
     }
diff --git a/BugMine.Web/Pages/Projects/Boards/Index.razor b/BugMine.Web/Pages/Projects/Boards/Index.razor
new file mode 100644
index 0000000..c1953c8
--- /dev/null
+++ b/BugMine.Web/Pages/Projects/Boards/Index.razor
@@ -0,0 +1,19 @@
+@page "/Projects/{ProjectSlug}/Boards"
+@using BugMine.Web.Components.ScopedContainers
+<h3>Boards</h3>
+<ProjectContainer ProjectSlug="@ProjectSlug" ProjectContext="@ProjectContext" Loaded="@OnProjectLoaded">
+    <p>@ProjectContext.Project.Info.Name</p>
+</ProjectContainer>
+
+@code {
+
+    [Parameter]
+    public string ProjectSlug { get; set; }
+
+    public ProjectContainer.ProjectContainerContext ProjectContext { get; set; } = new();
+
+    private async Task OnProjectLoaded() {
+        
+    }
+
+}
\ No newline at end of file
diff --git a/BugMine.Web/Pages/Projects/Issues/NewIssue.razor b/BugMine.Web/Pages/Projects/Issues/NewIssue.razor
index 159e20d..118a00c 100644
--- a/BugMine.Web/Pages/Projects/Issues/NewIssue.razor
+++ b/BugMine.Web/Pages/Projects/Issues/NewIssue.razor
@@ -1,8 +1,17 @@
 @page "/Projects/{ProjectSlug}/Issues/New"
+@using ArcaneLibs.Extensions
+@using BugMine.Sdk.Events.Timeline
+@using BugMine.Web.Components.ScopedContainers
 <h3>New issue</h3>
 
 <ProjectContainer ProjectSlug="@ProjectSlug" ProjectContext="@ProjectContext">
-    <h1>Hi from NewIssue!</h1>
+    <p>Title: <InputText @bind-Value="@IssueData.Name"/></p>
+    
+    @if (Constants.Debug) {
+        <p>Debug info:</p>
+        <p>Issue data:</p>
+        <pre>@IssueData.ToJson()</pre>
+    }
 </ProjectContainer>
 
 @code {
@@ -11,5 +20,7 @@
     public string ProjectSlug { get; set; } = null!;
 
     public ProjectContainer.ProjectContainerContext? ProjectContext { get; set; } = new();
+    
+    public BugMineIssueData IssueData { get; set; } = new();
 
 }
\ No newline at end of file
diff --git a/BugMine.Web/Pages/Projects/Issues/ViewIssue.razor b/BugMine.Web/Pages/Projects/Issues/ViewIssue.razor
new file mode 100644
index 0000000..e33f74d
--- /dev/null
+++ b/BugMine.Web/Pages/Projects/Issues/ViewIssue.razor
@@ -0,0 +1,24 @@
+@page "/Projects/{ProjectSlug}/Issues/{IssueId}"
+@using ArcaneLibs.Extensions
+@using BugMine.Sdk.Events.Timeline
+@using BugMine.Web.Components.ScopedContainers
+<h3>New issue</h3>
+
+<ProjectContainer ProjectSlug="@ProjectSlug" ProjectContext="@ProjectContext">
+    <IssueContainer IssueId="@IssueId" IssueContext="@IssueContext">
+        <p>meow</p>
+    </IssueContainer>
+</ProjectContainer>
+
+@code {
+
+    [Parameter]
+    public string ProjectSlug { get; set; } = null!;
+
+    [Parameter]
+    public string IssueId { get; set; } = null!;
+
+    public ProjectContainer.ProjectContainerContext? ProjectContext { get; set; } = new();
+    public IssueContainer.IssueContainerContext? IssueContext { get; set; } = new();
+
+}
\ No newline at end of file
diff --git a/BugMine.Web/Pages/Projects/NewProject.razor b/BugMine.Web/Pages/Projects/NewProject.razor
index f8c7dfd..2a7c7da 100644
--- a/BugMine.Web/Pages/Projects/NewProject.razor
+++ b/BugMine.Web/Pages/Projects/NewProject.razor
@@ -1,6 +1,8 @@
 @page "/Projects/New"
 @using ArcaneLibs.Extensions
+@using BugMine.Sdk.Events.State
 @using LibMatrix
+@inject ILogger<NewProject> Logger
 <h3>New project</h3>
 
 <span>Project name: </span>
@@ -62,7 +64,7 @@ else {
 
         }
         catch (MatrixException e) {
-            
+            Logger.LogError(e, "Failed to create project");
         }
     }
 
diff --git a/BugMine.Web/Pages/Projects/ViewProject.razor b/BugMine.Web/Pages/Projects/ViewProject.razor
index 91cc4d0..c1a2aa1 100644
--- a/BugMine.Web/Pages/Projects/ViewProject.razor
+++ b/BugMine.Web/Pages/Projects/ViewProject.razor
@@ -1,28 +1,36 @@
 @page "/Projects/{ProjectSlug}/"
-@using LibMatrix
-@using BugMine.Web.Classes.Exceptions
+@using System.Collections.Frozen
+@using System.Reflection
 @using ArcaneLibs.Extensions
+@using BugMine.Sdk.Events.Timeline
+@using BugMine.Web.Pages.Projects.Issues
+@using BugMine.Web.Components.ScopedContainers
+@using LibMatrix
 
 <ProgressLog ></ProgressLog>
 
 <ProjectContainer ProjectSlug="@ProjectSlug" ProjectContext="@ProjectContext" Loaded="@OnProjectLoaded">
-    <h1>Hi from ViewProject!</h1>
-    <h1>@ProjectContext!.Project!.Info.Name</h1>
-
-    @if (Progress == "loading-issues") {
+    <h3>@ProjectContext!.Project!.Info.Name</h3>
+    <p>@Followers.Count followers - @Issues?.Count issues</p>
+    <LinkButton href="@(typeof(NewIssue).GetCustomAttributes<RouteAttribute>().First().Template.Replace("{ProjectSlug}", ProjectSlug))">New issue</LinkButton>
+    @if (Progress == Status.Loading) {
         <p>Loading issues, got @(Issues?.Count ?? 0) so far... <SimpleSpinner/></p>
     }
 
     @* <p>@Project.Description</p> *@
     @if (Issues != null) {
         @foreach (var issue in Issues) {
-            <pre>@issue.Data.RawContent.ToJson()</pre>
+            var issueData = issue.Data.TypedContent as BugMineIssueData;
+            <div class="issue-card" @onclick="@(() => { NavigationManager.NavigateTo(GetIssueUrl(issue)); })">
+                <div style="width: 1em; height: 1em; background-color: #00FF00; display: inline-block;"></div> @* Color based on tags... *@
+                <p>@issueData.Name</p>
+            </div>
         }
     }
 </ProjectContainer>
 
 @code {
-    private string? _progress = "loading";
+    private Status? _progress = Status.Loading;
 
     public ProjectContainer.ProjectContainerContext? ProjectContext { get; set; } = new();
 
@@ -30,8 +38,9 @@
     public string ProjectSlug { get; set; } = null!;
 
     private List<BugMineIssue>? Issues { get; set; }
+    private FrozenSet<StateEventResponse> Followers { get; set; } = FrozenSet<StateEventResponse>.Empty;
 
-    private string? Progress {
+    private Status? Progress {
         get => _progress;
         set {
             _progress = value;
@@ -40,14 +49,34 @@
     }
 
     protected async Task OnProjectLoaded() {
-        Progress = "loading-issues";
-        await foreach (var issue in ProjectContext.Project.GetIssues()) {
+        Progress = Status.Loading;
+        ProjectContext!.Project!.Room.GetMembersListAsync().ContinueWith(x => {
+            Followers = x.Result;
+            StateHasChanged();
+        });
+        await foreach (var issue in ProjectContext.Project.GetIssues(chunkLimit: 1000)) {
             Issues ??= new List<BugMineIssue>();
             Issues.Add(issue);
-            StateHasChanged();
+            // StateHasChanged();
+            if (Issues.Count % 1000 == 0) {
+                StateHasChanged();
+                Console.WriteLine($"Got issue {Issues.Count} {issue.Data.RawContent.ToJson()}");
+            }
         }
 
+        Progress = Status.Done;
+
         StateHasChanged();
     }
 
+    private enum Status {
+        Loading,
+        Done
+    }
+
+    private string GetIssueUrl(BugMineIssue issue) =>
+        typeof(ViewIssue).GetCustomAttributes<RouteAttribute>().First().Template
+            .Replace("{ProjectSlug}", ProjectSlug)
+            .Replace("{IssueId}", issue.Data.EventId);
+
 }
\ No newline at end of file
diff --git a/BugMine.Web/Pages/Projects/ViewProject.razor.css b/BugMine.Web/Pages/Projects/ViewProject.razor.css
new file mode 100644
index 0000000..6f67e52
--- /dev/null
+++ b/BugMine.Web/Pages/Projects/ViewProject.razor.css
@@ -0,0 +1,8 @@
+.issue-card {
+    display: flex;
+    padding: 1rem;
+    border-radius: 0.5rem;
+    background-color: var(--bs-dark);
+    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.5);
+    transition: background-color 1s, box-shadow 3s;
+}
\ No newline at end of file
diff --git a/LibMatrix b/LibMatrix
-Subproject e1f99073f3d9788a4b48d2bb7091e3894dcefa1
+Subproject 6e1402baa6ad8517a8a8446832fed1d4b48cee5