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
|