Files
porthole/apps/web/app/lib/moments.ts
2026-02-05 09:14:45 -08:00

85 lines
2.0 KiB
TypeScript

export type MomentAsset = {
id: string;
capture_ts_utc: string;
};
export type MomentCluster = {
day: string;
start: string;
end: string;
count: number;
assets: MomentAsset[];
};
const MOMENT_WINDOW_MINUTES = 30;
const MOMENT_WINDOW_MS = MOMENT_WINDOW_MINUTES * 60 * 1000;
function dayKeyFromIso(iso: string) {
const d = new Date(iso);
const yyyy = d.getUTCFullYear();
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
const dd = String(d.getUTCDate()).padStart(2, "0");
return `${yyyy}-${mm}-${dd}`;
}
export function clusterMoments(input: MomentAsset[]): MomentCluster[] {
const byDay = new Map<string, MomentAsset[]>();
for (const asset of input) {
if (!asset.capture_ts_utc) continue;
const key = dayKeyFromIso(asset.capture_ts_utc);
const list = byDay.get(key);
if (list) list.push(asset);
else byDay.set(key, [asset]);
}
const clusters: MomentCluster[] = [];
for (const [day, assets] of byDay) {
const sorted = [...assets].sort((a, b) =>
a.capture_ts_utc.localeCompare(b.capture_ts_utc),
);
let current: MomentAsset[] = [];
let lastTs: number | null = null;
for (const asset of sorted) {
const ts = new Date(asset.capture_ts_utc).getTime();
if (!Number.isFinite(ts)) continue;
if (lastTs === null || ts - lastTs <= MOMENT_WINDOW_MS) {
current.push(asset);
} else {
const start = current[0]?.capture_ts_utc;
const end = current[current.length - 1]?.capture_ts_utc;
if (start && end) {
clusters.push({
day,
start,
end,
count: current.length,
assets: current,
});
}
current = [asset];
}
lastTs = ts;
}
if (current.length) {
const start = current[0].capture_ts_utc;
const end = current[current.length - 1].capture_ts_utc;
clusters.push({
day,
start,
end,
count: current.length,
assets: current,
});
}
}
return clusters;
}