fix: avatar not loaded fallback
parent
fa9c283fcf
commit
1455976689
|
|
@ -3,11 +3,12 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { TextField } from "@opencode-ai/ui/text-field"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Avatar } from "@opencode-ai/ui/avatar"
|
||||
import { createMemo, createSignal, For, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
import { type LocalProject, getAvatarColors } from "@/context/layout"
|
||||
import { Avatar } from "@opencode-ai/ui/avatar"
|
||||
import { ProjectIcon, isValidImageFile } from "@/components/project-icon"
|
||||
|
||||
const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
|
||||
|
||||
|
|
@ -33,9 +34,11 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
|||
const [dragOver, setDragOver] = createSignal(false)
|
||||
|
||||
function handleFileSelect(file: File) {
|
||||
if (!file.type.startsWith("image/")) return
|
||||
if (!isValidImageFile(file)) return
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => setStore("iconUrl", e.target?.result as string)
|
||||
reader.onload = (e) => {
|
||||
setStore("iconUrl", e.target?.result as string)
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +101,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
|||
<div class="flex gap-3 items-start">
|
||||
<div class="relative">
|
||||
<div
|
||||
class="size-16 rounded-lg overflow-hidden border border-dashed transition-colors cursor-pointer"
|
||||
class="size-12 rounded-lg overflow-hidden border border-dashed transition-colors cursor-pointer"
|
||||
classList={{
|
||||
"border-text-interactive-base bg-surface-info-base/20": dragOver(),
|
||||
"border-border-base hover:border-border-strong": !dragOver(),
|
||||
|
|
@ -108,20 +111,13 @@ export function DialogEditProject(props: { project: LocalProject }) {
|
|||
onDragLeave={handleDragLeave}
|
||||
onClick={() => document.getElementById("icon-upload")?.click()}
|
||||
>
|
||||
<Show
|
||||
when={store.iconUrl}
|
||||
fallback={
|
||||
<div class="size-full flex items-center justify-center">
|
||||
<Avatar
|
||||
fallback={store.name || defaultName()}
|
||||
{...getAvatarColors(store.color)}
|
||||
class="size-full"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<img src={store.iconUrl} alt="Project icon" class="size-full object-cover" />
|
||||
</Show>
|
||||
<ProjectIcon
|
||||
name={store.name || defaultName()}
|
||||
projectId={props.project.id}
|
||||
iconUrl={store.iconUrl}
|
||||
iconColor={store.color}
|
||||
class="size-full"
|
||||
/>
|
||||
</div>
|
||||
<Show when={store.iconUrl}>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
import { createMemo, splitProps, type ComponentProps, type JSX } from "solid-js"
|
||||
import { Avatar } from "@opencode-ai/ui/avatar"
|
||||
import { getAvatarColors } from "@/context/layout"
|
||||
|
||||
const OPENCODE_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750"
|
||||
const OPENCODE_FAVICON_URL = "https://opencode.ai/favicon.svg"
|
||||
|
||||
export interface ProjectIconProps extends Omit<ComponentProps<"div">, "children"> {
|
||||
name: string
|
||||
iconUrl?: string
|
||||
iconColor?: string
|
||||
projectId?: string
|
||||
size?: "small" | "normal" | "large"
|
||||
}
|
||||
|
||||
export const isValidImageUrl = (url: string | undefined): boolean => {
|
||||
if (!url) return false
|
||||
if (url.startsWith("data:image/x-icon")) return false
|
||||
if (url.startsWith("data:image/vnd.microsoft.icon")) return false
|
||||
return true
|
||||
}
|
||||
|
||||
export const isValidImageFile = (file: File): boolean => {
|
||||
if (!file.type.startsWith("image/")) return false
|
||||
if (file.type === "image/x-icon" || file.type === "image/vnd.microsoft.icon") return false
|
||||
return true
|
||||
}
|
||||
|
||||
export const ProjectIcon = (props: ProjectIconProps) => {
|
||||
const [local, rest] = splitProps(props, [
|
||||
"name",
|
||||
"iconUrl",
|
||||
"iconColor",
|
||||
"projectId",
|
||||
"size",
|
||||
"class",
|
||||
"classList",
|
||||
"style",
|
||||
])
|
||||
const colors = createMemo(() => getAvatarColors(local.iconColor))
|
||||
const validSrc = createMemo(() => {
|
||||
if (local.projectId === OPENCODE_PROJECT_ID) return OPENCODE_FAVICON_URL
|
||||
return isValidImageUrl(local.iconUrl) ? local.iconUrl : undefined
|
||||
})
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
fallback={local.name}
|
||||
src={validSrc()}
|
||||
size={local.size}
|
||||
{...colors()}
|
||||
class={local.class}
|
||||
classList={local.classList}
|
||||
style={local.style as JSX.CSSProperties}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
[data-component="avatar"][data-size="small"] {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.65rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue