- fixed icon saving
- slightly improved library layout
This commit is contained in:
olcxja 2026-10-02 08:59:27 +02:00
commit f8ce9a9945
4 changed files with 35 additions and 13 deletions

View file

@ -1678,7 +1678,10 @@ function wireEvents() {
if (res.ok) refreshConfigIcon() if (res.ok) refreshConfigIcon()
} }
qs('#gc-icon-auto').addEventListener('click', async () => { qs('#gc-icon-auto').addEventListener('click', async () => {
applyIconResult(await window.plwl.gamesAutoIcon(state.currentGame.id)) // extract from the exe the field shows, not the saved one - the user may
// have just pointed the app at another exe and expects its icon
const exe = absGamePathInput(qs('#gc-exe').value.trim(), state.currentGame)
applyIconResult(await window.plwl.gamesAutoIcon(state.currentGame.id, exe))
}) })
qs('#gc-icon-browse').addEventListener('click', async () => { qs('#gc-icon-browse').addEventListener('click', async () => {
// start in the app's own folder (prefix apps included) - that is where // start in the app's own folder (prefix apps included) - that is where

View file

@ -490,8 +490,8 @@ select {
margin: 1rem 0; margin: 1rem 0;
} }
.game-card { .game-card {
flex: 1 1 15rem; flex: 1 1 17rem;
min-width: 15rem; min-width: 17rem;
max-width: 24rem; max-width: 24rem;
background: var(--panel-color); background: var(--panel-color);
border: var(--border-width) solid var(--light-border-color); border: var(--border-width) solid var(--light-border-color);

View file

@ -25,10 +25,13 @@ function errorResult(err) {
const iconDir = id => path.join(DIRS.DATA, 'icons', id) const iconDir = id => path.join(DIRS.DATA, 'icons', id)
function clearIconDir(id) { // `keep` spares one file (the icon just written): the icon protocol picks the
// LAST icon.* alphabetically, so a leftover icon.png from an earlier pick
// would otherwise keep showing the old icon.
function clearIconDir(id, keep) {
try { try {
for (const f of fs.readdirSync(iconDir(id))) { for (const f of fs.readdirSync(iconDir(id))) {
if (f.startsWith('icon.')) fs.rmSync(path.join(iconDir(id), f), { force: true }) if (f.startsWith('icon.') && f !== keep) fs.rmSync(path.join(iconDir(id), f), { force: true })
} }
} catch {} } catch {}
} }
@ -36,7 +39,11 @@ function clearIconDir(id) {
function extractGameIcon(id, exePath) { function extractGameIcon(id, exePath) {
const out = path.join(iconDir(id), 'icon.ico') const out = path.join(iconDir(id), 'icon.ico')
const res = extractIconFromExe(exePath, out) const res = extractIconFromExe(exePath, out)
if (res.ok) library.updateGame(id, { iconSource: out }) // on failure nothing is cleared, so the previous icon survives a wrong exe
if (res.ok) {
clearIconDir(id, 'icon.ico')
library.updateGame(id, { iconSource: out })
}
return res return res
} }
@ -152,11 +159,14 @@ function registerIpc({ getWindow, onGamesChanged }) {
if (!game || !game.exePath) return { ok: false, error: 'bad-request' } if (!game || !game.exePath) return { ok: false, error: 'bad-request' }
return extractGameIcon(id, game.exePath) return extractGameIcon(id, game.exePath)
}) })
ipcMain.handle('games:autoIcon', (e, id) => { // exePath comes from the config field, which may hold a path the user just
// picked but has not saved yet - the icon has to come from THAT exe
ipcMain.handle('games:autoIcon', (e, id, exePath) => {
const game = library.getGame(id) const game = library.getGame(id)
if (!game || !game.exePath) return { ok: false, error: 'bad-request' } if (!game) return { ok: false, error: 'bad-request' }
clearIconDir(id) const exe = exePath || game.exePath
return extractGameIcon(id, game.exePath) if (!exe) return { ok: false, error: 'bad-request' }
return extractGameIcon(id, exe)
}) })
// "Open with PLWL" ------------------------------------------------------------------ // "Open with PLWL" ------------------------------------------------------------------
@ -253,7 +263,7 @@ function registerIpc({ getWindow, onGamesChanged }) {
}) })
// game icons as a protocol so the renderer can display them safely --------------------------- // game icons as a protocol so the renderer can display them safely ---------------------------
protocol.handle('plwl-icon', request => { protocol.handle('plwl-icon', async request => {
const id = decodeURIComponent(new URL(request.url).hostname) const id = decodeURIComponent(new URL(request.url).hostname)
let file = null let file = null
try { try {
@ -265,7 +275,16 @@ function registerIpc({ getWindow, onGamesChanged }) {
if (pick) file = path.join(iconDir(id), pick) if (pick) file = path.join(iconDir(id), pick)
} catch {} } catch {}
if (!file) return new Response('', { status: 404 }) if (!file) return new Response('', { status: 404 })
return net.fetch('file://' + file) // a new icon lands at the SAME url, so the browser would keep its cached
// copy and the library card would only catch up after a restart - never
// let the icon request be cached
const res = await net.fetch('file://' + file)
return new Response(await res.arrayBuffer(), {
headers: {
'content-type': res.headers.get('content-type') || 'image/x-icon',
'cache-control': 'no-store'
}
})
}) })
return { games } return { games }

View file

@ -69,7 +69,7 @@ contextBridge.exposeInMainWorld('plwl', {
gamesClearPlaytime: id => ipcRenderer.invoke('games:clearPlaytime', id), gamesClearPlaytime: id => ipcRenderer.invoke('games:clearPlaytime', id),
gamesSetIconFile: (id, p) => ipcRenderer.invoke('games:setIconFile', id, p), gamesSetIconFile: (id, p) => ipcRenderer.invoke('games:setIconFile', id, p),
gamesExtractIcon: id => ipcRenderer.invoke('games:extractIcon', id), gamesExtractIcon: id => ipcRenderer.invoke('games:extractIcon', id),
gamesAutoIcon: id => ipcRenderer.invoke('games:autoIcon', id), gamesAutoIcon: (id, exePath) => ipcRenderer.invoke('games:autoIcon', id, exePath),
gamesIconPath: id => ipcRenderer.invoke('games:iconPath', id), gamesIconPath: id => ipcRenderer.invoke('games:iconPath', id),
gamesResetIcon: id => ipcRenderer.invoke('games:resetIcon', id), gamesResetIcon: id => ipcRenderer.invoke('games:resetIcon', id),
gamesRemove: id => ipcRenderer.invoke('games:remove', id), gamesRemove: id => ipcRenderer.invoke('games:remove', id),