Settings, queue fixes, and more
This commit is contained in:
@@ -17,7 +17,7 @@ tauri-plugin-opener = "2.4.0"
|
|||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.141"
|
serde_json = "1.0.141"
|
||||||
tauri-plugin-os = "2.3.0"
|
tauri-plugin-os = "2.3.0"
|
||||||
reqwest = { version = "0.12.22", features = ["stream"] }
|
reqwest = { version = "0.12.22", default-features = false, features = ["stream", "rustls-tls"] }
|
||||||
tokio = "1.46.1"
|
tokio = "1.46.1"
|
||||||
futures-util = { version = "0.3.31", features = ["io"] }
|
futures-util = { version = "0.3.31", features = ["io"] }
|
||||||
tauri-plugin-decorum = "1.1.1"
|
tauri-plugin-decorum = "1.1.1"
|
||||||
|
|||||||
17
src/componets/Setting.css
Normal file
17
src/componets/Setting.css
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@import 'tailwindcss';
|
||||||
|
|
||||||
|
.setting-checkbox-wrapper {
|
||||||
|
@apply relative w-5 h-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-checkbox {
|
||||||
|
@apply appearance-none w-full h-full border-2 border-[#484848] rounded-md bg-[#242424] transition-colors duration-200 cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-checkbox:checked {
|
||||||
|
@apply bg-blue-500 border-blue-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-check-icon {
|
||||||
|
@apply absolute top-1/2 left-1/2 text-white text-[11px] pointer-events-none -translate-x-2/4 -translate-y-2/4;
|
||||||
|
}
|
||||||
21
src/componets/Setting.tsx
Normal file
21
src/componets/Setting.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { SettingProps } from '../types/SettingProps'
|
||||||
|
import './Setting.css'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
export function Setting ({ label, value, onChange, className }: SettingProps) {
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center gap-2 mb-2 ${className}`}>
|
||||||
|
<label className='text-white text-lg'>{label}</label>
|
||||||
|
<div className='setting-checkbox-wrapper'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
className='setting-checkbox'
|
||||||
|
checked={value}
|
||||||
|
onChange={() => onChange(!value)}
|
||||||
|
/>
|
||||||
|
{value && <FontAwesomeIcon icon={faCheck} className='fa-check-icon' />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export enum Keys {
|
export enum Keys {
|
||||||
SERVER_RECEIVE_TRANSFER_KEY = '',
|
SERVER_RECEIVE_TRANSFER_KEY = '',
|
||||||
SERVER_SEND_TRANSFER_KEY = '',
|
SERVER_SEND_TRANSFER_KEY = '',
|
||||||
CONFIG_ENCRYPTION_KEY = ''
|
CONFIG_ENCRYPTION_KEY = '',
|
||||||
|
VERSIONS_ENCRYPTION_KEY = ''
|
||||||
}
|
}
|
||||||
|
|||||||
60
src/main.tsx
60
src/main.tsx
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import Installs from './routes/Installs'
|
import Installs from './routes/Installs'
|
||||||
import Settings from './routes/Settings'
|
import Settings from './routes/Settings'
|
||||||
@@ -14,6 +14,7 @@ import { faAdd, faRemove, faX } from '@fortawesome/free-solid-svg-icons'
|
|||||||
import '@fontsource/roboto'
|
import '@fontsource/roboto'
|
||||||
import Leaderboards from './routes/Leaderboards'
|
import Leaderboards from './routes/Leaderboards'
|
||||||
import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/plugin-notification'
|
import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/plugin-notification'
|
||||||
|
import { readNormalConfig, readVersionsConfig, writeVersionsConfig } from './util/BazookaManager'
|
||||||
|
|
||||||
function App () {
|
function App () {
|
||||||
const [hash, setHash] = useState(window.location.hash || '#installs')
|
const [hash, setHash] = useState(window.location.hash || '#installs')
|
||||||
@@ -23,13 +24,13 @@ function App () {
|
|||||||
const [showPopup, setShowPopup] = useState(false)
|
const [showPopup, setShowPopup] = useState(false)
|
||||||
const [popupMode, setPopupMode] = useState<null | number>(null)
|
const [popupMode, setPopupMode] = useState<null | number>(null)
|
||||||
const [fadeOut, setFadeOut] = useState(false)
|
const [fadeOut, setFadeOut] = useState(false)
|
||||||
let activeDownloads = 0
|
const activeDownloads = useRef(0)
|
||||||
const queue: (() => void)[] = []
|
const queue = useRef<(() => void)[]>([])
|
||||||
|
|
||||||
function runNext() {
|
function runNext() {
|
||||||
if (activeDownloads >= 3 || queue.length === 0) return
|
if (activeDownloads.current >= 3 || queue.current.length === 0) return
|
||||||
activeDownloads++
|
activeDownloads.current++
|
||||||
const next = queue.shift()
|
const next = queue.current.shift()
|
||||||
next?.()
|
next?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,8 +50,17 @@ function App () {
|
|||||||
|
|
||||||
const unlistenDone = listen<string>('download-done', async (event) => {
|
const unlistenDone = listen<string>('download-done', async (event) => {
|
||||||
const versionName = event.payload
|
const versionName = event.payload
|
||||||
setDownloadProgress(prev => prev.filter(d => d.version.version !== versionName))
|
setDownloadProgress(prev => {
|
||||||
activeDownloads--
|
const downloaded = prev.find(d => d.version.version === versionName)
|
||||||
|
if (downloaded) {
|
||||||
|
readVersionsConfig().then(cfg => {
|
||||||
|
cfg.list.push(downloaded.version)
|
||||||
|
writeVersionsConfig(cfg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return prev.filter(d => d.version.version !== versionName)
|
||||||
|
})
|
||||||
|
activeDownloads.current--
|
||||||
runNext()
|
runNext()
|
||||||
if (downloadProgress.length === 0) {
|
if (downloadProgress.length === 0) {
|
||||||
await notifyUser('Downloads Complete', 'All downloads have completed.')
|
await notifyUser('Downloads Complete', 'All downloads have completed.')
|
||||||
@@ -64,7 +74,7 @@ function App () {
|
|||||||
d.version.version === versionName ? { ...d, failed: true } : d
|
d.version.version === versionName ? { ...d, failed: true } : d
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
activeDownloads--
|
activeDownloads.current--
|
||||||
runNext()
|
runNext()
|
||||||
await notifyUser('Download Failed', `The download for version ${versionName} has failed.`)
|
await notifyUser('Download Failed', `The download for version ${versionName} has failed.`)
|
||||||
})
|
})
|
||||||
@@ -76,11 +86,33 @@ function App () {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
function downloadVersions(versions: LauncherVersion[]) {
|
async function downloadVersions(versions: LauncherVersion[]) {
|
||||||
|
const config = await readNormalConfig()
|
||||||
|
const useWine = config.settings.useWineOnUnixWhenNeeded
|
||||||
|
const p = platform()
|
||||||
const newDownloads = versions.map(v => new DownloadProgress(v, 0, false, true))
|
const newDownloads = versions.map(v => new DownloadProgress(v, 0, false, true))
|
||||||
setDownloadProgress(prev => [...prev, ...newDownloads])
|
setDownloadProgress(prev => [...prev, ...newDownloads])
|
||||||
|
|
||||||
newDownloads.forEach(download => {
|
newDownloads.forEach(download => {
|
||||||
|
let plat = p
|
||||||
|
if ((p === 'macos' || 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 = () => {
|
const task = () => {
|
||||||
setDownloadProgress(prev => {
|
setDownloadProgress(prev => {
|
||||||
const i = prev.findIndex(d => d.version.version === download.version.version)
|
const i = prev.findIndex(d => d.version.version === download.version.version)
|
||||||
@@ -91,13 +123,13 @@ function App () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
invoke('download', {
|
invoke('download', {
|
||||||
url: download.version.downloadUrls[download.version.platforms.indexOf(platform())],
|
url,
|
||||||
name: download.version.version,
|
name: download.version.version,
|
||||||
executable: download.version.executables[download.version.platforms.indexOf(platform())]
|
executable: exe
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.push(task)
|
queue.current.push(task)
|
||||||
runNext()
|
runNext()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -110,6 +142,8 @@ function App () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function notifyUser(title: string, body: string) {
|
async function notifyUser(title: string, body: string) {
|
||||||
|
const config = await readNormalConfig()
|
||||||
|
if (!config.settings.allowNotifications) return
|
||||||
let permissionGranted = await isPermissionGranted();
|
let permissionGranted = await isPermissionGranted();
|
||||||
if (!permissionGranted) {
|
if (!permissionGranted) {
|
||||||
const permission = await requestPermission();
|
const permission = await requestPermission();
|
||||||
|
|||||||
@@ -2,15 +2,31 @@ import { useEffect } from 'react'
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { InstallsProps } from '../types/InstallsProps'
|
import { InstallsProps } from '../types/InstallsProps'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
|
import { readNormalConfig } from '../util/BazookaManager'
|
||||||
|
|
||||||
export default function Installs({ downloadProgress, showPopup, setShowPopup, setPopupMode, setFadeOut, setSelectedVersionList, setVersionList }: InstallsProps) {
|
export default function Installs({ downloadProgress, showPopup, setShowPopup, setPopupMode, setFadeOut, setSelectedVersionList, setVersionList }: InstallsProps) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showPopup) return
|
if (!showPopup) return
|
||||||
setSelectedVersionList([]);
|
setSelectedVersionList([])
|
||||||
setVersionList(null)
|
setVersionList(null)
|
||||||
axios.get('https://berrydash.lncvrt.xyz/database/launcher/versions.php')
|
;(async () => {
|
||||||
.then(res => setVersionList(res.data.filter((d: { platforms: string[] }) => d.platforms.includes(platform()))))
|
try {
|
||||||
.catch(() => setVersionList([]))
|
const config = await readNormalConfig()
|
||||||
|
const useWine = config.settings.useWineOnUnixWhenNeeded
|
||||||
|
const res = await axios.get('https://berrydash.lncvrt.xyz/database/launcher/versions.php')
|
||||||
|
const p = platform()
|
||||||
|
const filtered = res.data.filter((d: { platforms: string[] }) =>
|
||||||
|
p === 'macos' || p === 'linux'
|
||||||
|
? useWine
|
||||||
|
? d.platforms.includes('windows') || d.platforms.includes(p)
|
||||||
|
: d.platforms.includes(p)
|
||||||
|
: d.platforms.includes(p)
|
||||||
|
)
|
||||||
|
setVersionList(filtered)
|
||||||
|
} catch {
|
||||||
|
setVersionList([])
|
||||||
|
}
|
||||||
|
})()
|
||||||
}, [showPopup])
|
}, [showPopup])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,59 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Setting } from '../componets/Setting'
|
||||||
|
import { readNormalConfig, writeNormalConfig } from '../util/BazookaManager'
|
||||||
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
|
|
||||||
export default function Settings () {
|
export default function Settings () {
|
||||||
|
const [checkForNewVersionOnLoad, setCheckForNewVersionOnLoad] =
|
||||||
|
useState(false)
|
||||||
|
const [useWineOnUnixWhenNeeded, setUseWineOnUnixWhenNeeded] = useState(false)
|
||||||
|
const [allowNotifications, setAllowNotifications] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
;(async () => {
|
||||||
|
const config = await readNormalConfig()
|
||||||
|
setCheckForNewVersionOnLoad(config.settings.checkForNewVersionOnLoad)
|
||||||
|
setUseWineOnUnixWhenNeeded(config.settings.useWineOnUnixWhenNeeded)
|
||||||
|
setAllowNotifications(config.settings.allowNotifications)
|
||||||
|
})()
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className='text-3xl ml-4 mt-4'>Settings</p>
|
<p className='text-3xl ml-4 mt-4'>Settings</p>
|
||||||
|
<div className='ml-4 mt-4 bg-[#161616] border border-[#242424] rounded-lg p-4 w-fit h-fit'>
|
||||||
|
<Setting
|
||||||
|
label='Check for new version on load'
|
||||||
|
value={checkForNewVersionOnLoad}
|
||||||
|
onChange={async () => {
|
||||||
|
setCheckForNewVersionOnLoad(!checkForNewVersionOnLoad)
|
||||||
|
const config = await readNormalConfig()
|
||||||
|
config.settings.checkForNewVersionOnLoad = !checkForNewVersionOnLoad
|
||||||
|
await writeNormalConfig(config)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Setting
|
||||||
|
label='Allow sending notifications'
|
||||||
|
value={allowNotifications}
|
||||||
|
onChange={async () => {
|
||||||
|
setAllowNotifications(!allowNotifications)
|
||||||
|
const config = await readNormalConfig()
|
||||||
|
config.settings.allowNotifications = !allowNotifications
|
||||||
|
await writeNormalConfig(config)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Setting
|
||||||
|
label='Use wine to launch Berry Dash when needed'
|
||||||
|
value={useWineOnUnixWhenNeeded}
|
||||||
|
onChange={async () => {
|
||||||
|
setUseWineOnUnixWhenNeeded(!useWineOnUnixWhenNeeded)
|
||||||
|
const config = await readNormalConfig()
|
||||||
|
config.settings.useWineOnUnixWhenNeeded = !useWineOnUnixWhenNeeded
|
||||||
|
await writeNormalConfig(config)
|
||||||
|
}}
|
||||||
|
className={platform() == 'linux' || platform() == 'macos' ? '' : 'hidden'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/types/SettingProps.ts
Normal file
6
src/types/SettingProps.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export type SettingProps = {
|
||||||
|
label: string
|
||||||
|
value: boolean
|
||||||
|
onChange: (val: boolean) => void,
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export class SettingsType {
|
export class SettingsType {
|
||||||
constructor (
|
constructor (
|
||||||
public checkForNewVersionOnLoad: boolean = true,
|
public checkForNewVersionOnLoad: boolean = true,
|
||||||
public useWineOnUnixWhenNeeded: boolean = false
|
public allowNotifications: boolean = true,
|
||||||
|
public useWineOnUnixWhenNeeded: boolean = false,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/types/VersionsConfig.ts
Normal file
14
src/types/VersionsConfig.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { LauncherVersion } from './LauncherVersion'
|
||||||
|
|
||||||
|
export class VersionsConfig {
|
||||||
|
constructor (
|
||||||
|
public version: string,
|
||||||
|
public list: LauncherVersion[] = []
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static import (data: any) {
|
||||||
|
const cfg = new VersionsConfig(data.version)
|
||||||
|
Object.assign(cfg.list, data.list)
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,9 @@ import {
|
|||||||
} from '@tauri-apps/plugin-fs'
|
} from '@tauri-apps/plugin-fs'
|
||||||
import { decrypt, encrypt } from './Encryption'
|
import { decrypt, encrypt } from './Encryption'
|
||||||
import { Keys } from '../enums/Keys'
|
import { Keys } from '../enums/Keys'
|
||||||
|
import { VersionsConfig } from '../types/VersionsConfig'
|
||||||
|
|
||||||
export async function readConfig (): Promise<NormalConfig> {
|
export async function readNormalConfig (): Promise<NormalConfig> {
|
||||||
const version = await app.getVersion()
|
const version = await app.getVersion()
|
||||||
const options = {
|
const options = {
|
||||||
baseDir: BaseDirectory.AppLocalData
|
baseDir: BaseDirectory.AppLocalData
|
||||||
@@ -40,7 +41,7 @@ export async function readConfig (): Promise<NormalConfig> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function writeConfig (data: NormalConfig) {
|
export async function writeNormalConfig (data: NormalConfig) {
|
||||||
const options = {
|
const options = {
|
||||||
baseDir: BaseDirectory.AppLocalData
|
baseDir: BaseDirectory.AppLocalData
|
||||||
}
|
}
|
||||||
@@ -67,3 +68,60 @@ export async function writeConfig (data: NormalConfig) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function readVersionsConfig (): Promise<VersionsConfig> {
|
||||||
|
const version = await app.getVersion()
|
||||||
|
const options = {
|
||||||
|
baseDir: BaseDirectory.AppLocalData
|
||||||
|
}
|
||||||
|
const doesFolderExist = await exists('', options)
|
||||||
|
const doesConfigExist = await exists('versions.dat', options)
|
||||||
|
if (!doesFolderExist || !doesConfigExist) {
|
||||||
|
if (!doesFolderExist) {
|
||||||
|
await mkdir('', options)
|
||||||
|
}
|
||||||
|
const file = await create('versions.dat', options)
|
||||||
|
await file.write(
|
||||||
|
new TextEncoder().encode(
|
||||||
|
encrypt(
|
||||||
|
JSON.stringify(new VersionsConfig(version)),
|
||||||
|
Keys.VERSIONS_ENCRYPTION_KEY
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await file.close()
|
||||||
|
return new VersionsConfig(version)
|
||||||
|
}
|
||||||
|
const config = await readTextFile('versions.dat', options)
|
||||||
|
return VersionsConfig.import(
|
||||||
|
JSON.parse(decrypt(config, Keys.VERSIONS_ENCRYPTION_KEY))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function writeVersionsConfig (data: VersionsConfig) {
|
||||||
|
const options = {
|
||||||
|
baseDir: BaseDirectory.AppLocalData
|
||||||
|
}
|
||||||
|
const doesFolderExist = await exists('', options)
|
||||||
|
const doesConfigExist = await exists('versions.dat', options)
|
||||||
|
if (!doesFolderExist || !doesConfigExist) {
|
||||||
|
if (!doesFolderExist) {
|
||||||
|
await mkdir('', options)
|
||||||
|
}
|
||||||
|
const file = await create('versions.dat', options)
|
||||||
|
await file.write(
|
||||||
|
new TextEncoder().encode(
|
||||||
|
encrypt(JSON.stringify(data), Keys.VERSIONS_ENCRYPTION_KEY)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await file.close()
|
||||||
|
} else {
|
||||||
|
await writeFile(
|
||||||
|
'versions.dat',
|
||||||
|
new TextEncoder().encode(
|
||||||
|
encrypt(JSON.stringify(data), Keys.VERSIONS_ENCRYPTION_KEY)
|
||||||
|
),
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user