diff --git a/main.js b/main.js index e7ed82b..f32ee8f 100755 --- a/main.js +++ b/main.js @@ -889,16 +889,26 @@ ipcMain.handle('list-apps-detailed', async () => { /^TOTAL/i, /^\*has/i ]; + let versionColIdx = 2; // par défaut, colonne 3 (0-based) + let headerParsed = false; for (const raw of lines) { let line = raw.trim(); if (!line) continue; + if (line.startsWith('APPNAME') || line.startsWith('- APPNAME')) { + // Détecter la colonne version dynamiquement + const headerCols = line.replace(/^- /, '').split('|').map(s => s.trim()); + versionColIdx = headerCols.findIndex(col => col.toLowerCase().startsWith('version')); + headerParsed = true; + continue; + } + if (line.startsWith('-------')) continue; // ignorer la ligne de séparation if (line.startsWith('\u25c6')) line = line.slice(1).trim(); if (!line) continue; - // Try to parse "name | version | type | size" separated by | if present + // Try to parse "name | ... | version | ..." separated by | if (line.includes('|')) { const cols = line.split('|').map(s => s.trim()).filter(Boolean); const name = cols[0] ? cols[0].split(/\s+/)[0].trim() : null; - const version = cols[1] ? cols[1] : null; + const version = (typeof versionColIdx === 'number' && versionColIdx >= 0 && versionColIdx < cols.length) ? cols[versionColIdx] : null; if (name && !ignoreNamePatterns.some(re => re.test(name))) { installedSet.add(name); if (version) installedDesc.set(name, version); diff --git a/src/renderer/i18n/translations.js b/src/renderer/i18n/translations.js index 9f92959..ab1d370 100644 --- a/src/renderer/i18n/translations.js +++ b/src/renderer/i18n/translations.js @@ -17,6 +17,10 @@ 'missingPm.popup.autoCta': 'Installer', 'missingPm.popup.manualTitle': 'Installation personnalisée', 'missingPm.popup.manualDesc': "Exécutez le script AM-INSTALLER dans votre terminal pour choisir entre AM ou AppMan et leurs options. Consultez la documentation pour plus d'informations.", + 'missingPm.manual.confirmTitle': "Commande copiée", + 'missingPm.manual.confirmDesc': "Commande copiée. Collez‑la dans un terminal, faites l’installation qui vous convient, puis rouvrez AM‑GUI.", + 'missingPm.manual.ok': 'OK', + 'missingPm.manual.cancel': 'Annuler', 'missingPm.popup.manualCta': 'Copier la commande', 'missingPm.popup.docs': 'Voir la documentation', 'missingPm.popup.statusIdle': 'Choisissez une option pour continuer.', @@ -217,6 +221,10 @@ 'missingPm.auto.error': 'Auto-install failed: {msg}', 'missingPm.auto.errorShort': 'Auto-install failed.', 'missingPm.manual.copied': 'Command copied. Open a terminal and paste it.', + 'missingPm.manual.confirmTitle': 'Command copied', + 'missingPm.manual.confirmDesc': 'Command copied. Paste it into a terminal, run the installation you prefer, then reopen AM-GUI.', + 'missingPm.manual.ok': 'OK', + 'missingPm.manual.cancel': 'Cancel', 'missingPm.manual.copyError': 'Unable to copy the command.', 'toast.cancelRequested': 'Cancel requested…', 'settings.gpuTitle': 'GPU acceleration', @@ -402,6 +410,10 @@ 'missingPm.popup.manualTitle': 'Installazione personalizzata', 'missingPm.popup.manualDesc': "Esegui lo script AM-INSTALLER nel terminale per scegliere tra AM o AppMan e le rispettive opzioni. Consulta la documentazione per maggiori informazioni.", 'missingPm.popup.manualCta': 'Copia il comando', + 'missingPm.manual.confirmTitle': 'Comando copiato', + 'missingPm.manual.confirmDesc': "Comando copiato. Incollalo in un terminale, esegui l'installazione che preferisci, poi riapri AM‑GUI.", + 'missingPm.manual.ok': 'OK', + 'missingPm.manual.cancel': 'Annulla', 'missingPm.popup.docs': 'Apri la documentazione', 'missingPm.popup.statusIdle': 'Scegli un\'opzione per continuare.', 'missingPm.auto.installing': 'Download e configurazione in corso…', diff --git a/src/renderer/renderer.js b/src/renderer/renderer.js index 4d7c87b..4866228 100644 --- a/src/renderer/renderer.js +++ b/src/renderer/renderer.js @@ -1750,6 +1750,27 @@ async function handleManualInstallClick() { await copyTextToClipboard(command); showToast(t('missingPm.manual.copied')); setPmPopupStatus('missingPm.manual.copied'); + + // Show a confirmation dialog instructing user what to do next + const confirmed = await openActionConfirm({ + message: t('missingPm.manual.confirmDesc'), + okLabel: t('missingPm.manual.ok'), + intent: 'install' + }); + if (confirmed) { + // close the missingPm popup and exit the app so user can follow instructions + hideMissingPmPopup(); + if (window.electronAPI?.closeWindow) { + window.electronAPI.closeWindow(); + } + } else { + // User cancelled: keep popup open (return to choices) + setPmPopupStatus('missingPm.popup.statusIdle'); + setTimeout(() => { + const ctrl = ensureMissingPmPopup(); + ctrl?.autoBtn?.focus?.(); + }, 60); + } } catch (err) { console.error('Manual install copy error', err); showToast(t('missingPm.manual.copyError')); diff --git a/style.css b/style.css index acbdede..ceda0bf 100644 --- a/style.css +++ b/style.css @@ -555,6 +555,14 @@ body.details-mode .scroll-shell { top:calc(var(--header-h) + var(--subbar-h)); } /* Recherche centrée dans header */ .header-center .search input { width:clamp(320px, 42vw, 560px); } +/* Focus : recherche atténuée (permanent) */ +.search input:focus, .search input:focus-visible { + outline: none !important; + box-shadow: 0 0 0 1.5px rgba(90,120,150,0.35) !important; + border-color: rgba(90,120,150,0.8) !important; + background: var(--card) !important; +} + .header-actions { display:flex; align-items:center; gap:8px; margin-right:12px; -webkit-app-region:no-drag; } .search input { min-width: 280px; max-width: 460px; width: 30vw; @@ -1259,6 +1267,12 @@ body.pm-popup-open { overflow: hidden; } /* Modale générique */ .modal { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; background:rgba(0,0,0,.55); z-index:600; padding:40px 30px; backdrop-filter:blur(4px); } + +/* Ensure action confirm modal is above missingPm popup layer */ +#actionConfirmModal { z-index: 1601; } + +/* Ensure action confirm modal shows above pm popup layer */ +#actionConfirmModal { z-index: 1600; } .modal[hidden] { display:none !important; } .modal-dialog { background:var(--card); border:1px solid var(--border); border-radius:14px; width: clamp(420px, 70vw, 900px); max-height:80vh; display:flex; flex-direction:column; box-shadow:var(--shadow); } .modal-header { display:flex; align-items:center; gap:12px; padding:12px 16px; border-bottom:1px solid var(--border); } @@ -1281,6 +1295,20 @@ body.pm-popup-open { overflow: hidden; } .password-modal .password-input { width:100%; padding:0.5em; margin-bottom:1em; font-size:1em; border-radius:6px; border:1px solid var(--border); background:var(--bg); color:var(--text); } .password-modal .password-error { color:#c00; display:none; margin-bottom:1em; } .password-modal .modal-actions { display:flex; gap:0.5em; justify-content:flex-end; } + +/* Focus: inputs in modals (password, confirmations, etc.) */ +.password-modal .password-input:focus, +.modal-content input:focus, +.modal-content textarea:focus, +.password-modal .password-input:focus-visible, +.modal-content input:focus-visible, +.modal-content textarea:focus-visible { + outline: none !important; + box-shadow: 0 0 0 1.5px rgba(90,120,150,0.35) !important; + border-color: rgba(90,120,150,0.8) !important; + background: var(--card) !important; +} + @media (prefers-color-scheme: dark) { .password-modal .password-error { color:#ff6b6b; } }