index & hook #89

This commit is contained in:
Novattz
2025-12-26 22:11:44 +01:00
parent 919749d0ae
commit 3801404138
4 changed files with 189 additions and 8 deletions

View File

@@ -1,13 +1,27 @@
import { useState } from 'react'
import { invoke } from '@tauri-apps/api/core'
import { useAppContext } from '@/contexts/useAppContext'
import { useAppLogic } from '@/hooks'
import { useAppLogic, useConflictDetection } from '@/hooks'
import './styles/main.scss'
// Layout components
import { Header, Sidebar, InitialLoadingScreen, ErrorBoundary, UpdateScreen, AnimatedBackground } from '@/components/layout'
import {
Header,
Sidebar,
InitialLoadingScreen,
ErrorBoundary,
UpdateScreen,
AnimatedBackground,
} from '@/components/layout'
// Dialog components
import { ProgressDialog, DlcSelectionDialog, SettingsDialog } from '@/components/dialogs'
import {
ProgressDialog,
DlcSelectionDialog,
SettingsDialog,
ConflictDialog,
ReminderDialog,
} from '@/components/dialogs'
// Game components
import { GameList } from '@/components/games'
@@ -17,6 +31,7 @@ import { GameList } from '@/components/games'
*/
function App() {
const [updateComplete, setUpdateComplete] = useState(false)
// Get application logic from hook
const {
filter,
@@ -33,6 +48,7 @@ function App() {
// Get action handlers from context
const {
games,
dlcDialog,
handleDlcDialogClose,
handleProgressDialogClose,
@@ -45,8 +61,30 @@ function App() {
handleSettingsOpen,
handleSettingsClose,
handleSmokeAPISettingsOpen,
showToast,
} = useAppContext()
// Conflict detection
const { currentConflict, showReminder, resolveConflict, closeReminder } =
useConflictDetection(games)
// Handle conflict resolution
const handleConflictResolve = async () => {
const resolution = resolveConflict()
if (!resolution) return
// Always remove files - use the special conflict resolution command
try {
await invoke('resolve_platform_conflict', {
gameId: resolution.gameId,
conflictType: resolution.conflictType,
})
} catch (error) {
console.error('Error resolving conflict:', error)
showToast(`Failed to resolve conflict: ${error}`, 'error')
}
}
// Show update screen first
if (!updateComplete) {
return <UpdateScreen onComplete={() => setUpdateComplete(true)} />
@@ -73,7 +111,11 @@ function App() {
<div className="main-content">
{/* Sidebar for filtering */}
<Sidebar setFilter={setFilter} currentFilter={filter} onSettingsClick={handleSettingsOpen} />
<Sidebar
setFilter={setFilter}
currentFilter={filter}
onSettingsClick={handleSettingsOpen}
/>
{/* Show error or game list */}
{error ? (
@@ -123,10 +165,20 @@ function App() {
/>
{/* Settings Dialog */}
<SettingsDialog
visible ={settingsDialog.visible}
onClose={handleSettingsClose}
/>
<SettingsDialog visible={settingsDialog.visible} onClose={handleSettingsClose} />
{/* Conflict Detection Dialog */}
{currentConflict && (
<ConflictDialog
visible={true}
gameTitle={currentConflict.gameTitle}
conflictType={currentConflict.type}
onConfirm={handleConflictResolve}
/>
)}
{/* Steam Launch Options Reminder */}
<ReminderDialog visible={showReminder} onClose={closeReminder} />
</div>
</ErrorBoundary>
)

View File

@@ -8,6 +8,8 @@ export { default as ProgressDialog } from './ProgressDialog'
export { default as DlcSelectionDialog } from './DlcSelectionDialog'
export { default as SettingsDialog } from './SettingsDialog'
export { default as SmokeAPISettingsDialog } from './SmokeAPISettingsDialog'
export { default as ConflictDialog } from './ConflictDialog'
export { default as ReminderDialog } from './ReminderDialog'
// Export types
export type { DialogProps } from './Dialog'
@@ -17,3 +19,5 @@ export type { DialogFooterProps } from './DialogFooter'
export type { DialogActionsProps } from './DialogActions'
export type { ProgressDialogProps, InstallationInstructions } from './ProgressDialog'
export type { DlcSelectionDialogProps } from './DlcSelectionDialog'
export type { ConflictDialogProps } from './ConflictDialog'
export type { ReminderDialogProps } from './ReminderDialog'

View File

@@ -4,7 +4,9 @@ export { useDlcManager } from './useDlcManager'
export { useGameActions } from './useGameActions'
export { useToasts } from './useToasts'
export { useAppLogic } from './useAppLogic'
export { useConflictDetection } from './useConflictDetection'
// Export types
export type { ToastType, Toast, ToastOptions } from './useToasts'
export type { DlcDialogState } from './useDlcManager'
export type { Conflict, ConflictResolution } from './useConflictDetection'

View File

@@ -0,0 +1,123 @@
import { useState, useEffect, useCallback } from 'react'
import { Game } from '@/types'
export interface Conflict {
gameId: string
gameTitle: string
type: 'cream-to-proton' | 'smoke-to-native'
}
export interface ConflictResolution {
gameId: string
removeFiles: boolean
conflictType: 'cream-to-proton' | 'smoke-to-native'
}
/**
* Hook for detecting platform conflicts
* Identifies when unlocker files exist for the wrong platform
*/
export function useConflictDetection(games: Game[]) {
const [conflicts, setConflicts] = useState<Conflict[]>([])
const [currentConflict, setCurrentConflict] = useState<Conflict | null>(null)
const [showReminder, setShowReminder] = useState(false)
const [isProcessing, setIsProcessing] = useState(false)
const [resolvedConflicts, setResolvedConflicts] = useState<Set<string>>(new Set())
// Detect conflicts whenever games change
useEffect(() => {
const detectedConflicts: Conflict[] = []
games.forEach((game) => {
// Skip if we've already resolved a conflict for this game
if (resolvedConflicts.has(game.id)) {
return
}
// Conflict 1: CreamLinux installed but game is now Proton
if (!game.native && game.cream_installed) {
detectedConflicts.push({
gameId: game.id,
gameTitle: game.title,
type: 'cream-to-proton',
})
}
// Conflict 2: SmokeAPI installed but game is now Native
if (game.native && game.smoke_installed) {
detectedConflicts.push({
gameId: game.id,
gameTitle: game.title,
type: 'smoke-to-native',
})
}
})
setConflicts(detectedConflicts)
// Show the first conflict if we have any and not currently processing
if (detectedConflicts.length > 0 && !currentConflict && !isProcessing) {
setCurrentConflict(detectedConflicts[0])
}
}, [games, currentConflict, isProcessing, resolvedConflicts])
// Handle conflict resolution
const resolveConflict = useCallback((): ConflictResolution | null => {
if (!currentConflict || isProcessing) return null
setIsProcessing(true)
const resolution: ConflictResolution = {
gameId: currentConflict.gameId,
removeFiles: true, // Always remove files
conflictType: currentConflict.type,
}
// Mark this game as resolved so we don't re-detect the conflict
setResolvedConflicts((prev) => new Set(prev).add(currentConflict.gameId))
// Remove this conflict from the list
const remainingConflicts = conflicts.filter((c) => c.gameId !== currentConflict.gameId)
setConflicts(remainingConflicts)
// Close current conflict dialog immediately
setCurrentConflict(null)
// Determine what to show next based on conflict type
if (resolution.conflictType === 'cream-to-proton') {
// CreamLinux removal - show reminder after delay
setTimeout(() => {
setShowReminder(true)
setIsProcessing(false)
}, 100)
} else {
// SmokeAPI removal - no reminder, just show next conflict or finish
setTimeout(() => {
if (remainingConflicts.length > 0) {
setCurrentConflict(remainingConflicts[0])
}
setIsProcessing(false)
}, 100)
}
return resolution
}, [currentConflict, conflicts, isProcessing])
// Close reminder dialog
const closeReminder = useCallback(() => {
setShowReminder(false)
// After closing reminder, check if there are more conflicts
if (conflicts.length > 0) {
setCurrentConflict(conflicts[0])
}
}, [conflicts])
return {
currentConflict,
showReminder,
resolveConflict,
closeReminder,
hasConflicts: conflicts.length > 0,
}
}