This commit is contained in:
Tickbase
2026-04-30 21:00:09 +02:00
parent cf7fe20aa6
commit 348b1a5ed0
3 changed files with 169 additions and 9 deletions

View File

@@ -25,7 +25,7 @@ import {
} from '@/components/dialogs'
// Game components
import { GameList } from '@/components/games'
import { GameList, EpicGameList } from '@/components/games'
/**
* Main application component
@@ -71,11 +71,25 @@ function App() {
handleSelectCreamLinux,
handleSelectSmokeAPI,
closeUnlockerDialog,
epicGames,
epicLoading,
epicInstallingId,
loadEpicGames,
handleEpicInstall,
handleEpicUninstallScream,
handleEpicUninstallKoaloader,
handleEpicSettings,
} = useAppContext()
// Conflict detection
const { conflicts, showDialog, resolveConflict, closeDialog } =
useConflictDetection(games)
const { conflicts, showDialog, resolveConflict, closeDialog } = useConflictDetection(games)
const handleSetFilter = async (f: string) => {
setFilter(f)
if (f === 'epic' && epicGames.length === 0 && !epicLoading) {
await loadEpicGames()
}
}
// Handle conflict resolution
const handleConflictResolve = async (
@@ -126,13 +140,22 @@ function App() {
<div className="main-content">
{/* Sidebar for filtering */}
<Sidebar
setFilter={setFilter}
setFilter={handleSetFilter}
currentFilter={filter}
onSettingsClick={handleSettingsOpen}
/>
{/* Show error or game list */}
{error ? (
{filter === 'epic' ? (
<EpicGameList
games={epicGames}
isLoading={epicLoading}
installingId={epicInstallingId}
onInstall={handleEpicInstall}
onUninstallScream={handleEpicUninstallScream}
onUninstallKoaloader={handleEpicUninstallKoaloader}
onSettings={handleEpicSettings}
/>
) : error ? (
<div className="error-message">
<h3>Error Loading Games</h3>
<p>{error}</p>

View File

@@ -1,5 +1,5 @@
import { createContext } from 'react'
import { Game, DlcInfo } from '@/types'
import { Game, DlcInfo, EpicGame } from '@/types'
import { ActionType } from '@/components/buttons/ActionButton'
import { DlcDialogState } from '@/hooks/useDlcManager'
@@ -49,6 +49,16 @@ export interface AppContextType {
handleDlcDialogClose: () => void
handleUpdateDlcs: (gameId: string) => Promise<void>
// Epic Games
epicGames: EpicGame[]
epicLoading: boolean
epicInstallingId: string | null
loadEpicGames: () => Promise<void>
handleEpicInstall: (game: EpicGame) => void
handleEpicUninstallScream: (game: EpicGame) => void
handleEpicUninstallKoaloader: (game: EpicGame) => void
handleEpicSettings: (game: EpicGame) => void
// Game actions
progressDialog: ProgressDialogState
handleGameAction: (gameId: string, action: ActionType) => Promise<void>

View File

@@ -1,11 +1,12 @@
import { ReactNode, useState, useEffect } from 'react'
import { AppContext, AppContextType } from './AppContext'
import { useGames, useDlcManager, useGameActions, useToasts } from '@/hooks'
import { DlcInfo, Config } from '@/types'
import { DlcInfo, Config, EpicGame } from '@/types'
import { ActionType } from '@/components/buttons/ActionButton'
import { ToastContainer } from '@/components/notifications'
import { SmokeAPISettingsDialog, OptInDialog, RatingDialog, SmokeAPIVotesDialog } from '@/components/dialogs'
import { SmokeAPISettingsDialog, OptInDialog, RatingDialog, SmokeAPIVotesDialog, EpicUnlockerSelectionDialog, ScreamAPISettingsDialog } from '@/components/dialogs'
import { invoke } from '@tauri-apps/api/core'
import { listen } from '@tauri-apps/api/event'
// Context provider component
interface AppProviderProps {
@@ -43,6 +44,20 @@ export const AppProvider = ({ children }: AppProviderProps) => {
// Settings dialog state
const [settingsDialog, setSettingsDialog] = useState({ visible: false })
const [epicGames, setEpicGames] = useState<EpicGame[]>([])
const [epicLoading, setEpicLoading] = useState(false)
const [epicInstallingId, setEpicInstallingId] = useState<string | null>(null)
const [epicUnlockerDialog, setEpicUnlockerDialog] = useState<{
visible: boolean
game: EpicGame | null
}>({ visible: false, game: null })
const [screamSettingsDialog, setScreamSettingsDialog] = useState<{
visible: boolean
game: EpicGame | null
}>({ visible: false, game: null })
// SmokeAPI settings dialog state
const [smokeAPISettingsDialog, setSmokeAPISettingsDialog] = useState<{
visible: boolean
@@ -95,6 +110,91 @@ export const AppProvider = ({ children }: AppProviderProps) => {
.catch((err) => console.error('Failed to load config for reporting check:', err))
}, [])
useEffect(() => {
let unlisten: (() => void) | undefined
listen<EpicGame>('epic-game-updated', (event) => {
const updated = event.payload
const prev = epicGames.find((g) => g.app_name === updated.app_name)
setEpicGames((games) =>
games.map((g) => (g.app_name === updated.app_name ? updated : g))
)
setEpicInstallingId(null)
// Determine what changed and show appropriate toast
if (prev) {
const installedScream = !prev.scream_installed && updated.scream_installed
const uninstalledScream = prev.scream_installed && !updated.scream_installed
const installedKoa = !prev.koaloader_installed && updated.koaloader_installed
const uninstalledKoa = prev.koaloader_installed && !updated.koaloader_installed
if (installedScream) {
success(`ScreamAPI installed for ${updated.title}`)
} else if (uninstalledScream) {
info(`ScreamAPI removed from ${updated.title}`)
} else if (installedKoa) {
success(`Koaloader installed for ${updated.title}`)
} else if (uninstalledKoa) {
info(`Koaloader removed from ${updated.title}`)
}
if (updated.proxy_fallback_used) {
warning(
'No compatible proxy import found - installed using version.dll as a fallback. ' +
'If the game has issues, try the direct ScreamAPI method instead.'
)
}
}
}).then((fn) => { unlisten = fn })
return () => { unlisten?.() }
}, [epicGames, success, info, warning])
const loadEpicGames = async () => {
setEpicLoading(true)
try {
const games = await invoke<EpicGame[]>('scan_epic_games')
setEpicGames(games)
} catch (e) {
showError(`Failed to scan Epic games: ${e}`)
} finally {
setEpicLoading(false)
}
}
const runEpicAction = async (game: EpicGame, action: string) => {
setEpicInstallingId(game.app_name)
try {
await invoke('process_epic_action', { epicAction: { game, action } })
// state updated via epic-game-updated event listener
} catch (e) {
showError(`Action failed: ${e}`)
setEpicInstallingId(null)
}
}
const handleEpicInstall = (game: EpicGame) => {
setEpicUnlockerDialog({ visible: true, game })
}
const handleEpicUninstallScream = (game: EpicGame) => runEpicAction(game, 'uninstall_scream')
const handleEpicUninstallKoaloader = (game: EpicGame) => runEpicAction(game, 'uninstall_koaloader')
const handleEpicSettings = (game: EpicGame) => {
setScreamSettingsDialog({ visible: true, game })
}
const handleSelectScreamAPI = () => {
const game = epicUnlockerDialog.game
setEpicUnlockerDialog({ visible: false, game: null })
if (game) runEpicAction(game, 'install_scream')
}
const handleSelectKoaloader = () => {
const game = epicUnlockerDialog.game
setEpicUnlockerDialog({ visible: false, game: null })
if (game) runEpicAction(game, 'install_koaloader')
}
// Settings handlers
const handleSettingsOpen = () => {
setSettingsDialog({ visible: true })
@@ -366,6 +466,16 @@ export const AppProvider = ({ children }: AppProviderProps) => {
handleDlcDialogClose: closeDlcDialog,
handleUpdateDlcs: (gameId: string) => handleUpdateDlcs(gameId),
// Epic games
epicGames,
epicLoading,
epicInstallingId,
loadEpicGames,
handleEpicInstall,
handleEpicUninstallScream,
handleEpicUninstallKoaloader,
handleEpicSettings,
// Game actions
progressDialog,
handleGameAction,
@@ -458,6 +568,23 @@ export const AppProvider = ({ children }: AppProviderProps) => {
gameTitle={smokeAPISettingsDialog.gameTitle}
/>
{/* Epic Unlocker Selection Dialog */}
<EpicUnlockerSelectionDialog
visible={epicUnlockerDialog.visible}
game={epicUnlockerDialog.game}
onClose={() => setEpicUnlockerDialog({ visible: false, game: null })}
onSelectScreamAPI={handleSelectScreamAPI}
onSelectKoaloader={handleSelectKoaloader}
/>
{/* ScreamAPI Settings Dialog */}
<ScreamAPISettingsDialog
visible={screamSettingsDialog.visible}
onClose={() => setScreamSettingsDialog({ visible: false, game: null })}
gamePath={screamSettingsDialog.game?.install_path ?? ''}
gameTitle={screamSettingsDialog.game?.title ?? ''}
/>
{/* SmokeAPI Votes Dialog */}
<SmokeAPIVotesDialog
visible={smokeAPIVotesDialog.visible}