fix(app): improve icon override handling in project edit dialog (#23768)

This commit is contained in:
Brendan Allan
2026-04-23 00:25:36 -04:00
committed by Aiden Cline
parent 69b8ea0d66
commit 06066dbb7b
3 changed files with 23 additions and 19 deletions
@@ -26,8 +26,8 @@ export function DialogEditProject(props: { project: LocalProject }) {
const [store, setStore] = createStore({ const [store, setStore] = createStore({
name: defaultName(), name: defaultName(),
color: props.project.icon?.color || "pink", color: props.project.icon?.color,
iconUrl: props.project.icon?.override || "", iconOverride: props.project.icon?.override,
startup: props.project.commands?.start ?? "", startup: props.project.commands?.start ?? "",
dragOver: false, dragOver: false,
iconHover: false, iconHover: false,
@@ -39,7 +39,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
if (!file.type.startsWith("image/")) return if (!file.type.startsWith("image/")) return
const reader = new FileReader() const reader = new FileReader()
reader.onload = (e) => { reader.onload = (e) => {
setStore("iconUrl", e.target?.result as string) setStore("iconOverride", e.target?.result as string)
setStore("iconHover", false) setStore("iconHover", false)
} }
reader.readAsDataURL(file) reader.readAsDataURL(file)
@@ -68,7 +68,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
} }
function clearIcon() { function clearIcon() {
setStore("iconUrl", "") setStore("iconOverride", "")
} }
const saveMutation = useMutation(() => ({ const saveMutation = useMutation(() => ({
@@ -81,17 +81,17 @@ export function DialogEditProject(props: { project: LocalProject }) {
projectID: props.project.id, projectID: props.project.id,
directory: props.project.worktree, directory: props.project.worktree,
name, name,
icon: { color: store.color, override: store.iconUrl }, icon: { color: store.color || "", override: store.iconOverride || "" },
commands: { start }, commands: { start },
}) })
globalSync.project.icon(props.project.worktree, store.iconUrl || undefined) globalSync.project.icon(props.project.worktree, store.iconOverride || undefined)
dialog.close() dialog.close()
return return
} }
globalSync.project.meta(props.project.worktree, { globalSync.project.meta(props.project.worktree, {
name, name,
icon: { color: store.color, override: store.iconUrl || undefined }, icon: { color: store.color || undefined, override: store.iconOverride || undefined },
commands: { start: start || undefined }, commands: { start: start || undefined },
}) })
dialog.close() dialog.close()
@@ -130,13 +130,13 @@ export function DialogEditProject(props: { project: LocalProject }) {
classList={{ classList={{
"border-text-interactive-base bg-surface-info-base/20": store.dragOver, "border-text-interactive-base bg-surface-info-base/20": store.dragOver,
"border-border-base hover:border-border-strong": !store.dragOver, "border-border-base hover:border-border-strong": !store.dragOver,
"overflow-hidden": !!store.iconUrl, "overflow-hidden": !!store.iconOverride,
}} }}
onDrop={handleDrop} onDrop={handleDrop}
onDragOver={handleDragOver} onDragOver={handleDragOver}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}
onClick={() => { onClick={() => {
if (store.iconUrl && store.iconHover) { if (store.iconOverride && store.iconHover) {
clearIcon() clearIcon()
} else { } else {
iconInput?.click() iconInput?.click()
@@ -144,7 +144,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
}} }}
> >
<Show <Show
when={store.iconUrl} when={store.iconOverride || (!store.color && props.project.icon?.url)}
fallback={ fallback={
<div class="size-full flex items-center justify-center"> <div class="size-full flex items-center justify-center">
<Avatar <Avatar
@@ -156,7 +156,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
} }
> >
<img <img
src={store.iconUrl} src={store.iconOverride || props.project.icon?.url}
alt={language.t("dialog.project.edit.icon.alt")} alt={language.t("dialog.project.edit.icon.alt")}
class="size-full object-cover" class="size-full object-cover"
/> />
@@ -165,8 +165,8 @@ export function DialogEditProject(props: { project: LocalProject }) {
<div <div
class="absolute inset-0 size-16 bg-surface-raised-stronger-non-alpha/90 rounded-[6px] z-10 pointer-events-none flex items-center justify-center transition-opacity" class="absolute inset-0 size-16 bg-surface-raised-stronger-non-alpha/90 rounded-[6px] z-10 pointer-events-none flex items-center justify-center transition-opacity"
classList={{ classList={{
"opacity-100": store.iconHover && !store.iconUrl, "opacity-100": store.iconHover && !store.iconOverride,
"opacity-0": !(store.iconHover && !store.iconUrl), "opacity-0": !(store.iconHover && !store.iconOverride),
}} }}
> >
<Icon name="cloud-upload" size="large" class="text-icon-on-interactive-base drop-shadow-sm" /> <Icon name="cloud-upload" size="large" class="text-icon-on-interactive-base drop-shadow-sm" />
@@ -174,8 +174,8 @@ export function DialogEditProject(props: { project: LocalProject }) {
<div <div
class="absolute inset-0 size-16 bg-surface-raised-stronger-non-alpha/90 rounded-[6px] z-10 pointer-events-none flex items-center justify-center transition-opacity" class="absolute inset-0 size-16 bg-surface-raised-stronger-non-alpha/90 rounded-[6px] z-10 pointer-events-none flex items-center justify-center transition-opacity"
classList={{ classList={{
"opacity-100": store.iconHover && !!store.iconUrl, "opacity-100": store.iconHover && !!store.iconOverride,
"opacity-0": !(store.iconHover && !!store.iconUrl), "opacity-0": !(store.iconHover && !!store.iconOverride),
}} }}
> >
<Icon name="trash" size="large" class="text-icon-on-interactive-base drop-shadow-sm" /> <Icon name="trash" size="large" class="text-icon-on-interactive-base drop-shadow-sm" />
@@ -198,7 +198,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
</div> </div>
</div> </div>
<Show when={!store.iconUrl}> <Show when={!store.iconOverride}>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<label class="text-12-medium text-text-weak">{language.t("dialog.project.edit.color")}</label> <label class="text-12-medium text-text-weak">{language.t("dialog.project.edit.color")}</label>
<div class="flex gap-1.5"> <div class="flex gap-1.5">
@@ -215,7 +215,9 @@ export function DialogEditProject(props: { project: LocalProject }) {
"bg-transparent border border-transparent hover:bg-surface-base-hover hover:border-border-weak-base": "bg-transparent border border-transparent hover:bg-surface-base-hover hover:border-border-weak-base":
store.color !== color, store.color !== color,
}} }}
onClick={() => setStore("color", color)} onClick={() => {
setStore("color", store.color === color ? undefined : color)
}}
> >
<Avatar <Avatar
fallback={store.name || defaultName()} fallback={store.name || defaultName()}
+1 -1
View File
@@ -516,7 +516,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
} }
for (const project of projects) { for (const project of projects) {
if (project.icon?.color) continue if (project.icon?.color || project.icon.url) continue
const worktree = project.worktree const worktree = project.worktree
const existing = colors[worktree] const existing = colors[worktree]
const color = existing ?? pickAvailableColor(used) const color = existing ?? pickAvailableColor(used)
@@ -45,7 +45,9 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
src={ src={
props.project.id === OPENCODE_PROJECT_ID props.project.id === OPENCODE_PROJECT_ID
? "https://opencode.ai/favicon.svg" ? "https://opencode.ai/favicon.svg"
: props.project.icon?.override || props.project.icon?.url : props.project.icon?.color
? undefined
: props.project.icon?.override || props.project.icon?.url
} }
{...getAvatarColors(props.project.icon?.color)} {...getAvatarColors(props.project.icon?.color)}
class="size-full rounded" class="size-full rounded"