feat(app): support web image backgrounds

This commit is contained in:
Brendan Allan
2026-07-20 16:19:59 +00:00
parent 35fcf1ed58
commit 364e2d9f03
24 changed files with 118 additions and 42 deletions
+4 -4
View File
@@ -112,16 +112,16 @@ type PlatformBase = {
/** Read image from clipboard (desktop only) */
readClipboardImage?(): Promise<File | null>
/** Load and apply the saved desktop background image. */
/** Load and apply the saved app background image. */
loadBackgroundImage?(): Promise<boolean>
/** Whether the desktop currently has a background image. */
/** Whether the app currently has a background image. */
backgroundImage?: Accessor<boolean>
/** Select and apply a desktop background image. */
/** Select and apply an app background image. */
selectBackgroundImage?(): Promise<boolean>
/** Clear the saved desktop background image. */
/** Clear the saved app background image. */
clearBackgroundImage?(): Promise<void>
/** Export collected diagnostic logs (desktop only) */
+37
View File
@@ -1,6 +1,7 @@
// @refresh reload
import * as Sentry from "@sentry/solid"
import { createSignal } from "solid-js"
import { render } from "solid-js/web"
import { AppBaseProviders, AppInterface } from "@/app"
import { type Platform, PlatformProvider } from "@/context/platform"
@@ -8,6 +9,12 @@ import { dict as en } from "@/i18n/en"
import { dict as zh } from "@/i18n/zh"
import { handleNotificationClick } from "@/utils/notification-click"
import { authFromToken } from "@/utils/server"
import {
clearWebBackgroundImage,
loadWebBackgroundImage,
saveWebBackgroundImage,
selectWebBackgroundImage,
} from "@/utils/web-background-image"
import pkg from "../package.json"
import { ServerConnection } from "./context/server"
@@ -119,6 +126,21 @@ const clearAuthToken = () => {
history.replaceState(null, "", location.pathname + (params.size ? `?${params}` : "") + location.hash)
}
const [backgroundImage, setBackgroundImage] = createSignal(false)
let backgroundImageUrl: string | undefined
const applyBackgroundImage = (image: Blob | null) => {
if (backgroundImageUrl) URL.revokeObjectURL(backgroundImageUrl)
backgroundImageUrl = image ? URL.createObjectURL(image) : undefined
setBackgroundImage(!!backgroundImageUrl)
document.documentElement.toggleAttribute("data-background-image", !!backgroundImageUrl)
if (backgroundImageUrl) {
document.documentElement.style.setProperty("--app-background-image", `url("${backgroundImageUrl}")`)
return true
}
document.documentElement.style.removeProperty("--app-background-image")
return false
}
const platform: Platform = {
platform: "web",
version: pkg.version,
@@ -132,8 +154,23 @@ const platform: Platform = {
return stored ? ServerConnection.Key.make(stored) : null
},
setDefaultServer: writeDefaultServerUrl,
backgroundImage,
async loadBackgroundImage() {
return applyBackgroundImage(await loadWebBackgroundImage())
},
async selectBackgroundImage() {
const file = await selectWebBackgroundImage()
if (!file) return backgroundImage()
return applyBackgroundImage(await saveWebBackgroundImage(file))
},
async clearBackgroundImage() {
await clearWebBackgroundImage()
applyBackgroundImage(null)
},
}
void platform.loadBackgroundImage?.()
if (import.meta.env.VITE_SENTRY_DSN) {
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
+1 -1
View File
@@ -705,7 +705,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "خط الطرفية",
"settings.general.row.terminalFont.description": "خصّص الخط المستخدم في الطرفية",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "خط الواجهة",
+1 -1
View File
@@ -714,7 +714,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "Fonte do terminal",
"settings.general.row.terminalFont.description": "Personalize a fonte usada no terminal",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "Fonte da interface",
+1 -1
View File
@@ -779,7 +779,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "Font terminala",
"settings.general.row.terminalFont.description": "Prilagodite font koji se koristi u terminalu",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "UI font",
+1 -1
View File
@@ -774,7 +774,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "Terminalskrifttype",
"settings.general.row.terminalFont.description": "Tilpas den skrifttype, der bruges i terminalen",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "UI-skrifttype",
+1 -1
View File
@@ -725,7 +725,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "Terminalschriftart",
"settings.general.row.terminalFont.description": "Passe die im Terminal verwendete Schriftart an",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "UI-Schriftart",
+1 -1
View File
@@ -863,7 +863,7 @@ export const dict = {
"settings.general.row.theme.title": "Theme",
"settings.general.row.theme.description": "Customise how OpenCode is themed.",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.font.title": "Code Font",
+1 -1
View File
@@ -782,7 +782,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "Fuente del terminal",
"settings.general.row.terminalFont.description": "Personaliza la fuente utilizada en el terminal",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "Fuente de la interfaz",
+1 -1
View File
@@ -721,7 +721,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "Police du terminal",
"settings.general.row.terminalFont.description": "Personnalisez la police utilisée dans le terminal",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "Police de l'interface",
+1 -1
View File
@@ -710,7 +710,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "ターミナルのフォント",
"settings.general.row.terminalFont.description": "ターミナルで使用するフォントをカスタマイズ",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "UIフォント",
+1 -1
View File
@@ -581,7 +581,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "터미널 글꼴",
"settings.general.row.terminalFont.description": "터미널에서 사용할 글꼴을 설정합니다",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "UI 글꼴",
+1 -1
View File
@@ -655,7 +655,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "Terminalskrift",
"settings.general.row.terminalFont.description": "Tilpass skrifttypen som brukes i terminalen",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "UI-skrift",
+1 -1
View File
@@ -715,7 +715,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "Czcionka terminala",
"settings.general.row.terminalFont.description": "Dostosuj czcionkę używaną w terminalu",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "Czcionka interfejsu",
+1 -1
View File
@@ -779,7 +779,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "Шрифт терминала",
"settings.general.row.terminalFont.description": "Настройте шрифт, используемый в терминале",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "Шрифт интерфейса",
+1 -1
View File
@@ -772,7 +772,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "ฟอนต์เทอร์มินัล",
"settings.general.row.terminalFont.description": "ปรับแต่งฟอนต์ที่ใช้ในเทอร์มินัล",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "ฟอนต์ UI",
+1 -1
View File
@@ -785,7 +785,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "Terminal yazı tipi",
"settings.general.row.terminalFont.description": "Terminalde kullanılan yazı tipini özelleştirin",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "Arayüz Yazı Tipi",
+1 -1
View File
@@ -871,7 +871,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "Шрифт термінала",
"settings.general.row.terminalFont.description": "Налаштуйте шрифт, який використовується в терміналі",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "Шрифт інтерфейсу",
+1 -1
View File
@@ -769,7 +769,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "终端字体",
"settings.general.row.terminalFont.description": "自定义终端使用的字体",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "界面字体",
+1 -1
View File
@@ -764,7 +764,7 @@ export const dict = {
"settings.general.row.terminalFont.title": "終端機字型",
"settings.general.row.terminalFont.description": "自訂終端機使用的字型",
"settings.general.row.backgroundImage.title": "Background image",
"settings.general.row.backgroundImage.description": "Choose an image for the desktop background.",
"settings.general.row.backgroundImage.description": "Choose an image for the app background.",
"settings.general.row.backgroundImage.choose": "Choose image",
"settings.general.row.backgroundImage.remove": "Remove",
"settings.general.row.uiFont.title": "介面字型",
+24
View File
@@ -3,6 +3,30 @@
@import "@opencode-ai/ui/v2/styles/tailwind.css";
@import "tw-animate-css";
html[data-background-image] body {
background-image: linear-gradient(rgb(0 0 0 / 12%), rgb(0 0 0 / 12%)), var(--app-background-image);
background-color: transparent !important;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
html[data-background-image] #root {
background-color: transparent !important;
}
html[data-background-image] [data-background-surface="shell"] {
background-color: transparent !important;
}
html[data-background-image] [data-background-surface="content"] {
background-color: color-mix(in srgb, var(--background-base) 68%, transparent) !important;
}
html[data-background-image] [data-background-surface="panel"] {
background-color: color-mix(in srgb, var(--v2-background-bg-base) 56%, transparent) !important;
}
@font-face {
font-family: "JetBrainsMono Nerd Font Mono";
src: url("/assets/JetBrainsMonoNerdFontMono-Regular.woff2") format("woff2");
@@ -0,0 +1,33 @@
const cacheName = "opencode-background-image-v1"
const maxBytes = 20 * 1024 * 1024
function key() {
return new URL("/__opencode/background-image", location.origin).toString()
}
export async function loadWebBackgroundImage() {
const response = await (await caches.open(cacheName)).match(key())
return response?.blob() ?? null
}
export async function saveWebBackgroundImage(file: File) {
if (!file.type.startsWith("image/")) throw new Error("Unsupported background image format")
if (file.size > maxBytes) throw new Error("Background images must be 20 MB or smaller")
await (await caches.open(cacheName)).put(key(), new Response(file, { headers: { "Content-Type": file.type } }))
return file
}
export async function clearWebBackgroundImage() {
await (await caches.open(cacheName)).delete(key())
}
export function selectWebBackgroundImage() {
return new Promise<File | null>((resolve) => {
const input = document.createElement("input")
input.type = "file"
input.accept = "image/avif,image/bmp,image/gif,image/jpeg,image/png,image/webp"
input.onchange = () => resolve(input.files?.[0] ?? null)
input.oncancel = () => resolve(null)
input.click()
})
}
+2 -2
View File
@@ -118,12 +118,12 @@ const createPlatform = (windowState: DesktopWindowState): Platform => {
document.documentElement.toggleAttribute("data-background-image", !!image)
if (image) {
document.documentElement.style.setProperty(
"--desktop-background-image",
"--app-background-image",
`url("oc://renderer/background-image?revision=${encodeURIComponent(image.revision)}")`,
)
return true
}
document.documentElement.style.removeProperty("--desktop-background-image")
document.documentElement.style.removeProperty("--app-background-image")
return false
}
window.api.onBackgroundImageChanged(applyBackgroundImage)
-18
View File
@@ -1,18 +0,0 @@
html[data-background-image] body {
background-image: linear-gradient(rgb(0 0 0 / 18%), rgb(0 0 0 / 18%)), var(--desktop-background-image);
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
html[data-background-image] [data-background-surface="shell"] {
background-color: transparent !important;
}
html[data-background-image] [data-background-surface="content"] {
background-color: color-mix(in srgb, var(--background-base) 76%, transparent) !important;
}
html[data-background-image] [data-background-surface="panel"] {
background-color: color-mix(in srgb, var(--v2-background-bg-base) 70%, transparent) !important;
}