Przeglądaj źródła

fix(app): no more favicons

Adam 1 miesiąc temu
rodzic
commit
a4d1824412

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

@@ -22,7 +22,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
   const [store, setStore] = createStore({
     name: defaultName(),
     color: props.project.icon?.color || "pink",
-    iconUrl: props.project.icon?.url || "",
+    iconUrl: props.project.icon?.override || "",
     saving: false,
   })
 
@@ -74,7 +74,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
     await globalSDK.client.project.update({
       projectID: props.project.id,
       name,
-      icon: { color: store.color, url: store.iconUrl },
+      icon: { color: store.color, override: store.iconUrl },
     })
     setStore("saving", false)
     dialog.close()

+ 41 - 21
packages/app/src/context/layout.tsx

@@ -208,10 +208,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
       })
     })
 
-    const usedColors = new Set<AvatarColorKey>()
+    const [colors, setColors] = createStore<Record<string, AvatarColorKey>>({})
 
-    function pickAvailableColor(): AvatarColorKey {
-      const available = AVATAR_COLOR_KEYS.filter((c) => !usedColors.has(c))
+    function pickAvailableColor(used: Set<string>): AvatarColorKey {
+      const available = AVATAR_COLOR_KEYS.filter((c) => !used.has(c))
       if (available.length === 0) return AVATAR_COLOR_KEYS[Math.floor(Math.random() * AVATAR_COLOR_KEYS.length)]
       return available[Math.floor(Math.random() * available.length)]
     }
@@ -222,24 +222,15 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
       const metadata = projectID
         ? globalSync.data.project.find((x) => x.id === projectID)
         : globalSync.data.project.find((x) => x.worktree === project.worktree)
-      return [
-        {
-          ...(metadata ?? {}),
-          ...project,
-          icon: { url: metadata?.icon?.url, color: metadata?.icon?.color },
+      return {
+        ...(metadata ?? {}),
+        ...project,
+        icon: {
+          url: metadata?.icon?.url,
+          override: metadata?.icon?.override,
+          color: metadata?.icon?.color,
         },
-      ]
-    }
-
-    function colorize(project: LocalProject) {
-      if (project.icon?.color) return project
-      const color = pickAvailableColor()
-      usedColors.add(color)
-      project.icon = { ...project.icon, color }
-      if (project.id) {
-        globalSdk.client.project.update({ projectID: project.id, icon: { color } })
       }
-      return project
     }
 
     const roots = createMemo(() => {
@@ -277,8 +268,37 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
       })
     })
 
-    const enriched = createMemo(() => server.projects.list().flatMap(enrich))
-    const list = createMemo(() => enriched().flatMap(colorize))
+    const enriched = createMemo(() => server.projects.list().map(enrich))
+    const list = createMemo(() => {
+      const projects = enriched()
+      return projects.map((project) => {
+        const color = project.icon?.color ?? colors[project.worktree]
+        if (!color) return project
+        const icon = project.icon ? { ...project.icon, color } : { color }
+        return { ...project, icon }
+      })
+    })
+
+    createEffect(() => {
+      const projects = enriched()
+      if (projects.length === 0) return
+
+      const used = new Set<string>()
+      for (const project of projects) {
+        const color = project.icon?.color ?? colors[project.worktree]
+        if (color) used.add(color)
+      }
+
+      for (const project of projects) {
+        if (project.icon?.color) continue
+        if (colors[project.worktree]) continue
+        const color = pickAvailableColor(used)
+        used.add(color)
+        setColors(project.worktree, color)
+        if (!project.id) continue
+        void globalSdk.client.project.update({ projectID: project.id, icon: { color } })
+      }
+    })
 
     onMount(() => {
       Promise.all(

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

@@ -1284,7 +1284,7 @@ export default function Layout(props: ParentProps) {
         <div class="size-full rounded overflow-clip">
           <Avatar
             fallback={name()}
-            src={props.project.id === opencode ? "https://opencode.ai/favicon-v2.svg" : props.project.icon?.url}
+            src={props.project.id === opencode ? "https://opencode.ai/favicon-v2.svg" : props.project.icon?.override}
             {...getAvatarColors(props.project.icon?.color)}
             class="size-full rounded"
             style={

+ 4 - 0
packages/opencode/src/project/project.ts

@@ -25,6 +25,7 @@ export namespace Project {
       icon: z
         .object({
           url: z.string().optional(),
+          override: z.string().optional(),
           color: z.string().optional(),
         })
         .optional(),
@@ -190,6 +191,7 @@ export namespace Project {
     if (!existing.sandboxes) existing.sandboxes = []
 
     if (Flag.OPENCODE_EXPERIMENTAL_ICON_DISCOVERY) discover(existing)
+
     const result: Info = {
       ...existing,
       worktree,
@@ -213,6 +215,7 @@ export namespace Project {
 
   export async function discover(input: Info) {
     if (input.vcs !== "git") return
+    if (input.icon?.override) return
     if (input.icon?.url) return
     const glob = new Bun.Glob("**/{favicon}.{ico,png,svg,jpg,jpeg,webp}")
     const matches = await Array.fromAsync(
@@ -293,6 +296,7 @@ export namespace Project {
             ...draft.icon,
           }
           if (input.icon.url !== undefined) draft.icon.url = input.icon.url
+          if (input.icon.override !== undefined) draft.icon.override = input.icon.override || undefined
           if (input.icon.color !== undefined) draft.icon.color = input.icon.color
         }
         draft.time.updated = Date.now()

+ 1 - 0
packages/sdk/js/src/v2/gen/sdk.gen.ts

@@ -302,6 +302,7 @@ export class Project extends HeyApiClient {
       name?: string
       icon?: {
         url?: string
+        override?: string
         color?: string
       }
     },

+ 2 - 0
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -25,6 +25,7 @@ export type Project = {
   name?: string
   icon?: {
     url?: string
+    override?: string
     color?: string
   }
   time: {
@@ -2229,6 +2230,7 @@ export type ProjectUpdateData = {
     name?: string
     icon?: {
       url?: string
+      override?: string
       color?: string
     }
   }