mirror of
https://github.com/Novattz/creamlinux-installer.git
synced 2026-01-30 23:32:49 -05:00
add smokeapi settings dialog & styling #67
This commit is contained in:
@@ -43,6 +43,7 @@ function App() {
|
|||||||
settingsDialog,
|
settingsDialog,
|
||||||
handleSettingsOpen,
|
handleSettingsOpen,
|
||||||
handleSettingsClose,
|
handleSettingsClose,
|
||||||
|
handleSmokeAPISettingsOpen,
|
||||||
} = useAppContext()
|
} = useAppContext()
|
||||||
|
|
||||||
// Show update screen first
|
// Show update screen first
|
||||||
@@ -86,6 +87,7 @@ function App() {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onAction={handleGameAction}
|
onAction={handleGameAction}
|
||||||
onEdit={handleGameEdit}
|
onEdit={handleGameEdit}
|
||||||
|
onSmokeAPISettings={handleSmokeAPISettingsOpen}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
228
src/components/dialogs/SmokeAPISettingsDialog.tsx
Normal file
228
src/components/dialogs/SmokeAPISettingsDialog.tsx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogHeader,
|
||||||
|
DialogBody,
|
||||||
|
DialogFooter,
|
||||||
|
DialogActions,
|
||||||
|
} from '@/components/dialogs'
|
||||||
|
import { Button, AnimatedCheckbox } from '@/components/buttons'
|
||||||
|
import { Dropdown, DropdownOption } from '@/components/common'
|
||||||
|
//import { Icon, settings } from '@/components/icons'
|
||||||
|
|
||||||
|
interface SmokeAPIConfig {
|
||||||
|
$schema: string
|
||||||
|
$version: number
|
||||||
|
logging: boolean
|
||||||
|
log_steam_http: boolean
|
||||||
|
default_app_status: 'unlocked' | 'locked' | 'original'
|
||||||
|
override_app_status: Record<string, string>
|
||||||
|
override_dlc_status: Record<string, string>
|
||||||
|
auto_inject_inventory: boolean
|
||||||
|
extra_inventory_items: number[]
|
||||||
|
extra_dlcs: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SmokeAPISettingsDialogProps {
|
||||||
|
visible: boolean
|
||||||
|
onClose: () => void
|
||||||
|
gamePath: string
|
||||||
|
gameTitle: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG: SmokeAPIConfig = {
|
||||||
|
$schema:
|
||||||
|
'https://raw.githubusercontent.com/acidicoala/SmokeAPI/refs/tags/v4.0.0/res/SmokeAPI.schema.json',
|
||||||
|
$version: 4,
|
||||||
|
logging: false,
|
||||||
|
log_steam_http: false,
|
||||||
|
default_app_status: 'unlocked',
|
||||||
|
override_app_status: {},
|
||||||
|
override_dlc_status: {},
|
||||||
|
auto_inject_inventory: true,
|
||||||
|
extra_inventory_items: [],
|
||||||
|
extra_dlcs: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
const APP_STATUS_OPTIONS: DropdownOption<'unlocked' | 'locked' | 'original'>[] = [
|
||||||
|
{ value: 'unlocked', label: 'Unlocked' },
|
||||||
|
{ value: 'locked', label: 'Locked' },
|
||||||
|
{ value: 'original', label: 'Original' },
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SmokeAPI Settings Dialog
|
||||||
|
* Allows configuration of SmokeAPI for a specific game
|
||||||
|
*/
|
||||||
|
const SmokeAPISettingsDialog = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
gamePath,
|
||||||
|
gameTitle,
|
||||||
|
}: SmokeAPISettingsDialogProps) => {
|
||||||
|
const [enabled, setEnabled] = useState(false)
|
||||||
|
const [config, setConfig] = useState<SmokeAPIConfig>(DEFAULT_CONFIG)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [hasChanges, setHasChanges] = useState(false)
|
||||||
|
|
||||||
|
// Load existing config when dialog opens
|
||||||
|
const loadConfig = useCallback(async () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
try {
|
||||||
|
const existingConfig = await invoke<SmokeAPIConfig | null>('read_smokeapi_config', {
|
||||||
|
gamePath,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (existingConfig) {
|
||||||
|
setConfig(existingConfig)
|
||||||
|
setEnabled(true)
|
||||||
|
} else {
|
||||||
|
setConfig(DEFAULT_CONFIG)
|
||||||
|
setEnabled(false)
|
||||||
|
}
|
||||||
|
setHasChanges(false)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load SmokeAPI config:', error)
|
||||||
|
setConfig(DEFAULT_CONFIG)
|
||||||
|
setEnabled(false)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}, [gamePath])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible && gamePath) {
|
||||||
|
loadConfig()
|
||||||
|
}
|
||||||
|
}, [visible, gamePath, loadConfig])
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
try {
|
||||||
|
if (enabled) {
|
||||||
|
// Save the config
|
||||||
|
await invoke('write_smokeapi_config', {
|
||||||
|
gamePath,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Delete the config
|
||||||
|
await invoke('delete_smokeapi_config', {
|
||||||
|
gamePath,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setHasChanges(false)
|
||||||
|
onClose()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save SmokeAPI config:', error)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setHasChanges(false)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateConfig = <K extends keyof SmokeAPIConfig>(key: K, value: SmokeAPIConfig[K]) => {
|
||||||
|
setConfig((prev) => ({ ...prev, [key]: value }))
|
||||||
|
setHasChanges(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog visible={visible} onClose={handleCancel} size="medium">
|
||||||
|
<DialogHeader onClose={handleCancel} hideCloseButton={true}>
|
||||||
|
<div className="settings-header">
|
||||||
|
{/*<Icon name={settings} variant="solid" size="md" />*/}
|
||||||
|
<h3>SmokeAPI Settings</h3>
|
||||||
|
</div>
|
||||||
|
<p className="dialog-subtitle">{gameTitle}</p>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<DialogBody>
|
||||||
|
<div className="smokeapi-settings-content">
|
||||||
|
{/* Enable/Disable Section */}
|
||||||
|
<div className="settings-section">
|
||||||
|
<AnimatedCheckbox
|
||||||
|
checked={enabled}
|
||||||
|
onChange={() => {
|
||||||
|
setEnabled(!enabled)
|
||||||
|
setHasChanges(true)
|
||||||
|
}}
|
||||||
|
label="Enable SmokeAPI Configuration"
|
||||||
|
sublabel="Enable this to customize SmokeAPI settings for this game"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Settings Options */}
|
||||||
|
<div className={`settings-options ${!enabled ? 'disabled' : ''}`}>
|
||||||
|
<div className="settings-section">
|
||||||
|
<h4>General Settings</h4>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
label="Default App Status"
|
||||||
|
description="Specifies the default DLC status"
|
||||||
|
value={config.default_app_status}
|
||||||
|
options={APP_STATUS_OPTIONS}
|
||||||
|
onChange={(value) => updateConfig('default_app_status', value)}
|
||||||
|
disabled={!enabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-section">
|
||||||
|
<h4>Logging</h4>
|
||||||
|
|
||||||
|
<div className="checkbox-option">
|
||||||
|
<AnimatedCheckbox
|
||||||
|
checked={config.logging}
|
||||||
|
onChange={() => updateConfig('logging', !config.logging)}
|
||||||
|
label="Enable Logging"
|
||||||
|
sublabel="Enables logging to SmokeAPI.log.log file"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="checkbox-option">
|
||||||
|
<AnimatedCheckbox
|
||||||
|
checked={config.log_steam_http}
|
||||||
|
onChange={() => updateConfig('log_steam_http', !config.log_steam_http)}
|
||||||
|
label="Log Steam HTTP"
|
||||||
|
sublabel="Toggles logging of SteamHTTP traffic"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-section">
|
||||||
|
<h4>Inventory</h4>
|
||||||
|
|
||||||
|
<div className="checkbox-option">
|
||||||
|
<AnimatedCheckbox
|
||||||
|
checked={config.auto_inject_inventory}
|
||||||
|
onChange={() =>
|
||||||
|
updateConfig('auto_inject_inventory', !config.auto_inject_inventory)
|
||||||
|
}
|
||||||
|
label="Auto Inject Inventory"
|
||||||
|
sublabel="Automatically inject a list of all registered inventory items when the game queries user inventory"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogBody>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="secondary" onClick={handleCancel} disabled={isLoading}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" onClick={handleSave} disabled={isLoading || !hasChanges}>
|
||||||
|
{isLoading ? 'Saving...' : 'Save'}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SmokeAPISettingsDialog
|
||||||
@@ -7,6 +7,7 @@ export { default as DialogActions } from './DialogActions'
|
|||||||
export { default as ProgressDialog } from './ProgressDialog'
|
export { default as ProgressDialog } from './ProgressDialog'
|
||||||
export { default as DlcSelectionDialog } from './DlcSelectionDialog'
|
export { default as DlcSelectionDialog } from './DlcSelectionDialog'
|
||||||
export { default as SettingsDialog } from './SettingsDialog'
|
export { default as SettingsDialog } from './SettingsDialog'
|
||||||
|
export { default as SmokeAPISettingsDialog } from './SmokeAPISettingsDialog'
|
||||||
|
|
||||||
// Export types
|
// Export types
|
||||||
export type { DialogProps } from './Dialog'
|
export type { DialogProps } from './Dialog'
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ export interface ProgressDialogState {
|
|||||||
instructions?: InstallationInstructions
|
instructions?: InstallationInstructions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SmokeAPISettingsDialogState {
|
||||||
|
visible: boolean
|
||||||
|
gamePath: string
|
||||||
|
gameTitle: string
|
||||||
|
}
|
||||||
|
|
||||||
// Define the context type
|
// Define the context type
|
||||||
export interface AppContextType {
|
export interface AppContextType {
|
||||||
// Game state
|
// Game state
|
||||||
@@ -54,6 +60,11 @@ export interface AppContextType {
|
|||||||
handleSettingsOpen: () => void
|
handleSettingsOpen: () => void
|
||||||
handleSettingsClose: () => void
|
handleSettingsClose: () => void
|
||||||
|
|
||||||
|
// SmokeAPI settings
|
||||||
|
smokeAPISettingsDialog: SmokeAPISettingsDialogState
|
||||||
|
handleSmokeAPISettingsOpen: (gameId: string) => void
|
||||||
|
handleSmokeAPISettingsClose: () => void
|
||||||
|
|
||||||
// Toast notifications
|
// Toast notifications
|
||||||
showToast: (
|
showToast: (
|
||||||
message: string,
|
message: string,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useGames, useDlcManager, useGameActions, useToasts } from '@/hooks'
|
|||||||
import { DlcInfo } from '@/types'
|
import { DlcInfo } 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 } from '@/components/dialogs'
|
||||||
|
|
||||||
// Context provider component
|
// Context provider component
|
||||||
interface AppProviderProps {
|
interface AppProviderProps {
|
||||||
@@ -38,6 +39,17 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
// Settings dialog state
|
// Settings dialog state
|
||||||
const [settingsDialog, setSettingsDialog] = useState({ visible: false })
|
const [settingsDialog, setSettingsDialog] = useState({ visible: false })
|
||||||
|
|
||||||
|
// SmokeAPI settings dialog state
|
||||||
|
const [smokeAPISettingsDialog, setSmokeAPISettingsDialog] = useState<{
|
||||||
|
visible: boolean
|
||||||
|
gamePath: string
|
||||||
|
gameTitle: string
|
||||||
|
}>({
|
||||||
|
visible: false,
|
||||||
|
gamePath: '',
|
||||||
|
gameTitle: '',
|
||||||
|
})
|
||||||
|
|
||||||
// Settings handlers
|
// Settings handlers
|
||||||
const handleSettingsOpen = () => {
|
const handleSettingsOpen = () => {
|
||||||
setSettingsDialog({ visible: true })
|
setSettingsDialog({ visible: true })
|
||||||
@@ -47,6 +59,25 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
setSettingsDialog({ visible: false })
|
setSettingsDialog({ visible: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SmokeAPI settings handlers
|
||||||
|
const handleSmokeAPISettingsOpen = (gameId: string) => {
|
||||||
|
const game = games.find((g) => g.id === gameId)
|
||||||
|
if (!game) {
|
||||||
|
showError('Game not found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setSmokeAPISettingsDialog({
|
||||||
|
visible: true,
|
||||||
|
gamePath: game.path,
|
||||||
|
gameTitle: game.title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSmokeAPISettingsClose = () => {
|
||||||
|
setSmokeAPISettingsDialog((prev) => ({ ...prev, visible: false }))
|
||||||
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
@@ -201,6 +232,11 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
handleSettingsOpen,
|
handleSettingsOpen,
|
||||||
handleSettingsClose,
|
handleSettingsClose,
|
||||||
|
|
||||||
|
// SmokeAPI Settings
|
||||||
|
smokeAPISettingsDialog,
|
||||||
|
handleSmokeAPISettingsOpen,
|
||||||
|
handleSmokeAPISettingsClose,
|
||||||
|
|
||||||
// Toast notifications
|
// Toast notifications
|
||||||
showToast,
|
showToast,
|
||||||
}
|
}
|
||||||
@@ -209,6 +245,14 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
|||||||
<AppContext.Provider value={contextValue}>
|
<AppContext.Provider value={contextValue}>
|
||||||
{children}
|
{children}
|
||||||
<ToastContainer toasts={toasts} onDismiss={removeToast} />
|
<ToastContainer toasts={toasts} onDismiss={removeToast} />
|
||||||
|
|
||||||
|
{/* SmokeAPI Settings Dialog */}
|
||||||
|
<SmokeAPISettingsDialog
|
||||||
|
visible={smokeAPISettingsDialog.visible}
|
||||||
|
onClose={handleSmokeAPISettingsClose}
|
||||||
|
gamePath={smokeAPISettingsDialog.gamePath}
|
||||||
|
gameTitle={smokeAPISettingsDialog.gameTitle}
|
||||||
|
/>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2,3 +2,4 @@
|
|||||||
@forward './dlc_dialog';
|
@forward './dlc_dialog';
|
||||||
@forward './progress_dialog';
|
@forward './progress_dialog';
|
||||||
@forward './settings_dialog';
|
@forward './settings_dialog';
|
||||||
|
@forward './smokeapi_settings_dialog';
|
||||||
|
|||||||
66
src/styles/components/dialogs/_smokeapi_settings_dialog.scss
Normal file
66
src/styles/components/dialogs/_smokeapi_settings_dialog.scss
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
@use '../../themes/index' as *;
|
||||||
|
@use '../../abstracts/index' as *;
|
||||||
|
|
||||||
|
/*
|
||||||
|
SmokeAPI Settings Dialog styles
|
||||||
|
*/
|
||||||
|
|
||||||
|
.dialog-subtitle {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smokeapi-settings-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
transition: opacity var(--duration-normal) var(--easing-ease-out);
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
border-bottom: 1px solid var(--border-soft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-option {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px solid var(--border-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animated-checkbox {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.checkbox-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-sublabel {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user