diff --git a/src/app/GlobalProvider.tsx b/src/app/GlobalProvider.tsx index 110ba9a..64f777a 100644 --- a/src/app/GlobalProvider.tsx +++ b/src/app/GlobalProvider.tsx @@ -45,6 +45,12 @@ type GlobalCtxType = { downloadVersions: (list: string[]) => Promise category: number setCategory: Dispatch> + downloadQueue: string[] + setDownloadQueue: Dispatch> + closePopup: () => void + getSpecialVersionsList(game?: number | undefined): GameVersion[] + selectedGame: number | null + setViewingInfoFromDownloads: Dispatch> } const GlobalCtx = createContext(null) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0d64cce..4aa7ae5 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,18 +6,7 @@ import './Globals.css' import { DownloadProgress } from '@/types/DownloadProgress' import { invoke } from '@tauri-apps/api/core' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { - faAdd, - faCheck, - faChevronLeft, - faCode, - faDownload, - faInfo, - faRemove, - faShieldHalved, - faWarning, - faXmark -} from '@fortawesome/free-solid-svg-icons' +import { faChevronLeft, faXmark } from '@fortawesome/free-solid-svg-icons' import { readNormalConfig, readVersionsConfig, @@ -36,16 +25,18 @@ import { Game } from '@/types/Game' import { listen } from '@tauri-apps/api/event' import { usePathname } from 'next/navigation' import { arch, platform } from '@tauri-apps/plugin-os' -import VersionInfo from '@/componets/popups/VersionInfo' -import prettyBytes from 'pretty-bytes' -import ProgressBar from '@/componets/ProgressBar' import { notifyUser } from '@/lib/Notifications' import { isPermissionGranted, requestPermission } from '@tauri-apps/plugin-notification' import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs' -import { openFolder } from '@/lib/Util' + +import DownloadsPopup from '@/componets/popups/Downloads' +import VersionInfoPopup from '@/componets/popups/VersionInfo' +import ManagingVersionPopup from '@/componets/popups/ManageVersion' +import GamesDownloadPopup from '@/componets/popups/GamesDownload' +import VersionsDownloadPopup from '@/componets/popups/VersionsDownload' const roboto = Roboto({ subsets: ['latin'] @@ -174,20 +165,6 @@ export default function RootLayout ({ return { installed, total } } - function formatEtaSmart (seconds: number) { - if (seconds < 60) return `${Math.floor(seconds)}s` - if (seconds < 3600) - return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s` - if (seconds < 86400) { - const h = Math.floor(seconds / 3600) - const m = Math.floor((seconds % 3600) / 60) - return `${h}h ${m}m` - } - const d = Math.floor(seconds / 86400) - const h = Math.floor((seconds % 86400) / 3600) - return `${d}d ${h}h` - } - const closePopup = useCallback(() => { if (popupMode == 0 && selectedGame && pathname === '/') { setSelectedGame(null) @@ -525,7 +502,13 @@ export default function RootLayout ({ version, downloadVersions, category, - setCategory + setCategory, + downloadQueue, + setDownloadQueue, + closePopup, + getSpecialVersionsList, + selectedGame, + setViewingInfoFromDownloads }} >
{popupMode === 0 && selectedGame ? ( - <> -

- Select versions to download -

-
- {getSpecialVersionsList(selectedGame).map( - (v, i) => ( -
-
-

- {v.displayName} -

-
- - -
- ) - )} -
- + ) : popupMode === 0 && !selectedGame ? ( - <> -

- Select a game to download -

-
- {serverVersionList?.games - .filter(v => { - const data = getVersionsAmountData(v.id) - if (!data) return false - if (data.total > 0) return true - }) - .map((v, i) => ( -
-

{v.name}

-
-
-

- {(() => { - const data = getVersionsAmountData( - v.id - ) - if (!data) return 'N/A' - return `${data.installed}/${data.total}` - })()}{' '} - versions installed -

-
- - -
- - -
- ))} -
- + ) : popupMode === 1 ? ( - <> -

Downloads

-
- {downloadProgress.map((v, i) => { - const queuePosition = downloadQueue.indexOf( - v.version - ) - return ( -
-

- {getVersionInfo(v.version)?.displayName} -

-
- {v.failed || v.queued ? ( -
- - {v.failed - ? 'Download failed' - : queuePosition === 0 - ? 'Starting soon...' - : `Queued (Position ${ - queuePosition + 1 - })`} - - -
- ) : v.hash_checking || v.finishing ? ( - - {v.hash_checking - ? 'Checking hash' - : 'Finishing'} - ... - - ) : ( -
- - Downloaded{' '} - {prettyBytes(v.progressBytes, { - minimumFractionDigits: 1, - maximumFractionDigits: 1 - })}{' '} - of{' '} - {prettyBytes( - getVersionInfo(v.version)?.size ?? - 0, - { - minimumFractionDigits: 1, - maximumFractionDigits: 1 - } - )}{' '} - (ETA: {formatEtaSmart(v.etaSecs)}{' '} - • Speed:{' '} - {prettyBytes(v.speed, { - minimumFractionDigits: 1, - maximumFractionDigits: 1 - })} - /s) - - -
- )} -
-
- ) - })} -
- + ) : popupMode === 2 ? ( - managingVersion ? ( - <> -

- Manage{' '} - {getVersionInfo(managingVersion)?.displayName} -

-
- - - -
- - ) : ( -

- No version selected -

- ) + ) : popupMode === 3 ? ( - managingVersion && downloadedVersionsConfig ? ( - - ) : ( -

- No version selected -

- ) + ) : null} - {popupMode == 0 && - selectedGame && - serverVersionList != null && ( -
- - -
- )}
)} diff --git a/src/componets/popups/Downloads.tsx b/src/componets/popups/Downloads.tsx new file mode 100644 index 0000000..eb9b040 --- /dev/null +++ b/src/componets/popups/Downloads.tsx @@ -0,0 +1,94 @@ +import { useGlobal } from '@/app/GlobalProvider' +import prettyBytes from 'pretty-bytes' +import ProgressBar from '../ProgressBar' +import { formatEtaSmart } from '@/lib/Util' + +export default function DownloadsPopup () { + const { + downloadProgress, + getVersionInfo, + setDownloadProgress, + downloadQueue, + setDownloadQueue + } = useGlobal() + + return ( + <> +

Downloads

+
+ {downloadProgress.map((v, i) => { + const queuePosition = downloadQueue.indexOf(v.version) + return ( +
+

+ {getVersionInfo(v.version)?.displayName} +

+
+ {v.failed || v.queued ? ( +
+ + {v.failed + ? 'Download failed' + : queuePosition === 0 + ? 'Starting soon...' + : `Queued (Position ${queuePosition + 1})`} + + +
+ ) : v.hash_checking || v.finishing ? ( + + {v.hash_checking ? 'Checking hash' : 'Finishing'} + ... + + ) : ( +
+ + Downloaded{' '} + {prettyBytes(v.progressBytes, { + minimumFractionDigits: 1, + maximumFractionDigits: 1 + })}{' '} + of{' '} + {prettyBytes(getVersionInfo(v.version)?.size ?? 0, { + minimumFractionDigits: 1, + maximumFractionDigits: 1 + })}{' '} + (ETA: {formatEtaSmart(v.etaSecs)} • Speed:{' '} + {prettyBytes(v.speed, { + minimumFractionDigits: 1, + maximumFractionDigits: 1 + })} + /s) + + +
+ )} +
+
+ ) + })} +
+ + ) +} diff --git a/src/componets/popups/GamesDownload.tsx b/src/componets/popups/GamesDownload.tsx new file mode 100644 index 0000000..64dcbca --- /dev/null +++ b/src/componets/popups/GamesDownload.tsx @@ -0,0 +1,92 @@ +import { useGlobal } from '@/app/GlobalProvider' +import { + faCheck, + faCode, + faDownload, + faShieldHalved, + faWarning +} from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +export default function GamesDownloadPopup () { + const { serverVersionList, getVersionsAmountData, setSelectedGame } = + useGlobal() + + return ( + <> +

Select a game to download

+
+ {serverVersionList?.games + .filter(v => { + const data = getVersionsAmountData(v.id) + if (!data) return false + if (data.total > 0) return true + }) + .map((v, i) => ( +
+

{v.name}

+
+
+

+ {(() => { + const data = getVersionsAmountData(v.id) + if (!data) return 'N/A' + return `${data.installed}/${data.total}` + })()}{' '} + versions installed +

+
+ + +
+ + +
+ ))} +
+ + ) +} diff --git a/src/componets/popups/ManageVersion.tsx b/src/componets/popups/ManageVersion.tsx new file mode 100644 index 0000000..2316ccf --- /dev/null +++ b/src/componets/popups/ManageVersion.tsx @@ -0,0 +1,106 @@ +import { useGlobal } from '@/app/GlobalProvider' +import { writeVersionsConfig } from '@/lib/BazookaManager' +import { openFolder } from '@/lib/Util' +import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs' + +export default function ManageVersionPopup () { + const { + getVersionInfo, + managingVersion, + closePopup, + setDownloadedVersionsConfig, + setManagingVersion, + downloadVersions, + setSelectedVersionList, + setPopupMode + } = useGlobal() + if (!managingVersion) return <> + + return ( + <> +

+ Manage {getVersionInfo(managingVersion)?.displayName} +

+
+ + + +
+ + ) +} diff --git a/src/componets/popups/VersionInfo.tsx b/src/componets/popups/VersionInfo.tsx index 64bff30..0e16643 100644 --- a/src/componets/popups/VersionInfo.tsx +++ b/src/componets/popups/VersionInfo.tsx @@ -15,7 +15,7 @@ import { useEffect, useState } from 'react' import prettyBytes from 'pretty-bytes' import { message } from '@tauri-apps/plugin-dialog' -export default function VersionInfo () { +export default function VersionInfoPopup () { const { getGameInfo, getVersionInfo, diff --git a/src/componets/popups/VersionsDownload.tsx b/src/componets/popups/VersionsDownload.tsx new file mode 100644 index 0000000..bca45f2 --- /dev/null +++ b/src/componets/popups/VersionsDownload.tsx @@ -0,0 +1,128 @@ +import { useGlobal } from '@/app/GlobalProvider' +import { faAdd, faInfo, faRemove } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +export default function VersionsDownloadPopup () { + const { + selectedVersionList, + setSelectedVersionList, + setManagingVersion, + setPopupMode, + getSpecialVersionsList, + selectedGame, + setViewingInfoFromDownloads, + downloadedVersionsConfig, + downloadProgress, + downloadVersions, + getGameInfo, + downloadQueue + } = useGlobal() + if (!selectedGame) return <> + + return ( + <> +

Select versions to download

+
+ {getSpecialVersionsList(selectedGame).map((v, i) => ( +
+
+

+ {v.displayName} +

+
+ + +
+ ))} +
+
+ + +
+ + ) +} diff --git a/src/lib/Util.ts b/src/lib/Util.ts index f1e49e7..a216721 100644 --- a/src/lib/Util.ts +++ b/src/lib/Util.ts @@ -25,3 +25,17 @@ export const openFolder = async (name: string) => { await openPath(absolutePath) } + +export const formatEtaSmart = (seconds: number) => { + if (seconds < 60) return `${Math.floor(seconds)}s` + if (seconds < 3600) + return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s` + if (seconds < 86400) { + const h = Math.floor(seconds / 3600) + const m = Math.floor((seconds % 3600) / 60) + return `${h}h ${m}m` + } + const d = Math.floor(seconds / 86400) + const h = Math.floor((seconds % 86400) / 3600) + return `${d}d ${h}h` +}