dlc dialog

This commit is contained in:
Tickbase
2025-05-18 11:49:50 +02:00
parent f5abcfdb6d
commit a6407c96c8
4 changed files with 95 additions and 69 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect, useCallback } from 'react'
import Dialog from './Dialog' import Dialog from './Dialog'
import DialogHeader from './DialogHeader' import DialogHeader from './DialogHeader'
import DialogBody from './DialogBody' import DialogBody from './DialogBody'
@@ -22,6 +22,7 @@ export interface DlcSelectionDialogProps {
/** /**
* DLC Selection Dialog component * DLC Selection Dialog component
* Allows users to select which DLCs they want to enable * Allows users to select which DLCs they want to enable
* Works for both initial installation and editing existing configurations
*/ */
const DlcSelectionDialog = ({ const DlcSelectionDialog = ({
visible, visible,
@@ -34,6 +35,7 @@ const DlcSelectionDialog = ({
loadingProgress = 0, loadingProgress = 0,
estimatedTimeLeft = '', estimatedTimeLeft = '',
}: DlcSelectionDialogProps) => { }: DlcSelectionDialogProps) => {
// State for DLC management
const [selectedDlcs, setSelectedDlcs] = useState<DlcInfo[]>([]) const [selectedDlcs, setSelectedDlcs] = useState<DlcInfo[]>([])
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
const [selectAll, setSelectAll] = useState(true) const [selectAll, setSelectAll] = useState(true)
@@ -41,17 +43,29 @@ const DlcSelectionDialog = ({
// Initialize selected DLCs when DLC list changes // Initialize selected DLCs when DLC list changes
useEffect(() => { useEffect(() => {
if (dlcs.length > 0 && !initialized) { if (dlcs.length > 0) {
if (!initialized) {
// Initial setup - preserve the enabled state from incoming DLCs
setSelectedDlcs(dlcs) setSelectedDlcs(dlcs)
// Determine initial selectAll state based on if all DLCs are enabled // Determine initial selectAll state based on if all DLCs are enabled
const allSelected = dlcs.every((dlc) => dlc.enabled) const allSelected = dlcs.every((dlc) => dlc.enabled)
setSelectAll(allSelected) setSelectAll(allSelected)
// Mark as initialized so we don't reset selections on subsequent DLC additions // Mark as initialized to avoid resetting selections on subsequent updates
setInitialized(true) setInitialized(true)
} else {
// Find new DLCs that aren't in our current selection
const currentAppIds = new Set(selectedDlcs.map((dlc) => dlc.appid))
const newDlcs = dlcs.filter((dlc) => !currentAppIds.has(dlc.appid))
// If we found new DLCs, add them to our selection
if (newDlcs.length > 0) {
setSelectedDlcs((prev) => [...prev, ...newDlcs])
} }
}, [dlcs, initialized]) }
}
}, [dlcs, selectedDlcs, initialized])
// Memoize filtered DLCs to avoid unnecessary recalculations // Memoize filtered DLCs to avoid unnecessary recalculations
const filteredDlcs = React.useMemo(() => { const filteredDlcs = React.useMemo(() => {
@@ -65,33 +79,22 @@ const DlcSelectionDialog = ({
}, [selectedDlcs, searchQuery]) }, [selectedDlcs, searchQuery])
// Update DLC selection status // Update DLC selection status
const handleToggleDlc = (appid: string) => { const handleToggleDlc = useCallback((appid: string) => {
setSelectedDlcs((prev) => setSelectedDlcs((prev) =>
prev.map((dlc) => (dlc.appid === appid ? { ...dlc, enabled: !dlc.enabled } : dlc)) prev.map((dlc) => (dlc.appid === appid ? { ...dlc, enabled: !dlc.enabled } : dlc))
) )
} }, [])
// Update selectAll state when individual DLC selections change // Update selectAll state when individual DLC selections change
useEffect(() => { useEffect(() => {
if (selectedDlcs.length > 0) {
const allSelected = selectedDlcs.every((dlc) => dlc.enabled) const allSelected = selectedDlcs.every((dlc) => dlc.enabled)
setSelectAll(allSelected) setSelectAll(allSelected)
}
}, [selectedDlcs]) }, [selectedDlcs])
// Handle new DLCs being added while dialog is already open // Toggle all DLCs at once
useEffect(() => { const handleToggleSelectAll = useCallback(() => {
if (initialized && dlcs.length > selectedDlcs.length) {
// Find new DLCs that aren't in our current selection
const currentAppIds = new Set(selectedDlcs.map((dlc) => dlc.appid))
const newDlcs = dlcs.filter((dlc) => !currentAppIds.has(dlc.appid))
// Add new DLCs to our selection, maintaining their enabled state
if (newDlcs.length > 0) {
setSelectedDlcs((prev) => [...prev, ...newDlcs])
}
}
}, [dlcs, selectedDlcs, initialized])
const handleToggleSelectAll = () => {
const newSelectAllState = !selectAll const newSelectAllState = !selectAll
setSelectAll(newSelectAllState) setSelectAll(newSelectAllState)
@@ -101,15 +104,29 @@ const DlcSelectionDialog = ({
enabled: newSelectAllState, enabled: newSelectAllState,
})) }))
) )
} }, [selectAll])
const handleConfirm = () => { // Submit selected DLCs to parent component
const handleConfirm = useCallback(() => {
onConfirm(selectedDlcs) onConfirm(selectedDlcs)
}, [onConfirm, selectedDlcs])
// Reset dialog state when it opens or closes
useEffect(() => {
if (!visible) {
setInitialized(false)
setSelectedDlcs([])
setSearchQuery('')
} }
}, [visible])
// Count selected DLCs // Count selected DLCs
const selectedCount = selectedDlcs.filter((dlc) => dlc.enabled).length const selectedCount = selectedDlcs.filter((dlc) => dlc.enabled).length
// Format dialog title and messages based on mode
const dialogTitle = isEditMode ? 'Edit DLCs' : 'Select DLCs to Enable'
const actionButtonText = isEditMode ? 'Save Changes' : 'Install with Selected DLCs'
// Format loading message to show total number of DLCs found // Format loading message to show total number of DLCs found
const getLoadingInfoText = () => { const getLoadingInfoText = () => {
if (isLoading && loadingProgress < 100) { if (isLoading && loadingProgress < 100) {
@@ -128,7 +145,7 @@ const DlcSelectionDialog = ({
preventBackdropClose={isLoading} preventBackdropClose={isLoading}
> >
<DialogHeader onClose={onClose}> <DialogHeader onClose={onClose}>
<h3>{isEditMode ? 'Edit DLCs' : 'Select DLCs to Enable'}</h3> <h3>{dialogTitle}</h3>
<div className="dlc-game-info"> <div className="dlc-game-info">
<span className="game-title">{gameTitle}</span> <span className="game-title">{gameTitle}</span>
<span className="dlc-count"> <span className="dlc-count">
@@ -155,7 +172,7 @@ const DlcSelectionDialog = ({
</div> </div>
</div> </div>
{isLoading && ( {isLoading && loadingProgress > 0 && (
<div className="dlc-loading-progress"> <div className="dlc-loading-progress">
<div className="progress-bar-container"> <div className="progress-bar-container">
<div className="progress-bar" style={{ width: `${loadingProgress}%` }} /> <div className="progress-bar" style={{ width: `${loadingProgress}%` }} />
@@ -210,7 +227,7 @@ const DlcSelectionDialog = ({
onClick={handleConfirm} onClick={handleConfirm}
disabled={isLoading} disabled={isLoading}
> >
{isEditMode ? 'Save Changes' : 'Install with Selected DLCs'} {actionButtonText}
</Button> </Button>
</DialogActions> </DialogActions>
</DialogFooter> </DialogFooter>

View File

@@ -29,6 +29,7 @@ export const AppProvider = ({ children }: AppProviderProps) => {
setDlcDialog, setDlcDialog,
handleDlcDialogClose: closeDlcDialog, handleDlcDialogClose: closeDlcDialog,
streamGameDlcs, streamGameDlcs,
handleGameEdit,
} = useDlcManager() } = useDlcManager()
const { const {
@@ -47,35 +48,7 @@ export const AppProvider = ({ children }: AppProviderProps) => {
info info
} = useToasts() } = useToasts()
// Combined handler for game edit // Game action handler with proper error reporting
const handleGameEdit = async (gameId: string) => {
const game = games.find(g => g.id === gameId)
if (!game || !game.cream_installed) {
showError("Cannot edit game: not found or CreamLinux not installed")
return
}
try {
// Open the dialog
setDlcDialog({
...dlcDialog,
visible: true,
gameId,
gameTitle: game.title,
isLoading: true,
isEditMode: true,
dlcs: [], // start empty
progress: 0,
})
// Now fetch DLCs in the background
streamGameDlcs(gameId)
} catch (error) {
showError(`Failed to load DLCs: ${error}`)
}
}
// Enhanced game action handler with proper error reporting
const handleGameAction = async (gameId: string, action: ActionType) => { const handleGameAction = async (gameId: string, action: ActionType) => {
const game = games.find(g => g.id === gameId) const game = games.find(g => g.id === gameId)
if (!game) { if (!game) {
@@ -83,6 +56,31 @@ export const AppProvider = ({ children }: AppProviderProps) => {
return return
} }
// For DLC installation, we want to show the DLC selection dialog first
if (action === 'install_cream') {
try {
// Show DLC selection dialog
setDlcDialog({
...dlcDialog,
visible: true,
gameId,
gameTitle: game.title,
dlcs: [], // Start with empty list
isLoading: true,
isEditMode: false, // This is a new installation
progress: 0,
})
// Start streaming DLCs
streamGameDlcs(gameId)
return
} catch (error) {
showError(`Failed to prepare DLC installation: ${error}`)
return
}
}
// For other actions (uninstall cream, install/uninstall smoke)
// Mark game as installing // Mark game as installing
setGames(prevGames => setGames(prevGames =>
prevGames.map(g => g.id === gameId ? {...g, installing: true} : g) prevGames.map(g => g.id === gameId ? {...g, installing: true} : g)
@@ -154,7 +152,9 @@ export const AppProvider = ({ children }: AppProviderProps) => {
// DLC management // DLC management
dlcDialog, dlcDialog,
handleGameEdit, handleGameEdit: (gameId: string) => {
handleGameEdit(gameId, games)
},
handleDlcDialogClose: closeDlcDialog, handleDlcDialogClose: closeDlcDialog,
// Game actions // Game actions

View File

@@ -50,7 +50,7 @@ export function useDlcManager() {
const unlistenDlcFound = await listen<string>('dlc-found', (event) => { const unlistenDlcFound = await listen<string>('dlc-found', (event) => {
const dlc = JSON.parse(event.payload) as { appid: string; name: string } const dlc = JSON.parse(event.payload) as { appid: string; name: string }
// Add the DLC to the current list with enabled=true // Add the DLC to the current list with enabled=true by default
setDlcDialog((prev) => ({ setDlcDialog((prev) => ({
...prev, ...prev,
dlcs: [...prev.dlcs, { ...dlc, enabled: true }], dlcs: [...prev.dlcs, { ...dlc, enabled: true }],
@@ -175,7 +175,7 @@ export function useDlcManager() {
dlcs: [], dlcs: [],
enabledDlcs: [], enabledDlcs: [],
isLoading: true, isLoading: true,
isEditMode: true, isEditMode: true, // This is an edit operation
progress: 0, progress: 0,
progressMessage: 'Reading DLC configuration...', progressMessage: 'Reading DLC configuration...',
timeLeft: '', timeLeft: '',

View File

@@ -81,6 +81,13 @@ export function useGameActions() {
// Unified handler for game actions (install/uninstall) // Unified handler for game actions (install/uninstall)
const handleGameAction = useCallback(async (gameId: string, action: ActionType, games: Game[]) => { const handleGameAction = useCallback(async (gameId: string, action: ActionType, games: Game[]) => {
try { try {
// For CreamLinux installation, we should NOT call process_game_action directly
// Instead, we show the DLC selection dialog first, which is handled in AppProvider
if (action === 'install_cream') {
return
}
// For other actions (uninstall_cream, install_smoke, uninstall_smoke)
// Find game to get title // Find game to get title
const game = games.find((g) => g.id === gameId) const game = games.find((g) => g.id === gameId)
if (!game) return if (!game) return
@@ -179,7 +186,7 @@ export function useGameActions() {
setProgressDialog({ setProgressDialog({
visible: true, visible: true,
title: `Installing CreamLinux for ${game.title}`, title: `Installing CreamLinux for ${game.title}`,
message: 'Processing...', message: 'Preparing to download CreamLinux...',
progress: 0, progress: 0,
showInstructions: false, showInstructions: false,
instructions: undefined, instructions: undefined,
@@ -190,6 +197,8 @@ export function useGameActions() {
gameId, gameId,
selectedDlcs, selectedDlcs,
}) })
// Note: The progress dialog will be updated through the installation-progress event listener
} }
} catch (error) { } catch (error) {
console.error('Error processing DLC selection:', error) console.error('Error processing DLC selection:', error)