Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions apps/desktop/src/app/chat/sidebar/projects/project-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
openProjectRename,
revealPath,
setActiveProject,
updateProject
setProjectAppearance
} from '@/store/projects'

import type { SidebarProjectTree } from './workspace-groups'
Expand Down Expand Up @@ -110,6 +110,26 @@ export function ProjectMenu({
}
}

// Appearance writes route through the adopt-aware helper: an auto project is
// materialized on its first change (its id then changes), so close the picker
// when that happens to avoid a second write double-creating from a stale node.
const applyAppearance = (patch: { color?: null | string; icon?: null | string }) => {
void setProjectAppearance(project, patch).then(adopted => {
if (adopted) {
setAppearanceOpen(false)
}
})
}

// Set color / pick an icon — shown for explicit projects and for auto ones
// (where selecting adopts the repo as a real project so the look sticks).
const appearanceItem = (
<DropdownMenuItem onSelect={() => setAppearanceOpen(true)}>
<Codicon name="symbol-color" size="0.875rem" />
<span>{p.menuAppearance}</span>
</DropdownMenuItem>
)

const trigger = (
<DropdownMenuTrigger asChild>
<button
Expand Down Expand Up @@ -144,16 +164,23 @@ export function ProjectMenu({
onCloseAutoFocus={event => event.preventDefault()}
sideOffset={6}
>
{!project.isAuto && (
{project.isAuto ? (
// Inherited (auto) repos can still be themed — the change adopts the
// repo as a real project. Rename / add-folder / set-active stay out
// until then (they need the materialized record).
project.path ? (
<>
{appearanceItem}
<DropdownMenuSeparator />
</>
) : null
) : (
<>
<DropdownMenuItem onSelect={() => openProjectRename(target)}>
<Codicon name="edit" size="0.875rem" />
<span>{p.menuRename}</span>
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => setAppearanceOpen(true)}>
<Codicon name="symbol-color" size="0.875rem" />
<span>{p.menuAppearance}</span>
</DropdownMenuItem>
{appearanceItem}
<DropdownMenuItem onSelect={() => openProjectAddFolder(target)}>
<Codicon name="new-folder" size="0.875rem" />
<span>{p.menuAddFolder}</span>
Expand Down Expand Up @@ -197,7 +224,7 @@ export function ProjectMenu({
<ColorSwatches
clearIcon="circle-slash"
clearLabel={p.noColor}
onChange={color => void updateProject(project.id, { color })}
onChange={color => applyAppearance({ color })}
swatches={PROFILE_SWATCHES}
value={project.color ?? null}
/>
Expand All @@ -212,7 +239,7 @@ export function ProjectMenu({
project.icon === name && 'bg-(--ui-control-active-background) text-foreground'
)}
key={name}
onClick={() => void updateProject(project.id, { icon: project.icon === name ? null : name })}
onClick={() => applyAppearance({ icon: project.icon === name ? null : name })}
style={project.icon === name && project.color ? { color: project.color } : undefined}
type="button"
>
Expand Down
32 changes: 32 additions & 0 deletions apps/desktop/src/store/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,38 @@ export async function updateProject(
)
}

// Appearance for an AUTO (inherited git-repo) project has no projects.db row to
// write to — its id is just the repo path. So the first color/icon change ADOPTS
// the repo as a real project (folder = repo root, name = its label) carrying the
// chosen look; from then on it patches in place like any explicit project.
// Returns true when an adoption happened, so an incremental picker can close
// (the node's id changes on adopt, and a second stale write would double-create).
export async function setProjectAppearance(
project: Pick<SidebarProjectTree, 'color' | 'icon' | 'id' | 'isAuto' | 'label' | 'path'>,
patch: { color?: null | string; icon?: null | string }
): Promise<boolean> {
if (!project.isAuto) {
await updateProject(project.id, patch)

return false
}

if (!project.path) {
return false
}

await createProject({
name: project.label,
folders: [project.path],
primaryPath: project.path,
// Carry any already-set look so setting one field doesn't wipe the other.
color: (patch.color ?? project.color) || undefined,
icon: (patch.icon ?? project.icon) || undefined
})

return true
}

export async function addProjectFolder(
id: string,
path: string,
Expand Down
Loading