From 1eadf1ae9621d6af39e2fe676822785e75fea64f Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 2 Nov 2023 10:29:55 +0100 Subject: [PATCH 01/27] Add basic animation for population bubble --- .../assets/partners/arbitrum-population.svg | 31 ++++++++++++ .../partners/cyberconnect-population.svg | 31 ++++++++++++ .../assets/partners/frax-population.svg | 31 ++++++++++++ .../assets/partners/galxe-population.svg | 31 ++++++++++++ .../assets/partners/gitcoin-population.svg | 31 ++++++++++++ src/shared/constants/partners.ts | 31 ++++++++++-- src/shared/constants/realms-data.ts | 10 ++-- src/shared/types/island.ts | 2 +- src/ui/Footer/RealmBar/RealmBarIcon.tsx | 2 +- src/ui/Island/IslandRealms.tsx | 40 +++++++++------- src/ui/Island/Realm.tsx | 48 +++++++++++++++++++ 11 files changed, 258 insertions(+), 30 deletions(-) create mode 100644 src/shared/assets/partners/arbitrum-population.svg create mode 100644 src/shared/assets/partners/cyberconnect-population.svg create mode 100644 src/shared/assets/partners/frax-population.svg create mode 100644 src/shared/assets/partners/galxe-population.svg create mode 100644 src/shared/assets/partners/gitcoin-population.svg diff --git a/src/shared/assets/partners/arbitrum-population.svg b/src/shared/assets/partners/arbitrum-population.svg new file mode 100644 index 000000000..278a2ea3e --- /dev/null +++ b/src/shared/assets/partners/arbitrum-population.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/shared/assets/partners/cyberconnect-population.svg b/src/shared/assets/partners/cyberconnect-population.svg new file mode 100644 index 000000000..9bd71ee12 --- /dev/null +++ b/src/shared/assets/partners/cyberconnect-population.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/shared/assets/partners/frax-population.svg b/src/shared/assets/partners/frax-population.svg new file mode 100644 index 000000000..aee020d86 --- /dev/null +++ b/src/shared/assets/partners/frax-population.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/shared/assets/partners/galxe-population.svg b/src/shared/assets/partners/galxe-population.svg new file mode 100644 index 000000000..ceb4c4326 --- /dev/null +++ b/src/shared/assets/partners/galxe-population.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/shared/assets/partners/gitcoin-population.svg b/src/shared/assets/partners/gitcoin-population.svg new file mode 100644 index 000000000..4ec576b7e --- /dev/null +++ b/src/shared/assets/partners/gitcoin-population.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/shared/constants/partners.ts b/src/shared/constants/partners.ts index 8ab975791..11aeaa868 100644 --- a/src/shared/constants/partners.ts +++ b/src/shared/constants/partners.ts @@ -1,8 +1,13 @@ import arbitrum from "shared/assets/partners/arbitrum.svg" +import arbitrumPopulation from "shared/assets/partners/arbitrum-population.svg" import cyberconnect from "shared/assets/partners/cyberconnect.svg" +import cyberconnectPopulation from "shared/assets/partners/cyberconnect-population.svg" import frax from "shared/assets/partners/frax.svg" +import fraxPopulation from "shared/assets/partners/frax-population.svg" import galxe from "shared/assets/partners/galxe.svg" +import galxePopulation from "shared/assets/partners/galxe-population.svg" import gitcoin from "shared/assets/partners/gitcoin.svg" +import gitcoinPopulation from "shared/assets/partners/gitcoin-population.svg" import arbitrumShadow from "shared/assets/partners/arbitrum-shadow.svg" import cyberconnectShadow from "shared/assets/partners/cyberconnect-shadow.svg" import fraxShadow from "shared/assets/partners/frax-shadow.svg" @@ -10,9 +15,25 @@ import galxeShadow from "shared/assets/partners/galxe-shadow.svg" import gitcoinShadow from "shared/assets/partners/gitcoin-shadow.svg" export default { - arbitrum: { default: arbitrum, shadow: arbitrumShadow }, - cyberconnect: { default: cyberconnect, shadow: cyberconnectShadow }, - frax: { default: frax, shadow: fraxShadow }, - galxe: { default: galxe, shadow: galxeShadow }, - gitcoin: { default: gitcoin, shadow: gitcoinShadow }, + arbitrum: { + default: arbitrum, + shadow: arbitrumShadow, + population: arbitrumPopulation, + }, + cyberconnect: { + default: cyberconnect, + shadow: cyberconnectShadow, + population: cyberconnectPopulation, + }, + frax: { default: frax, shadow: fraxShadow, population: fraxPopulation }, + galxe: { + default: galxe, + shadow: galxeShadow, + population: galxePopulation, + }, + gitcoin: { + default: gitcoin, + shadow: gitcoinShadow, + population: gitcoinPopulation, + }, } diff --git a/src/shared/constants/realms-data.ts b/src/shared/constants/realms-data.ts index a6cd44754..601d7d1fc 100644 --- a/src/shared/constants/realms-data.ts +++ b/src/shared/constants/realms-data.ts @@ -71,7 +71,7 @@ export const realm4 = { color: "#D1F5F5", labelX: 380, labelY: 216.5, - partnerLogo: partners.gitcoin, + partnerIcons: partners.gitcoin, partnerColor: "#1D4E56", } @@ -127,7 +127,7 @@ export const realm7 = { color: "#6FE2A5", labelX: 540, labelY: 400, - partnerLogo: partners.cyberconnect, + partnerIcons: partners.cyberconnect, partnerColor: "#fff", } @@ -165,7 +165,7 @@ export const realm9 = { color: "#12AAFF", labelX: 700, labelY: 350, - partnerLogo: partners.arbitrum, + partnerIcons: partners.arbitrum, } export const realm10 = { @@ -357,7 +357,7 @@ export const realm19 = { color: "#1E5DFF", labelX: 264, labelY: 409, - partnerLogo: partners.galxe, + partnerIcons: partners.galxe, } export const realm20 = { @@ -411,7 +411,7 @@ export const realm22 = { color: "#E4EEEE", labelX: 337, labelY: 388, - partnerLogo: partners.frax, + partnerIcons: partners.frax, } export const realm23 = { diff --git a/src/shared/types/island.ts b/src/shared/types/island.ts index d044f00f6..7ee456d7e 100644 --- a/src/shared/types/island.ts +++ b/src/shared/types/island.ts @@ -78,6 +78,6 @@ export type RealmMapData = { color: string labelX: number labelY: number - partnerLogo: { default: string; shadow: string } + partnerIcons: { default: string; shadow: string; population: string } partnerColor?: string } diff --git a/src/ui/Footer/RealmBar/RealmBarIcon.tsx b/src/ui/Footer/RealmBar/RealmBarIcon.tsx index b82f0b09d..9b12427cf 100644 --- a/src/ui/Footer/RealmBar/RealmBarIcon.tsx +++ b/src/ui/Footer/RealmBar/RealmBarIcon.tsx @@ -29,7 +29,7 @@ export default function RealmBarIcon({ onMouseLeave={() => setIsTooltipVisible(false)} > - {realmImgLayers.map(({ realm, layer: crop, partnerLogo }) => ( - - ))} + {realmImgLayers.map( + ({ realm, layer: crop, partnerLogo, populationIcon }) => ( + + ) + )} ) } diff --git a/src/ui/Island/Realm.tsx b/src/ui/Island/Realm.tsx index bd83f660e..07eea99ae 100644 --- a/src/ui/Island/Realm.tsx +++ b/src/ui/Island/Realm.tsx @@ -11,6 +11,7 @@ import { REALM_FONT_FAMILY, REALM_FONT_STYLE, } from "shared/constants" +import { useInterval } from "shared/hooks" import { useIslandContext } from "../../shared/hooks/island" type RealmProps = { @@ -26,6 +27,7 @@ type RealmProps = { labelX: number labelY: number partnerLogo: HTMLImageElement + populationIcon: HTMLImageElement } export default function Realm({ @@ -41,6 +43,7 @@ export default function Realm({ labelX, labelY, partnerLogo, + populationIcon, }: RealmProps) { const [isHovered, setIsHovered] = useState(false) const [, setIsSelected] = useState(false) @@ -52,6 +55,7 @@ export default function Realm({ const imageLayerRef = useRef(null) const overlayRef = useRef(null) const partnerLogoRef = useRef(null) + const bubbleRef = useRef(null) const handleRealmClick = () => { setIsSelected((prev) => !prev) @@ -114,6 +118,11 @@ export default function Realm({ x: x + labelX + partnerLogoTranslate, y: y + labelY - 20, }, + population: { + opacity: 0, + x: x + labelX + partnerLogoTranslate, + y: y + labelY - 20, + }, }, highlight: { image: { shadowOpacity: 1 }, @@ -125,6 +134,11 @@ export default function Realm({ x: x + labelX + partnerLogoTranslate, y: y + labelY - 220, }, + population: { + opacity: 1, + x: x + labelX + partnerLogoTranslate, + y: y + labelY - 220, + }, }, } @@ -194,6 +208,31 @@ export default function Realm({ } }, [isHovered]) + const [showPopulationBubble, setShowPopulationBubble] = useState(false) + + useInterval(() => { + setShowPopulationBubble(true) + }, 5000) + + const [bubbleProps] = useSpring(() => { + const destinationStyle = showPopulationBubble + ? styles.highlight.population + : styles.default.population + + const config = { + precision: 0.0001, + duration: 2000, + easing: easings.easeOutCubic, + } + + return { + from: styles.default.population, + to: destinationStyle, + config, + onRest: () => setShowPopulationBubble(false), + } + }, [showPopulationBubble]) + return ( {/* @ts-expect-error FIXME: @react-spring-types */} @@ -253,6 +292,15 @@ export default function Realm({ scaleY={3.5} {...partnerLogoProps} /> + {/* This is the population bubble image */} + ) } From 083bb5ebe22668577e0c53e1426715c39a1e6dc7 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 2 Nov 2023 11:14:36 +0100 Subject: [PATCH 02/27] Use a hook to showing population bubble --- src/shared/hooks/island.ts | 17 +++++++++++++++++ src/ui/Island/Realm.tsx | 18 ++++++++---------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/shared/hooks/island.ts b/src/shared/hooks/island.ts index 451fe2893..851e76c64 100644 --- a/src/shared/hooks/island.ts +++ b/src/shared/hooks/island.ts @@ -12,6 +12,7 @@ import { useDappSelector, selectWalletAddress, selectRealms, + selectPopulationById, } from "redux-state" import { SECOND } from "shared/constants" import { @@ -137,3 +138,19 @@ export function useStakeCooldownPeriod() { return { hasCooldown: !!timeRemaining && timeRemaining > 0, timeRemaining } } + +export function usePopulationBubble(realmId: string): { + showBubble: boolean + setShowBubble: (newValue: boolean) => void +} { + const population = useDappSelector((state) => + selectPopulationById(state, realmId) + ) + const [showBubble, setShowBubble] = useState(false) + + useEffect(() => { + setShowBubble(true) + }, [population]) + + return { showBubble, setShowBubble } +} diff --git a/src/ui/Island/Realm.tsx b/src/ui/Island/Realm.tsx index 07eea99ae..31b14ca75 100644 --- a/src/ui/Island/Realm.tsx +++ b/src/ui/Island/Realm.tsx @@ -11,8 +11,10 @@ import { REALM_FONT_FAMILY, REALM_FONT_STYLE, } from "shared/constants" -import { useInterval } from "shared/hooks" -import { useIslandContext } from "../../shared/hooks/island" +import { + useIslandContext, + usePopulationBubble, +} from "../../shared/hooks/island" type RealmProps = { id: string @@ -208,14 +210,10 @@ export default function Realm({ } }, [isHovered]) - const [showPopulationBubble, setShowPopulationBubble] = useState(false) - - useInterval(() => { - setShowPopulationBubble(true) - }, 5000) + const { showBubble, setShowBubble } = usePopulationBubble(id) const [bubbleProps] = useSpring(() => { - const destinationStyle = showPopulationBubble + const destinationStyle = showBubble ? styles.highlight.population : styles.default.population @@ -229,9 +227,9 @@ export default function Realm({ from: styles.default.population, to: destinationStyle, config, - onRest: () => setShowPopulationBubble(false), + onRest: () => setShowBubble(false), } - }, [showPopulationBubble]) + }, [showBubble]) return ( From ebf2052652346c6938b1a63f025ea395e9eb312b Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 2 Nov 2023 13:04:48 +0100 Subject: [PATCH 03/27] Create a displayed population to handle bubbles --- src/redux-state/selectors/island.ts | 12 ++++++++---- src/redux-state/slices/island.ts | 10 ++++++++++ src/redux-state/thunks/island.ts | 15 +++++++++++---- src/shared/components/RealmModal/RealmHeader.tsx | 4 +++- src/shared/contracts/realmsData.ts | 1 + src/shared/hooks/island.ts | 2 +- src/shared/hooks/wallets.ts | 2 +- src/shared/types/island.ts | 1 + src/shared/utils/island.ts | 4 ++-- 9 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/redux-state/selectors/island.ts b/src/redux-state/selectors/island.ts index 306ea30dc..b83e214f5 100644 --- a/src/redux-state/selectors/island.ts +++ b/src/redux-state/selectors/island.ts @@ -171,27 +171,31 @@ export const selectSortedPopulation = createSelector(selectRealms, (realms) => { ...data, })) - const sortedRealms = realmsData.sort((a, b) => a.population - b.population) + const sortedRealms = realmsData.sort( + (a, b) => a.displayedPopulation - b.displayedPopulation + ) return sortedRealms }) export const selectPopulationById = createSelector( selectRealmById, - (realm) => realm?.population ?? 0 + (realm) => realm?.displayedPopulation ?? 0 ) export const selectTotalPopulation = createSelector( selectSortedPopulation, (realms) => realms.length - ? realms.map((realm) => realm.population).reduce((a, b) => a + b) + ? realms.map((realm) => realm.displayedPopulation).reduce((a, b) => a + b) : 0 ) export const selectMaxPopulation = createSelector( selectSortedPopulation, (realms) => - realms.length ? Math.max(...realms.map((realm) => realm.population)) : 0 + realms.length + ? Math.max(...realms.map((realm) => realm.displayedPopulation)) + : 0 ) /* Helpful selectors */ diff --git a/src/redux-state/slices/island.ts b/src/redux-state/slices/island.ts index 3208b3ef3..e493df92c 100644 --- a/src/redux-state/slices/island.ts +++ b/src/redux-state/slices/island.ts @@ -75,6 +75,15 @@ const islandSlice = createSlice({ immerState.realms[realmPopulation.id].population = realmPopulation.population }, + setRealmDisplayedPopulation: ( + immerState, + { + payload: realmPopulation, + }: { payload: { id: string; population: number } } + ) => { + immerState.realms[realmPopulation.id].displayedPopulation = + realmPopulation.population + }, setRealmXpAllocatable: ( immerState, { @@ -139,6 +148,7 @@ export const { resetIslandDisplay, resetIslandAccount, setRealmPopulation, + setRealmDisplayedPopulation, setRealmXpAllocatable, setRealmsData, setDisplayedRealmId, diff --git a/src/redux-state/thunks/island.ts b/src/redux-state/thunks/island.ts index 1afcd3876..d4346c064 100644 --- a/src/redux-state/thunks/island.ts +++ b/src/redux-state/thunks/island.ts @@ -6,6 +6,7 @@ import { setRealmXpAllocatable, setRealmsData, setSeasonInfo, + setRealmDisplayedPopulation, } from "redux-state/slices/island" import { REALMS_WITH_CONTRACT_NAME, @@ -95,7 +96,10 @@ export const initSeasonInfoData = createDappAsyncThunk( export const fetchPopulation = createDappAsyncThunk( "island/fetchPopulation", - async (_, { getState, dispatch, extra: { transactionService } }) => { + async ( + isInitFetch: boolean, + { getState, dispatch, extra: { transactionService } } + ) => { const { island: { realms }, } = getState() @@ -107,7 +111,10 @@ export const fetchPopulation = createDappAsyncThunk( }) if (result) { - result.forEach((data) => dispatch(setRealmPopulation(data))) + result.forEach((data) => { + dispatch(setRealmPopulation(data)) + if (isInitFetch) dispatch(setRealmDisplayedPopulation(data)) + }) } return !!result @@ -238,7 +245,7 @@ export const stakeTaho = createDappAsyncThunk( if (receipt) { dispatch(fetchWalletBalances()) - dispatch(fetchPopulation()) + dispatch(fetchPopulation(false)) } return !!receipt @@ -281,7 +288,7 @@ export const unstakeTaho = createDappAsyncThunk( if (receipt) { dispatch(fetchWalletBalances()) - dispatch(fetchPopulation()) + dispatch(fetchPopulation(false)) } return !!receipt diff --git a/src/shared/components/RealmModal/RealmHeader.tsx b/src/shared/components/RealmModal/RealmHeader.tsx index e8c0aa48c..61e8db32e 100644 --- a/src/shared/components/RealmModal/RealmHeader.tsx +++ b/src/shared/components/RealmModal/RealmHeader.tsx @@ -43,7 +43,9 @@ export default function RealmHeader() { /> Population - {separateThousandsByComma(realm?.population ?? 0)} + + {separateThousandsByComma(realm?.displayedPopulation ?? 0)} +
{ - await dispatch(fetchPopulation()) + await dispatch(fetchPopulation(true)) await dispatch(fetchXpAllocatable()) } diff --git a/src/shared/hooks/wallets.ts b/src/shared/hooks/wallets.ts index 3d5c7fa63..d7756dd3b 100644 --- a/src/shared/hooks/wallets.ts +++ b/src/shared/hooks/wallets.ts @@ -113,7 +113,7 @@ export function usePopulationFetch() { const populationFetchCallback = useCallback(async () => { if (account && dispatch) { - await dispatch(fetchPopulation()) + await dispatch(fetchPopulation(false)) } }, [account, dispatch]) diff --git a/src/shared/types/island.ts b/src/shared/types/island.ts index 7ee456d7e..95a2b1749 100644 --- a/src/shared/types/island.ts +++ b/src/shared/types/island.ts @@ -10,6 +10,7 @@ export type RealmAddressesData = { export type RealmContractData = { name: string population: number + displayedPopulation: number xpAllocatable: string xpToken: { name: string; symbol: string; contractAddress: string } } diff --git a/src/shared/utils/island.ts b/src/shared/utils/island.ts index 97ed73eb6..8dcffdf67 100644 --- a/src/shared/utils/island.ts +++ b/src/shared/utils/island.ts @@ -173,7 +173,7 @@ export function calculatePopulationIconsPositions( const positions: number[] = [] realmsData.forEach((realm, index) => { - const populationShare = realm.population / maxValue + const populationShare = realm.displayedPopulation / maxValue let iconPosition = Math.max( populationShare * width + POPULATION_BAR_GAP, POPULATION_BAR_GAP + index * POPULATION_ICON_SIZE @@ -185,7 +185,7 @@ export function calculatePopulationIconsPositions( } // Realm with biggest population - if (realm.population === maxValue) { + if (realm.displayedPopulation === maxValue) { iconPosition = width - (POPULATION_BAR_GAP + POPULATION_ICON_SIZE) } From 2473f31ee7078d12ac7d186df8549f0891afb014 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 2 Nov 2023 14:19:16 +0100 Subject: [PATCH 04/27] Use interval for updating population --- src/redux-state/selectors/island.ts | 5 +++++ src/shared/hooks/island.ts | 29 ++++++++++++++++++++++++++--- src/ui/Footer/RealmBar/index.tsx | 2 +- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/redux-state/selectors/island.ts b/src/redux-state/selectors/island.ts index b83e214f5..7c914ab9c 100644 --- a/src/redux-state/selectors/island.ts +++ b/src/redux-state/selectors/island.ts @@ -178,6 +178,11 @@ export const selectSortedPopulation = createSelector(selectRealms, (realms) => { }) export const selectPopulationById = createSelector( + selectRealmById, + (realm) => realm?.population ?? 0 +) + +export const selectDisplayedPopulationById = createSelector( selectRealmById, (realm) => realm?.displayedPopulation ?? 0 ) diff --git a/src/shared/hooks/island.ts b/src/shared/hooks/island.ts index ea4a3c86c..25d5d0095 100644 --- a/src/shared/hooks/island.ts +++ b/src/shared/hooks/island.ts @@ -13,6 +13,8 @@ import { selectWalletAddress, selectRealms, selectPopulationById, + selectDisplayedPopulationById, + setRealmDisplayedPopulation, } from "redux-state" import { SECOND } from "shared/constants" import { @@ -146,11 +148,32 @@ export function usePopulationBubble(realmId: string): { const population = useDappSelector((state) => selectPopulationById(state, realmId) ) + const displayedPopulation = useDappSelector((state) => + selectDisplayedPopulationById(state, realmId) + ) + const dispatch = useDappDispatch() + const [showBubble, setShowBubble] = useState(false) - useEffect(() => { - setShowBubble(true) - }, [population]) + const populationCallback = useCallback(async () => { + if (population < displayedPopulation) { + await dispatch( + setRealmDisplayedPopulation({ + id: realmId, + population, + }) + ) + } else if (population > displayedPopulation) { + setShowBubble(true) + await dispatch( + setRealmDisplayedPopulation({ + id: realmId, + population: displayedPopulation + 1, + }) + ) + } + }, [population, displayedPopulation, dispatch, realmId]) + useInterval(populationCallback, population ? SECOND * 10 : null) return { showBubble, setShowBubble } } diff --git a/src/ui/Footer/RealmBar/index.tsx b/src/ui/Footer/RealmBar/index.tsx index 17671594b..981ed60d3 100644 --- a/src/ui/Footer/RealmBar/index.tsx +++ b/src/ui/Footer/RealmBar/index.tsx @@ -76,7 +76,7 @@ export default function RealmsBar() { key={realm.id} id={realm.id} position={positions[index]} - population={realm.population} + population={realm.displayedPopulation} name={realm.name} /> ))} From 3aa7f0fdd8c442d44ede09bc836bfeb673be101d Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 3 Nov 2023 11:32:09 +0100 Subject: [PATCH 05/27] Generate random intervals for bubbles --- src/shared/hooks/island.ts | 5 ++++- src/shared/utils/misc.ts | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/shared/hooks/island.ts b/src/shared/hooks/island.ts index 25d5d0095..344da966f 100644 --- a/src/shared/hooks/island.ts +++ b/src/shared/hooks/island.ts @@ -25,6 +25,7 @@ import { initRealmsDataFromContracts, initSeasonInfoData, } from "redux-state/thunks/island" +import { randomInteger } from "shared/utils" import { useArbitrumProvider } from "./wallets" import { useInterval } from "./helpers" @@ -154,6 +155,8 @@ export function usePopulationBubble(realmId: string): { const dispatch = useDappDispatch() const [showBubble, setShowBubble] = useState(false) + // Generate random intervals for realms + const [delay] = useState(randomInteger(5, 15) * SECOND) const populationCallback = useCallback(async () => { if (population < displayedPopulation) { @@ -174,6 +177,6 @@ export function usePopulationBubble(realmId: string): { } }, [population, displayedPopulation, dispatch, realmId]) - useInterval(populationCallback, population ? SECOND * 10 : null) + useInterval(populationCallback, population ? delay : null) return { showBubble, setShowBubble } } diff --git a/src/shared/utils/misc.ts b/src/shared/utils/misc.ts index df70a7788..ded12cfca 100644 --- a/src/shared/utils/misc.ts +++ b/src/shared/utils/misc.ts @@ -126,3 +126,7 @@ export function createImageElement(source: string) { return image } + +export function randomInteger(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1)) + min +} From 44669d171628a683f4e991dc9c0941fcce1929f5 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 3 Nov 2023 14:24:19 +0100 Subject: [PATCH 06/27] Adding bubble to the modal window --- src/shared/components/RealmCutout/Bubble.tsx | 48 +++++++++++++++++++ .../components/RealmCutout/RealmCutout.tsx | 2 + src/shared/constants/realms.ts | 11 +++++ src/ui/Island/Realm.tsx | 22 ++++----- 4 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 src/shared/components/RealmCutout/Bubble.tsx diff --git a/src/shared/components/RealmCutout/Bubble.tsx b/src/shared/components/RealmCutout/Bubble.tsx new file mode 100644 index 000000000..b853613cd --- /dev/null +++ b/src/shared/components/RealmCutout/Bubble.tsx @@ -0,0 +1,48 @@ +import { animated, easings, useSpring } from "@react-spring/web" +import React, { useEffect, useState } from "react" +import { getRealmPopulationIcon } from "shared/constants" +import { usePopulationBubble } from "shared/hooks" + +export const STYLE = { + default: { opacity: 0, transform: "translateY(150px)" }, + highlight: { opacity: 1, transform: "translateY(50px)" }, +} + +export const BUBBLE_CONFIG = { + precision: 0.0001, + duration: 2000, + easing: easings.easeOutCubic, +} + +export default function Bubble({ realmId }: { realmId: string }) { + const { showBubble, setShowBubble } = usePopulationBubble(realmId) + + const [iconSrc, setIconSrc] = useState(null) + + useEffect(() => { + const icon = getRealmPopulationIcon(realmId) + setIconSrc(icon) + }, [realmId]) + + const [bubbleProps] = useSpring( + () => ({ + from: STYLE.default, + to: showBubble ? STYLE.highlight : STYLE.default, + config: BUBBLE_CONFIG, + onRest: () => setShowBubble(false), + }), + [showBubble] + ) + + if (!iconSrc) return null + + return ( + +
+ Bubble +
+
+ ) +} diff --git a/src/shared/components/RealmCutout/RealmCutout.tsx b/src/shared/components/RealmCutout/RealmCutout.tsx index 756a3ae9c..b8da2f2e5 100644 --- a/src/shared/components/RealmCutout/RealmCutout.tsx +++ b/src/shared/components/RealmCutout/RealmCutout.tsx @@ -8,6 +8,7 @@ import { useDappSelector, } from "redux-state" import RealmPin from "./RealmPin" +import Bubble from "./Bubble" const CUTOUT_HEIGHT = 208 const CUTOUT_WIDTH = 356 @@ -27,6 +28,7 @@ export default function RealmCutout() { return ( <>
+ {isStakedRealm && } realm.id === realmId) + ?.partnerIcons.population + + if (!populationIcon) { + throw new Error(`Missing population icon for realm ${realmId}`) + } + + return populationIcon +} + export const REALM_FONT_SIZE = 78 export const REALM_FONT_FAMILY = "QuincyCF" export const REALM_FONT_STYLE = "bold" diff --git a/src/ui/Island/Realm.tsx b/src/ui/Island/Realm.tsx index 31b14ca75..ab92e48ff 100644 --- a/src/ui/Island/Realm.tsx +++ b/src/ui/Island/Realm.tsx @@ -11,6 +11,8 @@ import { REALM_FONT_FAMILY, REALM_FONT_STYLE, } from "shared/constants" +import { BUBBLE_CONFIG } from "shared/components/RealmCutout/Bubble" +import { selectDisplayedRealmId, useDappSelector } from "redux-state" import { useIslandContext, usePopulationBubble, @@ -47,6 +49,7 @@ export default function Realm({ partnerLogo, populationIcon, }: RealmProps) { + const realmId = useDappSelector(selectDisplayedRealmId) const [isHovered, setIsHovered] = useState(false) const [, setIsSelected] = useState(false) @@ -213,23 +216,20 @@ export default function Realm({ const { showBubble, setShowBubble } = usePopulationBubble(id) const [bubbleProps] = useSpring(() => { - const destinationStyle = showBubble - ? styles.highlight.population - : styles.default.population - - const config = { - precision: 0.0001, - duration: 2000, - easing: easings.easeOutCubic, - } + // To prevent lag in animation, let's show only one babel for the realm. + // When a modal for the realm is open, do not show a bubble on the map. + const destinationStyle = + showBubble && !(realmId === id) + ? styles.highlight.population + : styles.default.population return { from: styles.default.population, to: destinationStyle, - config, + config: BUBBLE_CONFIG, onRest: () => setShowBubble(false), } - }, [showBubble]) + }, [showBubble, realmId, id]) return ( From fa2bd1c253b92c2d2a59332b2e1fbd1c516c2d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Mon, 6 Nov 2023 14:22:58 +0100 Subject: [PATCH 07/27] Set correct bubble animation --- src/shared/components/RealmCutout/Bubble.tsx | 2 +- src/ui/Footer/RealmBar/RealmBarTooltip.tsx | 2 +- src/ui/Island/Realm.tsx | 22 ++++++++++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/shared/components/RealmCutout/Bubble.tsx b/src/shared/components/RealmCutout/Bubble.tsx index b853613cd..b3cb1c485 100644 --- a/src/shared/components/RealmCutout/Bubble.tsx +++ b/src/shared/components/RealmCutout/Bubble.tsx @@ -9,7 +9,7 @@ export const STYLE = { } export const BUBBLE_CONFIG = { - precision: 0.0001, + precision: 0.1, duration: 2000, easing: easings.easeOutCubic, } diff --git a/src/ui/Footer/RealmBar/RealmBarTooltip.tsx b/src/ui/Footer/RealmBar/RealmBarTooltip.tsx index 77938557b..bf71aba3f 100644 --- a/src/ui/Footer/RealmBar/RealmBarTooltip.tsx +++ b/src/ui/Footer/RealmBar/RealmBarTooltip.tsx @@ -36,7 +36,7 @@ export default function RealmBarTooltip({

{name}

- {separateThousandsByComma(population)} + {separateThousandsByComma(population ?? 0)}

diff --git a/src/ui/Island/Realm.tsx b/src/ui/Island/Realm.tsx index ab92e48ff..177902059 100644 --- a/src/ui/Island/Realm.tsx +++ b/src/ui/Island/Realm.tsx @@ -142,6 +142,13 @@ export default function Realm({ population: { opacity: 1, x: x + labelX + partnerLogoTranslate, + y: y + labelY - 120, + }, + }, + finish: { + population: { + opacity: 0, + x: x + labelX + partnerLogoTranslate, y: y + labelY - 220, }, }, @@ -215,19 +222,26 @@ export default function Realm({ const { showBubble, setShowBubble } = usePopulationBubble(id) - const [bubbleProps] = useSpring(() => { + const [bubbleProps, set] = useSpring(() => { // To prevent lag in animation, let's show only one babel for the realm. // When a modal for the realm is open, do not show a bubble on the map. const destinationStyle = showBubble && !(realmId === id) - ? styles.highlight.population - : styles.default.population + ? [styles.highlight.population, styles.finish.population] + : { opacity: 0 } return { from: styles.default.population, to: destinationStyle, config: BUBBLE_CONFIG, - onRest: () => setShowBubble(false), + onRest: () => { + setShowBubble(false) + + // Restore bubble's initial position after animation has finished + if (!showBubble) { + set({ to: styles.default.population }) + } + }, } }, [showBubble, realmId, id]) From 581761200a77831d79afd66e966290160f2c051d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Mon, 6 Nov 2023 15:57:19 +0100 Subject: [PATCH 08/27] Animate `RealmBarIcon` component --- src/ui/Footer/RealmBar/RealmBarIcon.tsx | 117 +++++++++++++++++------- 1 file changed, 83 insertions(+), 34 deletions(-) diff --git a/src/ui/Footer/RealmBar/RealmBarIcon.tsx b/src/ui/Footer/RealmBar/RealmBarIcon.tsx index 9b12427cf..fbbe3dfcd 100644 --- a/src/ui/Footer/RealmBar/RealmBarIcon.tsx +++ b/src/ui/Footer/RealmBar/RealmBarIcon.tsx @@ -1,6 +1,7 @@ -import React, { useState } from "react" +import React, { useEffect, useState } from "react" import Icon from "shared/components/Icon" import { getRealmMapData } from "shared/constants" +import { animated, easings, useSpring } from "@react-spring/web" import RealmBarTooltip from "./RealmBarTooltip" type RealmBarIconProps = { @@ -18,42 +19,90 @@ export default function RealmBarIcon({ }: RealmBarIconProps) { const [isTooltipVisible, setIsTooltipVisible] = useState(false) const currentRealm = getRealmMapData(id) + const [displayedPosition, setDisplayedPosition] = useState(position) + const [prevPosition, setPrevPosition] = useState(null) + + useEffect(() => { + setPrevPosition(displayedPosition) + setDisplayedPosition(position) + }, [displayedPosition, position]) + + const [props] = useSpring( + () => ({ + from: { left: `${prevPosition}px` }, + to: { left: `${displayedPosition}px` }, + config: { duration: 1000, easing: easings.easeOutCubic }, + }), + [prevPosition, displayedPosition] + ) if (!currentRealm) return null return ( - <> -
setIsTooltipVisible(true)} - onMouseLeave={() => setIsTooltipVisible(false)} - > - - -
- - + setIsTooltipVisible(true)} + onMouseLeave={() => setIsTooltipVisible(false)} + > + + + ) + + // return ( + // <> + //
setIsTooltipVisible(true)} + // onMouseLeave={() => setIsTooltipVisible(false)} + // > + // + // + //
+ // + // + // ) } From 2fea5a98a9d1fe7fd799af119cdfb8698a3d6369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Tue, 7 Nov 2023 12:45:15 +0100 Subject: [PATCH 09/27] Fix `RealmBarIcon` animation --- src/shared/utils/island.ts | 2 +- src/ui/Footer/RealmBar/RealmBarIcon.tsx | 20 +++++++++++--------- src/ui/Footer/RealmBar/index.tsx | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/shared/utils/island.ts b/src/shared/utils/island.ts index 8dcffdf67..fbd33f82e 100644 --- a/src/shared/utils/island.ts +++ b/src/shared/utils/island.ts @@ -173,7 +173,7 @@ export function calculatePopulationIconsPositions( const positions: number[] = [] realmsData.forEach((realm, index) => { - const populationShare = realm.displayedPopulation / maxValue + const populationShare = realm.population / maxValue let iconPosition = Math.max( populationShare * width + POPULATION_BAR_GAP, POPULATION_BAR_GAP + index * POPULATION_ICON_SIZE diff --git a/src/ui/Footer/RealmBar/RealmBarIcon.tsx b/src/ui/Footer/RealmBar/RealmBarIcon.tsx index fbbe3dfcd..119374f98 100644 --- a/src/ui/Footer/RealmBar/RealmBarIcon.tsx +++ b/src/ui/Footer/RealmBar/RealmBarIcon.tsx @@ -27,14 +27,15 @@ export default function RealmBarIcon({ setDisplayedPosition(position) }, [displayedPosition, position]) - const [props] = useSpring( - () => ({ - from: { left: `${prevPosition}px` }, - to: { left: `${displayedPosition}px` }, - config: { duration: 1000, easing: easings.easeOutCubic }, - }), - [prevPosition, displayedPosition] - ) + const [props] = useSpring(() => { + if (Number.isNaN(prevPosition) || Number.isNaN(displayedPosition)) return {} + + return { + from: { left: prevPosition ?? 0 }, + to: { left: displayedPosition ?? 0 }, + config: { duration: 2000, easing: easings.easeOutCubic }, + } + }, [prevPosition, displayedPosition]) if (!currentRealm) return null @@ -42,16 +43,17 @@ export default function RealmBarIcon({ setIsTooltipVisible(true)} onMouseLeave={() => setIsTooltipVisible(false)} diff --git a/src/ui/Footer/RealmBar/index.tsx b/src/ui/Footer/RealmBar/index.tsx index 981ed60d3..17671594b 100644 --- a/src/ui/Footer/RealmBar/index.tsx +++ b/src/ui/Footer/RealmBar/index.tsx @@ -76,7 +76,7 @@ export default function RealmsBar() { key={realm.id} id={realm.id} position={positions[index]} - population={realm.displayedPopulation} + population={realm.population} name={realm.name} /> ))} From a3065c160b740edbe8b34896124a87cd315b2791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Tue, 7 Nov 2023 16:21:13 +0100 Subject: [PATCH 10/27] Animate `RealmBarIcon` using `displayedPopulation` property --- src/redux-state/selectors/population.ts | 46 ++++++++------- src/redux-state/utils/population.ts | 24 ++++++++ src/shared/hooks/index.ts | 1 + src/shared/hooks/island.ts | 43 -------------- src/shared/hooks/population.ts | 75 +++++++++++++++++++++++++ src/shared/utils/island.ts | 2 +- src/ui/Footer/RealmBar/index.tsx | 41 ++++---------- src/ui/Island/Realm.tsx | 5 +- 8 files changed, 140 insertions(+), 97 deletions(-) create mode 100644 src/redux-state/utils/population.ts create mode 100644 src/shared/hooks/population.ts diff --git a/src/redux-state/selectors/population.ts b/src/redux-state/selectors/population.ts index fdd240df1..73018c189 100644 --- a/src/redux-state/selectors/population.ts +++ b/src/redux-state/selectors/population.ts @@ -1,27 +1,19 @@ import { createSelector } from "@reduxjs/toolkit" -import { RealmData } from "shared/types" -import { selectRealmById, selectRealms } from "./realm" +import { + getDisplayedPopulationOfRealms, + getPopulationById, + getPopulationOfRealms, + sortPopulation, +} from "redux-state/utils/population" -const getPopulationOfRealms = (realms: RealmData[]) => - realms.map((realm) => realm.population) - -export const selectSortedPopulation = createSelector(selectRealms, (realms) => { - const realmsData = Object.entries(realms).map(([id, data]) => ({ - id, - ...data, - })) - - return realmsData.sort((a, b) => a.population - b.population) -}) - -export const selectPopulationById = createSelector( - selectRealmById, - (realm) => realm?.population ?? 0 +export const selectSortedPopulation = sortPopulation("population") +export const selectSortedDisplayedPopulation = sortPopulation( + "displayedPopulation" ) -export const selectDisplayedPopulationById = createSelector( - selectRealmById, - (realm) => realm?.displayedPopulation ?? 0 +export const selectPopulationById = getPopulationById("population") +export const selectDisplayedPopulationById = getPopulationById( + "displayedPopulation" ) export const selectTotalPopulation = createSelector( @@ -30,7 +22,21 @@ export const selectTotalPopulation = createSelector( realms.length ? getPopulationOfRealms(realms).reduce((a, b) => a + b) : 0 ) +export const selectTotalDisplayedPopulation = createSelector( + selectSortedDisplayedPopulation, + (realms) => + realms.length + ? getDisplayedPopulationOfRealms(realms).reduce((a, b) => a + b) + : 0 +) + export const selectMaxPopulation = createSelector( selectSortedPopulation, (realms) => (realms.length ? Math.max(...getPopulationOfRealms(realms)) : 0) ) + +export const selectMaxDisplayedPopulation = createSelector( + selectSortedDisplayedPopulation, + (realms) => + realms.length ? Math.max(...getDisplayedPopulationOfRealms(realms)) : 0 +) diff --git a/src/redux-state/utils/population.ts b/src/redux-state/utils/population.ts new file mode 100644 index 000000000..baf614a9a --- /dev/null +++ b/src/redux-state/utils/population.ts @@ -0,0 +1,24 @@ +import { createSelector } from "@reduxjs/toolkit" +import { selectRealmById, selectRealms } from "redux-state/selectors/realm" +import { RealmData } from "shared/types" + +type PopulationKey = "population" | "displayedPopulation" + +export const getPopulationOfRealms = (realms: RealmData[]) => + realms.map((realm) => realm.population) + +export const getDisplayedPopulationOfRealms = (realms: RealmData[]) => + realms.map((realm) => realm.displayedPopulation) + +export const getPopulationById = (key: PopulationKey) => + createSelector(selectRealmById, (realm) => realm?.[key] ?? 0) + +export const sortPopulation = (key: PopulationKey) => + createSelector(selectRealms, (realms) => { + const realmsData = Object.entries(realms).map(([id, data]) => ({ + id, + ...data, + })) + + return realmsData.sort((a, b) => a[key] - b[key]) + }) diff --git a/src/shared/hooks/index.ts b/src/shared/hooks/index.ts index cd4099aa1..342fd1b26 100644 --- a/src/shared/hooks/index.ts +++ b/src/shared/hooks/index.ts @@ -4,3 +4,4 @@ export * from "./island" export * from "./transactions" export * from "./assistant" export * from "./tenderly" +export * from "./population" diff --git a/src/shared/hooks/island.ts b/src/shared/hooks/island.ts index 344da966f..1d58e1662 100644 --- a/src/shared/hooks/island.ts +++ b/src/shared/hooks/island.ts @@ -12,9 +12,6 @@ import { useDappSelector, selectWalletAddress, selectRealms, - selectPopulationById, - selectDisplayedPopulationById, - setRealmDisplayedPopulation, } from "redux-state" import { SECOND } from "shared/constants" import { @@ -25,7 +22,6 @@ import { initRealmsDataFromContracts, initSeasonInfoData, } from "redux-state/thunks/island" -import { randomInteger } from "shared/utils" import { useArbitrumProvider } from "./wallets" import { useInterval } from "./helpers" @@ -141,42 +137,3 @@ export function useStakeCooldownPeriod() { return { hasCooldown: !!timeRemaining && timeRemaining > 0, timeRemaining } } - -export function usePopulationBubble(realmId: string): { - showBubble: boolean - setShowBubble: (newValue: boolean) => void -} { - const population = useDappSelector((state) => - selectPopulationById(state, realmId) - ) - const displayedPopulation = useDappSelector((state) => - selectDisplayedPopulationById(state, realmId) - ) - const dispatch = useDappDispatch() - - const [showBubble, setShowBubble] = useState(false) - // Generate random intervals for realms - const [delay] = useState(randomInteger(5, 15) * SECOND) - - const populationCallback = useCallback(async () => { - if (population < displayedPopulation) { - await dispatch( - setRealmDisplayedPopulation({ - id: realmId, - population, - }) - ) - } else if (population > displayedPopulation) { - setShowBubble(true) - await dispatch( - setRealmDisplayedPopulation({ - id: realmId, - population: displayedPopulation + 1, - }) - ) - } - }, [population, displayedPopulation, dispatch, realmId]) - - useInterval(populationCallback, population ? delay : null) - return { showBubble, setShowBubble } -} diff --git a/src/shared/hooks/population.ts b/src/shared/hooks/population.ts new file mode 100644 index 000000000..9ddc5d5f0 --- /dev/null +++ b/src/shared/hooks/population.ts @@ -0,0 +1,75 @@ +import { RefObject, useCallback, useEffect, useState } from "react" +import { + selectDisplayedPopulationById, + selectMaxDisplayedPopulation, + selectPopulationById, + selectSortedDisplayedPopulation, + setRealmDisplayedPopulation, + useDappDispatch, + useDappSelector, +} from "redux-state" +import { SECOND } from "shared/constants" +import { calculatePopulationIconsPositions, randomInteger } from "shared/utils" +import { useInterval } from "./helpers" + +export function usePopulationBubble(realmId: string): { + showBubble: boolean + setShowBubble: (newValue: boolean) => void +} { + const population = useDappSelector((state) => + selectPopulationById(state, realmId) + ) + const displayedPopulation = useDappSelector((state) => + selectDisplayedPopulationById(state, realmId) + ) + const dispatch = useDappDispatch() + + const [showBubble, setShowBubble] = useState(false) + // Generate random intervals for realms + const [delay] = useState(randomInteger(5, 15) * SECOND) + + const populationCallback = useCallback(async () => { + if (population < displayedPopulation) { + await dispatch( + setRealmDisplayedPopulation({ + id: realmId, + population, + }) + ) + } else if (population > displayedPopulation) { + setShowBubble(true) + await dispatch( + setRealmDisplayedPopulation({ + id: realmId, + population: displayedPopulation + 1, + }) + ) + } + }, [population, displayedPopulation, dispatch, realmId]) + + useInterval(populationCallback, population ? delay : null) + return { showBubble, setShowBubble } +} + +export function usePopulationIconPositions(ref: RefObject) { + const realmsData = useDappSelector(selectSortedDisplayedPopulation) + const maxPopulation = useDappSelector(selectMaxDisplayedPopulation) + + const [positions, setPositions] = useState([]) + + useEffect(() => { + if (!realmsData.length || !ref.current) return + + const { width } = ref.current.getBoundingClientRect() + + const pos = calculatePopulationIconsPositions( + width, + realmsData, + maxPopulation + ) + + setPositions(pos) + }, [realmsData, maxPopulation, ref]) + + return positions +} diff --git a/src/shared/utils/island.ts b/src/shared/utils/island.ts index fbd33f82e..8dcffdf67 100644 --- a/src/shared/utils/island.ts +++ b/src/shared/utils/island.ts @@ -173,7 +173,7 @@ export function calculatePopulationIconsPositions( const positions: number[] = [] realmsData.forEach((realm, index) => { - const populationShare = realm.population / maxValue + const populationShare = realm.displayedPopulation / maxValue let iconPosition = Math.max( populationShare * width + POPULATION_BAR_GAP, POPULATION_BAR_GAP + index * POPULATION_ICON_SIZE diff --git a/src/ui/Footer/RealmBar/index.tsx b/src/ui/Footer/RealmBar/index.tsx index 17671594b..39c85b742 100644 --- a/src/ui/Footer/RealmBar/index.tsx +++ b/src/ui/Footer/RealmBar/index.tsx @@ -1,44 +1,27 @@ -import React, { useEffect, useRef, useState } from "react" +import React, { useRef } from "react" import populationIcon from "shared/assets/icons/people.svg" import Icon from "shared/components/Icon" import Tooltip from "shared/components/Tooltip" +import { separateThousandsByComma } from "shared/utils" import { - calculatePopulationIconsPositions, - separateThousandsByComma, -} from "shared/utils" -import { - selectMaxPopulation, - selectSortedPopulation, - selectTotalPopulation, + selectSortedDisplayedPopulation, + selectTotalDisplayedPopulation, useDappSelector, } from "redux-state" -import { useVisibilityTransition } from "shared/hooks" +import { + useVisibilityTransition, + usePopulationIconPositions, +} from "shared/hooks" import { animated } from "@react-spring/web" import RealmBarIcon from "./RealmBarIcon" export default function RealmsBar() { - const realmsData = useDappSelector(selectSortedPopulation) - const totalPopulation = useDappSelector(selectTotalPopulation) - const maxPopulation = useDappSelector(selectMaxPopulation) - - const [positions, setPositions] = useState([]) + const realmsData = useDappSelector(selectSortedDisplayedPopulation) + const totalPopulation = useDappSelector(selectTotalDisplayedPopulation) const progressBarRef = useRef(null) const transition = useVisibilityTransition(totalPopulation > 0) - - useEffect(() => { - if (!realmsData.length || !progressBarRef.current) return - - const { width } = progressBarRef.current.getBoundingClientRect() - - const pos = calculatePopulationIconsPositions( - width, - realmsData, - maxPopulation - ) - - setPositions(pos) - }, [realmsData, maxPopulation, progressBarRef]) + const positions = usePopulationIconPositions(progressBarRef) return ( <> @@ -76,7 +59,7 @@ export default function RealmsBar() { key={realm.id} id={realm.id} position={positions[index]} - population={realm.population} + population={realm.displayedPopulation} name={realm.name} /> ))} diff --git a/src/ui/Island/Realm.tsx b/src/ui/Island/Realm.tsx index 177902059..be598ef75 100644 --- a/src/ui/Island/Realm.tsx +++ b/src/ui/Island/Realm.tsx @@ -13,10 +13,7 @@ import { } from "shared/constants" import { BUBBLE_CONFIG } from "shared/components/RealmCutout/Bubble" import { selectDisplayedRealmId, useDappSelector } from "redux-state" -import { - useIslandContext, - usePopulationBubble, -} from "../../shared/hooks/island" +import { useIslandContext, usePopulationBubble } from "../../shared/hooks" type RealmProps = { id: string From bd48a4ff91d853e2c9d8eb01d7f07f59575cd180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Wed, 8 Nov 2023 14:56:18 +0100 Subject: [PATCH 11/27] Refactor `population` selectors --- src/redux-state/selectors/population.ts | 25 +++++++++++++++++++++---- src/redux-state/utils/population.ts | 17 ----------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/redux-state/selectors/population.ts b/src/redux-state/selectors/population.ts index 73018c189..44ab70435 100644 --- a/src/redux-state/selectors/population.ts +++ b/src/redux-state/selectors/population.ts @@ -1,10 +1,24 @@ import { createSelector } from "@reduxjs/toolkit" import { getDisplayedPopulationOfRealms, - getPopulationById, getPopulationOfRealms, - sortPopulation, } from "redux-state/utils/population" +import { selectRealmById, selectRealms } from "./realm" + +type PopulationKey = "population" | "displayedPopulation" + +const getPopulationById = (key: PopulationKey) => + createSelector(selectRealmById, (realm) => realm?.[key] ?? 0) + +const sortPopulation = (key: PopulationKey) => + createSelector(selectRealms, (realms) => { + const realmsData = Object.entries(realms).map(([id, data]) => ({ + id, + ...data, + })) + + return realmsData.sort((a, b) => a[key] - b[key]) + }) export const selectSortedPopulation = sortPopulation("population") export const selectSortedDisplayedPopulation = sortPopulation( @@ -32,11 +46,14 @@ export const selectTotalDisplayedPopulation = createSelector( export const selectMaxPopulation = createSelector( selectSortedPopulation, - (realms) => (realms.length ? Math.max(...getPopulationOfRealms(realms)) : 0) + (realms) => + realms.length ? getPopulationOfRealms(realms)[realms.length - 1] : 0 ) export const selectMaxDisplayedPopulation = createSelector( selectSortedDisplayedPopulation, (realms) => - realms.length ? Math.max(...getDisplayedPopulationOfRealms(realms)) : 0 + realms.length + ? getDisplayedPopulationOfRealms(realms)[realms.length - 1] + : 0 ) diff --git a/src/redux-state/utils/population.ts b/src/redux-state/utils/population.ts index baf614a9a..ece3e6637 100644 --- a/src/redux-state/utils/population.ts +++ b/src/redux-state/utils/population.ts @@ -1,24 +1,7 @@ -import { createSelector } from "@reduxjs/toolkit" -import { selectRealmById, selectRealms } from "redux-state/selectors/realm" import { RealmData } from "shared/types" -type PopulationKey = "population" | "displayedPopulation" - export const getPopulationOfRealms = (realms: RealmData[]) => realms.map((realm) => realm.population) export const getDisplayedPopulationOfRealms = (realms: RealmData[]) => realms.map((realm) => realm.displayedPopulation) - -export const getPopulationById = (key: PopulationKey) => - createSelector(selectRealmById, (realm) => realm?.[key] ?? 0) - -export const sortPopulation = (key: PopulationKey) => - createSelector(selectRealms, (realms) => { - const realmsData = Object.entries(realms).map(([id, data]) => ({ - id, - ...data, - })) - - return realmsData.sort((a, b) => a[key] - b[key]) - }) From 1980c345dbd0e7b844c243f8a3fd0f6d7c0bb164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Wed, 8 Nov 2023 17:09:28 +0100 Subject: [PATCH 12/27] Type-o fixes and commented code removals --- .../{partners.ts => partners-icons.ts} | 0 src/shared/constants/realms-data.ts | 2 +- src/shared/utils/misc.ts | 4 --- src/shared/utils/numbers.ts | 6 +++- src/ui/Footer/RealmBar/RealmBarIcon.tsx | 36 ------------------- src/ui/Island/Realm.tsx | 2 +- 6 files changed, 7 insertions(+), 43 deletions(-) rename src/shared/constants/{partners.ts => partners-icons.ts} (100%) diff --git a/src/shared/constants/partners.ts b/src/shared/constants/partners-icons.ts similarity index 100% rename from src/shared/constants/partners.ts rename to src/shared/constants/partners-icons.ts diff --git a/src/shared/constants/realms-data.ts b/src/shared/constants/realms-data.ts index 601d7d1fc..d218f6d5c 100644 --- a/src/shared/constants/realms-data.ts +++ b/src/shared/constants/realms-data.ts @@ -1,4 +1,4 @@ -import partners from "./partners" +import partners from "./partners-icons" /* eslint-disable @typescript-eslint/no-loss-of-precision */ export const realm1 = { diff --git a/src/shared/utils/misc.ts b/src/shared/utils/misc.ts index ded12cfca..df70a7788 100644 --- a/src/shared/utils/misc.ts +++ b/src/shared/utils/misc.ts @@ -126,7 +126,3 @@ export function createImageElement(source: string) { return image } - -export function randomInteger(min: number, max: number) { - return Math.floor(Math.random() * (max - min + 1)) + min -} diff --git a/src/shared/utils/numbers.ts b/src/shared/utils/numbers.ts index 48af96e2e..3b5c9bae5 100644 --- a/src/shared/utils/numbers.ts +++ b/src/shared/utils/numbers.ts @@ -1,7 +1,11 @@ -// eslint-disable-next-line import/prefer-default-export export const separateThousandsByComma = ( value: number | bigint | string ): string => { const adjustedValue = typeof value === "string" ? +value : value return adjustedValue.toLocaleString("en-US", { maximumFractionDigits: 2 }) } + +// Generates a random integer in min-max range (inclusively) +export function randomInteger(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1)) + min +} diff --git a/src/ui/Footer/RealmBar/RealmBarIcon.tsx b/src/ui/Footer/RealmBar/RealmBarIcon.tsx index 119374f98..3f75a8f93 100644 --- a/src/ui/Footer/RealmBar/RealmBarIcon.tsx +++ b/src/ui/Footer/RealmBar/RealmBarIcon.tsx @@ -71,40 +71,4 @@ export default function RealmBarIcon({ /> ) - - // return ( - // <> - //
setIsTooltipVisible(true)} - // onMouseLeave={() => setIsTooltipVisible(false)} - // > - // - // - //
- // - // - // ) } diff --git a/src/ui/Island/Realm.tsx b/src/ui/Island/Realm.tsx index be598ef75..fb3cb70d8 100644 --- a/src/ui/Island/Realm.tsx +++ b/src/ui/Island/Realm.tsx @@ -220,7 +220,7 @@ export default function Realm({ const { showBubble, setShowBubble } = usePopulationBubble(id) const [bubbleProps, set] = useSpring(() => { - // To prevent lag in animation, let's show only one babel for the realm. + // To prevent lag in animation, let's show only one bubble for the realm. // When a modal for the realm is open, do not show a bubble on the map. const destinationStyle = showBubble && !(realmId === id) From b5fbf34c5da2bdf7d0506604d8d1078348188535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Wed, 8 Nov 2023 17:22:35 +0100 Subject: [PATCH 13/27] Remove dispatch awaiting --- src/shared/hooks/population.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/shared/hooks/population.ts b/src/shared/hooks/population.ts index 9ddc5d5f0..a4b1acebf 100644 --- a/src/shared/hooks/population.ts +++ b/src/shared/hooks/population.ts @@ -25,12 +25,11 @@ export function usePopulationBubble(realmId: string): { const dispatch = useDappDispatch() const [showBubble, setShowBubble] = useState(false) - // Generate random intervals for realms - const [delay] = useState(randomInteger(5, 15) * SECOND) + const [delay] = useState(randomInteger(5, 15) * SECOND) // Generate random intervals for realms - const populationCallback = useCallback(async () => { + const populationCallback = useCallback(() => { if (population < displayedPopulation) { - await dispatch( + dispatch( setRealmDisplayedPopulation({ id: realmId, population, @@ -38,7 +37,7 @@ export function usePopulationBubble(realmId: string): { ) } else if (population > displayedPopulation) { setShowBubble(true) - await dispatch( + dispatch( setRealmDisplayedPopulation({ id: realmId, population: displayedPopulation + 1, From 60862e29d12e2b94b01daf6f9d2afd7db77af884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Thu, 9 Nov 2023 09:29:11 +0100 Subject: [PATCH 14/27] `setShowBubble` after dispatching event --- src/shared/hooks/population.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shared/hooks/population.ts b/src/shared/hooks/population.ts index a4b1acebf..b87987403 100644 --- a/src/shared/hooks/population.ts +++ b/src/shared/hooks/population.ts @@ -36,13 +36,14 @@ export function usePopulationBubble(realmId: string): { }) ) } else if (population > displayedPopulation) { - setShowBubble(true) dispatch( setRealmDisplayedPopulation({ id: realmId, population: displayedPopulation + 1, }) ) + + setShowBubble(true) } }, [population, displayedPopulation, dispatch, realmId]) From 5bc56998ee8486fb3048e5ccc5e09d814e3fc922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Thu, 9 Nov 2023 10:44:48 +0100 Subject: [PATCH 15/27] Change condition for `setRealmDisplayedPopulation` --- src/redux-state/slices/island.ts | 4 ++-- src/redux-state/thunks/island.ts | 20 ++++++++++++-------- src/shared/hooks/island.ts | 2 +- src/shared/hooks/wallets.ts | 2 +- src/shared/types/island.ts | 2 ++ src/shared/utils/index.ts | 1 + src/shared/utils/population.ts | 8 ++++++++ 7 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 src/shared/utils/population.ts diff --git a/src/redux-state/slices/island.ts b/src/redux-state/slices/island.ts index ae250080a..a2c980e48 100644 --- a/src/redux-state/slices/island.ts +++ b/src/redux-state/slices/island.ts @@ -3,9 +3,9 @@ import { LeaderboardData, UnclaimedXpData, OverlayType, - RealmData, RealmDataWithId, SeasonInfo, + RealmDataById, } from "shared/types" export type IslandModeType = "default" | "join-realm" @@ -13,7 +13,7 @@ export type IslandModeType = "default" | "join-realm" export type IslandState = { mode: IslandModeType overlay: OverlayType - realms: { [id: string]: RealmData } + realms: RealmDataById leaderboards: { [id: string]: LeaderboardData } unclaimedXp: { [id: string]: UnclaimedXpData[] } stakingRealmId: string | null diff --git a/src/redux-state/thunks/island.ts b/src/redux-state/thunks/island.ts index d4346c064..88b34371b 100644 --- a/src/redux-state/thunks/island.ts +++ b/src/redux-state/thunks/island.ts @@ -29,7 +29,11 @@ import { TransactionProgressStatus, } from "shared/types" import { updateTransactionStatus } from "redux-state/slices/wallet" -import { bigIntToUserAmount, getAllowanceTransactionID } from "shared/utils" +import { + bigIntToUserAmount, + getAllowanceTransactionID, + isDisplayedPopulationAvailable, +} from "shared/utils" import { getRealmLeaderboardData, getUserLeaderboardRank, @@ -96,10 +100,7 @@ export const initSeasonInfoData = createDappAsyncThunk( export const fetchPopulation = createDappAsyncThunk( "island/fetchPopulation", - async ( - isInitFetch: boolean, - { getState, dispatch, extra: { transactionService } } - ) => { + async (_, { getState, dispatch, extra: { transactionService } }) => { const { island: { realms }, } = getState() @@ -110,10 +111,13 @@ export const fetchPopulation = createDappAsyncThunk( realmsWithAddress, }) + const displayedPopulationAvailable = isDisplayedPopulationAvailable(realms) + if (result) { result.forEach((data) => { dispatch(setRealmPopulation(data)) - if (isInitFetch) dispatch(setRealmDisplayedPopulation(data)) + if (!displayedPopulationAvailable) + dispatch(setRealmDisplayedPopulation(data)) }) } @@ -245,7 +249,7 @@ export const stakeTaho = createDappAsyncThunk( if (receipt) { dispatch(fetchWalletBalances()) - dispatch(fetchPopulation(false)) + dispatch(fetchPopulation()) } return !!receipt @@ -288,7 +292,7 @@ export const unstakeTaho = createDappAsyncThunk( if (receipt) { dispatch(fetchWalletBalances()) - dispatch(fetchPopulation(false)) + dispatch(fetchPopulation()) } return !!receipt diff --git a/src/shared/hooks/island.ts b/src/shared/hooks/island.ts index 1d58e1662..451fe2893 100644 --- a/src/shared/hooks/island.ts +++ b/src/shared/hooks/island.ts @@ -76,7 +76,7 @@ export function useGameDataFetch() { return const fetchData = async () => { - await dispatch(fetchPopulation(true)) + await dispatch(fetchPopulation()) await dispatch(fetchXpAllocatable()) } diff --git a/src/shared/hooks/wallets.ts b/src/shared/hooks/wallets.ts index d7756dd3b..3d5c7fa63 100644 --- a/src/shared/hooks/wallets.ts +++ b/src/shared/hooks/wallets.ts @@ -113,7 +113,7 @@ export function usePopulationFetch() { const populationFetchCallback = useCallback(async () => { if (account && dispatch) { - await dispatch(fetchPopulation(false)) + await dispatch(fetchPopulation()) } }, [account, dispatch]) diff --git a/src/shared/types/island.ts b/src/shared/types/island.ts index 95a2b1749..14a5deabd 100644 --- a/src/shared/types/island.ts +++ b/src/shared/types/island.ts @@ -28,6 +28,8 @@ export type RealmData = RealmAddressesData & export type RealmDataWithId = { id: string; data: RealmData } +export type RealmDataById = { [id: string]: RealmData } + export type RealmContractDataWithId = { id: string data: RealmAddressesData & RealmContractData diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts index d7ecf672a..2149d70c9 100644 --- a/src/shared/utils/index.ts +++ b/src/shared/utils/index.ts @@ -3,6 +3,7 @@ export * from "./address" export * from "./names" export * from "./claim" export * from "./pool" +export * from "./population" export * from "./providers" export * from "./island" export * from "./timers" diff --git a/src/shared/utils/population.ts b/src/shared/utils/population.ts new file mode 100644 index 000000000..43695a267 --- /dev/null +++ b/src/shared/utils/population.ts @@ -0,0 +1,8 @@ +/* eslint-disable import/prefer-default-export */ +import { RealmDataById } from "shared/types" + +export function isDisplayedPopulationAvailable(realms: RealmDataById) { + return Object.values(realms) + .map((realm) => realm.displayedPopulation) + .some((population) => population !== undefined) +} From 84219082b83a291fa0dcf7e584cd74f6e663ec00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Fri, 10 Nov 2023 09:30:19 +0100 Subject: [PATCH 16/27] Update displayed population if it is `null` in `redux` --- src/shared/hooks/population.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/hooks/population.ts b/src/shared/hooks/population.ts index b87987403..6849cd964 100644 --- a/src/shared/hooks/population.ts +++ b/src/shared/hooks/population.ts @@ -28,7 +28,7 @@ export function usePopulationBubble(realmId: string): { const [delay] = useState(randomInteger(5, 15) * SECOND) // Generate random intervals for realms const populationCallback = useCallback(() => { - if (population < displayedPopulation) { + if (population < displayedPopulation || !displayedPopulation) { dispatch( setRealmDisplayedPopulation({ id: realmId, From c2b2b8136efa44a24ac4479bdb68b536ff666c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Fri, 10 Nov 2023 09:42:07 +0100 Subject: [PATCH 17/27] Improve transition on onboarding `PopulationCount` --- src/ui/Footer/PopulationCount.tsx | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/ui/Footer/PopulationCount.tsx b/src/ui/Footer/PopulationCount.tsx index 26728a2ce..d29197706 100644 --- a/src/ui/Footer/PopulationCount.tsx +++ b/src/ui/Footer/PopulationCount.tsx @@ -3,15 +3,28 @@ import Icon from "shared/components/Icon" import { separateThousandsByComma } from "shared/utils" import populationIcon from "shared/assets/icons/people.svg" import { selectTotalPopulation, useDappSelector } from "redux-state" +import { useVisibilityTransition } from "shared/hooks" +import { animated } from "@react-spring/web" export default function PopulationCount() { const population = useDappSelector(selectTotalPopulation) + const transition = useVisibilityTransition(population > 0) if (!population) return null return ( <> -
+
{separateThousandsByComma(population)}

-
+