fix(app): non-git projects should be renameable
This commit is contained in:
@@ -6,6 +6,7 @@ import { Icon } from "@opencode-ai/ui/icon"
|
|||||||
import { createMemo, createSignal, For, Show } from "solid-js"
|
import { createMemo, createSignal, For, Show } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useGlobalSDK } from "@/context/global-sdk"
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { type LocalProject, getAvatarColors } from "@/context/layout"
|
import { type LocalProject, getAvatarColors } from "@/context/layout"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { Avatar } from "@opencode-ai/ui/avatar"
|
import { Avatar } from "@opencode-ai/ui/avatar"
|
||||||
@@ -16,6 +17,7 @@ const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] a
|
|||||||
export function DialogEditProject(props: { project: LocalProject }) {
|
export function DialogEditProject(props: { project: LocalProject }) {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
|
const globalSync = useGlobalSync()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
const folderName = createMemo(() => getFilename(props.project.worktree))
|
const folderName = createMemo(() => getFilename(props.project.worktree))
|
||||||
@@ -71,17 +73,27 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
|||||||
async function handleSubmit(e: SubmitEvent) {
|
async function handleSubmit(e: SubmitEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
if (!props.project.id) return
|
|
||||||
|
|
||||||
setStore("saving", true)
|
setStore("saving", true)
|
||||||
const name = store.name.trim() === folderName() ? "" : store.name.trim()
|
const name = store.name.trim() === folderName() ? "" : store.name.trim()
|
||||||
const start = store.startup.trim()
|
const start = store.startup.trim()
|
||||||
await globalSDK.client.project.update({
|
|
||||||
projectID: props.project.id,
|
if (props.project.id && props.project.id !== "global") {
|
||||||
directory: props.project.worktree,
|
await globalSDK.client.project.update({
|
||||||
|
projectID: props.project.id,
|
||||||
|
directory: props.project.worktree,
|
||||||
|
name,
|
||||||
|
icon: { color: store.color, override: store.iconUrl },
|
||||||
|
commands: { start },
|
||||||
|
})
|
||||||
|
setStore("saving", false)
|
||||||
|
dialog.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
globalSync.project.meta(props.project.worktree, {
|
||||||
name,
|
name,
|
||||||
icon: { color: store.color, override: store.iconUrl },
|
icon: { color: store.color, override: store.iconUrl || undefined },
|
||||||
commands: { start },
|
commands: { start: start || undefined },
|
||||||
})
|
})
|
||||||
setStore("saving", false)
|
setStore("saving", false)
|
||||||
dialog.close()
|
dialog.close()
|
||||||
|
|||||||
@@ -44,11 +44,23 @@ import { usePlatform } from "./platform"
|
|||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
|
|
||||||
|
type ProjectMeta = {
|
||||||
|
name?: string
|
||||||
|
icon?: {
|
||||||
|
override?: string
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
commands?: {
|
||||||
|
start?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
status: "loading" | "partial" | "complete"
|
status: "loading" | "partial" | "complete"
|
||||||
agent: Agent[]
|
agent: Agent[]
|
||||||
command: Command[]
|
command: Command[]
|
||||||
project: string
|
project: string
|
||||||
|
projectMeta: ProjectMeta | undefined
|
||||||
provider: ProviderListResponse
|
provider: ProviderListResponse
|
||||||
config: Config
|
config: Config
|
||||||
path: Path
|
path: Path
|
||||||
@@ -89,6 +101,12 @@ type VcsCache = {
|
|||||||
ready: Accessor<boolean>
|
ready: Accessor<boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MetaCache = {
|
||||||
|
store: Store<{ value: ProjectMeta | undefined }>
|
||||||
|
setStore: SetStoreFunction<{ value: ProjectMeta | undefined }>
|
||||||
|
ready: Accessor<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
type ChildOptions = {
|
type ChildOptions = {
|
||||||
bootstrap?: boolean
|
bootstrap?: boolean
|
||||||
}
|
}
|
||||||
@@ -100,6 +118,7 @@ function createGlobalSync() {
|
|||||||
const owner = getOwner()
|
const owner = getOwner()
|
||||||
if (!owner) throw new Error("GlobalSync must be created within owner")
|
if (!owner) throw new Error("GlobalSync must be created within owner")
|
||||||
const vcsCache = new Map<string, VcsCache>()
|
const vcsCache = new Map<string, VcsCache>()
|
||||||
|
const metaCache = new Map<string, MetaCache>()
|
||||||
const [globalStore, setGlobalStore] = createStore<{
|
const [globalStore, setGlobalStore] = createStore<{
|
||||||
ready: boolean
|
ready: boolean
|
||||||
error?: InitError
|
error?: InitError
|
||||||
@@ -149,9 +168,19 @@ function createGlobalSync() {
|
|||||||
if (!cache) throw new Error("Failed to create persisted cache")
|
if (!cache) throw new Error("Failed to create persisted cache")
|
||||||
vcsCache.set(directory, { store: cache[0], setStore: cache[1], ready: cache[3] })
|
vcsCache.set(directory, { store: cache[0], setStore: cache[1], ready: cache[3] })
|
||||||
|
|
||||||
|
const meta = runWithOwner(owner, () =>
|
||||||
|
persisted(
|
||||||
|
Persist.workspace(directory, "project", ["project.v1"]),
|
||||||
|
createStore({ value: undefined as ProjectMeta | undefined }),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (!meta) throw new Error("Failed to create persisted project metadata")
|
||||||
|
metaCache.set(directory, { store: meta[0], setStore: meta[1], ready: meta[3] })
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
children[directory] = createStore<State>({
|
children[directory] = createStore<State>({
|
||||||
project: "",
|
project: "",
|
||||||
|
projectMeta: meta[0].value,
|
||||||
provider: { all: [], connected: [], default: {} },
|
provider: { all: [], connected: [], default: {} },
|
||||||
config: {},
|
config: {},
|
||||||
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
path: { state: "", config: "", worktree: "", directory: "", home: "" },
|
||||||
@@ -253,6 +282,8 @@ function createGlobalSync() {
|
|||||||
const [store, setStore] = ensureChild(directory)
|
const [store, setStore] = ensureChild(directory)
|
||||||
const cache = vcsCache.get(directory)
|
const cache = vcsCache.get(directory)
|
||||||
if (!cache) return
|
if (!cache) return
|
||||||
|
const meta = metaCache.get(directory)
|
||||||
|
if (!meta) return
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: globalSDK.url,
|
baseUrl: globalSDK.url,
|
||||||
fetch: platform.fetch,
|
fetch: platform.fetch,
|
||||||
@@ -269,6 +300,13 @@ function createGlobalSync() {
|
|||||||
setStore("vcs", (value) => value ?? cached)
|
setStore("vcs", (value) => value ?? cached)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!meta.ready()) return
|
||||||
|
const cached = meta.store.value
|
||||||
|
if (!cached) return
|
||||||
|
setStore("projectMeta", (value) => value ?? cached)
|
||||||
|
})
|
||||||
|
|
||||||
const blockingRequests = {
|
const blockingRequests = {
|
||||||
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
|
project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)),
|
||||||
provider: () =>
|
provider: () =>
|
||||||
@@ -725,6 +763,23 @@ function createGlobalSync() {
|
|||||||
bootstrap()
|
bootstrap()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function projectMeta(directory: string, patch: ProjectMeta) {
|
||||||
|
const [store, setStore] = ensureChild(directory)
|
||||||
|
const cached = metaCache.get(directory)
|
||||||
|
if (!cached) return
|
||||||
|
const previous = store.projectMeta ?? {}
|
||||||
|
const icon = patch.icon ? { ...(previous.icon ?? {}), ...patch.icon } : previous.icon
|
||||||
|
const commands = patch.commands ? { ...(previous.commands ?? {}), ...patch.commands } : previous.commands
|
||||||
|
const next = {
|
||||||
|
...previous,
|
||||||
|
...patch,
|
||||||
|
icon,
|
||||||
|
commands,
|
||||||
|
}
|
||||||
|
cached.setStore("value", next)
|
||||||
|
setStore("projectMeta", next)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: globalStore,
|
data: globalStore,
|
||||||
set: setGlobalStore,
|
set: setGlobalStore,
|
||||||
@@ -746,6 +801,7 @@ function createGlobalSync() {
|
|||||||
},
|
},
|
||||||
project: {
|
project: {
|
||||||
loadSessions,
|
loadSessions,
|
||||||
|
meta: projectMeta,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,7 +222,8 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
const metadata = projectID
|
const metadata = projectID
|
||||||
? globalSync.data.project.find((x) => x.id === projectID)
|
? globalSync.data.project.find((x) => x.id === projectID)
|
||||||
: globalSync.data.project.find((x) => x.worktree === project.worktree)
|
: globalSync.data.project.find((x) => x.worktree === project.worktree)
|
||||||
return {
|
|
||||||
|
const base = {
|
||||||
...(metadata ?? {}),
|
...(metadata ?? {}),
|
||||||
...project,
|
...project,
|
||||||
icon: {
|
icon: {
|
||||||
@@ -231,6 +232,20 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
color: metadata?.icon?.color,
|
color: metadata?.icon?.color,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (projectID !== "global") return base
|
||||||
|
|
||||||
|
const local = childStore.projectMeta
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
name: local?.name,
|
||||||
|
commands: local?.commands,
|
||||||
|
icon: {
|
||||||
|
url: base.icon?.url,
|
||||||
|
override: local?.icon?.override,
|
||||||
|
color: local?.icon?.color,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const roots = createMemo(() => {
|
const roots = createMemo(() => {
|
||||||
@@ -296,6 +311,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
used.add(color)
|
used.add(color)
|
||||||
setColors(project.worktree, color)
|
setColors(project.worktree, color)
|
||||||
if (!project.id) continue
|
if (!project.id) continue
|
||||||
|
if (project.id === "global") {
|
||||||
|
globalSync.project.meta(project.worktree, { icon: { color } })
|
||||||
|
continue
|
||||||
|
}
|
||||||
void globalSdk.client.project.update({ projectID: project.id, directory: project.worktree, icon: { color } })
|
void globalSdk.client.project.update({ projectID: project.id, directory: project.worktree, icon: { color } })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1018,11 +1018,16 @@ export default function Layout(props: ParentProps) {
|
|||||||
const displayName = (project: LocalProject) => project.name || getFilename(project.worktree)
|
const displayName = (project: LocalProject) => project.name || getFilename(project.worktree)
|
||||||
|
|
||||||
async function renameProject(project: LocalProject, next: string) {
|
async function renameProject(project: LocalProject, next: string) {
|
||||||
if (!project.id) return
|
|
||||||
const current = displayName(project)
|
const current = displayName(project)
|
||||||
if (next === current) return
|
if (next === current) return
|
||||||
const name = next === getFilename(project.worktree) ? "" : next
|
const name = next === getFilename(project.worktree) ? "" : next
|
||||||
await globalSDK.client.project.update({ projectID: project.id, directory: project.worktree, name })
|
|
||||||
|
if (project.id && project.id !== "global") {
|
||||||
|
await globalSDK.client.project.update({ projectID: project.id, directory: project.worktree, name })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
globalSync.project.meta(project.worktree, { name })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renameSession(session: Session, next: string) {
|
async function renameSession(session: Session, next: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user