Move to NextJS + other changes
This commit is contained in:
55
.gitignore
vendored
55
.gitignore
vendored
@@ -1,24 +1,41 @@
|
|||||||
# Logs
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
logs
|
|
||||||
*.log
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
# env files (can opt-in for committing if needed)
|
||||||
dist
|
.env*
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
|
|
||||||
# Editor directories and files
|
# vercel
|
||||||
.vscode/*
|
.vercel
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
# typescript
|
||||||
.DS_Store
|
*.tsbuildinfo
|
||||||
*.suo
|
next-env.d.ts
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true
|
||||||
|
}
|
||||||
25
eslint.config.mjs
Normal file
25
eslint.config.mjs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = [
|
||||||
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"node_modules/**",
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
11
index.html
11
index.html
@@ -1,11 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
14
next.config.ts
Normal file
14
next.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { NextConfig } from 'next'
|
||||||
|
|
||||||
|
const isProd = process.env.NODE_ENV === 'production'
|
||||||
|
const internalHost = process.env.TAURI_DEV_HOST || 'localhost'
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
output: 'export',
|
||||||
|
images: {
|
||||||
|
unoptimized: true
|
||||||
|
},
|
||||||
|
assetPrefix: isProd ? undefined : `http://${internalHost}:3000`,
|
||||||
|
devIndicators: false
|
||||||
|
}
|
||||||
|
|
||||||
|
export default nextConfig
|
||||||
18
package.json
18
package.json
@@ -4,13 +4,13 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "next dev --turbopack",
|
||||||
"build": "tsc && vite build",
|
"build": "next build",
|
||||||
"preview": "vite preview",
|
"start": "next start",
|
||||||
|
"lint": "next lint",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/roboto": "5.2.6",
|
|
||||||
"@tauri-apps/api": "2.8.0",
|
"@tauri-apps/api": "2.8.0",
|
||||||
"@tauri-apps/plugin-dialog": "2.3.3",
|
"@tauri-apps/plugin-dialog": "2.3.3",
|
||||||
"@tauri-apps/plugin-fs": "2.4.2",
|
"@tauri-apps/plugin-fs": "2.4.2",
|
||||||
@@ -20,7 +20,8 @@
|
|||||||
"axios": "1.11.0",
|
"axios": "1.11.0",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1"
|
"react-dom": "19.1.1",
|
||||||
|
"next": "15.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "7.0.0",
|
"@fortawesome/fontawesome-svg-core": "7.0.0",
|
||||||
@@ -32,11 +33,12 @@
|
|||||||
"@types/crypto-js": "4.2.2",
|
"@types/crypto-js": "4.2.2",
|
||||||
"@types/react": "19.1.11",
|
"@types/react": "19.1.11",
|
||||||
"@types/react-dom": "19.1.7",
|
"@types/react-dom": "19.1.7",
|
||||||
"@vitejs/plugin-react": "5.0.1",
|
|
||||||
"crypto-js": "4.2.0",
|
"crypto-js": "4.2.0",
|
||||||
"postcss": "8.5.6",
|
"@types/node": "24.3.0",
|
||||||
"tailwindcss": "4.1.12",
|
"tailwindcss": "4.1.12",
|
||||||
"typescript": "5.9.2",
|
"typescript": "5.9.2",
|
||||||
"vite": "7.1.3"
|
"eslint": "9.34.0",
|
||||||
|
"eslint-config-next": "15.5.0",
|
||||||
|
"@eslint/eslintrc": "3.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export default {
|
const config = {
|
||||||
plugins: {
|
plugins: ["@tailwindcss/postcss"],
|
||||||
'@tailwindcss/postcss': {}
|
};
|
||||||
}
|
|
||||||
}
|
export default config;
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
"identifier": "xyz.lncvrt.berrydash-launcher",
|
"identifier": "xyz.lncvrt.berrydash-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "yarn dev",
|
"beforeDevCommand": "yarn dev",
|
||||||
"devUrl": "http://localhost:1420",
|
"devUrl": "http://localhost:3000",
|
||||||
"beforeBuildCommand": "yarn build",
|
"beforeBuildCommand": "yarn build",
|
||||||
"frontendDist": "../dist"
|
"frontendDist": "../out"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
"identifier": "xyz.lncvrt.berrydash-launcher",
|
"identifier": "xyz.lncvrt.berrydash-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "yarn dev",
|
"beforeDevCommand": "yarn dev",
|
||||||
"devUrl": "http://localhost:1420",
|
"devUrl": "http://localhost:3000",
|
||||||
"beforeBuildCommand": "yarn build",
|
"beforeBuildCommand": "yarn build",
|
||||||
"frontendDist": "../dist"
|
"frontendDist": "../out"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
"identifier": "xyz.lncvrt.berrydash-launcher",
|
"identifier": "xyz.lncvrt.berrydash-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "yarn dev",
|
"beforeDevCommand": "yarn dev",
|
||||||
"devUrl": "http://localhost:1420",
|
"devUrl": "http://localhost:3000",
|
||||||
"beforeBuildCommand": "yarn build",
|
"beforeBuildCommand": "yarn build",
|
||||||
"frontendDist": "../dist"
|
"frontendDist": "../out"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"macOSPrivateApi": true,
|
"macOSPrivateApi": true,
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
"identifier": "xyz.lncvrt.berrydash-launcher",
|
"identifier": "xyz.lncvrt.berrydash-launcher",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "yarn dev",
|
"beforeDevCommand": "yarn dev",
|
||||||
"devUrl": "http://localhost:1420",
|
"devUrl": "http://localhost:3000",
|
||||||
"beforeBuildCommand": "yarn build",
|
"beforeBuildCommand": "yarn build",
|
||||||
"frontendDist": "../dist"
|
"frontendDist": "../out"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
|
|||||||
47
src/app/GlobalProvider.tsx
Normal file
47
src/app/GlobalProvider.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { createContext, useContext, ReactNode } from 'react'
|
||||||
|
import { LauncherVersion } from './types/LauncherVersion'
|
||||||
|
import { DownloadProgress } from './types/DownloadProgress'
|
||||||
|
import { VersionsConfig } from './types/VersionsConfig'
|
||||||
|
import { NormalConfig } from './types/NormalConfig'
|
||||||
|
import { DownloadedVersion } from './types/DownloadedVersion'
|
||||||
|
|
||||||
|
type GlobalCtxType = {
|
||||||
|
versionList: LauncherVersion[] | null
|
||||||
|
setVersionList: (v: LauncherVersion[] | null) => void
|
||||||
|
selectedVersionList: LauncherVersion[]
|
||||||
|
setSelectedVersionList: (v: LauncherVersion[]) => void
|
||||||
|
downloadProgress: DownloadProgress[]
|
||||||
|
setDownloadProgress: (v: DownloadProgress[]) => void
|
||||||
|
showPopup: boolean
|
||||||
|
setShowPopup: (v: boolean) => void
|
||||||
|
popupMode: number | null
|
||||||
|
setPopupMode: (v: number | null) => void
|
||||||
|
fadeOut: boolean
|
||||||
|
setFadeOut: (v: boolean) => void
|
||||||
|
downloadedVersionsConfig: VersionsConfig | null
|
||||||
|
setDownloadedVersionsConfig: (v: VersionsConfig | null) => void
|
||||||
|
normalConfig: NormalConfig | null
|
||||||
|
setNormalConfig: (v: NormalConfig | null) => void
|
||||||
|
managingVersion: DownloadedVersion | null
|
||||||
|
setManagingVersion: (v: DownloadedVersion | null) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const GlobalCtx = createContext<GlobalCtxType | null>(null)
|
||||||
|
|
||||||
|
export const useGlobal = () => {
|
||||||
|
const ctx = useContext(GlobalCtx)
|
||||||
|
if (!ctx) throw new Error('useGlobal must be inside GlobalProvider')
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GlobalProvider = ({
|
||||||
|
children,
|
||||||
|
value
|
||||||
|
}: {
|
||||||
|
children: ReactNode
|
||||||
|
value: GlobalCtxType
|
||||||
|
}) => {
|
||||||
|
return <GlobalCtx.Provider value={value}>{children}</GlobalCtx.Provider>
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
@import 'tailwindcss';
|
@import "tailwindcss";
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Roboto', sans-serif;
|
|
||||||
@apply bg-[#0f0f0f] text-white select-none;
|
@apply bg-[#0f0f0f] text-white select-none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -1,4 +1,4 @@
|
|||||||
@import 'tailwindcss';
|
@import "tailwindcss";
|
||||||
|
|
||||||
.downloads-container {
|
.downloads-container {
|
||||||
@apply flex justify-center;
|
@apply flex justify-center;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'tailwindcss';
|
@import "tailwindcss";
|
||||||
|
|
||||||
.setting-checkbox-wrapper {
|
.setting-checkbox-wrapper {
|
||||||
@apply relative w-5 h-5;
|
@apply relative w-5 h-5;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { SettingProps } from '../types/SettingProps'
|
|
||||||
import './Setting.css'
|
import './Setting.css'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { SettingProps } from '../types/SettingProps'
|
||||||
|
|
||||||
export function Setting ({ label, value, onChange, className }: SettingProps) {
|
export function Setting ({ label, value, onChange, className }: SettingProps) {
|
||||||
return (
|
return (
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'tailwindcss';
|
@import "tailwindcss";
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
@apply fixed top-0 left-0 w-60 h-screen bg-[#161616] flex flex-col border-e-[1px] border-[#242424] z-[1];
|
@apply fixed top-0 left-0 w-60 h-screen bg-[#161616] flex flex-col border-e-[1px] border-[#242424] z-[1];
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import './Sidebar.css'
|
import './Sidebar.css'
|
||||||
import Icon from '../Icon.png'
|
import Icon from '../Icon.png'
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
@@ -12,16 +14,17 @@ import { faDiscord } from '@fortawesome/free-brands-svg-icons'
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
import { SidebarProps } from '../types/SidebarProps'
|
import { useGlobal } from '../GlobalProvider'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { usePathname } from 'next/navigation'
|
||||||
|
|
||||||
export default function Sidebar ({
|
export default function Sidebar () {
|
||||||
setShowPopup,
|
|
||||||
setPopupMode,
|
|
||||||
setFadeOut,
|
|
||||||
downloadProgress
|
|
||||||
}: SidebarProps) {
|
|
||||||
const [rot, setRot] = useState(0)
|
const [rot, setRot] = useState(0)
|
||||||
const [dir, setDir] = useState(1)
|
const [dir, setDir] = useState(1)
|
||||||
|
const { setShowPopup, setPopupMode, setFadeOut, downloadProgress } =
|
||||||
|
useGlobal()
|
||||||
|
const pathname = usePathname()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className='sidebar'>
|
<aside className='sidebar'>
|
||||||
@@ -43,11 +46,12 @@ export default function Sidebar ({
|
|||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
<div className='logo'>
|
<div className='logo'>
|
||||||
<img
|
<Image
|
||||||
draggable={false}
|
draggable={false}
|
||||||
src={Icon}
|
src={Icon}
|
||||||
width={48}
|
width={48}
|
||||||
height={48}
|
height={48}
|
||||||
|
alt=''
|
||||||
style={{
|
style={{
|
||||||
transform: `rotate(${rot}deg)`,
|
transform: `rotate(${rot}deg)`,
|
||||||
transition: 'transform 0.3s ease',
|
transition: 'transform 0.3s ease',
|
||||||
@@ -84,39 +88,27 @@ export default function Sidebar ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<nav className='nav-links'>
|
<nav className='nav-links'>
|
||||||
<a
|
<Link
|
||||||
draggable={false}
|
draggable={false}
|
||||||
href='#installs'
|
href='/'
|
||||||
className={`link ${
|
className={`link ${pathname === '/' ? 'active' : ''}`}
|
||||||
(window.location.hash || '#installs') === '#installs'
|
|
||||||
? 'active'
|
|
||||||
: ''
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faServer} className='mr-1' /> Installs
|
<FontAwesomeIcon icon={faServer} className='mr-1' /> Installs
|
||||||
</a>
|
</Link>
|
||||||
<a
|
<Link
|
||||||
draggable={false}
|
draggable={false}
|
||||||
href='#settings'
|
href='/settings'
|
||||||
className={`link ${
|
className={`link ${pathname === '/settings' ? 'active' : ''}`}
|
||||||
(window.location.hash || '#installs') === '#settings'
|
|
||||||
? 'active'
|
|
||||||
: ''
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faCog} className='mr-1' /> Settings
|
<FontAwesomeIcon icon={faCog} className='mr-1' /> Settings
|
||||||
</a>
|
</Link>
|
||||||
<a
|
<Link
|
||||||
draggable={false}
|
draggable={false}
|
||||||
href='#leaderboards'
|
href='/leaderboards'
|
||||||
className={`link ${
|
className={`link ${pathname === '/leaderboards' ? 'active' : ''}`}
|
||||||
(window.location.hash || '#installs') === '#leaderboards'
|
|
||||||
? 'active'
|
|
||||||
: ''
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faRankingStar} className='mr-1' /> Leaderboards
|
<FontAwesomeIcon icon={faRankingStar} className='mr-1' /> Leaderboards
|
||||||
</a>
|
</Link>
|
||||||
<a
|
<a
|
||||||
draggable={false}
|
draggable={false}
|
||||||
onClick={() => openUrl('https://berrydash.lncvrt.xyz/discord')}
|
onClick={() => openUrl('https://berrydash.lncvrt.xyz/discord')}
|
||||||
604
src/app/layout.tsx
Normal file
604
src/app/layout.tsx
Normal file
@@ -0,0 +1,604 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import Sidebar from './componets/Sidebar'
|
||||||
|
import './Globals.css'
|
||||||
|
import { LauncherVersion } from './types/LauncherVersion'
|
||||||
|
import { DownloadProgress } from './types/DownloadProgress'
|
||||||
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
import { listen } from '@tauri-apps/api/event'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { faAdd, faRemove, faX } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import {
|
||||||
|
isPermissionGranted,
|
||||||
|
requestPermission,
|
||||||
|
sendNotification
|
||||||
|
} from '@tauri-apps/plugin-notification'
|
||||||
|
import {
|
||||||
|
readNormalConfig,
|
||||||
|
readVersionsConfig,
|
||||||
|
writeVersionsConfig
|
||||||
|
} from './util/BazookaManager'
|
||||||
|
import { VersionsConfig } from './types/VersionsConfig'
|
||||||
|
import { DownloadedVersion } from './types/DownloadedVersion'
|
||||||
|
import { NormalConfig } from './types/NormalConfig'
|
||||||
|
import { app } from '@tauri-apps/api'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
|
import { GlobalProvider } from './GlobalProvider'
|
||||||
|
import { Roboto } from 'next/font/google'
|
||||||
|
|
||||||
|
const roboto = Roboto({
|
||||||
|
subsets: ['latin']
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function RootLayout ({
|
||||||
|
children
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [loadingText, setLoadingText] = useState('Loading...')
|
||||||
|
const [outdated, setOutdated] = useState(false)
|
||||||
|
const [versionList, setVersionList] = useState<null | LauncherVersion[]>(null)
|
||||||
|
const [selectedVersionList, setSelectedVersionList] = useState<
|
||||||
|
LauncherVersion[]
|
||||||
|
>([])
|
||||||
|
const [downloadProgress, setDownloadProgress] = useState<DownloadProgress[]>(
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const [showPopup, setShowPopup] = useState(false)
|
||||||
|
const [popupMode, setPopupMode] = useState<null | number>(null)
|
||||||
|
const [fadeOut, setFadeOut] = useState(false)
|
||||||
|
const activeDownloads = useRef(0)
|
||||||
|
const queue = useRef<(() => void)[]>([])
|
||||||
|
const [downloadedVersionsConfig, setDownloadedVersionsConfig] =
|
||||||
|
useState<VersionsConfig | null>(null)
|
||||||
|
const [normalConfig, setNormalConfig] = useState<NormalConfig | null>(null)
|
||||||
|
const [managingVersion, setManagingVersion] =
|
||||||
|
useState<DownloadedVersion | null>(null)
|
||||||
|
|
||||||
|
function runNext () {
|
||||||
|
if (activeDownloads.current === 0 && queue.current.length === 0) {
|
||||||
|
setFadeOut(true)
|
||||||
|
setTimeout(() => setShowPopup(false), 200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (activeDownloads.current >= 3 || queue.current.length === 0) return
|
||||||
|
activeDownloads.current++
|
||||||
|
const next = queue.current.shift()
|
||||||
|
next?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadVersions (versions: LauncherVersion[]) {
|
||||||
|
while (normalConfig != null) {
|
||||||
|
const useWine = normalConfig.settings.useWineOnUnixWhenNeeded
|
||||||
|
const p = platform()
|
||||||
|
const newDownloads = versions.map(
|
||||||
|
v => new DownloadProgress(v, 0, false, true)
|
||||||
|
)
|
||||||
|
setDownloadProgress(prev => [...prev, ...newDownloads])
|
||||||
|
|
||||||
|
newDownloads.forEach(download => {
|
||||||
|
let plat = p
|
||||||
|
if (p === 'linux' && useWine) {
|
||||||
|
if (
|
||||||
|
!download.version.platforms.includes(p) &&
|
||||||
|
download.version.platforms.includes('windows')
|
||||||
|
) {
|
||||||
|
plat = 'windows'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const idx = download.version.platforms.indexOf(plat)
|
||||||
|
const url = download.version.downloadUrls[idx]
|
||||||
|
const exe = download.version.executables[idx]
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
setDownloadProgress(prev =>
|
||||||
|
prev.map(d =>
|
||||||
|
d.version.version === download.version.version
|
||||||
|
? { ...d, failed: true }
|
||||||
|
: d
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = () => {
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(
|
||||||
|
d => d.version.version === download.version.version
|
||||||
|
)
|
||||||
|
if (i === -1) return prev
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = { ...copy[i], queued: false }
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
|
||||||
|
invoke('download', {
|
||||||
|
url,
|
||||||
|
name: download.version.version,
|
||||||
|
executable: exe
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.current.push(task)
|
||||||
|
runNext()
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOverlayClick (e: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
setFadeOut(true)
|
||||||
|
setTimeout(() => setShowPopup(false), 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifyUser = useCallback(
|
||||||
|
async (title: string, body: string) => {
|
||||||
|
if (!normalConfig?.settings.allowNotifications) return
|
||||||
|
|
||||||
|
let permissionGranted = await isPermissionGranted()
|
||||||
|
if (!permissionGranted) {
|
||||||
|
const permission = await requestPermission()
|
||||||
|
permissionGranted = permission === 'granted'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissionGranted) {
|
||||||
|
sendNotification({ title, body })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[normalConfig]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
;(async () => {
|
||||||
|
setLoadingText('Checking latest version...')
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
'https://berrydash.lncvrt.xyz/database/launcher/latest.php'
|
||||||
|
)
|
||||||
|
const client = await app.getVersion()
|
||||||
|
if (response.data !== client) {
|
||||||
|
setOutdated(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setLoadingText('Failed to check latest version.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setLoadingText('Loading configs...')
|
||||||
|
const normalConfig = await readNormalConfig()
|
||||||
|
const versionsConfig = await readVersionsConfig()
|
||||||
|
setDownloadedVersionsConfig(versionsConfig)
|
||||||
|
setNormalConfig(normalConfig)
|
||||||
|
if (platform() == 'windows') {
|
||||||
|
invoke('windows_rounded_corners', {
|
||||||
|
enabled: normalConfig.settings.useWindowsRoundedCorners
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setLoading(false)
|
||||||
|
})()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlistenProgress = listen<string>('download-progress', event => {
|
||||||
|
const [versionName, progStr] = event.payload.split(':')
|
||||||
|
const prog = Number(progStr)
|
||||||
|
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const i = prev.findIndex(d => d.version.version === versionName)
|
||||||
|
if (i === -1) return prev
|
||||||
|
const copy = [...prev]
|
||||||
|
copy[i] = { ...copy[i], progress: prog }
|
||||||
|
return copy
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const unlistenDone = listen<string>('download-done', async event => {
|
||||||
|
const versionName = event.payload
|
||||||
|
setDownloadProgress(prev => {
|
||||||
|
const downloaded = prev.find(d => d.version.version === versionName)
|
||||||
|
if (downloaded && downloadedVersionsConfig) {
|
||||||
|
const newDownloaded = DownloadedVersion.import(downloaded.version)
|
||||||
|
const updatedConfig = {
|
||||||
|
...downloadedVersionsConfig,
|
||||||
|
list: [...downloadedVersionsConfig.list, newDownloaded]
|
||||||
|
}
|
||||||
|
setDownloadedVersionsConfig(updatedConfig)
|
||||||
|
writeVersionsConfig(updatedConfig)
|
||||||
|
}
|
||||||
|
return prev.filter(d => d.version.version !== versionName)
|
||||||
|
})
|
||||||
|
activeDownloads.current--
|
||||||
|
runNext()
|
||||||
|
if (downloadProgress.length === 0) {
|
||||||
|
await notifyUser('Downloads Complete', 'All downloads have completed.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const unlistenFailed = listen<string>('download-failed', async event => {
|
||||||
|
const versionName = event.payload
|
||||||
|
setDownloadProgress(prev =>
|
||||||
|
prev.map(d =>
|
||||||
|
d.version.version === versionName ? { ...d, failed: true } : d
|
||||||
|
)
|
||||||
|
)
|
||||||
|
activeDownloads.current--
|
||||||
|
runNext()
|
||||||
|
await notifyUser(
|
||||||
|
'Download Failed',
|
||||||
|
`The download for version ${versionName} has failed.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const unlistenUninstalled = listen<string>(
|
||||||
|
'version-uninstalled',
|
||||||
|
async event => {
|
||||||
|
const versionName = event.payload
|
||||||
|
setDownloadedVersionsConfig(prev => {
|
||||||
|
if (!prev) return prev
|
||||||
|
const updatedList = prev.list.filter(
|
||||||
|
v => v.version.version !== versionName
|
||||||
|
)
|
||||||
|
const updatedConfig = { ...prev, list: updatedList }
|
||||||
|
writeVersionsConfig(updatedConfig)
|
||||||
|
setManagingVersion(null)
|
||||||
|
setFadeOut(true)
|
||||||
|
setTimeout(() => setShowPopup(false), 200)
|
||||||
|
return updatedConfig
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlistenProgress.then(f => f())
|
||||||
|
unlistenDone.then(f => f())
|
||||||
|
unlistenFailed.then(f => f())
|
||||||
|
unlistenUninstalled.then(f => f())
|
||||||
|
}
|
||||||
|
}, [downloadedVersionsConfig, downloadProgress, notifyUser])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = (e: MouseEvent) => e.preventDefault()
|
||||||
|
document.addEventListener('contextmenu', handler)
|
||||||
|
return () => document.removeEventListener('contextmenu', handler)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<html lang='en' className={roboto.className}>
|
||||||
|
<body>
|
||||||
|
{loading ? (
|
||||||
|
<div className='w-screen h-screen flex items-center justify-center'>
|
||||||
|
{outdated ? (
|
||||||
|
<div className='text-center'>
|
||||||
|
<p className='text-8xl mb-4'>Outdated Launcher!</p>
|
||||||
|
<p className='text-4xl mb-4'>
|
||||||
|
Please update to the latest version to continue.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className='button'
|
||||||
|
onClick={() =>
|
||||||
|
openUrl('https://berrydash.lncvrt.xyz/download')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Download latest version
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className='text-7xl text-center'>{loadingText}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={e => {
|
||||||
|
if (showPopup && e.key === 'Escape') {
|
||||||
|
setFadeOut(true)
|
||||||
|
setTimeout(() => setShowPopup(false), 200)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GlobalProvider
|
||||||
|
value={{
|
||||||
|
versionList,
|
||||||
|
setVersionList,
|
||||||
|
selectedVersionList,
|
||||||
|
setSelectedVersionList,
|
||||||
|
downloadProgress,
|
||||||
|
setDownloadProgress,
|
||||||
|
showPopup,
|
||||||
|
setShowPopup,
|
||||||
|
popupMode,
|
||||||
|
setPopupMode,
|
||||||
|
fadeOut,
|
||||||
|
setFadeOut,
|
||||||
|
downloadedVersionsConfig,
|
||||||
|
setDownloadedVersionsConfig,
|
||||||
|
normalConfig,
|
||||||
|
setNormalConfig,
|
||||||
|
managingVersion,
|
||||||
|
setManagingVersion
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sidebar />
|
||||||
|
</GlobalProvider>
|
||||||
|
<div
|
||||||
|
className='relative z-[2] ml-[239px] w-[761px] border-b border-[#242424] h-[33px] bg-[#161616]'
|
||||||
|
style={{
|
||||||
|
display: platform() == 'windows' ? 'block' : 'none'
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<div className='relative z-0'>
|
||||||
|
<main style={{ marginLeft: '15rem' }}>
|
||||||
|
<GlobalProvider
|
||||||
|
value={{
|
||||||
|
versionList,
|
||||||
|
setVersionList,
|
||||||
|
selectedVersionList,
|
||||||
|
setSelectedVersionList,
|
||||||
|
downloadProgress,
|
||||||
|
setDownloadProgress,
|
||||||
|
showPopup,
|
||||||
|
setShowPopup,
|
||||||
|
popupMode,
|
||||||
|
setPopupMode,
|
||||||
|
fadeOut,
|
||||||
|
setFadeOut,
|
||||||
|
downloadedVersionsConfig,
|
||||||
|
setDownloadedVersionsConfig,
|
||||||
|
normalConfig,
|
||||||
|
setNormalConfig,
|
||||||
|
managingVersion,
|
||||||
|
setManagingVersion
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</GlobalProvider>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
{showPopup && (
|
||||||
|
<div
|
||||||
|
className={`popup-overlay ${fadeOut ? 'fade-out' : ''}`}
|
||||||
|
onClick={handleOverlayClick}
|
||||||
|
>
|
||||||
|
<div className='popup-box'>
|
||||||
|
<button
|
||||||
|
className='close-button'
|
||||||
|
onClick={() => {
|
||||||
|
setFadeOut(true)
|
||||||
|
setTimeout(() => setShowPopup(false), 200)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faX} />
|
||||||
|
</button>
|
||||||
|
{popupMode === 0 ? (
|
||||||
|
<>
|
||||||
|
<p className='text-xl text-center'>
|
||||||
|
Select versions to download
|
||||||
|
</p>
|
||||||
|
<div className='popup-content'>
|
||||||
|
{versionList == null ? (
|
||||||
|
<p className='text-center'>
|
||||||
|
Getting version list...
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
versionList
|
||||||
|
.filter(
|
||||||
|
v =>
|
||||||
|
!downloadedVersionsConfig?.list.some(
|
||||||
|
dv => dv.version.version === v.version
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((v, i) => (
|
||||||
|
<div key={i} className='popup-entry'>
|
||||||
|
<p className='text-2xl'>
|
||||||
|
Berry Dash v{v.displayName}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className='button right-2 bottom-2'
|
||||||
|
onClick={() => {
|
||||||
|
if (!selectedVersionList) return
|
||||||
|
if (!selectedVersionList.includes(v)) {
|
||||||
|
setSelectedVersionList([
|
||||||
|
...selectedVersionList,
|
||||||
|
v
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
setSelectedVersionList(
|
||||||
|
selectedVersionList.filter(
|
||||||
|
x => x !== v
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedVersionList.includes(v) ? (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={faRemove} />{' '}
|
||||||
|
Remove
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={faAdd} /> Add
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : popupMode === 1 ? (
|
||||||
|
<>
|
||||||
|
<p className='text-xl text-center'>Downloads</p>
|
||||||
|
<div className='popup-content'>
|
||||||
|
{downloadProgress.length === 0 ? (
|
||||||
|
<p className='text-center mt-6'>
|
||||||
|
Nothing here...
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
downloadProgress.map((v, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className='popup-entry flex flex-col justify-between'
|
||||||
|
>
|
||||||
|
<p className='text-2xl'>
|
||||||
|
Berry Dash v{v.version.displayName}
|
||||||
|
</p>
|
||||||
|
<div className='mt-[25px] flex items-center justify-between'>
|
||||||
|
{v.failed ? (
|
||||||
|
<>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<span className='text-red-500'>
|
||||||
|
Download failed
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
className='button ml-30 mb-2'
|
||||||
|
onClick={() =>
|
||||||
|
setDownloadProgress(prev =>
|
||||||
|
prev.filter(
|
||||||
|
(_, idx) => idx !== i
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : v.queued ? (
|
||||||
|
<span className='text-yellow-500'>
|
||||||
|
Queued…
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
Downloading: {v.progress}% done
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : popupMode === 2 ? (
|
||||||
|
managingVersion ? (
|
||||||
|
<>
|
||||||
|
<p className='text-xl text-center'>
|
||||||
|
Manage version{' '}
|
||||||
|
{managingVersion.version.displayName}
|
||||||
|
</p>
|
||||||
|
<div className='popup-content flex flex-col items-center justify-center gap-2 h-full'>
|
||||||
|
<button
|
||||||
|
className='button'
|
||||||
|
onClick={() =>
|
||||||
|
invoke('uninstall_version', {
|
||||||
|
name: managingVersion.version.version
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Uninstall
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='button'
|
||||||
|
onClick={async () =>
|
||||||
|
invoke('open_folder', {
|
||||||
|
name: managingVersion.version.version
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Open Folder
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='button'
|
||||||
|
style={{
|
||||||
|
display:
|
||||||
|
platform() == 'macos' ? 'block' : 'none'
|
||||||
|
}}
|
||||||
|
onClick={async () => {
|
||||||
|
const exe =
|
||||||
|
managingVersion.version.executables[
|
||||||
|
managingVersion.version.platforms.indexOf(
|
||||||
|
platform()
|
||||||
|
)
|
||||||
|
]
|
||||||
|
await invoke('fix_mac_permissions', {
|
||||||
|
name: managingVersion.version.version,
|
||||||
|
executable: exe
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Fix permissions
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p className='text-xl text-center'>
|
||||||
|
No version selected
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
{popupMode == 0 && versionList != null && (
|
||||||
|
<div className='flex justify-center'>
|
||||||
|
<button
|
||||||
|
className='button w-fit mt-2 mb-[-16px]'
|
||||||
|
onClick={() => {
|
||||||
|
setFadeOut(true)
|
||||||
|
setTimeout(() => setShowPopup(false), 200)
|
||||||
|
downloadVersions(selectedVersionList)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Download {selectedVersionList.length} version
|
||||||
|
{selectedVersionList.length == 1 ? '' : 's'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='button w-fit mt-2 ml-2 mb-[-16px]'
|
||||||
|
onClick={() => {
|
||||||
|
const filtered = versionList.filter(
|
||||||
|
v =>
|
||||||
|
!downloadedVersionsConfig?.list.some(
|
||||||
|
dv => dv.version.version === v.version
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
selectedVersionList.length ===
|
||||||
|
filtered.length &&
|
||||||
|
filtered.every(v =>
|
||||||
|
selectedVersionList.includes(v)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
setSelectedVersionList([])
|
||||||
|
} else {
|
||||||
|
setSelectedVersionList(filtered)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedVersionList.length ===
|
||||||
|
versionList.filter(
|
||||||
|
v =>
|
||||||
|
!downloadedVersionsConfig?.list.some(
|
||||||
|
dv => dv.version.version === v.version
|
||||||
|
)
|
||||||
|
).length
|
||||||
|
? 'Deselect All'
|
||||||
|
: 'Select All'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'tailwindcss';
|
@import "tailwindcss";
|
||||||
|
|
||||||
.leaderboard-container {
|
.leaderboard-container {
|
||||||
@apply flex justify-center;
|
@apply flex justify-center;
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import './Leaderboards.css'
|
import './Leaderboards.css'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
@@ -1,24 +1,28 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { InstallsProps } from '../types/InstallsProps'
|
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
import './Installs.css'
|
import './Installs.css'
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
import { message } from '@tauri-apps/plugin-dialog'
|
import { message } from '@tauri-apps/plugin-dialog'
|
||||||
|
import { useGlobal } from './GlobalProvider'
|
||||||
|
|
||||||
|
export default function Installs () {
|
||||||
|
const {
|
||||||
|
downloadProgress,
|
||||||
|
showPopup,
|
||||||
|
setShowPopup,
|
||||||
|
setPopupMode,
|
||||||
|
setFadeOut,
|
||||||
|
setSelectedVersionList,
|
||||||
|
setVersionList,
|
||||||
|
downloadedVersionsConfig,
|
||||||
|
normalConfig,
|
||||||
|
setManagingVersion
|
||||||
|
} = useGlobal()
|
||||||
|
|
||||||
export default function Installs ({
|
|
||||||
downloadProgress,
|
|
||||||
showPopup,
|
|
||||||
setShowPopup,
|
|
||||||
setPopupMode,
|
|
||||||
setFadeOut,
|
|
||||||
setSelectedVersionList,
|
|
||||||
setVersionList,
|
|
||||||
downloadedVersionsConfig,
|
|
||||||
normalConfig,
|
|
||||||
setManagingVersion
|
|
||||||
}: InstallsProps) {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showPopup) return
|
if (!showPopup) return
|
||||||
setSelectedVersionList([])
|
setSelectedVersionList([])
|
||||||
@@ -45,7 +49,7 @@ export default function Installs ({
|
|||||||
setVersionList([])
|
setVersionList([])
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
}, [showPopup])
|
}, [normalConfig, setSelectedVersionList, setVersionList, showPopup])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mx-4 mt-4'>
|
<div className='mx-4 mt-4'>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Setting } from '../componets/Setting'
|
import { Setting } from '../componets/Setting'
|
||||||
import { writeNormalConfig } from '../util/BazookaManager'
|
import { writeNormalConfig } from '../util/BazookaManager'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
import { SettingsProps } from '../types/SettingsProps'
|
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
import { useGlobal } from '../GlobalProvider'
|
||||||
|
|
||||||
export default function Settings ({ normalConfig }: SettingsProps) {
|
export default function Settings () {
|
||||||
const [checkForNewVersionOnLoad, setCheckForNewVersionOnLoad] =
|
const [checkForNewVersionOnLoad, setCheckForNewVersionOnLoad] =
|
||||||
useState(false)
|
useState(false)
|
||||||
const [allowNotifications, setAllowNotifications] = useState(false)
|
const [allowNotifications, setAllowNotifications] = useState(false)
|
||||||
@@ -13,6 +15,7 @@ export default function Settings ({ normalConfig }: SettingsProps) {
|
|||||||
const [useWindowsRoundedCorners, setUseWindowsRoundedCorners] =
|
const [useWindowsRoundedCorners, setUseWindowsRoundedCorners] =
|
||||||
useState(false)
|
useState(false)
|
||||||
const [loaded, setLoaded] = useState(false)
|
const [loaded, setLoaded] = useState(false)
|
||||||
|
const { normalConfig } = useGlobal()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
@@ -31,7 +34,7 @@ export default function Settings ({ normalConfig }: SettingsProps) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
}, [])
|
}, [normalConfig])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
import { SettingsType } from './SettingsType'
|
import { SettingsType } from './SettingsType'
|
||||||
|
|
||||||
|
type NormalConfigData = {
|
||||||
|
version: string
|
||||||
|
settings?: Partial<SettingsType>
|
||||||
|
}
|
||||||
|
|
||||||
export class NormalConfig {
|
export class NormalConfig {
|
||||||
constructor (
|
constructor (
|
||||||
public version: string,
|
public version: string,
|
||||||
public settings: SettingsType = new SettingsType()
|
public settings: SettingsType = new SettingsType()
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
static import (data: any) {
|
static import (data: NormalConfigData) {
|
||||||
const cfg = new NormalConfig(data.version)
|
const cfg = new NormalConfig(data.version)
|
||||||
Object.assign(cfg.settings, data.settings)
|
if (data.settings) {
|
||||||
|
cfg.settings = { ...cfg.settings, ...data.settings }
|
||||||
|
}
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
import { DownloadedVersion } from './DownloadedVersion'
|
import { DownloadedVersion } from './DownloadedVersion'
|
||||||
|
|
||||||
|
type VersionsConfigData = {
|
||||||
|
version: string
|
||||||
|
list: DownloadedVersion[]
|
||||||
|
}
|
||||||
|
|
||||||
export class VersionsConfig {
|
export class VersionsConfig {
|
||||||
constructor (public version: string, public list: DownloadedVersion[] = []) {}
|
constructor (public version: string, public list: DownloadedVersion[] = []) {}
|
||||||
|
|
||||||
static import (data: any) {
|
static import (data: VersionsConfigData) {
|
||||||
const cfg = new VersionsConfig(data.version)
|
const cfg = new VersionsConfig(data.version)
|
||||||
Object.assign(cfg.list, data.list)
|
cfg.list = [...data.list]
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,8 +37,10 @@ export async function readNormalConfig (): Promise<NormalConfig> {
|
|||||||
return new NormalConfig(version)
|
return new NormalConfig(version)
|
||||||
}
|
}
|
||||||
const config = await readTextFile('config.dat', options)
|
const config = await readTextFile('config.dat', options)
|
||||||
return NormalConfig.import(JSON.parse(await decrypt(config, await getKey(2))))
|
return NormalConfig.import(
|
||||||
} catch (_) {
|
JSON.parse(await decrypt(config, await getKey(2)))
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
return new NormalConfig(version)
|
return new NormalConfig(version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +101,7 @@ export async function readVersionsConfig (): Promise<VersionsConfig> {
|
|||||||
return VersionsConfig.import(
|
return VersionsConfig.import(
|
||||||
JSON.parse(await decrypt(config, await getKey(3)))
|
JSON.parse(await decrypt(config, await getKey(3)))
|
||||||
)
|
)
|
||||||
} catch (_) {
|
} catch {
|
||||||
return new VersionsConfig(version)
|
return new VersionsConfig(version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
554
src/main.tsx
554
src/main.tsx
@@ -1,554 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
|
||||||
import ReactDOM from 'react-dom/client'
|
|
||||||
import Installs from './routes/Installs'
|
|
||||||
import Settings from './routes/Settings'
|
|
||||||
import Sidebar from './componets/Sidebar'
|
|
||||||
import './Globals.css'
|
|
||||||
import { LauncherVersion } from './types/LauncherVersion'
|
|
||||||
import { DownloadProgress } from './types/DownloadProgress'
|
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
|
||||||
import { listen } from '@tauri-apps/api/event'
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
||||||
import { faAdd, faRemove, faX } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import '@fontsource/roboto'
|
|
||||||
import Leaderboards from './routes/Leaderboards'
|
|
||||||
import {
|
|
||||||
isPermissionGranted,
|
|
||||||
requestPermission,
|
|
||||||
sendNotification
|
|
||||||
} from '@tauri-apps/plugin-notification'
|
|
||||||
import {
|
|
||||||
readNormalConfig,
|
|
||||||
readVersionsConfig,
|
|
||||||
writeVersionsConfig
|
|
||||||
} from './util/BazookaManager'
|
|
||||||
import { VersionsConfig } from './types/VersionsConfig'
|
|
||||||
import { DownloadedVersion } from './types/DownloadedVersion'
|
|
||||||
import { NormalConfig } from './types/NormalConfig'
|
|
||||||
import { app } from '@tauri-apps/api'
|
|
||||||
import axios from 'axios'
|
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
|
||||||
|
|
||||||
function App () {
|
|
||||||
const [hash, setHash] = useState(window.location.hash || '#installs')
|
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
const [loadingText, setLoadingText] = useState('Loading...')
|
|
||||||
const [outdated, setOutdated] = useState(false)
|
|
||||||
const [versionList, setVersionList] = useState<null | LauncherVersion[]>(null)
|
|
||||||
const [selectedVersionList, setSelectedVersionList] = useState<
|
|
||||||
LauncherVersion[]
|
|
||||||
>([])
|
|
||||||
const [downloadProgress, setDownloadProgress] = useState<DownloadProgress[]>(
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
const [showPopup, setShowPopup] = useState(false)
|
|
||||||
const [popupMode, setPopupMode] = useState<null | number>(null)
|
|
||||||
const [fadeOut, setFadeOut] = useState(false)
|
|
||||||
const activeDownloads = useRef(0)
|
|
||||||
const queue = useRef<(() => void)[]>([])
|
|
||||||
const [downloadedVersionsConfig, setDownloadedVersionsConfig] =
|
|
||||||
useState<VersionsConfig | null>(null)
|
|
||||||
const [normalConfig, setNormalConfig] = useState<NormalConfig | null>(null)
|
|
||||||
const [managingVersion, setManagingVersion] =
|
|
||||||
useState<DownloadedVersion | null>(null)
|
|
||||||
|
|
||||||
function runNext () {
|
|
||||||
if (activeDownloads.current === 0 && queue.current.length === 0) {
|
|
||||||
setFadeOut(true)
|
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (activeDownloads.current >= 3 || queue.current.length === 0) return
|
|
||||||
activeDownloads.current++
|
|
||||||
const next = queue.current.shift()
|
|
||||||
next?.()
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
;(async () => {
|
|
||||||
setLoadingText('Checking latest version...')
|
|
||||||
try {
|
|
||||||
const response = await axios.get(
|
|
||||||
'https://berrydash.lncvrt.xyz/database/launcher/latest.php'
|
|
||||||
)
|
|
||||||
const client = await app.getVersion()
|
|
||||||
if (response.data !== client) {
|
|
||||||
setOutdated(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setLoadingText('Failed to check latest version.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setLoadingText('Loading configs...')
|
|
||||||
const normalConfig = await readNormalConfig()
|
|
||||||
const versionsConfig = await readVersionsConfig()
|
|
||||||
setDownloadedVersionsConfig(versionsConfig)
|
|
||||||
setNormalConfig(normalConfig)
|
|
||||||
if (platform() == 'windows') {
|
|
||||||
invoke('windows_rounded_corners', {
|
|
||||||
enabled: normalConfig.settings.useWindowsRoundedCorners
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setLoading(false)
|
|
||||||
})()
|
|
||||||
}, [])
|
|
||||||
useEffect(() => {
|
|
||||||
const unlistenProgress = listen<string>('download-progress', event => {
|
|
||||||
const [versionName, progStr] = event.payload.split(':')
|
|
||||||
const prog = Number(progStr)
|
|
||||||
|
|
||||||
setDownloadProgress(prev => {
|
|
||||||
const i = prev.findIndex(d => d.version.version === versionName)
|
|
||||||
if (i === -1) return prev
|
|
||||||
const copy = [...prev]
|
|
||||||
copy[i] = { ...copy[i], progress: prog }
|
|
||||||
return copy
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const unlistenDone = listen<string>('download-done', async event => {
|
|
||||||
const versionName = event.payload
|
|
||||||
setDownloadProgress(prev => {
|
|
||||||
const downloaded = prev.find(d => d.version.version === versionName)
|
|
||||||
if (downloaded && downloadedVersionsConfig) {
|
|
||||||
const newDownloaded = DownloadedVersion.import(downloaded.version)
|
|
||||||
const updatedConfig = {
|
|
||||||
...downloadedVersionsConfig,
|
|
||||||
list: [...downloadedVersionsConfig.list, newDownloaded]
|
|
||||||
}
|
|
||||||
setDownloadedVersionsConfig(updatedConfig)
|
|
||||||
writeVersionsConfig(updatedConfig)
|
|
||||||
}
|
|
||||||
return prev.filter(d => d.version.version !== versionName)
|
|
||||||
})
|
|
||||||
activeDownloads.current--
|
|
||||||
runNext()
|
|
||||||
if (downloadProgress.length === 0) {
|
|
||||||
await notifyUser('Downloads Complete', 'All downloads have completed.')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const unlistenFailed = listen<string>('download-failed', async event => {
|
|
||||||
const versionName = event.payload
|
|
||||||
setDownloadProgress(prev =>
|
|
||||||
prev.map(d =>
|
|
||||||
d.version.version === versionName ? { ...d, failed: true } : d
|
|
||||||
)
|
|
||||||
)
|
|
||||||
activeDownloads.current--
|
|
||||||
runNext()
|
|
||||||
await notifyUser(
|
|
||||||
'Download Failed',
|
|
||||||
`The download for version ${versionName} has failed.`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const unlistenUninstalled = listen<string>(
|
|
||||||
'version-uninstalled',
|
|
||||||
async event => {
|
|
||||||
const versionName = event.payload
|
|
||||||
setDownloadedVersionsConfig(prev => {
|
|
||||||
if (!prev) return prev
|
|
||||||
const updatedList = prev.list.filter(
|
|
||||||
v => v.version.version !== versionName
|
|
||||||
)
|
|
||||||
const updatedConfig = { ...prev, list: updatedList }
|
|
||||||
writeVersionsConfig(updatedConfig)
|
|
||||||
setManagingVersion(null)
|
|
||||||
setFadeOut(true)
|
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
|
||||||
return updatedConfig
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unlistenProgress.then(f => f())
|
|
||||||
unlistenDone.then(f => f())
|
|
||||||
unlistenFailed.then(f => f())
|
|
||||||
unlistenUninstalled.then(f => f())
|
|
||||||
}
|
|
||||||
}, [downloadedVersionsConfig])
|
|
||||||
|
|
||||||
async function downloadVersions (versions: LauncherVersion[]) {
|
|
||||||
while (normalConfig != null) {
|
|
||||||
const useWine = normalConfig.settings.useWineOnUnixWhenNeeded
|
|
||||||
const p = platform()
|
|
||||||
const newDownloads = versions.map(
|
|
||||||
v => new DownloadProgress(v, 0, false, true)
|
|
||||||
)
|
|
||||||
setDownloadProgress(prev => [...prev, ...newDownloads])
|
|
||||||
|
|
||||||
newDownloads.forEach(download => {
|
|
||||||
let plat = p
|
|
||||||
if (p === 'linux' && useWine) {
|
|
||||||
if (
|
|
||||||
!download.version.platforms.includes(p) &&
|
|
||||||
download.version.platforms.includes('windows')
|
|
||||||
) {
|
|
||||||
plat = 'windows'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const idx = download.version.platforms.indexOf(plat)
|
|
||||||
const url = download.version.downloadUrls[idx]
|
|
||||||
const exe = download.version.executables[idx]
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
setDownloadProgress(prev =>
|
|
||||||
prev.map(d =>
|
|
||||||
d.version.version === download.version.version
|
|
||||||
? { ...d, failed: true }
|
|
||||||
: d
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const task = () => {
|
|
||||||
setDownloadProgress(prev => {
|
|
||||||
const i = prev.findIndex(
|
|
||||||
d => d.version.version === download.version.version
|
|
||||||
)
|
|
||||||
if (i === -1) return prev
|
|
||||||
const copy = [...prev]
|
|
||||||
copy[i] = { ...copy[i], queued: false }
|
|
||||||
return copy
|
|
||||||
})
|
|
||||||
|
|
||||||
invoke('download', {
|
|
||||||
url,
|
|
||||||
name: download.version.version,
|
|
||||||
executable: exe
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.current.push(task)
|
|
||||||
runNext()
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOverlayClick (e: React.MouseEvent<HTMLDivElement>) {
|
|
||||||
if (e.target === e.currentTarget) {
|
|
||||||
setFadeOut(true)
|
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function notifyUser (title: string, body: string) {
|
|
||||||
while (normalConfig != null) {
|
|
||||||
if (!normalConfig.settings.allowNotifications) return
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let permissionGranted = await isPermissionGranted()
|
|
||||||
if (!permissionGranted) {
|
|
||||||
const permission = await requestPermission()
|
|
||||||
permissionGranted = permission === 'granted'
|
|
||||||
}
|
|
||||||
if (permissionGranted) {
|
|
||||||
sendNotification({ title, body })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onHashChange = () => setHash(window.location.hash || '#installs')
|
|
||||||
window.addEventListener('hashchange', onHashChange)
|
|
||||||
return () => window.removeEventListener('hashchange', onHashChange)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handler = (e: MouseEvent) => e.preventDefault()
|
|
||||||
document.addEventListener('contextmenu', handler)
|
|
||||||
return () => document.removeEventListener('contextmenu', handler)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
function renderContent () {
|
|
||||||
if (hash === '#installs') {
|
|
||||||
return (
|
|
||||||
<Installs
|
|
||||||
downloadProgress={downloadProgress}
|
|
||||||
showPopup={showPopup}
|
|
||||||
setShowPopup={setShowPopup}
|
|
||||||
setPopupMode={setPopupMode}
|
|
||||||
setFadeOut={setFadeOut}
|
|
||||||
setSelectedVersionList={setSelectedVersionList}
|
|
||||||
setVersionList={setVersionList}
|
|
||||||
downloadedVersionsConfig={downloadedVersionsConfig}
|
|
||||||
normalConfig={normalConfig}
|
|
||||||
setManagingVersion={setManagingVersion}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (hash === '#settings') {
|
|
||||||
return <Settings normalConfig={normalConfig} />
|
|
||||||
} else if (hash === '#leaderboards') {
|
|
||||||
return <Leaderboards />
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return loading ? (
|
|
||||||
<div className='w-screen h-screen flex items-center justify-center'>
|
|
||||||
{outdated ? (
|
|
||||||
<div className='text-center'>
|
|
||||||
<p className='text-8xl mb-4'>Outdated Launcher!</p>
|
|
||||||
<p className='text-4xl mb-4'>
|
|
||||||
Please update to the latest version to continue.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
className='button'
|
|
||||||
onClick={() => openUrl('https://berrydash.lncvrt.xyz/download')}
|
|
||||||
>
|
|
||||||
Download latest version
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className='text-7xl text-center'>{loadingText}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
tabIndex={0}
|
|
||||||
onKeyDown={e => {
|
|
||||||
if (showPopup && e.key === 'Escape') {
|
|
||||||
setFadeOut(true)
|
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Sidebar
|
|
||||||
setShowPopup={setShowPopup}
|
|
||||||
setPopupMode={setPopupMode}
|
|
||||||
setFadeOut={setFadeOut}
|
|
||||||
downloadProgress={downloadProgress}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className='relative z-[2] ml-[239px] w-[761px] border-b border-[#242424] h-[33px] bg-[#161616]'
|
|
||||||
style={{ display: platform() == 'windows' ? 'block' : 'none' }}
|
|
||||||
></div>
|
|
||||||
<div className='relative z-0'>
|
|
||||||
<main style={{ marginLeft: '15rem' }}>{renderContent()}</main>
|
|
||||||
</div>
|
|
||||||
{showPopup && (
|
|
||||||
<div
|
|
||||||
className={`popup-overlay ${fadeOut ? 'fade-out' : ''}`}
|
|
||||||
onClick={handleOverlayClick}
|
|
||||||
>
|
|
||||||
<div className='popup-box'>
|
|
||||||
<button
|
|
||||||
className='close-button'
|
|
||||||
onClick={() => {
|
|
||||||
setFadeOut(true)
|
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faX} />
|
|
||||||
</button>
|
|
||||||
{popupMode === 0 ? (
|
|
||||||
<>
|
|
||||||
<p className='text-xl text-center'>
|
|
||||||
Select versions to download
|
|
||||||
</p>
|
|
||||||
<div className='popup-content'>
|
|
||||||
{versionList == null ? (
|
|
||||||
<p className='text-center'>Getting version list...</p>
|
|
||||||
) : (
|
|
||||||
versionList
|
|
||||||
.filter(
|
|
||||||
v =>
|
|
||||||
!downloadedVersionsConfig?.list.some(
|
|
||||||
dv => dv.version.version === v.version
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.map((v, i) => (
|
|
||||||
<div key={i} className='popup-entry'>
|
|
||||||
<p className='text-2xl'>
|
|
||||||
Berry Dash v{v.displayName}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
className='button right-2 bottom-2'
|
|
||||||
onClick={() => {
|
|
||||||
if (!selectedVersionList) return
|
|
||||||
if (!selectedVersionList.includes(v)) {
|
|
||||||
setSelectedVersionList([
|
|
||||||
...selectedVersionList,
|
|
||||||
v
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
setSelectedVersionList(
|
|
||||||
selectedVersionList.filter(x => x !== v)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selectedVersionList.includes(v) ? (
|
|
||||||
<>
|
|
||||||
<FontAwesomeIcon icon={faRemove} /> Remove
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<FontAwesomeIcon icon={faAdd} /> Add
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : popupMode === 1 ? (
|
|
||||||
<>
|
|
||||||
<p className='text-xl text-center'>Downloads</p>
|
|
||||||
<div className='popup-content'>
|
|
||||||
{downloadProgress.length === 0 ? (
|
|
||||||
<p className='text-center mt-6'>Nothing here...</p>
|
|
||||||
) : (
|
|
||||||
downloadProgress.map((v, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className='popup-entry flex flex-col justify-between'
|
|
||||||
>
|
|
||||||
<p className='text-2xl'>
|
|
||||||
Berry Dash v{v.version.displayName}
|
|
||||||
</p>
|
|
||||||
<div className='mt-[25px] flex items-center justify-between'>
|
|
||||||
{v.failed ? (
|
|
||||||
<>
|
|
||||||
<div className='flex items-center'>
|
|
||||||
<span className='text-red-500'>
|
|
||||||
Download failed
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
className='button ml-30 mb-2'
|
|
||||||
onClick={() =>
|
|
||||||
setDownloadProgress(prev =>
|
|
||||||
prev.filter((_, idx) => idx !== i)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : v.queued ? (
|
|
||||||
<span className='text-yellow-500'>Queued…</span>
|
|
||||||
) : (
|
|
||||||
<span>Downloading: {v.progress}% done</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : popupMode === 2 ? (
|
|
||||||
managingVersion ? (
|
|
||||||
<>
|
|
||||||
<p className='text-xl text-center'>
|
|
||||||
Manage version {managingVersion.version.displayName}
|
|
||||||
</p>
|
|
||||||
<div className='popup-content flex flex-col items-center justify-center gap-2 h-full'>
|
|
||||||
<button
|
|
||||||
className='button'
|
|
||||||
onClick={() =>
|
|
||||||
invoke('uninstall_version', {
|
|
||||||
name: managingVersion.version.version
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Uninstall
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className='button'
|
|
||||||
onClick={async () =>
|
|
||||||
invoke('open_folder', {
|
|
||||||
name: managingVersion.version.version
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Open Folder
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className='button'
|
|
||||||
style={{
|
|
||||||
display: platform() == 'macos' ? 'block' : 'none'
|
|
||||||
}}
|
|
||||||
onClick={async () => {
|
|
||||||
const exe =
|
|
||||||
managingVersion.version.executables[
|
|
||||||
managingVersion.version.platforms.indexOf(
|
|
||||||
platform()
|
|
||||||
)
|
|
||||||
]
|
|
||||||
await invoke('fix_mac_permissions', {
|
|
||||||
name: managingVersion.version.version,
|
|
||||||
executable: exe
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Fix permissions
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<p className='text-xl text-center'>No version selected</p>
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
{popupMode == 0 && versionList != null && (
|
|
||||||
<div className='flex justify-center'>
|
|
||||||
<button
|
|
||||||
className='button w-fit mt-2 mb-[-16px]'
|
|
||||||
onClick={() => {
|
|
||||||
setFadeOut(true)
|
|
||||||
setTimeout(() => setShowPopup(false), 200)
|
|
||||||
downloadVersions(selectedVersionList)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Download {selectedVersionList.length} version
|
|
||||||
{selectedVersionList.length == 1 ? '' : 's'}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className='button w-fit mt-2 ml-2 mb-[-16px]'
|
|
||||||
onClick={() => {
|
|
||||||
const filtered = versionList.filter(
|
|
||||||
v =>
|
|
||||||
!downloadedVersionsConfig?.list.some(
|
|
||||||
dv => dv.version.version === v.version
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
selectedVersionList.length === filtered.length &&
|
|
||||||
filtered.every(v => selectedVersionList.includes(v))
|
|
||||||
) {
|
|
||||||
setSelectedVersionList([])
|
|
||||||
} else {
|
|
||||||
setSelectedVersionList(filtered)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selectedVersionList.length ===
|
|
||||||
versionList.filter(
|
|
||||||
v =>
|
|
||||||
!downloadedVersionsConfig?.list.some(
|
|
||||||
dv => dv.version.version === v.version
|
|
||||||
)
|
|
||||||
).length
|
|
||||||
? 'Deselect All'
|
|
||||||
: 'Select All'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
|
||||||
<App />
|
|
||||||
)
|
|
||||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
@@ -1,25 +1,27 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2017",
|
||||||
"useDefineForClassFields": true,
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
"allowJs": true,
|
||||||
"module": "ESNext",
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
/* Bundler mode */
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"jsx": "preserve",
|
||||||
"jsx": "react-jsx",
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
/* Linting */
|
{
|
||||||
"strict": true,
|
"name": "next"
|
||||||
"noUnusedLocals": true,
|
}
|
||||||
"noUnusedParameters": true,
|
],
|
||||||
"noFallthroughCasesInSwitch": true
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { defineConfig } from "vite";
|
|
||||||
import react from "@vitejs/plugin-react";
|
|
||||||
|
|
||||||
// @ts-expect-error process is a nodejs global
|
|
||||||
const host = process.env.TAURI_DEV_HOST;
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig(async () => ({
|
|
||||||
plugins: [react()],
|
|
||||||
|
|
||||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
|
||||||
//
|
|
||||||
// 1. prevent vite from obscuring rust errors
|
|
||||||
clearScreen: false,
|
|
||||||
// 2. tauri expects a fixed port, fail if that port is not available
|
|
||||||
server: {
|
|
||||||
port: 1420,
|
|
||||||
strictPort: true,
|
|
||||||
host: host || false,
|
|
||||||
hmr: host
|
|
||||||
? {
|
|
||||||
protocol: "ws",
|
|
||||||
host,
|
|
||||||
port: 1421,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
watch: {
|
|
||||||
// 3. tell vite to ignore watching `src-tauri`
|
|
||||||
ignored: ["**/src-tauri/**"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
Reference in New Issue
Block a user