add dev rules
This commit is contained in:
parent
6f9c486774
commit
944d8f86c2
9 changed files with 112 additions and 27 deletions
|
|
@ -58,3 +58,8 @@ The desktop entry (`MimeType` + `Exec %f`) makes PLWL show up in Dolphin/Nautilu
|
|||
All UI text comes from JSON files in `files/translations/`. The `data` object carries the language names; `texts` maps keys to strings, with `{0}`, `{1}` ... placeholders filled at runtime.
|
||||
|
||||
The translations bundled with a release are launcher-owned: on every start they are refreshed into `files/translations/` whenever their content changed, so launcher updates deliver new/changed keys to existing installs. Extra language files the bundle does not ship (user-added) are never touched.
|
||||
|
||||
## Development
|
||||
|
||||
Codebase overview, conventions and rules for contributors: [development.md](development.md).
|
||||
|
||||
|
|
|
|||
73
development.md
Normal file
73
development.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Development
|
||||
|
||||
Short guide for anyone working on the PLWL codebase.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
src/ main process (Node/Electron backend)
|
||||
main.js entry point: tray, single-instance handoff, --launch / --open
|
||||
argv.js command-line parsing shared by boot and second-instance handoff
|
||||
window.js main window creation
|
||||
ipc/ all ipcMain handlers
|
||||
index.js handler registration, icon protocol, dialogs
|
||||
games.js games service: run/stop state, wine resolution, open-with handoff
|
||||
runner.js spawns wine/proton (env, prefix, playtime, logs)
|
||||
library.js apps registry (apps.json) + settings + playtime files
|
||||
wines.js wine/proton build scan, registry, portable symlink healing
|
||||
prefixes.js prefix registry (files/wineprefixes)
|
||||
importer.js app import (copy/move/leave/prefix)
|
||||
openwith.js "Open with PLWL": ad-hoc runs, no library entry
|
||||
shortcuts.js .desktop shortcut generation
|
||||
paths.js portable layout, FILES-relative path helpers, translation sync
|
||||
i18n.js translation loading + {0} placeholder filling
|
||||
preload.js contextBridge API exposed to the renderer
|
||||
appicon.js window/taskbar icon
|
||||
renderer/ UI (plain HTML/CSS/JS, no framework)
|
||||
app.js views, modals, custom-select, all renderer logic
|
||||
index.html static markup for every view
|
||||
style.css the whole look; CSS variables at the top
|
||||
files/ portable data the launcher ships/creates at runtime
|
||||
translations/ en-us.json, pl.json (copied to the data dir on start)
|
||||
test/ plain node scripts, run them with `node test/<name>.test.js`
|
||||
tools/ install.sh / createshortcut.sh / build helpers
|
||||
```
|
||||
|
||||
Main process vs renderer: the renderer never touches Node/Electron directly -
|
||||
everything goes through the `window.plwl` bridge (preload.js) and ipcMain
|
||||
handlers in src/ipc/. Keep new APIs in that pattern.
|
||||
|
||||
## Rules
|
||||
|
||||
- Keep it simple. Plain functions over classes, no abstractions "for later".
|
||||
- Comments explain the WHY (quirks, workarounds, races), not the WHAT. If the
|
||||
code says it, don't repeat it in words. No comments at all when nothing
|
||||
needs explaining.
|
||||
- No duplicate logic: the second time you copy something, move it into a
|
||||
helper (a shared module in src/ or a function next to its users) and call
|
||||
it from both places.
|
||||
- Everything user-visible goes through the translation files (en-us.json,
|
||||
pl.json) with `{0}`, `{1}` placeholders. No hardcoded strings in app.js.
|
||||
- Paths stored on disk are FILES-relative (portable layout): write with
|
||||
toPortablePath(), read with resolvePortablePath() - never store absolute
|
||||
paths for anything inside files/.
|
||||
- Registries (apps.json, wines.json, prefixes.json) are rewritten whole via
|
||||
writeJson(); playtime lives in its own tiny file so the registry is not
|
||||
rewritten while a game runs.
|
||||
- Deleting user data needs a confirm modal; best-effort cleanup is
|
||||
try/catch-and-ignore with a comment saying why it is safe to ignore.
|
||||
- Windows apps run through runGame()/buildEnv() - never spawn wine directly
|
||||
somewhere else (prefix handling, Proton env and playtime live there).
|
||||
- CSS: reuse the existing variables/classes (.button, .field-label,
|
||||
.custom-select, form-card); spacing rhythm comes from --button-margin and
|
||||
the flex gap of the container, not per-element magic numbers.
|
||||
- Tests are plain assert-by-print scripts (see test/): set PLWL_BUILD_DIR to
|
||||
a temp dir, check(), exit 1 on failure. Add one when fixing a bug.
|
||||
|
||||
## Verify before you commit
|
||||
|
||||
```
|
||||
node --check <changed js files> # syntax
|
||||
node test/import.test.js # ...and the other test files
|
||||
npm start # click through the flow you touched
|
||||
```
|
||||
|
|
@ -1,20 +1,9 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
function findPng() {
|
||||
const p = path.resolve(__dirname, '..', 'renderer', 'plwl.png')
|
||||
try {
|
||||
fs.accessSync(p)
|
||||
return p
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
const { launcherIconPath } = require('./paths')
|
||||
|
||||
function setAppIcon(app) {
|
||||
const png = findPng()
|
||||
const png = launcherIconPath()
|
||||
if (!png) return
|
||||
try {
|
||||
app.setIcon(png)
|
||||
|
|
|
|||
|
|
@ -40,10 +40,7 @@ function createGamesService({ getWindow, onGamesChanged }) {
|
|||
}
|
||||
|
||||
function resolveWine(game) {
|
||||
const system = { id: 'system', type: 'system', label: 'System Wine' }
|
||||
const wanted = game.wine === 'default' ? (library.loadSettings().defaultWine || 'system') : (game.wine || 'system')
|
||||
if (!wanted || wanted === 'system') return system
|
||||
return require('../wines').getWine(wanted) || system
|
||||
return require('../wines').resolveWine(game.wine || 'system')
|
||||
}
|
||||
|
||||
function handleExit(id) {
|
||||
|
|
|
|||
|
|
@ -87,8 +87,7 @@ autoQuitWhenDone = headless // booting just to run one game -> exit when it ends
|
|||
// Lives as long as the app does: shows "active in background" even when the
|
||||
// window is closed, and offers running games + Quit.
|
||||
function trayIconPath() {
|
||||
const png = path.resolve(__dirname, '..', 'renderer', 'plwl.png')
|
||||
return fs.existsSync(png) ? png : undefined
|
||||
return require('./paths').launcherIconPath() || undefined
|
||||
}
|
||||
|
||||
function createTray() {
|
||||
|
|
|
|||
|
|
@ -49,10 +49,7 @@ function textsFor(code) {
|
|||
}
|
||||
|
||||
function resolveWine(id) {
|
||||
const system = { id: 'system', type: 'system', label: 'System Wine' }
|
||||
const wanted = id === 'default' ? require('./library').loadSettings().defaultWine || 'system' : id
|
||||
if (!wanted || wanted === 'system') return system
|
||||
return require('./wines').getWine(wanted) || system
|
||||
return require('./wines').resolveWine(id)
|
||||
}
|
||||
|
||||
function resolvePrefix(name) {
|
||||
|
|
|
|||
18
src/paths.js
18
src/paths.js
|
|
@ -114,6 +114,22 @@ function resolvePortablePath(p) {
|
|||
return path.join(FILES, p)
|
||||
}
|
||||
|
||||
// The launcher's own icon (renderer/plwl.png), used by the tray, the app
|
||||
// icon and as the .desktop shortcut fallback. Cached: it never moves at
|
||||
// runtime.
|
||||
let launcherIcon = null
|
||||
function launcherIconPath() {
|
||||
if (launcherIcon === null) {
|
||||
const p = path.join(APP_ROOT, 'renderer', 'plwl.png')
|
||||
try {
|
||||
launcherIcon = fs.existsSync(p) ? p : ''
|
||||
} catch {
|
||||
launcherIcon = ''
|
||||
}
|
||||
}
|
||||
return launcherIcon || null
|
||||
}
|
||||
|
||||
function readJson(file, fallback) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
||||
|
|
@ -129,4 +145,4 @@ function writeJson(file, data) {
|
|||
fs.renameSync(tmp, file)
|
||||
}
|
||||
|
||||
module.exports = { RESOURCES, APP_ROOT, BUILD, FILES, DATA, DIRS, ensureDirs, syncBundledTranslations, readJson, writeJson, toPortablePath, resolvePortablePath }
|
||||
module.exports = { RESOURCES, APP_ROOT, BUILD, FILES, DATA, DIRS, ensureDirs, syncBundledTranslations, launcherIconPath, readJson, writeJson, toPortablePath, resolvePortablePath }
|
||||
|
|
|
|||
|
|
@ -63,8 +63,7 @@ function buildDesktopEntry(game) {
|
|||
} catch {}
|
||||
if (!icon) {
|
||||
// fall back to the launcher icon so the shortcut is never naked
|
||||
const png = path.resolve(__dirname, '..', 'renderer', 'plwl.png')
|
||||
if (fs.existsSync(png)) icon = png
|
||||
icon = require('./paths').launcherIconPath() || ''
|
||||
}
|
||||
const lines = [
|
||||
'[Desktop Entry]',
|
||||
|
|
|
|||
12
src/wines.js
12
src/wines.js
|
|
@ -156,6 +156,16 @@ function getDefaultWine() {
|
|||
return require('./library').loadSettings().defaultWine || 'system'
|
||||
}
|
||||
|
||||
// Resolve the wine build a run should use: 'default'/'system'/unknown all
|
||||
// fall back to System Wine. Shared by the games service and the ad-hoc
|
||||
// "open with" runner.
|
||||
function resolveWine(id) {
|
||||
const system = { id: 'system', type: 'system', label: 'System Wine' }
|
||||
const wanted = id === 'default' ? getDefaultWine() : id
|
||||
if (!wanted || wanted === 'system') return system
|
||||
return getWine(wanted) || system
|
||||
}
|
||||
|
||||
function setDefaultWine(id) {
|
||||
const wines = getWines()
|
||||
if (id !== 'system' && !wines.some(w => w.id === id)) return { ok: false, error: 'notFound' }
|
||||
|
|
@ -276,4 +286,4 @@ async function importWineFolder(srcDir) {
|
|||
return { ok: true, path: toPortablePath(dst), symlinksFixed }
|
||||
}
|
||||
|
||||
module.exports = { refreshWines, getWines, getWine, setWineDisabled, getDefaultWine, setDefaultWine, deleteWine, importWineFolder, findWineBinary, detectType, fixBuildSymlinks }
|
||||
module.exports = { refreshWines, getWines, getWine, resolveWine, setWineDisabled, getDefaultWine, setDefaultWine, deleteWine, importWineFolder, findWineBinary, detectType, fixBuildSymlinks }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue