mirror of
https://github.com/Novattz/creamlinux-installer.git
synced 2025-12-06 03:55:37 -05:00
Revert "Broken animation changes but ill fix it"
This reverts commit e29f44bbd5.
This commit is contained in:
100
src/App.tsx
100
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,30 +136,32 @@ 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) {
|
||||
// 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)
|
||||
}
|
||||
// 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,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -377,9 +379,9 @@ function App() {
|
||||
}, [])
|
||||
|
||||
const handleCloseProgressDialog = () => {
|
||||
// Set dialog to not visible - animation is handled by the component
|
||||
// Just hide the dialog without refreshing game list
|
||||
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
|
||||
@@ -648,42 +650,41 @@ 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 - animation is handled in DlcSelectionDialog
|
||||
|
||||
// Close dialog
|
||||
setDlcDialog((prev) => ({ ...prev, visible: false }))
|
||||
}
|
||||
|
||||
// Handle DLC selection confirmation
|
||||
const handleDlcConfirm = async (selectedDlcs: DlcInfo[]) => {
|
||||
// The dialog has already started its exit animation
|
||||
// Just make sure it's marked as invisible
|
||||
// Close the dialog first
|
||||
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
|
||||
@@ -696,13 +697,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,
|
||||
@@ -710,12 +711,10 @@ function App() {
|
||||
message: 'DLC configuration updated successfully!',
|
||||
progress: 100,
|
||||
}))
|
||||
|
||||
// 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
|
||||
|
||||
// Hide dialog after a delay
|
||||
setTimeout(() => {
|
||||
setProgressDialog((prev) => ({ ...prev, visible: false }))
|
||||
// Reset installing state
|
||||
setGames((prevGames) =>
|
||||
prevGames.map((g) => (g.id === gameId ? { ...g, installing: false } : g))
|
||||
@@ -732,7 +731,7 @@ function App() {
|
||||
showInstructions: false,
|
||||
instructions: undefined,
|
||||
})
|
||||
|
||||
|
||||
// Invoke the installation with the selected DLCs
|
||||
await invoke('install_cream_with_dlcs_command', {
|
||||
gameId,
|
||||
@@ -741,26 +740,29 @@ 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))
|
||||
)
|
||||
|
||||
// ProgressDialog will handle exit animation automatically
|
||||
|
||||
// Hide dialog after a delay
|
||||
setTimeout(() => {
|
||||
setProgressDialog((prev) => ({ ...prev, visible: false }))
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,13 +31,10 @@ const DlcSelectionDialog: React.FC<DlcSelectionDialogProps> = ({
|
||||
estimatedTimeLeft = '',
|
||||
}) => {
|
||||
const [selectedDlcs, setSelectedDlcs] = useState<DlcInfo[]>([])
|
||||
// Use a simple string for animation state - keep it simple
|
||||
const [animationState, setAnimationState] = useState('closed')
|
||||
const [showContent, setShowContent] = useState(false)
|
||||
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(() => {
|
||||
@@ -53,33 +50,19 @@ const DlcSelectionDialog: React.FC<DlcSelectionDialogProps> = ({
|
||||
}
|
||||
}, [visible, dlcs, initialized])
|
||||
|
||||
// Handle animations on visibility changes
|
||||
// Handle visibility changes
|
||||
useEffect(() => {
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
}, [visible, prevVisible, animationState])
|
||||
}, [visible])
|
||||
|
||||
// Memoize filtered DLCs to avoid unnecessary recalculations
|
||||
const filteredDlcs = useMemo(() => {
|
||||
@@ -132,11 +115,10 @@ const DlcSelectionDialog: React.FC<DlcSelectionDialogProps> = ({
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
// Just call onConfirm directly
|
||||
onConfirm(selectedDlcs)
|
||||
}
|
||||
|
||||
// Handle overlay click
|
||||
// Modified to prevent closing when loading
|
||||
const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
// Prevent clicks from propagating through the overlay
|
||||
e.stopPropagation()
|
||||
@@ -147,11 +129,6 @@ const DlcSelectionDialog: React.FC<DlcSelectionDialogProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the close button click
|
||||
const handleClose = () => {
|
||||
onClose()
|
||||
}
|
||||
|
||||
// Count selected DLCs
|
||||
const selectedCount = selectedDlcs.filter((dlc) => dlc.enabled).length
|
||||
|
||||
@@ -165,19 +142,14 @@ const DlcSelectionDialog: React.FC<DlcSelectionDialogProps> = ({
|
||||
return ''
|
||||
}
|
||||
|
||||
// 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'}`
|
||||
if (!visible) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={dialogClasses}
|
||||
className={`dlc-dialog-overlay ${showContent ? 'visible' : ''}`}
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
<div className={contentClasses}>
|
||||
<div className={`dlc-selection-dialog ${showContent ? 'dialog-visible' : ''}`}>
|
||||
<div className="dlc-dialog-header">
|
||||
<h3>{isEditMode ? 'Edit DLCs' : 'Select DLCs to Enable'}</h3>
|
||||
<div className="dlc-game-info">
|
||||
@@ -250,7 +222,7 @@ const DlcSelectionDialog: React.FC<DlcSelectionDialogProps> = ({
|
||||
<div className="dlc-dialog-actions">
|
||||
<button
|
||||
className="cancel-button"
|
||||
onClick={handleClose}
|
||||
onClick={onClose}
|
||||
disabled={isLoading && loadingProgress < 10} // Briefly disable to prevent accidental closing at start
|
||||
>
|
||||
Cancel
|
||||
@@ -264,4 +236,4 @@ const DlcSelectionDialog: React.FC<DlcSelectionDialogProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default DlcSelectionDialog
|
||||
export default DlcSelectionDialog
|
||||
|
||||
@@ -27,56 +27,24 @@ const ProgressDialog: React.FC<ProgressDialogProps> = ({
|
||||
onClose,
|
||||
}) => {
|
||||
const [copySuccess, setCopySuccess] = useState(false)
|
||||
// Use a simple string for animation state - keep it simple
|
||||
const [animationState, setAnimationState] = useState('closed')
|
||||
// Track previous visibility to detect changes
|
||||
const [prevVisible, setPrevVisible] = useState(false)
|
||||
|
||||
// Handle animations on visibility changes
|
||||
const [showContent, setShowContent] = useState(false)
|
||||
|
||||
// Reset copy state when dialog visibility changes
|
||||
useEffect(() => {
|
||||
// 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')
|
||||
}, 200) // Match animation duration
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
// Update previous visibility
|
||||
setPrevVisible(visible)
|
||||
}
|
||||
}, [visible, prevVisible, animationState])
|
||||
|
||||
// Auto-close on progress completion
|
||||
useEffect(() => {
|
||||
// Only auto-close if showing and progress reaches 100% and not showing instructions
|
||||
if (visible && progress >= 100 && !showInstructions && animationState === 'visible') {
|
||||
// Wait a moment before closing
|
||||
if (!visible) {
|
||||
setCopySuccess(false)
|
||||
setShowContent(false)
|
||||
} else {
|
||||
// Add a small delay to trigger the entrance animation
|
||||
const timer = setTimeout(() => {
|
||||
// Only proceed if we're still in visible state
|
||||
if (animationState === 'visible' && onClose) {
|
||||
onClose() // Call the onClose function directly
|
||||
}
|
||||
}, 1000) // Wait 1 second after completion
|
||||
|
||||
setShowContent(true)
|
||||
}, 50)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [progress, showInstructions, animationState, visible, onClose])
|
||||
|
||||
// Don't render if state is closed
|
||||
if (animationState === 'closed') {
|
||||
return null
|
||||
}
|
||||
|
||||
}, [visible])
|
||||
|
||||
if (!visible) return null
|
||||
|
||||
const handleCopyCommand = () => {
|
||||
if (instructions?.command) {
|
||||
navigator.clipboard.writeText(instructions.command)
|
||||
@@ -90,10 +58,13 @@ const ProgressDialog: React.FC<ProgressDialogProps> = ({
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
// If we can close, just call onClose directly
|
||||
if (onClose) {
|
||||
onClose()
|
||||
}
|
||||
setShowContent(false)
|
||||
// Delay closing to allow exit animation
|
||||
setTimeout(() => {
|
||||
if (onClose) {
|
||||
onClose()
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// Prevent closing when in progress
|
||||
@@ -175,17 +146,13 @@ const ProgressDialog: React.FC<ProgressDialogProps> = ({
|
||||
// Determine if close button should be enabled
|
||||
const isCloseButtonEnabled = showInstructions || progress >= 100
|
||||
|
||||
// Generate appropriate classes based on animation state
|
||||
const dialogClasses = `progress-dialog-overlay ${animationState === 'hiding' ? 'exiting' : 'visible'}`
|
||||
const contentClasses = `progress-dialog ${showInstructions ? 'with-instructions' : ''} dialog-${animationState === 'hiding' ? 'exiting' : 'visible'}`
|
||||
|
||||
return (
|
||||
<div
|
||||
className={dialogClasses}
|
||||
className={`progress-dialog-overlay ${showContent ? 'visible' : ''}`}
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
<div
|
||||
className={contentClasses}
|
||||
className={`progress-dialog ${showInstructions ? 'with-instructions' : ''} ${showContent ? 'dialog-visible' : ''}`}
|
||||
>
|
||||
<h3>{title}</h3>
|
||||
<p>{message}</p>
|
||||
@@ -239,4 +206,4 @@ const ProgressDialog: React.FC<ProgressDialogProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default ProgressDialog
|
||||
export default ProgressDialog
|
||||
|
||||
@@ -1,29 +1,6 @@
|
||||
@use '../variables' as *;
|
||||
@use '../mixins' as *;
|
||||
|
||||
// Shared keyframes for both enter and exit animations
|
||||
@keyframes modal-appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes modal-disappear {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
// Progress Dialog
|
||||
.progress-dialog-overlay {
|
||||
position: fixed;
|
||||
@@ -36,19 +13,22 @@
|
||||
@include flex-center;
|
||||
z-index: var(--z-modal);
|
||||
opacity: 0;
|
||||
animation: modal-appear 0.2s ease-out;
|
||||
cursor: pointer;
|
||||
pointer-events: auto; // Ensure clicks work
|
||||
|
||||
// When visible, fade in
|
||||
&.visible {
|
||||
opacity: 1;
|
||||
animation: modal-appear 0.2s ease-out forwards;
|
||||
}
|
||||
|
||||
// When exiting, fade out
|
||||
&.exiting {
|
||||
animation: modal-disappear 0.2s ease-out forwards;
|
||||
pointer-events: none; // Prevent clicks during exit animation
|
||||
@keyframes modal-appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,31 +36,32 @@
|
||||
background-color: var(--elevated-bg);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.3); // shadow-glow
|
||||
width: 450px;
|
||||
max-width: 90vw;
|
||||
border: 1px solid var(--border-soft);
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
cursor: default;
|
||||
|
||||
// When visible, show the dialog
|
||||
&.dialog-visible {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
transition: transform 0.2s var(--easing-bounce), opacity 0.2s ease-out;
|
||||
}
|
||||
|
||||
// When exiting, hide the dialog
|
||||
&.dialog-exiting {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
transition: transform 0.2s ease-in, opacity 0.2s ease-in;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.with-instructions {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
// Progress bar
|
||||
|
||||
@@ -13,18 +13,10 @@
|
||||
z-index: var(--z-modal);
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
pointer-events: auto; // Ensure clicks work
|
||||
|
||||
// When visible, fade in
|
||||
&.visible {
|
||||
opacity: 1;
|
||||
animation: modal-appear 0.2s ease-out forwards;
|
||||
}
|
||||
|
||||
// When exiting, fade out
|
||||
&.exiting {
|
||||
animation: modal-disappear 0.2s ease-out forwards;
|
||||
pointer-events: none; // Prevent clicks during exit animation
|
||||
animation: modal-appear 0.2s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,18 +34,12 @@
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
|
||||
// When visible, show the dialog
|
||||
&.dialog-visible {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
transition: transform 0.2s var(--easing-bounce), opacity 0.2s ease-out;
|
||||
}
|
||||
|
||||
// When exiting, hide the dialog
|
||||
&.dialog-exiting {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
transition: transform 0.2s ease-in, opacity 0.2s ease-in;
|
||||
opacity: 1;
|
||||
transition:
|
||||
transform 0.2s var(--easing-bounce),
|
||||
opacity 0.2s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user