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

-88
View File
@@ -1,88 +0,0 @@
'use client'
import {
createContext,
useContext,
ReactNode,
Dispatch,
SetStateAction
} from 'react'
import { DownloadProgress } from '@/types/DownloadProgress'
import { VersionsConfig } from '@/types/VersionsConfig'
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
import { GameVersion } from '@/types/GameVersion'
import { Game } from '@/types/Game'
import { Mod } from '@/types/Mod'
import { Store } from '@tauri-apps/plugin-store'
type GlobalCtxType = {
serverVersionList: ServerVersionsResponse | null
selectedVersionList: string[]
setSelectedVersionList: (value: SetStateAction<string[]>) => void
downloadProgress: DownloadProgress[]
setDownloadProgress: Dispatch<SetStateAction<DownloadProgress[]>>
showPopup: boolean
setShowPopup: Dispatch<SetStateAction<boolean>>
popupMode: number | null
setPopupMode: Dispatch<SetStateAction<number | null>>
fadeOut: boolean
setFadeOut: Dispatch<SetStateAction<boolean>>
downloadedVersionsConfig: VersionsConfig | null
setDownloadedVersionsConfig: Dispatch<SetStateAction<VersionsConfig | null>>
managingVersion: string | null
setManagingVersion: Dispatch<SetStateAction<string | 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
version: string | null
downloadVersions: (
list: Array<{
id: string
type: 0 | 1 | 2
modDownload?: number
gameId?: number
modId?: number
modVersion?: string
}>
) => Promise<void>
category: number
setCategory: Dispatch<SetStateAction<number>>
downloadQueue: string[]
setDownloadQueue: Dispatch<SetStateAction<string[]>>
closePopup: () => void
getSpecialVersionsList(game?: number | undefined): GameVersion[]
selectedGame: number | null
setViewingInfoFromDownloads: Dispatch<SetStateAction<boolean>>
showModInfo: Mod | null
setShowModInfo: Dispatch<SetStateAction<Mod | null>>
settings: Store | null
notificationsAllowed: boolean
sidebarAlwaysShowGames: boolean
linuxUseWine: boolean
linuxWineCommand: string
theme: string
}
const GlobalCtx = createContext<GlobalCtxType | null>(null)
export const useGlobal = () => {
const ctx = useContext(GlobalCtx)
if (!ctx) throw new Error('useGlobal must be inside GlobalProvider')
return ctx
}
export const GlobalProvider = ({
children,
value
}: {
children: ReactNode
value: GlobalCtxType
}) => {
return <GlobalCtx.Provider value={value}>{children}</GlobalCtx.Provider>
}
-151
View File
@@ -1,151 +0,0 @@
@import "tailwindcss";
html {
overscroll-behavior: none;
}
body {
@apply bg-(--col0) text-white select-none;
}
.dark-theme {
--col0: rgb(8, 8, 8);
--col1: rgb(16, 16, 16);
--col2: rgb(32, 32, 32);
--col3: rgb(48, 48, 48);
--col4: rgb(64, 64, 64);
--col5: rgb(80, 80, 80);
--col6: rgb(96, 96, 96);
--col7: rgb(112, 112, 112);
--col8: rgb(128, 128, 128);
--col9: rgb(144, 144, 144);
}
.red-theme {
--col0: rgb(32, 8, 8);
--col1: rgb(40, 16, 16);
--col2: rgb(56, 32, 32);
--col3: rgb(72, 48, 48);
--col4: rgb(88, 64, 64);
--col5: rgb(104, 80, 80);
--col6: rgb(120, 96, 96);
--col7: rgb(136, 112, 112);
--col8: rgb(152, 128, 128);
--col9: rgb(168, 144, 144);
}
.blue-theme {
--col0: rgb(8, 8, 32);
--col1: rgb(16, 16, 40);
--col2: rgb(32, 32, 56);
--col3: rgb(48, 48, 72);
--col4: rgb(64, 64, 88);
--col5: rgb(80, 80, 104);
--col6: rgb(96, 96, 120);
--col7: rgb(112, 112, 136);
--col8: rgb(128, 128, 152);
--col9: rgb(144, 144, 168);
}
.purple-theme {
--col0: rgb(20, 8, 32);
--col1: rgb(28, 16, 40);
--col2: rgb(44, 32, 56);
--col3: rgb(60, 48, 72);
--col4: rgb(76, 64, 88);
--col5: rgb(92, 80, 104);
--col6: rgb(108, 96, 120);
--col7: rgb(124, 112, 136);
--col8: rgb(140, 128, 152);
--col9: rgb(156, 144, 168);
}
.button {
@apply rounded-md cursor-pointer text-[16px] py-1 px-2 transition-colors border;
}
.btntheme1 {
@apply bg-(--col2) border-(--col4) hover:bg-(--col4) hover:border-(--col6) disabled:text-gray-300 disabled:bg-(--col1) disabled:border-(--col5) disabled:hover:text-gray-100 disabled:hover:bg-(--col2) disabled:hover:border-(--col6);
}
.btntheme2,
.downloads-entry .entry-info-item,
.downloads-entry .button {
@apply bg-(--col3) border-(--col5) hover:bg-(--col5) hover:border-(--col7) disabled:text-gray-300 disabled:bg-(--col2) disabled:border-(--col6) disabled:hover:text-gray-100 disabled:hover:bg-(--col3) disabled:hover:border-(--col7);
}
.btntheme3,
.downloads-entry:hover .entry-info-item,
.downloads-entry:hover .button {
@apply bg-(--col4) border-(--col6) hover:bg-(--col6) hover:border-(--col8) disabled:text-gray-300 disabled:bg-(--col2) disabled:border-(--col7) disabled:hover:text-gray-100 disabled:hover:bg-(--col4) disabled:hover:border-(--col8);
}
::-webkit-scrollbar {
@apply w-2;
}
::-webkit-scrollbar-track {
@apply bg-(--col2) rounded-lg;
}
::-webkit-scrollbar-thumb {
@apply bg-(--col4) w-1 rounded-lg active:bg-(--col5);
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.popup-overlay {
@apply fixed w-screen h-screen z-99999 flex justify-center items-center animate-[fadeIn_0.2s_ease-out_forwards] left-0 top-0 bg-[rgba(0,0,0,0.5)];
}
.popup-overlay.fade-out {
@apply animate-[fadeOut_0.2s_ease-out_forwards];
}
.popup-box {
@apply relative w-150 h-120 rounded-lg bg-(--col1) border border-(--col3) flex flex-col p-6;
}
.popup-content {
@apply flex-1 overflow-auto bg-(--col2) border border-(--col4) rounded-lg mt-4;
}
.popup-entry {
@apply relative h-fit bg-(--col3) m-2 p-2 rounded-lg border border-(--col5);
}
.popup-entry button {
@apply absolute;
}
.popup-top-button {
@apply flex justify-center items-center absolute text-2xl text-gray-200 hover:text-white cursor-pointer h-12 w-12 p-3 rounded-lg top-2 transition-colors border;
}
*:focus {
@apply outline-none;
}
.input-field {
@apply border-2 border-(--col4) rounded-md bg-(--col2) py-1 px-2 focus:bg-(--col3) focus:border-(--col6) transition-colors w-full;
}
.entry-info-item {
@apply flex flex-row items-center gap-1 border text-gray-300 py-1 px-2 rounded-md w-fit text-[16px] transition-colors cursor-pointer;
}
+2 -2
View File
@@ -2,13 +2,13 @@
import { useCallback, useEffect, useState } from 'react'
import { BirdColor } from '@/types/BerryDash/BirdColor'
import { GetIconForUser } from '@/lib/BerryDash'
import { GetIconForUser } from '@/lib/berrydashutil'
import Image from 'next/image'
import './styles.css'
import { useRouter } from 'next/navigation'
import { platform } from '@tauri-apps/plugin-os'
import { fetch } from '@tauri-apps/plugin-http'
import { verifySignature } from '@/lib/Util'
import { verifySignature } from '@/lib/util'
interface BaseEntry {
id: number
+73 -107
View File
@@ -3,36 +3,33 @@
import { useEffect } from 'react'
import '@/app/Installs.css'
import { invoke } from '@tauri-apps/api/core'
import { useGlobal } from '@/app/GlobalProvider'
import { useGlobal } from '@/providers/GlobalProvider'
import { useRouter, useSearchParams } from 'next/navigation'
import { platform } from '@tauri-apps/plugin-os'
import { faWarning } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { ask } from '@tauri-apps/plugin-dialog'
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 () {
const {
versionsList,
serverVersionList,
showPopup,
setShowPopup,
setPopupMode,
setFadeOut,
setSelectedVersionList,
downloadedVersionsConfig,
setManagingVersion,
getVersionInfo,
getGameInfo,
setSelectedGame,
serverVersionList,
category,
setCategory,
setDownloadedVersionsConfig,
downloadVersions,
downloadProgress,
linuxUseWine,
linuxWineCommand
linuxWineCommand,
versions
} = useGlobal()
const params = useSearchParams()
@@ -55,10 +52,7 @@ export default function Installs () {
if (!lastRevision) return false
return (
lastRevision > 0 &&
(downloadedVersionsConfig == undefined
? 0
: downloadedVersionsConfig?.list[version]) /
1000 <=
(versionsList == undefined ? 0 : versionsList[version]) / 1000 <=
lastRevision
)
}
@@ -122,12 +116,11 @@ export default function Installs () {
>
{category == -1 &&
Object.entries(game.categoryNames)
.sort(([a], [b]) => Number(b) - Number(a))
.filter(([key]) => {
const count = Object.keys(
downloadedVersionsConfig?.list ?? {}
).filter(v => {
const info = getVersionInfo(v)
const count = Object.keys(versionsList).filter(v => {
const info = serverVersionList?.versions.find(
vf => vf.id == v
)
if (!info) return false
if (platform() == 'linux' && info.wine && !linuxUseWine)
@@ -138,6 +131,7 @@ export default function Installs () {
return count >= 1
})
.sort(([a], [b]) => Number(b) - Number(a))
.map(([key, value]) => {
return (
<div
@@ -157,10 +151,10 @@ export default function Installs () {
<p>
{(() => {
const count =
Object.keys(
downloadedVersionsConfig?.list ?? []
).filter(v => {
const info = getVersionInfo(v)
Object.keys(versionsList).filter(v => {
const info = serverVersionList?.versions.find(
vf => vf.id == v
)
if (!info) return false
if (
platform() == 'linux' &&
@@ -181,44 +175,40 @@ export default function Installs () {
</div>
)
})}
{Object.keys(downloadedVersionsConfig?.list ?? []).filter(v => {
const info = getVersionInfo(v)
if (!info) return false
return info.game === id
}).length != 0 ? (
Object.keys(downloadedVersionsConfig?.list ?? [])
.sort((a, b) => {
const infoA = getVersionInfo(a)
const infoB = getVersionInfo(b)
if (!infoA || !infoB) return 0
return infoB.place - infoA.place
})
.filter(v => {
const info = getVersionInfo(v)
if (!info) return false
if (platform() == 'linux' && info.wine && !linuxUseWine)
return false
return (
info.game === id &&
(category == -1
? info.category == -1
: info.category == category)
)
})
.map(entry => (
{Object.keys(versionsList)
.filter(v => {
const info = serverVersionList?.versions.find(vf => vf.id == v)
if (!info) return false
if (platform() == 'linux' && info.wine && !linuxUseWine)
return false
return (
info.game === id &&
(category == -1
? info.category == -1
: info.category == category)
)
})
.sort((a, b) => {
const infoA = serverVersionList?.versions.find(vf => vf.id == a)
const infoB = serverVersionList?.versions.find(vf => vf.id == b)
if (!infoA || !infoB) return 0
return infoB.place - infoA.place
})
.map(v => {
const versionInfo = serverVersionList?.versions.find(
vf => vf.id == v
)
if (!versionInfo) return
return (
<div
key={entry}
key={v}
className={'downloads-entry'}
title={
'Click to launch game. Right-click to manage this version install'
}
onClick={async () => {
if (
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
) {
if (needsRevisionUpdate(versionInfo.lastRevision, v)) {
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.',
{
@@ -242,55 +232,45 @@ export default function Installs () {
setFadeOut(false)
//uninstall
setDownloadedVersionsConfig(prev => {
if (!prev) return prev
const updatedList = Object.fromEntries(
Object.entries(prev.list).filter(
([k]) => k !== entry
versions?.set(
'list',
Object.fromEntries(
Object.entries(versionsList).filter(
([k]) => k !== v
)
)
const updatedConfig = {
...prev,
list: updatedList
}
writeVersionsConfig(updatedConfig)
return updatedConfig
})
)
if (
await exists('game/' + entry, {
await exists('game/' + v, {
baseDir: BaseDirectory.AppLocalData
})
)
await remove('game/' + entry, {
await remove('game/' + v, {
baseDir: BaseDirectory.AppLocalData,
recursive: true
})
//reinstall
setSelectedVersionList([entry])
setSelectedVersionList([v])
downloadVersions([
{
id: entry,
id: v,
type: 0
}
])
} else {
openFolder(entry)
openFolder(v)
}
return
}
const verInfo = getVersionInfo(entry)
if (verInfo == undefined) return
const gameInfo = getGameInfo(verInfo.game)
if (gameInfo == undefined) return
invoke('launch_game', {
name: verInfo.id,
executable: verInfo.executable,
displayName: verInfo.displayName,
name: versionInfo.id,
executable: versionInfo.executable,
displayName: versionInfo.displayName,
useWine: !!(
platform() == 'linux' &&
verInfo.wine &&
versionInfo.wine &&
linuxUseWine
),
wineCommand: linuxWineCommand
@@ -298,16 +278,14 @@ export default function Installs () {
}}
onContextMenu={e => {
e.preventDefault()
setManagingVersion(entry)
setManagingVersion(v)
setPopupMode(2)
setShowPopup(true)
setFadeOut(false)
}}
>
<div className='h-18 w-screen relative'>
<p className='text-2xl'>
{getVersionInfo(entry)?.displayName}{' '}
</p>
<p className='text-2xl'>{versionInfo.displayName}</p>
<div className='flex gap-2 absolute left-0 bottom-0'>
<div
@@ -317,7 +295,7 @@ export default function Installs () {
<p>
Installed{' '}
{new Intl.DateTimeFormat(undefined).format(
downloadedVersionsConfig?.list[entry]
versionsList[v]
)}
</p>
</div>
@@ -325,13 +303,8 @@ export default function Installs () {
className='entry-info-item'
title='This version is using wine. It cannot be guarenteed to work fully and might not work at all.'
hidden={
!(
platform() == 'linux' && getVersionInfo(entry)?.wine
) ||
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
!(platform() == 'linux' && versionInfo.wine) ||
needsRevisionUpdate(versionInfo.lastRevision, v)
}
>
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
@@ -340,10 +313,7 @@ export default function Installs () {
<div
className='entry-info-item'
hidden={
!needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
!needsRevisionUpdate(versionInfo.lastRevision, v)
}
>
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
@@ -357,45 +327,41 @@ export default function Installs () {
e.stopPropagation()
if (
!(await exists('game/' + entry + '/BepInEx', {
!(await exists('game/' + v + '/BepInEx', {
baseDir: BaseDirectory.AppLocalData
}))
) {
if (
(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 " +
getVersionInfo(entry)?.displayName +
versionInfo.displayName +
'.',
{ title: 'BepInEx not found!', kind: 'error' }
)) &&
!downloadProgress.find(d => d.version == entry)
!downloadProgress.find(d => d.version == v)
) {
downloadVersions([
{
id: entry,
id: v,
type: 1
}
])
}
return
}
setManagingVersion(entry)
setManagingVersion(v)
setPopupMode(3)
setShowPopup(true)
setFadeOut(false)
}}
hidden={!getVersionInfo(entry)?.modSupportDownload}
hidden={!versionInfo.modSupportDownload}
>
Mod Manager
</button>
</div>
</div>
))
) : (
<div className='flex justify-center items-center h-full'>
<p className='text-3xl'>No versions installed</p>
</div>
)}
)
})}
</div>
</div>
</div>
+81 -136
View File
@@ -1,21 +1,14 @@
'use client'
import './Globals.css'
import '@/styles/globals.css'
import { useCallback, useEffect, useRef, useState } from 'react'
import Sidebar from '@/components/Sidebar'
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 { openUrl } from '@tauri-apps/plugin-opener'
import { GlobalProvider } from './GlobalProvider'
import { GlobalProvider } from '../providers/GlobalProvider'
import { Roboto } from 'next/font/google'
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
import { GameVersion } from '@/types/GameVersion'
import { Game } from '@/types/Game'
import { listen } from '@tauri-apps/api/event'
import { usePathname } from 'next/navigation'
import { arch, platform } from '@tauri-apps/plugin-os'
@@ -30,17 +23,21 @@ import {
remove
} from '@tauri-apps/plugin-fs'
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 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 { 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({
subsets: ['latin']
@@ -61,9 +58,6 @@ export default function RootLayout ({
useState<null | ServerVersionsResponse>(null)
const [selectedVersionList, setSelectedVersionList] = useState<string[]>([])
const [downloadedVersionsConfig, setDownloadedVersionsConfig] =
useState<VersionsConfig | null>(null)
const [showPopup, setShowPopup] = useState(false)
const [popupMode, setPopupMode] = useState<null | number>(null)
const [fadeOut, setFadeOut] = useState(false)
@@ -85,6 +79,7 @@ export default function RootLayout ({
const revisionCheck = useRef(false)
const [settings, setSettings] = useState<Store | null>(null)
const [versions, setVersions] = useState<Store | null>(null)
const [notificationsAllowed, setNotificationsAllowed] =
useState<boolean>(false)
@@ -95,13 +90,16 @@ export default function RootLayout ({
useState<string>('wine %path%')
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[] {
if (!serverVersionList) return []
return serverVersionList.versions
.filter(
v => !Object.keys(downloadedVersionsConfig?.list ?? []).includes(v.id)
)
.filter(v => !Object.keys(versionsList).includes(v.id))
.filter(v => {
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(() => {
if (popupMode == 0 && selectedGame && pathname === '/') {
setSelectedGame(null)
@@ -369,12 +311,21 @@ export default function RootLayout ({
theme: 'dark'
}
})
const versionsLocal = await load('versions.json', {
autoSave: true,
defaults: {
version: client,
list: {},
mods: {}
}
})
settingsLocal.set('version', client)
const oldSettingsOptions = {
versionsLocal.set('version', client)
const legacyOptions = {
baseDir: BaseDirectory.AppLocalData
}
if (await exists('config.json', oldSettingsOptions)) {
const config = await readTextFile('config.json', oldSettingsOptions)
if (await exists('config.json', legacyOptions)) {
const config = await readTextFile('config.json', legacyOptions)
const raw = JSON.parse(config)
if (
raw.settings &&
@@ -432,11 +383,10 @@ export default function RootLayout ({
})()
settingsLocal?.set('theme', tempTheme)
}
await remove('config.json', oldSettingsOptions)
await remove('config.json', legacyOptions)
}
const versionsConfig = await readVersionsConfig()
setDownloadedVersionsConfig(versionsConfig)
setSettings(settingsLocal)
setVersions(versionsLocal)
setLoading(false)
if (!(await isPermissionGranted())) {
@@ -511,7 +461,7 @@ export default function RootLayout ({
const versionId = downloadQueue[0]
const downloadInfo = downloadProgress.find(d => d.version == versionId)
if (!downloadInfo) return
const info = getVersionInfo(versionId)
const info = serverVersionList?.versions.find(vf => vf.id == versionId)
if (!info) {
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
@@ -520,7 +470,7 @@ export default function RootLayout ({
return
}
const gameInfo = getGameInfo(info.game)
const gameInfo = serverVersionList?.games.find(vf => vf.id == info.game)
if (!gameInfo) {
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
setDownloadQueue(prev => prev.slice(1))
@@ -590,31 +540,19 @@ export default function RootLayout ({
if (res === '1') {
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
if (downloadInfo.type != 1) {
setDownloadedVersionsConfig(prev => {
if (!prev) return prev
const updated =
downloadInfo.type == 2
? {
...prev,
mods: {
...prev.mods,
[downloadInfo.modGame! + '-' + downloadInfo.modId!]: {
[downloadInfo.modVersion!]: Date.now()
}
}
}
: {
...prev,
list: {
...prev.list,
[versionId]: Date.now()
}
}
writeVersionsConfig(updated)
return updated
})
if (downloadInfo.type == 2) {
versions?.set('mods', {
...modsList,
[downloadInfo.modGame! + '-' + downloadInfo.modId!]: {
[downloadInfo.modVersion!]: Date.now()
}
})
} else {
versions?.set('list', {
...versionsList,
[versionId]: Date.now()
})
}
}
} else if (res == '0') {
setDownloadProgress(prev => {
@@ -658,20 +596,20 @@ export default function RootLayout ({
}, [
downloadQueue,
isProcessingQueue,
getVersionInfo,
getGameInfo,
downloadProgress,
settings
serverVersionList,
settings,
versions,
versionsList,
modsList
])
useEffect(() => {
if (revisionCheck.current) return
if (!serverVersionList || !downloadedVersionsConfig) return
if (!serverVersionList) return
revisionCheck.current = true
;(async () => {
for (const [key, value] of Object.entries(
downloadedVersionsConfig.list
)) {
for (const [key, value] of Object.entries(versionsList)) {
const verInfo = serverVersionList.versions.find(item => item.id === key)
if (
@@ -690,7 +628,7 @@ export default function RootLayout ({
}
}
})()
}, [serverVersionList, downloadedVersionsConfig, downloadVersions])
}, [serverVersionList, downloadVersions, versionsList])
useEffect(() => {
if (
@@ -719,30 +657,40 @@ export default function RootLayout ({
])
useEffect(() => {
if (!settings) return
if (!settings || !versions) return
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)
unlisteners.push(settings!.onKeyChange<T>(key, fn))
}
watch<string>('theme', v => setTheme(v ?? 'dark'))
watch<boolean>('notificationsAllowed', v =>
function watchVersions<T> (key: string, fn: (v: T | undefined) => void) {
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)
)
watch<boolean>('sidebarAlwaysShowGames', v =>
watchSettings<boolean>('sidebarAlwaysShowGames', v =>
setSidebarAlwaysShowGames(v ?? true)
)
watch<boolean>('linuxUseWine', v => setLinuxUseWine(v ?? false))
watch<string>('linuxWineCommand', v =>
watchSettings<boolean>('linuxUseWine', v => setLinuxUseWine(v ?? false))
watchSettings<string>('linuxWineCommand', v =>
setLinuxWineCommand(v ?? 'wine %path%')
)
watchVersions<Record<string, number>>('list', v => setVersionsList(v ?? {}))
watchVersions<Record<string, Record<string, number>>>('mods', v =>
setModsList(v ?? {})
)
return () => {
unlisteners.forEach(u => u.then(fn => fn()))
}
}, [settings])
}, [settings, versions])
return (
<>
@@ -795,15 +743,9 @@ export default function RootLayout ({
setPopupMode,
fadeOut,
setFadeOut,
downloadedVersionsConfig,
setDownloadedVersionsConfig,
managingVersion,
setManagingVersion,
getVersionInfo,
getGameInfo,
getListOfGames,
setSelectedGame,
getVersionsAmountData,
viewingInfoFromDownloads,
version,
downloadVersions,
@@ -812,17 +754,20 @@ export default function RootLayout ({
downloadQueue,
setDownloadQueue,
closePopup,
getSpecialVersionsList,
selectedGame,
setViewingInfoFromDownloads,
showModInfo,
setShowModInfo,
getSpecialVersionsList,
settings,
versions,
notificationsAllowed,
sidebarAlwaysShowGames,
linuxUseWine,
linuxWineCommand,
theme
theme,
versionsList,
modsList
}}
>
<div
+69 -68
View File
@@ -2,7 +2,7 @@
import { useEffect } from 'react'
import './Installs.css'
import { useGlobal } from './GlobalProvider'
import { useGlobal } from '../providers/GlobalProvider'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
faCheck,
@@ -14,15 +14,14 @@ import { useRouter } from 'next/navigation'
export default function Installs () {
const {
versionsList,
serverVersionList,
showPopup,
setShowPopup,
setPopupMode,
setFadeOut,
setSelectedVersionList,
downloadedVersionsConfig,
setSelectedGame,
getListOfGames,
getVersionsAmountData,
setCategory
} = useGlobal()
@@ -58,74 +57,76 @@ export default function Installs () {
: 'h-[calc(100vh-84px)]'
}`}
>
{downloadedVersionsConfig &&
Object.keys(downloadedVersionsConfig.list).length ? (
getListOfGames()
.sort((a, b) => {
return a.id - b.id
})
.map(i => (
<div
key={i.id}
className={'downloads-entry'}
title={'Click to view game installs'}
onClick={() => {
setCategory(-1)
router.push('/game?id=' + i.id)
}}
>
<div className='h-18 w-screen relative'>
<p className='text-2xl'>{i.name}</p>
{serverVersionList?.games
.filter(g =>
serverVersionList.versions
.filter(v => v.game === g.id)
.some(v => Object.keys(versionsList).includes(v.id))
)
.map(i => (
<div
key={i.id}
className={'downloads-entry'}
title={'Click to view game installs'}
onClick={() => {
setCategory(-1)
router.push('/game?id=' + i.id)
}}
>
<div className='h-18 w-screen relative'>
<p className='text-2xl'>{i.name}</p>
<div className='flex gap-2 absolute left-0 bottom-0'>
<div
className='entry-info-item'
title='The amount of versions installed of this game in installed/installable format.'
onClick={e => e.stopPropagation()}
>
<p>
{(() => {
const data = getVersionsAmountData(i.id)
if (!data) return 'N/A'
return `${data.installed}/${data.total}`
})()}{' '}
versions installed
</p>
</div>
<div
className='entry-info-item'
hidden={!i.official}
title='This game is official.'
onClick={e => e.stopPropagation()}
>
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
<p>Official</p>
</div>
<div
className='entry-info-item'
hidden={i.official}
title={
i.verified
? 'This game is verified to be safe'
: 'This game is not verified to be save. Proceed with caution.'
}
onClick={e => e.stopPropagation()}
>
<FontAwesomeIcon
icon={i.verified ? faShieldHalved : faWarning}
color={i.verified ? '#19c84b' : '#ffc800'}
/>
<p>{i.verified ? 'Verified' : 'Unverified'}</p>
</div>
<div className='flex gap-2 absolute left-0 bottom-0'>
<div
className='entry-info-item'
title='The amount of versions installed of this game in installed/installable format.'
onClick={e => e.stopPropagation()}
>
<p>
{(() => {
const gameVersions =
serverVersionList.versions.filter(
v => v.game === i.id
)
const installed = gameVersions.filter(v =>
Object.keys(versionsList).includes(v.id)
).length
return gameVersions.length
? `${installed}/${gameVersions.length}`
: 'N/A'
})()}{' '}
versions installed
</p>
</div>
<div
className='entry-info-item'
hidden={!i.official}
title='This game is official.'
onClick={e => e.stopPropagation()}
>
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
<p>Official</p>
</div>
<div
className='entry-info-item'
hidden={i.official}
title={
i.verified
? 'This game is verified to be safe'
: 'This game is not verified to be save. Proceed with caution.'
}
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 className='flex justify-center items-center h-full'>
<p className='text-3xl'>No games installed</p>
</div>
)}
</div>
))}
</div>
</div>
</div>
+2 -2
View File
@@ -1,8 +1,8 @@
'use client'
import { Setting } from '@/components/Setting'
import { useGlobal } from '@/app/GlobalProvider'
import { copyToClipboard } from '@/lib/Clipboard'
import { useGlobal } from '@/providers/GlobalProvider'
import { copyToClipboard } from '@/lib/clipboard'
import { platform } from '@tauri-apps/plugin-os'
export default function Settings () {