fix(icons): паритет иконки окна с pack-иконкой и sync lockfile

- Копировать 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
This commit is contained in:
     Фонтош Иван Сергеевич
2026-04-19 15:00:33 +08:00
parent d14a674e22
commit 5e7dc5ea19
5 changed files with 108 additions and 36 deletions
+2
View File
@@ -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();
@@ -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'));
});
+53 -8
View File
@@ -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 p = resolveWindowIconPath();
if (!p) return undefined;
const tryPath = (filePath: string): Electron.NativeImage | undefined => {
try {
const img = nativeImage.createFromPath(p);
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;
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 */
}
}
}
function createWindow(kind: WindowKind): BrowserWindow {
+36 -22
View File
@@ -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"
}
+12 -2
View File
@@ -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);
}