Heavily improve codebase

This commit is contained in:
Lncvrt committed 2026-04-01 18:33:06 -07:00
1 parent c69060a360
commit 837c4a8863
20 files changed
+365 -581

No files matched your search

+2 -2
View File
@@ -2,13 +2,13 @@
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { BirdColor } from '@/types/BerryDash/BirdColor' import { BirdColor } from '@/types/BerryDash/BirdColor'
import { GetIconForUser } from '@/lib/BerryDash' import { GetIconForUser } from '@/lib/berrydashutil'
import Image from 'next/image' import Image from 'next/image'
import './styles.css' import './styles.css'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { platform } from '@tauri-apps/plugin-os' import { platform } from '@tauri-apps/plugin-os'
import { fetch } from '@tauri-apps/plugin-http' import { fetch } from '@tauri-apps/plugin-http'
import { verifySignature } from '@/lib/Util' import { verifySignature } from '@/lib/util'
interface BaseEntry { interface BaseEntry {
id: number id: number
+73 -107
View File
@@ -3,36 +3,33 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import '@/app/Installs.css' import '@/app/Installs.css'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { useGlobal } from '@/app/GlobalProvider' import { useGlobal } from '@/providers/GlobalProvider'
import { useRouter, useSearchParams } from 'next/navigation' import { useRouter, useSearchParams } from 'next/navigation'
import { platform } from '@tauri-apps/plugin-os' import { platform } from '@tauri-apps/plugin-os'
import { faWarning } from '@fortawesome/free-solid-svg-icons' import { faWarning } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { ask } from '@tauri-apps/plugin-dialog' import { ask } from '@tauri-apps/plugin-dialog'
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs' import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
import { writeVersionsConfig } from '@/lib/BazookaManager' import { openFolder } from '@/lib/util'
import { openFolder } from '@/lib/Util'
export default function Installs () { export default function Installs () {
const { const {
versionsList,
serverVersionList,
showPopup, showPopup,
setShowPopup, setShowPopup,
setPopupMode, setPopupMode,
setFadeOut, setFadeOut,
setSelectedVersionList, setSelectedVersionList,
downloadedVersionsConfig,
setManagingVersion, setManagingVersion,
getVersionInfo,
getGameInfo,
setSelectedGame, setSelectedGame,
serverVersionList,
category, category,
setCategory, setCategory,
setDownloadedVersionsConfig,
downloadVersions, downloadVersions,
downloadProgress, downloadProgress,
linuxUseWine, linuxUseWine,
linuxWineCommand linuxWineCommand,
versions
} = useGlobal() } = useGlobal()
const params = useSearchParams() const params = useSearchParams()
@@ -55,10 +52,7 @@ export default function Installs () {
if (!lastRevision) return false if (!lastRevision) return false
return ( return (
lastRevision > 0 && lastRevision > 0 &&
(downloadedVersionsConfig == undefined (versionsList == undefined ? 0 : versionsList[version]) / 1000 <=
? 0
: downloadedVersionsConfig?.list[version]) /
1000 <=
lastRevision lastRevision
) )
} }
@@ -122,12 +116,11 @@ export default function Installs () {
> >
{category == -1 && {category == -1 &&
Object.entries(game.categoryNames) Object.entries(game.categoryNames)
.sort(([a], [b]) => Number(b) - Number(a))
.filter(([key]) => { .filter(([key]) => {
const count = Object.keys( const count = Object.keys(versionsList).filter(v => {
downloadedVersionsConfig?.list ?? {} const info = serverVersionList?.versions.find(
).filter(v => { vf => vf.id == v
const info = getVersionInfo(v) )
if (!info) return false if (!info) return false
if (platform() == 'linux' && info.wine && !linuxUseWine) if (platform() == 'linux' && info.wine && !linuxUseWine)
@@ -138,6 +131,7 @@ export default function Installs () {
return count >= 1 return count >= 1
}) })
.sort(([a], [b]) => Number(b) - Number(a))
.map(([key, value]) => { .map(([key, value]) => {
return ( return (
<div <div
@@ -157,10 +151,10 @@ export default function Installs () {
<p> <p>
{(() => { {(() => {
const count = const count =
Object.keys( Object.keys(versionsList).filter(v => {
downloadedVersionsConfig?.list ?? [] const info = serverVersionList?.versions.find(
).filter(v => { vf => vf.id == v
const info = getVersionInfo(v) )
if (!info) return false if (!info) return false
if ( if (
platform() == 'linux' && platform() == 'linux' &&
@@ -181,44 +175,40 @@ export default function Installs () {
</div> </div>
) )
})} })}
{Object.keys(downloadedVersionsConfig?.list ?? []).filter(v => { {Object.keys(versionsList)
const info = getVersionInfo(v) .filter(v => {
if (!info) return false const info = serverVersionList?.versions.find(vf => vf.id == v)
return info.game === id if (!info) return false
}).length != 0 ? ( if (platform() == 'linux' && info.wine && !linuxUseWine)
Object.keys(downloadedVersionsConfig?.list ?? []) return false
.sort((a, b) => { return (
const infoA = getVersionInfo(a) info.game === id &&
const infoB = getVersionInfo(b) (category == -1
if (!infoA || !infoB) return 0 ? info.category == -1
return infoB.place - infoA.place : info.category == category)
}) )
.filter(v => { })
const info = getVersionInfo(v) .sort((a, b) => {
if (!info) return false const infoA = serverVersionList?.versions.find(vf => vf.id == a)
if (platform() == 'linux' && info.wine && !linuxUseWine) const infoB = serverVersionList?.versions.find(vf => vf.id == b)
return false if (!infoA || !infoB) return 0
return ( return infoB.place - infoA.place
info.game === id && })
(category == -1 .map(v => {
? info.category == -1 const versionInfo = serverVersionList?.versions.find(
: info.category == category) vf => vf.id == v
) )
}) if (!versionInfo) return
.map(entry => (
return (
<div <div
key={entry} key={v}
className={'downloads-entry'} className={'downloads-entry'}
title={ title={
'Click to launch game. Right-click to manage this version install' 'Click to launch game. Right-click to manage this version install'
} }
onClick={async () => { onClick={async () => {
if ( if (needsRevisionUpdate(versionInfo.lastRevision, v)) {
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
) {
const answer = await ask( const answer = await ask(
'Before proceeding, if you do not want your installation directory wiped just yet, please backup the files to another directory. When you click "Yes", it will be wiped. Click "No" if you want to open the installation folder instead.', 'Before proceeding, if you do not want your installation directory wiped just yet, please backup the files to another directory. When you click "Yes", it will be wiped. Click "No" if you want to open the installation folder instead.',
{ {
@@ -242,55 +232,45 @@ export default function Installs () {
setFadeOut(false) setFadeOut(false)
//uninstall //uninstall
setDownloadedVersionsConfig(prev => { versions?.set(
if (!prev) return prev 'list',
const updatedList = Object.fromEntries( Object.fromEntries(
Object.entries(prev.list).filter( Object.entries(versionsList).filter(
([k]) => k !== entry ([k]) => k !== v
) )
) )
const updatedConfig = { )
...prev,
list: updatedList
}
writeVersionsConfig(updatedConfig)
return updatedConfig
})
if ( if (
await exists('game/' + entry, { await exists('game/' + v, {
baseDir: BaseDirectory.AppLocalData baseDir: BaseDirectory.AppLocalData
}) })
) )
await remove('game/' + entry, { await remove('game/' + v, {
baseDir: BaseDirectory.AppLocalData, baseDir: BaseDirectory.AppLocalData,
recursive: true recursive: true
}) })
//reinstall //reinstall
setSelectedVersionList([entry]) setSelectedVersionList([v])
downloadVersions([ downloadVersions([
{ {
id: entry, id: v,
type: 0 type: 0
} }
]) ])
} else { } else {
openFolder(entry) openFolder(v)
} }
return return
} }
const verInfo = getVersionInfo(entry)
if (verInfo == undefined) return
const gameInfo = getGameInfo(verInfo.game)
if (gameInfo == undefined) return
invoke('launch_game', { invoke('launch_game', {
name: verInfo.id, name: versionInfo.id,
executable: verInfo.executable, executable: versionInfo.executable,
displayName: verInfo.displayName, displayName: versionInfo.displayName,
useWine: !!( useWine: !!(
platform() == 'linux' && platform() == 'linux' &&
verInfo.wine && versionInfo.wine &&
linuxUseWine linuxUseWine
), ),
wineCommand: linuxWineCommand wineCommand: linuxWineCommand
@@ -298,16 +278,14 @@ export default function Installs () {
}} }}
onContextMenu={e => { onContextMenu={e => {
e.preventDefault() e.preventDefault()
setManagingVersion(entry) setManagingVersion(v)
setPopupMode(2) setPopupMode(2)
setShowPopup(true) setShowPopup(true)
setFadeOut(false) setFadeOut(false)
}} }}
> >
<div className='h-18 w-screen relative'> <div className='h-18 w-screen relative'>
<p className='text-2xl'> <p className='text-2xl'>{versionInfo.displayName}</p>
{getVersionInfo(entry)?.displayName}{' '}
</p>
<div className='flex gap-2 absolute left-0 bottom-0'> <div className='flex gap-2 absolute left-0 bottom-0'>
<div <div
@@ -317,7 +295,7 @@ export default function Installs () {
<p> <p>
Installed{' '} Installed{' '}
{new Intl.DateTimeFormat(undefined).format( {new Intl.DateTimeFormat(undefined).format(
downloadedVersionsConfig?.list[entry] versionsList[v]
)} )}
</p> </p>
</div> </div>
@@ -325,13 +303,8 @@ export default function Installs () {
className='entry-info-item' className='entry-info-item'
title='This version is using wine. It cannot be guarenteed to work fully and might not work at all.' title='This version is using wine. It cannot be guarenteed to work fully and might not work at all.'
hidden={ hidden={
!( !(platform() == 'linux' && versionInfo.wine) ||
platform() == 'linux' && getVersionInfo(entry)?.wine needsRevisionUpdate(versionInfo.lastRevision, v)
) ||
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
} }
> >
<FontAwesomeIcon icon={faWarning} color='#ffc800' /> <FontAwesomeIcon icon={faWarning} color='#ffc800' />
@@ -340,10 +313,7 @@ export default function Installs () {
<div <div
className='entry-info-item' className='entry-info-item'
hidden={ hidden={
!needsRevisionUpdate( !needsRevisionUpdate(versionInfo.lastRevision, v)
getVersionInfo(entry)?.lastRevision,
entry
)
} }
> >
<FontAwesomeIcon icon={faWarning} color='#ffc800' /> <FontAwesomeIcon icon={faWarning} color='#ffc800' />
@@ -357,45 +327,41 @@ export default function Installs () {
e.stopPropagation() e.stopPropagation()
if ( if (
!(await exists('game/' + entry + '/BepInEx', { !(await exists('game/' + v + '/BepInEx', {
baseDir: BaseDirectory.AppLocalData baseDir: BaseDirectory.AppLocalData
})) }))
) { ) {
if ( if (
(await ask( (await ask(
"You don't have BepInEx (the mod loader for Unity Games), would you like to install it now? It is about 1MB in size. If you choose yes, it will download the recommended BepInEx version for " + "You don't have BepInEx (the mod loader for Unity Games), would you like to install it now? It is about 1MB in size. If you choose yes, it will download the recommended BepInEx version for " +
getVersionInfo(entry)?.displayName + versionInfo.displayName +
'.', '.',
{ title: 'BepInEx not found!', kind: 'error' } { title: 'BepInEx not found!', kind: 'error' }
)) && )) &&
!downloadProgress.find(d => d.version == entry) !downloadProgress.find(d => d.version == v)
) { ) {
downloadVersions([ downloadVersions([
{ {
id: entry, id: v,
type: 1 type: 1
} }
]) ])
} }
return return
} }
setManagingVersion(entry) setManagingVersion(v)
setPopupMode(3) setPopupMode(3)
setShowPopup(true) setShowPopup(true)
setFadeOut(false) setFadeOut(false)
}} }}
hidden={!getVersionInfo(entry)?.modSupportDownload} hidden={!versionInfo.modSupportDownload}
> >
Mod Manager Mod Manager
</button> </button>
</div> </div>
</div> </div>
)) )
) : ( })}
<div className='flex justify-center items-center h-full'>
<p className='text-3xl'>No versions installed</p>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
+81 -136
View File
@@ -1,21 +1,14 @@
'use client' 'use client'
import './Globals.css' import '@/styles/globals.css'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import Sidebar from '@/components/Sidebar' import Sidebar from '@/components/Sidebar'
import { DownloadProgress } from '@/types/DownloadProgress' import { DownloadProgress } from '@/types/DownloadProgress'
import { invoke } from '@tauri-apps/api/core'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faChevronLeft, faXmark } from '@fortawesome/free-solid-svg-icons'
import { readVersionsConfig, writeVersionsConfig } from '@/lib/BazookaManager'
import { VersionsConfig } from '@/types/VersionsConfig'
import { app } from '@tauri-apps/api' import { app } from '@tauri-apps/api'
import { openUrl } from '@tauri-apps/plugin-opener' import { openUrl } from '@tauri-apps/plugin-opener'
import { GlobalProvider } from './GlobalProvider' import { GlobalProvider } from '../providers/GlobalProvider'
import { Roboto } from 'next/font/google' import { Roboto } from 'next/font/google'
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse' import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
import { GameVersion } from '@/types/GameVersion'
import { Game } from '@/types/Game'
import { listen } from '@tauri-apps/api/event' import { listen } from '@tauri-apps/api/event'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
import { arch, platform } from '@tauri-apps/plugin-os' import { arch, platform } from '@tauri-apps/plugin-os'
@@ -30,17 +23,21 @@ import {
remove remove
} from '@tauri-apps/plugin-fs' } from '@tauri-apps/plugin-fs'
import { fetch } from '@tauri-apps/plugin-http' import { fetch } from '@tauri-apps/plugin-http'
import { verifySignature } from '@/lib/Util' import { verifySignature } from '@/lib/util'
import { getCurrentWindow, UserAttentionType } from '@tauri-apps/api/window' import { getCurrentWindow, UserAttentionType } from '@tauri-apps/api/window'
import VersionsDownloadPopup from '@/components/popups/VersionsDownload'
import GamesDownloadPopup from '@/components/popups/GamesDownload'
import DownloadsPopup from '@/components/popups/Downloads'
import ManageVersionPopup from '@/components/popups/ManageVersion'
import ModDownloadsPopup from '@/components/popups/ModDownloads'
import { Mod } from '@/types/Mod' import { Mod } from '@/types/Mod'
import { load, Store } from '@tauri-apps/plugin-store' import { load, Store } from '@tauri-apps/plugin-store'
import { notifyUser } from '@/lib/Notifications' import { notifyUser } from '@/lib/notifications'
import VersionsDownloadPopup from '@/popups/VersionsDownload'
import GamesDownloadPopup from '@/popups/GamesDownload'
import DownloadsPopup from '@/popups/Downloads'
import ManageVersionPopup from '@/popups/ManageVersion'
import ModDownloadsPopup from '@/popups/ModDownloads'
import { GameVersion } from '@/types/GameVersion'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faChevronLeft, faXmark } from '@fortawesome/free-solid-svg-icons'
import { invoke } from '@tauri-apps/api/core'
const roboto = Roboto({ const roboto = Roboto({
subsets: ['latin'] subsets: ['latin']
@@ -61,9 +58,6 @@ export default function RootLayout ({
useState<null | ServerVersionsResponse>(null) useState<null | ServerVersionsResponse>(null)
const [selectedVersionList, setSelectedVersionList] = useState<string[]>([]) const [selectedVersionList, setSelectedVersionList] = useState<string[]>([])
const [downloadedVersionsConfig, setDownloadedVersionsConfig] =
useState<VersionsConfig | null>(null)
const [showPopup, setShowPopup] = useState(false) const [showPopup, setShowPopup] = useState(false)
const [popupMode, setPopupMode] = useState<null | number>(null) const [popupMode, setPopupMode] = useState<null | number>(null)
const [fadeOut, setFadeOut] = useState(false) const [fadeOut, setFadeOut] = useState(false)
@@ -85,6 +79,7 @@ export default function RootLayout ({
const revisionCheck = useRef(false) const revisionCheck = useRef(false)
const [settings, setSettings] = useState<Store | null>(null) const [settings, setSettings] = useState<Store | null>(null)
const [versions, setVersions] = useState<Store | null>(null)
const [notificationsAllowed, setNotificationsAllowed] = const [notificationsAllowed, setNotificationsAllowed] =
useState<boolean>(false) useState<boolean>(false)
@@ -95,13 +90,16 @@ export default function RootLayout ({
useState<string>('wine %path%') useState<string>('wine %path%')
const [theme, setTheme] = useState<string>('dark') const [theme, setTheme] = useState<string>('dark')
const [versionsList, setVersionsList] = useState<Record<string, number>>({})
const [modsList, setModsList] = useState<
Record<string, Record<string, number>>
>({})
function getSpecialVersionsList (game?: number): GameVersion[] { function getSpecialVersionsList (game?: number): GameVersion[] {
if (!serverVersionList) return [] if (!serverVersionList) return []
return serverVersionList.versions return serverVersionList.versions
.filter( .filter(v => !Object.keys(versionsList).includes(v.id))
v => !Object.keys(downloadedVersionsConfig?.list ?? []).includes(v.id)
)
.filter(v => { .filter(v => {
if (platformName == 'linux' && v.wine && linuxUseWine) return false if (platformName == 'linux' && v.wine && linuxUseWine) return false
@@ -118,62 +116,6 @@ export default function RootLayout ({
}) })
} }
const getVersionInfo = useCallback(
(id: string | undefined): GameVersion | undefined => {
if (!id) return undefined
return serverVersionList?.versions.find(v => v.id === id)
},
[serverVersionList]
)
const getGameInfo = useCallback(
(game: number | undefined): Game | undefined => {
if (!game) return undefined
return serverVersionList?.games.find(g => g.id === game)
},
[serverVersionList]
)
function getListOfGames (): Game[] {
if (!downloadedVersionsConfig?.list) return []
const gamesMap = new Map<number, Game>()
Object.keys(downloadedVersionsConfig.list).forEach(i => {
const version = getVersionInfo(i)
if (!version) return
const game = getGameInfo(version.game)
if (!game) return
gamesMap.set(game.id, game)
})
return Array.from(gamesMap.values())
}
function getVersionsAmountData (gameId: number): {
installed: number
total: number
} | null {
if (!downloadedVersionsConfig || !serverVersionList) return null
const allowWine = platformName !== 'linux' || linuxUseWine
const installed = Object.keys(downloadedVersionsConfig.list).filter(v => {
const info = getVersionInfo(v)
if (!info) return false
if (info.wine && !allowWine) return false
return getGameInfo(info.game)?.id === gameId
}).length
const total = serverVersionList.versions.filter(v => {
if (!v) return false
if (v.wine && !allowWine) return false
return getGameInfo(v.game)?.id === gameId
}).length
return { installed, total }
}
const closePopup = useCallback(() => { const closePopup = useCallback(() => {
if (popupMode == 0 && selectedGame && pathname === '/') { if (popupMode == 0 && selectedGame && pathname === '/') {
setSelectedGame(null) setSelectedGame(null)
@@ -369,12 +311,21 @@ export default function RootLayout ({
theme: 'dark' theme: 'dark'
} }
}) })
const versionsLocal = await load('versions.json', {
autoSave: true,
defaults: {
version: client,
list: {},
mods: {}
}
})
settingsLocal.set('version', client) settingsLocal.set('version', client)
const oldSettingsOptions = { versionsLocal.set('version', client)
const legacyOptions = {
baseDir: BaseDirectory.AppLocalData baseDir: BaseDirectory.AppLocalData
} }
if (await exists('config.json', oldSettingsOptions)) { if (await exists('config.json', legacyOptions)) {
const config = await readTextFile('config.json', oldSettingsOptions) const config = await readTextFile('config.json', legacyOptions)
const raw = JSON.parse(config) const raw = JSON.parse(config)
if ( if (
raw.settings && raw.settings &&
@@ -432,11 +383,10 @@ export default function RootLayout ({
})() })()
settingsLocal?.set('theme', tempTheme) settingsLocal?.set('theme', tempTheme)
} }
await remove('config.json', oldSettingsOptions) await remove('config.json', legacyOptions)
} }
const versionsConfig = await readVersionsConfig()
setDownloadedVersionsConfig(versionsConfig)
setSettings(settingsLocal) setSettings(settingsLocal)
setVersions(versionsLocal)
setLoading(false) setLoading(false)
if (!(await isPermissionGranted())) { if (!(await isPermissionGranted())) {
@@ -511,7 +461,7 @@ export default function RootLayout ({
const versionId = downloadQueue[0] const versionId = downloadQueue[0]
const downloadInfo = downloadProgress.find(d => d.version == versionId) const downloadInfo = downloadProgress.find(d => d.version == versionId)
if (!downloadInfo) return if (!downloadInfo) return
const info = getVersionInfo(versionId) const info = serverVersionList?.versions.find(vf => vf.id == versionId)
if (!info) { if (!info) {
setDownloadProgress(prev => prev.filter(d => d.version !== versionId)) setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
@@ -520,7 +470,7 @@ export default function RootLayout ({
return return
} }
const gameInfo = getGameInfo(info.game) const gameInfo = serverVersionList?.games.find(vf => vf.id == info.game)
if (!gameInfo) { if (!gameInfo) {
setDownloadProgress(prev => prev.filter(d => d.version !== versionId)) setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
setDownloadQueue(prev => prev.slice(1)) setDownloadQueue(prev => prev.slice(1))
@@ -590,31 +540,19 @@ export default function RootLayout ({
if (res === '1') { if (res === '1') {
setDownloadProgress(prev => prev.filter(d => d.version !== versionId)) setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
if (downloadInfo.type != 1) { if (downloadInfo.type != 1) {
setDownloadedVersionsConfig(prev => { if (downloadInfo.type == 2) {
if (!prev) return prev versions?.set('mods', {
...modsList,
const updated = [downloadInfo.modGame! + '-' + downloadInfo.modId!]: {
downloadInfo.type == 2 [downloadInfo.modVersion!]: Date.now()
? { }
...prev, })
mods: { } else {
...prev.mods, versions?.set('list', {
[downloadInfo.modGame! + '-' + downloadInfo.modId!]: { ...versionsList,
[downloadInfo.modVersion!]: Date.now() [versionId]: Date.now()
} })
} }
}
: {
...prev,
list: {
...prev.list,
[versionId]: Date.now()
}
}
writeVersionsConfig(updated)
return updated
})
} }
} else if (res == '0') { } else if (res == '0') {
setDownloadProgress(prev => { setDownloadProgress(prev => {
@@ -658,20 +596,20 @@ export default function RootLayout ({
}, [ }, [
downloadQueue, downloadQueue,
isProcessingQueue, isProcessingQueue,
getVersionInfo,
getGameInfo,
downloadProgress, downloadProgress,
settings serverVersionList,
settings,
versions,
versionsList,
modsList
]) ])
useEffect(() => { useEffect(() => {
if (revisionCheck.current) return if (revisionCheck.current) return
if (!serverVersionList || !downloadedVersionsConfig) return if (!serverVersionList) return
revisionCheck.current = true revisionCheck.current = true
;(async () => { ;(async () => {
for (const [key, value] of Object.entries( for (const [key, value] of Object.entries(versionsList)) {
downloadedVersionsConfig.list
)) {
const verInfo = serverVersionList.versions.find(item => item.id === key) const verInfo = serverVersionList.versions.find(item => item.id === key)
if ( if (
@@ -690,7 +628,7 @@ export default function RootLayout ({
} }
} }
})() })()
}, [serverVersionList, downloadedVersionsConfig, downloadVersions]) }, [serverVersionList, downloadVersions, versionsList])
useEffect(() => { useEffect(() => {
if ( if (
@@ -719,30 +657,40 @@ export default function RootLayout ({
]) ])
useEffect(() => { useEffect(() => {
if (!settings) return if (!settings || !versions) return
const unlisteners: Promise<() => void>[] = [] const unlisteners: Promise<() => void>[] = []
function watch<T> (key: string, fn: (v: T | undefined) => void) { function watchSettings<T> (key: string, fn: (v: T | undefined) => void) {
settings!.get<T>(key).then(fn) settings!.get<T>(key).then(fn)
unlisteners.push(settings!.onKeyChange<T>(key, fn)) unlisteners.push(settings!.onKeyChange<T>(key, fn))
} }
watch<string>('theme', v => setTheme(v ?? 'dark')) function watchVersions<T> (key: string, fn: (v: T | undefined) => void) {
watch<boolean>('notificationsAllowed', v => versions!.get<T>(key).then(fn)
unlisteners.push(versions!.onKeyChange<T>(key, fn))
}
watchSettings<string>('theme', v => setTheme(v ?? 'dark'))
watchSettings<boolean>('notificationsAllowed', v =>
setNotificationsAllowed(v ?? true) setNotificationsAllowed(v ?? true)
) )
watch<boolean>('sidebarAlwaysShowGames', v => watchSettings<boolean>('sidebarAlwaysShowGames', v =>
setSidebarAlwaysShowGames(v ?? true) setSidebarAlwaysShowGames(v ?? true)
) )
watch<boolean>('linuxUseWine', v => setLinuxUseWine(v ?? false)) watchSettings<boolean>('linuxUseWine', v => setLinuxUseWine(v ?? false))
watch<string>('linuxWineCommand', v => watchSettings<string>('linuxWineCommand', v =>
setLinuxWineCommand(v ?? 'wine %path%') setLinuxWineCommand(v ?? 'wine %path%')
) )
watchVersions<Record<string, number>>('list', v => setVersionsList(v ?? {}))
watchVersions<Record<string, Record<string, number>>>('mods', v =>
setModsList(v ?? {})
)
return () => { return () => {
unlisteners.forEach(u => u.then(fn => fn())) unlisteners.forEach(u => u.then(fn => fn()))
} }
}, [settings]) }, [settings, versions])
return ( return (
<> <>
@@ -795,15 +743,9 @@ export default function RootLayout ({
setPopupMode, setPopupMode,
fadeOut, fadeOut,
setFadeOut, setFadeOut,
downloadedVersionsConfig,
setDownloadedVersionsConfig,
managingVersion, managingVersion,
setManagingVersion, setManagingVersion,
getVersionInfo,
getGameInfo,
getListOfGames,
setSelectedGame, setSelectedGame,
getVersionsAmountData,
viewingInfoFromDownloads, viewingInfoFromDownloads,
version, version,
downloadVersions, downloadVersions,
@@ -812,17 +754,20 @@ export default function RootLayout ({
downloadQueue, downloadQueue,
setDownloadQueue, setDownloadQueue,
closePopup, closePopup,
getSpecialVersionsList,
selectedGame, selectedGame,
setViewingInfoFromDownloads, setViewingInfoFromDownloads,
showModInfo, showModInfo,
setShowModInfo, setShowModInfo,
getSpecialVersionsList,
settings, settings,
versions,
notificationsAllowed, notificationsAllowed,
sidebarAlwaysShowGames, sidebarAlwaysShowGames,
linuxUseWine, linuxUseWine,
linuxWineCommand, linuxWineCommand,
theme theme,
versionsList,
modsList
}} }}
> >
<div <div
+69 -68
View File
@@ -2,7 +2,7 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import './Installs.css' import './Installs.css'
import { useGlobal } from './GlobalProvider' import { useGlobal } from '../providers/GlobalProvider'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { import {
faCheck, faCheck,
@@ -14,15 +14,14 @@ import { useRouter } from 'next/navigation'
export default function Installs () { export default function Installs () {
const { const {
versionsList,
serverVersionList,
showPopup, showPopup,
setShowPopup, setShowPopup,
setPopupMode, setPopupMode,
setFadeOut, setFadeOut,
setSelectedVersionList, setSelectedVersionList,
downloadedVersionsConfig,
setSelectedGame, setSelectedGame,
getListOfGames,
getVersionsAmountData,
setCategory setCategory
} = useGlobal() } = useGlobal()
@@ -58,74 +57,76 @@ export default function Installs () {
: 'h-[calc(100vh-84px)]' : 'h-[calc(100vh-84px)]'
}`} }`}
> >
{downloadedVersionsConfig && {serverVersionList?.games
Object.keys(downloadedVersionsConfig.list).length ? ( .filter(g =>
getListOfGames() serverVersionList.versions
.sort((a, b) => { .filter(v => v.game === g.id)
return a.id - b.id .some(v => Object.keys(versionsList).includes(v.id))
}) )
.map(i => ( .map(i => (
<div <div
key={i.id} key={i.id}
className={'downloads-entry'} className={'downloads-entry'}
title={'Click to view game installs'} title={'Click to view game installs'}
onClick={() => { onClick={() => {
setCategory(-1) setCategory(-1)
router.push('/game?id=' + i.id) router.push('/game?id=' + i.id)
}} }}
> >
<div className='h-18 w-screen relative'> <div className='h-18 w-screen relative'>
<p className='text-2xl'>{i.name}</p> <p className='text-2xl'>{i.name}</p>
<div className='flex gap-2 absolute left-0 bottom-0'> <div className='flex gap-2 absolute left-0 bottom-0'>
<div <div
className='entry-info-item' className='entry-info-item'
title='The amount of versions installed of this game in installed/installable format.' title='The amount of versions installed of this game in installed/installable format.'
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
> >
<p> <p>
{(() => { {(() => {
const data = getVersionsAmountData(i.id) const gameVersions =
if (!data) return 'N/A' serverVersionList.versions.filter(
return `${data.installed}/${data.total}` v => v.game === i.id
})()}{' '} )
versions installed const installed = gameVersions.filter(v =>
</p> Object.keys(versionsList).includes(v.id)
</div> ).length
<div return gameVersions.length
className='entry-info-item' ? `${installed}/${gameVersions.length}`
hidden={!i.official} : 'N/A'
title='This game is official.' })()}{' '}
onClick={e => e.stopPropagation()} versions installed
> </p>
<FontAwesomeIcon icon={faCheck} color='#19c84b' /> </div>
<p>Official</p> <div
</div> className='entry-info-item'
<div hidden={!i.official}
className='entry-info-item' title='This game is official.'
hidden={i.official} onClick={e => e.stopPropagation()}
title={ >
i.verified <FontAwesomeIcon icon={faCheck} color='#19c84b' />
? 'This game is verified to be safe' <p>Official</p>
: 'This game is not verified to be save. Proceed with caution.' </div>
} <div
onClick={e => e.stopPropagation()} className='entry-info-item'
> hidden={i.official}
<FontAwesomeIcon title={
icon={i.verified ? faShieldHalved : faWarning} i.verified
color={i.verified ? '#19c84b' : '#ffc800'} ? 'This game is verified to be safe'
/> : 'This game is not verified to be save. Proceed with caution.'
<p>{i.verified ? 'Verified' : 'Unverified'}</p> }
</div> onClick={e => e.stopPropagation()}
>
<FontAwesomeIcon
icon={i.verified ? faShieldHalved : faWarning}
color={i.verified ? '#19c84b' : '#ffc800'}
/>
<p>{i.verified ? 'Verified' : 'Unverified'}</p>
</div> </div>
</div> </div>
</div> </div>
)) </div>
) : ( ))}
<div className='flex justify-center items-center h-full'>
<p className='text-3xl'>No games installed</p>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
+2 -2
View File
@@ -1,8 +1,8 @@
'use client' 'use client'
import { Setting } from '@/components/Setting' import { Setting } from '@/components/Setting'
import { useGlobal } from '@/app/GlobalProvider' import { useGlobal } from '@/providers/GlobalProvider'
import { copyToClipboard } from '@/lib/Clipboard' import { copyToClipboard } from '@/lib/clipboard'
import { platform } from '@tauri-apps/plugin-os' import { platform } from '@tauri-apps/plugin-os'
export default function Settings () { export default function Settings () {
+1 -1
View File
@@ -1,4 +1,4 @@
import './Setting.css' import '@/styles/setting.css'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCheck } from '@fortawesome/free-solid-svg-icons' import { faCheck } from '@fortawesome/free-solid-svg-icons'
import { SettingProps } from '@/types/SettingProps' import { SettingProps } from '@/types/SettingProps'
+14 -13
View File
@@ -1,6 +1,6 @@
'use client' 'use client'
import './Sidebar.css' import '@/styles/sidebar.css'
import Icon from '@/assets/Icon.png' import Icon from '@/assets/Icon.png'
import { openUrl } from '@tauri-apps/plugin-opener' import { openUrl } from '@tauri-apps/plugin-opener'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
@@ -13,7 +13,7 @@ import {
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { faDiscord } from '@fortawesome/free-brands-svg-icons' import { faDiscord } from '@fortawesome/free-brands-svg-icons'
import { platform } from '@tauri-apps/plugin-os' import { platform } from '@tauri-apps/plugin-os'
import { useGlobal } from '@/app/GlobalProvider' import { useGlobal } from '@/providers/GlobalProvider'
import Image from 'next/image' import Image from 'next/image'
import Link from 'next/link' import Link from 'next/link'
import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { usePathname, useRouter, useSearchParams } from 'next/navigation'
@@ -27,13 +27,12 @@ const lexend = Lexend({
export default function Sidebar () { export default function Sidebar () {
const { const {
getListOfGames, serverVersionList,
versionsList,
setShowPopup, setShowPopup,
setPopupMode, setPopupMode,
setFadeOut, setFadeOut,
downloadProgress, downloadProgress,
downloadedVersionsConfig,
getVersionInfo,
category, category,
setCategory, setCategory,
sidebarAlwaysShowGames, sidebarAlwaysShowGames,
@@ -90,10 +89,12 @@ export default function Sidebar () {
> >
<FontAwesomeIcon icon={faHexagonNodes} className='mr-2' /> Games <FontAwesomeIcon icon={faHexagonNodes} className='mr-2' /> Games
</Link> </Link>
{getListOfGames() {serverVersionList?.games
.sort((a, b) => { .filter(g =>
return a.id - b.id serverVersionList.versions
}) .filter(v => v.game === g.id)
.some(v => Object.keys(versionsList).includes(v.id))
)
.map(i => ( .map(i => (
<React.Fragment key={i.id}> <React.Fragment key={i.id}>
<div <div
@@ -131,10 +132,10 @@ export default function Sidebar () {
{Object.entries(i.categoryNames) {Object.entries(i.categoryNames)
.sort(([a], [b]) => Number(b) - Number(a)) .sort(([a], [b]) => Number(b) - Number(a))
.filter(([key]) => { .filter(([key]) => {
const count = Object.keys( const count = Object.keys(versionsList).filter(v => {
downloadedVersionsConfig?.list ?? {} const info = serverVersionList.versions.find(
).filter(v => { vf => vf.id == v
const info = getVersionInfo(v) )
if (!info) return false if (!info) return false
if (platform() == 'linux' && info.wine && !linuxUseWine) if (platform() == 'linux' && info.wine && !linuxUseWine)
-73
View File
@@ -1,73 +0,0 @@
import { app } from '@tauri-apps/api'
import {
BaseDirectory,
create,
exists,
mkdir,
readTextFile,
writeFile
} from '@tauri-apps/plugin-fs'
import { VersionsConfig } from '@/types/VersionsConfig'
export async function readVersionsConfig (): Promise<VersionsConfig> {
const version = await app.getVersion()
try {
const options = {
baseDir: BaseDirectory.AppLocalData
}
const doesFolderExist = await exists('', options)
const doesConfigExist = await exists('versions.json', options)
if (!doesFolderExist || !doesConfigExist) {
if (!doesFolderExist) {
await mkdir('', options)
}
const file = await create('versions.json', options)
await file.write(
new TextEncoder().encode(
JSON.stringify(new VersionsConfig(version), null, 2)
)
)
await file.close()
return new VersionsConfig(version)
}
const config = await readTextFile('versions.json', options)
const raw = JSON.parse(config)
if (raw.list && raw.timestamps) {
raw.list = raw.timestamps
delete raw.timestamps
await writeFile(
'versions.json',
new TextEncoder().encode(JSON.stringify(raw, null, 2)),
options
)
}
raw.version = version
writeVersionsConfig(raw)
return VersionsConfig.import(raw)
} catch {
return new VersionsConfig(version)
}
}
export async function writeVersionsConfig (data: VersionsConfig) {
const options = {
baseDir: BaseDirectory.AppLocalData
}
const doesFolderExist = await exists('', options)
const doesConfigExist = await exists('versions.json', options)
if (!doesFolderExist || !doesConfigExist) {
if (!doesFolderExist) {
await mkdir('', options)
}
const file = await create('versions.json', options)
await file.write(new TextEncoder().encode(JSON.stringify(data, null, 2)))
await file.close()
} else {
await writeFile(
'versions.json',
new TextEncoder().encode(JSON.stringify(data, null, 2)),
options
)
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { writeText } from '@tauri-apps/plugin-clipboard-manager' import { writeText } from '@tauri-apps/plugin-clipboard-manager'
import { notifyUser } from './Notifications' import { notifyUser } from './notifications'
export async function copyToClipboard ( export async function copyToClipboard (
text: string, text: string,
File renamed without changes.
@@ -1,7 +1,7 @@
import { useGlobal } from '@/app/GlobalProvider' import { useGlobal } from '@/providers/GlobalProvider'
import prettyBytes from 'pretty-bytes' import prettyBytes from 'pretty-bytes'
import ProgressBar from '../ProgressBar' import ProgressBar from '@/components/ProgressBar'
import { formatEtaSmart } from '@/lib/Util' import { formatEtaSmart } from '@/lib/util'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { import {
faCancel, faCancel,
@@ -10,19 +10,20 @@ import {
faTrash faTrash
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { writeVersionsConfig } from '@/lib/BazookaManager' import { notifyUser } from '@/lib/notifications'
import { notifyUser } from '@/lib/Notifications'
import { getCurrentWindow, UserAttentionType } from '@tauri-apps/api/window' import { getCurrentWindow, UserAttentionType } from '@tauri-apps/api/window'
export default function DownloadsPopup () { export default function DownloadsPopup () {
const { const {
downloadProgress, downloadProgress,
getVersionInfo,
setDownloadProgress, setDownloadProgress,
downloadQueue, downloadQueue,
setDownloadQueue, setDownloadQueue,
setDownloadedVersionsConfig, notificationsAllowed,
notificationsAllowed versionsList,
modsList,
versions,
serverVersionList
} = useGlobal() } = useGlobal()
return ( return (
@@ -68,31 +69,19 @@ export default function DownloadsPopup () {
prev.filter(d => d.version !== v.version) prev.filter(d => d.version !== v.version)
) )
if (v.type != 1) { if (v.type != 1) {
setDownloadedVersionsConfig(prev => { if (v.type == 2) {
if (!prev) return prev versions?.set('mods', {
...modsList,
const updated = [v.modGame! + '-' + v.modId!]: {
v.type == 2 [v.modVersion!]: Date.now()
? { }
...prev, })
mods: { } else {
...prev.mods, versions?.set('list', {
[v.modGame! + '-' + v.modId!]: { ...versionsList,
[v.modVersion!]: Date.now() [v.version]: Date.now()
} })
} }
}
: {
...prev,
list: {
...prev.list,
[v.version]: Date.now()
}
}
writeVersionsConfig(updated)
return updated
})
} }
} else if (res == '0') { } else if (res == '0') {
setDownloadProgress(prev => { setDownloadProgress(prev => {
@@ -127,7 +116,9 @@ export default function DownloadsPopup () {
await notifyUser( await notifyUser(
'Download Failed', 'Download Failed',
`The download for version ${ `The download for version ${
getVersionInfo(v.version)?.displayName serverVersionList?.versions.find(
vf => vf.id == v.version
)?.displayName
} has failed.` } has failed.`
) )
await getCurrentWindow().requestUserAttention( await getCurrentWindow().requestUserAttention(
@@ -186,11 +177,14 @@ export default function DownloadsPopup () {
</div> </div>
<p className='text-2xl text-center'> <p className='text-2xl text-center'>
{v.type == 1 {v.type == 1
? 'Mod Manager for ' ? 'Mod loader for '
: v.type == 2 : v.type == 2
? 'Mod for ' ? 'Mod for '
: null}{' '} : null}{' '}
{getVersionInfo(v.version)?.displayName} {
serverVersionList?.versions.find(vf => vf.id == v.version)
?.displayName
}
</p> </p>
<div className='mt-6.25 flex items-center justify-between'> <div className='mt-6.25 flex items-center justify-between'>
{v.failed || v.queued ? ( {v.failed || v.queued ? (
@@ -1,4 +1,4 @@
import { useGlobal } from '@/app/GlobalProvider' import { useGlobal } from '@/providers/GlobalProvider'
import { import {
faCheck, faCheck,
faCode, faCode,
@@ -9,18 +9,18 @@ import {
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
export default function GamesDownloadPopup () { export default function GamesDownloadPopup () {
const { serverVersionList, getVersionsAmountData, setSelectedGame } = const { serverVersionList, setSelectedGame, versionsList } = useGlobal()
useGlobal()
return ( return (
<> <>
<p className='text-xl text-center'>Select a game to download</p> <p className='text-xl text-center'>Select a game to download</p>
<div className='popup-content'> <div className='popup-content'>
{serverVersionList?.games {serverVersionList?.games
.filter(v => { .filter(i => {
const data = getVersionsAmountData(v.id) const gameVersions = serverVersionList.versions.filter(
if (!data) return false vf => vf.game === i.id
if (data.total > 0) return true )
if (gameVersions.length > 0) return true
}) })
.map((v, i) => ( .map((v, i) => (
<div key={i} className='popup-entry'> <div key={i} className='popup-entry'>
@@ -32,9 +32,15 @@ export default function GamesDownloadPopup () {
> >
<p> <p>
{(() => { {(() => {
const data = getVersionsAmountData(v.id) const gameVersions = serverVersionList.versions.filter(
if (!data) return 'N/A' vf => vf.game === v.id
return `${data.installed}/${data.total}` )
const installed = gameVersions.filter(v =>
Object.keys(versionsList).includes(v.id)
).length
return gameVersions.length
? `${installed}/${gameVersions.length}`
: 'N/A'
})()}{' '} })()}{' '}
versions installed versions installed
</p> </p>
@@ -74,11 +80,7 @@ export default function GamesDownloadPopup () {
<button <button
className='button btntheme3 right-2 bottom-2' className='button btntheme3 right-2 bottom-2'
onClick={() => setSelectedGame(v.id)} onClick={() => setSelectedGame(v.id)}
title={`Click to download specific versions of the game. You have ${(() => { title='Click to download more versions of the game.'
const data = getVersionsAmountData(v.id)
if (!data) return 'N/A'
return `${data.installed} of ${data.total}`
})()} versions downloaded.`}
> >
<> <>
<FontAwesomeIcon icon={faDownload} /> Download <FontAwesomeIcon icon={faDownload} /> Download
@@ -9,28 +9,26 @@ import {
faWarning faWarning
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useGlobal } from '@/app/GlobalProvider' import { useGlobal } from '@/providers/GlobalProvider'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import prettyBytes from 'pretty-bytes' import prettyBytes from 'pretty-bytes'
import { message } from '@tauri-apps/plugin-dialog' import { message } from '@tauri-apps/plugin-dialog'
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs' import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
import { writeVersionsConfig } from '@/lib/BazookaManager' import { openFolder } from '@/lib/util'
import { openFolder } from '@/lib/Util'
export default function ManageVersionPopup () { export default function ManageVersionPopup () {
const { const {
getGameInfo, versionsList,
getVersionInfo, serverVersionList,
managingVersion, managingVersion,
downloadedVersionsConfig,
viewingInfoFromDownloads, viewingInfoFromDownloads,
setManagingVersion, setManagingVersion,
closePopup, closePopup,
setDownloadedVersionsConfig,
setPopupMode, setPopupMode,
setSelectedVersionList, setSelectedVersionList,
downloadVersions downloadVersions,
versions
} = useGlobal() } = useGlobal()
const [versionSize, setVersionSize] = useState<number>(0) const [versionSize, setVersionSize] = useState<number>(0)
@@ -43,10 +41,14 @@ export default function ManageVersionPopup () {
}) })
}, [managingVersion, setVersionSize, viewingInfoFromDownloads]) }, [managingVersion, setVersionSize, viewingInfoFromDownloads])
if (!managingVersion || !downloadedVersionsConfig) return <></> if (!managingVersion) return <></>
const versionInfo = getVersionInfo(managingVersion) const versionInfo = serverVersionList?.versions.find(
const gameInfo = getGameInfo(versionInfo?.game) vf => vf.id == managingVersion
)
const gameInfo = serverVersionList?.games.find(
vf => vf.id == versionInfo?.game
)
return ( return (
<> <>
@@ -59,7 +61,7 @@ export default function ManageVersionPopup () {
<p> <p>
Installed{' '} Installed{' '}
{new Intl.DateTimeFormat(undefined).format( {new Intl.DateTimeFormat(undefined).format(
downloadedVersionsConfig.list[managingVersion] ?? 0 versionsList[managingVersion] ?? 0
)} )}
</p> </p>
</div> </div>
@@ -135,18 +137,14 @@ export default function ManageVersionPopup () {
onClick={async () => { onClick={async () => {
closePopup() closePopup()
setDownloadedVersionsConfig(prev => { versions?.set(
if (!prev) return prev 'list',
const updatedList = Object.fromEntries( Object.fromEntries(
Object.entries(prev.list).filter(([k]) => k !== managingVersion) Object.entries(versionsList).filter(
([k]) => k !== managingVersion
)
) )
const updatedConfig = { )
...prev,
list: updatedList
}
writeVersionsConfig(updatedConfig)
return updatedConfig
})
if ( if (
await exists('game/' + managingVersion, { await exists('game/' + managingVersion, {
@@ -171,18 +169,14 @@ export default function ManageVersionPopup () {
setPopupMode(1) setPopupMode(1)
//uninstall //uninstall
setDownloadedVersionsConfig(prev => { versions?.set(
if (!prev) return prev 'list',
const updatedList = Object.fromEntries( Object.fromEntries(
Object.entries(prev.list).filter(([k]) => k !== managingVersion) Object.entries(versionsList).filter(
([k]) => k !== managingVersion
)
) )
const updatedConfig = { )
...prev,
list: updatedList
}
writeVersionsConfig(updatedConfig)
return updatedConfig
})
if ( if (
await exists('game/' + managingVersion, { await exists('game/' + managingVersion, {
@@ -1,8 +1,7 @@
'use client' 'use client'
import { useGlobal } from '@/app/GlobalProvider' import { useGlobal } from '@/providers/GlobalProvider'
import { writeVersionsConfig } from '@/lib/BazookaManager' import { verifySignature } from '@/lib/util'
import { verifySignature } from '@/lib/Util'
import { Mod } from '@/types/Mod' import { Mod } from '@/types/Mod'
import { import {
faArrowUpRightFromSquare, faArrowUpRightFromSquare,
@@ -23,14 +22,14 @@ import { useEffect, useState } from 'react'
export default function ModDownloadsPopup () { export default function ModDownloadsPopup () {
const { const {
getVersionInfo, modsList,
serverVersionList,
managingVersion, managingVersion,
downloadedVersionsConfig,
showModInfo, showModInfo,
setShowModInfo, setShowModInfo,
downloadVersions, downloadVersions,
downloadProgress, downloadProgress,
setDownloadedVersionsConfig versions
} = useGlobal() } = useGlobal()
const [mods, setMods] = useState<Mod[] | 0 | 1>(0) const [mods, setMods] = useState<Mod[] | 0 | 1>(0)
@@ -52,9 +51,11 @@ export default function ModDownloadsPopup () {
})() })()
}, [managingVersion]) }, [managingVersion])
if (!managingVersion) return <></> if (!managingVersion || !serverVersionList) return <></>
const versionInfo = getVersionInfo(managingVersion) const versionInfo = serverVersionList.versions.find(
v => v.id == managingVersion
)
return ( return (
<> <>
@@ -110,10 +111,10 @@ export default function ModDownloadsPopup () {
(!showModInfo && (!showModInfo &&
mods.filter(v => mods.filter(v =>
tab == 0 tab == 0
? Object.keys(downloadedVersionsConfig?.mods ?? []).includes( ? Object.keys(modsList).includes(
String(versionInfo?.game + '-' + v.id) String(versionInfo?.game + '-' + v.id)
) )
: !Object.keys(downloadedVersionsConfig?.mods ?? []).includes( : !Object.keys(modsList).includes(
String(versionInfo?.game + '-' + v.id) String(versionInfo?.game + '-' + v.id)
) )
).length == 0) ? ( ).length == 0) ? (
@@ -131,10 +132,10 @@ export default function ModDownloadsPopup () {
{mods {mods
.filter(v => .filter(v =>
tab == 0 tab == 0
? Object.keys(downloadedVersionsConfig?.mods ?? []).includes( ? Object.keys(modsList).includes(
String(versionInfo?.game + '-' + v.id) String(versionInfo?.game + '-' + v.id)
) )
: !Object.keys(downloadedVersionsConfig?.mods ?? []).includes( : !Object.keys(modsList).includes(
String(versionInfo?.game + '-' + v.id) String(versionInfo?.game + '-' + v.id)
) )
) )
@@ -142,9 +143,7 @@ export default function ModDownloadsPopup () {
const localVersion = const localVersion =
tab == 0 tab == 0
? Object.keys( ? Object.keys(
downloadedVersionsConfig?.mods?.[ modsList[versionInfo?.game + '-' + v.id] ?? {}
versionInfo?.game + '-' + v.id
] ?? {}
)[0] )[0]
: null : null
return ( return (
@@ -201,20 +200,14 @@ export default function ModDownloadsPopup () {
baseDir: BaseDirectory.AppLocalData, baseDir: BaseDirectory.AppLocalData,
recursive: true recursive: true
}) })
setDownloadedVersionsConfig(prev => { versions?.set(
if (!prev) return prev 'mods',
const updatedMods = Object.fromEntries( Object.fromEntries(
Object.entries(prev.mods).filter( Object.entries(modsList).filter(
([k]) => k !== versionInfo?.game + '-' + v.id ([k]) => k !== versionInfo?.game + '-' + v.id
) )
) )
const updatedConfig = { )
...prev,
mods: updatedMods
}
writeVersionsConfig(updatedConfig)
return updatedConfig
})
downloadVersions([ downloadVersions([
{ {
@@ -273,7 +266,7 @@ export default function ModDownloadsPopup () {
: '(No description added)'} : '(No description added)'}
</div> </div>
<div className='flex flex-row h-fit w-full gap-2 justify-center my-2'> <div className='flex flex-row h-fit w-full gap-2 justify-center my-2'>
{Object.keys(downloadedVersionsConfig?.mods ?? []).includes( {Object.keys(modsList).includes(
String(versionInfo?.game + '-' + showModInfo.id) String(versionInfo?.game + '-' + showModInfo.id)
) ? ( ) ? (
<button <button
@@ -294,21 +287,15 @@ export default function ModDownloadsPopup () {
baseDir: BaseDirectory.AppLocalData, baseDir: BaseDirectory.AppLocalData,
recursive: true recursive: true
}) })
setDownloadedVersionsConfig(prev => { versions?.set(
if (!prev) return prev 'mods',
const updatedMods = Object.fromEntries( Object.fromEntries(
Object.entries(prev.mods).filter( Object.entries(modsList).filter(
([k]) => ([k]) =>
k !== versionInfo?.game + '-' + showModInfo.id k !== versionInfo?.game + '-' + showModInfo.id
) )
) )
const updatedConfig = { )
...prev,
mods: updatedMods
}
writeVersionsConfig(updatedConfig)
return updatedConfig
})
}} }}
> >
<FontAwesomeIcon icon={faTrash} className='text-red-400' />{' '} <FontAwesomeIcon icon={faTrash} className='text-red-400' />{' '}
@@ -1,22 +1,23 @@
import { useGlobal } from '@/app/GlobalProvider' import { useGlobal } from '@/providers/GlobalProvider'
import { faAdd, faInfo, faRemove } from '@fortawesome/free-solid-svg-icons' import { faAdd, faInfo, faRemove } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
export default function VersionsDownloadPopup () { export default function VersionsDownloadPopup () {
const { const {
serverVersionList,
getSpecialVersionsList,
selectedVersionList, selectedVersionList,
setSelectedVersionList, setSelectedVersionList,
setManagingVersion, setManagingVersion,
setPopupMode, setPopupMode,
getSpecialVersionsList,
selectedGame, selectedGame,
setViewingInfoFromDownloads, setViewingInfoFromDownloads,
downloadedVersionsConfig, downloadVersions
downloadVersions,
getGameInfo
} = useGlobal() } = useGlobal()
if (!selectedGame) return <></> const game = serverVersionList?.games.find(g => g.id === selectedGame)
if (!selectedGame || !game) return <></>
return ( return (
<> <>
@@ -78,24 +79,18 @@ export default function VersionsDownloadPopup () {
<button <button
className='button btntheme1 w-fit mt-2 -mb-4' className='button btntheme1 w-fit mt-2 -mb-4'
onClick={() => { onClick={() => {
if (downloadedVersionsConfig) { downloadVersions(
downloadVersions( selectedVersionList.map(versionId => ({
selectedVersionList.map(versionId => ({ id: versionId,
id: versionId, type: 0
type: 0 }))
})) )
) setPopupMode(1)
setPopupMode(1)
}
}} }}
disabled={selectedVersionList.length == 0} disabled={selectedVersionList.length == 0}
title={ title={`Download ${selectedVersionList.length} version${
selectedVersionList.length == 0 selectedVersionList.length == 1 ? '' : 's'
? 'Select at least one version to download' } of ${game.name}`}
: `Download ${selectedVersionList.length} version${
selectedVersionList.length == 1 ? '' : 's'
} of ${getGameInfo(selectedGame)?.name}`
}
> >
Download {selectedVersionList.length} version Download {selectedVersionList.length} version
{selectedVersionList.length == 1 ? '' : 's'} {selectedVersionList.length == 1 ? '' : 's'}
@@ -8,12 +8,10 @@ import {
SetStateAction SetStateAction
} from 'react' } from 'react'
import { DownloadProgress } from '@/types/DownloadProgress' import { DownloadProgress } from '@/types/DownloadProgress'
import { VersionsConfig } from '@/types/VersionsConfig'
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse' import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
import { GameVersion } from '@/types/GameVersion'
import { Game } from '@/types/Game'
import { Mod } from '@/types/Mod' import { Mod } from '@/types/Mod'
import { Store } from '@tauri-apps/plugin-store' import { Store } from '@tauri-apps/plugin-store'
import { GameVersion } from '@/types/GameVersion'
type GlobalCtxType = { type GlobalCtxType = {
serverVersionList: ServerVersionsResponse | null serverVersionList: ServerVersionsResponse | null
@@ -27,18 +25,9 @@ type GlobalCtxType = {
setPopupMode: Dispatch<SetStateAction<number | null>> setPopupMode: Dispatch<SetStateAction<number | null>>
fadeOut: boolean fadeOut: boolean
setFadeOut: Dispatch<SetStateAction<boolean>> setFadeOut: Dispatch<SetStateAction<boolean>>
downloadedVersionsConfig: VersionsConfig | null
setDownloadedVersionsConfig: Dispatch<SetStateAction<VersionsConfig | null>>
managingVersion: string | null managingVersion: string | null
setManagingVersion: Dispatch<SetStateAction<string | null>> setManagingVersion: Dispatch<SetStateAction<string | null>>
setSelectedGame: Dispatch<SetStateAction<number | null>> setSelectedGame: Dispatch<SetStateAction<number | null>>
getVersionInfo: (id: string | undefined) => GameVersion | undefined
getGameInfo: (game: number | undefined) => Game | undefined
getListOfGames(): Game[]
getVersionsAmountData: (gameId: number) => {
installed: number
total: number
} | null
viewingInfoFromDownloads: boolean viewingInfoFromDownloads: boolean
version: string | null version: string | null
downloadVersions: ( downloadVersions: (
@@ -56,17 +45,20 @@ type GlobalCtxType = {
downloadQueue: string[] downloadQueue: string[]
setDownloadQueue: Dispatch<SetStateAction<string[]>> setDownloadQueue: Dispatch<SetStateAction<string[]>>
closePopup: () => void closePopup: () => void
getSpecialVersionsList(game?: number | undefined): GameVersion[]
selectedGame: number | null selectedGame: number | null
setViewingInfoFromDownloads: Dispatch<SetStateAction<boolean>> setViewingInfoFromDownloads: Dispatch<SetStateAction<boolean>>
showModInfo: Mod | null showModInfo: Mod | null
setShowModInfo: Dispatch<SetStateAction<Mod | null>> setShowModInfo: Dispatch<SetStateAction<Mod | null>>
getSpecialVersionsList: (game?: number) => GameVersion[]
settings: Store | null settings: Store | null
versions: Store | null
notificationsAllowed: boolean notificationsAllowed: boolean
sidebarAlwaysShowGames: boolean sidebarAlwaysShowGames: boolean
linuxUseWine: boolean linuxUseWine: boolean
linuxWineCommand: string linuxWineCommand: string
theme: string theme: string
versionsList: Record<string, number>
modsList: Record<string, Record<string, number>>
} }
const GlobalCtx = createContext<GlobalCtxType | null>(null) const GlobalCtx = createContext<GlobalCtxType | null>(null)
File renamed without changes.
File renamed without changes.
File renamed without changes.
-20
View File
@@ -1,20 +0,0 @@
export type VersionsConfigData = {
version: string
list: Record<string, number>
mods: Record<string, Record<string, number>>
}
export class VersionsConfig {
constructor (
public version: string,
public list: Record<string, number> = {},
public mods: Record<string, Record<string, number>> = {}
) {}
static import (data: VersionsConfigData) {
const cfg = new VersionsConfig(data.version)
cfg.list = { ...data.list }
cfg.mods = { ...data.mods }
return cfg
}
}