feat(tui): add workspace management dialog (#29612)
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import type { Workspace } from "@opencode-ai/sdk/v2"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
|
||||
import { useProject } from "@tui/context/project"
|
||||
import { useRoute } from "@tui/context/route"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { createMemo, createSignal, onMount } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { useSDK } from "../context/sdk"
|
||||
import { useToast } from "../ui/toast"
|
||||
|
||||
type WorkspaceOption = { workspace: Workspace }
|
||||
|
||||
export function DialogWorkspaceList() {
|
||||
const dialog = useDialog()
|
||||
const route = useRoute()
|
||||
const sync = useSync()
|
||||
const sdk = useSDK()
|
||||
const toast = useToast()
|
||||
const project = useProject()
|
||||
const { theme } = useTheme()
|
||||
const [deleting, setDeleting] = createSignal<string>()
|
||||
const [removing, setRemoving] = createSignal<string>()
|
||||
const [expanded, setExpanded] = createStore<Record<string, boolean>>({})
|
||||
|
||||
const current = createMemo(() => {
|
||||
if (route.data.type === "session") return sync.session.get(route.data.sessionID)?.workspaceID
|
||||
return project.workspace.current()
|
||||
})
|
||||
|
||||
const options = createMemo<DialogSelectOption<WorkspaceOption>[]>(() =>
|
||||
project.workspace
|
||||
.list()
|
||||
.toSorted((a, b) => a.name.localeCompare(b.name))
|
||||
.map((workspace) => {
|
||||
const status = project.workspace.status(workspace.id)
|
||||
return {
|
||||
title:
|
||||
removing() === workspace.id
|
||||
? "Deleting..."
|
||||
: deleting() === workspace.id
|
||||
? `Delete ${workspace.name}? Press delete again`
|
||||
: workspace.name,
|
||||
value: { workspace },
|
||||
footer: workspace.type,
|
||||
details: expanded[workspace.id] && workspace.directory ? [workspace.directory] : undefined,
|
||||
gutter: () => <text fg={status === "connected" ? theme.success : theme.error}>●</text>,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
function showDetails(workspace: Workspace) {
|
||||
setExpanded(workspace.id, (open) => !open)
|
||||
}
|
||||
|
||||
async function remove(workspace: Workspace) {
|
||||
if (removing()) return
|
||||
if (deleting() !== workspace.id) {
|
||||
setDeleting(workspace.id)
|
||||
return
|
||||
}
|
||||
|
||||
setDeleting(undefined)
|
||||
setRemoving(workspace.id)
|
||||
const result = await sdk.client.experimental.workspace.remove({ id: workspace.id }).catch((err) => ({
|
||||
error: err,
|
||||
}))
|
||||
if (result?.error) {
|
||||
setRemoving(undefined)
|
||||
toast.show({
|
||||
variant: "error",
|
||||
title: "Failed to delete workspace",
|
||||
message: errorMessage(result.error),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (current() === workspace.id) {
|
||||
project.workspace.set(undefined)
|
||||
route.navigate({ type: "home" })
|
||||
}
|
||||
await project.workspace.sync()
|
||||
await sync.bootstrap({ fatal: false }).catch(() => undefined)
|
||||
setRemoving(undefined)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
dialog.setSize("large")
|
||||
void sdk.client.experimental.workspace.syncList().catch(() => undefined)
|
||||
void project.workspace.sync()
|
||||
})
|
||||
|
||||
return (
|
||||
<DialogSelect
|
||||
title="Workspaces"
|
||||
options={options()}
|
||||
onMove={(option) => {
|
||||
setDeleting(undefined)
|
||||
}}
|
||||
onSelect={(option) => showDetails(option.value.workspace)}
|
||||
actions={[
|
||||
{
|
||||
command: "session.delete",
|
||||
title: "delete",
|
||||
onTrigger: (option) => void remove(option.value.workspace),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user