Skip to content

Commit

Permalink
[FIX] Download Manager Toast Information (#1095)
Browse files Browse the repository at this point in the history
* feat: add useGameInfo hook

* feat: improve hasProgress hook to deal with extraction and etaInMs

* tech: refactor DownloadToastManager to use hasProgress hook and size from manifest

* fix: hasProgress starting from previous app

* fix: lint

* fix: pr comments

---------

Co-authored-by: Flavio F Lima <[email protected]>
  • Loading branch information
flavioislima and flavioislima authored Oct 11, 2024
1 parent 58dffe6 commit 3a644dd
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 69 deletions.
2 changes: 1 addition & 1 deletion src/frontend/components/UI/DLCList/DLC/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const DLC = ({ dlc, runner, mainAppInfo, onClose }: Props) => {
const [dlcInfo, setDlcInfo] = useState<GameInfo | null>(null)
const [dlcSize, setDlcSize] = useState<number>(0)
const [refreshing, setRefreshing] = useState(true)
const [progress] = hasProgress(app_name)
const { progress } = hasProgress(app_name)

const isInstalled = dlcInfo?.is_installed

Expand Down
65 changes: 11 additions & 54 deletions src/frontend/components/UI/DownloadToastManager/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useContext, useEffect, useState } from 'react'
import { DownloadToast, Images, CircularButton } from '@hyperplay/ui'
import Draggable from 'react-draggable'
import { DMQueueElement, GameStatus, InstallProgress } from 'common/types'
import { DMQueueElement } from 'common/types'
import { DMQueue } from 'frontend/types'
import ContextProvider from 'frontend/state/ContextProvider'
import { useTranslation } from 'react-i18next'
Expand All @@ -12,31 +12,31 @@ import { downloadStatus } from '@hyperplay/ui/dist/components/DownloadToast'
import { useGetDownloadStatusText } from 'frontend/hooks/useGetDownloadStatusText'
import DMQueueState from 'frontend/state/DMQueueState'
import { isNotNative } from 'frontend/helpers/library'

const nullProgress: InstallProgress = {
bytes: '0',
eta: '00:00:00'
}
import { hasProgress } from 'frontend/hooks/hasProgress'
import { useGameInfo } from 'frontend/hooks/useGameInfo'

export default function DownloadToastManager() {
const [latestElement, setLatestElement] = useState<DMQueueElement>()
const [currentElement, setCurrentElement] = useState<DMQueueElement>()
const [progress, setProgress] = useState<InstallProgress>(nullProgress)
const [showDownloadToast, setShowDownloadToast] = useState(true)
const { showDialogModal, platform } = useContext(ContextProvider)
const { t } = useTranslation('gamepage')
const [showPlay, setShowPlay] = useState(false)
const [showStopInstallModal, setShowStopInstallModal] = useState(false)

const appName = currentElement?.params?.gameInfo?.app_name ?? ''
const gameInfo = currentElement?.params.gameInfo
const { statusText: downloadStatusText, status } = useGetDownloadStatusText(
appName,
gameInfo
)
const { installInfo } = useGameInfo({
appName,
runner: gameInfo?.runner ?? 'hyperplay',
platform: currentElement?.params.platformToInstall
})
const installedPlatform = currentElement?.params.platformToInstall
const isExtracting = status === 'extracting'
const isInstallingDistributables = status === 'distributables'
const { progress, etaInMs } = hasProgress(appName, isExtracting)

let showPlayTimeout: NodeJS.Timeout | undefined = undefined

Expand Down Expand Up @@ -81,41 +81,6 @@ export default function DownloadToastManager() {
}
}, [])

useEffect(() => {
if (!currentElement) return
setProgress(nullProgress)
const handleProgressUpdate = async (
_e: Electron.IpcRendererEvent,
{ appName: appWithProgress, progress: currentProgress }: GameStatus
) => {
if (
currentElement?.params.appName === appWithProgress &&
currentProgress?.bytes &&
currentProgress.percent
) {
setProgress(currentProgress)
}
}

const setGameStatusRemoveListener =
currentElement?.params.appName !== undefined
? window.api.onProgressUpdate(
currentElement?.params.appName,
handleProgressUpdate
)
: () => console.log('appName was undefined in download toast manager')

return () => {
setGameStatusRemoveListener()
}
}, [currentElement])

useEffect(() => {
if (isExtracting) {
setProgress(nullProgress) // reset progress to 0
}
}, [isExtracting])

if (currentElement === undefined) {
return <></>
}
Expand Down Expand Up @@ -167,14 +132,6 @@ export default function DownloadToastManager() {
const title = currentElement?.params.gameInfo.title
? currentElement?.params.gameInfo.title
: 'Game'
const downloadSizeInMB = progress.percent
? (downloadedMB / progress.percent) * 100
: 0

const estimatedCompletionTimeInMs =
progress.downSpeed && !isInstallingDistributables
? (downloadSizeInMB / progress.downSpeed) * 1000
: 0

let imgUrl = currentElement?.params.gameInfo.art_cover
? currentElement?.params.gameInfo.art_cover
Expand Down Expand Up @@ -202,7 +159,7 @@ export default function DownloadToastManager() {
}

const adjustedDownloadedInBytes = downloadedMB * 1024 * 1024
const adjustedDownloadSizeInBytes = downloadSizeInMB * 1024 * 1024
const adjustedDownloadSizeInBytes = installInfo?.manifest.download_size || 0

return (
<Draggable>
Expand All @@ -213,7 +170,7 @@ export default function DownloadToastManager() {
gameTitle={title}
downloadedInBytes={adjustedDownloadedInBytes}
downloadSizeInBytes={adjustedDownloadSizeInBytes}
estimatedCompletionTimeInMs={estimatedCompletionTimeInMs}
estimatedCompletionTimeInMs={etaInMs}
onCancelClick={() => {
setShowStopInstallModal(true)
window.api.trackEvent({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type Props = {
}

export default React.memo(function CurrentDownload({ appName, runner }: Props) {
const [progress] = hasProgress(appName)
const { progress } = hasProgress(appName)
const [gameTitle, setGameTitle] = useState('')
const { sidebarCollapsed, libraryStatus } = useContext(ContextProvider)
const { t } = useTranslation()
Expand Down
44 changes: 36 additions & 8 deletions src/frontend/hooks/hasProgress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@ import { GameStatus, InstallProgress } from 'common/types'

const storage: Storage = window.localStorage

export const hasProgress = (appName: string) => {
const nullProgress: InstallProgress = {
bytes: '0',
eta: '00:00:00',
percent: 0
}

const convertEtaToMs = (eta: string) => {
const [hours, minutes, seconds] = eta.split(':').map(Number)
return hours * 60 * 60 * 1000 + minutes * 60 * 1000 + seconds * 1000 || 0
}

let currentApp: string = ''

export const hasProgress = (appName: string, isExtracting?: boolean) => {
const previousProgress = JSON.parse(
storage.getItem(appName) || '{}'
) as InstallProgress

const [progress, setProgress] = useState<InstallProgress>(
previousProgress ?? {
bytes: '0.00MB',
eta: '00:00:00',
percent: 0
}
previousProgress ?? nullProgress
)

const calculatePercent = (currentProgress: InstallProgress) => {
Expand All @@ -34,7 +43,8 @@ export const hasProgress = (appName: string) => {
_e: Electron.IpcRendererEvent,
{ appName: appWithProgress, progress: currentProgress }: GameStatus
) => {
if (appName === appWithProgress && currentProgress) {
if (appWithProgress && appName === appWithProgress && currentProgress) {
currentApp = appName
setProgress({
...currentProgress,
percent: calculatePercent(currentProgress)
Expand All @@ -51,5 +61,23 @@ export const hasProgress = (appName: string) => {
}
}, [appName])

return [progress, previousProgress]
useEffect(() => {
if (isExtracting) {
setProgress(nullProgress) // reset progress to 0
}
}, [])

if (!currentApp) {
return {
progress: nullProgress,
previousProgress,
etaInMs: 0
}
}

return {
progress,
previousProgress,
etaInMs: convertEtaToMs(progress.eta || '00:00:00')
}
}
2 changes: 1 addition & 1 deletion src/frontend/hooks/hasStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const hasStatus = (
gameSize?: string
) => {
const { libraryStatus } = React.useContext(ContextProvider)
const [progress] = hasProgress(appName)
const { progress } = hasProgress(appName)
const { t } = useTranslation('gamepage')

const [gameStatus, setGameStatus] = React.useState<{
Expand Down
51 changes: 51 additions & 0 deletions src/frontend/hooks/useGameInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { HyperPlayInstallInfo, InstallPlatform, Runner } from 'common/types'
import { GogInstallInfo } from 'common/types/gog'
import { LegendaryInstallInfo } from 'common/types/legendary'
import { getGameInfo, getInstallInfo } from 'frontend/helpers'
import { useQuery } from '@tanstack/react-query'

type InstallInfo =
| GogInstallInfo
| LegendaryInstallInfo
| HyperPlayInstallInfo
| null

interface UseGameInfoProps {
appName: string
runner: Runner
platform?: InstallPlatform
channel?: string
}

export const useGameInfo = ({
appName,
runner,
platform,
channel
}: UseGameInfoProps) => {
const query = useQuery({
queryKey: ['gameData', appName, runner, platform, channel],
queryFn: async () => {
const gameInfo = await getGameInfo(appName, runner)

let installInfo: InstallInfo = null
if (gameInfo && platform) {
installInfo = await getInstallInfo(
appName,
gameInfo.runner,
platform,
channel ?? 'main'
)
}

return { gameInfo, installInfo }
}
})

return {
...query.data,
isLoading: query.isLoading,
isError: query.isError || false,
error: query.error
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const DownloadManagerItem = observer(({ element, current, state }: Props) => {
install: { is_dlc }
} = gameInfo || {}

const [progress] = hasProgress(appName)
const { progress } = hasProgress(appName)
const { status } = element
const finished = status === 'done'
const canceled = status === 'error' || (status === 'abort' && !current)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function ProgressHeader(props: {
state: DownloadManagerState
}) {
const { t } = useTranslation()
const [progress] = hasProgress(props.appName)
const { progress } = hasProgress(props.appName)
const [avgSpeed, setAvgDownloadSpeed] = useState<Point[]>(
Array<Point>(20).fill({ download: 0, disk: 0 })
)
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/screens/Game/GamePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export default observer(function GamePage(): JSX.Element | null {
const { status, folder } = hasStatus(appName, gameInfo)
const gameAvailable = gameInfo.is_installed && status !== 'notAvailable'

const [progress, previousProgress] = hasProgress(appName)
const { progress, previousProgress } = hasProgress(appName)

const [extraInfo, setExtraInfo] = useState<ExtraInfo | null>(null)
const [autoSyncSaves, setAutoSyncSaves] = useState(false)
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/screens/Library/components/GameCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const GameCard = ({
const isInstallable =
gameInfo.installable === undefined || gameInfo.installable // If it's undefined we assume it's installable

const [progress, previousProgress] = hasProgress(appName)
const { progress, previousProgress } = hasProgress(appName)
const { install_size: size = '0', platform: installPlatform } = {
...gameInstallInfo
}
Expand Down

0 comments on commit 3a644dd

Please sign in to comment.