- beautifului.dev 组件直连:Sidebar Nav、Tool Chips、Approval Card(抽屉化)、Streaming/Thinking、Code Block、Selection Actions、Prompt Bar、Search
88 lines
3.0 KiB
JavaScript
88 lines
3.0 KiB
JavaScript
// 生成应用图标(build/icon.ico + build/icon.png):
|
||
// npx electron scripts/make-icons.cjs
|
||
// 用 Electron 离屏渲染 build/icon.svg(居中于方形画布、留 9% 边距),
|
||
// 输出多尺寸 PNG 并打包为 PNG 内嵌式 ICO(Vista+ 支持)。
|
||
const { app, BrowserWindow } = require('electron')
|
||
const { readFileSync, writeFileSync } = require('node:fs')
|
||
const { join } = require('node:path')
|
||
|
||
app.commandLine.appendSwitch('disable-gpu')
|
||
|
||
const SIZES = [16, 24, 32, 48, 64, 128, 256]
|
||
const ROOT = join(__dirname, '..')
|
||
|
||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
||
|
||
/** PNG 内嵌式 ICO 打包 */
|
||
function buildIco(pngs) {
|
||
const count = pngs.length
|
||
const header = Buffer.alloc(6)
|
||
header.writeUInt16LE(0, 0) // reserved
|
||
header.writeUInt16LE(1, 2) // type: icon
|
||
header.writeUInt16LE(count, 4)
|
||
const entries = Buffer.alloc(16 * count)
|
||
const blobs = []
|
||
let offset = 6 + 16 * count
|
||
pngs.forEach((png, i) => {
|
||
const size = png.size
|
||
entries.writeUInt8(size >= 256 ? 0 : size, i * 16 + 0) // width
|
||
entries.writeUInt8(size >= 256 ? 0 : size, i * 16 + 1) // height
|
||
entries.writeUInt8(0, i * 16 + 2) // palette
|
||
entries.writeUInt8(0, i * 16 + 3) // reserved
|
||
entries.writeUInt16LE(1, i * 16 + 4) // planes
|
||
entries.writeUInt16LE(32, i * 16 + 6) // bpp
|
||
entries.writeUInt32LE(png.data.length, i * 16 + 8)
|
||
entries.writeUInt32LE(offset, i * 16 + 12)
|
||
offset += png.data.length
|
||
blobs.push(png.data)
|
||
})
|
||
return Buffer.concat([header, entries, ...blobs])
|
||
}
|
||
|
||
async function renderSize(win, svgDataUrl, size) {
|
||
await win.loadURL(
|
||
'data:text/html,' +
|
||
encodeURIComponent(
|
||
`<!doctype html><html><body style="margin:0;background:transparent">` +
|
||
`<div style="display:flex;align-items:center;justify-content:center;width:${size}px;height:${size}px">` +
|
||
`<img src="${svgDataUrl}" style="width:${Math.round(size * 0.86)}px;height:auto;display:block"/>` +
|
||
`</div></body></html>`
|
||
)
|
||
)
|
||
await sleep(350)
|
||
const image = await win.webContents.capturePage({ x: 0, y: 0, width: size, height: size })
|
||
return image.toPNG()
|
||
}
|
||
|
||
async function run() {
|
||
const svg = readFileSync(join(ROOT, 'build', 'icon.svg'), 'utf8')
|
||
const svgDataUrl = 'data:image/svg+xml;base64,' + Buffer.from(svg, 'utf8').toString('base64')
|
||
|
||
const win = new BrowserWindow({
|
||
width: 256,
|
||
height: 256,
|
||
show: false,
|
||
frame: false,
|
||
transparent: true,
|
||
webPreferences: { paintWhenInitiallyHidden: true, offscreen: true }
|
||
})
|
||
|
||
const pngs = []
|
||
for (const size of SIZES) {
|
||
await win.setSize ? win.setSize(size, size) : null
|
||
const png = await renderSize(win, svgDataUrl, size)
|
||
pngs.push({ size, data: png })
|
||
console.log('rendered', size, `${png.length}B`)
|
||
}
|
||
|
||
writeFileSync(join(ROOT, 'build', 'icon.png'), pngs.find((p) => p.size === 256).data)
|
||
writeFileSync(join(ROOT, 'build', 'icon.ico'), buildIco(pngs))
|
||
console.log('written build/icon.ico & build/icon.png')
|
||
app.exit(0)
|
||
}
|
||
|
||
app.whenReady().then(run).catch((err) => {
|
||
console.error(err)
|
||
app.exit(1)
|
||
})
|