149 lines
4.2 KiB
TypeScript
149 lines
4.2 KiB
TypeScript
import type { ConversationSummary } from "$lib/api/types";
|
|
import { parseDate } from "$lib/format";
|
|
import { byActivity } from "$lib/shell/state";
|
|
|
|
export interface DayGroup {
|
|
branches: ConversationSummary[];
|
|
day: string;
|
|
deep: ConversationSummary[];
|
|
forks: ConversationSummary[];
|
|
jobs: ConversationSummary[];
|
|
label: string;
|
|
live: boolean;
|
|
master: ConversationSummary | null;
|
|
others: ConversationSummary[];
|
|
}
|
|
|
|
const DAY_MS = 86_400_000;
|
|
|
|
function localDay(iso: string | null | undefined): string {
|
|
const date = parseDate(iso) ?? new Date();
|
|
const y = date.getFullYear();
|
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
const d = String(date.getDate()).padStart(2, "0");
|
|
return `${y}-${m}-${d}`;
|
|
}
|
|
|
|
export function dayLabel(day: string, now = new Date()): string {
|
|
const today = localDay(now.toISOString());
|
|
const yesterday = localDay(new Date(now.getTime() - DAY_MS).toISOString());
|
|
if (day === today) {
|
|
return "Today";
|
|
}
|
|
if (day === yesterday) {
|
|
return "Yesterday";
|
|
}
|
|
const date = new Date(`${day}T12:00:00`);
|
|
return date.toLocaleDateString(undefined, {
|
|
day: "numeric",
|
|
month: "short",
|
|
weekday: "short",
|
|
});
|
|
}
|
|
|
|
function activityOf(row: ConversationSummary): string {
|
|
return row.last_activity_at ?? row.created_at ?? "";
|
|
}
|
|
|
|
// Days as the operator remembers them: the master that began that day,
|
|
// and under it everything that last moved that day - branches, deep
|
|
// chats, forks, job runs. Open branches follow the master through the
|
|
// rotation, so their parent says nothing about when they were alive;
|
|
// the day of their last word does.
|
|
export function groupByDay(
|
|
rows: ConversationSummary[],
|
|
now = new Date()
|
|
): DayGroup[] {
|
|
const groups = new Map<string, DayGroup>();
|
|
const group = (day: string): DayGroup => {
|
|
let found = groups.get(day);
|
|
if (!found) {
|
|
found = {
|
|
branches: [],
|
|
day,
|
|
deep: [],
|
|
forks: [],
|
|
jobs: [],
|
|
label: dayLabel(day, now),
|
|
live: false,
|
|
master: null,
|
|
others: [],
|
|
};
|
|
groups.set(day, found);
|
|
}
|
|
return found;
|
|
};
|
|
const masters = rows
|
|
.filter((row) => row.kind === "master")
|
|
.sort((a, b) => (b.created_at ?? "").localeCompare(a.created_at ?? ""));
|
|
for (const master of masters) {
|
|
const g = group(localDay(master.created_at));
|
|
if (!g.master || master.status === "open") {
|
|
g.master = master;
|
|
} else {
|
|
g.others.push(master);
|
|
}
|
|
}
|
|
for (const row of rows) {
|
|
if (row.kind === "master") {
|
|
continue;
|
|
}
|
|
const g = group(localDay(activityOf(row)));
|
|
if (row.kind === "branch") {
|
|
g.branches.push(row);
|
|
} else if (row.kind === "deep") {
|
|
g.deep.push(row);
|
|
} else if (row.kind === "fork") {
|
|
g.forks.push(row);
|
|
} else if (row.kind === "job") {
|
|
g.jobs.push(row);
|
|
} else {
|
|
g.others.push(row);
|
|
}
|
|
}
|
|
const out = [...groups.values()];
|
|
for (const g of out) {
|
|
g.branches.sort(byActivity);
|
|
g.deep.sort(byActivity);
|
|
g.forks.sort(byActivity);
|
|
g.jobs.sort(byActivity);
|
|
g.others.sort(byActivity);
|
|
g.live = [
|
|
g.master,
|
|
...g.branches,
|
|
...g.deep,
|
|
...g.forks,
|
|
...g.jobs,
|
|
...g.others,
|
|
].some((row) => row && (row.running_turn || row.pending_question));
|
|
}
|
|
return out.sort((a, b) => b.day.localeCompare(a.day));
|
|
}
|
|
|
|
// One phrase for the day header: what the day held, and whether its
|
|
// master is still the live one.
|
|
export function daySummary(group: DayGroup, today: boolean): string {
|
|
const parts: string[] = [];
|
|
if (group.master?.status === "open" && !today) {
|
|
parts.push("master still open");
|
|
}
|
|
const count = (n: number, one: string, many = `${one}s`) =>
|
|
`${n} ${n === 1 ? one : many}`;
|
|
if (group.branches.length) {
|
|
parts.push(count(group.branches.length, "branch", "branches"));
|
|
}
|
|
if (group.deep.length) {
|
|
parts.push(count(group.deep.length, "deep", "deep"));
|
|
}
|
|
if (group.forks.length) {
|
|
parts.push(count(group.forks.length, "fork"));
|
|
}
|
|
if (group.jobs.length) {
|
|
parts.push(count(group.jobs.length, "job run"));
|
|
}
|
|
if (group.others.length) {
|
|
parts.push(count(group.others.length, "more", "more"));
|
|
}
|
|
return parts.join(" · ");
|
|
}
|