The app has an icon
Every build so far shipped the default Electron one. electron-builder said so on every run — "default Electron icon is used, reason=application icon is not set" — in a line that is very easy to read past. A store people install games with should not look like a framework demo in the Dock. The mark is a W with three lines running into it: the product's initial, and what it is doing. It uses the window's own palette, so the icon and the application it opens are the same object. Drawn for the smallest size first, which is what settled it — at 32px the W still reads and the lines survive as motion rather than as noise, where a ring, an outline or fine detail did not. A portal, a play triangle and a send arrow were each drawn and each discarded: they already mean a loading spinner, a media player and a submit button. `resources/icon.svg` is the source and the only file anybody should edit. `make icons` renders the rest. Three committed binaries with no way to regenerate them is how an icon becomes something nobody dares change, so the ICO is written here rather than shelling out to ImageMagick — the container is a header and 16 bytes per image, which is not worth a build dependency this machine does not have. `directories.buildResources` had to move off the default: electron-builder looks in `build/`, which this project uses for compiled output and wipes on `make clean`, so the icons would have been deleted before every package. The window picks it up when run from source too, where there is otherwise nothing to carry an icon and a dev run looks like a different application. Guarded on `app.isPackaged`, because `resources/` is not inside the package and pointing at it there would be a path that does not exist. Verified by reading the icon back out of the built bundle rather than trusting the config: extracted from `WarpEngine Client.app/Contents/Resources/icon.icns` and looked at, and the ICO parsed entry by entry — 7 images, 16 to 256, each a valid PNG. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@@ -36,7 +36,7 @@ BUILDER_ARGS := $(if $(STORES_API),-- --config.extraMetadata.warpEngine.registry
|
|||||||
|
|
||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
.PHONY: help setup node-check build typecheck lint lint-fix check start smoke uitest storetest test \
|
.PHONY: help setup node-check build typecheck lint lint-fix check start smoke uitest storetest icons test \
|
||||||
dist dist-mac dist-win dist-linux release publish clean distclean version
|
dist dist-mac dist-win dist-linux release publish clean distclean version
|
||||||
|
|
||||||
help: ## List available targets
|
help: ## List available targets
|
||||||
@@ -83,6 +83,9 @@ smoke: ## Drive the store bridge with no window at all
|
|||||||
uitest: ## Load the window once and report what rendered
|
uitest: ## Load the window once and report what rendered
|
||||||
npm run uitest
|
npm run uitest
|
||||||
|
|
||||||
|
icons: ## Render every icon format from resources/icon.svg
|
||||||
|
npm run icons
|
||||||
|
|
||||||
storetest: ## Add and remove a store in a sandbox (the only code that deletes a tree)
|
storetest: ## Add and remove a store in a sandbox (the only code that deletes a tree)
|
||||||
npm run storetest
|
npm run storetest
|
||||||
|
|
||||||
|
|||||||
@@ -230,6 +230,7 @@ names. `make` on its own lists everything.
|
|||||||
| `make lint` | the strict rule set (`lint-fix` fixes what it can) |
|
| `make lint` | the strict rule set (`lint-fix` fixes what it can) |
|
||||||
| `make check` | **typecheck, lint and every test suite** — the gate |
|
| `make check` | **typecheck, lint and every test suite** — the gate |
|
||||||
| `make start` | run the app against whatever store is installed |
|
| `make start` | run the app against whatever store is installed |
|
||||||
|
| `make icons` | render every icon format from `resources/icon.svg` |
|
||||||
| `make smoke` | drive the store with no window and no Electron at all |
|
| `make smoke` | drive the store with no window and no Electron at all |
|
||||||
| `make storetest` | add and remove a store in a sandbox — the only code that deletes a tree |
|
| `make storetest` | add and remove a store in a sandbox — the only code that deletes a tree |
|
||||||
| `SMOKE_HOME=<dir> SMOKE_TOKEN=<bearer> npm run smoke` | the same, against a sandbox store and as a signed-in person |
|
| `SMOKE_HOME=<dir> SMOKE_TOKEN=<bearer> npm run smoke` | the same, against a sandbox store and as a signed-in person |
|
||||||
@@ -246,6 +247,23 @@ The npm scripts still work directly (`npm start`, `npm run dist:mac`) — the
|
|||||||
Makefile adds no logic of its own beyond the release step. Every script that runs the
|
Makefile adds no logic of its own beyond the release step. Every script that runs the
|
||||||
app builds first, so there is no way to test a stale bundle.
|
app builds first, so there is no way to test a stale bundle.
|
||||||
|
|
||||||
|
### The icon
|
||||||
|
|
||||||
|
`resources/icon.svg` is the source and the only file to edit; `make icons` renders the
|
||||||
|
rest — `icon.png`, `icon.ico`, `icon.icns` and the `icons/` directory Linux packages
|
||||||
|
want. Three committed binaries with no way to regenerate them is how an icon becomes
|
||||||
|
something nobody dares change, so the render is a script rather than a memory.
|
||||||
|
|
||||||
|
It needs `rsvg-convert` (`brew install librsvg`, `apt install librsvg2-bin`). The
|
||||||
|
`.icns` step additionally needs `iconutil`, which exists only on macOS — elsewhere it
|
||||||
|
is skipped with a warning and the committed `.icns` stands, which is what a mac build
|
||||||
|
uses anyway.
|
||||||
|
|
||||||
|
The mark is a **W with three lines running into it**: the product's initial, and what
|
||||||
|
it is doing. It was drawn for the smallest size first — at 32px the W still reads and
|
||||||
|
the lines survive as motion rather than as noise. A portal, a play triangle and a send
|
||||||
|
arrow were all tried and all discarded: each already means something else.
|
||||||
|
|
||||||
### Continuous integration
|
### Continuous integration
|
||||||
|
|
||||||
`.woodpecker.yaml` builds the **Linux and Windows** packages, and on a tag attaches
|
`.woodpecker.yaml` builds the **Linux and Windows** packages, and on a tag attaches
|
||||||
|
|||||||
@@ -1,30 +1,23 @@
|
|||||||
# WarpEngine Client 2.5.0
|
# WarpEngine Client 2.5.1
|
||||||
|
|
||||||
**A store can be taken off the machine again.** Hovering a store in the side menu shows
|
**The app has an icon.** Until now every build shipped the default Electron one —
|
||||||
a bin. It asks first, and says how many titles will go with it — because removing a
|
`electron-builder` said so on every run, in a line easy to read past: *"default Electron
|
||||||
store *uninstalls what it installed*. That is not a convenience: a store's `state.json`
|
icon is used, reason=application icon is not set"*. A store you install games with
|
||||||
is the only record of which payloads, icons and menu entries belong to it, so leaving
|
should not look like a framework demo in the Dock.
|
||||||
the games behind would leave orphans nothing could ever identify, least of all a later
|
|
||||||
install of the same store into the same folder. The catalog cache, the settings and any
|
|
||||||
sign-in token go too.
|
|
||||||
|
|
||||||
**Adding one moved to a + beside Refresh**, and it now takes a catalog address of your
|
The mark is a **W with three lines running into it**: the product's initial, and what it
|
||||||
own as well as the ones the registry lists. A bare host is enough — `https` is assumed —
|
is doing. It uses the window's own palette, so the icon and the application it opens are
|
||||||
and the name is taken from the address. Both are actions on the whole store rather than
|
the same object. It was drawn for the smallest size first — at 32px the W still reads
|
||||||
on one of them, which is why they sit together; the full-width "Add a store…" button
|
and the lines survive as motion rather than as noise. A portal, a play triangle and a
|
||||||
under the list read as a third store.
|
send arrow were each tried and each discarded: they already mean a loading spinner, a
|
||||||
|
media player and a submit button.
|
||||||
|
|
||||||
**The picker has a way out.** Opening it with a store already installed used to be a
|
`resources/icon.svg` is the source and the only file to edit. `make icons` renders the
|
||||||
trap: the grid was replaced and nothing short of installing something brought it back.
|
`.png`, the `.ico`, the `.icns` and the Linux size directory from it — three committed
|
||||||
|
binaries with no way to regenerate them is how an icon becomes something nobody dares
|
||||||
|
touch.
|
||||||
|
|
||||||
**Two things found by the new test.** `make storetest` adds and removes a store in a
|
The window also picks it up when run from source, where there is otherwise no icon to
|
||||||
sandbox, because removal is the only code here that deletes a directory tree and the
|
carry and a dev run looks like a different application from the one being built.
|
||||||
path it deletes is named by the window. It immediately caught that `http://` was
|
|
||||||
accepted and became a store called *http* — the trailing slashes were being stripped
|
|
||||||
before the scheme was checked — and that `STORE_ROOT` only *prepended* to the search
|
|
||||||
path, so a "sandboxed" run still listed the real stores. With a delete button in the
|
|
||||||
window, a sandbox that can reach a working installation is not a sandbox; it replaces
|
|
||||||
the search path now.
|
|
||||||
|
|
||||||
**`btn-secondary` had no styling at all.** It was introduced in 2.4.0 on the card's
|
Nothing else changed: same store handling, same catalog, same sign-in.
|
||||||
sign-in button and on the sign-in panel, and rendered as a plain button in both places.
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "warp-engine-client",
|
"name": "warp-engine-client",
|
||||||
"version": "2.4.0",
|
"version": "2.5.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "warp-engine-client",
|
"name": "warp-engine-client",
|
||||||
"version": "2.4.0",
|
"version": "2.5.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^26.2.0",
|
"@types/node": "^26.2.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "warp-engine-client",
|
"name": "warp-engine-client",
|
||||||
"productName": "WarpEngine Client",
|
"productName": "WarpEngine Client",
|
||||||
"version": "2.5.0",
|
"version": "2.5.1",
|
||||||
"description": "Graphical client for WarpEngine stores: install a catalog into your own application menu.",
|
"description": "Graphical client for WarpEngine stores: install a catalog into your own application menu.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Teletype Games <games@teletype.hu>",
|
"author": "Teletype Games <games@teletype.hu>",
|
||||||
@@ -23,7 +23,8 @@
|
|||||||
"dist:win": "npm run build && electron-builder --win",
|
"dist:win": "npm run build && electron-builder --win",
|
||||||
"dist:linux": "npm run build && electron-builder --linux",
|
"dist:linux": "npm run build && electron-builder --linux",
|
||||||
"storetest": "npm run build && node build/scripts/StoreLifecycleTest.js",
|
"storetest": "npm run build && node build/scripts/StoreLifecycleTest.js",
|
||||||
"test": "npm run smoke && npm run storetest && npm run uitest"
|
"test": "npm run smoke && npm run storetest && npm run uitest",
|
||||||
|
"icons": "node scripts/build-icons.mjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^26.2.0",
|
"@types/node": "^26.2.0",
|
||||||
@@ -47,7 +48,8 @@
|
|||||||
"dmg",
|
"dmg",
|
||||||
"zip"
|
"zip"
|
||||||
],
|
],
|
||||||
"artifactName": "WarpEngineClient-${version}-${arch}-mac.${ext}"
|
"artifactName": "WarpEngineClient-${version}-${arch}-mac.${ext}",
|
||||||
|
"icon": "resources/icon.icns"
|
||||||
},
|
},
|
||||||
"dmg": {
|
"dmg": {
|
||||||
"artifactName": "WarpEngineClient-${version}-${arch}.${ext}"
|
"artifactName": "WarpEngineClient-${version}-${arch}.${ext}"
|
||||||
@@ -56,7 +58,8 @@
|
|||||||
"target": [
|
"target": [
|
||||||
"nsis",
|
"nsis",
|
||||||
"portable"
|
"portable"
|
||||||
]
|
],
|
||||||
|
"icon": "resources/icon.ico"
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"artifactName": "WarpEngineClient-Setup-${version}-${arch}.${ext}"
|
"artifactName": "WarpEngineClient-Setup-${version}-${arch}.${ext}"
|
||||||
@@ -69,12 +72,16 @@
|
|||||||
"target": [
|
"target": [
|
||||||
"AppImage",
|
"AppImage",
|
||||||
"deb"
|
"deb"
|
||||||
]
|
],
|
||||||
|
"icon": "resources/icons"
|
||||||
},
|
},
|
||||||
"appImage": {
|
"appImage": {
|
||||||
"artifactName": "WarpEngineClient-${version}-${arch}.${ext}"
|
"artifactName": "WarpEngineClient-${version}-${arch}.${ext}"
|
||||||
},
|
},
|
||||||
"afterPack": "scripts/after-pack.js"
|
"afterPack": "scripts/after-pack.js",
|
||||||
|
"directories": {
|
||||||
|
"buildResources": "resources"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"allowScripts": {
|
"allowScripts": {
|
||||||
"electron@43.4.0": true,
|
"electron@43.4.0": true,
|
||||||
|
|||||||
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 117 KiB |
@@ -0,0 +1,44 @@
|
|||||||
|
<!--
|
||||||
|
The WarpEngine Client mark.
|
||||||
|
|
||||||
|
A W, and three lines running into it. The W is the product's initial; the lines are
|
||||||
|
what the W is doing — motion, read left to right, which is also why they are dimmer
|
||||||
|
than it is. Together they say "warp" without a spaceship, a portal or a play triangle,
|
||||||
|
each of which was tried and each of which turned out to mean something else already:
|
||||||
|
a send arrow, a loading spinner, a media player.
|
||||||
|
|
||||||
|
Drawn for the smallest size first. At 32px the W still reads and the lines survive as
|
||||||
|
a stack of motion rather than as noise; anything with an outline, a ring or fine
|
||||||
|
detail did not. The palette is the window's own, taken from src/renderer/style.css,
|
||||||
|
so the icon and the application it opens are the same object.
|
||||||
|
|
||||||
|
This file is the source. `npm run icons` renders every format from it; nothing here is
|
||||||
|
hand-edited binary.
|
||||||
|
-->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="tile" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#1e2937"/>
|
||||||
|
<stop offset="1" stop-color="#0d1116"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="glow" cx="0.5" cy="0.42" r="0.6">
|
||||||
|
<stop offset="0" stop-color="#37b98a" stop-opacity="0.24"/>
|
||||||
|
<stop offset="1" stop-color="#37b98a" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Inset by 7%: a macOS icon is a rounded tile with air around it, and the same
|
||||||
|
shape is what Windows and the Linux menus get. -->
|
||||||
|
<rect x="72" y="72" width="880" height="880" rx="200" fill="url(#tile)"/>
|
||||||
|
<rect x="72" y="72" width="880" height="880" rx="200" fill="url(#glow)"/>
|
||||||
|
<rect x="72" y="72" width="880" height="880" rx="200" fill="none" stroke="#2a3440" stroke-width="8"/>
|
||||||
|
|
||||||
|
<g stroke="#2c8f6c" stroke-width="48" stroke-linecap="round">
|
||||||
|
<path d="M196 400 H286"/>
|
||||||
|
<path d="M172 512 H274"/>
|
||||||
|
<path d="M196 624 H286"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<path d="M366 348 L462 676 L588 456 L714 676 L810 348" fill="none" stroke="#37b98a"
|
||||||
|
stroke-width="82" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 639 B |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Render every icon format from resources/icon.svg.
|
||||||
|
//
|
||||||
|
// The point of this script is that the icon stays *editable*. Three committed binaries
|
||||||
|
// with no way to regenerate them is how an icon becomes something nobody dares touch;
|
||||||
|
// here the SVG is the source and everything else is output, so changing the mark is
|
||||||
|
// changing one file and running this.
|
||||||
|
//
|
||||||
|
// npm run icons
|
||||||
|
//
|
||||||
|
// Needs `rsvg-convert` (brew install librsvg). The .icns additionally needs `iconutil`,
|
||||||
|
// which only exists on macOS — on Linux that step is skipped with a warning, because CI
|
||||||
|
// builds Linux and Windows there and the committed .icns is what a mac build uses.
|
||||||
|
|
||||||
|
import { execFileSync } from 'node:child_process'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
const ROOT = path.resolve(import.meta.dirname, '..')
|
||||||
|
const RESOURCES = path.join(ROOT, 'resources')
|
||||||
|
const SOURCE = path.join(RESOURCES, 'icon.svg')
|
||||||
|
|
||||||
|
/** Windows wants these; anything larger than 256 cannot go in an ICO as PNG anyway. */
|
||||||
|
const ICO_SIZES = [16, 24, 32, 48, 64, 128, 256]
|
||||||
|
|
||||||
|
/** What macOS asks for in an iconset, with the @2x names it insists on. */
|
||||||
|
const ICNS_ENTRIES = [
|
||||||
|
[16, 'icon_16x16.png'], [32, 'icon_16x16@2x.png'],
|
||||||
|
[32, 'icon_32x32.png'], [64, 'icon_32x32@2x.png'],
|
||||||
|
[128, 'icon_128x128.png'], [256, 'icon_128x128@2x.png'],
|
||||||
|
[256, 'icon_256x256.png'], [512, 'icon_256x256@2x.png'],
|
||||||
|
[512, 'icon_512x512.png'], [1024, 'icon_512x512@2x.png']
|
||||||
|
]
|
||||||
|
|
||||||
|
function render (size, target) {
|
||||||
|
execFileSync('rsvg-convert', ['-w', String(size), '-h', String(size), SOURCE, '-o', target])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ICO holding PNGs.
|
||||||
|
*
|
||||||
|
* The format allows it since Vista and every tool this project's packages reach has
|
||||||
|
* supported it for longer than that. Writing the container by hand is a few lines and
|
||||||
|
* saves a dependency on ImageMagick, which is not installed here and is not worth
|
||||||
|
* making a build requirement for 22 bytes of header per image.
|
||||||
|
*/
|
||||||
|
function writeIco (pngs, target) {
|
||||||
|
const header = Buffer.alloc(6)
|
||||||
|
header.writeUInt16LE(0, 0)
|
||||||
|
header.writeUInt16LE(1, 2) // 1 = icon
|
||||||
|
header.writeUInt16LE(pngs.length, 4)
|
||||||
|
|
||||||
|
const directory = Buffer.alloc(16 * pngs.length)
|
||||||
|
let offset = header.length + directory.length
|
||||||
|
|
||||||
|
pngs.forEach(({ size, data }, index) => {
|
||||||
|
const at = index * 16
|
||||||
|
// 0 means 256 in this field, which is the whole reason 256 is the largest size here.
|
||||||
|
directory.writeUInt8(size >= 256 ? 0 : size, at)
|
||||||
|
directory.writeUInt8(size >= 256 ? 0 : size, at + 1)
|
||||||
|
directory.writeUInt8(0, at + 2) // palette: none
|
||||||
|
directory.writeUInt8(0, at + 3) // reserved
|
||||||
|
directory.writeUInt16LE(1, at + 4) // colour planes
|
||||||
|
directory.writeUInt16LE(32, at + 6) // bits per pixel
|
||||||
|
directory.writeUInt32LE(data.length, at + 8)
|
||||||
|
directory.writeUInt32LE(offset, at + 12)
|
||||||
|
offset += data.length
|
||||||
|
})
|
||||||
|
|
||||||
|
fs.writeFileSync(target, Buffer.concat([header, directory, ...pngs.map((p) => p.data)]))
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildIco () {
|
||||||
|
const temporary = fs.mkdtempSync(path.join(RESOURCES, '.ico-'))
|
||||||
|
try {
|
||||||
|
const pngs = ICO_SIZES.map((size) => {
|
||||||
|
const file = path.join(temporary, `${size}.png`)
|
||||||
|
render(size, file)
|
||||||
|
return { size, data: fs.readFileSync(file) }
|
||||||
|
})
|
||||||
|
writeIco(pngs, path.join(RESOURCES, 'icon.ico'))
|
||||||
|
console.log(` icon.ico ${ICO_SIZES.join(', ')}`)
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(temporary, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildIcns () {
|
||||||
|
const iconset = path.join(RESOURCES, 'icon.iconset')
|
||||||
|
fs.rmSync(iconset, { recursive: true, force: true })
|
||||||
|
fs.mkdirSync(iconset)
|
||||||
|
try {
|
||||||
|
for (const [size, name] of ICNS_ENTRIES) render(size, path.join(iconset, name))
|
||||||
|
execFileSync('iconutil', ['-c', 'icns', iconset, '-o', path.join(RESOURCES, 'icon.icns')])
|
||||||
|
console.log(' icon.icns 16 … 512@2x')
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(iconset, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linux takes a directory of sizes; electron-builder reads whatever is in it.
|
||||||
|
*
|
||||||
|
* Named `<size>x<size>.png`, which is the convention it expects and also what a
|
||||||
|
* `.desktop` entry's icon lookup walks.
|
||||||
|
*/
|
||||||
|
function buildLinuxIcons () {
|
||||||
|
const directory = path.join(RESOURCES, 'icons')
|
||||||
|
fs.rmSync(directory, { recursive: true, force: true })
|
||||||
|
fs.mkdirSync(directory)
|
||||||
|
const sizes = [16, 32, 48, 64, 128, 256, 512, 1024]
|
||||||
|
for (const size of sizes) render(size, path.join(directory, `${size}x${size}.png`))
|
||||||
|
console.log(` icons/ ${sizes.join(', ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function main () {
|
||||||
|
if (!fs.existsSync(SOURCE)) {
|
||||||
|
console.error(`no ${path.relative(ROOT, SOURCE)} — the icon source is missing`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
execFileSync('rsvg-convert', ['--version'], { stdio: 'ignore' })
|
||||||
|
} catch {
|
||||||
|
console.error('rsvg-convert is not installed (brew install librsvg / apt install librsvg2-bin)')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('rendering icons from resources/icon.svg')
|
||||||
|
render(1024, path.join(RESOURCES, 'icon.png'))
|
||||||
|
console.log(' icon.png 1024')
|
||||||
|
buildLinuxIcons()
|
||||||
|
buildIco()
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
buildIcns()
|
||||||
|
} else {
|
||||||
|
// Not fatal: the committed .icns is what a mac build uses, and only a Mac can make
|
||||||
|
// one. Saying so is better than a build that quietly ships the Electron default.
|
||||||
|
console.warn(' icon.icns skipped — iconutil is macOS only; the committed one stands')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { BrowserWindow, shell, type BrowserWindowConstructorOptions } from 'electron'
|
import { app, BrowserWindow, shell, type BrowserWindowConstructorOptions } from 'electron'
|
||||||
|
|
||||||
const WINDOW_OPTIONS: BrowserWindowConstructorOptions = {
|
const WINDOW_OPTIONS: BrowserWindowConstructorOptions = {
|
||||||
width: 1040,
|
width: 1040,
|
||||||
@@ -10,6 +10,22 @@ const WINDOW_OPTIONS: BrowserWindowConstructorOptions = {
|
|||||||
title: 'WarpEngine Client'
|
title: 'WarpEngine Client'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The window icon, in development only.
|
||||||
|
*
|
||||||
|
* A packaged app carries its icon in the bundle, the .exe and the .desktop entry, and
|
||||||
|
* `resources/` is not inside the package at all — pointing at it there would be a path
|
||||||
|
* that does not exist. Run from source there is nothing to carry the icon, so the
|
||||||
|
* window and the taskbar show Electron's own, which makes a dev run look like a
|
||||||
|
* different application from the one being built. macOS ignores this either way: the
|
||||||
|
* Dock icon comes from the bundle.
|
||||||
|
*/
|
||||||
|
function developmentIcon (): Pick<BrowserWindowConstructorOptions, 'icon'> {
|
||||||
|
if (app.isPackaged) return {}
|
||||||
|
|
||||||
|
return { icon: path.join(__dirname, '..', '..', 'resources', 'icons', '512x512.png') }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The one window.
|
* The one window.
|
||||||
*
|
*
|
||||||
@@ -23,6 +39,7 @@ export class MainWindowFactory {
|
|||||||
public createWindow (): BrowserWindow {
|
public createWindow (): BrowserWindow {
|
||||||
const window = new BrowserWindow({
|
const window = new BrowserWindow({
|
||||||
...WINDOW_OPTIONS,
|
...WINDOW_OPTIONS,
|
||||||
|
...developmentIcon(),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: path.join(__dirname, '..', 'preload', 'preload.js'),
|
preload: path.join(__dirname, '..', 'preload', 'preload.js'),
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
|
|||||||