diff --git a/README.md b/README.md index 005b7fe..fb864fb 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,6 @@ cd PLWL ./olcxja.plwl/olcxja.plwl ``` -## Importing from a wine prefix - -Besides copy/move/leave, an app can be imported **from a wine prefix**: pick the prefix, then the app folder inside its `drive_c`. Nothing is copied — paths are stored relative to the prefix, so wine sees the app as if it were normally installed on `C:\` (registry entries pointing to `C:\...` keep working). Both layouts work: plain wine prefixes (`drive_c` at the top) and Proton-managed ones (`drive_c` under `pfx/`). The app stays tied to that prefix; removing the launcher entry never deletes files from the prefix. - ## Wine & Proton support - **System Wine** — uses the `wine` binary from PATH. diff --git a/files/translations/en-us.json b/files/translations/en-us.json index c06afd5..7895a87 100644 --- a/files/translations/en-us.json +++ b/files/translations/en-us.json @@ -28,22 +28,14 @@ "library.never": "Never", "library.portable": "Portable", "library.external": "External", - "library.inPrefix": "In prefix", "library.removeConfirm": "Remove {0} from the launcher?{1}", "library.removeFilesNote": "\nIts imported files in files/apps will also be deleted.", - "library.removePrefixNote": "\nFiles stay in the prefix - only the launcher entry is removed.", "library.tools": "Wine tools", "library.viewLogs": "View logs", "import.question": "Should the app files be brought into the launcher so it stays portable, or left where they are?", "import.mode.copy": "Copy into launcher (portable)", "import.mode.move": "Move into launcher (portable)", "import.mode.leave": "Leave in place", - "import.mode.prefix": "Import from a wine prefix", - "import.prefix.title": "Import from prefix", - "import.prefix.hint": "Pick the wine prefix the app lives in. Nothing is copied - the app is launched from the prefix, so Windows paths keep working (C:\\...).", - "import.pickPrefixFolder": "Choose the app folder inside the prefix (drive_c)", - "import.prefix.outside": "the selected folder is outside the prefix's drive_c", - "import.prefix.noDriveC": "this prefix has no drive_c yet - run any app in it once so wine creates it", "import.pickFolder": "Choose the app folder", "import.progress": "Importing {0}…", "import.fileDone": "{0} files copied", @@ -69,8 +61,6 @@ "config.wineDefault": "Default ({0})", "config.prefix": "Wine prefix", "config.prefixDefault": "Default prefix ({0})", - "config.prefixLocked": "This app was imported from a prefix - its prefix cannot be changed", - "config.prefixLockedHint": "Pinned to the prefix it was imported from (its paths are stored relative to it)", "config.dllOverrides": "DLL overrides", "config.overrides.add": "Add override", "config.overrides.dll": "DLL name", diff --git a/files/translations/pl.json b/files/translations/pl.json index 7c8568e..3c36138 100644 --- a/files/translations/pl.json +++ b/files/translations/pl.json @@ -28,22 +28,14 @@ "library.never": "Nigdy", "library.portable": "Przenośna", "library.external": "Zewnętrzna", - "library.inPrefix": "W prefiksie", "library.removeConfirm": "Usunąć {0} z launchera?{1}", "library.removeFilesNote": "\nJej zaimportowane pliki w files/apps również zostaną usunięte.", - "library.removePrefixNote": "\nPliki zostają w prefiksie - usuwany jest tylko wpis w launcherze.", "library.tools": "Narzędzia Wine", "library.viewLogs": "Podejrzyj logi", "import.question": "Czy pliki aplikacji mają trafić do launchera, aby był przenośny, czy mają zostać tam, gdzie są?", "import.mode.copy": "Skopiuj do launchera (przenośne)", "import.mode.move": "Przenieś do launchera (przenośne)", "import.mode.leave": "Zostaw w miejscu", - "import.mode.prefix": "Importuj z prefiksu wine", - "import.prefix.title": "Import z prefiksu", - "import.prefix.hint": "Wybierz prefiks wine, w którym jest aplikacja. Nic nie jest kopiowane - aplikacja odpalana jest z prefiksu, więc ścieżki windowsowe (C:\\...) dalej działają.", - "import.pickPrefixFolder": "Wybierz folder aplikacji wewnątrz prefiksu (drive_c)", - "import.prefix.outside": "wybrany folder jest poza drive_c prefiksu", - "import.prefix.noDriveC": "ten prefiks nie ma jeszcze drive_c - uruchom w nim dowolną apkę raz, żeby wine je utworzył", "import.pickFolder": "Wybierz folder aplikacji", "import.progress": "Importowanie {0}…", "import.fileDone": "Skopiowane pliki: {0}", @@ -69,8 +61,6 @@ "config.wineDefault": "Domyślna ({0})", "config.prefix": "Prefiks Wine", "config.prefixDefault": "Prefiks domyślny ({0})", - "config.prefixLocked": "Ta aplikacja została zaimportowana z prefiksu - nie można zmienić jej prefiksu", - "config.prefixLockedHint": "Przypięta do prefiksu, z którego została zaimportowana (ścieżki trzymane są względem niego)", "config.dllOverrides": "Nadpisania DLL", "config.overrides.add": "Dodaj nadpisanie", "config.overrides.dll": "Nazwa DLL", diff --git a/package.json b/package.json index 7a04c09..8148eaa 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "main": "src/main.js", "scripts": { "start": "electron .", - "test": "node test/import.test.js && node test/prefix-import.test.js && node test/icons.test.js && node test/shortcuts.test.js && node test/wines-portable.test.js", - "smoke": "node test/import.test.js && node test/prefix-import.test.js && node test/icons.test.js && node test/shortcuts.test.js && node test/wines-portable.test.js" + "test": "node test/import.test.js && node test/icons.test.js && node test/shortcuts.test.js && node test/wines-portable.test.js", + "smoke": "node test/import.test.js && node test/icons.test.js && node test/shortcuts.test.js && node test/wines-portable.test.js" }, "devDependencies": { "@electron/asar": "^3.4.1", diff --git a/renderer/app.js b/renderer/app.js index f9ccae2..f08d8cc 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -104,20 +104,8 @@ function toast(message, kind) { }) } -// The teardown of a closing modal is delayed (for the fade-out) - an -// eagerly armed timer must never kill a modal opened in the meantime (e.g. -// import mode -> prefix picker happens within milliseconds), so showModal -// cancels any pending teardown and clears the closing state. -let modalTeardownTimer = null - function showModal(html) { - if (modalTeardownTimer) { - clearTimeout(modalTeardownTimer) - modalTeardownTimer = null - } const overlay = qs('#modal-overlay') - const modal = qs('#modal') - modal.classList.remove('closing') qs('#modal').innerHTML = html overlay.style.display = 'flex' requestAnimationFrame(() => requestAnimationFrame(() => overlay.classList.add('show'))) @@ -130,9 +118,7 @@ function closeModal() { if (overlay.style.display === 'none') return modal.classList.add('closing') overlay.classList.remove('show') - if (modalTeardownTimer) clearTimeout(modalTeardownTimer) - modalTeardownTimer = setTimeout(() => { - modalTeardownTimer = null + setTimeout(() => { overlay.style.display = 'none' modal.classList.remove('closing') modal.innerHTML = '' @@ -166,7 +152,6 @@ class CustomSelect { this.options = options this.value = value this.onChange = onChange - this.disabled = false this.render() if (!this.host.dataset.wired) { this.wire() @@ -174,11 +159,6 @@ class CustomSelect { } } - setDisabled(v) { - this.disabled = !!v - this.host.classList.toggle('disabled', this.disabled) - } - render() { const selected = this.options.find(o => o.value === this.value) this.host.classList.add('custom-select') @@ -225,7 +205,6 @@ class CustomSelect { } toggle() { - if (this.disabled) return const wasOpen = this.host.classList.contains('open') CustomSelect.closeAll() if (!wasOpen) this.host.classList.add('open') @@ -331,29 +310,6 @@ function absPathInput(v) { return v } -// Prefix-imported apps: show paths the way wine sees them - relative to the -// prefix ("pfx/drive_c/..." or "drive_c/...") instead of the long -// files/wineprefixes//... form. -function displayGamePath(p, game) { - if (game && game.importSource === 'prefix' && game.prefix) { - const files = window.plwl.FILES_DIR - const base = files ? files + '/wineprefixes/' + game.prefix + '/' : '' - if (base && p && p.startsWith(base)) return p.slice(base.length) - } - return displayPath(p) -} - -// Inverse of displayGamePath: accept a prefix-relative (drive_c/... or -// pfx/drive_c/...) path and resolve it under the game's prefix; anything -// else falls through to the normal FILES/absolute handling. -function absGamePathInput(v, game) { - if (game && game.importSource === 'prefix' && game.prefix && /^(pfx\/)?drive_c([\/]|$)/i.test(v || '')) { - const files = window.plwl.FILES_DIR - if (files) return files + '/wineprefixes/' + game.prefix + '/' + v - } - return absPathInput(v) -} - function fmtPlaytime(seconds) { const s = Math.round(seconds || 0) if (s < 60) return t('library.playtimeSeconds', s) @@ -364,23 +320,15 @@ function fmtPlaytime(seconds) { } async function attachCardIcon(card, game) { - // placeholder first so every card has a stable icon slot; swapped for the - // real icon once it loads - games without one just keep the placeholder - const ph = document.createElement('div') - ph.className = 'game-icon placeholder' - card.prepend(ph) const p = await window.plwl.gamesIconPath(game.id) if (!p) return const img = document.createElement('img') img.className = 'game-icon' img.alt = '' - img.onerror = () => img.remove() // placeholder stays visible - img.onload = () => { - ph.remove() - card.prepend(img) - } + img.onerror = () => img.remove() + img.onload = () => card.prepend(img) img.src = iconUrl(game.id) - if (img.complete && img.naturalWidth > 0) img.onload() + if (img.complete && img.naturalWidth > 0) card.prepend(img) } function renderLibrary() { @@ -393,15 +341,11 @@ function renderLibrary() { card.className = 'game-card' card.style.animationDelay = `${idx * 0.04}s` const portable = game.mode === 'copy' || game.mode === 'move' - const inPrefix = game.importSource === 'prefix' - const sourceBadge = inPrefix - ? `${t('library.inPrefix')}${game.prefix ? ' · ' + game.prefix : ''}` - : `${portable ? t('library.portable') : t('library.external')}` const last = game.lastPlayed ? new Date(game.lastPlayed).toLocaleDateString() : t('library.never') card.innerHTML = `

- ${sourceBadge} + ${portable ? t('library.portable') : t('library.external')} ${wineLabel(game.wine)}
${t('library.lastPlayed', last)}
@@ -438,9 +382,7 @@ function renderLibrary() { else toast(t('library.viewLogs')) } card.querySelector('[data-act=remove]').onclick = async () => { - const note = game.mode === 'copy' || game.mode === 'move' - ? t('library.removeFilesNote') - : (inPrefix ? t('library.removePrefixNote') : '') + const note = game.mode === 'copy' || game.mode === 'move' ? t('library.removeFilesNote') : '' const ok = await confirmModal(t('library.removeConfirm', game.name, note), t('library.remove')) if (!ok) return await window.plwl.gamesRemove(game.id) @@ -504,7 +446,6 @@ async function startImportFlow() { -