diff --git a/src/components/dialogs/DlcSelectionDialog.tsx b/src/components/dialogs/DlcSelectionDialog.tsx index 9cbecef..12b349e 100644 --- a/src/components/dialogs/DlcSelectionDialog.tsx +++ b/src/components/dialogs/DlcSelectionDialog.tsx @@ -117,8 +117,10 @@ const DlcSelectionDialog = ({ // Submit selected DLCs to parent component const handleConfirm = useCallback(() => { - onConfirm(selectedDlcs) - }, [onConfirm, selectedDlcs]) + // Create a deep copy to prevent reference issues + const dlcsCopy = JSON.parse(JSON.stringify(selectedDlcs)); + onConfirm(dlcsCopy); + }, [onConfirm, selectedDlcs]); // Count selected DLCs const selectedCount = selectedDlcs.filter((dlc) => dlc.enabled).length diff --git a/src/components/icons/Icon.tsx b/src/components/icons/Icon.tsx index 7332951..2b3cfb4 100644 --- a/src/components/icons/Icon.tsx +++ b/src/components/icons/Icon.tsx @@ -102,7 +102,7 @@ const Icon: React.FC = ({ if (BRAND_ICON_NAMES.has(name)) { defaultVariant = 'brand' } else { - defaultVariant = 'bold' // Default to outline for non-brand icons + defaultVariant = 'bold' // Default to bold for non-brand icons } } diff --git a/src/components/icons/brands/index.ts b/src/components/icons/brands/index.ts index a7088e1..d93d8dc 100644 --- a/src/components/icons/brands/index.ts +++ b/src/components/icons/brands/index.ts @@ -1,6 +1,7 @@ // Bold variant icons export { ReactComponent as Linux } from './linux.svg' -export { ReactComponent as steam } from './steam.svg' -export { ReactComponent as windows } from './windows.svg' -export { ReactComponent as github } from './github.svg' -export { ReactComponent as discord } from './discord.svg' \ No newline at end of file +export { ReactComponent as Steam } from './steam.svg' +export { ReactComponent as Windows } from './windows.svg' +export { ReactComponent as Github } from './github.svg' +export { ReactComponent as Discord } from './discord.svg' +export { ReactComponent as Proton } from './proton.svg' \ No newline at end of file diff --git a/src/components/icons/brands/proton.svg b/src/components/icons/brands/proton.svg new file mode 100644 index 0000000..59621c6 --- /dev/null +++ b/src/components/icons/brands/proton.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts index 23a3000..37e0275 100644 --- a/src/components/icons/index.ts +++ b/src/components/icons/index.ts @@ -27,11 +27,13 @@ export const search = 'Search' export const trash = 'Trash' export const warning = 'Warning' export const wine = 'Wine' +export const diamond = 'Diamond' // Brand icons export const discord = 'Discord' export const github = 'GitHub' export const linux = 'Linux' +export const proton = 'Proton' export const steam = 'Steam' export const windows = 'Windows' @@ -54,11 +56,13 @@ export const IconNames = { Trash: trash, Warning: warning, Wine: wine, + Diamond: diamond, // Brand icons Discord: discord, GitHub: github, Linux: linux, + Proton: proton, Steam: steam, Windows: windows, } as const diff --git a/src/components/icons/ui/bold/diamond.svg b/src/components/icons/ui/bold/diamond.svg new file mode 100644 index 0000000..016049c --- /dev/null +++ b/src/components/icons/ui/bold/diamond.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/icons/ui/bold/index.ts b/src/components/icons/ui/bold/index.ts index 7d29193..5af9a10 100644 --- a/src/components/icons/ui/bold/index.ts +++ b/src/components/icons/ui/bold/index.ts @@ -14,4 +14,5 @@ export { ReactComponent as Refresh } from './refresh.svg' export { ReactComponent as Search } from './search.svg' export { ReactComponent as Trash } from './trash.svg' export { ReactComponent as Warning } from './warning.svg' -export { ReactComponent as Wine } from './wine.svg' \ No newline at end of file +export { ReactComponent as Wine } from './wine.svg' +export { ReactComponent as Diamond } from './diamond.svg' \ No newline at end of file diff --git a/src/components/icons/ui/outline/diamond.svg b/src/components/icons/ui/outline/diamond.svg new file mode 100644 index 0000000..a573710 --- /dev/null +++ b/src/components/icons/ui/outline/diamond.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/icons/ui/outline/index.ts b/src/components/icons/ui/outline/index.ts index 7c0e689..fee3596 100644 --- a/src/components/icons/ui/outline/index.ts +++ b/src/components/icons/ui/outline/index.ts @@ -15,3 +15,4 @@ export { ReactComponent as Search } from './search.svg' export { ReactComponent as Trash } from './trash.svg' export { ReactComponent as Warning } from './warning.svg' export { ReactComponent as Wine } from './wine.svg' +export { ReactComponent as Diamond } from './diamond.svg' \ No newline at end of file diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index e91e1b9..decbe65 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,5 +1,5 @@ import { Button } from '@/components/buttons' -import { Icon, info, refresh, search } from '@/components/icons' +import { Icon, diamond, refresh, search } from '@/components/icons' interface HeaderProps { onRefresh: () => void @@ -21,7 +21,7 @@ const Header = ({ return (
- +

CreamLinux

diff --git a/src/components/layout/InitialLoadingScreen.tsx b/src/components/layout/InitialLoadingScreen.tsx index cb9004e..eed246f 100644 --- a/src/components/layout/InitialLoadingScreen.tsx +++ b/src/components/layout/InitialLoadingScreen.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useEffect, useState } from 'react' interface InitialLoadingScreenProps { message: string; @@ -9,28 +9,40 @@ interface InitialLoadingScreenProps { /** * Initial loading screen displayed when the app first loads */ -const InitialLoadingScreen = ({ - message, - progress, - onComplete -}: InitialLoadingScreenProps) => { - // Call onComplete when progress reaches 100% - useEffect(() => { - if (progress >= 100 && onComplete) { - const timer = setTimeout(() => { - onComplete(); - }, 500); // Small delay to show completion - - return () => clearTimeout(timer); - } - }, [progress, onComplete]); +const InitialLoadingScreen = ({ message, progress, onComplete }: InitialLoadingScreenProps) => { + const [detailedStatus, setDetailedStatus] = useState([ + "Initializing application...", + "Setting up Steam integration...", + "Preparing DLC management..." + ]); + // Use a sequence of messages based on progress + useEffect(() => { + const messages = [ + { threshold: 10, message: "Checking system requirements..." }, + { threshold: 30, message: "Scanning Steam libraries..." }, + { threshold: 50, message: "Discovering games..." }, + { threshold: 70, message: "Analyzing game configurations..." }, + { threshold: 90, message: "Preparing user interface..." }, + { threshold: 100, message: "Ready to launch!" } + ]; + + // Find current status message based on progress + const currentMessage = messages.find(m => progress <= m.threshold)?.message || "Loading..."; + + // Add new messages to the log as progress increases + if (currentMessage && !detailedStatus.includes(currentMessage)) { + setDetailedStatus(prev => [...prev, currentMessage]); + } + }, [progress, detailedStatus]); + return (

CreamLinux

+ {/* Enhanced animation with SVG or more elaborate CSS animation */}
@@ -40,6 +52,16 @@ const InitialLoadingScreen = ({

{message}

+ {/* Add a detailed status log that shows progress steps */} +
+ {detailedStatus.slice(-4).map((status, index) => ( +
+ + {status} +
+ ))} +
+
@@ -47,7 +69,7 @@ const InitialLoadingScreen = ({
{Math.round(progress)}%
- ) -} + ); +}; export default InitialLoadingScreen \ No newline at end of file diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 6f8ddee..4ee8841 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,4 +1,4 @@ -import { Icon, layers, linux, wine } from '@/components/icons' +import { Icon, layers, linux, proton } from '@/components/icons' interface SidebarProps { setFilter: (filter: string) => void @@ -22,7 +22,7 @@ const Sidebar = ({ setFilter, currentFilter }: SidebarProps) => { const filters: FilterItem[] = [ { id: 'all', label: 'All Games', icon: layers, variant: 'bold' }, { id: 'native', label: 'Native', icon: linux, variant: 'brand' }, - { id: 'proton', label: 'Proton Required', icon: wine, variant: 'bold' } + { id: 'proton', label: 'Proton Required', icon: proton, variant: 'brand' } ] return ( diff --git a/src/components/notifications/Toast.tsx b/src/components/notifications/Toast.tsx index cf2fc46..4faf2de 100644 --- a/src/components/notifications/Toast.tsx +++ b/src/components/notifications/Toast.tsx @@ -23,13 +23,17 @@ const Toast = ({ onDismiss }: ToastProps) => { const [visible, setVisible] = useState(false) + const [isClosing, setIsClosing] = useState(false); // Use useCallback to memoize the handleDismiss function const handleDismiss = useCallback(() => { - setVisible(false) + setIsClosing(true); // Give time for exit animation - setTimeout(() => onDismiss(id), 300) - }, [id, onDismiss]) + setTimeout(() => { + setVisible(false); + setTimeout(() => onDismiss(id), 50); + }, 300); + }, [id, onDismiss]); // Handle animation on mount/unmount useEffect(() => { @@ -69,7 +73,7 @@ const Toast = ({ } return ( -
+
{getIcon()}
{title &&

{title}

} diff --git a/src/contexts/AppProvider.tsx b/src/contexts/AppProvider.tsx index 6b66c00..bfa46e8 100644 --- a/src/contexts/AppProvider.tsx +++ b/src/contexts/AppProvider.tsx @@ -107,15 +107,25 @@ export const AppProvider = ({ children }: AppProviderProps) => { // DLC confirmation wrapper const handleDlcConfirm = (selectedDlcs: DlcInfo[]) => { - closeDlcDialog() const { gameId, isEditMode } = dlcDialog + // MODIFIED: Create a deep copy to ensure we don't have reference issues + const dlcsCopy = selectedDlcs.map(dlc => ({...dlc})) + + // Log detailed info before closing dialog + console.log(`Saving ${dlcsCopy.filter(d => d.enabled).length} enabled and ${ + dlcsCopy.length - dlcsCopy.filter(d => d.enabled).length + } disabled DLCs`) + + // Close dialog FIRST to avoid UI state issues + closeDlcDialog() + // Update game state to show it's installing setGames(prevGames => prevGames.map(g => g.id === gameId ? { ...g, installing: true } : g) ) - executeDlcConfirm(selectedDlcs, gameId, isEditMode, games) + executeDlcConfirm(dlcsCopy, gameId, isEditMode, games) .then(() => { success(isEditMode ? "DLC configuration updated successfully" diff --git a/src/hooks/useDlcManager.ts b/src/hooks/useDlcManager.ts index 9f457b9..70cbb95 100644 --- a/src/hooks/useDlcManager.ts +++ b/src/hooks/useDlcManager.ts @@ -25,6 +25,7 @@ export function useDlcManager() { const [isFetchingDlcs, setIsFetchingDlcs] = useState(false) const dlcFetchController = useRef(null) const activeDlcFetchId = useRef(null) + const [forceReload, setForceReload] = useState(false) // Add this state to force reloads // DLC selection dialog state const [dlcDialog, setDlcDialog] = useState({ @@ -155,7 +156,7 @@ export function useDlcManager() { } } - // Handle game edit (show DLC management dialog) + // MODIFIED: Handle game edit (show DLC management dialog) with proper reloading const handleGameEdit = async (gameId: string, games: Game[]) => { const game = games.find((g) => g.id === gameId) if (!game || !game.cream_installed) return @@ -172,7 +173,7 @@ export function useDlcManager() { visible: true, gameId, gameTitle: game.title, - dlcs: [], + dlcs: [], // Always start with empty DLCs to force a fresh load enabledDlcs: [], isLoading: true, isEditMode: true, // This is an edit operation @@ -182,23 +183,33 @@ export function useDlcManager() { error: null, }) - // Try to read all DLCs from the configuration file first (including disabled ones) + // MODIFIED: Always get a fresh copy from the config file + console.log('Loading DLC configuration from disk...') try { const allDlcs = await invoke('get_all_dlcs_command', { gamePath: game.path, - }).catch(() => [] as DlcInfo[]) + }).catch((e) => { + console.error('Error loading DLCs:', e) + return [] as DlcInfo[] + }) if (allDlcs.length > 0) { - // If we have DLCs from the config file, use them + // Log the fresh DLC config console.log('Loaded existing DLC configuration:', allDlcs) + + // IMPORTANT: Create a completely new array to avoid reference issues + const freshDlcs = allDlcs.map(dlc => ({...dlc})) setDlcDialog((prev) => ({ ...prev, - dlcs: allDlcs, + dlcs: freshDlcs, isLoading: false, progress: 100, progressMessage: 'Loaded existing DLC configuration', })) + + // Reset force reload flag + setForceReload(false) return } } catch (error) { @@ -245,7 +256,7 @@ export function useDlcManager() { } } - // Handle DLC selection dialog close + // MODIFIED: Handle DLC selection dialog close const handleDlcDialogClose = () => { // Cancel any in-progress DLC fetching if (isFetchingDlcs && activeDlcFetchId.current) { @@ -267,8 +278,15 @@ export function useDlcManager() { dlcFetchController.current = null } - // Close dialog - setDlcDialog((prev) => ({ ...prev, visible: false })) + // Close dialog and reset state + setDlcDialog((prev) => ({ + ...prev, + visible: false, + dlcs: [], // Clear DLCs to force a reload next time + })) + + // Set flag to force reload next time + setForceReload(true) } // Update DLCs being streamed with enabled state @@ -291,5 +309,6 @@ export function useDlcManager() { streamGameDlcs, handleGameEdit, handleDlcDialogClose, + forceReload, } } \ No newline at end of file diff --git a/src/hooks/useGameActions.ts b/src/hooks/useGameActions.ts index 55c7f23..95c31ce 100644 --- a/src/hooks/useGameActions.ts +++ b/src/hooks/useGameActions.ts @@ -148,10 +148,12 @@ export function useGameActions() { // Find the game const game = games.find((g) => g.id === gameId) if (!game) return - + try { if (isEditMode) { - // If in edit mode, we're updating existing cream_api.ini + // MODIFIED: Create a deep copy to ensure we don't have reference issues + const dlcsCopy = selectedDlcs.map(dlc => ({...dlc})); + // Show progress dialog for editing setProgressDialog({ visible: true, @@ -161,13 +163,15 @@ export function useGameActions() { showInstructions: false, instructions: undefined, }) - + + console.log('Saving DLC configuration:', dlcsCopy) + // Call the backend to update the DLC configuration await invoke('update_dlc_configuration_command', { gamePath: game.path, - dlcs: selectedDlcs, + dlcs: dlcsCopy, }) - + // Update progress dialog for completion setProgressDialog((prev) => ({ ...prev, @@ -175,7 +179,7 @@ export function useGameActions() { message: 'DLC configuration updated successfully!', progress: 100, })) - + // Hide dialog after a delay setTimeout(() => { setProgressDialog((prev) => ({ ...prev, visible: false })) @@ -191,7 +195,7 @@ export function useGameActions() { showInstructions: false, instructions: undefined, }) - + // Invoke the installation with the selected DLCs await invoke('install_cream_with_dlcs_command', { gameId, @@ -202,14 +206,14 @@ export function useGameActions() { } } catch (error) { console.error('Error processing DLC selection:', error) - + // Show error in progress dialog setProgressDialog((prev) => ({ ...prev, message: `Error: ${error}`, progress: 100, })) - + // Hide dialog after a delay setTimeout(() => { setProgressDialog((prev) => ({ ...prev, visible: false })) diff --git a/src/styles/components/dialogs/_dialog.scss b/src/styles/components/dialogs/_dialog.scss index deb56c6..f252eaa 100644 --- a/src/styles/components/dialogs/_dialog.scss +++ b/src/styles/components/dialogs/_dialog.scss @@ -109,6 +109,10 @@ overflow-y: auto; flex: 1; @include custom-scrollbar; + + p { + margin-bottom: 1rem; + } } // Dialog footer diff --git a/src/styles/components/dialogs/_progress_dialog.scss b/src/styles/components/dialogs/_progress_dialog.scss index 54a5294..1ac5067 100644 --- a/src/styles/components/dialogs/_progress_dialog.scss +++ b/src/styles/components/dialogs/_progress_dialog.scss @@ -33,7 +33,7 @@ // Instruction container .instruction-container { - margin-top: 1.5rem; + margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border-soft); diff --git a/src/styles/components/layout/_loading_screen.scss b/src/styles/components/layout/_loading_screen.scss index 71d9aa8..44c1a82 100644 --- a/src/styles/components/layout/_loading_screen.scss +++ b/src/styles/components/layout/_loading_screen.scss @@ -69,6 +69,44 @@ min-height: 3rem; } + .loading-status-log { + margin: 1rem 0; + text-align: left; + max-height: 100px; + overflow-y: auto; + background-color: rgba(0, 0, 0, 0.2); + border-radius: var(--radius-sm); + padding: 0.5rem; + + .status-line { + margin: 0.5rem 0; + display: flex; + align-items: center; + + .status-indicator { + color: var(--primary-color); + margin-right: 0.5rem; + font-size: 1.2rem; + } + + .status-text { + color: var(--text-secondary); + font-size: 0.9rem; + } + + &:last-child { + .status-indicator { + color: var(--success); + } + + .status-text { + color: var(--text-primary); + font-weight: 600; + } + } + } + } + .progress-bar-container { height: 8px; background-color: var(--border-soft); diff --git a/src/styles/components/notifications/_toast.scss b/src/styles/components/notifications/_toast.scss index f00ff54..d7857cd 100644 --- a/src/styles/components/notifications/_toast.scss +++ b/src/styles/components/notifications/_toast.scss @@ -77,6 +77,11 @@ transform: translateY(0); } + &.closing { + opacity: 0; + transform: translateY(-10px); + } + // Type-specific styling &.toast-success { border-color: var(--success);