Skip to content
Source
Sign inSign up

homeThreadList.ts

149 lines4.6 KB

1import { buildProjectGroups } from "@t3tools/client-runtime/state/project-grouping";
2import type {
3 EnvironmentProject,
4 EnvironmentThreadShell,
5} from "@t3tools/client-runtime/state/shell";
6import {
7 getThreadSortTimestamp,
8 toSortableTimestamp,
9} from "@t3tools/client-runtime/state/thread-sort";
10import type {
11 EnvironmentId,
12 ScopedProjectRef,
13 SidebarProjectGroupingMode,
14 SidebarProjectSortOrder,
15} from "@t3tools/contracts";
16import * as Arr from "effect/Array";
17import * as Order from "effect/Order";
18​
19import { scopedProjectKey } from "../../lib/scopedEntities";
20import type { PendingNewTask } from "../../state/use-pending-new-tasks";
21​
22export type HomeProjectSortOrder = Exclude<SidebarProjectSortOrder, "manual">;
23​
24export interface HomeProjectScope {
25 readonly key: string;
26 readonly title: string;
27 readonly representative: EnvironmentProject;
28 readonly projects: ReadonlyArray<EnvironmentProject>;
29 readonly projectRefs: ReadonlyArray<ScopedProjectRef>;
30 readonly pinned: boolean;
31 readonly archived: boolean;
33​
34function getProjectSortTimestamp(
35 project: EnvironmentProject,
36 sortOrder: HomeProjectSortOrder,
37): number {
38 return sortOrder === "created_at"
39 ? (toSortableTimestamp(project.createdAt) ?? Number.NEGATIVE_INFINITY)
40 : (toSortableTimestamp(project.updatedAt) ??
41 toSortableTimestamp(project.createdAt) ??
42 Number.NEGATIVE_INFINITY);
44​
45export function buildHomeProjectScopes(input: {
46 readonly projects: ReadonlyArray<EnvironmentProject>;
47 readonly environmentId: EnvironmentId | null;
48 readonly projectGroupingMode: SidebarProjectGroupingMode;
49}): ReadonlyArray<HomeProjectScope> {
50 const projects = input.projects.filter(
51 (project) => input.environmentId === null || project.environmentId === input.environmentId,
52 );
53 return buildProjectGroups({
54 projects,
55 groupScratchProjects: true,
56 settings: {
57 sidebarProjectGroupingMode: input.projectGroupingMode,
58 sidebarProjectGroupingOverrides: {},
59 },
60 }).map((group) => {
61 return {
62 key: group.key,
63 title: group.label,
64 representative: group.representative,
65 projects: group.members.map((member) => member.project),
66 projectRefs: group.memberProjectRefs,
67 pinned: group.pinned,
68 archived: group.archived,
69 };
70 });
72​
73export function findHomeProjectScope(
74 scopes: ReadonlyArray<HomeProjectScope>,
75 key: string | null,
76): HomeProjectScope | null {
77 if (key === null) {
78 return null;
79 }
80 return (
81 scopes.find((scope) => scope.key === key) ??
82 scopes.find((scope) =>
83 scope.projectRefs.some(
84 (projectRef) => scopedProjectKey(projectRef.environmentId, projectRef.projectId) === key,
85 ),
86 ) ??
87 null
88 );
90​
91export function sortHomeProjectScopes(input: {
92 readonly scopes: ReadonlyArray<HomeProjectScope>;
93 readonly threads: ReadonlyArray<EnvironmentThreadShell>;
94 readonly pendingTasks: ReadonlyArray<PendingNewTask>;
95 readonly projectSortOrder: HomeProjectSortOrder;
96}): ReadonlyArray<HomeProjectScope> {
97 const scopeKeyByProjectRef = new Map(
98 input.scopes.flatMap((scope) =>
99 scope.projectRefs.map(
100 (projectRef) =>
101 [scopedProjectKey(projectRef.environmentId, projectRef.projectId), scope.key] as const,
102 ),
103 ),
104 );
105 const latestActivityByScope = new Map<string, number>();
106 const recordActivity = (scopeKey: string | undefined, timestamp: number) => {
107 if (!scopeKey || !Number.isFinite(timestamp)) return;
108 latestActivityByScope.set(
109 scopeKey,
110 Math.max(latestActivityByScope.get(scopeKey) ?? Number.NEGATIVE_INFINITY, timestamp),
111 );
112 };
113​
114 for (const thread of input.threads) {
115 if (thread.archivedAt !== null) continue;
116 recordActivity(
117 scopeKeyByProjectRef.get(scopedProjectKey(thread.environmentId, thread.projectId)),
118 getThreadSortTimestamp(thread, input.projectSortOrder),
119 );
120 }
121 for (const pendingTask of input.pendingTasks) {
122 recordActivity(
123 scopeKeyByProjectRef.get(scopedProjectKey(pendingTask.environmentId, pendingTask.projectId)),
124 Date.parse(pendingTask.createdAt),
125 );
126 }
127​
128 return Arr.sort(
129 input.scopes,
130 Order.mapInput(
131 Order.Struct({
132 timestamp: Order.flip(Order.Number),
133 title: Order.String,
134 key: Order.String,
135 }),
136 (scope: HomeProjectScope) => ({
137 timestamp:
138 latestActivityByScope.get(scope.key) ??
139 Math.max(
140 ...scope.projects.map((project) =>
141 getProjectSortTimestamp(project, input.projectSortOrder),
142 ),
143 ),
144 title: scope.title,
145 key: scope.key,
146 }),
147 ),
148 );