diff --git a/public/devices/README.md b/public/devices/README.md index d8e0889f..ce8f7107 100644 --- a/public/devices/README.md +++ b/public/devices/README.md @@ -274,3 +274,14 @@ package: **Needs a maintainer upload** to the `openmouse-devices` R2 bucket; the mapping in `src/ui/device-images.ts` is in place and falls back cleanly to the placeholder until then. + +`unknown-keyboard.png` is the keyboard placeholder, the keyboard twin of +`unknown-device.png`: a flat black silhouette with a white Arial Bold question +mark, on a transparent 1428×716 canvas. Every device labelled a keyboard that +has no art of its own resolves to it, and keyboard images fall back to it on a +load error. Only the outline derives from a source image, a transparent pngtree +stock render of a 60% keyboard (`pngtree-compact-60-mechanical-gaming-keyboard-with-retro-carbon-pbt-keycaps-png-image_18714675.webp`); +no colour, keycaps, legends or shading carried over. It is committed at +`public/devices/unknown-keyboard.png` and served from the repo until a +maintainer uploads it to the bucket; then drop its `LOCAL_OVERRIDES` entry in +`src/shared/device-images.ts` and point the tests at `CDN + "unknown-keyboard.png"`. diff --git a/public/devices/keychron-keyboard.svg b/public/devices/keychron-keyboard.svg deleted file mode 100644 index 4d149bea..00000000 --- a/public/devices/keychron-keyboard.svg +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/devices/unknown-keyboard.png b/public/devices/unknown-keyboard.png new file mode 100644 index 00000000..de43f8a2 Binary files /dev/null and b/public/devices/unknown-keyboard.png differ diff --git a/src/app/AppSidebar.tsx b/src/app/AppSidebar.tsx index 957987dc..4aff2351 100644 --- a/src/app/AppSidebar.tsx +++ b/src/app/AppSidebar.tsx @@ -14,7 +14,7 @@ import { import { memo, useCallback, type ReactNode } from "react"; import type { ControlSnapshot, SidebarDevice } from "../device/types"; import type { MouseStatus } from "@openmouse/protocol/drivers/mouse-types"; -import { deviceImage } from "../shared/device-images"; +import { deviceImage, placeholderImage } from "../shared/device-images"; import { BridgeStatusButton } from "../features/bridge/BridgeStatus"; import { BatteryIcon } from "../shared/ui"; import { useUiLocale } from "../shared/ui-locale"; @@ -82,7 +82,7 @@ const SidebarDeviceRow = memo(function SidebarDeviceRow({ localeTableRevision: number; }): ReactNode { const locale = useUiLocale(); - const image = deviceImage({ vendorId: device.vendorId, productId: device.productId } as HIDDevice, device.name); + const image = deviceImage({ vendorId: device.vendorId, productId: device.productId } as HIDDevice, device.name, device.kind); const openDevice = useCallback(() => open(device.index), [open, device.index]); return (
  • @@ -100,7 +100,7 @@ const SidebarDeviceRow = memo(function SidebarDeviceRow({ draggable={false} onError={(event) => { event.currentTarget.onerror = null; - event.currentTarget.src = deviceImage(null); + event.currentTarget.src = placeholderImage(device.kind); }} /> {live ? : null} diff --git a/src/features/devices/DeviceHero.tsx b/src/features/devices/DeviceHero.tsx index 37e080fd..58da7530 100644 --- a/src/features/devices/DeviceHero.tsx +++ b/src/features/devices/DeviceHero.tsx @@ -4,8 +4,7 @@ import { useMemo, useState, type KeyboardEvent, type ReactNode } from "react"; import * as control from "../../device/controller"; import { cachedBatterySamples, type BatterySample } from "./battery-history"; import type { ControlSnapshot, WorkspaceTab } from "../../device/types"; -import { deviceImage, isUnknownDevice } from "../../shared/device-images"; -import { KeyboardArt } from "../../shared/KeyboardArt"; +import { deviceImage, isUnknownDevice, placeholderImage } from "../../shared/device-images"; import { ConnectionIcon } from "./DeviceStats"; const ARTWORK_DISCUSSION_URL = @@ -31,10 +30,9 @@ export function DeviceHero({ const active = control.getActiveDevice(); const activeEntry = snapshot.devices.find((device) => device.selected) ?? null; const name = status?.name ?? keyboard?.name ?? activeEntry?.name ?? ""; - // Arbiter keyboards have no product render; draw a keyboard rather than the unknown-device mouse. - const keyboardArt = keyboard?.family === "arbiter" && !snapshot.deviceArtwork; + // A keyboard with no render of its own gets the keyboard placeholder, never the mouse one. const image = snapshot.deviceArtwork - ?? (keyboard && !keyboardArt ? deviceImage({ vendorId: keyboard.vendorId, productId: keyboard.productId } as HIDDevice, keyboard.name) : null); + ?? (keyboard ? deviceImage({ vendorId: keyboard.vendorId, productId: keyboard.productId } as HIDDevice, keyboard.name, "keyboard") : null); const needsArtwork = Boolean(status && active && (imageFailed || isUnknownDevice( { vendorId: active.vendorId, productId: active.productId } as HIDDevice, status.name, @@ -84,13 +82,12 @@ export function DeviceHero({ draggable={false} onError={(event) => { event.currentTarget.onerror = null; - event.currentTarget.src = deviceImage(null); + event.currentTarget.src = placeholderImage(keyboard && !snapshot.deviceArtwork ? "keyboard" : "mouse"); setImageFailed(true); }} /> ) : null} - {keyboardArt ?
    : null} {needsArtwork ? (