Heavily improve codebase
This commit is contained in:
20 files changed
+365
-581
No files matched your search
@@ -1,17 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
.setting-checkbox-wrapper {
|
||||
@apply relative w-5 h-5;
|
||||
}
|
||||
|
||||
.setting-checkbox {
|
||||
@apply appearance-none w-full h-full border-2 border-(--col4) rounded-md bg-(--col2) transition-colors cursor-pointer;
|
||||
}
|
||||
|
||||
.setting-checkbox:checked {
|
||||
@apply bg-(--col4) border-(--col6);
|
||||
}
|
||||
|
||||
.fa-check-icon {
|
||||
@apply absolute top-1/2 left-1/2 text-white text-[11px] pointer-events-none -translate-x-2/4 -translate-y-2/4;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import './Setting.css'
|
||||
import '@/styles/setting.css'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||
import { SettingProps } from '@/types/SettingProps'
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
.sidebar {
|
||||
@apply fixed top-0 left-0 w-60 h-screen bg-(--col1) flex flex-col border-e border-(--col2) z-1;
|
||||
}
|
||||
|
||||
.sidebar-downloads {
|
||||
@apply text-[#bdbdbd] fixed bottom-2 left-2 bg-(--col2) rounded-lg border border-(--col3) px-4 py-3 text-xl cursor-pointer transition-colors duration-[0.25s] opacity-75;
|
||||
}
|
||||
|
||||
.sidebar-downloads:hover {
|
||||
@apply text-white opacity-100;
|
||||
@apply bg-(--col3) border-(--col4);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
@apply flex flex-col px-4 space-y-1;
|
||||
}
|
||||
|
||||
.link {
|
||||
@apply text-[#bdbdbd] p-2 rounded-md no-underline cursor-pointer transition-colors duration-[0.25s] border border-transparent;
|
||||
}
|
||||
|
||||
.link.active {
|
||||
@apply bg-(--col2) border-(--col3);
|
||||
}
|
||||
|
||||
.link.active,
|
||||
.link:hover {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
.link.active:hover {
|
||||
@apply bg-(--col3) border-(--col4);
|
||||
}
|
||||
|
||||
.sidebar ::-webkit-scrollbar {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.macos-drag {
|
||||
@apply h-7 fixed top-0 left-0 w-60 -z-10;
|
||||
}
|
||||
+14
-13
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import './Sidebar.css'
|
||||
import '@/styles/sidebar.css'
|
||||
import Icon from '@/assets/Icon.png'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faDiscord } from '@fortawesome/free-brands-svg-icons'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import { useGlobal } from '@/providers/GlobalProvider'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
@@ -27,13 +27,12 @@ const lexend = Lexend({
|
||||
|
||||
export default function Sidebar () {
|
||||
const {
|
||||
getListOfGames,
|
||||
serverVersionList,
|
||||
versionsList,
|
||||
setShowPopup,
|
||||
setPopupMode,
|
||||
setFadeOut,
|
||||
downloadProgress,
|
||||
downloadedVersionsConfig,
|
||||
getVersionInfo,
|
||||
category,
|
||||
setCategory,
|
||||
sidebarAlwaysShowGames,
|
||||
@@ -90,10 +89,12 @@ export default function Sidebar () {
|
||||
>
|
||||
<FontAwesomeIcon icon={faHexagonNodes} className='mr-2' /> Games
|
||||
</Link>
|
||||
{getListOfGames()
|
||||
.sort((a, b) => {
|
||||
return a.id - b.id
|
||||
})
|
||||
{serverVersionList?.games
|
||||
.filter(g =>
|
||||
serverVersionList.versions
|
||||
.filter(v => v.game === g.id)
|
||||
.some(v => Object.keys(versionsList).includes(v.id))
|
||||
)
|
||||
.map(i => (
|
||||
<React.Fragment key={i.id}>
|
||||
<div
|
||||
@@ -131,10 +132,10 @@ export default function Sidebar () {
|
||||
{Object.entries(i.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)
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import ProgressBar from '../ProgressBar'
|
||||
import { formatEtaSmart } from '@/lib/Util'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faCancel,
|
||||
faPause,
|
||||
faPlay,
|
||||
faTrash
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { writeVersionsConfig } from '@/lib/BazookaManager'
|
||||
import { notifyUser } from '@/lib/Notifications'
|
||||
import { getCurrentWindow, UserAttentionType } from '@tauri-apps/api/window'
|
||||
|
||||
export default function DownloadsPopup () {
|
||||
const {
|
||||
downloadProgress,
|
||||
getVersionInfo,
|
||||
setDownloadProgress,
|
||||
downloadQueue,
|
||||
setDownloadQueue,
|
||||
setDownloadedVersionsConfig,
|
||||
notificationsAllowed
|
||||
} = useGlobal()
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className='text-xl text-center'>Downloads</p>
|
||||
<div className='popup-content'>
|
||||
{downloadProgress.map((v, i) => {
|
||||
const queuePosition = downloadQueue.indexOf(v.version)
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className='popup-entry flex flex-col justify-between relative'
|
||||
>
|
||||
<div className='absolute right-2 top-2 flex flex-row gap-2'>
|
||||
<div
|
||||
className='cursor-pointer bg-(--col5) hover:bg-(--col7) border border-(--col7) hover:border-(--col9) transition-colors w-8 h-8 flex items-center justify-center rounded-full'
|
||||
hidden={!v.downloading && !v.paused}
|
||||
onClick={async () => {
|
||||
if (v.downloading) {
|
||||
await invoke('cancel_download', { name: v.version })
|
||||
} else {
|
||||
setDownloadProgress(prev => {
|
||||
const i = prev.findIndex(d => d.version === v.version)
|
||||
if (i === -1) return prev
|
||||
const copy = [...prev]
|
||||
copy[i] = {
|
||||
...copy[i],
|
||||
paused: false,
|
||||
downloading: true
|
||||
}
|
||||
return copy
|
||||
})
|
||||
const res = await invoke<string>('download', {
|
||||
url: v.url,
|
||||
name: v.version,
|
||||
hash: v.hash,
|
||||
downloadType: v.type,
|
||||
modId: String(v.modId ?? '')
|
||||
})
|
||||
|
||||
if (res === '1') {
|
||||
setDownloadProgress(prev =>
|
||||
prev.filter(d => d.version !== v.version)
|
||||
)
|
||||
if (v.type != 1) {
|
||||
setDownloadedVersionsConfig(prev => {
|
||||
if (!prev) return prev
|
||||
|
||||
const updated =
|
||||
v.type == 2
|
||||
? {
|
||||
...prev,
|
||||
mods: {
|
||||
...prev.mods,
|
||||
[v.modGame! + '-' + v.modId!]: {
|
||||
[v.modVersion!]: Date.now()
|
||||
}
|
||||
}
|
||||
}
|
||||
: {
|
||||
...prev,
|
||||
list: {
|
||||
...prev.list,
|
||||
[v.version]: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
writeVersionsConfig(updated)
|
||||
return updated
|
||||
})
|
||||
}
|
||||
} else if (res == '0') {
|
||||
setDownloadProgress(prev => {
|
||||
const i = prev.findIndex(d => d.version === v.version)
|
||||
if (i === -1) return prev
|
||||
if (prev[i].canceled) {
|
||||
return prev.filter((_, idx) => idx !== i)
|
||||
}
|
||||
const copy = [...prev]
|
||||
copy[i] = {
|
||||
...copy[i],
|
||||
downloading: false,
|
||||
paused: true,
|
||||
failed: false
|
||||
}
|
||||
return copy
|
||||
})
|
||||
} else if (res == '-1') {
|
||||
setDownloadProgress(prev =>
|
||||
prev.map(d =>
|
||||
d.version === v.version
|
||||
? {
|
||||
...d,
|
||||
queued: false,
|
||||
failed: true,
|
||||
progress: 0
|
||||
}
|
||||
: d
|
||||
)
|
||||
)
|
||||
if (notificationsAllowed)
|
||||
await notifyUser(
|
||||
'Download Failed',
|
||||
`The download for version ${
|
||||
getVersionInfo(v.version)?.displayName
|
||||
} has failed.`
|
||||
)
|
||||
await getCurrentWindow().requestUserAttention(
|
||||
UserAttentionType.Critical
|
||||
)
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={v.downloading ? faPause : faPlay}
|
||||
className='w-6 h-6'
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className='cursor-pointer bg-(--col5) hover:bg-(--col7) border border-(--col7) hover:border-(--col9) transition-colors w-8 h-8 flex items-center justify-center rounded-full'
|
||||
hidden={!v.downloading && !v.paused}
|
||||
onClick={async () => {
|
||||
if (!v.paused) {
|
||||
await invoke('cancel_download', { name: v.version })
|
||||
setDownloadProgress(prev => {
|
||||
const i = prev.findIndex(d => d.version === v.version)
|
||||
if (i === -1) return prev
|
||||
const copy = [...prev]
|
||||
copy[i] = {
|
||||
...copy[i],
|
||||
canceled: true
|
||||
}
|
||||
return copy
|
||||
})
|
||||
} else {
|
||||
setDownloadProgress(prev => {
|
||||
const i = prev.findIndex(d => d.version === v.version)
|
||||
if (i === -1) return prev
|
||||
return prev.filter((_, idx) => idx !== i)
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCancel} className='w-6 h-6' />
|
||||
</div>
|
||||
<div
|
||||
className='cursor-pointer bg-(--col5) hover:bg-(--col7) border border-(--col7) hover:border-(--col9) transition-colors w-8 h-8 flex items-center justify-center rounded-full'
|
||||
hidden={!v.failed && !v.queued}
|
||||
onClick={() => {
|
||||
setDownloadQueue(prev =>
|
||||
prev.filter(id => id !== v.version)
|
||||
)
|
||||
setDownloadProgress(prev =>
|
||||
prev.filter(d => d.version !== v.version)
|
||||
)
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} className='w-6 h-6' />
|
||||
</div>
|
||||
</div>
|
||||
<p className='text-2xl text-center'>
|
||||
{v.type == 1
|
||||
? 'Mod Manager for '
|
||||
: v.type == 2
|
||||
? 'Mod for '
|
||||
: null}{' '}
|
||||
{getVersionInfo(v.version)?.displayName}
|
||||
</p>
|
||||
<div className='mt-6.25 flex items-center justify-between'>
|
||||
{v.failed || v.queued ? (
|
||||
<div className='flex items-center justify-between w-full'>
|
||||
<span
|
||||
className={`${
|
||||
v.failed ? 'text-red-500' : 'text-yellow-300'
|
||||
} inline-block text-center flex-1`}
|
||||
>
|
||||
{v.failed
|
||||
? 'Download failed'
|
||||
: queuePosition === 0
|
||||
? 'Starting soon...'
|
||||
: `Queued (Position ${queuePosition + 1})`}
|
||||
</span>
|
||||
</div>
|
||||
) : v.hash_checking ? (
|
||||
<span className='text-blue-300 inline-block w-full text-center'>
|
||||
Verifying file integerty...
|
||||
</span>
|
||||
) : v.unzipping ? (
|
||||
<div className='flex flex-col gap-1 w-full'>
|
||||
<span className='text-center'>
|
||||
Unzipped {v.unzipped} / {v.unzipTotal} files
|
||||
</span>
|
||||
<ProgressBar
|
||||
progress={(v.unzipped / v.unzipTotal) * 100}
|
||||
className='w-full'
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex flex-col gap-1 w-full'>
|
||||
<span className='text-center'>
|
||||
Downloaded{' '}
|
||||
{prettyBytes(v.progressBytes, {
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 1
|
||||
})}{' '}
|
||||
of{' '}
|
||||
{prettyBytes(v.size, {
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 1
|
||||
})}{' '}
|
||||
(ETA: {formatEtaSmart(v.etaSecs)} • Speed:{' '}
|
||||
{prettyBytes(v.speed, {
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 1
|
||||
})}
|
||||
/s)
|
||||
</span>
|
||||
<ProgressBar progress={v.progress} className='w-full' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import {
|
||||
faCheck,
|
||||
faCode,
|
||||
faDownload,
|
||||
faShieldHalved,
|
||||
faWarning
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
||||
export default function GamesDownloadPopup () {
|
||||
const { serverVersionList, getVersionsAmountData, setSelectedGame } =
|
||||
useGlobal()
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className='text-xl text-center'>Select a game to download</p>
|
||||
<div className='popup-content'>
|
||||
{serverVersionList?.games
|
||||
.filter(v => {
|
||||
const data = getVersionsAmountData(v.id)
|
||||
if (!data) return false
|
||||
if (data.total > 0) return true
|
||||
})
|
||||
.map((v, i) => (
|
||||
<div key={i} className='popup-entry'>
|
||||
<p className='text-2xl'>{v.name}</p>
|
||||
<div className='flex gap-2'>
|
||||
<div
|
||||
className='entry-info-item btntheme3'
|
||||
title='The amount of versions installed of this game in installed/installable format.'
|
||||
>
|
||||
<p>
|
||||
{(() => {
|
||||
const data = getVersionsAmountData(v.id)
|
||||
if (!data) return 'N/A'
|
||||
return `${data.installed}/${data.total}`
|
||||
})()}{' '}
|
||||
versions installed
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme3'
|
||||
hidden={!v.official}
|
||||
title='This game is official.'
|
||||
>
|
||||
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
|
||||
<p>Official</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme3'
|
||||
hidden={v.official}
|
||||
title={
|
||||
v.verified
|
||||
? 'This game is verified to be safe'
|
||||
: 'This game is NOT verified to be save. Proceed with caution.'
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={v.verified ? faShieldHalved : faWarning}
|
||||
color={v.verified ? '#19c84b' : '#ffc800'}
|
||||
/>
|
||||
<p>{v.verified ? 'Verified' : 'Unverified'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme3 mt-2'
|
||||
hidden={v.developer == null}
|
||||
title={`The developer of ${v.name} is ${v.developer}.`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCode} color='lightgray' />
|
||||
<p>Developer: {v.developer}</p>
|
||||
</div>
|
||||
<button
|
||||
className='button btntheme3 right-2 bottom-2'
|
||||
onClick={() => setSelectedGame(v.id)}
|
||||
title={`Click to download specific versions of the game. You have ${(() => {
|
||||
const data = getVersionsAmountData(v.id)
|
||||
if (!data) return 'N/A'
|
||||
return `${data.installed} of ${data.total}`
|
||||
})()} versions downloaded.`}
|
||||
>
|
||||
<>
|
||||
<FontAwesomeIcon icon={faDownload} /> Download
|
||||
</>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
faCheck,
|
||||
faCode,
|
||||
faHardDrive,
|
||||
faShieldHalved,
|
||||
faWarning
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { useEffect, useState } from 'react'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import { message } from '@tauri-apps/plugin-dialog'
|
||||
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
||||
import { writeVersionsConfig } from '@/lib/BazookaManager'
|
||||
import { openFolder } from '@/lib/Util'
|
||||
|
||||
export default function ManageVersionPopup () {
|
||||
const {
|
||||
getGameInfo,
|
||||
getVersionInfo,
|
||||
managingVersion,
|
||||
downloadedVersionsConfig,
|
||||
viewingInfoFromDownloads,
|
||||
setManagingVersion,
|
||||
closePopup,
|
||||
setDownloadedVersionsConfig,
|
||||
setPopupMode,
|
||||
setSelectedVersionList,
|
||||
downloadVersions
|
||||
} = useGlobal()
|
||||
const [versionSize, setVersionSize] = useState<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (viewingInfoFromDownloads) return
|
||||
invoke<string>('folder_size', {
|
||||
version: managingVersion
|
||||
}).then(size => {
|
||||
setVersionSize(Number(size))
|
||||
})
|
||||
}, [managingVersion, setVersionSize, viewingInfoFromDownloads])
|
||||
|
||||
if (!managingVersion || !downloadedVersionsConfig) return <></>
|
||||
|
||||
const versionInfo = getVersionInfo(managingVersion)
|
||||
const gameInfo = getGameInfo(versionInfo?.game)
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className='text-xl text-center'>Viewing {versionInfo?.displayName}</p>
|
||||
<div className='popup-content flex flex-col items-center justify-center gap-2 h-full'>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={viewingInfoFromDownloads}
|
||||
>
|
||||
<p>
|
||||
Installed{' '}
|
||||
{new Intl.DateTimeFormat(undefined).format(
|
||||
downloadedVersionsConfig.list[managingVersion] ?? 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={!versionInfo || versionInfo.releaseDate == 0}
|
||||
>
|
||||
<p>
|
||||
Released{' '}
|
||||
{new Intl.DateTimeFormat(undefined).format(
|
||||
versionInfo?.releaseDate ? versionInfo.releaseDate * 1000 : 0
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className='entry-info-item btntheme2' hidden={!gameInfo?.official}>
|
||||
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
|
||||
<p>Official</p>
|
||||
</div>
|
||||
<div className='entry-info-item btntheme2' hidden={gameInfo?.official}>
|
||||
<FontAwesomeIcon
|
||||
icon={gameInfo?.verified ? faShieldHalved : faWarning}
|
||||
color={gameInfo?.verified ? '#19c84b' : '#ffc800'}
|
||||
/>
|
||||
<p>{gameInfo?.verified ? 'Verified' : 'Unverified'}</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={gameInfo?.developer == null}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCode} color='lightgray' />
|
||||
<p>Developer: {gameInfo?.developer}</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
hidden={viewingInfoFromDownloads}
|
||||
>
|
||||
<FontAwesomeIcon icon={faHardDrive} color='lightgray' />
|
||||
<p>
|
||||
Size on disk:{' '}
|
||||
{versionSize && versionSize > 0
|
||||
? prettyBytes(versionSize, {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})
|
||||
: 'N/A'}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
onClick={async () => {
|
||||
if (!versionInfo) return
|
||||
await message(atob(versionInfo.changelog), {
|
||||
title: 'Changelog for ' + versionInfo.displayName,
|
||||
kind: 'info'
|
||||
})
|
||||
}}
|
||||
hidden={!versionInfo?.changelog}
|
||||
>
|
||||
<p>View Changelog</p>
|
||||
<FontAwesomeIcon icon={faArrowUpRightFromSquare} color='lightgray' />
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
onClick={async () => openFolder(managingVersion)}
|
||||
title="Click to browse the game's files."
|
||||
hidden={viewingInfoFromDownloads}
|
||||
>
|
||||
Open Folder
|
||||
<FontAwesomeIcon icon={faArrowUpRightFromSquare} color='lightgray' />
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
onClick={async () => {
|
||||
closePopup()
|
||||
|
||||
setDownloadedVersionsConfig(prev => {
|
||||
if (!prev) return prev
|
||||
const updatedList = Object.fromEntries(
|
||||
Object.entries(prev.list).filter(([k]) => k !== managingVersion)
|
||||
)
|
||||
const updatedConfig = {
|
||||
...prev,
|
||||
list: updatedList
|
||||
}
|
||||
writeVersionsConfig(updatedConfig)
|
||||
return updatedConfig
|
||||
})
|
||||
|
||||
if (
|
||||
await exists('game/' + managingVersion, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
)
|
||||
await remove('game/' + managingVersion, {
|
||||
baseDir: BaseDirectory.AppLocalData,
|
||||
recursive: true
|
||||
})
|
||||
}}
|
||||
title='Click to uninstall this game. This will NOT remove any progress or any save files.'
|
||||
hidden={viewingInfoFromDownloads}
|
||||
>
|
||||
Uninstall
|
||||
</div>
|
||||
<div
|
||||
className='entry-info-item btntheme2'
|
||||
onClick={async () => {
|
||||
//change popup to downloads
|
||||
setManagingVersion(null)
|
||||
setPopupMode(1)
|
||||
|
||||
//uninstall
|
||||
setDownloadedVersionsConfig(prev => {
|
||||
if (!prev) return prev
|
||||
const updatedList = Object.fromEntries(
|
||||
Object.entries(prev.list).filter(([k]) => k !== managingVersion)
|
||||
)
|
||||
const updatedConfig = {
|
||||
...prev,
|
||||
list: updatedList
|
||||
}
|
||||
writeVersionsConfig(updatedConfig)
|
||||
return updatedConfig
|
||||
})
|
||||
|
||||
if (
|
||||
await exists('game/' + managingVersion, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
)
|
||||
await remove('game/' + managingVersion, {
|
||||
baseDir: BaseDirectory.AppLocalData,
|
||||
recursive: true
|
||||
})
|
||||
|
||||
//reinstall
|
||||
setSelectedVersionList([managingVersion])
|
||||
downloadVersions([
|
||||
{
|
||||
id: managingVersion,
|
||||
type: 0
|
||||
}
|
||||
])
|
||||
}}
|
||||
title="Click to reinstall this game. This will NOT remove any progress or any save files. This WILL uninstall any modifications to the game's executable files."
|
||||
hidden={viewingInfoFromDownloads}
|
||||
>
|
||||
Reinstall
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,381 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import { writeVersionsConfig } from '@/lib/BazookaManager'
|
||||
import { verifySignature } from '@/lib/Util'
|
||||
import { Mod } from '@/types/Mod'
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
faClock,
|
||||
faCodeBranch,
|
||||
faDownload,
|
||||
faGlobe,
|
||||
faPeopleGroup,
|
||||
faRefresh,
|
||||
faTrash
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { message } from '@tauri-apps/plugin-dialog'
|
||||
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
||||
import { fetch } from '@tauri-apps/plugin-http'
|
||||
import { arch, platform } from '@tauri-apps/plugin-os'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export default function ModDownloadsPopup () {
|
||||
const {
|
||||
getVersionInfo,
|
||||
managingVersion,
|
||||
downloadedVersionsConfig,
|
||||
showModInfo,
|
||||
setShowModInfo,
|
||||
downloadVersions,
|
||||
downloadProgress,
|
||||
setDownloadedVersionsConfig
|
||||
} = useGlobal()
|
||||
|
||||
const [mods, setMods] = useState<Mod[] | 0 | 1>(0)
|
||||
const [tab, setTab] = useState<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (!managingVersion) return
|
||||
;(async () => {
|
||||
const response = await fetch(
|
||||
`https://games.lncvrt.xyz/api/launcher/mods?platform=${platform()}&arch=${arch()}&version=${managingVersion}`
|
||||
)
|
||||
const signature = response.headers.get('x-signature') ?? ''
|
||||
const data = await response.json()
|
||||
if (await verifySignature(JSON.stringify(data), signature)) {
|
||||
setMods(data as Mod[])
|
||||
} else {
|
||||
setMods(1)
|
||||
}
|
||||
})()
|
||||
}, [managingVersion])
|
||||
|
||||
if (!managingVersion) return <></>
|
||||
|
||||
const versionInfo = getVersionInfo(managingVersion)
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className='popup-top-button btntheme1 right-2'
|
||||
onClick={async () => {
|
||||
setMods(0)
|
||||
const response = await fetch(
|
||||
`https://games.lncvrt.xyz/api/launcher/mods?platform=${platform()}&arch=${arch()}&version=${managingVersion}`
|
||||
)
|
||||
const signature = response.headers.get('x-signature') ?? ''
|
||||
const data = await response.json()
|
||||
if (await verifySignature(JSON.stringify(data), signature)) {
|
||||
setMods(data as Mod[])
|
||||
} else {
|
||||
setMods(1)
|
||||
}
|
||||
}}
|
||||
hidden={typeof mods == 'number' || showModInfo != null}
|
||||
>
|
||||
<FontAwesomeIcon icon={faRefresh} />
|
||||
</button>
|
||||
<p className='text-xl text-center'>
|
||||
{!showModInfo
|
||||
? versionInfo?.displayName + ' Mod Manager'
|
||||
: showModInfo.name +
|
||||
' by ' +
|
||||
showModInfo.creators[0] +
|
||||
(showModInfo.creators.length > 1
|
||||
? ' + ' + (showModInfo.creators.length - 1) + ' more'
|
||||
: '')}
|
||||
</p>
|
||||
{typeof mods != 'number' && !showModInfo ? (
|
||||
<div className='flex flex-row justify-center items-center -mb-2 mt-0.5 gap-1.5'>
|
||||
<button
|
||||
className={`button ${tab == 0 ? 'btntheme3' : 'btntheme2'}`}
|
||||
onClick={() => setTab(0)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faDownload} />
|
||||
Installed
|
||||
</button>
|
||||
<button
|
||||
className={`button ${tab == 1 ? 'btntheme3' : 'btntheme2'}`}
|
||||
onClick={() => setTab(1)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faGlobe} />
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<div className='popup-content h-full'>
|
||||
{typeof mods == 'number' ||
|
||||
(!showModInfo &&
|
||||
mods.filter(v =>
|
||||
tab == 0
|
||||
? Object.keys(downloadedVersionsConfig?.mods ?? []).includes(
|
||||
String(versionInfo?.game + '-' + v.id)
|
||||
)
|
||||
: !Object.keys(downloadedVersionsConfig?.mods ?? []).includes(
|
||||
String(versionInfo?.game + '-' + v.id)
|
||||
)
|
||||
).length == 0) ? (
|
||||
<p className='text-2xl flex justify-center items-center h-full'>
|
||||
{typeof mods == 'number'
|
||||
? mods == 0
|
||||
? 'Loading'
|
||||
: 'Failed to load mods'
|
||||
: tab == 0
|
||||
? 'No mods installed'
|
||||
: 'No mods available to download'}
|
||||
</p>
|
||||
) : !showModInfo ? (
|
||||
<div className='flex flex-col items-center justify-center gap-2 p-2'>
|
||||
{mods
|
||||
.filter(v =>
|
||||
tab == 0
|
||||
? Object.keys(downloadedVersionsConfig?.mods ?? []).includes(
|
||||
String(versionInfo?.game + '-' + v.id)
|
||||
)
|
||||
: !Object.keys(downloadedVersionsConfig?.mods ?? []).includes(
|
||||
String(versionInfo?.game + '-' + v.id)
|
||||
)
|
||||
)
|
||||
.map(v => {
|
||||
const localVersion =
|
||||
tab == 0
|
||||
? Object.keys(
|
||||
downloadedVersionsConfig?.mods?.[
|
||||
versionInfo?.game + '-' + v.id
|
||||
] ?? {}
|
||||
)[0]
|
||||
: null
|
||||
return (
|
||||
<div
|
||||
key={v.id}
|
||||
className='bg-(--col3) border border-(--col5) rounded-lg w-full h-16 flex flex-row'
|
||||
>
|
||||
<div className='flex flex-col justify-center h-full px-3 w-fit'>
|
||||
<p>
|
||||
<span className='text-lg'>{v.name}</span>{' '}
|
||||
<span
|
||||
className={
|
||||
tab != 0 || v.latestVersion == localVersion
|
||||
? 'text-green-300'
|
||||
: 'text-red-300'
|
||||
}
|
||||
>
|
||||
v{tab == 0 ? localVersion : v.latestVersion}
|
||||
</span>
|
||||
<span
|
||||
className='text-green-300'
|
||||
hidden={tab != 0 || v.latestVersion == localVersion}
|
||||
>
|
||||
{' '}
|
||||
(available: v{v.latestVersion})
|
||||
</span>
|
||||
</p>
|
||||
<p className='text-yellow-200'>
|
||||
Made by {v.creators[0]}
|
||||
{v.creators.length > 1
|
||||
? ' + ' + (v.creators.length - 1) + ' more'
|
||||
: null}
|
||||
</p>
|
||||
</div>
|
||||
<div className='flex flex-row items-center h-full gap-2 px-3 ml-auto'>
|
||||
<p className='text-green-400' hidden={tab == 0}>
|
||||
<FontAwesomeIcon icon={faDownload} />{' '}
|
||||
{v.downloads.toLocaleString()}
|
||||
</p>
|
||||
<button
|
||||
className='button btntheme3'
|
||||
onClick={async () => {
|
||||
const path =
|
||||
'game/' +
|
||||
versionInfo?.id +
|
||||
'/BepInEx/plugins/' +
|
||||
v.id
|
||||
if (
|
||||
await exists(path, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
)
|
||||
await remove(path, {
|
||||
baseDir: BaseDirectory.AppLocalData,
|
||||
recursive: true
|
||||
})
|
||||
setDownloadedVersionsConfig(prev => {
|
||||
if (!prev) return prev
|
||||
const updatedMods = Object.fromEntries(
|
||||
Object.entries(prev.mods).filter(
|
||||
([k]) => k !== versionInfo?.game + '-' + v.id
|
||||
)
|
||||
)
|
||||
const updatedConfig = {
|
||||
...prev,
|
||||
mods: updatedMods
|
||||
}
|
||||
writeVersionsConfig(updatedConfig)
|
||||
return updatedConfig
|
||||
})
|
||||
|
||||
downloadVersions([
|
||||
{
|
||||
id: managingVersion,
|
||||
type: 2,
|
||||
modDownload: v.latestDownload,
|
||||
gameId: versionInfo?.game,
|
||||
modId: v.id,
|
||||
modVersion: v.latestVersion
|
||||
}
|
||||
])
|
||||
}}
|
||||
hidden={tab != 0 || v.latestVersion == localVersion}
|
||||
disabled={downloadProgress.length != 0}
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme3'
|
||||
onClick={() => setShowModInfo(v)}
|
||||
>
|
||||
{tab == 0 ? 'View' : 'Get'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex flex-col h-full w-full items-center'>
|
||||
<div className='flex flex-col p-2 h-fit w-fit bg-(--col3) border border-(--col5) rounded-lg m-2 items-center'>
|
||||
<p>
|
||||
<FontAwesomeIcon icon={faDownload} className='text-green-400' />{' '}
|
||||
Downloads {showModInfo.downloads.toLocaleString()}
|
||||
</p>
|
||||
<p>
|
||||
<FontAwesomeIcon icon={faClock} /> Released{' '}
|
||||
{new Intl.DateTimeFormat(undefined).format(
|
||||
showModInfo.released * 1000
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<FontAwesomeIcon icon={faClock} /> Updated{' '}
|
||||
{new Intl.DateTimeFormat(undefined).format(
|
||||
showModInfo.updated * 1000
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<FontAwesomeIcon icon={faCodeBranch} /> Version{' '}
|
||||
{showModInfo.latestVersion}
|
||||
</p>
|
||||
</div>
|
||||
<div className='flex flex-col p-2 bg-(--col3) border border-(--col5) rounded-lg h-full w-[calc(100%-16px)]'>
|
||||
{showModInfo.description
|
||||
? atob(showModInfo.description)
|
||||
: '(No description added)'}
|
||||
</div>
|
||||
<div className='flex flex-row h-fit w-full gap-2 justify-center my-2'>
|
||||
{Object.keys(downloadedVersionsConfig?.mods ?? []).includes(
|
||||
String(versionInfo?.game + '-' + showModInfo.id)
|
||||
) ? (
|
||||
<button
|
||||
className='button btntheme2 w-fit'
|
||||
disabled={downloadProgress.length != 0}
|
||||
onClick={async () => {
|
||||
const path =
|
||||
'game/' +
|
||||
versionInfo?.id +
|
||||
'/BepInEx/plugins/' +
|
||||
showModInfo.id
|
||||
if (
|
||||
await exists(path, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
)
|
||||
await remove(path, {
|
||||
baseDir: BaseDirectory.AppLocalData,
|
||||
recursive: true
|
||||
})
|
||||
setDownloadedVersionsConfig(prev => {
|
||||
if (!prev) return prev
|
||||
const updatedMods = Object.fromEntries(
|
||||
Object.entries(prev.mods).filter(
|
||||
([k]) =>
|
||||
k !== versionInfo?.game + '-' + showModInfo.id
|
||||
)
|
||||
)
|
||||
const updatedConfig = {
|
||||
...prev,
|
||||
mods: updatedMods
|
||||
}
|
||||
writeVersionsConfig(updatedConfig)
|
||||
return updatedConfig
|
||||
})
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} className='text-red-400' />{' '}
|
||||
Uninstall
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className='button btntheme2 w-fit'
|
||||
onClick={() => {
|
||||
downloadVersions([
|
||||
{
|
||||
id: managingVersion,
|
||||
type: 2,
|
||||
modDownload: showModInfo.latestDownload,
|
||||
gameId: versionInfo?.game,
|
||||
modId: showModInfo.id,
|
||||
modVersion: showModInfo.latestVersion
|
||||
}
|
||||
])
|
||||
}}
|
||||
disabled={downloadProgress.length != 0}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faDownload}
|
||||
className='text-green-400'
|
||||
/>{' '}
|
||||
Install
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className='button btntheme2 w-fit'
|
||||
onClick={async () => {
|
||||
await message(
|
||||
!showModInfo.changelog
|
||||
? '(No changelog provided)'
|
||||
: atob(showModInfo?.changelog ?? ''),
|
||||
{
|
||||
title: 'Changelog for ' + showModInfo.name,
|
||||
kind: 'info'
|
||||
}
|
||||
)
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
color='lightgray'
|
||||
/>{' '}
|
||||
View changelog
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme2 w-fit'
|
||||
hidden={showModInfo.creators.length < 2}
|
||||
onClick={async () => {
|
||||
if (!versionInfo) return
|
||||
await message(showModInfo.creators.join(', '), {
|
||||
title: 'Creators for ' + showModInfo.name,
|
||||
kind: 'info'
|
||||
})
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPeopleGroup} color='lightgray' /> View
|
||||
all creators
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import { faAdd, faInfo, faRemove } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
||||
export default function VersionsDownloadPopup () {
|
||||
const {
|
||||
selectedVersionList,
|
||||
setSelectedVersionList,
|
||||
setManagingVersion,
|
||||
setPopupMode,
|
||||
getSpecialVersionsList,
|
||||
selectedGame,
|
||||
setViewingInfoFromDownloads,
|
||||
downloadedVersionsConfig,
|
||||
downloadVersions,
|
||||
getGameInfo
|
||||
} = useGlobal()
|
||||
|
||||
if (!selectedGame) return <></>
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className='text-xl text-center'>Select versions to download</p>
|
||||
<div className='popup-content'>
|
||||
{getSpecialVersionsList(selectedGame).map((v, i) => (
|
||||
<div key={i} className='popup-entry'>
|
||||
<div className='flex items-center'>
|
||||
<p
|
||||
className={`text-2xl truncate ${
|
||||
selectedVersionList.includes(v.id)
|
||||
? 'max-w-84.5'
|
||||
: 'max-w-91.5'
|
||||
}`}
|
||||
>
|
||||
{v.displayName}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
className='button btntheme3 right-20.75 bottom-1.75'
|
||||
onClick={() => {
|
||||
setSelectedVersionList(prev =>
|
||||
prev.includes(v.id)
|
||||
? prev.filter(i => i !== v.id)
|
||||
: [...prev, v.id]
|
||||
)
|
||||
}}
|
||||
title={
|
||||
selectedVersionList.includes(v.id)
|
||||
? 'This version will be downloaded. Click to remove from the list of versions that will be downloaded.'
|
||||
: 'This version will NOT be downloaded. Click to add from the list of versions that will be downloaded.'
|
||||
}
|
||||
>
|
||||
{selectedVersionList.includes(v.id) ? (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faRemove} /> Remove
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faAdd} /> Add
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme3 right-1.5 bottom-1.75'
|
||||
onClick={() => {
|
||||
setManagingVersion(v.id)
|
||||
setViewingInfoFromDownloads(true)
|
||||
setPopupMode(2)
|
||||
}}
|
||||
title='Click to view version info'
|
||||
>
|
||||
<FontAwesomeIcon icon={faInfo} /> Info
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className='flex justify-center'>
|
||||
<button
|
||||
className='button btntheme1 w-fit mt-2 -mb-4'
|
||||
onClick={() => {
|
||||
if (downloadedVersionsConfig) {
|
||||
downloadVersions(
|
||||
selectedVersionList.map(versionId => ({
|
||||
id: versionId,
|
||||
type: 0
|
||||
}))
|
||||
)
|
||||
setPopupMode(1)
|
||||
}
|
||||
}}
|
||||
disabled={selectedVersionList.length == 0}
|
||||
title={
|
||||
selectedVersionList.length == 0
|
||||
? 'Select at least one version to download'
|
||||
: `Download ${selectedVersionList.length} version${
|
||||
selectedVersionList.length == 1 ? '' : 's'
|
||||
} of ${getGameInfo(selectedGame)?.name}`
|
||||
}
|
||||
>
|
||||
Download {selectedVersionList.length} version
|
||||
{selectedVersionList.length == 1 ? '' : 's'}
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme1 w-fit mt-2 ml-2 -mb-4'
|
||||
onClick={() => {
|
||||
const allIds = getSpecialVersionsList(selectedGame).map(v => v.id)
|
||||
setSelectedVersionList(prev =>
|
||||
prev.length === allIds.length ? [] : allIds
|
||||
)
|
||||
}}
|
||||
title={
|
||||
selectedVersionList.length ===
|
||||
getSpecialVersionsList(selectedGame).length
|
||||
? 'Click to remove all selected versions for download.'
|
||||
: 'Click to add all selected versions for download.'
|
||||
}
|
||||
>
|
||||
{selectedVersionList.length ===
|
||||
getSpecialVersionsList(selectedGame).length
|
||||
? 'Deselect All'
|
||||
: 'Select All'}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user