Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39da88a856
|
||
|
|
29c90d2f06
|
||
|
|
467dd1b0a1
|
||
|
|
a1a69c9ac5
|
||
|
|
0163e09b02
|
||
|
|
fbb4b15971
|
||
|
|
77933a2298
|
||
|
|
7209a36752
|
||
|
|
f00b78996a
|
||
|
|
38779c1715
|
||
|
|
fa67d26fe9
|
||
|
|
81d47ea71d
|
||
|
|
70433b76ab
|
||
|
|
105913503c
|
||
|
|
2ad54f976b
|
||
|
|
d8d59febbb
|
||
|
|
a618c76292
|
||
|
|
118fad49d8
|
No files matched your search
@@ -29,7 +29,7 @@
|
||||
"@tauri-apps/cli": "2.10.0",
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/node": "25.2.3",
|
||||
"@types/react": "19.2.13",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"crypto-js": "4.2.0",
|
||||
"eslint": "9.39.2",
|
||||
@@ -292,7 +292,7 @@
|
||||
|
||||
"@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.13", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ=="],
|
||||
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "lncvrt-games-launcher",
|
||||
"private": true,
|
||||
"version": "1.5.8",
|
||||
"version": "1.6.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -35,7 +35,7 @@
|
||||
"@tauri-apps/cli": "2.10.0",
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/node": "25.2.3",
|
||||
"@types/react": "19.2.13",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"crypto-js": "4.2.0",
|
||||
"eslint": "9.39.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lncvrt-games-launcher"
|
||||
version = "1.5.8"
|
||||
version = "1.6.0"
|
||||
authors = ["Lncvrt"]
|
||||
edition = "2024"
|
||||
|
||||
@@ -23,7 +23,7 @@ futures-util = { version = "0.3.31", features = ["io"] }
|
||||
tauri-plugin-decorum = "1.1.1"
|
||||
tauri-plugin-fs = "2.4.5"
|
||||
zip = "7.4.0"
|
||||
libc = "0.2.181"
|
||||
libc = "0.2.182"
|
||||
tauri-plugin-dialog = "2.6.0"
|
||||
tauri-plugin-notification = "2.3.3"
|
||||
sysinfo = "0.38.1"
|
||||
|
||||
@@ -32,6 +32,12 @@
|
||||
"allow": [
|
||||
{ "path": "$APPLOCALDATA/game/**" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "opener:allow-open-path",
|
||||
"allow": [
|
||||
{ "path": "$APPLOCALDATA/game/**" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+3
-27
@@ -12,8 +12,6 @@ use std::{
|
||||
};
|
||||
use sysinfo::System;
|
||||
use tauri::{AppHandle, Emitter, Manager};
|
||||
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
use tauri_plugin_os::platform;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::{io::AsyncWriteExt, time::timeout};
|
||||
@@ -280,6 +278,9 @@ fn launch_game(
|
||||
//if already running on macos, it'll auto take the user to that proccess
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
{
|
||||
use tauri_plugin_dialog::MessageDialogKind;
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
if !use_wine && is_running_by_path(&exe_path) {
|
||||
app.dialog()
|
||||
.message(format!(
|
||||
@@ -324,30 +325,6 @@ fn launch_game(
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn open_folder(app: AppHandle, name: String) {
|
||||
let game_path = app
|
||||
.path()
|
||||
.app_local_data_dir()
|
||||
.unwrap()
|
||||
.join("game")
|
||||
.join(&name);
|
||||
if game_path.exists() {
|
||||
app.opener()
|
||||
.open_path(game_path.to_string_lossy(), None::<&str>)
|
||||
.unwrap();
|
||||
} else {
|
||||
app.dialog()
|
||||
.message(format!(
|
||||
"Game folder \"{}\" not found.",
|
||||
game_path.display()
|
||||
))
|
||||
.kind(MessageDialogKind::Error)
|
||||
.title("Folder not found")
|
||||
.show(|_| {});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
#[allow(unused_variables)]
|
||||
@@ -369,7 +346,6 @@ pub fn run() {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
download,
|
||||
launch_game,
|
||||
open_folder,
|
||||
folder_size
|
||||
])
|
||||
.setup(|app| {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Lncvrt Games Launcher",
|
||||
"version": "1.5.8",
|
||||
"version": "1.6.0",
|
||||
"identifier": "xyz.lncvrt.games-launcher",
|
||||
"build": {
|
||||
"beforeDevCommand": "next dev",
|
||||
"beforeDevCommand": "next dev --webpack",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeBuildCommand": "next build",
|
||||
"frontendDist": "../out"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Lncvrt Games Launcher",
|
||||
"version": "1.5.8",
|
||||
"version": "1.6.0",
|
||||
"identifier": "xyz.lncvrt.games-launcher",
|
||||
"build": {
|
||||
"beforeDevCommand": "next dev",
|
||||
"beforeDevCommand": "next dev --webpack",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeBuildCommand": "next build",
|
||||
"frontendDist": "../out"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Lncvrt Games Launcher",
|
||||
"version": "1.5.8",
|
||||
"version": "1.6.0",
|
||||
"identifier": "xyz.lncvrt.games-launcher",
|
||||
"build": {
|
||||
"beforeDevCommand": "next dev",
|
||||
"beforeDevCommand": "next dev --webpack",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeBuildCommand": "next build",
|
||||
"frontendDist": "../out"
|
||||
@@ -30,7 +30,7 @@
|
||||
"active": true,
|
||||
"targets": ["app"],
|
||||
"macOS": {
|
||||
"minimumSystemVersion": "15.0"
|
||||
"minimumSystemVersion": "13.0"
|
||||
},
|
||||
"icon": ["icons/icon.icns"]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Lncvrt Games Launcher",
|
||||
"version": "1.5.8",
|
||||
"version": "1.6.0",
|
||||
"identifier": "xyz.lncvrt.games-launcher",
|
||||
"build": {
|
||||
"beforeDevCommand": "next dev",
|
||||
"beforeDevCommand": "next dev --webpack",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeBuildCommand": "next build",
|
||||
"frontendDist": "../out"
|
||||
|
||||
@@ -7,12 +7,12 @@ import {
|
||||
Dispatch,
|
||||
SetStateAction
|
||||
} from 'react'
|
||||
import { DownloadProgress } from './types/DownloadProgress'
|
||||
import { VersionsConfig } from './types/VersionsConfig'
|
||||
import { NormalConfig } from './types/NormalConfig'
|
||||
import { ServerVersionsResponse } from './types/ServerVersionsResponse'
|
||||
import { GameVersion } from './types/GameVersion'
|
||||
import { Game } from './types/Game'
|
||||
import { DownloadProgress } from '@/types/DownloadProgress'
|
||||
import { VersionsConfig } from '@/types/VersionsConfig'
|
||||
import { NormalConfig } from '@/types/NormalConfig'
|
||||
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
|
||||
import { GameVersion } from '@/types/GameVersion'
|
||||
import { Game } from '@/types/Game'
|
||||
|
||||
type GlobalCtxType = {
|
||||
serverVersionList: ServerVersionsResponse | null
|
||||
@@ -45,6 +45,12 @@ type GlobalCtxType = {
|
||||
downloadVersions: (list: 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>>
|
||||
}
|
||||
|
||||
const GlobalCtx = createContext<GlobalCtxType | null>(null)
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
}
|
||||
|
||||
.downloads-entry {
|
||||
@apply flex justify-between items-center m-2 p-2 rounded-lg text-gray-200 text-lg transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5);
|
||||
@apply flex justify-between items-center m-2 p-2 rounded-lg text-gray-200 text-lg transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5) cursor-pointer;
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { BirdColor } from '@/types/BerryDash/BirdColor'
|
||||
import axios from 'axios'
|
||||
import { GetIconForUser } from '@/lib/BerryDash'
|
||||
import Image from 'next/image'
|
||||
import './styles.css'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
interface BaseEntry {
|
||||
id: number
|
||||
username: string
|
||||
value: number
|
||||
icon: number
|
||||
overlay: number
|
||||
birdColor: BirdColor
|
||||
overlayColor: BirdColor
|
||||
customIcon: string | null
|
||||
}
|
||||
|
||||
interface Stats {
|
||||
highScore: string
|
||||
totalNormalBerries: string
|
||||
totalPoisonBerries: string
|
||||
totalSlowBerries: string
|
||||
totalUltraBerries: string
|
||||
totalSpeedyBerries: string
|
||||
totalCoinBerries: string
|
||||
totalRandomBerries: string
|
||||
totalAntiBerries: string
|
||||
totalGoldenBerries: string
|
||||
coins: string
|
||||
}
|
||||
|
||||
interface LeaderboardEntry extends BaseEntry {
|
||||
type: 'leaderboard'
|
||||
value: number
|
||||
}
|
||||
|
||||
interface Account extends BaseEntry {
|
||||
type: 'account'
|
||||
stats: Stats
|
||||
xp: bigint
|
||||
}
|
||||
|
||||
export function calculateXP (
|
||||
normalBerries: bigint,
|
||||
poisonBerries: bigint,
|
||||
slowBerries: bigint,
|
||||
ultraBerries: bigint,
|
||||
speedyBerries: bigint,
|
||||
coinBerries: bigint,
|
||||
randomBerries: bigint,
|
||||
antiBerries: bigint,
|
||||
goldenBerries: bigint
|
||||
): bigint {
|
||||
let totalXp = 0n
|
||||
totalXp += normalBerries
|
||||
totalXp -= poisonBerries
|
||||
totalXp -= slowBerries
|
||||
totalXp += ultraBerries * 5n
|
||||
totalXp += speedyBerries * 10n
|
||||
totalXp += coinBerries * 10n
|
||||
totalXp += randomBerries
|
||||
totalXp -= antiBerries
|
||||
totalXp += goldenBerries * 4n
|
||||
|
||||
if (totalXp < 0n) totalXp = 0n
|
||||
return totalXp
|
||||
}
|
||||
|
||||
export function calculateLevel (xp: bigint): number {
|
||||
const levelDivisor = 50.0
|
||||
|
||||
const xpNumber = Number(xp)
|
||||
const discriminant = 95 * 95 + levelDivisor * 2 * xpNumber
|
||||
const level = (-95 + Math.sqrt(discriminant)) / levelDivisor
|
||||
return Math.floor(level) + 1
|
||||
}
|
||||
|
||||
export default function BerryDashLeaderboards () {
|
||||
const [selected, setSelected] = useState<number>(-1)
|
||||
const [selectedBerryOption, setSelectedBerryOption] = useState<number>(0)
|
||||
const [entries, setEntries] = useState<(LeaderboardEntry | Account)[]>([])
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const Refresh = useCallback(async () => {
|
||||
try {
|
||||
if (selected == 3 || selected == 4) {
|
||||
const result = await axios.get(
|
||||
'https://games.lncvrt.xyz/api/berrydash/account?username='
|
||||
)
|
||||
if (result.data.success) {
|
||||
let accounts = result.data.data as Account[]
|
||||
|
||||
accounts = accounts.map(acc => {
|
||||
const xp = calculateXP(
|
||||
BigInt(acc.stats.totalNormalBerries),
|
||||
BigInt(acc.stats.totalPoisonBerries),
|
||||
BigInt(acc.stats.totalSlowBerries),
|
||||
BigInt(acc.stats.totalUltraBerries),
|
||||
BigInt(acc.stats.totalSpeedyBerries),
|
||||
BigInt(acc.stats.totalCoinBerries),
|
||||
BigInt(acc.stats.totalRandomBerries),
|
||||
BigInt(acc.stats.totalAntiBerries),
|
||||
BigInt(acc.stats.totalGoldenBerries)
|
||||
)
|
||||
return { ...acc, xp }
|
||||
})
|
||||
|
||||
accounts.sort((a, b) => (b.xp > a.xp ? 1 : b.xp < a.xp ? -1 : 0))
|
||||
|
||||
setEntries(accounts)
|
||||
}
|
||||
} else {
|
||||
const result = await axios.get(
|
||||
'https://games.lncvrt.xyz/api/berrydash/leaderboard/' +
|
||||
(selected == 0
|
||||
? 'score'
|
||||
: selected == 1
|
||||
? 'berry?berry=' + selectedBerryOption
|
||||
: selected == 2
|
||||
? 'coin'
|
||||
: selected == 5
|
||||
? 'legacy'
|
||||
: 'total')
|
||||
)
|
||||
if (result.data.success) {
|
||||
setEntries(result.data.data as LeaderboardEntry[])
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
setEntries([])
|
||||
}
|
||||
}, [selected, selectedBerryOption])
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'Lncvrt Games - Berry Dash Leaderboards'
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (selected != -1) setTimeout(() => Refresh(), 0)
|
||||
}, [selected, Refresh])
|
||||
|
||||
return (
|
||||
<div className='mx-4 mt-4'>
|
||||
<div className='flex justify-between items-center mb-4'>
|
||||
<p className='text-3xl'>Berry Dash Leaderboards</p>
|
||||
<div className='flex gap-2'>
|
||||
<button
|
||||
className='button btntheme1'
|
||||
onClick={() => {
|
||||
setEntries([])
|
||||
Refresh()
|
||||
}}
|
||||
title='Click to refresh the leaderboards.'
|
||||
hidden={selected == -1}
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme1'
|
||||
onClick={() => {
|
||||
if (selected == -1) router.push('/game?id=1')
|
||||
else {
|
||||
setSelected(-1)
|
||||
setEntries([])
|
||||
}
|
||||
}}
|
||||
title='Click to go up a level.'
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='box'>
|
||||
{selected == -1 ? (
|
||||
<>
|
||||
<p className='text-center mt-4 text-xl'>Select a Leaderboard</p>
|
||||
<div className='flex flex-col gap-2 mt-4 items-center justify-center'>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(0)}
|
||||
>
|
||||
Score Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(1)}
|
||||
>
|
||||
Berry Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(2)}
|
||||
>
|
||||
Coins Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(3)}
|
||||
>
|
||||
Level Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(4)}
|
||||
>
|
||||
Total XP Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(5)}
|
||||
>
|
||||
Legacy Leaderboard
|
||||
</button>
|
||||
<button
|
||||
className='leaderboard-button'
|
||||
onClick={() => setSelected(6)}
|
||||
>
|
||||
Total Berries Leaderboard
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className={`flex flex-col gap-2 overflow-y-auto ${
|
||||
selected == 1 ? 'h-[calc(100vh-128px)]' : 'h-[calc(100vh-96px)]'
|
||||
} px-1`}
|
||||
>
|
||||
{entries.map((item, index) => {
|
||||
const isAccount = 'stats' in item
|
||||
const isLeaderboard = 'value' in item && !isAccount
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className='leaderboard-entry flex justify-between items-center'
|
||||
>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Image
|
||||
src={
|
||||
!item.customIcon
|
||||
? `https://games-r2.lncvrt.xyz/game-assets/berrydash/icons/bird_${
|
||||
item.icon === 1
|
||||
? GetIconForUser(item.id)
|
||||
: item.icon
|
||||
}.png`
|
||||
: `https://games.lncvrt.xyz/api/berrydash/icon-marketplace/icon?id=${item.customIcon}&raw=true`
|
||||
}
|
||||
className='pointer-events-none'
|
||||
width={48}
|
||||
height={48}
|
||||
alt=''
|
||||
unoptimized
|
||||
/>
|
||||
<p>
|
||||
{item.username} (#{index + 1})
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isLeaderboard ? (
|
||||
<p>
|
||||
{selected === 1 || selected === 6
|
||||
? 'Berries'
|
||||
: selected === 2
|
||||
? 'Coins'
|
||||
: 'Score'}
|
||||
: {item.value.toLocaleString('en-US')}
|
||||
</p>
|
||||
) : isAccount ? (
|
||||
<p>
|
||||
{selected === 3 ? 'Level' : 'XP'}:{' '}
|
||||
{selected === 3
|
||||
? calculateLevel(item.xp).toLocaleString('en-US')
|
||||
: item.xp.toLocaleString('en-US')}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{selected == 1 && (
|
||||
<div className='flex justify-center'>
|
||||
<select
|
||||
value={selectedBerryOption}
|
||||
onChange={e => setSelectedBerryOption(Number(e.target.value))}
|
||||
className='leaderboard-select mt-2'
|
||||
>
|
||||
<option value='0'>Normal Berry</option>
|
||||
<option value='1'>Poison Berry</option>
|
||||
<option value='2'>Slow Berry</option>
|
||||
<option value='3'>Ultra Berry</option>
|
||||
<option value='4'>Speedy Berry</option>
|
||||
<option value='5'>Coin Berry</option>
|
||||
<option value='6'>Random Berry</option>
|
||||
<option value='7'>Anti Berry</option>
|
||||
<option value='8'>Golden Berry</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
.box {
|
||||
@apply bg-(--col1) border border-(--col3) rounded-lg w-auto p-1 h-[calc(100vh-84px)];
|
||||
}
|
||||
|
||||
.leaderboard-button,
|
||||
.leaderboard-select {
|
||||
@apply bg-(--col2) hover:bg-(--col4) border border-(--col4) hover:border-(--col6) rounded-lg px-4 py-2 inline-block transition-all duration-200;
|
||||
}
|
||||
|
||||
.leaderboard-entry {
|
||||
@apply rounded-lg text-gray-200 text-lg px-1 py-0.5 transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5) cursor-pointer;
|
||||
}
|
||||
+40
-120
@@ -1,16 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import '../Installs.css'
|
||||
import '@/app/Installs.css'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { useGlobal } from '../GlobalProvider'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { useGlobal } from '@/app/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 { ask, message } from '@tauri-apps/plugin-dialog'
|
||||
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
||||
import { writeVersionsConfig } from '../util/BazookaManager'
|
||||
import { writeVersionsConfig } from '@/lib/BazookaManager'
|
||||
import { openFolder } from '@/lib/Util'
|
||||
|
||||
export default function Installs () {
|
||||
const {
|
||||
@@ -20,7 +21,6 @@ export default function Installs () {
|
||||
setFadeOut,
|
||||
setSelectedVersionList,
|
||||
downloadedVersionsConfig,
|
||||
downloadProgress,
|
||||
normalConfig,
|
||||
setManagingVersion,
|
||||
getVersionInfo,
|
||||
@@ -34,6 +34,7 @@ export default function Installs () {
|
||||
} = useGlobal()
|
||||
|
||||
const params = useSearchParams()
|
||||
const router = useRouter()
|
||||
|
||||
const id = Number(params.get('id') || 0)
|
||||
const game = serverVersionList?.games.find(g => g.id === id)
|
||||
@@ -63,8 +64,28 @@ export default function Installs () {
|
||||
return (
|
||||
<div className='mx-4 mt-4'>
|
||||
<div className='flex justify-between items-center mb-4'>
|
||||
<p className='text-3xl'>Installs</p>
|
||||
<p
|
||||
className={`text-3xl truncate ${
|
||||
category != -1
|
||||
? 'w-[calc(100vw-495px)]'
|
||||
: game.id == 1
|
||||
? 'w-[calc(100vw-560px)]'
|
||||
: 'w-[calc(100vw-440px)]'
|
||||
}`}
|
||||
>
|
||||
{game.name} Installs
|
||||
</p>
|
||||
<div className='flex gap-2'>
|
||||
<button
|
||||
className='button btntheme1'
|
||||
onClick={() => {
|
||||
router.push('/game/berrydash/leaderboards')
|
||||
}}
|
||||
title='View the leaderboards for this game.'
|
||||
hidden={game.id != 1}
|
||||
>
|
||||
Leaderboards
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme1'
|
||||
onClick={() => {
|
||||
@@ -123,21 +144,9 @@ export default function Installs () {
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={`downloads-entry ${
|
||||
normalConfig?.settings.useLegacyInteractButtons
|
||||
? ''
|
||||
: 'cursor-pointer'
|
||||
}`}
|
||||
title={
|
||||
normalConfig?.settings.useLegacyInteractButtons
|
||||
? ''
|
||||
: 'Click to view category'
|
||||
}
|
||||
onClick={() => {
|
||||
if (normalConfig?.settings.useLegacyInteractButtons)
|
||||
return
|
||||
setCategory(Number(key))
|
||||
}}
|
||||
className={'downloads-entry'}
|
||||
title={'Click to view category'}
|
||||
onClick={() => setCategory(Number(key))}
|
||||
>
|
||||
<div className='h-18 w-screen relative'>
|
||||
<p className='text-2xl'>{value}</p>
|
||||
@@ -171,17 +180,6 @@ export default function Installs () {
|
||||
})()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className='button absolute right-0 bottom-0'
|
||||
hidden={
|
||||
!normalConfig?.settings.useLegacyInteractButtons
|
||||
}
|
||||
title='Click to view category'
|
||||
onClick={() => setCategory(Number(key))}
|
||||
>
|
||||
Installs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -217,33 +215,23 @@ export default function Installs () {
|
||||
.map(entry => (
|
||||
<div
|
||||
key={entry}
|
||||
className={`downloads-entry ${
|
||||
normalConfig?.settings.useLegacyInteractButtons ||
|
||||
needsRevisionUpdate(
|
||||
getVersionInfo(entry)?.lastRevision,
|
||||
entry
|
||||
)
|
||||
? ''
|
||||
: 'cursor-pointer'
|
||||
}`}
|
||||
className={'downloads-entry'}
|
||||
title={
|
||||
normalConfig?.settings.useLegacyInteractButtons ||
|
||||
needsRevisionUpdate(
|
||||
getVersionInfo(entry)?.lastRevision,
|
||||
entry
|
||||
)
|
||||
? ''
|
||||
: 'Click to launch game. Right-click to manage this version install'
|
||||
'Click to launch game. Right-click to manage this version install'
|
||||
}
|
||||
onClick={async () => {
|
||||
if (
|
||||
normalConfig?.settings.useLegacyInteractButtons ||
|
||||
needsRevisionUpdate(
|
||||
getVersionInfo(entry)?.lastRevision,
|
||||
entry
|
||||
)
|
||||
) {
|
||||
await message(
|
||||
"Can't launch game. Need revision update.",
|
||||
{ title: 'Error launching game', kind: 'error' }
|
||||
)
|
||||
return
|
||||
}
|
||||
const verInfo = getVersionInfo(entry)
|
||||
if (verInfo == undefined) return
|
||||
const gameInfo = getGameInfo(verInfo.game)
|
||||
@@ -262,15 +250,6 @@ export default function Installs () {
|
||||
}}
|
||||
onContextMenu={e => {
|
||||
e.preventDefault()
|
||||
if (
|
||||
normalConfig?.settings.useLegacyInteractButtons ||
|
||||
needsRevisionUpdate(
|
||||
getVersionInfo(entry)?.lastRevision,
|
||||
entry
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
setManagingVersion(entry)
|
||||
setPopupMode(2)
|
||||
setShowPopup(true)
|
||||
@@ -346,58 +325,6 @@ export default function Installs () {
|
||||
>
|
||||
View Info
|
||||
</button>
|
||||
<button
|
||||
className='button'
|
||||
hidden={
|
||||
!normalConfig?.settings.useLegacyInteractButtons ||
|
||||
needsRevisionUpdate(
|
||||
getVersionInfo(entry)?.lastRevision,
|
||||
entry
|
||||
)
|
||||
}
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
setManagingVersion(entry)
|
||||
setPopupMode(2)
|
||||
setShowPopup(true)
|
||||
setFadeOut(false)
|
||||
}}
|
||||
title='Click to manage this version install'
|
||||
>
|
||||
Manage
|
||||
</button>
|
||||
<button
|
||||
className='button'
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
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,
|
||||
useWine: !!(
|
||||
platform() === 'linux' &&
|
||||
verInfo.wine &&
|
||||
normalConfig?.settings.useWineOnUnixWhenNeeded
|
||||
),
|
||||
wineCommand:
|
||||
normalConfig?.settings.wineOnUnixCommand
|
||||
})
|
||||
}}
|
||||
hidden={
|
||||
!normalConfig?.settings.useLegacyInteractButtons ||
|
||||
needsRevisionUpdate(
|
||||
getVersionInfo(entry)?.lastRevision,
|
||||
entry
|
||||
)
|
||||
}
|
||||
title='Click to launch game'
|
||||
>
|
||||
Launch
|
||||
</button>
|
||||
<button
|
||||
className='button'
|
||||
onClick={async e => {
|
||||
@@ -454,9 +381,7 @@ export default function Installs () {
|
||||
setSelectedVersionList([entry])
|
||||
downloadVersions([entry])
|
||||
} else {
|
||||
invoke('open_folder', {
|
||||
name: entry
|
||||
})
|
||||
openFolder(entry)
|
||||
}
|
||||
}}
|
||||
hidden={
|
||||
@@ -465,12 +390,7 @@ export default function Installs () {
|
||||
entry
|
||||
)
|
||||
}
|
||||
disabled={downloadProgress.length != 0}
|
||||
title={
|
||||
downloadProgress.length != 0
|
||||
? 'Cannot update while downloads are in progress '
|
||||
: 'Click to update the game'
|
||||
}
|
||||
title={'Click to update the game'}
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
|
||||
+99
-467
@@ -1,51 +1,43 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import Sidebar from './componets/Sidebar'
|
||||
import Sidebar from '@/componets/Sidebar'
|
||||
import './Globals.css'
|
||||
import { DownloadProgress } from './types/DownloadProgress'
|
||||
import { DownloadProgress } from '@/types/DownloadProgress'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import {
|
||||
faAdd,
|
||||
faCheck,
|
||||
faChevronLeft,
|
||||
faCode,
|
||||
faDownload,
|
||||
faInfo,
|
||||
faRemove,
|
||||
faShieldHalved,
|
||||
faWarning,
|
||||
faXmark
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faChevronLeft, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
readNormalConfig,
|
||||
readVersionsConfig,
|
||||
writeVersionsConfig
|
||||
} from './util/BazookaManager'
|
||||
import { VersionsConfig } from './types/VersionsConfig'
|
||||
import { NormalConfig } from './types/NormalConfig'
|
||||
} from '@/lib/BazookaManager'
|
||||
import { VersionsConfig } from '@/types/VersionsConfig'
|
||||
import { NormalConfig } from '@/types/NormalConfig'
|
||||
import { app } from '@tauri-apps/api'
|
||||
import axios from 'axios'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { GlobalProvider } from './GlobalProvider'
|
||||
import { Roboto } from 'next/font/google'
|
||||
import { ServerVersionsResponse } from './types/ServerVersionsResponse'
|
||||
import { GameVersion } from './types/GameVersion'
|
||||
import { Game } from './types/Game'
|
||||
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'
|
||||
import VersionInfo from './componets/VersionInfo'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import ProgressBar from './componets/ProgressBar'
|
||||
import { notifyUser } from './util/Notifications'
|
||||
import { notifyUser } from '@/lib/Notifications'
|
||||
import {
|
||||
isPermissionGranted,
|
||||
requestPermission
|
||||
} from '@tauri-apps/plugin-notification'
|
||||
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
||||
|
||||
import DownloadsPopup from '@/componets/popups/Downloads'
|
||||
import VersionInfoPopup from '@/componets/popups/VersionInfo'
|
||||
import ManagingVersionPopup from '@/componets/popups/ManageVersion'
|
||||
import GamesDownloadPopup from '@/componets/popups/GamesDownload'
|
||||
import VersionsDownloadPopup from '@/componets/popups/VersionsDownload'
|
||||
|
||||
const roboto = Roboto({
|
||||
subsets: ['latin']
|
||||
})
|
||||
@@ -75,6 +67,8 @@ export default function RootLayout ({
|
||||
const [downloadProgress, setDownloadProgress] = useState<DownloadProgress[]>(
|
||||
[]
|
||||
)
|
||||
const [downloadQueue, setDownloadQueue] = useState<string[]>([])
|
||||
const [isProcessingQueue, setIsProcessingQueue] = useState<boolean>(false)
|
||||
const [managingVersion, setManagingVersion] = useState<string | null>(null)
|
||||
const [viewingInfoFromDownloads, setViewingInfoFromDownloads] =
|
||||
useState<boolean>(false)
|
||||
@@ -84,6 +78,7 @@ export default function RootLayout ({
|
||||
|
||||
const pathname = usePathname()
|
||||
const revisionCheck = useRef(false)
|
||||
const previousQueueLength = useRef(0)
|
||||
|
||||
function getSpecialVersionsList (game?: number): GameVersion[] {
|
||||
if (!normalConfig || !serverVersionList) return []
|
||||
@@ -170,21 +165,7 @@ export default function RootLayout ({
|
||||
return { installed, total }
|
||||
}
|
||||
|
||||
function formatEtaSmart (seconds: number) {
|
||||
if (seconds < 60) return `${Math.floor(seconds)}s`
|
||||
if (seconds < 3600)
|
||||
return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s`
|
||||
if (seconds < 86400) {
|
||||
const h = Math.floor(seconds / 3600)
|
||||
const m = Math.floor((seconds % 3600) / 60)
|
||||
return `${h}h ${m}m`
|
||||
}
|
||||
const d = Math.floor(seconds / 86400)
|
||||
const h = Math.floor((seconds % 86400) / 3600)
|
||||
return `${d}d ${h}h`
|
||||
}
|
||||
|
||||
function closePopup () {
|
||||
const closePopup = useCallback(() => {
|
||||
if (popupMode == 0 && selectedGame && pathname === '/') {
|
||||
setSelectedGame(null)
|
||||
setSelectedVersionList([])
|
||||
@@ -197,7 +178,7 @@ export default function RootLayout ({
|
||||
setFadeOut(true)
|
||||
setTimeout(() => setShowPopup(false), 200)
|
||||
}
|
||||
}
|
||||
}, [popupMode, selectedGame, pathname, viewingInfoFromDownloads])
|
||||
|
||||
useEffect(() => {
|
||||
let unlistenProgress: (() => void) | null = null
|
||||
@@ -305,34 +286,51 @@ export default function RootLayout ({
|
||||
if (list.length === 0) return
|
||||
setSelectedVersionList([])
|
||||
|
||||
const newDownloads = list.map(
|
||||
const newVersions = list.filter(
|
||||
version =>
|
||||
!downloadQueue.includes(version) &&
|
||||
!downloadProgress.some(d => d.version === version)
|
||||
)
|
||||
|
||||
if (newVersions.length === 0) return
|
||||
|
||||
const newDownloads = newVersions.map(
|
||||
version =>
|
||||
new DownloadProgress(version, 0, 0, false, true, false, false, 0, 0)
|
||||
)
|
||||
|
||||
setDownloadProgress(newDownloads)
|
||||
|
||||
for (const download of newDownloads) {
|
||||
const info = getVersionInfo(download.version)
|
||||
if (!info) {
|
||||
setDownloadProgress(prev =>
|
||||
prev.filter(d => d.version !== download.version)
|
||||
setDownloadProgress(prev => [...prev, ...newDownloads])
|
||||
setDownloadQueue(prev => [...prev, ...newVersions])
|
||||
},
|
||||
[downloadQueue, downloadProgress]
|
||||
)
|
||||
continue
|
||||
|
||||
useEffect(() => {
|
||||
if (isProcessingQueue || downloadQueue.length === 0) return
|
||||
|
||||
const processNextDownload = async () => {
|
||||
setIsProcessingQueue(true)
|
||||
|
||||
const versionId = downloadQueue[0]
|
||||
const info = getVersionInfo(versionId)
|
||||
|
||||
if (!info) {
|
||||
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
|
||||
setDownloadQueue(prev => prev.slice(1))
|
||||
setIsProcessingQueue(false)
|
||||
return
|
||||
}
|
||||
|
||||
const gameInfo = getGameInfo(info.game)
|
||||
if (!gameInfo) {
|
||||
setDownloadProgress(prev =>
|
||||
prev.filter(d => d.version !== download.version)
|
||||
)
|
||||
continue
|
||||
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
|
||||
setDownloadQueue(prev => prev.slice(1))
|
||||
setIsProcessingQueue(false)
|
||||
return
|
||||
}
|
||||
|
||||
setDownloadProgress(prev =>
|
||||
prev.map(d =>
|
||||
d.version === download.version ? { ...d, queued: false } : d
|
||||
)
|
||||
prev.map(d => (d.version === versionId ? { ...d, queued: false } : d))
|
||||
)
|
||||
|
||||
try {
|
||||
@@ -349,9 +347,7 @@ export default function RootLayout ({
|
||||
})
|
||||
|
||||
if (res === '1') {
|
||||
setDownloadProgress(prev =>
|
||||
prev.filter(d => d.version !== download.version)
|
||||
)
|
||||
setDownloadProgress(prev => prev.filter(d => d.version !== versionId))
|
||||
setDownloadedVersionsConfig(prev => {
|
||||
if (!prev) return prev
|
||||
|
||||
@@ -359,7 +355,7 @@ export default function RootLayout ({
|
||||
...prev,
|
||||
list: {
|
||||
...prev.list,
|
||||
[download.version]: Date.now()
|
||||
[versionId]: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +365,7 @@ export default function RootLayout ({
|
||||
} else {
|
||||
setDownloadProgress(prev =>
|
||||
prev.map(d =>
|
||||
d.version === download.version
|
||||
d.version === versionId
|
||||
? { ...d, queued: false, failed: true, progress: 0 }
|
||||
: d
|
||||
)
|
||||
@@ -380,16 +376,39 @@ export default function RootLayout ({
|
||||
`The download for version ${info.displayName} has failed.`
|
||||
)
|
||||
}
|
||||
|
||||
setDownloadQueue(prev => prev.slice(1))
|
||||
setIsProcessingQueue(false)
|
||||
}
|
||||
|
||||
if (normalConfig?.settings.allowNotifications)
|
||||
await notifyUser('Downloads Finished', 'All downloads have finished.')
|
||||
processNextDownload()
|
||||
}, [
|
||||
downloadQueue,
|
||||
isProcessingQueue,
|
||||
getVersionInfo,
|
||||
getGameInfo,
|
||||
normalConfig
|
||||
])
|
||||
|
||||
setFadeOut(true)
|
||||
setTimeout(() => setShowPopup(false), 200)
|
||||
},
|
||||
[getGameInfo, getVersionInfo, normalConfig]
|
||||
)
|
||||
useEffect(() => {
|
||||
if (
|
||||
downloadQueue.length === 0 &&
|
||||
downloadProgress.length === 0 &&
|
||||
!isProcessingQueue &&
|
||||
previousQueueLength.current > 0 &&
|
||||
normalConfig?.settings.allowNotifications
|
||||
) {
|
||||
notifyUser('Downloads Finished', 'All downloads have finished.')
|
||||
setTimeout(() => closePopup(), 0)
|
||||
}
|
||||
previousQueueLength.current = downloadQueue.length + downloadProgress.length
|
||||
}, [
|
||||
downloadQueue,
|
||||
downloadProgress,
|
||||
isProcessingQueue,
|
||||
normalConfig,
|
||||
closePopup
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (revisionCheck.current) return
|
||||
@@ -483,7 +502,13 @@ export default function RootLayout ({
|
||||
version,
|
||||
downloadVersions,
|
||||
category,
|
||||
setCategory
|
||||
setCategory,
|
||||
downloadQueue,
|
||||
setDownloadQueue,
|
||||
closePopup,
|
||||
getSpecialVersionsList,
|
||||
selectedGame,
|
||||
setViewingInfoFromDownloads
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@@ -536,409 +561,16 @@ export default function RootLayout ({
|
||||
/>
|
||||
</button>
|
||||
{popupMode === 0 && selectedGame ? (
|
||||
<>
|
||||
<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(3)
|
||||
}}
|
||||
title='Click to view version info'
|
||||
>
|
||||
<FontAwesomeIcon icon={faInfo} /> Info
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
<VersionsDownloadPopup />
|
||||
) : popupMode === 0 && !selectedGame ? (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
<GamesDownloadPopup />
|
||||
) : popupMode === 1 ? (
|
||||
<>
|
||||
<p className='text-xl text-center'>Downloads</p>
|
||||
<div className='popup-content'>
|
||||
{downloadProgress.map((v, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className='popup-entry flex flex-col justify-between'
|
||||
>
|
||||
<p className='text-2xl text-center'>
|
||||
{getVersionInfo(v.version)?.displayName}
|
||||
</p>
|
||||
<div className='mt-6.25 flex items-center justify-between'>
|
||||
{v.failed ? (
|
||||
<>
|
||||
<div className='flex items-center'>
|
||||
<span className='text-red-500 inline-block w-full text-center'>
|
||||
Download failed
|
||||
</span>
|
||||
<button
|
||||
className='button btntheme3 ml-30 mb-2'
|
||||
onClick={() => {
|
||||
setDownloadProgress(prev =>
|
||||
prev.filter(
|
||||
d => d.version !== v.version
|
||||
)
|
||||
)
|
||||
}}
|
||||
title='Click to remove this version from this menu.'
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : v.queued ? (
|
||||
<span className='text-yellow-500 inline-block w-full text-center'>
|
||||
Queued…
|
||||
</span>
|
||||
) : v.queued ? (
|
||||
<span className='text-yellow-500 inline-block w-full text-center'>
|
||||
Queued…
|
||||
</span>
|
||||
) : v.hash_checking ? (
|
||||
<span className='text-blue-500 inline-block w-full text-center'>
|
||||
Checking hash...
|
||||
</span>
|
||||
) : v.finishing ? (
|
||||
<span className='text-green-500 inline-block w-full text-center'>
|
||||
Finishing...
|
||||
</span>
|
||||
) : (
|
||||
<div className='flex flex-col gap-1 w-full'>
|
||||
<span className='text-center'>
|
||||
Downloaded{' '}
|
||||
{prettyBytes(v.progressBytes, {
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 1
|
||||
})}{' '}
|
||||
of{' '}
|
||||
{prettyBytes(
|
||||
getVersionInfo(v.version)?.size ?? 0,
|
||||
{
|
||||
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>
|
||||
</>
|
||||
<DownloadsPopup />
|
||||
) : popupMode === 2 ? (
|
||||
managingVersion ? (
|
||||
<>
|
||||
<p className='text-xl text-center'>
|
||||
Manage{' '}
|
||||
{getVersionInfo(managingVersion)?.displayName}
|
||||
</p>
|
||||
<div className='popup-content flex flex-col items-center justify-center gap-2 h-full'>
|
||||
<button
|
||||
className='button btntheme2'
|
||||
disabled={downloadProgress.length != 0}
|
||||
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.'
|
||||
>
|
||||
Uninstall
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme2'
|
||||
disabled={downloadProgress.length != 0}
|
||||
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([managingVersion])
|
||||
}}
|
||||
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."
|
||||
>
|
||||
Reinstall
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme2'
|
||||
onClick={async () =>
|
||||
invoke('open_folder', {
|
||||
name: managingVersion
|
||||
})
|
||||
}
|
||||
title="Click to browse the game's files."
|
||||
>
|
||||
Open Folder
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className='text-xl text-center'>
|
||||
No version selected
|
||||
</p>
|
||||
)
|
||||
<ManagingVersionPopup />
|
||||
) : popupMode === 3 ? (
|
||||
managingVersion && downloadedVersionsConfig ? (
|
||||
<VersionInfo />
|
||||
) : (
|
||||
<p className='text-xl text-center'>
|
||||
No version selected
|
||||
</p>
|
||||
)
|
||||
<VersionInfoPopup />
|
||||
) : null}
|
||||
{popupMode == 0 &&
|
||||
selectedGame &&
|
||||
serverVersionList != null && (
|
||||
<div className='flex justify-center'>
|
||||
<button
|
||||
className='button btntheme1 w-fit mt-2 -mb-4'
|
||||
onClick={() => {
|
||||
setFadeOut(true)
|
||||
setTimeout(() => setShowPopup(false), 200)
|
||||
if (downloadedVersionsConfig)
|
||||
downloadVersions(selectedVersionList)
|
||||
}}
|
||||
disabled={downloadProgress.length != 0}
|
||||
title={
|
||||
downloadProgress.length != 0
|
||||
? "You cannot download the versions as you have another download that hasn't completed."
|
||||
: `Click to 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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+2
-23
@@ -68,18 +68,9 @@ export default function Installs () {
|
||||
.map(i => (
|
||||
<div
|
||||
key={i.id}
|
||||
className={`downloads-entry ${
|
||||
normalConfig?.settings.useLegacyInteractButtons
|
||||
? ''
|
||||
: 'cursor-pointer'
|
||||
}`}
|
||||
title={
|
||||
normalConfig?.settings.useLegacyInteractButtons
|
||||
? ''
|
||||
: 'Click to view game installs'
|
||||
}
|
||||
className={'downloads-entry'}
|
||||
title={'Click to view game installs'}
|
||||
onClick={() => {
|
||||
if (normalConfig?.settings.useLegacyInteractButtons) return
|
||||
setCategory(-1)
|
||||
router.push('/game?id=' + i.id)
|
||||
}}
|
||||
@@ -128,18 +119,6 @@ export default function Installs () {
|
||||
<p>{i.verified ? 'Verified' : 'Unverified'}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className='button absolute right-0 bottom-0'
|
||||
hidden={!normalConfig?.settings.useLegacyInteractButtons}
|
||||
title='Click to view game installs'
|
||||
onClick={() => {
|
||||
setCategory(-1)
|
||||
router.push('/game?id=' + i.id)
|
||||
}}
|
||||
>
|
||||
Installs
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Setting } from '../componets/Setting'
|
||||
import { writeNormalConfig } from '../util/BazookaManager'
|
||||
import { useGlobal } from '../GlobalProvider'
|
||||
import { copyToClipboard } from '../util/Clipboard'
|
||||
import { Setting } from '@/componets/Setting'
|
||||
import { writeNormalConfig } from '@/lib/BazookaManager'
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import { copyToClipboard } from '@/lib/Clipboard'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
|
||||
export default function Settings () {
|
||||
const [allowNotifications, setAllowNotifications] = useState(false)
|
||||
const [alwaysShowGamesInSidebar, setAlwaysShowGamesInSidebar] =
|
||||
useState(false)
|
||||
const [useLegacyInteractButtons, setUseLegacyInteractButtons] =
|
||||
useState(false)
|
||||
const [useWineOnUnixWhenNeeded, setUseWineOnUnixWhenNeeded] = useState(false)
|
||||
const [wineOnUnixCommand, setWineOnUnixCommand] = useState('wine %path%')
|
||||
const [theme, setTheme] = useState(0)
|
||||
@@ -27,9 +25,6 @@ export default function Settings () {
|
||||
setAlwaysShowGamesInSidebar(
|
||||
normalConfig.settings.alwaysShowGamesInSidebar
|
||||
)
|
||||
setUseLegacyInteractButtons(
|
||||
normalConfig.settings.useLegacyInteractButtons
|
||||
)
|
||||
setUseWineOnUnixWhenNeeded(
|
||||
normalConfig.settings.useWineOnUnixWhenNeeded
|
||||
)
|
||||
@@ -92,29 +87,6 @@ export default function Settings () {
|
||||
}}
|
||||
title="This setting will make it so when you are on a page like this, the games won't disappear."
|
||||
/>
|
||||
<Setting
|
||||
label='Show Installs/Launch/Manage Buttons'
|
||||
value={useLegacyInteractButtons}
|
||||
onChange={async () => {
|
||||
if (!normalConfig) return
|
||||
setUseLegacyInteractButtons(!useLegacyInteractButtons)
|
||||
setNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
useLegacyInteractButtons: !useLegacyInteractButtons
|
||||
}
|
||||
})
|
||||
await writeNormalConfig({
|
||||
...normalConfig,
|
||||
settings: {
|
||||
...normalConfig.settings,
|
||||
useLegacyInteractButtons: !useLegacyInteractButtons
|
||||
}
|
||||
})
|
||||
}}
|
||||
title='Enable the legacy method of using the installs/launch/manage buttons. In the future this setting may be removed so try and get used to the new method.'
|
||||
/>
|
||||
<Setting
|
||||
label='Use wine when needed to launch games'
|
||||
value={useWineOnUnixWhenNeeded}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface LeaderboardEntry {
|
||||
username: string
|
||||
userid: bigint
|
||||
value: bigint
|
||||
icon: number
|
||||
overlay: number
|
||||
birdColor: number[]
|
||||
overlayColor: number[]
|
||||
customIcon: string | null
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { LeaderboardEntry } from './LeaderboardEntry'
|
||||
|
||||
export interface LeaderboardResponse {
|
||||
entries: LeaderboardEntry[]
|
||||
customIcons: Record<string, string>
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
|
||||
import './Setting.css'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||
import { SettingProps } from '../types/SettingProps'
|
||||
import { SettingProps } from '@/types/SettingProps'
|
||||
|
||||
export function Setting ({
|
||||
label,
|
||||
File renamed without changes.
@@ -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 '../GlobalProvider'
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
@@ -89,7 +89,7 @@ export default function Sidebar () {
|
||||
draggable={false}
|
||||
href='/'
|
||||
className={`link relative flex items-center ${
|
||||
pathname === '/' || pathname === '/game' ? 'active' : ''
|
||||
pathname === '/' || pathname.startsWith('/game') ? 'active' : ''
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faHexagonNodes} className='mr-2' /> Games
|
||||
@@ -103,13 +103,15 @@ export default function Sidebar () {
|
||||
<div
|
||||
draggable={false}
|
||||
className={`link ${
|
||||
pathname === '/game' && Number(params.get('id') || 0) == i.id
|
||||
(pathname === '/game' &&
|
||||
Number(params.get('id') || 0) == i.id) ||
|
||||
(i.id == 1 && pathname === '/game/berrydash/leaderboards')
|
||||
? 'active'
|
||||
: ''
|
||||
} ml-auto w-50 ${
|
||||
normalConfig?.settings.alwaysShowGamesInSidebar ||
|
||||
pathname === '/' ||
|
||||
pathname === '/game'
|
||||
pathname.startsWith('/game')
|
||||
? ''
|
||||
: 'hidden'
|
||||
}`}
|
||||
@@ -156,15 +158,17 @@ export default function Sidebar () {
|
||||
key={`${i.id}-${key}`}
|
||||
draggable={false}
|
||||
className={`link ${
|
||||
pathname === '/game' &&
|
||||
Number(params.get('id') || 0) == i.id &&
|
||||
category == Number(key)
|
||||
(pathname === '/game' &&
|
||||
Number(params.get('id') || 0) == i.id) ||
|
||||
(i.id == 1 &&
|
||||
pathname === '/game/berrydash/leaderboards' &&
|
||||
category == Number(key))
|
||||
? 'active'
|
||||
: ''
|
||||
} ml-auto w-47.5 ${
|
||||
normalConfig?.settings.alwaysShowGamesInSidebar ||
|
||||
pathname === '/' ||
|
||||
pathname === '/game'
|
||||
pathname.startsWith('/game')
|
||||
? ''
|
||||
: 'hidden'
|
||||
}`}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import ProgressBar from '../ProgressBar'
|
||||
import { formatEtaSmart } from '@/lib/Util'
|
||||
|
||||
export default function DownloadsPopup () {
|
||||
const {
|
||||
downloadProgress,
|
||||
getVersionInfo,
|
||||
setDownloadProgress,
|
||||
downloadQueue,
|
||||
setDownloadQueue
|
||||
} = 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'>
|
||||
<p className='text-2xl text-center'>
|
||||
{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>
|
||||
<button
|
||||
className='button btntheme3 -ml-1.25'
|
||||
onClick={() => {
|
||||
setDownloadQueue(prev =>
|
||||
prev.filter(id => id !== v.version)
|
||||
)
|
||||
setDownloadProgress(prev =>
|
||||
prev.filter(d => d.version !== v.version)
|
||||
)
|
||||
}}
|
||||
title='Click to remove this version from the download queue.'
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
) : v.hash_checking || v.finishing ? (
|
||||
<span
|
||||
className={`${
|
||||
v.hash_checking ? 'text-blue-300' : 'text-green-300'
|
||||
} inline-block w-full text-center`}
|
||||
>
|
||||
{v.hash_checking ? 'Checking hash' : 'Finishing'}
|
||||
...
|
||||
</span>
|
||||
) : (
|
||||
<div className='flex flex-col gap-1 w-full'>
|
||||
<span className='text-center'>
|
||||
Downloaded{' '}
|
||||
{prettyBytes(v.progressBytes, {
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 1
|
||||
})}{' '}
|
||||
of{' '}
|
||||
{prettyBytes(getVersionInfo(v.version)?.size ?? 0, {
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { useGlobal } from '@/app/GlobalProvider'
|
||||
import { writeVersionsConfig } from '@/lib/BazookaManager'
|
||||
import { openFolder } from '@/lib/Util'
|
||||
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
|
||||
|
||||
export default function ManageVersionPopup () {
|
||||
const {
|
||||
getVersionInfo,
|
||||
managingVersion,
|
||||
closePopup,
|
||||
setDownloadedVersionsConfig,
|
||||
setManagingVersion,
|
||||
downloadVersions,
|
||||
setSelectedVersionList,
|
||||
setPopupMode
|
||||
} = useGlobal()
|
||||
if (!managingVersion) return <></>
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className='text-xl text-center'>
|
||||
Manage {getVersionInfo(managingVersion)?.displayName}
|
||||
</p>
|
||||
<div className='popup-content flex flex-col items-center justify-center gap-2 h-full'>
|
||||
<button
|
||||
className='button 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.'
|
||||
>
|
||||
Uninstall
|
||||
</button>
|
||||
<button
|
||||
className='button 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([managingVersion])
|
||||
}}
|
||||
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."
|
||||
>
|
||||
Reinstall
|
||||
</button>
|
||||
<button
|
||||
className='button btntheme2'
|
||||
onClick={async () => openFolder(managingVersion)}
|
||||
title="Click to browse the game's files."
|
||||
>
|
||||
Open Folder
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -9,13 +9,13 @@ import {
|
||||
faWarning
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { useGlobal } from '../GlobalProvider'
|
||||
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'
|
||||
|
||||
export default function VersionInfo () {
|
||||
export default function VersionInfoPopup () {
|
||||
const {
|
||||
getGameInfo,
|
||||
getVersionInfo,
|
||||
@@ -0,0 +1,128 @@
|
||||
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,
|
||||
downloadProgress,
|
||||
downloadVersions,
|
||||
getGameInfo,
|
||||
downloadQueue
|
||||
} = 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(3)
|
||||
}}
|
||||
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)
|
||||
}
|
||||
}}
|
||||
disabled={selectedVersionList.length === 0}
|
||||
title={
|
||||
selectedVersionList.length === 0
|
||||
? 'Select at least one version to download'
|
||||
: downloadProgress.length > 0 || downloadQueue.length > 0
|
||||
? `Add ${selectedVersionList.length} version${
|
||||
selectedVersionList.length == 1 ? '' : 's'
|
||||
} to download queue`
|
||||
: `Download ${selectedVersionList.length} version${
|
||||
selectedVersionList.length == 1 ? '' : 's'
|
||||
} of ${getGameInfo(selectedGame)?.name}`
|
||||
}
|
||||
>
|
||||
{downloadProgress.length > 0 || downloadQueue.length > 0
|
||||
? `Add ${selectedVersionList.length} to Queue`
|
||||
: `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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { app } from '@tauri-apps/api'
|
||||
import { NormalConfig } from '../types/NormalConfig'
|
||||
import { NormalConfig } from '@/types/NormalConfig'
|
||||
import {
|
||||
BaseDirectory,
|
||||
create,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
readTextFile,
|
||||
writeFile
|
||||
} from '@tauri-apps/plugin-fs'
|
||||
import { VersionsConfig } from '../types/VersionsConfig'
|
||||
import { VersionsConfig } from '@/types/VersionsConfig'
|
||||
|
||||
export async function readNormalConfig (): Promise<NormalConfig> {
|
||||
const version = await app.getVersion()
|
||||
@@ -0,0 +1,7 @@
|
||||
export const GetIconForUser = (user: number) => {
|
||||
if (user == 1) return -1
|
||||
else if (user == 2) return -2
|
||||
else if (user == 4) return -3
|
||||
else if (user == 3) return -4
|
||||
else return 1
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NormalConfig } from '../types/NormalConfig'
|
||||
import { NormalConfig } from '@/types/NormalConfig'
|
||||
import { notifyUser } from './Notifications'
|
||||
|
||||
export async function copyToClipboard (
|
||||
File renamed without changes.
@@ -0,0 +1,41 @@
|
||||
import { message } from '@tauri-apps/plugin-dialog'
|
||||
import { BaseDirectory, exists, stat } from '@tauri-apps/plugin-fs'
|
||||
import { openPath } from '@tauri-apps/plugin-opener'
|
||||
import { join, appLocalDataDir } from '@tauri-apps/api/path'
|
||||
|
||||
export const openFolder = async (name: string) => {
|
||||
const relativePath = await join('game', name)
|
||||
const basePath = await appLocalDataDir()
|
||||
const absolutePath = await join(basePath, relativePath)
|
||||
|
||||
const folderExists = await exists(relativePath, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
const folderStat = await stat(relativePath, {
|
||||
baseDir: BaseDirectory.AppLocalData
|
||||
})
|
||||
|
||||
if (!folderExists || folderStat.isFile) {
|
||||
await message(`Game folder "${absolutePath}" not found.`, {
|
||||
title: 'Folder not found',
|
||||
kind: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await openPath(absolutePath)
|
||||
}
|
||||
|
||||
export const formatEtaSmart = (seconds: number) => {
|
||||
if (seconds < 60) return `${Math.floor(seconds)}s`
|
||||
if (seconds < 3600)
|
||||
return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s`
|
||||
if (seconds < 86400) {
|
||||
const h = Math.floor(seconds / 3600)
|
||||
const m = Math.floor((seconds % 3600) / 60)
|
||||
return `${h}h ${m}m`
|
||||
}
|
||||
const d = Math.floor(seconds / 86400)
|
||||
const h = Math.floor((seconds % 86400) / 3600)
|
||||
return `${d}d ${h}h`
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export type BirdColor = [number, number, number]
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -2,7 +2,6 @@ export class SettingsType {
|
||||
constructor (
|
||||
public allowNotifications: boolean = true,
|
||||
public alwaysShowGamesInSidebar: boolean = true,
|
||||
public useLegacyInteractButtons: boolean = false,
|
||||
public useWineOnUnixWhenNeeded: boolean = false,
|
||||
public wineOnUnixCommand: string = 'wine %path%',
|
||||
public theme: number = 0
|
||||
File renamed without changes.
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"target": "es2024",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
Reference in new issue
Block a user