'use client' import { useCallback, useEffect, useRef, useState } from 'react' import Sidebar from './componets/Sidebar' import './Globals.css' import { LauncherVersion } from './types/LauncherVersion' import { DownloadProgress } from './types/DownloadProgress' import { platform } from '@tauri-apps/plugin-os' import { invoke } from '@tauri-apps/api/core' import { listen } from '@tauri-apps/api/event' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faAdd, faRemove, faXmark } from '@fortawesome/free-solid-svg-icons' import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/plugin-notification' import { readNormalConfig, readVersionsConfig, writeVersionsConfig } from './util/BazookaManager' import { VersionsConfig } from './types/VersionsConfig' import { DownloadedVersion } from './types/DownloadedVersion' import { NormalConfig } from './types/NormalConfig' import { app } from '@tauri-apps/api' import axios from 'axios' import { openUrl } from '@tauri-apps/plugin-opener' import { GlobalProvider } from './GlobalProvider' import { Roboto } from 'next/font/google' const roboto = Roboto({ subsets: ['latin'] }) export default function RootLayout ({ children }: { children: React.ReactNode }) { const [loading, setLoading] = useState(true) const [loadingText, setLoadingText] = useState('Loading...') const [outdated, setOutdated] = useState(false) const [versionList, setVersionList] = useState(null) const [selectedVersionList, setSelectedVersionList] = useState< LauncherVersion[] >([]) const [downloadProgress, setDownloadProgress] = useState( [] ) const [showPopup, setShowPopup] = useState(false) const [popupMode, setPopupMode] = useState(null) const [fadeOut, setFadeOut] = useState(false) const activeDownloads = useRef(0) const queue = useRef<(() => void)[]>([]) const [downloadedVersionsConfig, setDownloadedVersionsConfig] = useState(null) const [normalConfig, setNormalConfig] = useState(null) const [managingVersion, setManagingVersion] = useState(null) function runNext () { if (activeDownloads.current === 0 && queue.current.length === 0) { setFadeOut(true) setTimeout(() => setShowPopup(false), 200) return } if (activeDownloads.current >= 3 || queue.current.length === 0) return activeDownloads.current++ const next = queue.current.shift() next?.() } async function downloadVersions (versions: LauncherVersion[]) { while (normalConfig != null) { const useWine = normalConfig.settings.useWineOnUnixWhenNeeded const p = platform() const newDownloads = versions.map( v => new DownloadProgress(v, 0, false, true) ) setDownloadProgress(prev => [...prev, ...newDownloads]) newDownloads.forEach(download => { let plat = p if (p === 'linux' && useWine) { if ( !download.version.platforms.includes(p) && download.version.platforms.includes('windows') ) { plat = 'windows' } } const idx = download.version.platforms.indexOf(plat) const url = download.version.downloadUrls[idx] const exe = download.version.executables[idx] if (!url) { setDownloadProgress(prev => prev.map(d => d.version.version === download.version.version ? { ...d, failed: true } : d ) ) return } const task = () => { setDownloadProgress(prev => { const i = prev.findIndex( d => d.version.version === download.version.version ) if (i === -1) return prev const copy = [...prev] copy[i] = { ...copy[i], queued: false } return copy }) invoke('download', { url, name: download.version.version, executable: exe }) } queue.current.push(task) runNext() }) break } } function handleOverlayClick (e: React.MouseEvent) { if (e.target === e.currentTarget) { setFadeOut(true) setTimeout(() => setShowPopup(false), 200) } } const notifyUser = useCallback( async (title: string, body: string) => { if (!normalConfig?.settings.allowNotifications) return let permissionGranted = await isPermissionGranted() if (!permissionGranted) { const permission = await requestPermission() permissionGranted = permission === 'granted' } if (permissionGranted) { sendNotification({ title, body }) } }, [normalConfig] ) useEffect(() => { ;(async () => { if (process.env.NODE_ENV === 'production') { setLoadingText('Checking latest version...') try { const response = await axios.get( 'https://games.lncvrt.xyz/database/launcher/latest.php' ) const client = await app.getVersion() if (response.data !== client) { setOutdated(true) return } } catch { setLoadingText('Failed to check latest version.') return } } setLoadingText('Loading configs...') const normalConfig = await readNormalConfig() const versionsConfig = await readVersionsConfig() setDownloadedVersionsConfig(versionsConfig) setNormalConfig(normalConfig) setLoading(false) })() }, []) useEffect(() => { let unlistenProgress: (() => void) | null = null let unlistenDone: (() => void) | null = null let unlistenFailed: (() => void) | null = null let unlistenUninstalled: (() => void) | null = null listen('download-progress', event => { const [versionName, progStr] = event.payload.split(':') const prog = Number(progStr) setDownloadProgress(prev => { const i = prev.findIndex(d => d.version.version === versionName) if (i === -1) return prev const copy = [...prev] copy[i] = { ...copy[i], progress: prog } return copy }) }).then(f => (unlistenProgress = f)) listen('download-done', event => { const versionName = event.payload setDownloadProgress(prev => { const downloaded = prev.find(d => d.version.version === versionName) if (!downloaded) return prev setDownloadedVersionsConfig(prevConfig => { if (!prevConfig) return prevConfig const newDownloaded = DownloadedVersion.import(downloaded.version) const updatedConfig = { ...prevConfig, list: [...prevConfig.list, newDownloaded] } writeVersionsConfig(updatedConfig) return updatedConfig }) return prev.filter(d => d.version.version !== versionName) }) activeDownloads.current-- runNext() setDownloadProgress(curr => { if (curr.length === 0) notifyUser('Downloads Complete', 'All downloads have completed.') return curr }) }).then(f => (unlistenDone = f)) listen('download-failed', async event => { const versionName = event.payload setDownloadProgress(prev => prev.map(d => d.version.version === versionName ? { ...d, failed: true } : d ) ) activeDownloads.current-- runNext() await notifyUser( 'Download Failed', `The download for version ${versionName} has failed.` ) }).then(f => (unlistenFailed = f)) listen('version-uninstalled', event => { const versionName = event.payload setDownloadedVersionsConfig(prev => { if (!prev) return prev const updatedList = prev.list.filter( v => v.version.version !== versionName ) const updatedConfig = { ...prev, list: updatedList } writeVersionsConfig(updatedConfig) setManagingVersion(null) setFadeOut(true) setTimeout(() => setShowPopup(false), 200) return updatedConfig }) }).then(f => (unlistenUninstalled = f)) return () => { unlistenProgress?.() unlistenDone?.() unlistenFailed?.() unlistenUninstalled?.() } }, [notifyUser]) useEffect(() => { const handler = (e: MouseEvent) => e.preventDefault() document.addEventListener('contextmenu', handler) return () => document.removeEventListener('contextmenu', handler) }, []) return ( <> {loading ? (
{outdated ? (

Outdated Launcher!

Please update to the latest version to continue.

) : (

{loadingText}

)}
) : ( <>
{ if (showPopup && e.key === 'Escape') { setFadeOut(true) setTimeout(() => setShowPopup(false), 200) } }} >
{children}
{showPopup && (
{popupMode === 0 ? ( <>

Select versions to download

{versionList == null ? (

Getting version list...

) : ( versionList .filter( v => !downloadedVersionsConfig?.list.some( dv => dv.version.version === v.version ) ) .map((v, i) => (

Berry Dash v{v.displayName}

)) )}
) : popupMode === 1 ? ( <>

Downloads

{downloadProgress.length === 0 ? (

Nothing here...

) : ( downloadProgress.map((v, i) => (

Berry Dash v{v.version.displayName}

{v.failed ? ( <>
Download failed
) : v.queued ? ( Queued… ) : ( Downloading: {v.progress}% done )}
)) )}
) : popupMode === 2 ? ( managingVersion ? ( <>

Manage version{' '} {managingVersion.version.displayName}

) : (

No version selected

) ) : null} {popupMode == 0 && versionList != null && (
)}
)}
)} ) }