wip(desktop): progress

This commit is contained in:
Adam 2025-12-15 06:34:08 -06:00
parent 34db739442
commit 5fbcb203f5
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
2 changed files with 134 additions and 17 deletions

View file

@ -139,6 +139,7 @@ function DialogCommand(props: { options: CommandOption[] }) {
emptyMessage="No commands found"
items={() => props.options.filter((x) => !x.id.startsWith("suggested.") || !x.disabled)}
key={(x) => x?.id}
filterKeys={["title", "description", "category"]}
groupBy={(x) => x.category ?? ""}
onSelect={(option) => {
if (option) {

View file

@ -36,6 +36,7 @@ import { Binary } from "@opencode-ai/util/binary"
import { Header } from "@/components/header"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { DialogSelectProvider } from "@/components/dialog-select-provider"
import { useCommand } from "@/context/command"
export default function Layout(props: ParentProps) {
const [store, setStore] = createStore({
@ -52,6 +53,137 @@ export default function Layout(props: ParentProps) {
const navigate = useNavigate()
const providers = useProviders()
const dialog = useDialog()
const command = useCommand()
const currentSessions = createMemo(() => {
if (!params.dir) return []
const directory = base64Decode(params.dir)
return globalSync.child(directory)[0].session ?? []
})
function navigateSessionByOffset(offset: number) {
const projects = layout.projects.list()
if (projects.length === 0) return
const currentDirectory = params.dir ? base64Decode(params.dir) : undefined
const projectIndex = currentDirectory ? projects.findIndex((p) => p.worktree === currentDirectory) : -1
// If we're not in any project, navigate to the first/last project based on direction
if (projectIndex === -1) {
const targetProject = offset > 0 ? projects[0] : projects[projects.length - 1]
if (targetProject) navigateToProject(targetProject.worktree)
return
}
const sessions = currentSessions()
const sessionIndex = params.id ? sessions.findIndex((s) => s.id === params.id) : -1
// Calculate target index within current project
let targetIndex: number
if (sessionIndex === -1) {
// Not on a session - go to first session for "next", last session for "prev"
targetIndex = offset > 0 ? 0 : sessions.length - 1
} else {
targetIndex = sessionIndex + offset
}
// If target is within bounds, navigate to that session
if (targetIndex >= 0 && targetIndex < sessions.length) {
navigateToSession(sessions[targetIndex])
return
}
// Navigate to adjacent project
const nextProjectIndex = projectIndex + (offset > 0 ? 1 : -1)
const nextProject = projects[nextProjectIndex]
if (!nextProject) return
const nextProjectSessions = globalSync.child(nextProject.worktree)[0].session ?? []
if (nextProjectSessions.length === 0) {
// Navigate to the project's new session page if no sessions
navigateToProject(nextProject.worktree)
return
}
// If going down (offset > 0), go to first session; if going up (offset < 0), go to last session
const targetSession = offset > 0 ? nextProjectSessions[0] : nextProjectSessions[nextProjectSessions.length - 1]
navigate(`/${base64Encode(nextProject.worktree)}/session/${targetSession.id}`)
}
async function archiveSession(session: Session) {
const [store, setStore] = globalSync.child(session.directory)
const sessions = store.session ?? []
const index = sessions.findIndex((s) => s.id === session.id)
// Get next session (prefer next, then prev) before removing
const nextSession = sessions[index + 1] ?? sessions[index - 1]
await globalSDK.client.session.update({
directory: session.directory,
sessionID: session.id,
time: { archived: Date.now() },
})
setStore(
produce((draft) => {
const match = Binary.search(draft.session, session.id, (s) => s.id)
if (match.found) draft.session.splice(match.index, 1)
}),
)
if (session.id === params.id) {
if (nextSession) {
navigate(`/${params.dir}/session/${nextSession.id}`)
} else {
navigate(`/${params.dir}/session`)
}
}
}
command.register(() => [
{
id: "sidebar.toggle",
title: "Toggle sidebar",
category: "View",
keybind: "mod+b",
onSelect: () => layout.sidebar.toggle(),
},
...(platform.openDirectoryPickerDialog
? [
{
id: "project.open",
title: "Open project",
category: "Project",
keybind: "mod+o",
onSelect: () => chooseProject(),
},
]
: []),
{
id: "session.previous",
title: "Previous session",
category: "Session",
keybind: "alt+arrowup",
disabled: !params.dir,
onSelect: () => navigateSessionByOffset(-1),
},
{
id: "session.next",
title: "Next session",
category: "Session",
keybind: "alt+arrowdown",
disabled: !params.dir,
onSelect: () => navigateSessionByOffset(1),
},
{
id: "session.archive",
title: "Archive session",
category: "Session",
keybind: "mod+shift+backspace",
disabled: !params.dir || !params.id,
onSelect: () => {
const session = currentSessions().find((s) => s.id === params.id)
if (session) archiveSession(session)
},
},
])
function connectProvider() {
dialog.replace(() => <DialogSelectProvider />)
@ -293,22 +425,6 @@ export default function Layout(props: ParentProps) {
session.id !== params.id &&
globalSync.child(props.project.worktree)[0].session_status[session.id]?.type === "busy",
)
async function archive(session: Session) {
await globalSDK.client.session.update({
directory: session.directory,
sessionID: session.id,
time: { archived: Date.now() },
})
setStore(
produce((draft) => {
const match = Binary.search(draft.session, session.id, (s) => s.id)
if (match.found) draft.session.splice(match.index, 1)
}),
)
if (session.id === params.id) {
navigate(`/${params.dir}/session`)
}
}
return (
<div
class="group/session relative w-full pl-4 pr-2 py-1 rounded-md cursor-default transition-colors
@ -363,7 +479,7 @@ export default function Layout(props: ParentProps) {
<div class="hidden group-hover/session:flex group-active/session:flex group-focus-within/session:flex text-text-base gap-1 items-center absolute top-1 right-1">
{/* <IconButton icon="dot-grid" variant="ghost" /> */}
<Tooltip placement="right" value="Archive session">
<IconButton icon="archive" variant="ghost" onClick={() => archive(session)} />
<IconButton icon="archive" variant="ghost" onClick={() => archiveSession(session)} />
</Tooltip>
</div>
</div>