From 5e7dc5ea190f3ab20adee0ac4f20490ed4905e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=D0=A4=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D0=BE=D1=88=20=D0=98=D0=B2=D0=B0=D0=BD=20=D0=A1=D0=B5=D1=80?= =?UTF-8?q?=D0=B3=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sun, 19 Apr 2026 15:00:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(icons):=20=D0=BF=D0=B0=D1=80=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D1=82=20=D0=B8=D0=BA=D0=BE=D0=BD=D0=BA=D0=B8=20=D0=BE?= =?UTF-8?q?=D0=BA=D0=BD=D0=B0=20=D1=81=20pack-=D0=B8=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D0=BA=D0=BE=D0=B9=20=D0=B8=20sync=20lockfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Копировать build/icon.png в dist/renderer/app-pack-icon.png после Vite - Приоритет pack PNG для BrowserWindow; на win32/linux без SVG в nativeImage - macOS: app.dock.setIcon из того же набора PNG - package-lock.json в соответствии с package.json Made-with: Cursor --- app/main/index.ts | 2 + .../windows/createWindows.editorClose.test.ts | 3 +- app/main/windows/createWindows.ts | 67 ++++++++++++++++--- package-lock.json | 58 ++++++++++------ scripts/build.mjs | 14 +++- 5 files changed, 108 insertions(+), 36 deletions(-) diff --git a/app/main/index.ts b/app/main/index.ts index 08f5649..add6e00 100644 --- a/app/main/index.ts +++ b/app/main/index.ts @@ -9,6 +9,7 @@ import { registerDndAssetProtocol } from './protocol/dndAssetProtocol'; import { getAppSemanticVersion, getOptionalBuildNumber } from './versionInfo'; import { VideoPlaybackStore } from './video/videoPlaybackStore'; import { + applyDockIconIfNeeded, closeMultiWindow, createWindows, focusEditorWindow, @@ -323,6 +324,7 @@ async function main() { }); installIpcRouter(); + applyDockIconIfNeeded(); createWindows(); emitSessionState(); emitEffectsState(); diff --git a/app/main/windows/createWindows.editorClose.test.ts b/app/main/windows/createWindows.editorClose.test.ts index 60891ad..36fc993 100644 --- a/app/main/windows/createWindows.editorClose.test.ts +++ b/app/main/windows/createWindows.editorClose.test.ts @@ -20,9 +20,10 @@ void test('createWindows: закрытие редактора завершает assert.ok(src.includes('markAppQuitting')); }); -void test('createWindows: иконка окна (PNG приоритетно, затем SVG)', () => { +void test('createWindows: иконка окна (pack PNG, затем window PNG; SVG только вне win32)', () => { const src = readCreateWindows(); assert.ok(src.includes('resolveWindowIconPath')); + assert.ok(src.includes('app-pack-icon.png')); assert.ok(src.includes('app-window-icon.png')); assert.ok(src.includes('app-logo.svg')); }); diff --git a/app/main/windows/createWindows.ts b/app/main/windows/createWindows.ts index c874bc4..7af3427 100644 --- a/app/main/windows/createWindows.ts +++ b/app/main/windows/createWindows.ts @@ -40,18 +40,34 @@ function getPreloadPath(): string { } /** - * Иконка окна. На Windows `nativeImage` из SVG часто пустой — сначала ищем PNG - * (`app-window-icon.png`), затем SVG из public / dist. + * PNG для иконки окна / дока: тот же растр, что electron-builder берёт из `build/icon.png` + * (копия в dist после сборки), затем окно 256px, затем dev-пути. SVG не используем для + * nativeImage на Windows — иначе пустая картинка и дефолтная иконка Electron вместо exe. */ -function resolveWindowIconPath(): string | undefined { +function resolveBrandingPngPaths(): string[] { const root = app.getAppPath(); - const candidates = [ + return [ + path.join(root, 'dist', 'renderer', 'app-pack-icon.png'), path.join(root, 'dist', 'renderer', 'app-window-icon.png'), + path.join(root, 'build', 'icon.png'), path.join(root, 'app', 'renderer', 'public', 'app-window-icon.png'), + ]; +} + +function resolveWindowIconPath(): string | undefined { + for (const p of resolveBrandingPngPaths()) { + try { + if (fs.existsSync(p)) return p; + } catch { + /* ignore */ + } + } + const root = app.getAppPath(); + const svgFallback = [ path.join(root, 'dist', 'renderer', 'app-logo.svg'), path.join(root, 'app', 'renderer', 'public', 'app-logo.svg'), ]; - for (const p of candidates) { + for (const p of svgFallback) { try { if (fs.existsSync(p)) return p; } catch { @@ -62,15 +78,44 @@ function resolveWindowIconPath(): string | undefined { } function resolveWindowIcon(): Electron.NativeImage | undefined { + const tryPath = (filePath: string): Electron.NativeImage | undefined => { + try { + const img = nativeImage.createFromPath(filePath); + if (!img.isEmpty()) return img; + } catch { + /* ignore */ + } + return undefined; + }; + + if (process.platform === 'win32' || process.platform === 'linux') { + for (const p of resolveBrandingPngPaths()) { + if (!fs.existsSync(p)) continue; + const img = tryPath(p); + if (img) return img; + } + return undefined; + } + const p = resolveWindowIconPath(); if (!p) return undefined; - try { - const img = nativeImage.createFromPath(p); - if (!img.isEmpty()) return img; - } catch { - /* ignore */ + return tryPath(p); +} + +/** macOS: в Dock показываем тот же PNG, что и у упакованного приложения на Windows (иконка exe). */ +export function applyDockIconIfNeeded(): void { + if (process.platform !== 'darwin' || !app.dock) return; + for (const p of resolveBrandingPngPaths()) { + if (!fs.existsSync(p)) continue; + try { + const img = nativeImage.createFromPath(p); + if (img.isEmpty()) continue; + app.dock.setIcon(img); + return; + } catch { + /* try next */ + } } - return undefined; } function createWindow(kind: WindowKind): BrowserWindow { diff --git a/package-lock.json b/package-lock.json index af84c32..e3b919c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,7 +76,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -694,6 +693,7 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, + "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -715,6 +715,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -731,6 +732,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -745,10 +747,34 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 10.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -2838,7 +2864,6 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2936,7 +2961,6 @@ "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.2", "@typescript-eslint/types": "8.58.2", @@ -3494,7 +3518,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3528,7 +3551,6 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4041,7 +4063,6 @@ "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -4144,7 +4165,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -4761,7 +4781,8 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -4865,7 +4886,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -5164,7 +5184,6 @@ "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", @@ -5492,6 +5511,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -5512,6 +5532,7 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -5775,7 +5796,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -5840,7 +5860,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5901,7 +5920,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -6028,7 +6046,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -8807,6 +8824,7 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -9409,7 +9427,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9514,6 +9531,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -9531,6 +9549,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -9551,7 +9570,6 @@ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9672,7 +9690,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9682,7 +9699,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -9933,6 +9949,7 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -10787,6 +10804,7 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -11582,7 +11600,6 @@ "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11684,7 +11701,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -11799,7 +11815,6 @@ "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -12150,7 +12165,6 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/scripts/build.mjs b/scripts/build.mjs index 03a48a1..1057a68 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -1,7 +1,9 @@ -import { build } from 'esbuild'; +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { execFileSync } from 'node:child_process'; + +import { build } from 'esbuild'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -45,3 +47,11 @@ execFileSync('node', [path.join(root, 'scripts', 'gen-window-icon.mjs')], { stdio: 'inherit', }); runViteBuild(); + +// Тот же PNG, что electron-builder кладёт в .exe/.app — в рантайме для окна/дока (паритет с Windows). +const packIcon = path.join(root, 'build', 'icon.png'); +const distPackIcon = path.join(root, 'dist', 'renderer', 'app-pack-icon.png'); +if (fs.existsSync(packIcon)) { + fs.copyFileSync(packIcon, distPackIcon); + console.log('copied pack icon to', distPackIcon); +}