mirror of
https://github.com/Novattz/creamlinux-installer.git
synced 2025-12-06 03:55:37 -05:00
feat: replace legacy Python CLI with GUI app
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,11 +10,13 @@ lerna-debug.log*
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
docs
|
||||
*.local
|
||||
*.lock
|
||||
.env
|
||||
CHANGELOG.md
|
||||
scripts/prepare-release.js
|
||||
scripts/update-server.js
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
|
||||
31
README.md
31
README.md
@@ -4,6 +4,17 @@ CreamLinux is a GUI application for Linux that simplifies the management of DLC
|
||||
|
||||

|
||||
|
||||
## Beta Status
|
||||
|
||||
⚠️ **IMPORTANT**: CreamLinux is currently in BETA. This means:
|
||||
|
||||
- Some features may be incomplete or subject to change
|
||||
- You might encounter bugs or unexpected behavior
|
||||
- The application is under active development
|
||||
- Your feedback and bug reports are invaluable
|
||||
|
||||
While the core functionality is working, please be aware that this is an early release. Im continuously working to improve stability, add features, and enhance the user experience. Please report any issues you encounter on [GitHub Issues page](https://github.com/Novattz/creamlinux-installer/issues).
|
||||
|
||||
## Features
|
||||
|
||||
- **Auto-discovery**: Automatically finds Steam games installed on your system
|
||||
@@ -16,7 +27,7 @@ CreamLinux is a GUI application for Linux that simplifies the management of DLC
|
||||
|
||||
### AppImage (Recommended)
|
||||
|
||||
1. Download the latest `CreamLinux.AppImage` from the [Releases](https://github.com/novattz/creamlinux/releases) page
|
||||
1. Download the latest `CreamLinux.AppImage` from the [Releases](https://github.com/Novattz/creamlinux-installer/releases) page
|
||||
2. Make it executable:
|
||||
```bash
|
||||
chmod +x CreamLinux.AppImage
|
||||
@@ -91,28 +102,12 @@ update-desktop-database ~/.local/share/applications
|
||||
|
||||
- **Game doesn't load**: Make sure the launch options are correctly set in Steam
|
||||
- **DLCs not showing up**: Try refreshing the game list and reinstalling
|
||||
- **Cannot find Steam**: Ensure Steam is installed and you've launched it at least once
|
||||
- **Cannot find Steam**: Ensure Steam is installed and you've launched it at least once (Flatpak is not supported yet)
|
||||
|
||||
### Debug Logs
|
||||
|
||||
Logs are stored at: `~/.cache/creamlinux/creamlinux.log`
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
### Development Setup
|
||||
|
||||
1. Clone this repository
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
3. Start the development server:
|
||||
```bash
|
||||
npm run tauri dev
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details.
|
||||
|
||||
19
latest.json
19
latest.json
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"version": "0.1.13",
|
||||
"notes": "Release version 0.1.13",
|
||||
"pub_date": "2025-05-18T23:25:37.931Z",
|
||||
"platforms": {
|
||||
"linux-x86_64": {
|
||||
"url": "https://github.com/novattz/rust-gui-dev/releases/download/v0.1.13/Creamlinux_0.1.13_amd64.AppImage",
|
||||
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUeFJ4alZDbGRMTEIxT2JMYS9QTDdSZUtIdWV1cGtvYlc5NnJrTUE1TndkdVAyS2xBbzJwbUQ1SVp1WWVtNWgwb01pU0ovcG1DOFVaQU03MjJZZzRyZUo5UThiUTJ3NEFJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzQ3NjEwNzM3CWZpbGU6Q3JlYW1saW51eF8wLjEuMTNfYW1kNjQuQXBwSW1hZ2UKY2lHVlVObXBqcW4xTEFEL3EwQTdFSi8zVnF5cHJGcHFKQmx5VEZ3TXZmaUVMa0N2R2lmUVREa1gzZzJmL2dXNEluMDQxUFdpUGsya3hkUXkrbHV2QlE9PQo="
|
||||
},
|
||||
"linux-deb": {
|
||||
"url": "https://github.com/novattz/rust-gui-dev/releases/download/v0.1.13/Creamlinux_0.1.13_amd64.deb",
|
||||
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUeFJ4alZDbGRMTEhGaXlpcExROGo5eXdPekE5NzhPNjFMbXFTUHVTRWtxRHFZTmI2T2xqMHNsM1JOZk5VOFNTYnhGTmhZUjBkaFoyVU1KYVRwa1R5dGF4NEp2ZnV4SGdFPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzQ3NjEwNzM3CWZpbGU6Q3JlYW1saW51eF8wLjEuMTNfYW1kNjQuZGViCmFnMzI1Q2gyamlMOTZPV1ZNQ2xXZzI5ekpOV3ZWQWI2OC9hQ1JpWUU4dTd3ZUNhdlRZWFZ6WVZ6RythNGt5YXl6UThLVll1K3doWDhubTFJS3NabEFnPT0K"
|
||||
},
|
||||
"linux-rpm": {
|
||||
"url": "https://github.com/novattz/rust-gui-dev/releases/download/v0.1.13/Creamlinux-0.1.13-1.x86_64.rpm",
|
||||
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUeFJ4alZDbGRMTE9tNHJiQ1V6bFdINEYxTyt6UFN3OEpLR1NOZWJQZU9KekRyM3V0RmN5Wk1rRTVpejFtTXhlN25BR243UEpHVHRObkxZOEt3SXRINjhrQzdoNWZ5QkFVPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzQ3NjEwNzM3CWZpbGU6Q3JlYW1saW51eC0wLjEuMTMtMS54ODZfNjQucnBtClFscVNjYjNhTUk1T2NCZ3huZE00V3dRc2V0NDFXOUNjbElWMllUNHRLSnkwYmpGaFN4WGxWVWRxdVRxTDljdWd2dGs4Q2R5ZjFaeDVCV1hqTTl0YUJnPT0K"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
{
|
||||
"name": "creamlinux",
|
||||
"private": true,
|
||||
"version": "0.1.13",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"author": "Tickbase",
|
||||
"repository": "https://github.com/Novattz/creamlinux-installer",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
@@ -47,4 +50,4 @@
|
||||
"vite": "^6.3.1",
|
||||
"vite-plugin-svgr": "^4.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.13"
|
||||
version = "1.0.0"
|
||||
description = "DLC Manager for Steam games on Linux"
|
||||
authors = ["tickbase"]
|
||||
license = ""
|
||||
repository = ""
|
||||
license = "MIT"
|
||||
repository = "https://github.com/Novattz/creamlinux-installer"
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ async fn fetch_game_dlcs(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Cache in memory for this session (but not on disk)
|
||||
// Cache in memory for this session
|
||||
let state = app_handle.state::<AppState>();
|
||||
let mut cache = state.dlc_cache.lock();
|
||||
cache.insert(
|
||||
@@ -323,7 +323,7 @@ async fn stream_game_dlcs(game_id: String, app_handle: tauri::AppHandle) -> Resu
|
||||
game_id
|
||||
);
|
||||
|
||||
// Convert to DLCInfoWithState for in-memory caching only
|
||||
// Convert to DLCInfoWithState for in-memory caching
|
||||
let dlcs_with_state = dlcs
|
||||
.into_iter()
|
||||
.map(|dlc| DlcInfoWithState {
|
||||
@@ -333,7 +333,7 @@ async fn stream_game_dlcs(game_id: String, app_handle: tauri::AppHandle) -> Resu
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Update in-memory cache without storing to disk
|
||||
// Update in-memory
|
||||
let state = app_handle.state::<AppState>();
|
||||
let mut dlc_cache = state.dlc_cache.lock();
|
||||
dlc_cache.insert(
|
||||
|
||||
@@ -558,7 +558,6 @@ pub async fn find_installed_games(steamapps_paths: &[PathBuf]) -> Vec<GameInfo>
|
||||
|
||||
// Every 10 files, yield to allow progress updates
|
||||
if manifest_idx % 10 == 0 {
|
||||
// We would update progress here in a full implementation
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,22 +11,21 @@
|
||||
"targets": "all",
|
||||
"category": "Utility",
|
||||
"createUpdaterArtifacts": true,
|
||||
"icon": [
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.png"
|
||||
]
|
||||
"icon": ["icons/128x128.png", "icons/128x128@2x.png", "icons/icon.png"]
|
||||
},
|
||||
"productName": "Creamlinux",
|
||||
"mainBinaryName": "creamlinux",
|
||||
"version": "0.1.13",
|
||||
"version": "1.0.0",
|
||||
"identifier": "com.creamlinux.dev",
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDJDNEI1NzBBRDUxODQ3RjEKUldUeFJ4alZDbGRMTE5Vc241NG5yL080UklnaW1iUGdUWElPRXloRGtKZ3M2SWkzK0RGSDh3Q2kK",
|
||||
"endpoints": [
|
||||
"https://github.com/Novattz/rust-gui-dev/releases/latest/download/latest.json"
|
||||
]
|
||||
"https://github.com/Novattz/creamlinux-installer/releases/latest/download/latest.json"
|
||||
],
|
||||
"windows": {
|
||||
"installMode": "passive"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
@@ -46,4 +45,4 @@
|
||||
"csp": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useAppContext } from '@/contexts/useAppContext'
|
||||
import { UpdateChecker } from '@/components/updater'
|
||||
import { UpdateNotifier } from '@/components/updater'
|
||||
import { useAppLogic } from '@/hooks'
|
||||
import './styles/main.scss'
|
||||
|
||||
@@ -105,10 +105,12 @@ function App() {
|
||||
onClose={handleDlcDialogClose}
|
||||
onConfirm={handleDlcConfirm}
|
||||
/>
|
||||
<UpdateChecker />
|
||||
|
||||
{/* Simple update notifier that uses toast - no UI component */}
|
||||
<UpdateNotifier />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
export default App
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 362 KiB After Width: | Height: | Size: 1.3 MiB |
@@ -35,7 +35,7 @@ const ActionButton: FC<ActionButtonProps> = ({
|
||||
return isInstalled ? `Uninstall ${product}` : `Install ${product}`
|
||||
}
|
||||
|
||||
// Map to our button variant
|
||||
// Map to button variant
|
||||
const getButtonVariant = (): ButtonVariant => {
|
||||
// For uninstall actions, use danger variant
|
||||
if (isInstalled) return 'danger'
|
||||
|
||||
@@ -4,17 +4,18 @@ export interface DialogHeaderProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
onClose?: () => void
|
||||
hideCloseButton?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Header component for dialogs
|
||||
* Contains the title and optional close button
|
||||
*/
|
||||
const DialogHeader = ({ children, className = '', onClose }: DialogHeaderProps) => {
|
||||
const DialogHeader = ({ children, className = '', onClose, hideCloseButton = false }: DialogHeaderProps) => {
|
||||
return (
|
||||
<div className={`dialog-header ${className}`}>
|
||||
{children}
|
||||
{onClose && (
|
||||
{onClose && !hideCloseButton && (
|
||||
<button className="dialog-close-button" onClick={onClose} aria-label="Close dialog">
|
||||
×
|
||||
</button>
|
||||
|
||||
@@ -141,7 +141,7 @@ const DlcSelectionDialog = ({
|
||||
|
||||
return (
|
||||
<Dialog visible={visible} onClose={onClose} size="large" preventBackdropClose={isLoading}>
|
||||
<DialogHeader onClose={onClose}>
|
||||
<DialogHeader onClose={onClose} hideCloseButton={true}>
|
||||
<h3>{dialogTitle}</h3>
|
||||
<div className="dlc-game-info">
|
||||
<span className="game-title">{gameTitle}</span>
|
||||
|
||||
@@ -123,7 +123,7 @@ const ProgressDialog = ({
|
||||
size="medium"
|
||||
preventBackdropClose={!isCloseButtonEnabled}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogHeader onClose={onClose} hideCloseButton={true}>
|
||||
<h3>{title}</h3>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ const AnimatedBackground = () => {
|
||||
color: string
|
||||
}
|
||||
|
||||
// Color palette matching our theme
|
||||
// Color palette matching theme
|
||||
const colors = [
|
||||
'rgba(74, 118, 196, 0.5)', // primary blue
|
||||
'rgba(155, 125, 255, 0.5)', // purple
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { check, type Update, type DownloadEvent } from '@tauri-apps/plugin-updater'
|
||||
import { relaunch } from '@tauri-apps/plugin-process'
|
||||
import { Button } from '@/components/buttons'
|
||||
|
||||
/**
|
||||
* React component that checks for updates and provides
|
||||
* UI for downloading and installing them
|
||||
*/
|
||||
const UpdateChecker = () => {
|
||||
const [updateAvailable, setUpdateAvailable] = useState(false)
|
||||
const [updateInfo, setUpdateInfo] = useState<Update | null>(null)
|
||||
const [isChecking, setIsChecking] = useState(false)
|
||||
const [isDownloading, setIsDownloading] = useState(false)
|
||||
const [downloadProgress, setDownloadProgress] = useState(0)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
// Check for updates on component mount
|
||||
useEffect(() => {
|
||||
checkForUpdates()
|
||||
}, [])
|
||||
|
||||
const checkForUpdates = async () => {
|
||||
try {
|
||||
setIsChecking(true)
|
||||
setError(null)
|
||||
|
||||
// Check for updates
|
||||
const update = await check()
|
||||
|
||||
if (update) {
|
||||
console.log(`Update available: ${update.version}`)
|
||||
setUpdateAvailable(true)
|
||||
setUpdateInfo(update)
|
||||
} else {
|
||||
console.log('No updates available')
|
||||
setUpdateAvailable(false)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to check for updates:', err)
|
||||
setError(`Failed to check for updates: ${err instanceof Error ? err.message : String(err)}`)
|
||||
} finally {
|
||||
setIsChecking(false)
|
||||
}
|
||||
}
|
||||
|
||||
const downloadAndInstallUpdate = async () => {
|
||||
if (!updateInfo) return
|
||||
|
||||
try {
|
||||
setIsDownloading(true)
|
||||
setError(null)
|
||||
|
||||
let downloaded = 0
|
||||
let contentLength = 0
|
||||
|
||||
// Download and install update
|
||||
await updateInfo.downloadAndInstall((event: DownloadEvent) => {
|
||||
switch (event.event) {
|
||||
case 'Started':
|
||||
// Started event includes contentLength
|
||||
if ('contentLength' in event.data && typeof event.data.contentLength === 'number') {
|
||||
contentLength = event.data.contentLength
|
||||
console.log(`Started downloading ${contentLength} bytes`)
|
||||
}
|
||||
break
|
||||
case 'Progress':
|
||||
// Progress event includes chunkLength
|
||||
if ('chunkLength' in event.data && typeof event.data.chunkLength === 'number' && contentLength > 0) {
|
||||
downloaded += event.data.chunkLength
|
||||
const progress = (downloaded / contentLength) * 100
|
||||
setDownloadProgress(progress)
|
||||
console.log(`Downloaded ${downloaded} from ${contentLength}`)
|
||||
}
|
||||
break
|
||||
case 'Finished':
|
||||
console.log('Download finished')
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Update installed, relaunching application')
|
||||
await relaunch()
|
||||
} catch (err) {
|
||||
console.error('Failed to download and install update:', err)
|
||||
setError(`Failed to download and install update: ${err instanceof Error ? err.message : String(err)}`)
|
||||
setIsDownloading(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (isChecking) {
|
||||
return <div className="update-checker">Checking for updates...</div>
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="update-checker error">
|
||||
<p>{error}</p>
|
||||
<Button variant="primary" onClick={checkForUpdates}>Try Again</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!updateAvailable || !updateInfo) {
|
||||
return null // Don't show anything if there's no update
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="update-checker">
|
||||
<div className="update-info">
|
||||
<h3>Update Available</h3>
|
||||
<p>Version {updateInfo.version} is available to download.</p>
|
||||
{updateInfo.body && <p className="update-notes">{updateInfo.body}</p>}
|
||||
</div>
|
||||
|
||||
{isDownloading ? (
|
||||
<div className="update-progress">
|
||||
<div className="progress-bar-container">
|
||||
<div
|
||||
className="progress-bar"
|
||||
style={{ width: `${downloadProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p>Downloading: {Math.round(downloadProgress)}%</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="update-actions">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={downloadAndInstallUpdate}
|
||||
disabled={isDownloading}
|
||||
>
|
||||
Download & Install
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => setUpdateAvailable(false)}
|
||||
>
|
||||
Later
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdateChecker
|
||||
14
src/components/updater/UpdateNotifier.tsx
Normal file
14
src/components/updater/UpdateNotifier.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useUpdateChecker } from '@/hooks/useUpdateChecker'
|
||||
|
||||
/**
|
||||
* Simple component that uses the update checker hook
|
||||
* Can be dropped in anywhere in the app
|
||||
*/
|
||||
const UpdateNotifier = () => {
|
||||
useUpdateChecker()
|
||||
|
||||
// This component doesn't render anything
|
||||
return null
|
||||
}
|
||||
|
||||
export default UpdateNotifier
|
||||
@@ -1 +1,5 @@
|
||||
export { default as UpdateChecker } from './UpdateChecker'
|
||||
// Update checker implementation
|
||||
export { default as useUpdateChecker } from '@/hooks/useUpdateChecker'
|
||||
|
||||
// Simple component for using the checker
|
||||
export { default as UpdateNotifier } from './UpdateNotifier'
|
||||
@@ -100,7 +100,7 @@ export const AppProvider = ({ children }: AppProviderProps) => {
|
||||
const handleDlcConfirm = (selectedDlcs: DlcInfo[]) => {
|
||||
const { gameId, isEditMode } = dlcDialog
|
||||
|
||||
// MODIFIED: 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 }))
|
||||
|
||||
// Log detailed info before closing dialog
|
||||
|
||||
@@ -25,7 +25,7 @@ export function useDlcManager() {
|
||||
const [isFetchingDlcs, setIsFetchingDlcs] = useState(false)
|
||||
const dlcFetchController = useRef<AbortController | null>(null)
|
||||
const activeDlcFetchId = useRef<string | null>(null)
|
||||
const [forceReload, setForceReload] = useState(false) // Add this state to force reloads
|
||||
const [forceReload, setForceReload] = useState(false)
|
||||
|
||||
// DLC selection dialog state
|
||||
const [dlcDialog, setDlcDialog] = useState<DlcDialogState>({
|
||||
@@ -156,7 +156,7 @@ export function useDlcManager() {
|
||||
}
|
||||
}
|
||||
|
||||
// MODIFIED: Handle game edit (show DLC management dialog) with proper reloading
|
||||
// Handle game edit (show DLC management dialog) with proper reloading
|
||||
const handleGameEdit = async (gameId: string, games: Game[]) => {
|
||||
const game = games.find((g) => g.id === gameId)
|
||||
if (!game || !game.cream_installed) return
|
||||
@@ -173,17 +173,17 @@ export function useDlcManager() {
|
||||
visible: true,
|
||||
gameId,
|
||||
gameTitle: game.title,
|
||||
dlcs: [], // Always start with empty DLCs to force a fresh load
|
||||
dlcs: [],
|
||||
enabledDlcs: [],
|
||||
isLoading: true,
|
||||
isEditMode: true, // This is an edit operation
|
||||
isEditMode: true,
|
||||
progress: 0,
|
||||
progressMessage: 'Reading DLC configuration...',
|
||||
timeLeft: '',
|
||||
error: null,
|
||||
})
|
||||
|
||||
// MODIFIED: Always get a fresh copy from the config file
|
||||
// Always get a fresh copy from the config file
|
||||
console.log('Loading DLC configuration from disk...')
|
||||
try {
|
||||
const allDlcs = await invoke<DlcInfo[]>('get_all_dlcs_command', {
|
||||
@@ -197,7 +197,7 @@ export function useDlcManager() {
|
||||
// Log the fresh DLC config
|
||||
console.log('Loaded existing DLC configuration:', allDlcs)
|
||||
|
||||
// IMPORTANT: Create a completely new array to avoid reference issues
|
||||
// Create a completely new array to avoid reference issues
|
||||
const freshDlcs = allDlcs.map((dlc) => ({ ...dlc }))
|
||||
|
||||
setDlcDialog((prev) => ({
|
||||
@@ -256,7 +256,7 @@ export function useDlcManager() {
|
||||
}
|
||||
}
|
||||
|
||||
// MODIFIED: Handle DLC selection dialog close
|
||||
// Handle DLC selection dialog close
|
||||
const handleDlcDialogClose = () => {
|
||||
// Cancel any in-progress DLC fetching
|
||||
if (isFetchingDlcs && activeDlcFetchId.current) {
|
||||
|
||||
@@ -150,7 +150,7 @@ export function useGameActions() {
|
||||
|
||||
try {
|
||||
if (isEditMode) {
|
||||
// MODIFIED: 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 }))
|
||||
|
||||
// Show progress dialog for editing
|
||||
@@ -201,7 +201,7 @@ export function useGameActions() {
|
||||
selectedDlcs,
|
||||
})
|
||||
|
||||
// Note: The progress dialog will be updated through the installation-progress event listener
|
||||
// The progress dialog will be updated through the installation-progress event listener
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing DLC selection:', error)
|
||||
|
||||
43
src/hooks/useUpdateChecker.ts
Normal file
43
src/hooks/useUpdateChecker.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useEffect } from 'react'
|
||||
import { check } from '@tauri-apps/plugin-updater'
|
||||
import { useToasts } from '@/hooks'
|
||||
|
||||
/**
|
||||
* Hook that silently checks for updates and shows a toast notification if an update is available
|
||||
*/
|
||||
export function useUpdateChecker() {
|
||||
const { success, error } = useToasts()
|
||||
|
||||
useEffect(() => {
|
||||
// Check for updates on component mount
|
||||
const checkForUpdates = async () => {
|
||||
try {
|
||||
// Check for updates
|
||||
const update = await check()
|
||||
|
||||
// If update is available, show a toast notification
|
||||
if (update) {
|
||||
console.log(`Update available: ${update.version}`)
|
||||
success(`Update v${update.version} available! Check GitHub for details.`, {
|
||||
duration: 8000 // Show for 8 seconds
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
// Log error but don't show to user
|
||||
console.error('Update check failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Small delay to avoid interfering with app startup
|
||||
const timer = setTimeout(() => {
|
||||
checkForUpdates()
|
||||
}, 3000)
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [success, error])
|
||||
|
||||
// This hook doesn't return anything
|
||||
return null
|
||||
}
|
||||
|
||||
export default useUpdateChecker
|
||||
Reference in New Issue
Block a user