diff --git a/package.json b/package.json index b4ed8d031..94e96111d 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,6 @@ "lodash": "^4.17.21", "lottie-react": "^2.4.0", "parse-torrent": "9.1.5", - "pretty-bytes": "^6.1.1", "ps-list": "^8.1.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/renderer/hooks/use-download.ts b/src/renderer/hooks/use-download.ts index 6acccffd6..522dd2c2d 100644 --- a/src/renderer/hooks/use-download.ts +++ b/src/renderer/hooks/use-download.ts @@ -1,5 +1,4 @@ import { addMilliseconds } from "date-fns"; -import prettyBytes from "pretty-bytes"; import { formatDownloadProgress } from "@renderer/helpers"; import { useLibrary } from "./use-library"; @@ -12,6 +11,7 @@ import { } from "@renderer/features"; import type { GameShop, TorrentProgress } from "@types"; import { useDate } from "./use-date"; +import { formatBytes } from "@renderer/utils"; export function useDownload() { const { updateLibrary } = useLibrary(); @@ -113,9 +113,7 @@ export function useDownload() { fileSize: lastPacket?.game.fileSize, isVerifying, gameId: lastPacket?.game.id, - downloadSpeed: `${prettyBytes(lastPacket?.downloadSpeed ?? 0, { - bits: true, - })}/s`, + downloadSpeed: `${formatBytes(lastPacket?.downloadSpeed ?? 0)}/s`, isDownloading: Boolean(lastPacket), progress: getProgress(), numPeers: lastPacket?.numPeers, diff --git a/src/renderer/pages/downloads/downloads.tsx b/src/renderer/pages/downloads/downloads.tsx index 9e9879695..aecf7b5eb 100644 --- a/src/renderer/pages/downloads/downloads.tsx +++ b/src/renderer/pages/downloads/downloads.tsx @@ -1,4 +1,3 @@ -import prettyBytes from "pretty-bytes"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; @@ -11,6 +10,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal"; import * as styles from "./downloads.css"; import { DeleteModal } from "./delete-modal"; +import { formatBytes } from "@renderer/utils"; export function Downloads() { const { library, updateLibrary } = useLibrary(); @@ -61,10 +61,10 @@ export function Downloads() { const isGameDownloading = isDownloading && gameDownloading?.id === game?.id; if (!game) return "N/A"; - if (game.fileSize) return prettyBytes(game.fileSize); + if (game.fileSize) return formatBytes(game.fileSize); if (gameDownloading?.fileSize && isGameDownloading) - return prettyBytes(gameDownloading.fileSize); + return formatBytes(gameDownloading.fileSize); return game.repack?.fileSize ?? "N/A"; }; @@ -87,7 +87,7 @@ export function Downloads() { ) : ( <>

- {prettyBytes(gameDownloading?.bytesDownloaded)} /{" "} + {formatBytes(gameDownloading?.bytesDownloaded)} /{" "} {finalDownloadSize}

diff --git a/src/renderer/pages/game-details/hero-panel.tsx b/src/renderer/pages/game-details/hero-panel.tsx index 7cff3b372..95273a301 100644 --- a/src/renderer/pages/game-details/hero-panel.tsx +++ b/src/renderer/pages/game-details/hero-panel.tsx @@ -1,5 +1,4 @@ import { format } from "date-fns"; -import prettyBytes from "pretty-bytes"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -12,6 +11,7 @@ import { NoEntryIcon, PlusCircleIcon } from "@primer/octicons-react"; import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal"; import * as styles from "./hero-panel.css"; import { useDate } from "@renderer/hooks/use-date"; +import { formatBytes } from "@renderer/utils"; export interface HeroPanelProps { game: Game | null; @@ -122,10 +122,10 @@ export function HeroPanel({ const finalDownloadSize = useMemo(() => { if (!game) return "N/A"; - if (game.fileSize) return prettyBytes(game.fileSize); + if (game.fileSize) return formatBytes(game.fileSize); if (gameDownloading?.fileSize && isGameDownloading) - return prettyBytes(gameDownloading.fileSize); + return formatBytes(gameDownloading.fileSize); return game.repack?.fileSize ?? "N/A"; }, [game, isGameDownloading, gameDownloading]); @@ -172,7 +172,7 @@ export function HeroPanel({ ) : (

- {prettyBytes(gameDownloading?.bytesDownloaded)} /{" "} + {formatBytes(gameDownloading?.bytesDownloaded)} /{" "} {finalDownloadSize} {numPeers} peers / {numSeeds} seeds @@ -192,7 +192,7 @@ export function HeroPanel({ })}

- {prettyBytes(game.bytesDownloaded)} / {finalDownloadSize} + {formatBytes(game.bytesDownloaded)} / {finalDownloadSize}

); diff --git a/src/renderer/pages/game-details/repacks-modal.tsx b/src/renderer/pages/game-details/repacks-modal.tsx index fcefe6740..b3917b23d 100644 --- a/src/renderer/pages/game-details/repacks-modal.tsx +++ b/src/renderer/pages/game-details/repacks-modal.tsx @@ -1,6 +1,5 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import prettyBytes from "pretty-bytes"; import { Button, Modal, TextField } from "@renderer/components"; import type { GameRepack, ShopDetails } from "@types"; @@ -10,6 +9,7 @@ import * as styles from "./repacks-modal.css"; import type { DiskSpace } from "check-disk-space"; import { format } from "date-fns"; import { SPACING_UNIT } from "@renderer/theme.css"; +import { formatBytes } from "@renderer/utils"; export interface RepacksModalProps { visible: boolean; @@ -66,7 +66,7 @@ export function RepacksModal({ visible={visible} title={`${gameDetails.name} Repacks`} description={t("space_left_on_disk", { - space: prettyBytes(diskFreeSpace?.free ?? 0), + space: formatBytes(diskFreeSpace?.free ?? 0), })} onClose={onClose} > diff --git a/src/renderer/utils/format-bytes.ts b/src/renderer/utils/format-bytes.ts new file mode 100644 index 000000000..5aa390727 --- /dev/null +++ b/src/renderer/utils/format-bytes.ts @@ -0,0 +1,15 @@ +const FORMAT = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + +export const formatBytes = (bytes: number): string => { + if (!Number.isFinite(bytes) || isNaN(bytes) || bytes < 0) { + return `N/A ${FORMAT[0]}`; + } + + const byteKBase = 1024; + + const base = Math.floor(Math.log(bytes) / Math.log(byteKBase)); + + const formatedByte = bytes / byteKBase ** base; + + return `${Math.trunc(formatedByte * 10) / 10} ${FORMAT[base]}`; +}; diff --git a/src/renderer/utils/index.ts b/src/renderer/utils/index.ts new file mode 100644 index 000000000..7a828a7f5 --- /dev/null +++ b/src/renderer/utils/index.ts @@ -0,0 +1 @@ +export * from "./format-bytes"; diff --git a/yarn.lock b/yarn.lock index 9c1d19fd3..0dab07523 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8639,11 +8639,6 @@ prettier@^3.2.5: resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz" integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== -pretty-bytes@^6.1.1: - version "6.1.1" - resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz" - integrity sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ== - pretty-error@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz" @@ -9710,16 +9705,7 @@ stream-transform@^2.1.3: dependencies: mixme "^0.5.1" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9797,14 +9783,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -10825,16 +10804,7 @@ word-wrap@^1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==