From 364e2d9f03412710e79f79a1d5d06f7545e91eb6 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:19:59 +0000 Subject: [PATCH] feat(app): support web image backgrounds --- packages/app/src/context/platform.tsx | 8 ++-- packages/app/src/entry.tsx | 37 +++++++++++++++++++ packages/app/src/i18n/ar.ts | 2 +- packages/app/src/i18n/br.ts | 2 +- packages/app/src/i18n/bs.ts | 2 +- packages/app/src/i18n/da.ts | 2 +- packages/app/src/i18n/de.ts | 2 +- packages/app/src/i18n/en.ts | 2 +- packages/app/src/i18n/es.ts | 2 +- packages/app/src/i18n/fr.ts | 2 +- packages/app/src/i18n/ja.ts | 2 +- packages/app/src/i18n/ko.ts | 2 +- packages/app/src/i18n/no.ts | 2 +- packages/app/src/i18n/pl.ts | 2 +- packages/app/src/i18n/ru.ts | 2 +- packages/app/src/i18n/th.ts | 2 +- packages/app/src/i18n/tr.ts | 2 +- packages/app/src/i18n/uk.ts | 2 +- packages/app/src/i18n/zh.ts | 2 +- packages/app/src/i18n/zht.ts | 2 +- packages/app/src/index.css | 24 ++++++++++++ .../app/src/utils/web-background-image.ts | 33 +++++++++++++++++ packages/desktop/src/renderer/index.tsx | 4 +- packages/desktop/src/renderer/styles.css | 18 --------- 24 files changed, 118 insertions(+), 42 deletions(-) create mode 100644 packages/app/src/utils/web-background-image.ts diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx index f511e94ec6..c9effb2fc1 100644 --- a/packages/app/src/context/platform.tsx +++ b/packages/app/src/context/platform.tsx @@ -112,16 +112,16 @@ type PlatformBase = { /** Read image from clipboard (desktop only) */ readClipboardImage?(): Promise - /** Load and apply the saved desktop background image. */ + /** Load and apply the saved app background image. */ loadBackgroundImage?(): Promise - /** Whether the desktop currently has a background image. */ + /** Whether the app currently has a background image. */ backgroundImage?: Accessor - /** Select and apply a desktop background image. */ + /** Select and apply an app background image. */ selectBackgroundImage?(): Promise - /** Clear the saved desktop background image. */ + /** Clear the saved app background image. */ clearBackgroundImage?(): Promise /** Export collected diagnostic logs (desktop only) */ diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx index c41c1bb4b6..96c092bf6b 100644 --- a/packages/app/src/entry.tsx +++ b/packages/app/src/entry.tsx @@ -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, diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index ca8d0b1fec..397fcbd7ff 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -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": "خط الواجهة", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 49a95f5cc8..019221d36c 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -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", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index 1961072084..463e58f0a8 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -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", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 7b068e3da6..2d9b339bba 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -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", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index 82d87e20db..cdf3f04670 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -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", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 42680e1202..f99d5b61af 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -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", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index cccdb2e9f0..91c3b9bf9c 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -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", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 249e04ff27..67b13eb091 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -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", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 7dfd031f73..79d036cb9a 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -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フォント", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index e6ecae4c9f..610ad04ca3 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -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 글꼴", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 8ab2b32424..3d1484fd7a 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -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", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 8f01e698b9..1a4a35de1c 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -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", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 3e2518b5be..16465b75a6 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -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": "Шрифт интерфейса", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 352b2e01a3..865579ef74 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -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", diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index fb13377fd4..c733af6cde 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -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", diff --git a/packages/app/src/i18n/uk.ts b/packages/app/src/i18n/uk.ts index 4c35ca9c39..1b558dd8d2 100644 --- a/packages/app/src/i18n/uk.ts +++ b/packages/app/src/i18n/uk.ts @@ -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": "Шрифт інтерфейсу", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 84eeae9843..cfb686b71a 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -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": "界面字体", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 5e5be498bb..8a1c6fdfa1 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -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": "介面字型", diff --git a/packages/app/src/index.css b/packages/app/src/index.css index 78a9e103a7..08da436ae9 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -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"); diff --git a/packages/app/src/utils/web-background-image.ts b/packages/app/src/utils/web-background-image.ts new file mode 100644 index 0000000000..89e497cf17 --- /dev/null +++ b/packages/app/src/utils/web-background-image.ts @@ -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((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() + }) +} diff --git a/packages/desktop/src/renderer/index.tsx b/packages/desktop/src/renderer/index.tsx index e178173d01..55f164d8e8 100644 --- a/packages/desktop/src/renderer/index.tsx +++ b/packages/desktop/src/renderer/index.tsx @@ -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) diff --git a/packages/desktop/src/renderer/styles.css b/packages/desktop/src/renderer/styles.css index 0b485ed0f3..e69de29bb2 100644 --- a/packages/desktop/src/renderer/styles.css +++ b/packages/desktop/src/renderer/styles.css @@ -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; -}