import { useRef, useState } from "react"; import { Button, ChatComposer, type ChatComposerHandle, ProjectPicker, serialize, type SessionSidebarRepo, } from "@ace/ui"; import { IconChevronDown, IconHash, IconPlus } from "@ace/ui/icons"; import type { Listing } from "@ace/host/protocol"; import { here } from "./desktop"; import type { AppProject } from "./projects"; /** * A project's dashboard draft and history follow its identity, so setting up a GitHub project here * keeps them. Earlier versions scoped each to one checkout; the first one found moves over once. */ function adopt(project: AppProject): string { const scope = `/projects/${project.id}/dashboard`; for (const kind of ["draft", "history"]) { const key = `ace:${kind}:${scope}`; try { // useDraft still moves drafts that earlier versions kept in sessionStorage. const stores = kind === "draft" ? [localStorage, sessionStorage] : [localStorage]; if (stores.some((store) => store.getItem(key) !== null)) continue; for (const checkout of project.checkouts) { const legacy = `ace:${kind}:/projects/${checkout}/dashboard`; const store = legacy !== key && stores.find((store) => store.getItem(legacy) !== null); if (!store) continue; // A failed write throws before the legacy copy is removed. store.setItem(key, store.getItem(legacy)!); store.removeItem(legacy); break; } } catch { // Unavailable storage leaves both scopes as they were. } } return scope; } const MODES = [{ id: "ace", name: "Ace", placeholder: "Start a new channel" }]; export function Dashboard( { project, repos, channels, local, connected, onSetup, onProject, onOpen, onChannel, onCreate }: { project: AppProject; repos: SessionSidebarRepo[]; channels: Listing[]; local: boolean; connected: boolean; /** Offered for a GitHub project with no checkout on this host. */ onSetup?: () => void; onProject: (id: string) => void; onOpen: () => void; onChannel: (channel: Listing) => void; onCreate: (text?: string, onCreated?: () => void) => Promise; }, ) { const composer = useRef(null); const [busy, setBusy] = useState(false); const [scope] = useState(() => adopt(project)); const creatable = local || !!onSetup; const active = channels.filter((channel) => channel.state !== "archived") .sort((a, b) => b.created - a.created); const hour = new Date().getHours(); const greeting = hour < 12 ? "Good morning" : hour < 18 ? "Good afternoon" : "Good evening"; async function start(text?: string) { if (busy) return; setBusy(true); try { await onCreate(text, () => { const input = composer.current; const doc = input?.get(); if (input && doc && serialize(doc) === text) input.clear(); }); } finally { setBusy(false); } } return (
onProject(repo.id)} onAddRepo={connected ? onOpen : undefined} trigger={ )}
{creatable && (
void start(serialize(doc))} />
)}

{active.length ? "Pick back up" : "Channels"}

{creatable && ( )}
{active.length ? (
{active.map((channel) => ( ))}
) : (

Your channels in this project will appear here.

)}
); }