From 528daa2996aa7bc060f8eeb5f8c2b158354d7cce Mon Sep 17 00:00:00 2001 From: RedBeardEth <90423049+RedBeardEth@users.noreply.github.com> Date: Wed, 11 Dec 2024 01:03:46 +1100 Subject: [PATCH 1/3] update signing-policy for mainnet --- client/src/hooks/context/signing-policy.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/hooks/context/signing-policy.tsx b/client/src/hooks/context/signing-policy.tsx index b2de204ac8..055c1d9d1d 100644 --- a/client/src/hooks/context/signing-policy.tsx +++ b/client/src/hooks/context/signing-policy.tsx @@ -1,3 +1,5 @@ +import { env } from "../../../env"; + export const signingPolicy = [ { types: { @@ -19,7 +21,7 @@ export const signingPolicy = [ domain: { name: "Eternum", version: "1", - chainId: "SN_SEPOLIA", + chainId: env.VITE_PUBLIC_CHAIN == "mainnet" ? "SN_MAIN" : "SN_SEPOLIA", revision: "1", }, }, From d050ec3073a27e5305cfd18eacf95df4a2ee0f84 Mon Sep 17 00:00:00 2001 From: RedBeardEth <90423049+RedBeardEth@users.noreply.github.com> Date: Wed, 11 Dec 2024 01:32:25 +1100 Subject: [PATCH 2/3] update client controller --- client/package.json | 4 +-- pnpm-lock.yaml | 80 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/client/package.json b/client/package.json index 33f04dbd43..1ab9c72066 100644 --- a/client/package.json +++ b/client/package.json @@ -17,8 +17,8 @@ }, "dependencies": { "@bibliothecadao/eternum": "workspace:^", - "@cartridge/connector": "0.5.3", - "@cartridge/controller": "0.5.3", + "@cartridge/connector": "0.5.4", + "@cartridge/controller": "0.5.4", "@dojoengine/core": "1.0.1", "@dojoengine/create-burner": "1.0.1", "@dojoengine/react": "1.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d5f512373..8dfe5a1e03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,11 +131,11 @@ importers: specifier: workspace:^ version: link:../sdk/packages/eternum '@cartridge/connector': - specifier: 0.5.3 - version: 0.5.3(encoding@0.1.13)(get-starknet-core@3.3.4(starknet@6.11.0(encoding@0.1.13)))(react@18.3.1)(typescript@5.6.3) + specifier: 0.5.4 + version: 0.5.4(encoding@0.1.13)(get-starknet-core@3.3.4(starknet@6.11.0(encoding@0.1.13)))(react@18.3.1)(typescript@5.6.3) '@cartridge/controller': - specifier: 0.5.3 - version: 0.5.3(encoding@0.1.13) + specifier: 0.5.4 + version: 0.5.4(encoding@0.1.13) '@dojoengine/core': specifier: 1.0.1 version: 1.0.1(starknet@6.11.0(encoding@0.1.13))(typescript@5.6.3) @@ -144,13 +144,13 @@ importers: version: 1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(starknet@6.11.0(encoding@0.1.13))(typescript@5.6.3) '@dojoengine/react': specifier: 1.0.1 - version: 1.0.1(@types/node@20.17.1)(@types/react@18.3.12)(@vitest/ui@2.1.3(vitest@2.1.3))(jsdom@24.1.3)(react@18.3.1)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(type-fest@4.26.1)(typescript@5.6.3)(zod@3.23.8) + version: 1.0.1(@types/node@20.17.1)(@types/react@18.3.12)(@vitest/ui@2.1.3)(jsdom@24.1.3)(react@18.3.1)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(type-fest@4.26.1)(typescript@5.6.3)(zod@3.23.8) '@dojoengine/recs': specifier: ^2.0.13 version: 2.0.13(typescript@5.6.3)(zod@3.23.8) '@dojoengine/state': specifier: 1.0.1 - version: 1.0.1(@types/node@20.17.1)(@vitest/ui@2.1.3(vitest@2.1.3))(jsdom@24.1.3)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(typescript@5.6.3)(zod@3.23.8) + version: 1.0.1(@types/node@20.17.1)(@vitest/ui@2.1.3)(jsdom@24.1.3)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(typescript@5.6.3)(zod@3.23.8) '@dojoengine/torii-client': specifier: 1.0.1 version: 1.0.1 @@ -279,7 +279,7 @@ importers: version: 0.20.5(@vite-pwa/assets-generator@0.2.6)(vite@5.4.10(@types/node@20.17.1)(terser@5.36.0))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0) vitest-canvas-mock: specifier: ^0.3.3 - version: 0.3.3(vitest@2.1.3(@types/node@20.17.1)(@vitest/ui@2.1.3)(jsdom@24.1.3)(terser@5.36.0)) + version: 0.3.3(vitest@2.1.3) wouter: specifier: ^2.12.1 version: 2.12.1(react@18.3.1) @@ -319,7 +319,7 @@ importers: version: 4.3.3(vite@5.4.10(@types/node@20.17.1)(terser@5.36.0)) '@vitest/coverage-v8': specifier: ^2.0.5 - version: 2.1.3(vitest@2.1.3(@types/node@20.17.1)(@vitest/ui@2.1.3)(jsdom@24.1.3)(terser@5.36.0)) + version: 2.1.3(vitest@2.1.3) '@vitest/ui': specifier: ^2.0.1 version: 2.1.3(vitest@2.1.3) @@ -431,13 +431,13 @@ importers: version: 1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(starknet@6.11.0(encoding@0.1.13))(typescript@5.6.3) '@dojoengine/react': specifier: 1.0.1 - version: 1.0.1(@types/node@20.17.1)(@types/react@18.3.12)(@vitest/ui@2.1.3(vitest@2.1.3))(jsdom@24.1.3)(react@18.3.1)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(type-fest@4.26.1)(typescript@5.6.3)(zod@3.23.8) + version: 1.0.1(@types/node@20.17.1)(@types/react@18.3.12)(@vitest/ui@2.1.3)(jsdom@24.1.3)(react@18.3.1)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(type-fest@4.26.1)(typescript@5.6.3)(zod@3.23.8) '@dojoengine/recs': specifier: ^2.0.13 version: 2.0.13(typescript@5.6.3)(zod@3.23.8) '@dojoengine/state': specifier: 1.0.1 - version: 1.0.1(@types/node@20.17.1)(@vitest/ui@2.1.3(vitest@2.1.3))(jsdom@24.1.3)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(typescript@5.6.3)(zod@3.23.8) + version: 1.0.1(@types/node@20.17.1)(@vitest/ui@2.1.3)(jsdom@24.1.3)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(typescript@5.6.3)(zod@3.23.8) '@dojoengine/torii-client': specifier: 1.0.1 version: 1.0.1 @@ -1438,18 +1438,30 @@ packages: '@cartridge/account-wasm@0.5.3': resolution: {integrity: sha512-UZ37MOc4FFgngvcgxo0CpjRlmID7MN1x8Io2svQn7SDbaYPpJkrBNfGUcm+nhgDAkgDS3kTTYObzRBH8dXArAQ==} + '@cartridge/account-wasm@0.5.4': + resolution: {integrity: sha512-vi08BWNp2kflrEWLsqaIwrMajUOtIeRZlTFAGyJ3ejcaFNMlx0VzgwRCTiRtoh6MSa/WtUIlTjshmFUyHMd/CQ==} + '@cartridge/connector@0.5.3': resolution: {integrity: sha512-CvWENcVYrTqOPNIcARiiz96Bkyv/EkXkbJebUrUhUBTBuMs2EpMg39qD31mLHNUw62XxynEo6Zs4Zen5OWzE7g==} + '@cartridge/connector@0.5.4': + resolution: {integrity: sha512-UqOGzRqP2rLsw1TQ6ZswUM/Uf35IFKTOZBkshenVOS6n4QEdgQKUVKDKW71lr6O2/w0uXU6fGxiBSQ3V+Pn23A==} + '@cartridge/controller@0.5.3': resolution: {integrity: sha512-6nx5ZT1U9S38TvhgAjNC/oQrGgrqLiGFNwp0gbWwHZb1ktNp1j560ClOZ3NDQAdnQNUZ5uCdvxZp/XvrFYljvw==} + '@cartridge/controller@0.5.4': + resolution: {integrity: sha512-8wwcA8GwLTWf/uNU5kf07BoLj1SYRR0EdRCBx55FQmc6MxX1TNto6H0shLIAV2iehXYE/zjqX24GN930gSqm7Q==} + '@cartridge/penpal@6.2.3': resolution: {integrity: sha512-K8h9VqBfFPXcAFQNnvgBnejF/dp7249pS4jXu3NhNYR6JqMQxtcrDqfnPmJvbF4ECEBs+8Z2UiwlRQiKt5nNsg==} '@cartridge/presets@0.5.3': resolution: {integrity: sha512-97xKkqtBkQqz6F+apM6fA8d2t+y2RdXNOEduJmIzYbn8Vc7jo34/b/xYSfUHHGg2XWtKy4ovHBd98KhXwwtUTQ==} + '@cartridge/presets@0.5.4': + resolution: {integrity: sha512-kC2tcZ+dBv6NoQOnlWrb+WroulVWYVjQy7PP5v1+Q07LjJujzjNlyXG1pPU1BpJCGDbE16/qlbUujhYbBvnVgQ==} + '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==} cpu: [arm64] @@ -10690,6 +10702,8 @@ snapshots: '@cartridge/account-wasm@0.5.3': {} + '@cartridge/account-wasm@0.5.4': {} + '@cartridge/connector@0.5.3(encoding@0.1.13)(get-starknet-core@3.3.4(starknet@6.11.0(encoding@0.1.13)))(react@18.3.1)(typescript@5.6.3)': dependencies: '@cartridge/controller': 0.5.3(encoding@0.1.13) @@ -10703,6 +10717,19 @@ snapshots: - typescript - utf-8-validate + '@cartridge/connector@0.5.4(encoding@0.1.13)(get-starknet-core@3.3.4(starknet@6.11.0(encoding@0.1.13)))(react@18.3.1)(typescript@5.6.3)': + dependencies: + '@cartridge/controller': 0.5.4(encoding@0.1.13) + '@starknet-react/core': 3.5.0(get-starknet-core@3.3.4(starknet@6.11.0(encoding@0.1.13)))(react@18.3.1)(starknet@6.11.0(encoding@0.1.13))(typescript@5.6.3) + starknet: 6.11.0(encoding@0.1.13) + transitivePeerDependencies: + - bufferutil + - encoding + - get-starknet-core + - react + - typescript + - utf-8-validate + '@cartridge/controller@0.5.3(encoding@0.1.13)': dependencies: '@cartridge/account-wasm': 0.5.3 @@ -10718,12 +10745,31 @@ snapshots: transitivePeerDependencies: - encoding + '@cartridge/controller@0.5.4(encoding@0.1.13)': + dependencies: + '@cartridge/account-wasm': 0.5.4 + '@cartridge/penpal': 6.2.3 + '@cartridge/presets': 0.5.4 + '@starknet-io/types-js': 0.7.7 + '@telegram-apps/sdk': 2.5.2 + base64url: 3.0.1 + cbor-x: 1.6.0 + fast-deep-equal: 3.1.3 + query-string: 7.1.3 + starknet: 6.11.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + '@cartridge/penpal@6.2.3': {} '@cartridge/presets@0.5.3': dependencies: '@starknet-io/types-js': 0.7.7 + '@cartridge/presets@0.5.4': + dependencies: + '@starknet-io/types-js': 0.7.7 + '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': optional: true @@ -10821,10 +10867,10 @@ snapshots: - typescript - utf-8-validate - '@dojoengine/react@1.0.1(@types/node@20.17.1)(@types/react@18.3.12)(@vitest/ui@2.1.3(vitest@2.1.3))(jsdom@24.1.3)(react@18.3.1)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(type-fest@4.26.1)(typescript@5.6.3)(zod@3.23.8)': + '@dojoengine/react@1.0.1(@types/node@20.17.1)(@types/react@18.3.12)(@vitest/ui@2.1.3)(jsdom@24.1.3)(react@18.3.1)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(type-fest@4.26.1)(typescript@5.6.3)(zod@3.23.8)': dependencies: '@dojoengine/recs': 2.0.13(typescript@5.6.3)(zod@3.23.8) - '@dojoengine/state': 1.0.1(@types/node@20.17.1)(@vitest/ui@2.1.3(vitest@2.1.3))(jsdom@24.1.3)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(typescript@5.6.3)(zod@3.23.8) + '@dojoengine/state': 1.0.1(@types/node@20.17.1)(@vitest/ui@2.1.3)(jsdom@24.1.3)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(typescript@5.6.3)(zod@3.23.8) '@dojoengine/torii-client': 1.0.1 '@dojoengine/utils': 1.0.1(starknet@6.11.0(encoding@0.1.13))(typescript@5.6.3)(zod@3.23.8) '@latticexyz/utils': 2.2.14 @@ -10883,12 +10929,12 @@ snapshots: - utf-8-validate - zod - '@dojoengine/state@1.0.1(@types/node@20.17.1)(@vitest/ui@2.1.3(vitest@2.1.3))(jsdom@24.1.3)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(typescript@5.6.3)(zod@3.23.8)': + '@dojoengine/state@1.0.1(@types/node@20.17.1)(@vitest/ui@2.1.3)(jsdom@24.1.3)(starknet@6.11.0(encoding@0.1.13))(terser@5.36.0)(typescript@5.6.3)(zod@3.23.8)': dependencies: '@dojoengine/recs': 2.0.13(typescript@5.6.3)(zod@3.23.8) '@dojoengine/torii-client': 1.0.1 starknet: 6.11.0(encoding@0.1.13) - vitest: 1.6.0(@types/node@20.17.1)(@vitest/ui@2.1.3(vitest@2.1.3))(jsdom@24.1.3)(terser@5.36.0) + vitest: 1.6.0(@types/node@20.17.1)(@vitest/ui@2.1.3)(jsdom@24.1.3)(terser@5.36.0) transitivePeerDependencies: - '@edge-runtime/vm' - '@types/node' @@ -14315,7 +14361,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@2.1.3(vitest@2.1.3(@types/node@20.17.1)(@vitest/ui@2.1.3)(jsdom@24.1.3)(terser@5.36.0))': + '@vitest/coverage-v8@2.1.3(vitest@2.1.3)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -20314,12 +20360,12 @@ snapshots: tsx: 4.19.2 yaml: 2.6.0 - vitest-canvas-mock@0.3.3(vitest@2.1.3(@types/node@20.17.1)(@vitest/ui@2.1.3)(jsdom@24.1.3)(terser@5.36.0)): + vitest-canvas-mock@0.3.3(vitest@2.1.3): dependencies: jest-canvas-mock: 2.5.2 vitest: 2.1.3(@types/node@20.17.1)(@vitest/ui@2.1.3)(jsdom@24.1.3)(terser@5.36.0) - vitest@1.6.0(@types/node@20.17.1)(@vitest/ui@2.1.3(vitest@2.1.3))(jsdom@24.1.3)(terser@5.36.0): + vitest@1.6.0(@types/node@20.17.1)(@vitest/ui@2.1.3)(jsdom@24.1.3)(terser@5.36.0): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 From fcedbb36ca4eba0e7bca48fba8e97b530cd2f3b5 Mon Sep 17 00:00:00 2001 From: tedison <76473430+edisontim@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:04:14 -0500 Subject: [PATCH 3/3] add countdown backdrop (#2383) --- client/src/hooks/context/DojoContext.tsx | 45 +++++++++++++----------- client/src/hooks/useSeasonStart.tsx | 23 ++++++++++++ client/src/ui/layouts/Onboarding.tsx | 31 ---------------- client/src/ui/modules/LoadingScreen.tsx | 42 ++++++++++++++++++++++ client/src/ui/utils/utils.tsx | 6 ++-- 5 files changed, 92 insertions(+), 55 deletions(-) create mode 100644 client/src/hooks/useSeasonStart.tsx diff --git a/client/src/hooks/context/DojoContext.tsx b/client/src/hooks/context/DojoContext.tsx index 09032c5f09..abae0c1971 100644 --- a/client/src/hooks/context/DojoContext.tsx +++ b/client/src/hooks/context/DojoContext.tsx @@ -3,7 +3,7 @@ import { SetupNetworkResult } from "@/dojo/setupNetwork"; import { Position } from "@/types/Position"; import { OnboardingContainer, StepContainer } from "@/ui/layouts/Onboarding"; import { OnboardingButton } from "@/ui/layouts/OnboardingButton"; -import { LoadingScreen } from "@/ui/modules/LoadingScreen"; +import { CountdownTimer, LoadingScreen } from "@/ui/modules/LoadingScreen"; import { ACCOUNT_CHANGE_EVENT, SpectateButton } from "@/ui/modules/onboarding/Steps"; import { ContractAddress } from "@bibliothecadao/eternum"; import ControllerConnector from "@cartridge/connector/controller"; @@ -15,7 +15,7 @@ import { ReactNode, createContext, useContext, useEffect, useMemo, useState } fr import { Account, AccountInterface, RpcProvider } from "starknet"; import { Env, env } from "../../../env"; import { SetupResult } from "../../dojo/setup"; -import { displayAddress, getRandomBackgroundImage } from "../../ui/utils/utils"; +import { displayAddress } from "../../ui/utils/utils"; import { useQuery } from "../helpers/useQuery"; import { useAddressStore } from "../store/useAddressStore"; import useUIStore from "../store/useUIStore"; @@ -261,7 +261,6 @@ const DojoContextProvider = ({ return ; } - // Handle Loading Screen if (isDev) { if (!burnerAccount) { return ; @@ -272,24 +271,28 @@ const DojoContextProvider = ({ } if (!isConnected && !isConnecting && !controllerAccount && !isSpectatorMode) { return ( - - -
- {!isConnected && ( - <> - - - - Log In - - - )} -
-
-
+ <> + + + + +
+ {!isConnected && ( + <> + + + + Log In + + + )} +
+
+
+ ); } diff --git a/client/src/hooks/useSeasonStart.tsx b/client/src/hooks/useSeasonStart.tsx new file mode 100644 index 0000000000..529a2df688 --- /dev/null +++ b/client/src/hooks/useSeasonStart.tsx @@ -0,0 +1,23 @@ +import { configManager } from "@/dojo/setup"; +import { useEffect, useState } from "react"; + +export const useSeasonStart = () => { + const seasonStart = BigInt(configManager.getSeasonConfig().startAt || 0); + const nextBlockTimestamp = BigInt(Math.floor(Date.now() / 1000)); + + const [countdown, setCountdown] = useState(0n); + useEffect(() => { + if (nextBlockTimestamp === 0n || seasonStart === 0n) return; + + const initialCountdown = seasonStart - nextBlockTimestamp; + setCountdown(initialCountdown); + + const timer = setInterval(() => { + setCountdown((prev) => prev - 1n); + }, 1000); + + return () => clearInterval(timer); + }, [nextBlockTimestamp, seasonStart]); + + return { seasonStart, countdown, nextBlockTimestamp }; +}; diff --git a/client/src/ui/layouts/Onboarding.tsx b/client/src/ui/layouts/Onboarding.tsx index 3b49027e07..f0aaee150f 100644 --- a/client/src/ui/layouts/Onboarding.tsx +++ b/client/src/ui/layouts/Onboarding.tsx @@ -148,7 +148,6 @@ export const OnboardingContainer = ({ children, backgroundImage, controller = tr />
- {children}
@@ -265,33 +264,3 @@ const SeasonPassButton = ({ setSettleRealm }: SeasonPassButtonProps) => { ); }; -const SeasonStartTimer = () => { - const nextBlockTimestamp = BigInt(useUIStore.getState().nextBlockTimestamp || 0); - const seasonStart = BigInt(configManager.getSeasonConfig().startAt || 0); - - const [countdown, setCountdown] = useState(0n); - useEffect(() => { - if (nextBlockTimestamp === 0n || seasonStart === 0n) return; - - const initialCountdown = seasonStart - nextBlockTimestamp; - setCountdown(initialCountdown); - - const timer = setInterval(() => { - setCountdown((prev) => prev - 1n); - }, 1000); - - return () => clearInterval(timer); - }, [nextBlockTimestamp, seasonStart]); - - if (countdown <= 0n) { - return null; - } - - return ( -
-
-

{formatTime(Number(countdown), undefined, false, true)}

-
-
- ); -}; diff --git a/client/src/ui/modules/LoadingScreen.tsx b/client/src/ui/modules/LoadingScreen.tsx index 33e71070a4..49de190afc 100644 --- a/client/src/ui/modules/LoadingScreen.tsx +++ b/client/src/ui/modules/LoadingScreen.tsx @@ -1,3 +1,4 @@ +import { useSeasonStart } from "@/hooks/useSeasonStart"; import { useEffect, useState } from "react"; import "../../index.css"; import { OnboardingContainer, StepContainer } from "../layouts/Onboarding"; @@ -50,3 +51,44 @@ export const LoadingScreen = ({ backgroundImage }: { backgroundImage: string }) ); }; + +export function CountdownTimer() { + const { seasonStart, countdown, nextBlockTimestamp } = useSeasonStart(); + + const days = Math.floor(Number(countdown) / (3600 * 24)); + const hours = Math.floor((Number(countdown) % (3600 * 24)) / 3600); + const minutes = Math.floor((Number(countdown) % 3600) / 60); + const seconds = Number(countdown) % 60; + + if (countdown < 0 || nextBlockTimestamp === 0n || seasonStart === 0n) return null; + + return ( +
+
+
+ +

Eternum is Launching in

+
+ + + + +
+
+
+ ); +} + +interface TimeUnitProps { + value: number; + label: string; +} + +function TimeUnit({ value, label }: TimeUnitProps) { + return ( +
+ {value.toString().padStart(2, "0")} + {label} +
+ ); +} diff --git a/client/src/ui/utils/utils.tsx b/client/src/ui/utils/utils.tsx index bd92c0e80f..d5c21ee028 100644 --- a/client/src/ui/utils/utils.tsx +++ b/client/src/ui/utils/utils.tsx @@ -246,10 +246,10 @@ export const formatTime = ( const formattedSeconds = remainingSeconds.toString().padStart(2, "0"); parts.push(`${formattedHours}:${formattedMinutes}:${formattedSeconds}`); } else { - if (hours > 0 && format & TimeFormat.H) parts.push(`${hours} ${abbreviate ? "h" : "hour(s)"}`); - if (minutes > 0 && format & TimeFormat.M) parts.push(`${minutes} ${abbreviate ? "m" : "minute(s)"}`); + if (hours > 0 && format & TimeFormat.H) parts.push(`${hours}${abbreviate ? "h" : " hour(s)"}`); + if (minutes > 0 && format & TimeFormat.M) parts.push(`${minutes}${abbreviate ? "m" : " minute(s)"}`); if (remainingSeconds > 0 && format & TimeFormat.S) - parts.push(`${remainingSeconds} ${abbreviate ? "s" : "second(s)"}`); + parts.push(`${remainingSeconds}${abbreviate ? "s" : " second(s)"}`); } return parts.join(" ");