import { FC, useState, useEffect, CSSProperties } from 'react'; import { ModalRoot, DialogBody, DialogHeader, DialogControlsSection, DialogControlsSectionHeader, ButtonItem, PanelSectionRow, Field, Toggle, Spinner, Focusable, showModal, ConfirmModal } from '@decky/ui'; import { FaCheck, FaTimes, FaDownload, FaTrash, FaCog } from 'react-icons/fa'; import flatpakTargetImage from '../../assets/flatpak-target.png'; import { checkFlatpakExtensionStatus, installFlatpakExtension, uninstallFlatpakExtension, getFlatpakApps, setFlatpakAppOverride, removeFlatpakAppOverride, FlatpakExtensionStatus, FlatpakApp, FlatpakAppInfo } from '../api/lsfgApi'; import t from '../i18n/i18n'; interface FlatpaksModalProps { closeModal?: () => void; } export const FlatpaksModal: FC = ({ closeModal }) => { const [extensionStatus, setExtensionStatus] = useState(null); const [flatpakApps, setFlatpakApps] = useState(null); const [loading, setLoading] = useState(true); const [operationInProgress, setOperationInProgress] = useState(null); const loadData = async () => { setLoading(true); try { const [statusResult, appsResult] = await Promise.all([ checkFlatpakExtensionStatus(), getFlatpakApps() ]); setExtensionStatus(statusResult); setFlatpakApps(appsResult); } catch (error) { console.error('Error loading Flatpak data:', error); } finally { setLoading(false); } }; useEffect(() => { loadData(); }, []); const handleExtensionOperation = async (operation: 'install' | 'uninstall', version: string) => { const operationId = `${operation}-${version}`; setOperationInProgress(operationId); try { const result = operation === 'install' ? await installFlatpakExtension(version) : await uninstallFlatpakExtension(version); if (result.success) { // Reload status after operation const newStatus = await checkFlatpakExtensionStatus(); setExtensionStatus(newStatus); } } catch (error) { console.error(`Error ${operation}ing extension:`, error); } finally { setOperationInProgress(null); } }; const handleAppOverrideToggle = async (app: FlatpakApp) => { const hasOverrides = app.has_filesystem_override && app.has_env_override; const operationId = `app-${app.app_id}`; setOperationInProgress(operationId); try { const result = hasOverrides ? await removeFlatpakAppOverride(app.app_id) : await setFlatpakAppOverride(app.app_id); if (result.success) { // Reload apps data after operation const newApps = await getFlatpakApps(); setFlatpakApps(newApps); } } catch (error) { console.error('Error toggling app override:', error); } finally { setOperationInProgress(null); } }; const confirmOperation = (operation: () => void, title: string, description: string) => { showModal( {}} /> ); }; if (loading) { return ( {t('FLATPAK_MODAL_TITLE', 'Flatpak Extensions')}
); } const instructionSteps = [ { id: 'try-first', title: t('FLATPAK_STEP_TRY_FIRST', 'Try first:'), command: '~/lsfg' }, { id: 'try-full-path', title: t('FLATPAK_STEP_TRY_FULL_PATH', "If that doesn't work, try full path:"), command: '/home/(username)/lsfg' }, { id: 'final-result', title: t('FLATPAK_STEP_FINAL', 'Final result should look like:'), command: '~/lsfg "usr/bin/flatpak"' } ]; const focusableInstructionStyle: CSSProperties = { padding: '10px', background: 'rgba(0, 0, 0, 0.3)', borderRadius: '6px', marginBottom: '12px' }; const commandStyle: CSSProperties = { fontFamily: 'monospace', fontSize: '0.85em', background: 'rgba(0, 0, 0, 0.45)', padding: '8px', borderRadius: '4px', marginTop: '6px' }; return ( {t('FLATPAK_MODAL_TITLE', 'Flatpak Extensions')} {/* Extension Status Section */} {t('FLATPAK_RUNTIME_INSTALLER', 'Runtime Extension Installer')} {extensionStatus && extensionStatus.success ? ( <> {/* 23.08 Runtime */} : } > { const operation = extensionStatus.installed_23_08 ? 'uninstall' : 'install'; const action = () => handleExtensionOperation(operation, '23.08'); if (operation === 'uninstall') { confirmOperation( action, t('FLATPAK_UNINSTALL_TITLE', 'Uninstall Runtime Extension'), `${t('FLATPAK_UNINSTALL_CONFIRM_PREFIX', 'Are you sure you want to uninstall the')} 23.08 ${t('FLATPAK_UNINSTALL_CONFIRM_SUFFIX', 'runtime extension?')}` ); } else { action(); } }} disabled={operationInProgress === 'install-23.08' || operationInProgress === 'uninstall-23.08'} > {operationInProgress === 'install-23.08' || operationInProgress === 'uninstall-23.08' ? ( ) : extensionStatus.installed_23_08 ? ( <> {t('FLATPAK_UNINSTALL_BTN', 'Uninstall')} ) : ( <> {t('FLATPAK_INSTALL_BTN', 'Install')} )} {/* 24.08 Runtime */} : } > { const operation = extensionStatus.installed_24_08 ? 'uninstall' : 'install'; const action = () => handleExtensionOperation(operation, '24.08'); if (operation === 'uninstall') { confirmOperation( action, t('FLATPAK_UNINSTALL_TITLE', 'Uninstall Runtime Extension'), `${t('FLATPAK_UNINSTALL_CONFIRM_PREFIX', 'Are you sure you want to uninstall the')} 24.08 ${t('FLATPAK_UNINSTALL_CONFIRM_SUFFIX', 'runtime extension?')}` ); } else { action(); } }} disabled={operationInProgress === 'install-24.08' || operationInProgress === 'uninstall-24.08'} > {operationInProgress === 'install-24.08' || operationInProgress === 'uninstall-24.08' ? ( ) : extensionStatus.installed_24_08 ? ( <> {t('FLATPAK_UNINSTALL_BTN', 'Uninstall')} ) : ( <> {t('FLATPAK_INSTALL_BTN', 'Install')} )} {/* 25.08 Runtime */} : } > { const operation = extensionStatus.installed_25_08 ? 'uninstall' : 'install'; const action = () => handleExtensionOperation(operation, '25.08'); if (operation === 'uninstall') { confirmOperation( action, t('FLATPAK_UNINSTALL_TITLE', 'Uninstall Runtime Extension'), `${t('FLATPAK_UNINSTALL_CONFIRM_PREFIX', 'Are you sure you want to uninstall the')} 25.08 ${t('FLATPAK_UNINSTALL_CONFIRM_SUFFIX', 'runtime extension?')}` ); } else { action(); } }} disabled={operationInProgress === 'install-25.08' || operationInProgress === 'uninstall-25.08'} > {operationInProgress === 'install-25.08' || operationInProgress === 'uninstall-25.08' ? ( ) : extensionStatus.installed_25_08 ? ( <> {t('FLATPAK_UNINSTALL_BTN', 'Uninstall')} ) : ( <> {t('FLATPAK_INSTALL_BTN', 'Install')} )} ) : ( } /> )} {/* Flatpak Apps Section */} {t('FLATPAK_APPS_TITLE', 'Flatpak Applications')} {flatpakApps && flatpakApps.success ? ( flatpakApps.apps.length > 0 ? ( flatpakApps.apps.map((app) => { const hasOverrides = app.has_filesystem_override && app.has_env_override; const partialOverrides = app.has_filesystem_override || app.has_env_override; let statusColor = 'red'; let statusText = t('FLATPAK_STATUS_NO_OVERRIDES', 'No overrides'); if (hasOverrides) { statusColor = 'green'; statusText = t('FLATPAK_STATUS_CONFIGURED', 'Configured'); } else if (partialOverrides) { statusColor = 'orange'; statusText = t('FLATPAK_STATUS_PARTIAL', 'Partial'); } return ( } > handleAppOverrideToggle(app)} disabled={operationInProgress === `app-${app.app_id}`} /> ); }) ) : ( ) ) : ( } /> )} {/* Steam Configuration Instructions */} {t('FLATPAK_STEAM_CONFIG_TITLE', 'Steam Configuration')}
{t('FLATPAK_STEAM_CONFIG_HEADER', 'Configure Steam Flatpak Shortcuts')}
{t('FLATPAK_STEAM_CONFIG_DESC', 'In Steam, open your flatpak game and click the cog wheel.')}
IMPORTANT: {t('FLATPAK_STEAM_CONFIG_IMPORTANT', 'Set this in TARGET (NOT LAUNCH OPTIONS)')}
{instructionSteps.map((step) => ( {}} style={focusableInstructionStyle} >
{step.title}
{step.command}
))} {}} style={{ marginTop: '4px' }} >
Steam Properties Target Field Example
{/* Close Button */} {t('FLATPAK_CLOSE', 'Close')}
); };