fix: workspace icon fix, fallback, clean structure
parent
1d8f764114
commit
2610fec62b
|
|
@ -3,15 +3,20 @@ 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 { getFilename } from "@opencode-ai/util/path"
|
||||
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
|
||||
|
||||
function getFilename(input: string) {
|
||||
const parts = input.split("/")
|
||||
return parts[parts.length - 1] || input
|
||||
}
|
||||
|
||||
export function DialogEditProject(props: { project: LocalProject }) {
|
||||
const dialog = useDialog()
|
||||
const globalSDK = useGlobalSDK()
|
||||
|
|
@ -29,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)
|
||||
}
|
||||
|
||||
|
|
@ -94,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(),
|
||||
|
|
@ -104,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,73 @@
|
|||
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 (isValidImageUrl(local.iconUrl)) {
|
||||
return local.iconUrl
|
||||
}
|
||||
if (local.projectId === OPENCODE_PROJECT_ID) {
|
||||
return OPENCODE_FAVICON_URL
|
||||
}
|
||||
return 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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ import { A, useNavigate, useParams } from "@solidjs/router"
|
|||
import { useLayout, getAvatarColors, LocalProject } from "@/context/layout"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
|
||||
import { Avatar } from "@opencode-ai/ui/avatar"
|
||||
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
|
|
@ -56,6 +55,7 @@ import { DialogSelectProvider } from "@/components/dialog-select-provider"
|
|||
import { DialogEditProject } from "@/components/dialog-edit-project"
|
||||
import { DialogSelectServer } from "@/components/dialog-select-server"
|
||||
import { useCommand, type CommandOption } from "@/context/command"
|
||||
import { ProjectIcon } from "@/components/project-icon"
|
||||
import { ConstrainDragXAxis } from "@/utils/solid-dnd"
|
||||
import { navStart } from "@/utils/perf"
|
||||
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
||||
|
|
@ -763,10 +763,12 @@ export default function Layout(props: ParentProps) {
|
|||
|
||||
return (
|
||||
<div class="relative size-5 shrink-0 rounded-sm">
|
||||
<Avatar
|
||||
fallback={name()}
|
||||
src={props.project.id === opencode ? "https://opencode.ai/favicon.svg" : props.project.icon?.url}
|
||||
{...getAvatarColors(props.project.icon?.color)}
|
||||
<ProjectIcon
|
||||
name={name()}
|
||||
projectId={props.project.id}
|
||||
iconUrl={props.project.icon?.url}
|
||||
iconColor={props.project.icon?.color}
|
||||
size="small"
|
||||
class={`size-full ${props.class ?? ""}`}
|
||||
style={
|
||||
notifications().length > 0 && props.notify ? { "-webkit-mask-image": mask, "mask-image": mask } : undefined
|
||||
|
|
|
|||
Loading…
Reference in New Issue