Formatting

This commit is contained in:
Tickbase
2025-05-18 18:23:06 +02:00
parent bbbd7482c1
commit 81519e89b7
61 changed files with 714 additions and 775 deletions

View File

@@ -1,10 +1,10 @@
// Export all hooks
export { useGames } from './useGames';
export { useDlcManager } from './useDlcManager';
export { useGameActions } from './useGameActions';
export { useToasts } from './useToasts';
export { useAppLogic } from './useAppLogic';
export { useGames } from './useGames'
export { useDlcManager } from './useDlcManager'
export { useGameActions } from './useGameActions'
export { useToasts } from './useToasts'
export { useAppLogic } from './useAppLogic'
// Export types
export type { ToastType, Toast, ToastOptions } from './useToasts';
export type { DlcDialogState } from './useDlcManager';
export type { ToastType, Toast, ToastOptions } from './useToasts'
export type { DlcDialogState } from './useDlcManager'

View File

@@ -2,7 +2,7 @@ import { useState, useCallback, useEffect, useRef } from 'react'
import { useAppContext } from '@/contexts/useAppContext'
interface UseAppLogicOptions {
autoLoad?: boolean;
autoLoad?: boolean
}
/**
@@ -11,24 +11,18 @@ interface UseAppLogicOptions {
*/
export function useAppLogic(options: UseAppLogicOptions = {}) {
const { autoLoad = true } = options
// Get values from app context
const {
games,
loadGames,
isLoading,
error,
showToast
} = useAppContext()
const { games, loadGames, isLoading, error, showToast } = useAppContext()
// Local state for filtering and UI
const [filter, setFilter] = useState('all')
const [searchQuery, setSearchQuery] = useState('')
const [isInitialLoad, setIsInitialLoad] = useState(true)
const isInitializedRef = useRef(false)
const [scanProgress, setScanProgress] = useState({
message: 'Initializing...',
progress: 0
const [scanProgress, setScanProgress] = useState({
message: 'Initializing...',
progress: 0,
})
// Filter games based on current filter and search
@@ -41,8 +35,8 @@ export function useAppLogic(options: UseAppLogicOptions = {}) {
(filter === 'proton' && !game.native)
// Then filter by search query
const searchMatch = !searchQuery.trim() ||
game.title.toLowerCase().includes(searchQuery.toLowerCase())
const searchMatch =
!searchQuery.trim() || game.title.toLowerCase().includes(searchQuery.toLowerCase())
return platformMatch && searchMatch
})
@@ -52,25 +46,25 @@ export function useAppLogic(options: UseAppLogicOptions = {}) {
const handleSearchChange = useCallback((query: string) => {
setSearchQuery(query)
}, [])
// Handle initial loading with simulated progress
useEffect(() => {
if (!autoLoad || !isInitialLoad || isInitializedRef.current) return
isInitializedRef.current = true
console.log("[APP LOGIC #2] Starting initialization")
console.log('[APP LOGIC #2] Starting initialization')
const initialLoad = async () => {
try {
setScanProgress({ message: 'Scanning for games...', progress: 20 })
await new Promise(resolve => setTimeout(resolve, 800))
await new Promise((resolve) => setTimeout(resolve, 800))
setScanProgress({ message: 'Loading game information...', progress: 50 })
await loadGames()
setScanProgress({ message: 'Finishing up...', progress: 90 })
await new Promise(resolve => setTimeout(resolve, 500))
await new Promise((resolve) => setTimeout(resolve, 500))
setScanProgress({ message: 'Ready!', progress: 100 })
setTimeout(() => setIsInitialLoad(false), 500)
} catch (error) {
@@ -79,10 +73,10 @@ export function useAppLogic(options: UseAppLogicOptions = {}) {
setTimeout(() => setIsInitialLoad(false), 2000)
}
}
initialLoad()
}, [autoLoad, isInitialLoad, loadGames, showToast])
// Force a refresh
const handleRefresh = useCallback(async () => {
try {
@@ -104,6 +98,6 @@ export function useAppLogic(options: UseAppLogicOptions = {}) {
filteredGames: filteredGames(),
handleRefresh,
isLoading,
error
error,
}
}
}

View File

@@ -4,17 +4,17 @@ import { listen } from '@tauri-apps/api/event'
import { Game, DlcInfo } from '@/types'
export interface DlcDialogState {
visible: boolean;
gameId: string;
gameTitle: string;
dlcs: DlcInfo[];
enabledDlcs: string[];
isLoading: boolean;
isEditMode: boolean;
progress: number;
progressMessage: string;
timeLeft: string;
error: string | null;
visible: boolean
gameId: string
gameTitle: string
dlcs: DlcInfo[]
enabledDlcs: string[]
isLoading: boolean
isEditMode: boolean
progress: number
progressMessage: string
timeLeft: string
error: string | null
}
/**
@@ -60,9 +60,9 @@ export function useDlcManager() {
// When progress is 100%, mark loading as complete and reset fetch state
const unlistenDlcProgress = await listen<{
message: string;
progress: number;
timeLeft?: string;
message: string
progress: number
timeLeft?: string
}>('dlc-progress', (event) => {
const { message, progress, timeLeft } = event.payload
@@ -196,9 +196,9 @@ export function useDlcManager() {
if (allDlcs.length > 0) {
// 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}))
const freshDlcs = allDlcs.map((dlc) => ({ ...dlc }))
setDlcDialog((prev) => ({
...prev,
@@ -207,7 +207,7 @@ export function useDlcManager() {
progress: 100,
progressMessage: 'Loaded existing DLC configuration',
}))
// Reset force reload flag
setForceReload(false)
return
@@ -279,12 +279,12 @@ export function useDlcManager() {
}
// Close dialog and reset state
setDlcDialog((prev) => ({
...prev,
setDlcDialog((prev) => ({
...prev,
visible: false,
dlcs: [], // Clear DLCs to force a reload next time
}))
// Set flag to force reload next time
setForceReload(true)
}
@@ -311,4 +311,4 @@ export function useDlcManager() {
handleDlcDialogClose,
forceReload,
}
}
}

View File

@@ -26,16 +26,17 @@ export function useGameActions() {
try {
// Listen for progress updates from the backend
const unlistenProgress = await listen<{
title: string;
message: string;
progress: number;
complete: boolean;
show_instructions?: boolean;
instructions?: InstallationInstructions;
title: string
message: string
progress: number
complete: boolean
show_instructions?: boolean
instructions?: InstallationInstructions
}>('installation-progress', (event) => {
console.log('Received installation-progress event:', event)
const { title, message, progress, complete, show_instructions, instructions } = event.payload
const { title, message, progress, complete, show_instructions, instructions } =
event.payload
if (complete && !show_instructions) {
// Hide dialog when complete if no instructions
@@ -64,7 +65,7 @@ export function useGameActions() {
let cleanup: (() => void) | null = null
setupEventListeners().then(unlisten => {
setupEventListeners().then((unlisten) => {
cleanup = unlisten
})
@@ -79,156 +80,156 @@ export function useGameActions() {
}, [])
// Unified handler for game actions (install/uninstall)
const handleGameAction = useCallback(async (gameId: string, action: ActionType, games: Game[]) => {
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
const game = games.find((g) => g.id === gameId)
if (!game) return
const handleGameAction = useCallback(
async (gameId: string, action: ActionType, games: Game[]) => {
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
}
// Get title based on action
const isCream = action.includes('cream')
const isInstall = action.includes('install')
const product = isCream ? 'CreamLinux' : 'SmokeAPI'
const operation = isInstall ? 'Installing' : 'Uninstalling'
// For other actions (uninstall_cream, install_smoke, uninstall_smoke)
// Find game to get title
const game = games.find((g) => g.id === gameId)
if (!game) return
// Show progress dialog
setProgressDialog({
visible: true,
title: `${operation} ${product} for ${game.title}`,
message: isInstall ? 'Downloading required files...' : 'Removing files...',
progress: isInstall ? 0 : 30,
showInstructions: false,
instructions: undefined,
})
// Get title based on action
const isCream = action.includes('cream')
const isInstall = action.includes('install')
const product = isCream ? 'CreamLinux' : 'SmokeAPI'
const operation = isInstall ? 'Installing' : 'Uninstalling'
console.log(`Invoking process_game_action for game ${gameId} with action ${action}`)
// Call the backend with the unified action
await invoke('process_game_action', {
gameAction: {
game_id: gameId,
action,
},
})
} catch (error) {
console.error(`Error processing action ${action} for game ${gameId}:`, 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 }))
}, 3000)
// Rethrow to allow upstream handling
throw error
}
}, [])
// Handle DLC selection confirmation
const handleDlcConfirm = useCallback(async (
selectedDlcs: DlcInfo[],
gameId: string,
isEditMode: boolean,
games: Game[]
) => {
// Find the game
const game = games.find((g) => g.id === gameId)
if (!game) return
try {
if (isEditMode) {
// MODIFIED: Create a deep copy to ensure we don't have reference issues
const dlcsCopy = selectedDlcs.map(dlc => ({...dlc}));
// Show progress dialog for editing
// Show progress dialog
setProgressDialog({
visible: true,
title: `Updating DLCs for ${game.title}`,
message: 'Updating DLC configuration...',
progress: 30,
title: `${operation} ${product} for ${game.title}`,
message: isInstall ? 'Downloading required files...' : 'Removing files...',
progress: isInstall ? 0 : 30,
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: dlcsCopy,
console.log(`Invoking process_game_action for game ${gameId} with action ${action}`)
// Call the backend with the unified action
await invoke('process_game_action', {
gameAction: {
game_id: gameId,
action,
},
})
// Update progress dialog for completion
} catch (error) {
console.error(`Error processing action ${action} for game ${gameId}:`, error)
// Show error in progress dialog
setProgressDialog((prev) => ({
...prev,
title: `Update Complete: ${game.title}`,
message: 'DLC configuration updated successfully!',
message: `Error: ${error}`,
progress: 100,
}))
// Hide dialog after a delay
setTimeout(() => {
setProgressDialog((prev) => ({ ...prev, visible: false }))
}, 2000)
} else {
// We're doing a fresh install with selected DLCs
// Show progress dialog for installation right away
setProgressDialog({
visible: true,
title: `Installing CreamLinux for ${game.title}`,
message: 'Preparing to download CreamLinux...',
progress: 0,
showInstructions: false,
instructions: undefined,
})
// Invoke the installation with the selected DLCs
await invoke('install_cream_with_dlcs_command', {
gameId,
selectedDlcs,
})
// Note: The progress dialog will be updated through the installation-progress event listener
}, 3000)
// Rethrow to allow upstream handling
throw error
}
} 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 }))
}, 3000)
// Rethrow to allow upstream handling
throw error
}
}, [])
},
[]
)
// Handle DLC selection confirmation
const handleDlcConfirm = useCallback(
async (selectedDlcs: DlcInfo[], gameId: string, isEditMode: boolean, games: Game[]) => {
// Find the game
const game = games.find((g) => g.id === gameId)
if (!game) return
try {
if (isEditMode) {
// 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,
title: `Updating DLCs for ${game.title}`,
message: 'Updating DLC configuration...',
progress: 30,
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: dlcsCopy,
})
// Update progress dialog for completion
setProgressDialog((prev) => ({
...prev,
title: `Update Complete: ${game.title}`,
message: 'DLC configuration updated successfully!',
progress: 100,
}))
// Hide dialog after a delay
setTimeout(() => {
setProgressDialog((prev) => ({ ...prev, visible: false }))
}, 2000)
} else {
// We're doing a fresh install with selected DLCs
// Show progress dialog for installation right away
setProgressDialog({
visible: true,
title: `Installing CreamLinux for ${game.title}`,
message: 'Preparing to download CreamLinux...',
progress: 0,
showInstructions: false,
instructions: undefined,
})
// Invoke the installation with the selected DLCs
await invoke('install_cream_with_dlcs_command', {
gameId,
selectedDlcs,
})
// Note: The progress dialog will be updated through the installation-progress event listener
}
} 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 }))
}, 3000)
// Rethrow to allow upstream handling
throw error
}
},
[]
)
return {
progressDialog,
setProgressDialog,
handleCloseProgressDialog,
handleGameAction,
handleDlcConfirm
handleDlcConfirm,
}
}
}

View File

@@ -64,17 +64,15 @@ export function useGames() {
// Update only the specific game in the state
setGames((prevGames) =>
prevGames.map((game) =>
game.id === updatedGame.id
? { ...updatedGame, platform: 'Steam' }
: game
game.id === updatedGame.id ? { ...updatedGame, platform: 'Steam' } : game
)
)
})
// Listen for scan progress events
const unlistenScanProgress = await listen<{
message: string;
progress: number;
message: string
progress: number
}>('scan-progress', (event) => {
const { message, progress } = event.payload
@@ -102,7 +100,7 @@ export function useGames() {
// Cleanup function
return () => {
unlisteners.forEach(fn => fn())
unlisteners.forEach((fn) => fn())
}
}, [loadGames, isInitialLoad])
@@ -123,4 +121,4 @@ export function useGames() {
updateGame,
setGames,
}
}
}

View File

@@ -10,19 +10,19 @@ export type ToastType = 'success' | 'error' | 'warning' | 'info'
* Toast interface
*/
export interface Toast {
id: string;
message: string;
type: ToastType;
duration?: number;
title?: string;
id: string
message: string
type: ToastType
duration?: number
title?: string
}
/**
* Toast options interface
*/
export interface ToastOptions {
title?: string;
duration?: number;
title?: string
duration?: number
}
/**
@@ -36,51 +36,66 @@ export function useToasts() {
* Removes a toast by ID
*/
const removeToast = useCallback((id: string) => {
setToasts(currentToasts => currentToasts.filter(toast => toast.id !== id))
setToasts((currentToasts) => currentToasts.filter((toast) => toast.id !== id))
}, [])
/**
* Adds a new toast with the specified type and options
*/
const addToast = useCallback((toast: Omit<Toast, 'id'>) => {
const id = uuidv4()
const newToast = { ...toast, id }
setToasts(currentToasts => [...currentToasts, newToast])
// Auto-remove toast after its duration expires
if (toast.duration !== Infinity) {
setTimeout(() => {
removeToast(id)
}, toast.duration || 5000) // Default 5 seconds
}
return id
}, [removeToast])
const addToast = useCallback(
(toast: Omit<Toast, 'id'>) => {
const id = uuidv4()
const newToast = { ...toast, id }
setToasts((currentToasts) => [...currentToasts, newToast])
// Auto-remove toast after its duration expires
if (toast.duration !== Infinity) {
setTimeout(() => {
removeToast(id)
}, toast.duration || 5000) // Default 5 seconds
}
return id
},
[removeToast]
)
/**
* Shorthand method for success toasts
*/
const success = useCallback((message: string, options: ToastOptions = {}) =>
addToast({ message, type: 'success', ...options }), [addToast])
const success = useCallback(
(message: string, options: ToastOptions = {}) =>
addToast({ message, type: 'success', ...options }),
[addToast]
)
/**
* Shorthand method for error toasts
*/
const error = useCallback((message: string, options: ToastOptions = {}) =>
addToast({ message, type: 'error', ...options }), [addToast])
const error = useCallback(
(message: string, options: ToastOptions = {}) =>
addToast({ message, type: 'error', ...options }),
[addToast]
)
/**
* Shorthand method for warning toasts
*/
const warning = useCallback((message: string, options: ToastOptions = {}) =>
addToast({ message, type: 'warning', ...options }), [addToast])
const warning = useCallback(
(message: string, options: ToastOptions = {}) =>
addToast({ message, type: 'warning', ...options }),
[addToast]
)
/**
* Shorthand method for info toasts
*/
const info = useCallback((message: string, options: ToastOptions = {}) =>
addToast({ message, type: 'info', ...options }), [addToast])
const info = useCallback(
(message: string, options: ToastOptions = {}) =>
addToast({ message, type: 'info', ...options }),
[addToast]
)
return {
toasts,
@@ -91,4 +106,4 @@ export function useToasts() {
warning,
info,
}
}
}