Browse Source

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

Brendan Allan 4 days ago
parent
commit
a45d9a9b0a

+ 19 - 17
packages/app/src/components/dialog-edit-project.tsx

@@ -26,8 +26,8 @@ export function DialogEditProject(props: { project: LocalProject }) {
 
   const [store, setStore] = createStore({
     name: defaultName(),
-    color: props.project.icon?.color || "pink",
-    iconUrl: props.project.icon?.override || "",
+    color: props.project.icon?.color,
+    iconOverride: props.project.icon?.override,
     startup: props.project.commands?.start ?? "",
     dragOver: false,
     iconHover: false,
@@ -39,7 +39,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
     if (!file.type.startsWith("image/")) return
     const reader = new FileReader()
     reader.onload = (e) => {
-      setStore("iconUrl", e.target?.result as string)
+      setStore("iconOverride", e.target?.result as string)
       setStore("iconHover", false)
     }
     reader.readAsDataURL(file)
@@ -68,7 +68,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
   }
 
   function clearIcon() {
-    setStore("iconUrl", "")
+    setStore("iconOverride", "")
   }
 
   const saveMutation = useMutation(() => ({
@@ -81,17 +81,17 @@ export function DialogEditProject(props: { project: LocalProject }) {
           projectID: props.project.id,
           directory: props.project.worktree,
           name,
-          icon: { color: store.color, override: store.iconUrl },
+          icon: { color: store.color || "", override: store.iconOverride || "" },
           commands: { start },
         })
-        globalSync.project.icon(props.project.worktree, store.iconUrl || undefined)
+        globalSync.project.icon(props.project.worktree, store.iconOverride || undefined)
         dialog.close()
         return
       }
 
       globalSync.project.meta(props.project.worktree, {
         name,
-        icon: { color: store.color, override: store.iconUrl || undefined },
+        icon: { color: store.color || undefined, override: store.iconOverride || undefined },
         commands: { start: start || undefined },
       })
       dialog.close()
@@ -130,13 +130,13 @@ export function DialogEditProject(props: { project: LocalProject }) {
                   classList={{
                     "border-text-interactive-base bg-surface-info-base/20": store.dragOver,
                     "border-border-base hover:border-border-strong": !store.dragOver,
-                    "overflow-hidden": !!store.iconUrl,
+                    "overflow-hidden": !!store.iconOverride,
                   }}
                   onDrop={handleDrop}
                   onDragOver={handleDragOver}
                   onDragLeave={handleDragLeave}
                   onClick={() => {
-                    if (store.iconUrl && store.iconHover) {
+                    if (store.iconOverride && store.iconHover) {
                       clearIcon()
                     } else {
                       iconInput?.click()
@@ -144,7 +144,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
                   }}
                 >
                   <Show
-                    when={store.iconUrl}
+                    when={store.iconOverride || (!store.color && props.project.icon?.url)}
                     fallback={
                       <div class="size-full flex items-center justify-center">
                         <Avatar
@@ -156,7 +156,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
                     }
                   >
                     <img
-                      src={store.iconUrl}
+                      src={store.iconOverride || props.project.icon?.url}
                       alt={language.t("dialog.project.edit.icon.alt")}
                       class="size-full object-cover"
                     />
@@ -165,8 +165,8 @@ export function DialogEditProject(props: { project: LocalProject }) {
                 <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"
                   classList={{
-                    "opacity-100": store.iconHover && !store.iconUrl,
-                    "opacity-0": !(store.iconHover && !store.iconUrl),
+                    "opacity-100": store.iconHover && !store.iconOverride,
+                    "opacity-0": !(store.iconHover && !store.iconOverride),
                   }}
                 >
                   <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
                   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={{
-                    "opacity-100": store.iconHover && !!store.iconUrl,
-                    "opacity-0": !(store.iconHover && !!store.iconUrl),
+                    "opacity-100": store.iconHover && !!store.iconOverride,
+                    "opacity-0": !(store.iconHover && !!store.iconOverride),
                   }}
                 >
                   <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>
 
-          <Show when={!store.iconUrl}>
+          <Show when={!store.iconOverride}>
             <div class="flex flex-col gap-2">
               <label class="text-12-medium text-text-weak">{language.t("dialog.project.edit.color")}</label>
               <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":
                           store.color !== color,
                       }}
-                      onClick={() => setStore("color", color)}
+                      onClick={() => {
+                        setStore("color", store.color === color ? undefined : color)
+                      }}
                     >
                       <Avatar
                         fallback={store.name || defaultName()}

+ 1 - 1
packages/app/src/context/layout.tsx

@@ -516,7 +516,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
       }
 
       for (const project of projects) {
-        if (project.icon?.color) continue
+        if (project.icon?.color || project.icon.url) continue
         const worktree = project.worktree
         const existing = colors[worktree]
         const color = existing ?? pickAvailableColor(used)

+ 3 - 1
packages/app/src/pages/layout/sidebar-items.tsx

@@ -45,7 +45,9 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
           src={
             props.project.id === OPENCODE_PROJECT_ID
               ? "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)}
           class="size-full rounded"