diff --git a/.gitignore b/.gitignore index 42fb520..3c34251 100644 --- a/.gitignore +++ b/.gitignore @@ -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/* diff --git a/README.md b/README.md index 5461511..91fdf31 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,17 @@ CreamLinux is a GUI application for Linux that simplifies the management of DLC ![Screenshot](./src/assets/screenshot.png) +## 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. diff --git a/latest.json b/latest.json deleted file mode 100644 index 4dccf8c..0000000 --- a/latest.json +++ /dev/null @@ -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" - } - } -} \ No newline at end of file diff --git a/package.json b/package.json index a8bef13..243a987 100644 --- a/package.json +++ b/package.json @@ -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" } -} \ No newline at end of file +} diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9314e28..3fad4bb 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -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" diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6e5af5e..57e4c37 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -275,7 +275,7 @@ async fn fetch_game_dlcs( }) .collect::>(); - // Cache in memory for this session (but not on disk) + // Cache in memory for this session let state = app_handle.state::(); 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::>(); - // Update in-memory cache without storing to disk + // Update in-memory let state = app_handle.state::(); let mut dlc_cache = state.dlc_cache.lock(); dlc_cache.insert( diff --git a/src-tauri/src/searcher.rs b/src-tauri/src/searcher.rs index 107a115..19fd219 100644 --- a/src-tauri/src/searcher.rs +++ b/src-tauri/src/searcher.rs @@ -558,7 +558,6 @@ pub async fn find_installed_games(steamapps_paths: &[PathBuf]) -> Vec // 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; } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index d2b4afe..837e490 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -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 } } -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index 73a9f4a..c2ada0f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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} /> - + + {/* Simple update notifier that uses toast - no UI component */} + ) } -export default App +export default App \ No newline at end of file diff --git a/src/assets/screenshot.png b/src/assets/screenshot.png index 98847d9..c9ddfa4 100644 Binary files a/src/assets/screenshot.png and b/src/assets/screenshot.png differ diff --git a/src/components/buttons/ActionButton.tsx b/src/components/buttons/ActionButton.tsx index e85554e..e324dd7 100644 --- a/src/components/buttons/ActionButton.tsx +++ b/src/components/buttons/ActionButton.tsx @@ -35,7 +35,7 @@ const ActionButton: FC = ({ 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' diff --git a/src/components/dialogs/DialogHeader.tsx b/src/components/dialogs/DialogHeader.tsx index 194b7d4..72cfa79 100644 --- a/src/components/dialogs/DialogHeader.tsx +++ b/src/components/dialogs/DialogHeader.tsx @@ -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 (
{children} - {onClose && ( + {onClose && !hideCloseButton && ( diff --git a/src/components/dialogs/DlcSelectionDialog.tsx b/src/components/dialogs/DlcSelectionDialog.tsx index f9a9891..8276b77 100644 --- a/src/components/dialogs/DlcSelectionDialog.tsx +++ b/src/components/dialogs/DlcSelectionDialog.tsx @@ -141,7 +141,7 @@ const DlcSelectionDialog = ({ return ( - +

{dialogTitle}

{gameTitle} diff --git a/src/components/dialogs/ProgressDialog.tsx b/src/components/dialogs/ProgressDialog.tsx index 532aaa2..eda10b4 100644 --- a/src/components/dialogs/ProgressDialog.tsx +++ b/src/components/dialogs/ProgressDialog.tsx @@ -123,7 +123,7 @@ const ProgressDialog = ({ size="medium" preventBackdropClose={!isCloseButtonEnabled} > - +

{title}

diff --git a/src/components/layout/AnimatedBackground.tsx b/src/components/layout/AnimatedBackground.tsx index 336025b..e4203f0 100644 --- a/src/components/layout/AnimatedBackground.tsx +++ b/src/components/layout/AnimatedBackground.tsx @@ -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 diff --git a/src/components/updater/UpdateChecker.tsx b/src/components/updater/UpdateChecker.tsx deleted file mode 100644 index 2db283f..0000000 --- a/src/components/updater/UpdateChecker.tsx +++ /dev/null @@ -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(null) - const [isChecking, setIsChecking] = useState(false) - const [isDownloading, setIsDownloading] = useState(false) - const [downloadProgress, setDownloadProgress] = useState(0) - const [error, setError] = useState(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
Checking for updates...
- } - - if (error) { - return ( -
-

{error}

- -
- ) - } - - if (!updateAvailable || !updateInfo) { - return null // Don't show anything if there's no update - } - - return ( -
-
-

Update Available

-

Version {updateInfo.version} is available to download.

- {updateInfo.body &&

{updateInfo.body}

} -
- - {isDownloading ? ( -
-
-
-
-

Downloading: {Math.round(downloadProgress)}%

-
- ) : ( -
- - -
- )} -
- ) -} - -export default UpdateChecker \ No newline at end of file diff --git a/src/components/updater/UpdateNotifier.tsx b/src/components/updater/UpdateNotifier.tsx new file mode 100644 index 0000000..13dc828 --- /dev/null +++ b/src/components/updater/UpdateNotifier.tsx @@ -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 \ No newline at end of file diff --git a/src/components/updater/index.ts b/src/components/updater/index.ts index dbca055..f460d77 100644 --- a/src/components/updater/index.ts +++ b/src/components/updater/index.ts @@ -1 +1,5 @@ -export { default as UpdateChecker } from './UpdateChecker' \ No newline at end of file +// Update checker implementation +export { default as useUpdateChecker } from '@/hooks/useUpdateChecker' + +// Simple component for using the checker +export { default as UpdateNotifier } from './UpdateNotifier' \ No newline at end of file diff --git a/src/contexts/AppProvider.tsx b/src/contexts/AppProvider.tsx index 91f4466..641c608 100644 --- a/src/contexts/AppProvider.tsx +++ b/src/contexts/AppProvider.tsx @@ -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 diff --git a/src/hooks/useDlcManager.ts b/src/hooks/useDlcManager.ts index f962d5d..f403bac 100644 --- a/src/hooks/useDlcManager.ts +++ b/src/hooks/useDlcManager.ts @@ -25,7 +25,7 @@ export function useDlcManager() { const [isFetchingDlcs, setIsFetchingDlcs] = useState(false) const dlcFetchController = useRef(null) const activeDlcFetchId = useRef(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({ @@ -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('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) { diff --git a/src/hooks/useGameActions.ts b/src/hooks/useGameActions.ts index d92d398..dfc7f77 100644 --- a/src/hooks/useGameActions.ts +++ b/src/hooks/useGameActions.ts @@ -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) diff --git a/src/hooks/useUpdateChecker.ts b/src/hooks/useUpdateChecker.ts new file mode 100644 index 0000000..29d892e --- /dev/null +++ b/src/hooks/useUpdateChecker.ts @@ -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 \ No newline at end of file