feat(tui): show project copy in session list (#31421)
This commit is contained in:
parent
6566ede935
commit
ffcb45d7c9
@ -36,7 +36,12 @@ export const DirectoriesInput = Schema.Struct({
|
||||
}).annotate({ identifier: "Project.DirectoriesInput" })
|
||||
export type DirectoriesInput = typeof DirectoriesInput.Type
|
||||
|
||||
export const Directories = Schema.Array(AbsolutePath).annotate({ identifier: "Project.Directories" })
|
||||
export const Directories = Schema.Array(
|
||||
Schema.Struct({
|
||||
directory: AbsolutePath,
|
||||
type: Schema.Literals(["main", "root", "git_worktree"]),
|
||||
}),
|
||||
).annotate({ identifier: "Project.Directories" })
|
||||
export type Directories = typeof Directories.Type
|
||||
|
||||
export interface Interface {
|
||||
@ -73,13 +78,13 @@ export const layer = Layer.effect(
|
||||
|
||||
const directories = Effect.fn("Project.directories")(function* (input: DirectoriesInput) {
|
||||
const rows = yield* db
|
||||
.select({ directory: ProjectDirectoryTable.directory })
|
||||
.select({ directory: ProjectDirectoryTable.directory, type: ProjectDirectoryTable.type })
|
||||
.from(ProjectDirectoryTable)
|
||||
.where(eq(ProjectDirectoryTable.project_id, input.projectID))
|
||||
.orderBy(desc(ProjectDirectoryTable.time_created), asc(ProjectDirectoryTable.directory))
|
||||
.all()
|
||||
.pipe(Effect.orDie)
|
||||
return rows.map((row) => AbsolutePath.make(row.directory))
|
||||
return rows.map((row) => ({ directory: AbsolutePath.make(row.directory), type: row.type }))
|
||||
})
|
||||
|
||||
const cached = Effect.fnUntraced(function* (dir: string) {
|
||||
|
||||
@ -59,9 +59,11 @@ describe("Project directories schemas", () => {
|
||||
projectID: ProjectV2.ID.make("project"),
|
||||
},
|
||||
)
|
||||
expect(Schema.decodeUnknownSync(ProjectV2.Directories)([AbsolutePath.make("/tmp/project")])).toEqual([
|
||||
AbsolutePath.make("/tmp/project"),
|
||||
])
|
||||
expect(
|
||||
Schema.decodeUnknownSync(ProjectV2.Directories)([
|
||||
{ directory: AbsolutePath.make("/tmp/project"), type: "main" },
|
||||
]),
|
||||
).toEqual([{ directory: AbsolutePath.make("/tmp/project"), type: "main" }])
|
||||
}),
|
||||
)
|
||||
|
||||
@ -90,8 +92,8 @@ describe("Project directories schemas", () => {
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
expect(yield* project.directories({ projectID })).toEqual([
|
||||
AbsolutePath.make("/repo/z"),
|
||||
AbsolutePath.make("/repo/a"),
|
||||
{ directory: AbsolutePath.make("/repo/z"), type: "root" },
|
||||
{ directory: AbsolutePath.make("/repo/a"), type: "main" },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
@ -34,6 +34,8 @@ function json<T>(response: HttpClientResponse.HttpClientResponse) {
|
||||
}
|
||||
|
||||
describe("project directories and copies endpoints", () => {
|
||||
type ProjectDirectory = { directory: string; type: "main" | "root" | "git_worktree" }
|
||||
|
||||
it.instance(
|
||||
"lists directories and manages git worktree copies",
|
||||
() =>
|
||||
@ -51,7 +53,7 @@ describe("project directories and copies endpoints", () => {
|
||||
|
||||
const initial = yield* request(test.directory, `${base}/directories`)
|
||||
expect(initial.status).toBe(200)
|
||||
expect(yield* json<string[]>(initial)).toEqual([test.directory])
|
||||
expect(yield* json<ProjectDirectory[]>(initial)).toEqual([{ directory: test.directory, type: "main" }])
|
||||
|
||||
const create = yield* request(test.directory, copies, {
|
||||
method: "POST",
|
||||
@ -63,7 +65,10 @@ describe("project directories and copies endpoints", () => {
|
||||
expect(created.directory).toBe(createdDirectory)
|
||||
|
||||
const listed = yield* request(test.directory, `${base}/directories`)
|
||||
expect(yield* json<string[]>(listed)).toContain(created.directory)
|
||||
expect(yield* json<ProjectDirectory[]>(listed)).toContainEqual({
|
||||
directory: created.directory,
|
||||
type: "git_worktree",
|
||||
})
|
||||
|
||||
yield* Effect.promise(() => Bun.write(path.join(created.directory, "dirty.txt"), "dirty"))
|
||||
|
||||
@ -94,7 +99,10 @@ describe("project directories and copies endpoints", () => {
|
||||
})
|
||||
expect(refresh.status).toBe(204)
|
||||
const refreshed = yield* request(test.directory, `${base}/directories`)
|
||||
expect((yield* json<string[]>(refreshed)).length).toBe(2)
|
||||
expect(yield* json<ProjectDirectory[]>(refreshed)).toEqual([
|
||||
{ directory: externalDirectory, type: "git_worktree" },
|
||||
{ directory: test.directory, type: "main" },
|
||||
])
|
||||
}),
|
||||
{ git: true },
|
||||
)
|
||||
|
||||
@ -3743,7 +3743,10 @@ export type ConfigV2ExperimentalPolicy = {
|
||||
resource: string
|
||||
}
|
||||
|
||||
export type ProjectDirectories = Array<string>
|
||||
export type ProjectDirectories = Array<{
|
||||
directory: string
|
||||
type: "main" | "root" | "git_worktree"
|
||||
}>
|
||||
|
||||
export type ProjectCopyCopy = {
|
||||
directory: string
|
||||
|
||||
@ -23864,7 +23864,18 @@
|
||||
"ProjectDirectories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directory": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["main", "root", "git_worktree"]
|
||||
}
|
||||
},
|
||||
"required": ["directory", "type"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"ProjectCopyCopy": {
|
||||
|
||||
@ -63,7 +63,7 @@ export function DialogMoveSession(props: {
|
||||
try {
|
||||
await sdk.client.experimental.projectCopy.refresh({ projectID }, { throwOnError: true })
|
||||
const directories = await sdk.client.project.directories({ projectID }, { throwOnError: true })
|
||||
return directories.data ?? []
|
||||
return directories.data?.map((item) => item.directory) ?? []
|
||||
} finally {
|
||||
setWorking(false)
|
||||
}
|
||||
|
||||
@ -2,13 +2,13 @@ import { useDialog } from "../ui/dialog"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
import { useRoute } from "../context/route"
|
||||
import { useSync } from "../context/sync"
|
||||
import { createMemo, createResource, createSignal, onMount, type JSX } from "solid-js"
|
||||
import { createMemo, createResource, createSignal, onMount } from "solid-js"
|
||||
import path from "path"
|
||||
import { Locale } from "../util/locale"
|
||||
import { useProject } from "../context/project"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useSDK } from "../context/sdk"
|
||||
import { useLocal } from "../context/local"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { DialogSessionRename } from "./dialog-session-rename"
|
||||
import { createDebouncedSignal } from "../util/signal"
|
||||
import { useToast } from "../ui/toast"
|
||||
@ -16,7 +16,6 @@ import { openWorkspaceSelect, type WorkspaceSelection, warpWorkspaceSession } fr
|
||||
import { Spinner } from "./spinner"
|
||||
import { errorMessage } from "../util/error"
|
||||
import { DialogSessionDeleteFailed } from "./dialog-session-delete-failed"
|
||||
import { WorkspaceLabel } from "./workspace-label"
|
||||
import { useCommandShortcut } from "../keymap"
|
||||
|
||||
export function DialogSessionList() {
|
||||
@ -170,24 +169,13 @@ export function DialogSessionList() {
|
||||
function buildOption(id: string, category: string) {
|
||||
const x = sessionMap.get(id)
|
||||
if (!x) return undefined
|
||||
const workspace = x.workspaceID ? project.workspace.get(x.workspaceID) : undefined
|
||||
|
||||
let footer: JSX.Element | string = ""
|
||||
if (Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) {
|
||||
if (x.workspaceID) {
|
||||
footer = workspace ? (
|
||||
<WorkspaceLabel
|
||||
type={workspace.type}
|
||||
name={workspace.name}
|
||||
status={project.workspace.status(x.workspaceID) ?? "error"}
|
||||
/>
|
||||
) : (
|
||||
<WorkspaceLabel type="unknown" name={x.workspaceID} status="error" />
|
||||
)
|
||||
}
|
||||
} else {
|
||||
footer = Locale.time(x.time.updated)
|
||||
}
|
||||
const directory = x.path
|
||||
? x.directory.endsWith(x.path)
|
||||
? x.directory.slice(0, -x.path.length).replace(/\/$/, "")
|
||||
: undefined
|
||||
: x.directory
|
||||
const footer =
|
||||
directory && directory !== project.data.project.mainDir ? Locale.truncate(path.basename(directory), 20) : ""
|
||||
|
||||
const isDeleting = toDelete() === x.id
|
||||
const status = sync.data.session_status?.[x.id]
|
||||
|
||||
@ -23,6 +23,7 @@ export const { use: useProject, provider: ProjectProvider } = createSimpleContex
|
||||
project: {
|
||||
id: undefined as string | undefined,
|
||||
worktree: undefined as string | undefined,
|
||||
mainDir: undefined as string | undefined,
|
||||
},
|
||||
instance: {
|
||||
path: defaultPath,
|
||||
@ -36,15 +37,19 @@ export const { use: useProject, provider: ProjectProvider } = createSimpleContex
|
||||
|
||||
async function sync() {
|
||||
const workspace = store.workspace.current
|
||||
const [path, project] = await Promise.all([
|
||||
const [instancePath, project] = await Promise.all([
|
||||
sdk.client.path.get({ workspace }),
|
||||
sdk.client.project.current({ workspace }),
|
||||
])
|
||||
const directories = project.data?.id
|
||||
? await sdk.client.project.directories({ projectID: project.data.id, workspace })
|
||||
: undefined
|
||||
|
||||
batch(() => {
|
||||
setStore("instance", "path", reconcile(path.data || defaultPath))
|
||||
setStore("instance", "path", reconcile(instancePath.data || defaultPath))
|
||||
setStore("project", "id", project.data?.id)
|
||||
setStore("project", "worktree", project.data?.worktree)
|
||||
setStore("project", "mainDir", directories?.data?.find((item) => item.type === "main")?.directory)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user