mirror of
https://github.com/Novattz/creamlinux-installer.git
synced 2026-08-01 19:18:25 -04:00
contexts
This commit is contained in:
@@ -34,11 +34,23 @@ export interface RatingDialogState {
|
|||||||
steamPath: string
|
steamPath: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A single entry in the Overview page's recent-activity feed. Session-only
|
||||||
|
// (not persisted) just a friendlier view of what you've been doing since
|
||||||
|
// the app was launched.
|
||||||
|
export interface ActivityItem {
|
||||||
|
id: string
|
||||||
|
message: string
|
||||||
|
type: 'install' | 'uninstall' | 'update'
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
// Define the context type
|
// Define the context type
|
||||||
export interface AppContextType {
|
export interface AppContextType {
|
||||||
// Game state
|
// Game state
|
||||||
games: Game[]
|
games: Game[]
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
|
isInitialLoad: boolean
|
||||||
|
scanProgress: { message: string; progress: number }
|
||||||
error: string | null
|
error: string | null
|
||||||
loadGames: () => Promise<boolean>
|
loadGames: () => Promise<boolean>
|
||||||
handleProgressDialogClose: () => void
|
handleProgressDialogClose: () => void
|
||||||
@@ -64,11 +76,6 @@ export interface AppContextType {
|
|||||||
handleGameAction: (gameId: string, action: ActionType) => Promise<void>
|
handleGameAction: (gameId: string, action: ActionType) => Promise<void>
|
||||||
handleDlcConfirm: (selectedDlcs: DlcInfo[]) => void
|
handleDlcConfirm: (selectedDlcs: DlcInfo[]) => void
|
||||||
|
|
||||||
// Settings
|
|
||||||
settingsDialog: { visible: boolean }
|
|
||||||
handleSettingsOpen: () => void
|
|
||||||
handleSettingsClose: () => void
|
|
||||||
|
|
||||||
// SmokeAPI settings
|
// SmokeAPI settings
|
||||||
smokeAPISettingsDialog: SmokeAPISettingsDialogState
|
smokeAPISettingsDialog: SmokeAPISettingsDialogState
|
||||||
handleSmokeAPISettingsOpen: (gameId: string) => void
|
handleSmokeAPISettingsOpen: (gameId: string) => void
|
||||||
@@ -90,6 +97,9 @@ export interface AppContextType {
|
|||||||
handleSubmitRating: (worked: boolean) => Promise<void>
|
handleSubmitRating: (worked: boolean) => Promise<void>
|
||||||
reportingEnabled: boolean
|
reportingEnabled: boolean
|
||||||
|
|
||||||
|
// Recent activity feed (session-only, shown on Overview)
|
||||||
|
activityFeed: ActivityItem[]
|
||||||
|
|
||||||
// Toast notifications
|
// Toast notifications
|
||||||
showToast: (
|
showToast: (
|
||||||
message: string,
|
message: string,
|
||||||
|
|||||||
+110
-67
@@ -1,10 +1,10 @@
|
|||||||
import { ReactNode, useState, useEffect } from 'react'
|
import { ReactNode, useState, useEffect, useRef } from 'react'
|
||||||
import { AppContext, AppContextType } from './AppContext'
|
import { AppContext, AppContextType, ActivityItem } from './AppContext'
|
||||||
import { useGames, useDlcManager, useGameActions, useToasts } from '@/hooks'
|
import { useGames, useDlcManager, useGameActions, useToasts } from '@/hooks'
|
||||||
import { DlcInfo, Config, EpicGame } from '@/types'
|
import { DlcInfo, Config, EpicGame, Game } from '@/types'
|
||||||
import { ActionType } from '@/components/buttons/ActionButton'
|
import { ActionType } from '@/components/buttons/ActionButton'
|
||||||
import { ToastContainer } from '@/components/notifications'
|
import { ToastContainer } from '@/components/notifications'
|
||||||
import { SmokeAPISettingsDialog, OptInDialog, RatingDialog, SmokeAPIVotesDialog, EpicUnlockerSelectionDialog, ScreamAPISettingsDialog } from '@/components/dialogs'
|
import { ApiSettingsDialog, smokeApiSettingsSpec, screamApiSettingsSpec, OptInDialog, RatingDialog, SmokeAPIVotesDialog, UnlockerChoiceDialog } from '@/components/dialogs'
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
import { listen } from '@tauri-apps/api/event'
|
import { listen } from '@tauri-apps/api/event'
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ interface AppProviderProps {
|
|||||||
*/
|
*/
|
||||||
export const AppProvider = ({ children }: AppProviderProps) => {
|
export const AppProvider = ({ children }: AppProviderProps) => {
|
||||||
// Use our custom hooks
|
// Use our custom hooks
|
||||||
const { games, isLoading, error, loadGames, setGames } = useGames()
|
const { games, isLoading, isInitialLoad, scanProgress, error, loadGames, setGames } = useGames()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dlcDialog,
|
dlcDialog,
|
||||||
@@ -41,12 +41,11 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
|
|
||||||
const { toasts, removeToast, success, error: showError, warning, info } = useToasts()
|
const { toasts, removeToast, success, error: showError, warning, info } = useToasts()
|
||||||
|
|
||||||
// Settings dialog state
|
|
||||||
const [settingsDialog, setSettingsDialog] = useState({ visible: false })
|
|
||||||
|
|
||||||
const [epicGames, setEpicGames] = useState<EpicGame[]>([])
|
const [epicGames, setEpicGames] = useState<EpicGame[]>([])
|
||||||
const [epicLoading, setEpicLoading] = useState(false)
|
const [epicLoading, setEpicLoading] = useState(false)
|
||||||
const [epicInstallingId, setEpicInstallingId] = useState<string | null>(null)
|
const [epicInstallingId, setEpicInstallingId] = useState<string | null>(null)
|
||||||
|
const epicGamesRef = useRef<EpicGame[]>(epicGames)
|
||||||
|
epicGamesRef.current = epicGames
|
||||||
|
|
||||||
const [epicUnlockerDialog, setEpicUnlockerDialog] = useState<{
|
const [epicUnlockerDialog, setEpicUnlockerDialog] = useState<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
@@ -84,6 +83,17 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
const [optInDialog, setOptInDialog] = useState(false)
|
const [optInDialog, setOptInDialog] = useState(false)
|
||||||
const [reportingEnabled, setReportingEnabled] = useState(false)
|
const [reportingEnabled, setReportingEnabled] = useState(false)
|
||||||
|
|
||||||
|
// Recent activity feed (session-only) shown on the Overview page
|
||||||
|
const [activityFeed, setActivityFeed] = useState<ActivityItem[]>([])
|
||||||
|
const pushActivity = (message: string, type: ActivityItem['type']) => {
|
||||||
|
setActivityFeed((prev) =>
|
||||||
|
[{ id: `${Date.now()}-${Math.random()}`, message, type, timestamp: Date.now() }, ...prev].slice(
|
||||||
|
0,
|
||||||
|
20
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Rating dialog state
|
// Rating dialog state
|
||||||
const [ratingDialog, setRatingDialog] = useState<{
|
const [ratingDialog, setRatingDialog] = useState<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
@@ -114,13 +124,16 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
let unlisten: (() => void) | undefined
|
let unlisten: (() => void) | undefined
|
||||||
listen<EpicGame>('epic-game-updated', (event) => {
|
listen<EpicGame>('epic-game-updated', (event) => {
|
||||||
const updated = event.payload
|
const updated = event.payload
|
||||||
const prev = epicGames.find((g) => g.app_name === updated.app_name)
|
// Read from the ref (not the epicGames closure) so this effect doesn't
|
||||||
|
// need epicGames as a dependency. That would tear down and re-create
|
||||||
|
// the Tauri listener on every single update instead of registering once.
|
||||||
|
const prev = epicGamesRef.current.find((g) => g.app_name === updated.app_name)
|
||||||
|
|
||||||
setEpicGames((games) =>
|
setEpicGames((games) =>
|
||||||
games.map((g) => (g.app_name === updated.app_name ? updated : g))
|
games.map((g) => (g.app_name === updated.app_name ? updated : g))
|
||||||
)
|
)
|
||||||
setEpicInstallingId(null)
|
setEpicInstallingId(null)
|
||||||
|
|
||||||
// Determine what changed and show appropriate toast
|
// Determine what changed and show appropriate toast
|
||||||
if (prev) {
|
if (prev) {
|
||||||
const installedScream = !prev.scream_installed && updated.scream_installed
|
const installedScream = !prev.scream_installed && updated.scream_installed
|
||||||
@@ -130,12 +143,16 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
|
|
||||||
if (installedScream) {
|
if (installedScream) {
|
||||||
success(`ScreamAPI installed for ${updated.title}`)
|
success(`ScreamAPI installed for ${updated.title}`)
|
||||||
|
pushActivity(`Installed ScreamAPI for ${updated.title}`, 'install')
|
||||||
} else if (uninstalledScream) {
|
} else if (uninstalledScream) {
|
||||||
info(`ScreamAPI removed from ${updated.title}`)
|
info(`ScreamAPI removed from ${updated.title}`)
|
||||||
|
pushActivity(`Removed ScreamAPI from ${updated.title}`, 'uninstall')
|
||||||
} else if (installedKoa) {
|
} else if (installedKoa) {
|
||||||
success(`Koaloader installed for ${updated.title}`)
|
success(`Koaloader installed for ${updated.title}`)
|
||||||
|
pushActivity(`Installed Koaloader for ${updated.title}`, 'install')
|
||||||
} else if (uninstalledKoa) {
|
} else if (uninstalledKoa) {
|
||||||
info(`Koaloader removed from ${updated.title}`)
|
info(`Koaloader removed from ${updated.title}`)
|
||||||
|
pushActivity(`Removed Koaloader from ${updated.title}`, 'uninstall')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updated.proxy_fallback_used) {
|
if (updated.proxy_fallback_used) {
|
||||||
@@ -147,7 +164,7 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
}
|
}
|
||||||
}).then((fn) => { unlisten = fn })
|
}).then((fn) => { unlisten = fn })
|
||||||
return () => { unlisten?.() }
|
return () => { unlisten?.() }
|
||||||
}, [epicGames, success, info, warning])
|
}, [success, info, warning])
|
||||||
|
|
||||||
const loadEpicGames = async () => {
|
const loadEpicGames = async () => {
|
||||||
setEpicLoading(true)
|
setEpicLoading(true)
|
||||||
@@ -195,15 +212,6 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
if (game) runEpicAction(game, 'install_koaloader')
|
if (game) runEpicAction(game, 'install_koaloader')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings handlers
|
|
||||||
const handleSettingsOpen = () => {
|
|
||||||
setSettingsDialog({ visible: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSettingsClose = () => {
|
|
||||||
setSettingsDialog({ visible: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
// SmokeAPI settings handlers
|
// SmokeAPI settings handlers
|
||||||
const handleSmokeAPISettingsOpen = (gameId: string) => {
|
const handleSmokeAPISettingsOpen = (gameId: string) => {
|
||||||
const game = games.find((g) => g.id === gameId)
|
const game = games.find((g) => g.id === gameId)
|
||||||
@@ -234,9 +242,12 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
const handleSmokeAPIVotesConfirm = () => {
|
const handleSmokeAPIVotesConfirm = () => {
|
||||||
const gameId = smokeAPIVotesDialog.gameId
|
const gameId = smokeAPIVotesDialog.gameId
|
||||||
setSmokeAPIVotesDialog({ visible: false, gameId: null, gameTitle: null })
|
setSmokeAPIVotesDialog({ visible: false, gameId: null, gameTitle: null })
|
||||||
if (gameId) {
|
const game = gameId ? games.find((g) => g.id === gameId) : undefined
|
||||||
// Now actually run the install
|
if (gameId && game) {
|
||||||
executeGameAction(gameId, 'install_smoke', games)
|
// Now actually run the install this is what was silently skipping
|
||||||
|
// the success toast and activity feed entry before, since it called
|
||||||
|
// the raw action executor instead of the notifying wrapper.
|
||||||
|
runGameAction(gameId, 'install_smoke', game)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +301,39 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs a game action and reports the result via toast + activity feed.
|
||||||
|
// Used both by handleGameAction's own fallthrough case and by
|
||||||
|
// handleSmokeAPIVotesConfirm, which needs to run the action *after* the
|
||||||
|
// votes dialog closes without re-triggering handleGameAction's own
|
||||||
|
// interception checks (which would just show that dialog again).
|
||||||
|
const runGameAction = async (gameId: string, action: ActionType, game: Game) => {
|
||||||
|
setGames((prevGames) =>
|
||||||
|
prevGames.map((g) => (g.id === gameId ? { ...g, installing: true } : g))
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await executeGameAction(gameId, action, games)
|
||||||
|
|
||||||
|
const product = action.includes('cream') ? 'CreamLinux' : 'SmokeAPI'
|
||||||
|
const isUninstall = action.includes('uninstall')
|
||||||
|
const isInstall = action.includes('install') && !isUninstall
|
||||||
|
|
||||||
|
if (isInstall) {
|
||||||
|
success(`Successfully installed ${product} for ${game.title}`)
|
||||||
|
pushActivity(`Installed ${product} for ${game.title}`, 'install')
|
||||||
|
} else if (isUninstall) {
|
||||||
|
info(`${product} uninstalled from ${game.title}`)
|
||||||
|
pushActivity(`Uninstalled ${product} from ${game.title}`, 'uninstall')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError(`Action failed: ${error}`)
|
||||||
|
} finally {
|
||||||
|
setGames((prevGames) =>
|
||||||
|
prevGames.map((g) => (g.id === gameId ? { ...g, installing: false } : g))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Game action handler with proper error reporting
|
// Game action handler with proper error reporting
|
||||||
const handleGameAction = async (gameId: string, action: ActionType) => {
|
const handleGameAction = async (gameId: string, action: ActionType) => {
|
||||||
const game = games.find((g) => g.id === gameId)
|
const game = games.find((g) => g.id === gameId)
|
||||||
@@ -341,7 +385,7 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// This will show the UnlockerSelectionDialog and handle the callback
|
// This will show the UnlockerChoiceDialog and handle the callback
|
||||||
await executeGameAction(gameId, action, games)
|
await executeGameAction(gameId, action, games)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(`Action failed: ${error}`)
|
showError(`Action failed: ${error}`)
|
||||||
@@ -355,41 +399,12 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For other actions (uninstall cream, install/uninstall smoke)
|
// For other actions (uninstall cream, install/uninstall smoke)
|
||||||
// Mark game as installing
|
await runGameAction(gameId, action, game)
|
||||||
setGames((prevGames) =>
|
|
||||||
prevGames.map((g) => (g.id === gameId ? { ...g, installing: true } : g))
|
|
||||||
)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await executeGameAction(gameId, action, games)
|
|
||||||
|
|
||||||
// Show appropriate success message based on action type
|
|
||||||
const product = action.includes('cream') ? 'CreamLinux' : 'SmokeAPI'
|
|
||||||
const isUninstall = action.includes('uninstall')
|
|
||||||
const isInstall = action.includes('install') && !isUninstall
|
|
||||||
|
|
||||||
console.log('DEBUG: Action processed. Product:', product, 'isInstall:', isInstall, 'isUninstall:', isUninstall, 'action:', action)
|
|
||||||
|
|
||||||
if (isInstall) {
|
|
||||||
success(`Successfully installed ${product} for ${game.title}`)
|
|
||||||
} else if (isUninstall) {
|
|
||||||
info(`${product} uninstalled from ${game.title}`)
|
|
||||||
} else {
|
|
||||||
console.log('Unknown action type:', action)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError(`Action failed: ${error}`)
|
|
||||||
} finally {
|
|
||||||
// Reset installing state
|
|
||||||
setGames((prevGames) =>
|
|
||||||
prevGames.map((g) => (g.id === gameId ? { ...g, installing: false } : g))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DLC confirmation wrapper
|
// DLC confirmation wrapper
|
||||||
const handleDlcConfirm = (selectedDlcs: DlcInfo[]) => {
|
const handleDlcConfirm = (selectedDlcs: DlcInfo[]) => {
|
||||||
const { gameId, isEditMode } = dlcDialog
|
const { gameId, gameTitle, isEditMode } = dlcDialog
|
||||||
|
|
||||||
// Create a deep copy to ensure we don't have reference issues
|
// Create a deep copy to ensure we don't have reference issues
|
||||||
const dlcsCopy = selectedDlcs.map((dlc) => ({ ...dlc }))
|
const dlcsCopy = selectedDlcs.map((dlc) => ({ ...dlc }))
|
||||||
@@ -416,6 +431,12 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
? 'DLC configuration updated successfully'
|
? 'DLC configuration updated successfully'
|
||||||
: 'CreamLinux installed with selected DLCs'
|
: 'CreamLinux installed with selected DLCs'
|
||||||
)
|
)
|
||||||
|
pushActivity(
|
||||||
|
isEditMode
|
||||||
|
? `Updated DLC configuration for ${gameTitle}`
|
||||||
|
: `Installed CreamLinux for ${gameTitle}`,
|
||||||
|
isEditMode ? 'update' : 'install'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
showError(`DLC operation failed: ${error}`)
|
showError(`DLC operation failed: ${error}`)
|
||||||
@@ -455,6 +476,8 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
// Game state
|
// Game state
|
||||||
games,
|
games,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isInitialLoad,
|
||||||
|
scanProgress,
|
||||||
error,
|
error,
|
||||||
loadGames,
|
loadGames,
|
||||||
|
|
||||||
@@ -482,11 +505,6 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
handleDlcConfirm,
|
handleDlcConfirm,
|
||||||
handleProgressDialogClose: handleCloseProgressDialog,
|
handleProgressDialogClose: handleCloseProgressDialog,
|
||||||
|
|
||||||
// Settings
|
|
||||||
settingsDialog,
|
|
||||||
handleSettingsOpen,
|
|
||||||
handleSettingsClose,
|
|
||||||
|
|
||||||
// SmokeAPI Settings
|
// SmokeAPI Settings
|
||||||
smokeAPISettingsDialog,
|
smokeAPISettingsDialog,
|
||||||
handleSmokeAPISettingsOpen,
|
handleSmokeAPISettingsOpen,
|
||||||
@@ -504,6 +522,9 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
handleSubmitRating,
|
handleSubmitRating,
|
||||||
reportingEnabled,
|
reportingEnabled,
|
||||||
|
|
||||||
|
// Recent activity feed
|
||||||
|
activityFeed,
|
||||||
|
|
||||||
// Toast notifications
|
// Toast notifications
|
||||||
showToast,
|
showToast,
|
||||||
|
|
||||||
@@ -561,28 +582,50 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
<ToastContainer toasts={toasts} onDismiss={removeToast} />
|
<ToastContainer toasts={toasts} onDismiss={removeToast} />
|
||||||
|
|
||||||
{/* SmokeAPI Settings Dialog */}
|
{/* SmokeAPI Settings Dialog */}
|
||||||
<SmokeAPISettingsDialog
|
<ApiSettingsDialog
|
||||||
visible={smokeAPISettingsDialog.visible}
|
visible={smokeAPISettingsDialog.visible}
|
||||||
onClose={handleSmokeAPISettingsClose}
|
onClose={handleSmokeAPISettingsClose}
|
||||||
gamePath={smokeAPISettingsDialog.gamePath}
|
gamePath={smokeAPISettingsDialog.gamePath}
|
||||||
gameTitle={smokeAPISettingsDialog.gameTitle}
|
gameTitle={smokeAPISettingsDialog.gameTitle}
|
||||||
|
spec={smokeApiSettingsSpec}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Epic Unlocker Selection Dialog */}
|
{/* Epic Unlocker Selection Dialog */}
|
||||||
<EpicUnlockerSelectionDialog
|
<UnlockerChoiceDialog
|
||||||
visible={epicUnlockerDialog.visible}
|
visible={epicUnlockerDialog.visible}
|
||||||
game={epicUnlockerDialog.game}
|
gameTitle={epicUnlockerDialog.game?.title ?? ''}
|
||||||
onClose={() => setEpicUnlockerDialog({ visible: false, game: null })}
|
onClose={() => setEpicUnlockerDialog({ visible: false, game: null })}
|
||||||
onSelectScreamAPI={handleSelectScreamAPI}
|
options={[
|
||||||
onSelectKoaloader={handleSelectKoaloader}
|
{
|
||||||
|
key: 'scream',
|
||||||
|
title: 'ScreamAPI',
|
||||||
|
badge: 'recommended',
|
||||||
|
description:
|
||||||
|
'Replaces the EOS SDK DLL directly with ScreamAPI. Works for most Epic games and requires no additional files. DLC unlocking is automatic.',
|
||||||
|
buttonLabel: 'Install ScreamAPI',
|
||||||
|
buttonVariant: 'primary',
|
||||||
|
onSelect: handleSelectScreamAPI,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'koaloader',
|
||||||
|
title: 'Koaloader + ScreamAPI',
|
||||||
|
badge: 'alternative',
|
||||||
|
description:
|
||||||
|
"Uses a proxy DLL to inject ScreamAPI without modifying the EOS SDK. Try this if the recommended method doesn't work for your game.",
|
||||||
|
buttonLabel: 'Install Koaloader',
|
||||||
|
buttonVariant: 'secondary',
|
||||||
|
onSelect: handleSelectKoaloader,
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* ScreamAPI Settings Dialog */}
|
{/* ScreamAPI Settings Dialog */}
|
||||||
<ScreamAPISettingsDialog
|
<ApiSettingsDialog
|
||||||
visible={screamSettingsDialog.visible}
|
visible={screamSettingsDialog.visible}
|
||||||
onClose={() => setScreamSettingsDialog({ visible: false, game: null })}
|
onClose={() => setScreamSettingsDialog({ visible: false, game: null })}
|
||||||
gamePath={screamSettingsDialog.game?.install_path ?? ''}
|
gamePath={screamSettingsDialog.game?.install_path ?? ''}
|
||||||
gameTitle={screamSettingsDialog.game?.title ?? ''}
|
gameTitle={screamSettingsDialog.game?.title ?? ''}
|
||||||
|
spec={screamApiSettingsSpec}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* SmokeAPI Votes Dialog */}
|
{/* SmokeAPI Votes Dialog */}
|
||||||
|
|||||||
Reference in New Issue
Block a user