Settings, queue fixes, and more
This commit is contained in:
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 {
|
||||
SERVER_RECEIVE_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 Installs from './routes/Installs'
|
||||
import Settings from './routes/Settings'
|
||||
@@ -14,6 +14,7 @@ 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'
|
||||
|
||||
function App () {
|
||||
const [hash, setHash] = useState(window.location.hash || '#installs')
|
||||
@@ -23,13 +24,13 @@ function App () {
|
||||
const [showPopup, setShowPopup] = useState(false)
|
||||
const [popupMode, setPopupMode] = useState<null | number>(null)
|
||||
const [fadeOut, setFadeOut] = useState(false)
|
||||
let activeDownloads = 0
|
||||
const queue: (() => void)[] = []
|
||||
const activeDownloads = useRef(0)
|
||||
const queue = useRef<(() => void)[]>([])
|
||||
|
||||
function runNext() {
|
||||
if (activeDownloads >= 3 || queue.length === 0) return
|
||||
activeDownloads++
|
||||
const next = queue.shift()
|
||||
if (activeDownloads.current >= 3 || queue.current.length === 0) return
|
||||
activeDownloads.current++
|
||||
const next = queue.current.shift()
|
||||
next?.()
|
||||
}
|
||||
|
||||
@@ -49,8 +50,17 @@ function App () {
|
||||
|
||||
const unlistenDone = listen<string>('download-done', async (event) => {
|
||||
const versionName = event.payload
|
||||
setDownloadProgress(prev => prev.filter(d => d.version.version !== versionName))
|
||||
activeDownloads--
|
||||
setDownloadProgress(prev => {
|
||||
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()
|
||||
if (downloadProgress.length === 0) {
|
||||
await notifyUser('Downloads Complete', 'All downloads have completed.')
|
||||
@@ -64,7 +74,7 @@ function App () {
|
||||
d.version.version === versionName ? { ...d, failed: true } : d
|
||||
)
|
||||
)
|
||||
activeDownloads--
|
||||
activeDownloads.current--
|
||||
runNext()
|
||||
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))
|
||||
setDownloadProgress(prev => [...prev, ...newDownloads])
|
||||
|
||||
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 = () => {
|
||||
setDownloadProgress(prev => {
|
||||
const i = prev.findIndex(d => d.version.version === download.version.version)
|
||||
@@ -91,13 +123,13 @@ function App () {
|
||||
})
|
||||
|
||||
invoke('download', {
|
||||
url: download.version.downloadUrls[download.version.platforms.indexOf(platform())],
|
||||
url,
|
||||
name: download.version.version,
|
||||
executable: download.version.executables[download.version.platforms.indexOf(platform())]
|
||||
executable: exe
|
||||
})
|
||||
}
|
||||
|
||||
queue.push(task)
|
||||
queue.current.push(task)
|
||||
runNext()
|
||||
})
|
||||
}
|
||||
@@ -110,6 +142,8 @@ function App () {
|
||||
}
|
||||
|
||||
async function notifyUser(title: string, body: string) {
|
||||
const config = await readNormalConfig()
|
||||
if (!config.settings.allowNotifications) return
|
||||
let permissionGranted = await isPermissionGranted();
|
||||
if (!permissionGranted) {
|
||||
const permission = await requestPermission();
|
||||
|
||||
@@ -2,15 +2,31 @@ import { useEffect } from 'react'
|
||||
import axios from 'axios'
|
||||
import { InstallsProps } from '../types/InstallsProps'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
import { readNormalConfig } from '../util/BazookaManager'
|
||||
|
||||
export default function Installs({ downloadProgress, showPopup, setShowPopup, setPopupMode, setFadeOut, setSelectedVersionList, setVersionList }: InstallsProps) {
|
||||
useEffect(() => {
|
||||
if (!showPopup) return
|
||||
setSelectedVersionList([]);
|
||||
setSelectedVersionList([])
|
||||
setVersionList(null)
|
||||
axios.get('https://berrydash.lncvrt.xyz/database/launcher/versions.php')
|
||||
.then(res => setVersionList(res.data.filter((d: { platforms: string[] }) => d.platforms.includes(platform()))))
|
||||
.catch(() => setVersionList([]))
|
||||
;(async () => {
|
||||
try {
|
||||
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])
|
||||
|
||||
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 () {
|
||||
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 (
|
||||
<>
|
||||
<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 {
|
||||
constructor (
|
||||
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'
|
||||
import { decrypt, encrypt } from './Encryption'
|
||||
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 options = {
|
||||
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 = {
|
||||
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