mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 12:10:45 +03:00
feat(app): switch project avatars between letters and generated from Knobs (#57930)
This commit is contained in:
committed by
GitHub
Unverified
parent
565a2e3a33
commit
0fccb330e1
@@ -328,7 +328,7 @@ test.describe("touch", () => {
|
||||
await projects.click()
|
||||
const picker = page.getByRole("dialog", { name: "Projects", exact: true })
|
||||
await expect(picker.getByRole("button", { name: "All projects", exact: true })).toBeVisible()
|
||||
await picker.getByRole("button", { name: fixture.project.name, exact: true }).click()
|
||||
await picker.getByRole("button", { name: new RegExp(` ${fixture.project.name}$`) }).click()
|
||||
await expect(picker).toBeHidden()
|
||||
await expect(projects).toContainText(fixture.project.name)
|
||||
|
||||
|
||||
@@ -372,7 +372,6 @@ test("icon-only inactive tabs retain their avatar and attention badge instead of
|
||||
const tabB = page.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(b.id)}"])`)
|
||||
await expect(tabB.locator('[data-slot="tab-title"]')).toBeHidden()
|
||||
await expect(tabB.locator('[data-component="project-avatar-v2"]')).toBeVisible()
|
||||
await expect(tabB.locator('[data-slot="project-avatar-generated"]')).toBeVisible()
|
||||
await expect(tabB.locator('[data-slot="tab-avatar-unread-dot"]')).toBeVisible()
|
||||
await expect(tabB.locator('[data-slot="tab-avatar-unread-dot"]')).toHaveCSS("width", "6px")
|
||||
await expect(tabB.locator('[data-slot="tab-avatar-unread-dot"]')).toHaveCSS("height", "6px")
|
||||
|
||||
@@ -25,6 +25,13 @@ Bun.plugin({
|
||||
|
||||
const { ProjectAvatar, ProjectIconStoreProvider, generateProjectIcon } = await import("@opencode/ui/project-avatar")
|
||||
|
||||
const { motionTuning } = await import("@opencode/ui/motion-tuning")
|
||||
|
||||
// Generated avatars are opt-in under Knobs → Experiments.
|
||||
motionTuning.list().forEach((knob) => {
|
||||
if (knob.id === "projectAvatar.style") motionTuning.set(knob, "generated")
|
||||
})
|
||||
|
||||
const getContext = HTMLCanvasElement.prototype.getContext
|
||||
|
||||
const requestFrame = window.requestAnimationFrame
|
||||
|
||||
@@ -4,11 +4,13 @@ import {
|
||||
createEffect,
|
||||
createMemo,
|
||||
onCleanup,
|
||||
createSignal,
|
||||
onMount,
|
||||
splitProps,
|
||||
Show,
|
||||
useContext,
|
||||
} from "solid-js"
|
||||
import { motionTuning } from "../../motion-tuning"
|
||||
import { attachGeneratedAvatar } from "./animation"
|
||||
import {
|
||||
generateProjectIcon,
|
||||
@@ -19,6 +21,23 @@ import "./project-avatar.css"
|
||||
|
||||
export { generateProjectIcon, GENERATED_PROJECT_ICON_STYLE, type GeneratedProjectIcon } from "./generated"
|
||||
|
||||
const avatarChoice = motionTuning.choice("projectAvatar.style", {
|
||||
label: "Project avatars",
|
||||
group: "Experiments",
|
||||
value: "letters",
|
||||
labels: { letters: "Letters", generated: "Generated" },
|
||||
})
|
||||
|
||||
const [avatarStyle, setAvatarStyle] = createSignal(avatarChoice())
|
||||
|
||||
// A live switch crossfades the avatars through a view transition; nothing else on the page changes.
|
||||
motionTuning.subscribe(() => {
|
||||
if (avatarChoice() === avatarStyle()) return
|
||||
|
||||
if (typeof document === "undefined" || !document.startViewTransition) return setAvatarStyle(avatarChoice())
|
||||
document.startViewTransition(() => setAvatarStyle(avatarChoice()))
|
||||
})
|
||||
|
||||
const segmenter =
|
||||
typeof Intl !== "undefined" && "Segmenter" in Intl
|
||||
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
||||
@@ -142,7 +161,8 @@ export function ProjectAvatar(props: ProjectAvatarProps) {
|
||||
"style",
|
||||
])
|
||||
|
||||
const generated = () => (split.src || split.variant === "outline" ? undefined : split.generated)
|
||||
const generated = () =>
|
||||
split.src || split.variant === "outline" || avatarStyle() === "letters" ? undefined : split.generated
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user