mirror of
https://github.com/Novattz/creamlinux-installer.git
synced 2026-04-30 03:52:04 -04:00
stuff
This commit is contained in:
@@ -64,6 +64,8 @@ function App() {
|
||||
handleSettingsOpen,
|
||||
handleSettingsClose,
|
||||
handleSmokeAPISettingsOpen,
|
||||
handleOpenRating,
|
||||
reportingEnabled,
|
||||
showToast,
|
||||
unlockerSelectionDialog,
|
||||
handleSelectCreamLinux,
|
||||
@@ -143,6 +145,8 @@ function App() {
|
||||
onAction={handleGameAction}
|
||||
onEdit={handleGameEdit}
|
||||
onSmokeAPISettings={handleSmokeAPISettingsOpen}
|
||||
onRate={handleOpenRating}
|
||||
reportingEnabled={reportingEnabled}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -190,6 +194,7 @@ function App() {
|
||||
{/* Unlocker Selection Dialog */}
|
||||
<UnlockerSelectionDialog
|
||||
visible={unlockerSelectionDialog.visible}
|
||||
gameId={unlockerSelectionDialog.gameId}
|
||||
gameTitle={unlockerSelectionDialog.gameTitle || ''}
|
||||
onClose={closeUnlockerDialog}
|
||||
onSelectCreamLinux={handleSelectCreamLinux}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export { default as LoadingIndicator } from './LoadingIndicator'
|
||||
export { default as ProgressBar } from './ProgressBar'
|
||||
export { default as Dropdown } from './Dropdown'
|
||||
export { default as VotesDisplay } from './VotesDisplay'
|
||||
|
||||
export type { LoadingSize, LoadingType } from './LoadingIndicator'
|
||||
export type { DropdownOption } from './Dropdown'
|
||||
export type { GameVotes } from './VotesDisplay'
|
||||
@@ -11,7 +11,10 @@ export { default as SettingsDialog } from './SettingsDialog'
|
||||
export { default as SmokeAPISettingsDialog } from './SmokeAPISettingsDialog'
|
||||
export { default as ConflictDialog } from './ConflictDialog'
|
||||
export { default as DisclaimerDialog } from './DisclaimerDialog'
|
||||
export { default as UnlockerSelectionDialog} from './UnlockerSelectionDialog'
|
||||
export { default as UnlockerSelectionDialog } from './UnlockerSelectionDialog'
|
||||
export { default as OptInDialog } from './OptInDialog'
|
||||
export { default as RatingDialog } from './RatingDialog'
|
||||
export { default as SmokeAPIVotesDialog } from './SmokeAPIVotesDialog'
|
||||
|
||||
// Export types
|
||||
export type { DialogProps } from './Dialog'
|
||||
@@ -24,3 +27,5 @@ export type { DlcSelectionDialogProps } from './DlcSelectionDialog'
|
||||
export type { AddDlcDialogProps } from './AddDlcDialog'
|
||||
export type { ConflictDialogProps, Conflict } from './ConflictDialog'
|
||||
export type { UnlockerSelectionDialogProps } from './UnlockerSelectionDialog'
|
||||
export type { RatingDialogProps } from './RatingDialog'
|
||||
export type { SmokeAPIVotesDialogProps } from './SmokeAPIVotesDialog'
|
||||
@@ -9,13 +9,15 @@ interface GameItemProps {
|
||||
onAction: (gameId: string, action: ActionType) => Promise<void>
|
||||
onEdit?: (gameId: string) => void
|
||||
onSmokeAPISettings?: (gameId: string) => void
|
||||
onRate?: (gameId: string) => void
|
||||
reportingEnabled?: boolean // When false/undefined, rate button is not rendered at all.
|
||||
}
|
||||
|
||||
/**
|
||||
* Individual game card component
|
||||
* Displays game information and action buttons
|
||||
*/
|
||||
const GameItem = ({ game, onAction, onEdit, onSmokeAPISettings }: GameItemProps) => {
|
||||
const GameItem = ({ game, onAction, onEdit, onSmokeAPISettings, onRate, reportingEnabled }: GameItemProps) => {
|
||||
const [imageUrl, setImageUrl] = useState<string | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [hasError, setHasError] = useState(false)
|
||||
@@ -93,6 +95,13 @@ const GameItem = ({ game, onAction, onEdit, onSmokeAPISettings }: GameItemProps)
|
||||
}
|
||||
}
|
||||
|
||||
// Rating handler
|
||||
const handleRate = () => {
|
||||
if (onRate && (game.cream_installed || game.smoke_installed)) {
|
||||
onRate(game.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine background image
|
||||
const backgroundImage =
|
||||
!isLoading && imageUrl
|
||||
@@ -179,6 +188,20 @@ const GameItem = ({ game, onAction, onEdit, onSmokeAPISettings }: GameItemProps)
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Rate button */}
|
||||
{(game.cream_installed || game.smoke_installed) && onRate && reportingEnabled && (
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={handleRate}
|
||||
disabled={!!game.installing}
|
||||
title="Rate compatibility"
|
||||
className="edit-button rate-button"
|
||||
leftIcon={<Icon name="Star" variant="solid" size="md" />}
|
||||
iconOnly
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Edit button - only enabled if CreamLinux is installed */}
|
||||
{game.cream_installed && (
|
||||
<Button
|
||||
|
||||
@@ -10,13 +10,15 @@ interface GameListProps {
|
||||
onAction: (gameId: string, action: ActionType) => Promise<void>
|
||||
onEdit?: (gameId: string) => void
|
||||
onSmokeAPISettings?: (gameId: string) => void
|
||||
onRate?: (gameId: string) => void
|
||||
reportingEnabled?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Main game list component
|
||||
* Displays games in a grid with search and filtering applied
|
||||
*/
|
||||
const GameList = ({ games, isLoading, onAction, onEdit, onSmokeAPISettings }: GameListProps) => {
|
||||
const GameList = ({ games, isLoading, onAction, onEdit, onSmokeAPISettings, onRate, reportingEnabled }: GameListProps) => {
|
||||
const [imagesPreloaded, setImagesPreloaded] = useState(false)
|
||||
|
||||
// Sort games alphabetically by title
|
||||
@@ -57,7 +59,7 @@ const GameList = ({ games, isLoading, onAction, onEdit, onSmokeAPISettings }: Ga
|
||||
) : (
|
||||
<div className="game-grid">
|
||||
{sortedGames.map((game) => (
|
||||
<GameItem key={game.id} game={game} onAction={onAction} onEdit={onEdit} onSmokeAPISettings={onSmokeAPISettings} />
|
||||
<GameItem key={game.id} game={game} onAction={onAction} onEdit={onEdit} onSmokeAPISettings={onSmokeAPISettings} onRate={onRate} reportingEnabled={reportingEnabled} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -26,6 +26,14 @@ export interface SmokeAPISettingsDialogState {
|
||||
gameTitle: string
|
||||
}
|
||||
|
||||
export interface RatingDialogState {
|
||||
visible: boolean
|
||||
gameId: string
|
||||
gameTitle: string
|
||||
unlocker: 'creamlinux' | 'smokeapi'
|
||||
steamPath: string
|
||||
}
|
||||
|
||||
// Define the context type
|
||||
export interface AppContextType {
|
||||
// Game state
|
||||
@@ -56,6 +64,22 @@ export interface AppContextType {
|
||||
handleSmokeAPISettingsOpen: (gameId: string) => void
|
||||
handleSmokeAPISettingsClose: () => void
|
||||
|
||||
// SmokeAPI votes dialog
|
||||
smokeAPIVotesDialog: {
|
||||
visible: boolean
|
||||
gameId: string | null
|
||||
gameTitle: string | null
|
||||
}
|
||||
handleSmokeAPIVotesClose: () => void
|
||||
handleSmokeAPIVotesConfirm: () => void
|
||||
|
||||
// Rating dialog
|
||||
ratingDialog: RatingDialogState
|
||||
handleOpenRating: (gameId: string) => void
|
||||
handleCloseRating: () => void
|
||||
handleSubmitRating: (worked: boolean) => Promise<void>
|
||||
reportingEnabled: boolean
|
||||
|
||||
// Toast notifications
|
||||
showToast: (
|
||||
message: string,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { ReactNode, useState, useEffect } from 'react'
|
||||
import { AppContext, AppContextType } from './AppContext'
|
||||
import { useGames, useDlcManager, useGameActions, useToasts } from '@/hooks'
|
||||
import { DlcInfo } from '@/types'
|
||||
import { DlcInfo, Config } from '@/types'
|
||||
import { ActionType } from '@/components/buttons/ActionButton'
|
||||
import { ToastContainer } from '@/components/notifications'
|
||||
import { SmokeAPISettingsDialog } from '@/components/dialogs'
|
||||
import { SmokeAPISettingsDialog, OptInDialog, RatingDialog, SmokeAPIVotesDialog } from '@/components/dialogs'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
// Context provider component
|
||||
interface AppProviderProps {
|
||||
@@ -53,6 +54,47 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
||||
gameTitle: '',
|
||||
})
|
||||
|
||||
// SmokeAPI votes dialog state
|
||||
const [smokeAPIVotesDialog, setSmokeAPIVotesDialog] = useState<{
|
||||
visible: boolean
|
||||
gameId: string | null
|
||||
gameTitle: string | null
|
||||
}>({
|
||||
visible: false,
|
||||
gameId: null,
|
||||
gameTitle: null,
|
||||
})
|
||||
|
||||
// Opt-in dialog state
|
||||
const [optInDialog, setOptInDialog] = useState(false)
|
||||
const [reportingEnabled, setReportingEnabled] = useState(false)
|
||||
|
||||
// Rating dialog state
|
||||
const [ratingDialog, setRatingDialog] = useState<{
|
||||
visible: boolean
|
||||
gameId: string
|
||||
gameTitle: string
|
||||
unlocker: 'creamlinux' | 'smokeapi'
|
||||
steamPath: string
|
||||
}>({
|
||||
visible: false,
|
||||
gameId: '',
|
||||
gameTitle: '',
|
||||
unlocker: 'creamlinux',
|
||||
steamPath: '',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
invoke<Config>('load_config')
|
||||
.then((cfg) => {
|
||||
setReportingEnabled(cfg.reporting_opted_in)
|
||||
if (!cfg.reporting_has_seen_prompt) {
|
||||
setOptInDialog(true)
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error('Failed to load config for reporting check:', err))
|
||||
}, [])
|
||||
|
||||
// Settings handlers
|
||||
const handleSettingsOpen = () => {
|
||||
setSettingsDialog({ visible: true })
|
||||
@@ -85,6 +127,69 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleSmokeAPIVotesClose = () => {
|
||||
setSmokeAPIVotesDialog({ visible: false, gameId: null, gameTitle: null })
|
||||
}
|
||||
|
||||
const handleSmokeAPIVotesConfirm = () => {
|
||||
const gameId = smokeAPIVotesDialog.gameId
|
||||
setSmokeAPIVotesDialog({ visible: false, gameId: null, gameTitle: null })
|
||||
if (gameId) {
|
||||
// Now actually run the install
|
||||
executeGameAction(gameId, 'install_smoke', games)
|
||||
}
|
||||
}
|
||||
|
||||
const handleOptInAccept = async () => {
|
||||
try {
|
||||
await invoke('set_reporting_opt_in', { optedIn: true })
|
||||
setReportingEnabled(true)
|
||||
} catch (err) {
|
||||
console.error('Failed to save reporting opt-in:', err)
|
||||
}
|
||||
setOptInDialog(false)
|
||||
}
|
||||
|
||||
const handleOptInDecline = async () => {
|
||||
try {
|
||||
await invoke('set_reporting_opt_in', { optedIn: false })
|
||||
setReportingEnabled(false)
|
||||
} catch (err) {
|
||||
console.error('Failed to save reporting opt-out:', err)
|
||||
}
|
||||
setOptInDialog(false)
|
||||
}
|
||||
|
||||
const handleOpenRating = (gameId: string) => {
|
||||
const game = games.find((g) => g.id === gameId)
|
||||
if (!game) return
|
||||
|
||||
setRatingDialog({
|
||||
visible: true,
|
||||
gameId,
|
||||
gameTitle: game.title,
|
||||
unlocker: game.cream_installed ? 'creamlinux' : 'smokeapi',
|
||||
steamPath: game.path,
|
||||
})
|
||||
}
|
||||
|
||||
const handleCloseRating = () => {
|
||||
setRatingDialog((prev) => ({ ...prev, visible: false }))
|
||||
}
|
||||
|
||||
const handleSubmitRating = async (worked: boolean) => {
|
||||
try {
|
||||
await invoke('submit_report', {
|
||||
gameId: ratingDialog.gameId,
|
||||
unlocker: ratingDialog.unlocker,
|
||||
worked,
|
||||
steamPath: ratingDialog.steamPath,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Failed to submit rating:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Game action handler with proper error reporting
|
||||
const handleGameAction = async (gameId: string, action: ActionType) => {
|
||||
const game = games.find((g) => g.id === gameId)
|
||||
@@ -117,6 +222,16 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
// intercept install_smoke for votes dialog
|
||||
if (action === 'install_smoke' && !game.native) {
|
||||
setSmokeAPIVotesDialog({
|
||||
visible: true,
|
||||
gameId: game.id,
|
||||
gameTitle: game.title,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// For install_unlocker action, executeGameAction will handle showing the dialog
|
||||
// We should NOT show any notifications here - they'll be shown after actual installation
|
||||
if (action === 'install_unlocker') {
|
||||
@@ -267,6 +382,18 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
||||
handleSmokeAPISettingsOpen,
|
||||
handleSmokeAPISettingsClose,
|
||||
|
||||
// SmokeAPI Votes
|
||||
smokeAPIVotesDialog,
|
||||
handleSmokeAPIVotesClose,
|
||||
handleSmokeAPIVotesConfirm,
|
||||
|
||||
// Rating
|
||||
ratingDialog,
|
||||
handleOpenRating,
|
||||
handleCloseRating,
|
||||
handleSubmitRating,
|
||||
reportingEnabled,
|
||||
|
||||
// Toast notifications
|
||||
showToast,
|
||||
|
||||
@@ -330,6 +457,32 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
||||
gamePath={smokeAPISettingsDialog.gamePath}
|
||||
gameTitle={smokeAPISettingsDialog.gameTitle}
|
||||
/>
|
||||
|
||||
{/* SmokeAPI Votes Dialog */}
|
||||
<SmokeAPIVotesDialog
|
||||
visible={smokeAPIVotesDialog.visible}
|
||||
gameId={smokeAPIVotesDialog.gameId}
|
||||
gameTitle={smokeAPIVotesDialog.gameTitle}
|
||||
onClose={handleSmokeAPIVotesClose}
|
||||
onConfirm={handleSmokeAPIVotesConfirm}
|
||||
/>
|
||||
|
||||
{/* Opt-in Dialog */}
|
||||
<OptInDialog
|
||||
visible={optInDialog}
|
||||
onAccept={handleOptInAccept}
|
||||
onDecline={handleOptInDecline}
|
||||
/>
|
||||
|
||||
{/* Rating Dialog */}
|
||||
<RatingDialog
|
||||
visible={ratingDialog.visible}
|
||||
gameId={ratingDialog.gameId}
|
||||
gameTitle={ratingDialog.gameTitle}
|
||||
unlocker={ratingDialog.unlocker}
|
||||
onClose={handleCloseRating}
|
||||
onSubmit={handleSubmitRating}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -5,4 +5,6 @@
|
||||
export interface Config {
|
||||
/** Whether to show the disclaimer on startup */
|
||||
show_disclaimer: boolean
|
||||
reporting_opted_in: boolean
|
||||
reporting_has_seen_prompt: boolean
|
||||
}
|
||||
Reference in New Issue
Block a user