167 lines
4.8 KiB
JavaScript
167 lines
4.8 KiB
JavaScript
import { get } from "./api.js";
|
|
export const state = {
|
|
me: undefined,
|
|
projects: [],
|
|
tasks: new Map(),
|
|
sessions: new Map(),
|
|
inbox: [],
|
|
selectedTaskId: undefined,
|
|
transcript: [],
|
|
lastTranscriptSeq: 0,
|
|
connected: false,
|
|
};
|
|
const listeners = new Map();
|
|
export function on(topic, fn) {
|
|
let set = listeners.get(topic);
|
|
if (!set) {
|
|
set = new Set();
|
|
listeners.set(topic, set);
|
|
}
|
|
set.add(fn);
|
|
return () => set.delete(fn);
|
|
}
|
|
export function onEach(topics, fn) {
|
|
const offs = topics.map((t) => on(t, fn));
|
|
return () => {
|
|
for (const off of offs) {
|
|
off();
|
|
}
|
|
};
|
|
}
|
|
export function activeTasks() {
|
|
return state.projects.flatMap((project) => (state.tasks.get(project.id) ?? []).filter((t) => t.status === "active").map((task) => ({ project, task })));
|
|
}
|
|
export function notify(topic, detail) {
|
|
for (const fn of listeners.get(topic) ?? []) {
|
|
fn(detail);
|
|
}
|
|
}
|
|
export function findTask(id) {
|
|
for (const tasks of state.tasks.values()) {
|
|
const task = tasks.find((t) => t.id === id);
|
|
if (task) {
|
|
return task;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
export function currentSession(taskId) {
|
|
return state.sessions.get(taskId)?.findLast((s) => s.state !== "completed") ?? state.sessions.get(taskId)?.at(-1);
|
|
}
|
|
export async function loadMe() {
|
|
state.me = await get("/api/me");
|
|
notify("me");
|
|
}
|
|
export async function loadProjects() {
|
|
state.projects = await get("/api/projects");
|
|
await Promise.all(state.projects.map((p) => loadTasks(p.id)));
|
|
notify("projects");
|
|
}
|
|
export async function loadTasks(projectId) {
|
|
const tasks = await get("/api/projects/{projectId}/tasks", { path: { projectId } });
|
|
state.tasks.set(projectId, tasks);
|
|
await Promise.all(tasks.filter((t) => t.status === "active").map((t) => loadSessions(t.id)));
|
|
notify("tasks");
|
|
}
|
|
export async function loadSessions(taskId) {
|
|
state.sessions.set(taskId, await get("/api/tasks/{taskId}/sessions", { path: { taskId } }));
|
|
notify("sessions", taskId);
|
|
}
|
|
export async function loadInbox() {
|
|
state.inbox = await get("/api/inbox");
|
|
notify("inbox");
|
|
}
|
|
let liveDuringLoad;
|
|
const visitsKey = "byre.visits";
|
|
export function lastVisits() {
|
|
return JSON.parse(localStorage.getItem(visitsKey) ?? "{}");
|
|
}
|
|
function markVisited(taskId) {
|
|
if (!taskId) {
|
|
return;
|
|
}
|
|
localStorage.setItem(visitsKey, JSON.stringify({ ...lastVisits(), [taskId]: Date.now() }));
|
|
}
|
|
export async function selectTask(taskId) {
|
|
markVisited(state.selectedTaskId);
|
|
markVisited(taskId);
|
|
state.selectedTaskId = taskId;
|
|
state.transcript = [];
|
|
state.lastTranscriptSeq = 0;
|
|
liveDuringLoad = undefined;
|
|
notify("selection");
|
|
if (!taskId) {
|
|
return;
|
|
}
|
|
const held = [];
|
|
liveDuringLoad = held;
|
|
for (;;) {
|
|
const page = await get("/api/tasks/{taskId}/events", { path: { taskId }, query: { after: state.lastTranscriptSeq, limit: 1000 } });
|
|
if (state.selectedTaskId !== taskId || liveDuringLoad !== held) {
|
|
return;
|
|
}
|
|
appendTranscript(page);
|
|
if (page.length < 1000) {
|
|
break;
|
|
}
|
|
}
|
|
liveDuringLoad = undefined;
|
|
appendTranscript(held);
|
|
}
|
|
function appendTranscript(events) {
|
|
const fresh = events.filter((ev) => ev.seq > state.lastTranscriptSeq);
|
|
if (fresh.length === 0) {
|
|
return;
|
|
}
|
|
state.transcript.push(...fresh);
|
|
state.lastTranscriptSeq = fresh.at(-1).seq;
|
|
notify("transcript", fresh);
|
|
}
|
|
export function handleEvent(ev) {
|
|
if (ev.seq === 0) {
|
|
if (ev.task_id && ev.task_id === state.selectedTaskId && !liveDuringLoad) {
|
|
notify("stream", ev);
|
|
}
|
|
notify("event", ev);
|
|
return;
|
|
}
|
|
if (ev.task_id && ev.task_id === state.selectedTaskId) {
|
|
if (liveDuringLoad) {
|
|
liveDuringLoad.push(ev);
|
|
}
|
|
else {
|
|
appendTranscript([ev]);
|
|
}
|
|
}
|
|
switch (ev.kind) {
|
|
case "user.updated":
|
|
if (ev.payload["login"] === state.me?.login) {
|
|
void loadMe();
|
|
}
|
|
break;
|
|
case "project.created":
|
|
void loadProjects();
|
|
break;
|
|
case "task.created":
|
|
case "task.merge_title":
|
|
case "task.merged":
|
|
case "task.discarded":
|
|
if (ev.project_id) {
|
|
void loadTasks(ev.project_id);
|
|
}
|
|
break;
|
|
case "session.created":
|
|
case "session.state":
|
|
case "session.model":
|
|
if (ev.task_id) {
|
|
void loadSessions(ev.task_id);
|
|
}
|
|
break;
|
|
case "inbox.created":
|
|
case "inbox.updated":
|
|
void loadInbox();
|
|
break;
|
|
}
|
|
notify("event", ev);
|
|
}
|
|
//# sourceMappingURL=state.js.map
|