first commit

This commit is contained in:
2026-10-03 13:40:26 +02:00
parent 732a5b5a16
commit 566f415fa7
555 changed files with 62704 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
import { inject, Injectable, Provider } from '@angular/core';
import { injectDispatch } from '@ngrx/signals/events';
import {
IssueLink,
IssueLinkType,
IssueStatus,
UpdateIssueInput,
} from '@task-stream/web/tracker/domain';
import { issuePageEvents } from '../events/issue.events';
import { IssueStore } from '../stores/issue.store';
/**
* One ticket for presenters: details, comments and history, live. Commands act
* on the open ticket, so pages don't pass its id.
*/
@Injectable()
export class IssueFacade {
private readonly store = inject(IssueStore);
private readonly dispatch = injectDispatch(issuePageEvents);
readonly issue = this.store.issue;
readonly comments = this.store.comments;
readonly history = this.store.history;
readonly outwardLinks = this.store.outwardLinks;
readonly inwardLinks = this.store.inwardLinks;
readonly loading = this.store.loading;
readonly error = this.store.error;
/** Signed-in user's id (authors can edit their comments). */
readonly me = this.store.me;
/** Loads the ticket (by id or key, e.g. BE-3) and follows its changes. */
open(idOrKey: string): void {
this.dispatch.opened({ idOrKey });
}
/** QA_FAILED and CANCELLED need a reason. */
changeStatus(status: IssueStatus, reason?: string | null): void {
this.withIssue((issueId) =>
this.dispatch.statusChangeRequested({ issueId, status, reason }),
);
}
assign(assigneeId: string | null): void {
this.withIssue((issueId) =>
this.dispatch.assignRequested({ issueId, assigneeId }),
);
}
update(changes: Omit<UpdateIssueInput, 'issueId'>): void {
this.withIssue((issueId) =>
this.dispatch.updateRequested({ issueId, ...changes }),
);
}
/** Relates this ticket with another one by its key (e.g. BE-3). */
link(type: IssueLinkType, targetKey: string): void {
this.withIssue((issueId) =>
this.dispatch.linkByKeyRequested({ issueId, type, targetKey }),
);
}
unlink(link: IssueLink): void {
this.withIssue((issueId) =>
this.dispatch.unlinkRequested({
issueId,
type: link.type,
targetIssueId: link.issueId,
}),
);
}
setRelease(releaseId: string | null): void {
this.withIssue((issueId) =>
this.dispatch.releaseChangeRequested({ issueId, releaseId }),
);
}
addComment(body: string): void {
this.withIssue((issueId) =>
this.dispatch.commentAddRequested({ issueId, body }),
);
}
editComment(commentId: string, body: string): void {
this.dispatch.commentEditRequested({ commentId, body });
}
deleteComment(commentId: string): void {
this.dispatch.commentDeleteRequested({ commentId });
}
private withIssue(command: (issueId: string) => void): void {
const issueId = this.store.issue()?.id;
if (issueId) command(issueId);
}
}
/** Store + facade of a ticket, for the issue route's component. */
export function provideIssueFacade(): Provider[] {
return [IssueStore, IssueFacade];
}