diff --git a/src/App.tsx b/src/App.tsx index c550648..61dbf75 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -126,7 +126,7 @@ function App() { // Listen for progress updates from the backend const unlistenProgress = await listen('installation-progress', (event) => { console.log('Received installation-progress event:', event) - + const { title, message, progress, complete, show_instructions, instructions } = event.payload as { title: string @@ -136,32 +136,30 @@ function App() { show_instructions?: boolean instructions?: InstructionInfo } - + + // Always update progress dialog + setProgressDialog({ + visible: true, // Always set to visible - our ProgressDialog component handles exit animation + title, + message, + progress, + showInstructions: show_instructions || false, + instructions, + }) + + // If complete and no instructions, we need to schedule a game list refresh + // The dialog will auto-close with animation, but we still need to refresh the games if (complete && !show_instructions) { - // Hide dialog when complete if no instructions - setTimeout(() => { - setProgressDialog((prev) => ({ ...prev, visible: false })) - - // Only refresh games list if dialog is closing without instructions - if (!refreshInProgress.current) { - refreshInProgress.current = true - setTimeout(() => { - loadGames().then(() => { - refreshInProgress.current = false - }) - }, 100) - } - }, 1000) - } else { - // Update progress dialog - setProgressDialog({ - visible: true, - title, - message, - progress, - showInstructions: show_instructions || false, - instructions, - }) + // Schedule a refresh for after the dialog closes + if (!refreshInProgress.current) { + refreshInProgress.current = true + // Wait for dialog animation + close delay (about 1.2s total) + setTimeout(() => { + loadGames().then(() => { + refreshInProgress.current = false + }) + }, 1300) + } } }) @@ -379,9 +377,9 @@ function App() { }, []) const handleCloseProgressDialog = () => { - // Just hide the dialog without refreshing game list + // Set dialog to not visible - animation is handled by the component setProgressDialog((prev) => ({ ...prev, visible: false })) - + // Only refresh if we need to (instructions didn't trigger update) if (progressDialog.showInstructions === false && !refreshInProgress.current) { refreshInProgress.current = true @@ -650,41 +648,42 @@ function App() { // Cancel any in-progress DLC fetching if (isFetchingDlcs && activeDlcFetchId.current) { console.log(`Aborting DLC fetch for game ${activeDlcFetchId.current}`) - + // This will signal to the Rust backend that we want to stop the process invoke('abort_dlc_fetch', { gameId: activeDlcFetchId.current }).catch((err) => console.error('Error aborting DLC fetch:', err) ) - + // Reset state activeDlcFetchId.current = null setIsFetchingDlcs(false) } - + // Clear controller if (dlcFetchController.current) { dlcFetchController.current.abort() dlcFetchController.current = null } - - // Close dialog + + // Close dialog - animation is handled in DlcSelectionDialog setDlcDialog((prev) => ({ ...prev, visible: false })) } // Handle DLC selection confirmation const handleDlcConfirm = async (selectedDlcs: DlcInfo[]) => { - // Close the dialog first + // The dialog has already started its exit animation + // Just make sure it's marked as invisible setDlcDialog((prev) => ({ ...prev, visible: false })) - + const gameId = dlcDialog.gameId const game = games.find((g) => g.id === gameId) if (!game) return - + // Update local state to show installation in progress setGames((prevGames) => prevGames.map((g) => (g.id === gameId ? { ...g, installing: true } : g)) ) - + try { if (dlcDialog.isEditMode) { // If in edit mode, we're updating existing cream_api.ini @@ -697,13 +696,13 @@ function App() { showInstructions: false, instructions: undefined, }) - + // Call the backend to update the DLC configuration await invoke('update_dlc_configuration_command', { gamePath: game.path, dlcs: selectedDlcs, }) - + // Update progress dialog for completion setProgressDialog((prev) => ({ ...prev, @@ -711,10 +710,12 @@ function App() { message: 'DLC configuration updated successfully!', progress: 100, })) - - // Hide dialog after a delay + + // The ProgressDialog component will now handle the exit animation + // when progress reaches 100% after a delay + + // But we still need to reset installing state with a delay setTimeout(() => { - setProgressDialog((prev) => ({ ...prev, visible: false })) // Reset installing state setGames((prevGames) => prevGames.map((g) => (g.id === gameId ? { ...g, installing: false } : g)) @@ -731,7 +732,7 @@ function App() { showInstructions: false, instructions: undefined, }) - + // Invoke the installation with the selected DLCs await invoke('install_cream_with_dlcs_command', { gameId, @@ -740,29 +741,26 @@ function App() { console.error(`Error installing CreamLinux with selected DLCs:`, err) throw err }) - + // We don't need to manually close the dialog or update the game state // because the backend will emit progress events that handle this } } catch (error) { console.error('Error processing DLC selection:', error) - + // Show error in progress dialog setProgressDialog((prev) => ({ ...prev, message: `Error: ${error}`, progress: 100, })) - + // Reset installing state setGames((prevGames) => prevGames.map((g) => (g.id === gameId ? { ...g, installing: false } : g)) ) - - // Hide dialog after a delay - setTimeout(() => { - setProgressDialog((prev) => ({ ...prev, visible: false })) - }, 3000) + + // ProgressDialog will handle exit animation automatically } } diff --git a/src/components/DlcSelectionDialog.tsx b/src/components/DlcSelectionDialog.tsx index 0091db4..4100206 100644 --- a/src/components/DlcSelectionDialog.tsx +++ b/src/components/DlcSelectionDialog.tsx @@ -31,10 +31,13 @@ const DlcSelectionDialog: React.FC = ({ estimatedTimeLeft = '', }) => { const [selectedDlcs, setSelectedDlcs] = useState([]) - const [showContent, setShowContent] = useState(false) + // Use a simple string for animation state - keep it simple + const [animationState, setAnimationState] = useState('closed') const [searchQuery, setSearchQuery] = useState('') const [selectAll, setSelectAll] = useState(true) const [initialized, setInitialized] = useState(false) + // Track previous visibility to detect changes + const [prevVisible, setPrevVisible] = useState(false) // Initialize selected DLCs when DLC list changes useEffect(() => { @@ -50,19 +53,33 @@ const DlcSelectionDialog: React.FC = ({ } }, [visible, dlcs, initialized]) - // Handle visibility changes + // Handle animations on visibility changes useEffect(() => { - if (visible) { - // Show content immediately for better UX - const timer = setTimeout(() => { - setShowContent(true) - }, 50) - return () => clearTimeout(timer) - } else { - setShowContent(false) - setInitialized(false) // Reset initialized state when dialog closes + // Only respond to actual changes in visibility + if (visible !== prevVisible) { + if (visible) { + // Show animation + setAnimationState('visible') + } else { + // Hide animation - but only if we're currently visible + if (animationState === 'visible') { + setAnimationState('hiding') + // After animation completes, set to closed + const timer = setTimeout(() => { + setAnimationState('closed') + // Also reset initialization when fully closed + if (!visible) { + setInitialized(false) + } + }, 200) // Match animation duration + + return () => clearTimeout(timer) + } + } + // Update previous visibility + setPrevVisible(visible) } - }, [visible]) + }, [visible, prevVisible, animationState]) // Memoize filtered DLCs to avoid unnecessary recalculations const filteredDlcs = useMemo(() => { @@ -115,10 +132,11 @@ const DlcSelectionDialog: React.FC = ({ } const handleConfirm = () => { + // Just call onConfirm directly onConfirm(selectedDlcs) } - // Modified to prevent closing when loading + // Handle overlay click const handleOverlayClick = (e: React.MouseEvent) => { // Prevent clicks from propagating through the overlay e.stopPropagation() @@ -129,6 +147,11 @@ const DlcSelectionDialog: React.FC = ({ } } + // Handle the close button click + const handleClose = () => { + onClose() + } + // Count selected DLCs const selectedCount = selectedDlcs.filter((dlc) => dlc.enabled).length @@ -142,14 +165,19 @@ const DlcSelectionDialog: React.FC = ({ return '' } - if (!visible) return null + // Don't render anything if we're in closed state + if (animationState === 'closed') return null + + // Generate appropriate classes based on animation state + const dialogClasses = `dlc-dialog-overlay ${animationState === 'hiding' ? 'exiting' : 'visible'}` + const contentClasses = `dlc-selection-dialog dialog-${animationState === 'hiding' ? 'exiting' : 'visible'}` return (
-
+

{isEditMode ? 'Edit DLCs' : 'Select DLCs to Enable'}

@@ -222,7 +250,7 @@ const DlcSelectionDialog: React.FC = ({