diff --git a/apps/desktop/electron-builder.ts b/apps/desktop/electron-builder.ts index 8b08ddd17ba..43ff1976736 100644 --- a/apps/desktop/electron-builder.ts +++ b/apps/desktop/electron-builder.ts @@ -18,6 +18,7 @@ const productName = pkg.productName; const macIconPath = join(pkg.resources, "build/icons/icon.icns"); const linuxIconPath = join(pkg.resources, "build/icons"); const winIconPath = join(pkg.resources, "build/icons/icon.ico"); +const dmgBackgroundPath = join(pkg.resources, "build/installer/background.tiff"); const config: Configuration = { appId: "com.superset.desktop", @@ -86,6 +87,11 @@ const config: Configuration = { // Rebuild native modules for Electron's Node.js version npmRebuild: true, + // macOS DMG installer + dmg: { + ...(existsSync(dmgBackgroundPath) ? { background: dmgBackgroundPath } : {}), + }, + // macOS mac: { ...(existsSync(macIconPath) ? { icon: macIconPath } : {}), diff --git a/apps/desktop/src/main/lib/dock-icon.ts b/apps/desktop/src/main/lib/dock-icon.ts index c40b48df31f..b6660dcc855 100644 --- a/apps/desktop/src/main/lib/dock-icon.ts +++ b/apps/desktop/src/main/lib/dock-icon.ts @@ -1,239 +1,57 @@ +import { existsSync } from "node:fs"; import { join } from "node:path"; import { app, nativeImage } from "electron"; import { env } from "main/env.main"; -import { getWorkspaceName } from "shared/env.shared"; -import twColors from "tailwindcss/colors"; +import { prerelease } from "semver"; /** - * Deterministic hash of a string, returned as a non-negative integer. + * Returns true if this is a canary (prerelease) build, e.g. "0.0.53-canary". */ -function hashString(seed: string): number { - let hash = 0; - for (let i = 0; i < seed.length; i++) { - hash = seed.charCodeAt(i) + ((hash << 5) - hash); - hash |= 0; - } - return Math.abs(hash); -} - -/** - * Parses an OKLCH CSS string like "oklch(63.7% 0.237 25.331)". - */ -function parseOklch(str: string): { l: number; c: number; h: number } | null { - const match = str.match(/oklch\(([\d.]+)%\s+([\d.]+)\s+([\d.]+)\)/); - if (!match) return null; - return { - l: Number(match[1]) / 100, - c: Number(match[2]), - h: Number(match[3]), - }; -} - -/** - * Converts OKLCH to sRGB (all values 0-255). - */ -function oklchToRgb(l: number, c: number, h: number): [number, number, number] { - const hRad = (h * Math.PI) / 180; - const a = c * Math.cos(hRad); - const b = c * Math.sin(hRad); - - // OKLab → LMS (cube-root space) - const l_ = l + 0.3963377774 * a + 0.2158037573 * b; - const m_ = l - 0.1055613458 * a - 0.0638541728 * b; - const s_ = l - 0.0894841775 * a - 1.291485548 * b; - - const lc = l_ * l_ * l_; - const mc = m_ * m_ * m_; - const sc = s_ * s_ * s_; - - // LMS → linear sRGB - const rLin = +4.0767416621 * lc - 3.3077115913 * mc + 0.2309699292 * sc; - const gLin = -1.2684380046 * lc + 2.6097574011 * mc - 0.3413193965 * sc; - const bLin = -0.0041960863 * lc + 0.7034186147 * mc + 0.2967775076 * sc; - - const toSrgb = (v: number) => { - const clamped = Math.max(0, Math.min(1, v)); - return clamped <= 0.0031308 - ? 12.92 * clamped - : 1.055 * clamped ** (1 / 2.4) - 0.055; - }; - - return [ - Math.round(toSrgb(rLin) * 255), - Math.round(toSrgb(gLin) * 255), - Math.round(toSrgb(bLin) * 255), - ]; +function isCanaryBuild(): boolean { + const components = prerelease(app.getVersion()); + return components !== null && components.length > 0; } -/** All Tailwind 500-level colors as RGB tuples. */ -const TAILWIND_500_COLORS: [number, number, number][] = (() => { - const skip = new Set(["inherit", "current", "transparent", "black", "white"]); - const result: [number, number, number][] = []; - for (const [name, val] of Object.entries(twColors)) { - if (skip.has(name) || typeof val !== "object" || !("500" in val)) continue; - const parsed = parseOklch((val as Record)["500"] as string); - if (parsed) result.push(oklchToRgb(parsed.l, parsed.c, parsed.h)); - } - return result; -})(); - /** - * Gets the path to the app icon PNG. + * Returns the icons directory path. */ -function getIconPath(): string { +function getIconsDir(): string { if (app.isPackaged) { - return join( - process.resourcesPath, - "app.asar/resources/build/icons/icon.png", - ); + return join(process.resourcesPath, "app.asar/resources/build/icons"); } - if (env.NODE_ENV === "development") { - return join(app.getAppPath(), "src/resources/build/icons/icon.png"); + return join(app.getAppPath(), "src/resources/build/icons"); } - - return join(__dirname, "../resources/build/icons/icon.png"); + return join(__dirname, "../resources/build/icons"); } /** - * Signed distance function for a rounded rectangle. - * Negative = inside, positive = outside, zero = on boundary. + * Resolves the dock icon path for the current build type. + * Resolution order (first existing file wins): + * - dev: icon-dev.png → icon.png + * - canary: icon-canary.png → icon.png + * - stable: icon.png */ -function sdfRoundedRect( - px: number, - py: number, - left: number, - top: number, - right: number, - bottom: number, - radius: number, -): number { - const cx = (left + right) / 2; - const cy = (top + bottom) / 2; - const halfW = (right - left) / 2; - const halfH = (bottom - top) / 2; - - const dx = Math.abs(px - cx) - halfW + radius; - const dy = Math.abs(py - cy) - halfH + radius; - - return ( - Math.sqrt(Math.max(dx, 0) ** 2 + Math.max(dy, 0) ** 2) + - Math.min(Math.max(dx, dy), 0) - - radius - ); -} - -/** - * Finds the bounding box of non-transparent pixels in a bitmap. - */ -function findContentBounds( - bitmap: Buffer, - width: number, - height: number, -): { top: number; left: number; bottom: number; right: number } { - let top = height; - let left = width; - let bottom = 0; - let right = 0; +function getIconPath(): string { + const dir = getIconsDir(); - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - if ((bitmap[(y * width + x) * 4 + 3] ?? 0) > 10) { - if (y < top) top = y; - if (y > bottom) bottom = y; - if (x < left) left = x; - if (x > right) right = x; - } - } + if (env.NODE_ENV === "development") { + const devIcon = join(dir, "icon-dev.png"); + if (existsSync(devIcon)) return devIcon; + } else if (isCanaryBuild()) { + const canaryIcon = join(dir, "icon-canary.png"); + if (existsSync(canaryIcon)) return canaryIcon; } - return { top, left, bottom, right }; + return join(dir, "icon.png"); } /** - * Draws a rounded-rectangle border on raw RGBA bitmap data. - * The border is drawn inward from the specified bounds. - */ -function drawBorder({ - bitmap, - width, - thickness, - left, - top, - right, - bottom, - cornerRadius, - rgb, -}: { - bitmap: Buffer; - width: number; - thickness: number; - left: number; - top: number; - right: number; - bottom: number; - cornerRadius: number; - rgb: [number, number, number]; -}) { - const innerRadius = Math.max(0, cornerRadius - thickness); - - for (let y = top; y <= bottom; y++) { - for (let x = left; x <= right; x++) { - const outerDist = sdfRoundedRect( - x, - y, - left, - top, - right, - bottom, - cornerRadius, - ); - const innerDist = sdfRoundedRect( - x, - y, - left + thickness, - top + thickness, - right - thickness, - bottom - thickness, - innerRadius, - ); - - // Anti-aliased edges - const outerAlpha = Math.max(0, Math.min(1, 0.5 - outerDist)); - const innerAlpha = Math.max(0, Math.min(1, innerDist + 0.5)); - const borderAlpha = outerAlpha * innerAlpha; - - if (borderAlpha > 0.001) { - const offset = (y * width + x) * 4; - const r = bitmap[offset] ?? 0; - const g = bitmap[offset + 1] ?? 0; - const b = bitmap[offset + 2] ?? 0; - const a = bitmap[offset + 3] ?? 0; - bitmap[offset] = Math.round( - rgb[0] * borderAlpha + r * (1 - borderAlpha), - ); - bitmap[offset + 1] = Math.round( - rgb[1] * borderAlpha + g * (1 - borderAlpha), - ); - bitmap[offset + 2] = Math.round( - rgb[2] * borderAlpha + b * (1 - borderAlpha), - ); - bitmap[offset + 3] = Math.max(a, Math.round(borderAlpha * 255)); - } - } - } -} - -/** - * Sets the macOS dock icon with a colored border based on the workspace name. - * No-op on non-macOS platforms or when no workspace name is set. + * Sets the macOS dock icon based on the current build type and macOS version. + * No-op on non-macOS platforms. */ export function setWorkspaceDockIcon(): void { if (process.platform !== "darwin") return; - if (env.NODE_ENV !== "development") return; - - const workspaceName = getWorkspaceName(); - if (!workspaceName) return; try { const iconPath = getIconPath(); @@ -243,41 +61,8 @@ export function setWorkspaceDockIcon(): void { return; } - const size = icon.getSize(); - const bitmap = icon.toBitmap(); - - const hash = hashString(workspaceName); - const rgb = - TAILWIND_500_COLORS[hash % TAILWIND_500_COLORS.length] ?? - ([59, 130, 246] as [number, number, number]); // blue-500 fallback - - // Find the actual icon content area (skip transparent padding) - const bounds = findContentBounds(bitmap, size.width, size.height); - const thickness = Math.round(size.width * 0.038); - const cornerRadius = Math.round(size.width * 0.22); - - // Draw border flush with the content edges, overlapping inward - drawBorder({ - bitmap, - width: size.width, - thickness, - left: bounds.left, - top: bounds.top, - right: bounds.right, - bottom: bounds.bottom, - cornerRadius, - rgb, - }); - - const newIcon = nativeImage.createFromBitmap(bitmap, { - width: size.width, - height: size.height, - }); - - app.dock?.setIcon(newIcon); - console.log( - `[dock-icon] Set workspace dock icon border rgb(${rgb.join(",")}) for "${workspaceName}"`, - ); + app.dock?.setIcon(icon); + console.log(`[dock-icon] Set dock icon from: ${iconPath}`); } catch (error) { console.error("[dock-icon] Failed to set dock icon:", error); } diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/assets/superset-empty-state-wordmark.svg b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/assets/superset-empty-state-wordmark.svg index 421ac8d56e7..df3f5c70930 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/assets/superset-empty-state-wordmark.svg +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/assets/superset-empty-state-wordmark.svg @@ -1,3 +1,13 @@ - - + + + + + + + + + + + + diff --git a/apps/desktop/src/resources/build/icons/icon-canary.icns b/apps/desktop/src/resources/build/icons/icon-canary.icns index 9979c557092..c05bb9d2996 100644 Binary files a/apps/desktop/src/resources/build/icons/icon-canary.icns and b/apps/desktop/src/resources/build/icons/icon-canary.icns differ diff --git a/apps/desktop/src/resources/build/icons/icon-canary.ico b/apps/desktop/src/resources/build/icons/icon-canary.ico index 70a4daa1ef9..e5bcff052fc 100644 Binary files a/apps/desktop/src/resources/build/icons/icon-canary.ico and b/apps/desktop/src/resources/build/icons/icon-canary.ico differ diff --git a/apps/desktop/src/resources/build/icons/icon-canary.png b/apps/desktop/src/resources/build/icons/icon-canary.png index b3f3337e44a..5cfab3f3ba7 100644 Binary files a/apps/desktop/src/resources/build/icons/icon-canary.png and b/apps/desktop/src/resources/build/icons/icon-canary.png differ diff --git a/apps/desktop/src/resources/build/icons/icon-dev.icns b/apps/desktop/src/resources/build/icons/icon-dev.icns new file mode 100644 index 00000000000..ab9acd9b682 Binary files /dev/null and b/apps/desktop/src/resources/build/icons/icon-dev.icns differ diff --git a/apps/desktop/src/resources/build/icons/icon-dev.ico b/apps/desktop/src/resources/build/icons/icon-dev.ico new file mode 100644 index 00000000000..e5bc7f9f6ce Binary files /dev/null and b/apps/desktop/src/resources/build/icons/icon-dev.ico differ diff --git a/apps/desktop/src/resources/build/icons/icon-dev.png b/apps/desktop/src/resources/build/icons/icon-dev.png new file mode 100644 index 00000000000..6e8a2b8cde3 Binary files /dev/null and b/apps/desktop/src/resources/build/icons/icon-dev.png differ diff --git a/apps/desktop/src/resources/build/icons/icon.icns b/apps/desktop/src/resources/build/icons/icon.icns index 27082bc1a5c..c4249b96cda 100644 Binary files a/apps/desktop/src/resources/build/icons/icon.icns and b/apps/desktop/src/resources/build/icons/icon.icns differ diff --git a/apps/desktop/src/resources/build/icons/icon.ico b/apps/desktop/src/resources/build/icons/icon.ico index aea4682ff7f..2f2babb1520 100644 Binary files a/apps/desktop/src/resources/build/icons/icon.ico and b/apps/desktop/src/resources/build/icons/icon.ico differ diff --git a/apps/desktop/src/resources/build/icons/icon.png b/apps/desktop/src/resources/build/icons/icon.png index 3b6373301d0..f83930055c5 100644 Binary files a/apps/desktop/src/resources/build/icons/icon.png and b/apps/desktop/src/resources/build/icons/icon.png differ diff --git a/apps/desktop/src/resources/build/installer/background.tiff b/apps/desktop/src/resources/build/installer/background.tiff new file mode 100644 index 00000000000..fe6576d55d8 Binary files /dev/null and b/apps/desktop/src/resources/build/installer/background.tiff differ diff --git a/apps/desktop/src/resources/tray/iconTemplate.png b/apps/desktop/src/resources/tray/iconTemplate.png index c62c39e0717..463d63e71e2 100644 Binary files a/apps/desktop/src/resources/tray/iconTemplate.png and b/apps/desktop/src/resources/tray/iconTemplate.png differ diff --git a/apps/marketing/public/favicon-192.png b/apps/marketing/public/favicon-192.png index c3d37155891..94b86ed1106 100644 Binary files a/apps/marketing/public/favicon-192.png and b/apps/marketing/public/favicon-192.png differ diff --git a/apps/marketing/public/title.svg b/apps/marketing/public/title.svg index 726a91b632e..6a5eb8e3ed2 100644 --- a/apps/marketing/public/title.svg +++ b/apps/marketing/public/title.svg @@ -1,51 +1,106 @@ - - + + + + + + + + + + + + + + + + + - - - - + - - + + - - - + + + - - + + - - + + - + - - + + - - - + + + - - + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + +