From 6f2e1cce55fe59188b5621b18408c0545849b1ed Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Sun, 15 Dec 2024 19:59:29 +1100 Subject: [PATCH 1/5] memo the market --- client/src/ui/components/bank/ResourceBar.tsx | 264 ++-- .../components/trading/MarketOrderPanel.tsx | 1157 +++++++++-------- .../trading/MarketResourceSideBar.tsx | 61 +- .../trading/MarketTradingHistory.tsx | 11 +- .../ui/components/trading/RealmProduction.tsx | 96 +- .../components/trading/ResourceArrivals.tsx | 17 +- .../trading/SelectEntityFromList.tsx | 78 +- .../trading/TransferBetweenEntities.tsx | 254 ++-- .../src/ui/modules/navigation/QuestMenu.tsx | 6 +- 9 files changed, 1003 insertions(+), 941 deletions(-) diff --git a/client/src/ui/components/bank/ResourceBar.tsx b/client/src/ui/components/bank/ResourceBar.tsx index 769380e2e..b07a1402b 100644 --- a/client/src/ui/components/bank/ResourceBar.tsx +++ b/client/src/ui/components/bank/ResourceBar.tsx @@ -5,152 +5,154 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import TextInput from "@/ui/elements/TextInput"; import { divideByPrecision, formatNumber } from "@/ui/utils/utils"; import { ID, Resources, ResourcesIds, findResourceById, findResourceIdByTrait } from "@bibliothecadao/eternum"; -import { useEffect, useRef, useState } from "react"; +import { memo, useEffect, useRef, useState } from "react"; import { HintSection } from "../hints/HintModal"; -export const ResourceBar = ({ - entityId, - lordsFee, - resources, - resourceId, - setResourceId, - amount, - setAmount, - disableInput = false, - onFocus, - onBlur, - max = Infinity, -}: { - entityId: ID; - lordsFee: number; - resources: Resources[]; - resourceId: ResourcesIds; - setResourceId: (resourceId: ResourcesIds) => void; - amount: number; - setAmount: (amount: number) => void; - disableInput?: boolean; - onFocus?: () => void; // New prop - onBlur?: () => void; // New prop - max?: number; -}) => { - const { getBalance } = useResourceBalance(); +export const ResourceBar = memo( + ({ + entityId, + lordsFee, + resources, + resourceId, + setResourceId, + amount, + setAmount, + disableInput = false, + onFocus, + onBlur, + max = Infinity, + }: { + entityId: ID; + lordsFee: number; + resources: Resources[]; + resourceId: ResourcesIds; + setResourceId: (resourceId: ResourcesIds) => void; + amount: number; + setAmount: (amount: number) => void; + disableInput?: boolean; + onFocus?: () => void; // New prop + onBlur?: () => void; // New prop + max?: number; + }) => { + const { getBalance } = useResourceBalance(); - const [selectedResourceBalance, setSelectedResourceBalance] = useState(0); - const [searchInput, setSearchInput] = useState(""); - const [open, setOpen] = useState(false); + const [selectedResourceBalance, setSelectedResourceBalance] = useState(0); + const [searchInput, setSearchInput] = useState(""); + const [open, setOpen] = useState(false); - const inputRef = useRef(null); + const inputRef = useRef(null); - useEffect(() => { - setSelectedResourceBalance(divideByPrecision(getBalance(entityId, Number(resourceId)).balance)); - }, [resourceId, getBalance, entityId]); + useEffect(() => { + setSelectedResourceBalance(divideByPrecision(getBalance(entityId, Number(resourceId)).balance)); + }, [resourceId, getBalance, entityId]); - const handleResourceChange = (trait: string) => { - const newResourceId = findResourceIdByTrait(trait); - setResourceId(newResourceId); - }; + const handleResourceChange = (trait: string) => { + const newResourceId = findResourceIdByTrait(trait); + setResourceId(newResourceId); + }; - const handleAmountChange = (amount: number) => { - setAmount(amount); - }; + const handleAmountChange = (amount: number) => { + setAmount(amount); + }; - const hasLordsFees = lordsFee > 0 && resourceId === ResourcesIds.Lords; - const finalResourceBalance = hasLordsFees ? selectedResourceBalance - lordsFee : selectedResourceBalance; + const hasLordsFees = lordsFee > 0 && resourceId === ResourcesIds.Lords; + const finalResourceBalance = hasLordsFees ? selectedResourceBalance - lordsFee : selectedResourceBalance; - const filteredResources = resources.filter( - (resource) => resource.trait.toLowerCase().startsWith(searchInput.toLowerCase()) || resource.id === resourceId, - ); + const filteredResources = resources.filter( + (resource) => resource.trait.toLowerCase().startsWith(searchInput.toLowerCase()) || resource.id === resourceId, + ); - const handleOpenChange = (newOpen: boolean) => { - setOpen(newOpen); - if (newOpen && inputRef.current) { - setResourceId(ResourcesIds.Wood); - setSearchInput(""); - setTimeout(() => { - inputRef.current?.focus(); - }, 0); - } - }; + const handleOpenChange = (newOpen: boolean) => { + setOpen(newOpen); + if (newOpen && inputRef.current) { + setResourceId(ResourcesIds.Wood); + setSearchInput(""); + setTimeout(() => { + inputRef.current?.focus(); + }, 0); + } + }; - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - if (filteredResources.length > 0) { - const selectedResource = filteredResources.find((resource) => resource.id !== resourceId); - if (selectedResource) { - setResourceId(selectedResource.id); - setOpen(false); + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + if (filteredResources.length > 0) { + const selectedResource = filteredResources.find((resource) => resource.id !== resourceId); + if (selectedResource) { + setResourceId(selectedResource.id); + setOpen(false); + } } + setSearchInput(""); + } else { + e.stopPropagation(); } - setSearchInput(""); - } else { - e.stopPropagation(); - } - }; + }; + + return ( +
+
+ - return ( -
-
- + {!disableInput && ( +
handleAmountChange(finalResourceBalance)} + > + Max: {isNaN(selectedResourceBalance) ? "0" : selectedResourceBalance.toLocaleString()} + {hasLordsFees && ( +
+
{`[+${isNaN(lordsFee) ? "0" : formatNumber(lordsFee, 2)}]`}
+
+ )} +
+ )} +
- {!disableInput && ( -
handleAmountChange(finalResourceBalance)} - > - Max: {isNaN(selectedResourceBalance) ? "0" : selectedResourceBalance.toLocaleString()} - {hasLordsFees && ( -
-
{`[+${isNaN(lordsFee) ? "0" : formatNumber(lordsFee, 2)}]`}
+
- - -
- ); -}; + ); + }, +); diff --git a/client/src/ui/components/trading/MarketOrderPanel.tsx b/client/src/ui/components/trading/MarketOrderPanel.tsx index 9539027ba..031e46a29 100644 --- a/client/src/ui/components/trading/MarketOrderPanel.tsx +++ b/client/src/ui/components/trading/MarketOrderPanel.tsx @@ -27,187 +27,195 @@ import { type MarketInterface, } from "@bibliothecadao/eternum"; import clsx from "clsx"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { memo, useCallback, useEffect, useMemo, useState } from "react"; import { ConfirmationPopup } from "../bank/ConfirmationPopup"; -export const MarketResource = ({ - entityId, - resourceId, - active, - onClick, - askPrice, - bidPrice, - ammPrice, -}: { - entityId: ID; - resourceId: ResourcesIds; - active: boolean; - onClick: (value: number) => void; - askPrice: number; - bidPrice: number; - ammPrice: number; -}) => { - const { currentDefaultTick } = useNextBlockTimestamp(); - const resourceManager = useResourceManager(entityId, resourceId); - - const production = useMemo(() => { - return resourceManager.getProduction(); - }, []); - - const balance = useMemo(() => { - return resourceManager.balance(currentDefaultTick); - }, [resourceManager, production, currentDefaultTick]); - - const resource = useMemo(() => { - return findResourceById(resourceId); - }, [resourceId]); - - return ( -
{ - onClick(resourceId); - }} - className={`w-full border-gold/5 rounded-xl h-8 p-1 cursor-pointer grid grid-cols-5 gap-1 hover:bg-gold/10 hover: group ${ - active ? "bg-gold/10" : "" - }`} - > -
- -
{resource?.trait || ""}
-
- [{currencyFormat(balance ? Number(balance) : 0, 0)}] -
-
- -
{formatNumber(bidPrice, 4)}
-
{formatNumber(askPrice, 4)}
-
{formatNumber(ammPrice, 4)}
-
- ); -}; - -export const MarketOrderPanel = ({ - resourceId, - entityId, - resourceAskOffers, - resourceBidOffers, -}: { - resourceId: ResourcesIds; - entityId: ID; - resourceAskOffers: MarketInterface[]; - resourceBidOffers: MarketInterface[]; -}) => { - const selectedResourceBidOffers = useMemo(() => { - return resourceBidOffers - .filter((offer) => (resourceId ? offer.makerGets[0]?.resourceId === resourceId : true)) - .sort((a, b) => b.ratio - a.ratio); - }, [resourceBidOffers, resourceId]); - - const selectedResourceAskOffers = useMemo(() => { - return resourceAskOffers - .filter((offer) => offer.takerGets[0].resourceId === resourceId) - .sort((a, b) => b.ratio - a.ratio); - }, [resourceAskOffers, resourceId]); - - const isResourcesLocked = useIsResourcesLocked(entityId); - - return ( -
- - -
- ); -}; - -const MarketOrders = ({ - resourceId, - entityId, - isBuy = false, - offers, - isResourcesLocked, -}: { - resourceId: ResourcesIds; - entityId: ID; - isBuy?: boolean; - offers: MarketInterface[]; - isResourcesLocked: boolean; -}) => { - const [updateBalance, setUpdateBalance] = useState(false); - - const lowestPrice = useMemo(() => { - const price = offers.reduce((acc, offer) => (offer.perLords < acc ? offer.perLords : acc), Infinity); - return price === Infinity ? 0 : price; - }, [offers]); - - return ( -
- {/* Market Price */} +export const MarketResource = memo( + ({ + entityId, + resourceId, + active, + onClick, + askPrice, + bidPrice, + ammPrice, + }: { + entityId: ID; + resourceId: ResourcesIds; + active: boolean; + onClick: (value: number) => void; + askPrice: number; + bidPrice: number; + ammPrice: number; + }) => { + const { currentDefaultTick } = useNextBlockTimestamp(); + const resourceManager = useResourceManager(entityId, resourceId); + + const production = useMemo(() => { + return resourceManager.getProduction(); + }, []); + + const balance = useMemo(() => { + return resourceManager.balance(currentDefaultTick); + }, [resourceManager, production, currentDefaultTick]); + + const resource = useMemo(() => { + return findResourceById(resourceId); + }, [resourceId]); + + return (
{ + onClick(resourceId); + }} + className={`w-full border-gold/5 rounded-xl h-8 p-1 cursor-pointer grid grid-cols-5 gap-1 hover:bg-gold/10 hover: group ${ + active ? "bg-gold/10" : "" }`} > -
-
-
{findResourceById(resourceId)?.trait || ""}
-
- -
{formatNumber(lowestPrice, 4)}
-
+
+ +
{resource?.trait || ""}
+
+ [{currencyFormat(balance ? Number(balance) : 0, 0)}]
-
- {offers.length} {isBuy ? "bid" : "ask"} + +
{formatNumber(bidPrice, 4)}
+
{formatNumber(askPrice, 4)}
+
+ {formatNumber(ammPrice, 4)}
- -
- - + ); + }, +); + +export const MarketOrderPanel = memo( + ({ + resourceId, + entityId, + resourceAskOffers, + resourceBidOffers, + }: { + resourceId: ResourcesIds; + entityId: ID; + resourceAskOffers: MarketInterface[]; + resourceBidOffers: MarketInterface[]; + }) => { + const selectedResourceBidOffers = useMemo(() => { + return resourceBidOffers + .filter((offer) => (resourceId ? offer.makerGets[0]?.resourceId === resourceId : true)) + .sort((a, b) => b.ratio - a.ratio); + }, [resourceBidOffers, resourceId]); + + const selectedResourceAskOffers = useMemo(() => { + return resourceAskOffers + .filter((offer) => offer.takerGets[0].resourceId === resourceId) + .sort((a, b) => b.ratio - a.ratio); + }, [resourceAskOffers, resourceId]); + + const isResourcesLocked = useIsResourcesLocked(entityId); + + return ( +
+ + +
+ ); + }, +); + +const MarketOrders = memo( + ({ + resourceId, + entityId, + isBuy = false, + offers, + isResourcesLocked, + }: { + resourceId: ResourcesIds; + entityId: ID; + isBuy?: boolean; + offers: MarketInterface[]; + isResourcesLocked: boolean; + }) => { + const [updateBalance, setUpdateBalance] = useState(false); + + const lowestPrice = useMemo(() => { + const price = offers.reduce((acc, offer) => (offer.perLords < acc ? offer.perLords : acc), Infinity); + return price === Infinity ? 0 : price; + }, [offers]); + + return ( +
+ {/* Market Price */}
- {offers.map((offer, index) => ( - - ))} - {isResourcesLocked && ( -
- Resources locked in battle +
+
+
{findResourceById(resourceId)?.trait || ""}
+
+ +
{formatNumber(lowestPrice, 4)}
+
- )} +
+
+ {offers.length} {isBuy ? "bid" : "ask"} +
-
- -
- ); -}; +
+ -const OrderRowHeader = ({ resourceId, isBuy }: { resourceId?: number; isBuy: boolean }) => { +
+ {offers.map((offer, index) => ( + + ))} + {isResourcesLocked && ( +
+ Resources locked in battle +
+ )} +
+
+ + +
+ ); + }, +); + +const OrderRowHeader = memo(({ resourceId, isBuy }: { resourceId?: number; isBuy: boolean }) => { return (
qty.
@@ -226,469 +234,466 @@ const OrderRowHeader = ({ resourceId, isBuy }: { resourceId?: number; isBuy: boo
Action
); -}; - -const OrderRow = ({ - offer, - entityId, - isBuy, - updateBalance, - setUpdateBalance, -}: { - offer: MarketInterface; - entityId: ID; - isBuy: boolean; - updateBalance: boolean; - setUpdateBalance: (value: boolean) => void; -}) => { - const { computeTravelTime } = useTravel(); - const dojo = useDojo(); - - const { play: playLordsSound } = useUiSounds(soundSelector.addLords); - - const lordsManager = new ResourceManager(dojo.setup, entityId, ResourcesIds.Lords); - const lordsBalance = useMemo(() => Number(lordsManager.getResource()?.balance || 0n), [entityId, updateBalance]); - - const resourceManager = useResourceManager(entityId, offer.makerGets[0].resourceId); - - const resourceBalance = useMemo( - () => Number(resourceManager.getResource()?.balance || 0n), - [entityId, updateBalance], - ); - - const { getRealmAddressName } = useRealm(); - - const isMakerResourcesLocked = useIsResourcesLocked(offer.makerId); - - const [confirmOrderModal, setConfirmOrderModal] = useState(false); - - const travelTime = useMemo( - () => computeTravelTime(entityId, offer.makerId, configManager.getSpeedConfig(DONKEY_ENTITY_TYPE), true), - [entityId, offer], - ); - - const returnResources = useMemo(() => { - return [offer.takerGets[0].resourceId, offer.takerGets[0].amount]; - }, [offer]); - - const [loading, setLoading] = useState(false); - - const isSelf = useMemo(() => { - return entityId === offer.makerId; - }, [entityId, offer.makerId, offer.tradeId]); - - const getsDisplay = useMemo(() => { - return isBuy ? currencyFormat(offer.makerGets[0].amount, 3) : currencyFormat(offer.takerGets[0].amount, 3); - }, [entityId, offer.makerId, offer.tradeId, offer]); - - const getsDisplayNumber = useMemo(() => { - return isBuy ? offer.makerGets[0].amount : offer.takerGets[0].amount; - }, [entityId, offer.makerId, offer.tradeId]); +}); + +const OrderRow = memo( + ({ + offer, + entityId, + isBuy, + updateBalance, + setUpdateBalance, + }: { + offer: MarketInterface; + entityId: ID; + isBuy: boolean; + updateBalance: boolean; + setUpdateBalance: (value: boolean) => void; + }) => { + const { computeTravelTime } = useTravel(); + const dojo = useDojo(); + + const { play: playLordsSound } = useUiSounds(soundSelector.addLords); + + const lordsManager = new ResourceManager(dojo.setup, entityId, ResourcesIds.Lords); + const lordsBalance = useMemo(() => Number(lordsManager.getResource()?.balance || 0n), [entityId, updateBalance]); + + const resourceManager = useResourceManager(entityId, offer.makerGets[0].resourceId); + + const resourceBalance = useMemo( + () => Number(resourceManager.getResource()?.balance || 0n), + [entityId, updateBalance], + ); - const getDisplayResource = useMemo(() => { - return isBuy ? offer.makerGets[0].resourceId : offer.takerGets[0].resourceId; - }, [entityId, offer.makerId, offer.tradeId]); + const { getRealmAddressName } = useRealm(); - const getTotalLords = useMemo(() => { - return isBuy ? offer.takerGets[0].amount : offer.makerGets[0].amount; - }, [entityId, offer.makerId, offer.tradeId, offer]); + const isMakerResourcesLocked = useIsResourcesLocked(offer.makerId); - const { currentDefaultTick } = useNextBlockTimestamp(); + const [confirmOrderModal, setConfirmOrderModal] = useState(false); - const resourceBalanceRatio = useMemo( - () => (resourceBalance < getsDisplayNumber ? resourceBalance / getsDisplayNumber : 1), - [resourceBalance, getsDisplayNumber], - ); - const lordsBalanceRatio = useMemo( - () => (lordsBalance < getTotalLords ? lordsBalance / getTotalLords : 1), - [lordsBalance, getTotalLords], - ); - const [inputValue, setInputValue] = useState(() => { - return isBuy - ? divideByPrecision(offer.makerGets[0].amount) * resourceBalanceRatio - : divideByPrecision(offer.takerGets[0].amount) * lordsBalanceRatio; - }); - - useEffect(() => { - setInputValue( - isBuy - ? divideByPrecision(offer.makerGets[0].amount) * resourceBalanceRatio - : divideByPrecision(offer.takerGets[0].amount) * lordsBalanceRatio, + const travelTime = useMemo( + () => computeTravelTime(entityId, offer.makerId, configManager.getSpeedConfig(DONKEY_ENTITY_TYPE), true), + [entityId, offer], ); - }, [resourceBalanceRatio, lordsBalanceRatio]); - const calculatedResourceAmount = useMemo(() => { - return multiplyByPrecision(inputValue); - }, [inputValue, getsDisplay, getTotalLords]); + const returnResources = useMemo(() => { + return [offer.takerGets[0].resourceId, offer.takerGets[0].amount]; + }, [offer]); - const calculatedLords = useMemo(() => { - return Math.ceil((inputValue / parseFloat(getsDisplay.replace(/,/g, ""))) * getTotalLords); - }, [inputValue, getsDisplay, getTotalLords]); + const [loading, setLoading] = useState(false); - const orderWeight = useMemo(() => { - const totalWeight = getTotalResourceWeight([ - { - resourceId: offer.takerGets[0].resourceId, - amount: isBuy ? calculatedLords : calculatedResourceAmount, - }, - ]); - return totalWeight; - }, [entityId, calculatedResourceAmount, calculatedLords]); + const isSelf = useMemo(() => { + return entityId === offer.makerId; + }, [entityId, offer.makerId, offer.tradeId]); - const donkeysNeeded = useMemo(() => { - return calculateDonkeysNeeded(orderWeight); - }, [orderWeight]); + const getsDisplay = useMemo(() => { + return isBuy ? currencyFormat(offer.makerGets[0].amount, 3) : currencyFormat(offer.takerGets[0].amount, 3); + }, [entityId, offer.makerId, offer.tradeId, offer]); - const donkeyProductionManager = useResourceManager(entityId, ResourcesIds.Donkey); + const getsDisplayNumber = useMemo(() => { + return isBuy ? offer.makerGets[0].amount : offer.takerGets[0].amount; + }, [entityId, offer.makerId, offer.tradeId]); - const donkeyProduction = useMemo(() => { - return donkeyProductionManager.getProduction(); - }, []); + const getDisplayResource = useMemo(() => { + return isBuy ? offer.makerGets[0].resourceId : offer.takerGets[0].resourceId; + }, [entityId, offer.makerId, offer.tradeId]); - const donkeyBalance = useMemo(() => { - return divideByPrecision(donkeyProductionManager.balance(currentDefaultTick)); - }, [donkeyProductionManager, donkeyProduction, currentDefaultTick]); + const getTotalLords = useMemo(() => { + return isBuy ? offer.takerGets[0].amount : offer.makerGets[0].amount; + }, [entityId, offer.makerId, offer.tradeId, offer]); - const accountName = useMemo(() => { - return getRealmAddressName(offer.makerId); - }, [offer.originName]); + const { currentDefaultTick } = useNextBlockTimestamp(); - const onAccept = async () => { - try { - setLoading(true); - setConfirmOrderModal(false); - - await dojo.setup.systemCalls.accept_partial_order({ - signer: dojo.account.account, - taker_id: entityId, - trade_id: offer.tradeId, - maker_gives_resources: [offer.takerGets[0].resourceId, offer.takerGets[0].amount], - taker_gives_resources: [offer.makerGets[0].resourceId, offer.makerGets[0].amount], - taker_gives_actual_amount: isBuy ? calculatedResourceAmount : calculatedLords, - }); - } catch (error) { - console.error("Failed to accept order", error); - } finally { - playLordsSound(); - setUpdateBalance(!updateBalance); - setLoading(false); - } - }; + const resourceBalanceRatio = useMemo( + () => (resourceBalance < getsDisplayNumber ? resourceBalance / getsDisplayNumber : 1), + [resourceBalance, getsDisplayNumber], + ); + const lordsBalanceRatio = useMemo( + () => (lordsBalance < getTotalLords ? lordsBalance / getTotalLords : 1), + [lordsBalance, getTotalLords], + ); + const [inputValue, setInputValue] = useState(() => { + return isBuy + ? divideByPrecision(offer.makerGets[0].amount) * resourceBalanceRatio + : divideByPrecision(offer.takerGets[0].amount) * lordsBalanceRatio; + }); - return ( -
- {isMakerResourcesLocked && ( -
- Resources locked in battle -
- )} -
-
- {" "} - {getsDisplay} -
- {travelTime && ( -
- {Math.floor(travelTime / 60)} hrs {travelTime % 60} mins + useEffect(() => { + setInputValue( + isBuy + ? divideByPrecision(offer.makerGets[0].amount) * resourceBalanceRatio + : divideByPrecision(offer.takerGets[0].amount) * lordsBalanceRatio, + ); + }, [resourceBalanceRatio, lordsBalanceRatio]); + + const calculatedResourceAmount = useMemo(() => { + return multiplyByPrecision(inputValue); + }, [inputValue, getsDisplay, getTotalLords]); + + const calculatedLords = useMemo(() => { + return Math.ceil((inputValue / parseFloat(getsDisplay.replace(/,/g, ""))) * getTotalLords); + }, [inputValue, getsDisplay, getTotalLords]); + + const orderWeight = useMemo(() => { + const totalWeight = getTotalResourceWeight([ + { + resourceId: offer.takerGets[0].resourceId, + amount: isBuy ? calculatedLords : calculatedResourceAmount, + }, + ]); + return totalWeight; + }, [entityId, calculatedResourceAmount, calculatedLords]); + + const donkeysNeeded = useMemo(() => { + return calculateDonkeysNeeded(orderWeight); + }, [orderWeight]); + + const donkeyProductionManager = useResourceManager(entityId, ResourcesIds.Donkey); + + const donkeyProduction = useMemo(() => { + return donkeyProductionManager.getProduction(); + }, []); + + const donkeyBalance = useMemo(() => { + return divideByPrecision(donkeyProductionManager.balance(currentDefaultTick)); + }, [donkeyProductionManager, donkeyProduction, currentDefaultTick]); + + const accountName = useMemo(() => { + return getRealmAddressName(offer.makerId); + }, [offer.originName]); + + const onAccept = async () => { + try { + setLoading(true); + setConfirmOrderModal(false); + + await dojo.setup.systemCalls.accept_partial_order({ + signer: dojo.account.account, + taker_id: entityId, + trade_id: offer.tradeId, + maker_gives_resources: [offer.takerGets[0].resourceId, offer.takerGets[0].amount], + taker_gives_resources: [offer.makerGets[0].resourceId, offer.makerGets[0].amount], + taker_gives_actual_amount: isBuy ? calculatedResourceAmount : calculatedLords, + }); + } catch (error) { + console.error("Failed to accept order", error); + } finally { + playLordsSound(); + setUpdateBalance(!updateBalance); + setLoading(false); + } + }; + + return ( +
+ {isMakerResourcesLocked && ( +
+ Resources locked in battle
)} -
{formatNumber(offer.perLords, 4)}
-
- - {currencyFormat(getTotalLords, 0)} +
+
+ {" "} + {getsDisplay} +
+ {travelTime && ( +
+ {Math.floor(travelTime / 60)} hrs {travelTime % 60} mins +
+ )} +
{formatNumber(offer.perLords, 4)}
+
+ + {currencyFormat(getTotalLords, 0)} +
+ {!isSelf ? ( + + ) : ( + + )} +
+ expire: {new Date(offer.expiresAt * 1000).toLocaleString()} +
+
+ {accountName} ({offer.originName}) +
- {!isSelf ? ( - - ) : ( - - )} -
- expire: {new Date(offer.expiresAt * 1000).toLocaleString()} -
-
- {accountName} ({offer.originName}) -
-
- {confirmOrderModal && ( - donkeyBalance || donkeyBalance === 0} - onCancel={() => { - setConfirmOrderModal(false); - }} - > -
-
-
- - +
+
+
+ + +
+ {isBuy ? "Sell" : "Buy"}{" "} + {inputValue} {findResourceById(getDisplayResource)?.trait} for{" "} + {currencyFormat(calculatedLords, 2)} Lords
- {isBuy ? "Sell" : "Buy"}{" "} - {inputValue} {findResourceById(getDisplayResource)?.trait} for{" "} - {currencyFormat(calculatedLords, 2)} Lords -
-
-
Donkeys Required for Transfer
-
donkeyBalance ? "text-red" : "text-green"}`}> - {donkeysNeeded} [{donkeyBalance}] +
+
Donkeys Required for Transfer
+
donkeyBalance ? "text-red" : "text-green"}`}> + {donkeysNeeded} [{donkeyBalance}] +
-
- - )} -
- ); -}; - -const OrderCreation = ({ - entityId, - resourceId, - isBuy = false, -}: { - entityId: ID; - resourceId: ResourcesIds; - isBuy?: boolean; -}) => { - const [loading, setLoading] = useState(false); - const [resource, setResource] = useState(1000); - const [lords, setLords] = useState(100); - const [bid, setBid] = useState(String(lords / resource)); - const { nextBlockTimestamp } = useNextBlockTimestamp(); - - const { play: playLordsSound } = useUiSounds(soundSelector.addLords); - - const { - account: { account }, - setup: { - systemCalls: { create_order }, - }, - } = useDojo(); - useEffect(() => { - setBid(String(lords / resource)); - }, [resource, lords]); - - const updateLords = useCallback((newBid: number, newResource: number) => { - setLords(Number(newBid * newResource)); - }, []); - - const handleBidChange = (newBid: number) => { - const numericBid = Number(newBid); - if (!isNaN(numericBid) && numericBid > 0) { - setBid(String(newBid)); - updateLords(numericBid, resource); - } - }; - - const takerGives = useMemo(() => { - return isBuy ? [resourceId, multiplyByPrecision(resource)] : [ResourcesIds.Lords, multiplyByPrecision(lords)]; - }, [resource, resourceId, lords]); - - const makerGives = useMemo(() => { - return isBuy ? [ResourcesIds.Lords, multiplyByPrecision(lords)] : [resourceId, multiplyByPrecision(resource)]; - }, [resource, resourceId, lords]); - - const createOrder = async () => { - if (!nextBlockTimestamp) return; - setLoading(true); - - await create_order({ - signer: account, - maker_id: entityId, - maker_gives_resources: makerGives, - taker_id: 0, - taker_gives_resources: takerGives, - expires_at: nextBlockTimestamp + ONE_MONTH, - }).finally(() => { - playLordsSound(); - setLoading(false); - }); - }; + + )} +
+ ); + }, +); + +const OrderCreation = memo( + ({ entityId, resourceId, isBuy = false }: { entityId: ID; resourceId: ResourcesIds; isBuy?: boolean }) => { + const [loading, setLoading] = useState(false); + const [resource, setResource] = useState(1000); + const [lords, setLords] = useState(100); + const [bid, setBid] = useState(String(lords / resource)); + const { nextBlockTimestamp } = useNextBlockTimestamp(); + + const { play: playLordsSound } = useUiSounds(soundSelector.addLords); + + const { + account: { account }, + setup: { + systemCalls: { create_order }, + }, + } = useDojo(); + useEffect(() => { + setBid(String(lords / resource)); + }, [resource, lords]); + + const updateLords = useCallback((newBid: number, newResource: number) => { + setLords(Number(newBid * newResource)); + }, []); + + const handleBidChange = (newBid: number) => { + const numericBid = Number(newBid); + if (!isNaN(numericBid) && numericBid > 0) { + setBid(String(newBid)); + updateLords(numericBid, resource); + } + }; + + const takerGives = useMemo(() => { + return isBuy ? [resourceId, multiplyByPrecision(resource)] : [ResourcesIds.Lords, multiplyByPrecision(lords)]; + }, [resource, resourceId, lords]); + + const makerGives = useMemo(() => { + return isBuy ? [ResourcesIds.Lords, multiplyByPrecision(lords)] : [resourceId, multiplyByPrecision(resource)]; + }, [resource, resourceId, lords]); + + const createOrder = async () => { + if (!nextBlockTimestamp) return; + setLoading(true); - const orderWeight = useMemo(() => { - const totalWeight = getTotalResourceWeight([ - { resourceId: isBuy ? resourceId : ResourcesIds.Lords, amount: isBuy ? resource : lords }, - ]); - return totalWeight; - }, [resource, lords]); + await create_order({ + signer: account, + maker_id: entityId, + maker_gives_resources: makerGives, + taker_id: 0, + taker_gives_resources: takerGives, + expires_at: nextBlockTimestamp + ONE_MONTH, + }).finally(() => { + playLordsSound(); + setLoading(false); + }); + }; - const donkeysNeeded = useMemo(() => { - return calculateDonkeysNeeded(multiplyByPrecision(orderWeight)); - }, [orderWeight]); + const orderWeight = useMemo(() => { + const totalWeight = getTotalResourceWeight([ + { resourceId: isBuy ? resourceId : ResourcesIds.Lords, amount: isBuy ? resource : lords }, + ]); + return totalWeight; + }, [resource, lords]); - const { currentDefaultTick } = useNextBlockTimestamp(); + const donkeysNeeded = useMemo(() => { + return calculateDonkeysNeeded(multiplyByPrecision(orderWeight)); + }, [orderWeight]); - const donkeyProductionManager = useResourceManager(entityId, ResourcesIds.Donkey); + const { currentDefaultTick } = useNextBlockTimestamp(); - const donkeyProduction = useMemo(() => { - return donkeyProductionManager.getProduction(); - }, []); + const donkeyProductionManager = useResourceManager(entityId, ResourcesIds.Donkey); - const donkeyBalance = useMemo(() => { - return donkeyProductionManager.balance(currentDefaultTick); - }, [donkeyProductionManager, donkeyProduction, currentDefaultTick]); + const donkeyProduction = useMemo(() => { + return donkeyProductionManager.getProduction(); + }, []); - const resourceProductionManager = useResourceManager(entityId, resourceId); + const donkeyBalance = useMemo(() => { + return donkeyProductionManager.balance(currentDefaultTick); + }, [donkeyProductionManager, donkeyProduction, currentDefaultTick]); - const resourceProduction = useMemo(() => { - return resourceProductionManager.getProduction(); - }, [resourceId]); + const resourceProductionManager = useResourceManager(entityId, resourceId); - const resourceBalance = useMemo(() => { - return resourceProductionManager.balance(currentDefaultTick); - }, [resourceProduction, currentDefaultTick, resourceId]); + const resourceProduction = useMemo(() => { + return resourceProductionManager.getProduction(); + }, [resourceId]); - const lordsProductionManager = useResourceManager(entityId, ResourcesIds.Lords); + const resourceBalance = useMemo(() => { + return resourceProductionManager.balance(currentDefaultTick); + }, [resourceProduction, currentDefaultTick, resourceId]); - const lordsProduction = useMemo(() => { - return lordsProductionManager.getProduction(); - }, []); + const lordsProductionManager = useResourceManager(entityId, ResourcesIds.Lords); - const lordsBalance = useMemo(() => { - return lordsProductionManager.balance(currentDefaultTick); - }, [lordsProductionManager, lordsProduction, currentDefaultTick]); + const lordsProduction = useMemo(() => { + return lordsProductionManager.getProduction(); + }, []); - const canBuy = useMemo(() => { - return isBuy ? lordsBalance > lords : resourceBalance > resource; - }, [resource, lords, donkeyBalance, lordsBalance, resourceBalance]); + const lordsBalance = useMemo(() => { + return lordsProductionManager.balance(currentDefaultTick); + }, [lordsProductionManager, lordsProduction, currentDefaultTick]); - const enoughDonkeys = useMemo(() => { - if (resourceId === ResourcesIds.Donkey) return true; - return donkeyBalance > donkeysNeeded; - }, [donkeyBalance, donkeysNeeded, resourceId]); + const canBuy = useMemo(() => { + return isBuy ? lordsBalance > lords : resourceBalance > resource; + }, [resource, lords, donkeyBalance, lordsBalance, resourceBalance]); - return ( -
-
-
-
- {" "} - {isBuy ? "Buy" : "Sell"} -
- { - setResource(Number(value)); - }} - max={!isBuy ? divideByPrecision(resourceBalance) : Infinity} - /> + const enoughDonkeys = useMemo(() => { + if (resourceId === ResourcesIds.Donkey) return true; + return donkeyBalance > donkeysNeeded; + }, [donkeyBalance, donkeysNeeded, resourceId]); -
- {currencyFormat(resourceBalance ? Number(resourceBalance) : 0, 0)} avail. -
-
-
-
+ return ( +
+
+
+
+ {" "} + {isBuy ? "Buy" : "Sell"} +
{ + setResource(Number(value)); + }} + max={!isBuy ? divideByPrecision(resourceBalance) : Infinity} /> -
- - per / + +
+ {currencyFormat(resourceBalance ? Number(resourceBalance) : 0, 0)} avail.
-
-
-
- {isBuy ? "Cost" : "Gain"} +
+
+ +
+ + per /{" "} + +
+
- { - setLords(Number(value)); - }} - max={isBuy ? divideByPrecision(lordsBalance) : Infinity} - /> +
+
+ {isBuy ? "Cost" : "Gain"} +
+ { + setLords(Number(value)); + }} + max={isBuy ? divideByPrecision(lordsBalance) : Infinity} + /> -
- {currencyFormat(lordsBalance ? Number(lordsBalance) : 0, 0)} avail. +
+ {currencyFormat(lordsBalance ? Number(lordsBalance) : 0, 0)} avail. +
-
-
-
-
-
Donkeys Used
-
- {donkeysNeeded.toLocaleString()}{" "} -
- [{currencyFormat(donkeyBalance ? Number(donkeyBalance) : 0, 0).toLocaleString()} avail.] +
+
+
+
Donkeys Used
+
+ {donkeysNeeded.toLocaleString()}{" "} +
+ [{currencyFormat(donkeyBalance ? Number(donkeyBalance) : 0, 0).toLocaleString()} avail.] +
-
-
-
Weight
-
-
{divideByPrecision(orderWeight).toLocaleString()} kgs
+
+
Weight
+
+
{divideByPrecision(orderWeight).toLocaleString()} kgs
+
-
- + +
-
- ); -}; + ); + }, +); diff --git a/client/src/ui/components/trading/MarketResourceSideBar.tsx b/client/src/ui/components/trading/MarketResourceSideBar.tsx index 800ff7aa2..278d109b7 100644 --- a/client/src/ui/components/trading/MarketResourceSideBar.tsx +++ b/client/src/ui/components/trading/MarketResourceSideBar.tsx @@ -29,6 +29,38 @@ export const MarketResourceSidebar = ({ }); }, []); + const resourceList = useMemo(() => { + return filteredResources + .filter((resourceId) => resourceId !== ResourcesIds.Lords) + .map((resourceId) => { + const marketManager = bankEntityId ? new MarketManager(setup, bankEntityId, 0n, resourceId) : undefined; + + const askPrice = resourceBidOffers + .filter((offer) => (resourceId ? offer.makerGets[0]?.resourceId === resourceId : true)) + .reduce((acc, offer) => (offer.perLords > acc ? offer.perLords : acc), 0); + + const bidPrice = resourceAskOffers + .filter((offer) => offer.takerGets[0].resourceId === resourceId) + .reduce((acc, offer) => (offer.perLords < acc ? offer.perLords : acc), Infinity); + + const ammPrice = marketManager?.getMarketPrice() || 0; + + return ( + + ); + }); + }, [filteredResources, bankEntityId, setup, resourceBidOffers, resourceAskOffers, selectedResource, entityId, onClick]); + + return (
@@ -41,34 +73,7 @@ export const MarketResourceSidebar = ({
- {filteredResources - .filter((resourceId) => resourceId !== ResourcesIds.Lords) - .map((resourceId) => { - const marketManager = bankEntityId ? new MarketManager(setup, bankEntityId, 0n, resourceId) : undefined; - - const askPrice = resourceBidOffers - .filter((offer) => (resourceId ? offer.makerGets[0]?.resourceId === resourceId : true)) - .reduce((acc, offer) => (offer.perLords > acc ? offer.perLords : acc), 0); - - const bidPrice = resourceAskOffers - .filter((offer) => offer.takerGets[0].resourceId === resourceId) - .reduce((acc, offer) => (offer.perLords < acc ? offer.perLords : acc), Infinity); - - const ammPrice = marketManager?.getMarketPrice() || 0; - - return ( - - ); - })} + {resourceList}
); diff --git a/client/src/ui/components/trading/MarketTradingHistory.tsx b/client/src/ui/components/trading/MarketTradingHistory.tsx index 1bf6bb2c7..86bdd8351 100644 --- a/client/src/ui/components/trading/MarketTradingHistory.tsx +++ b/client/src/ui/components/trading/MarketTradingHistory.tsx @@ -6,7 +6,7 @@ import { SelectResource } from "@/ui/elements/SelectResource"; import { ID, Resource, ResourcesIds } from "@bibliothecadao/eternum"; import { defineComponentSystem, getComponentValue, isComponentUpdate } from "@dojoengine/recs"; import { getEntityIdFromKeys } from "@dojoengine/utils"; -import { useEffect, useState } from "react"; +import { memo, useEffect, useMemo, useState } from "react"; import { EventType, TradeHistoryEvent, TradeHistoryRowHeader } from "./TradeHistoryEvent"; const MAX_TRADES = 100; @@ -23,7 +23,7 @@ export type TradeEvent = { }; }; -export const MarketTradingHistory = () => { +export const MarketTradingHistory = memo(() => { const { account: { account: { address }, @@ -103,7 +103,10 @@ export const MarketTradingHistory = () => { }); }, []); - const filteredTradeEvents = showOnlyYourSwaps ? tradeEvents.filter((trade) => trade.event.isYours) : tradeEvents; + const filteredTradeEvents = useMemo( + () => (showOnlyYourSwaps ? tradeEvents.filter((trade) => trade.event.isYours) : tradeEvents), + [showOnlyYourSwaps, tradeEvents], + ); const [selectedResourceId, setSelectedResourceId] = useState(null); @@ -135,4 +138,4 @@ export const MarketTradingHistory = () => { })}
); -}; +}); diff --git a/client/src/ui/components/trading/RealmProduction.tsx b/client/src/ui/components/trading/RealmProduction.tsx index 8ac624917..80af913df 100644 --- a/client/src/ui/components/trading/RealmProduction.tsx +++ b/client/src/ui/components/trading/RealmProduction.tsx @@ -4,7 +4,7 @@ import useUIStore from "@/hooks/store/useUIStore"; import { SelectResource } from "@/ui/elements/SelectResource"; import { unpackResources } from "@/ui/utils/packedData"; import { ResourcesIds } from "@bibliothecadao/eternum"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { RealmResourcesIO } from "../resources/RealmResourcesIO"; export const RealmProduction = () => { @@ -15,66 +15,64 @@ export const RealmProduction = () => { const [filterProduced, setFilterProduced] = useState(null); const [filterConsumed, setFilterConsumed] = useState(null); + const resourcesInputs = useMemo(() => configManager.resourceInputs, []); + + const filteredRealms = useMemo(() => { + if (!realms) return []; + + return realms.filter((realm) => { + if (!realm) return false; + + const resourcesProduced = unpackResources(realm.resourceTypesPacked); + if (filterProduced && !resourcesProduced.includes(filterProduced)) return false; + + if (filterConsumed) { + const resourcesConsumed = new Set( + resourcesProduced.flatMap((resourceId) => + resourcesInputs[resourceId] + .filter((input) => input.resource !== ResourcesIds["Wheat"] && input.resource !== ResourcesIds["Fish"]) + .map((input) => input.resource), + ), + ); + + if (!resourcesConsumed.has(filterConsumed)) return false; + } + + return true; + }); + }, [realms, filterProduced, filterConsumed, resourcesInputs]); + + const handleRealmClick = (realm: any) => { + toggleModal(null); + setSelectedPlayer(realm.owner); + }; + return (

Search produced resource

- setFilterProduced(resourceId)} - className="w-full" - realmProduction={true} - /> +

Search consumed resource

- setFilterConsumed(resourceId)} - className="w-full" - realmProduction={true} - /> +
- {realms && - realms.map((realm, index) => { - if (!realm) return; - - const resourcesProduced = unpackResources(realm.resourceTypesPacked); - if (filterProduced && !resourcesProduced.includes(filterProduced)) return; - - const resourcesInputs = configManager.resourceInputs; - - const resourcesConsumed = [ - ...new Set( - resourcesProduced.flatMap((resourceId) => { - return resourcesInputs[resourceId] - .filter( - (input) => input.resource !== ResourcesIds["Wheat"] && input.resource !== ResourcesIds["Fish"], - ) - .map((input) => input.resource); - }), - ), - ]; - if (filterConsumed && !resourcesConsumed.includes(filterConsumed!)) return; - - return ( -
{ - toggleModal(null); - setSelectedPlayer(realm.owner); - }} - > -

{realm.ownerName}

-

{realm.name}

-
- {realm.realmId && } -
- ); - })} + {filteredRealms.map((realm, index) => ( +
handleRealmClick(realm)} + > +

{realm.ownerName}

+

{realm.name}

+
+ {realm.realmId && } +
+ ))}
); diff --git a/client/src/ui/components/trading/ResourceArrivals.tsx b/client/src/ui/components/trading/ResourceArrivals.tsx index eadbd8944..72dc5ddde 100644 --- a/client/src/ui/components/trading/ResourceArrivals.tsx +++ b/client/src/ui/components/trading/ResourceArrivals.tsx @@ -1,21 +1,22 @@ import { ArrivalInfo } from "@/hooks/helpers/use-resource-arrivals"; import { Headline } from "@/ui/elements/Headline"; import { HintModalButton } from "@/ui/elements/HintModalButton"; +import { memo } from "react"; import { EntityArrival } from "../entities/Entity"; import { HintSection } from "../hints/HintModal"; -export const AllResourceArrivals = ({ arrivals, className }: { arrivals: ArrivalInfo[]; className?: string }) => { - return ( -
+export const AllResourceArrivals = memo( + ({ arrivals, className = "" }: { arrivals: ArrivalInfo[]; className?: string }) => ( +
Transfers
- {arrivals.map((arrival) => { - return ; - })} + {arrivals.map((arrival) => ( + + ))}
- ); -}; + ), +); diff --git a/client/src/ui/components/trading/SelectEntityFromList.tsx b/client/src/ui/components/trading/SelectEntityFromList.tsx index 702140a64..d22d99857 100644 --- a/client/src/ui/components/trading/SelectEntityFromList.tsx +++ b/client/src/ui/components/trading/SelectEntityFromList.tsx @@ -2,46 +2,50 @@ import { useRealm } from "@/hooks/helpers/useRealm"; import Button from "@/ui/elements/Button"; import { ID } from "@bibliothecadao/eternum"; import clsx from "clsx"; +import { memo } from "react"; -export const SelectEntityFromList = ({ - onSelect, - selectedEntityId, - selectedCounterpartyId, - entities, -}: { +interface Entity { + entity_id: ID; + name: string; +} + +interface SelectEntityFromListProps { onSelect: (name: string, entityId: ID) => void; selectedEntityId: ID | null; selectedCounterpartyId: ID | null; - entities: any[]; -}) => { - const { getRealmAddressName } = useRealm(); + entities: Entity[]; +} - return ( -
- {entities.map((entity, index) => { - const realmName = getRealmAddressName(entity.entity_id); - return ( -
onSelect(entity.name, entity.entity_id!)} - > -
- {realmName} ({entity.name}) -
- -
- ); - })} -
- ); -}; +
+ {realmName} ({entity.name}) +
+ +
+ ); + })} +
+ ); + }, +); diff --git a/client/src/ui/components/trading/TransferBetweenEntities.tsx b/client/src/ui/components/trading/TransferBetweenEntities.tsx index 4f7e181f6..e2a004fd7 100644 --- a/client/src/ui/components/trading/TransferBetweenEntities.tsx +++ b/client/src/ui/components/trading/TransferBetweenEntities.tsx @@ -10,7 +10,7 @@ import TextInput from "@/ui/elements/TextInput"; import { multiplyByPrecision } from "@/ui/utils/utils"; import { DONKEY_ENTITY_TYPE, ID } from "@bibliothecadao/eternum"; import { ArrowRight, LucideArrowRight } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; +import { memo, useEffect, useMemo, useState } from "react"; import { TravelInfo } from "../resources/TravelInfo"; import { ToggleComponent } from "../toggle/ToggleComponent"; import { SelectEntityFromList } from "./SelectEntityFromList"; @@ -41,6 +41,126 @@ interface SelectedEntity { entityId: ID; } +const SelectEntitiesStep = memo( + ({ + selectedEntityIdFrom, + selectedEntityIdTo, + setSelectedEntityIdFrom, + setSelectedEntityIdTo, + travelTime, + entitiesListWithAccountNames, + fromSearchTerm, + setFromSearchTerm, + toSearchTerm, + setToSearchTerm, + filtered, + filterBy, + setSelectedStepId, + }: { + selectedEntityIdFrom: SelectedEntity | null; + selectedEntityIdTo: SelectedEntity | null; + setSelectedEntityIdFrom: (entity: SelectedEntity | null) => void; + setSelectedEntityIdTo: (entity: SelectedEntity | null) => void; + travelTime: number | undefined; + entitiesListWithAccountNames: { entities: any[]; name: string }[]; + fromSearchTerm: string; + setFromSearchTerm: (term: string) => void; + toSearchTerm: string; + setToSearchTerm: (term: string) => void; + filtered: boolean; + filterBy: (filtered: boolean) => void; + setSelectedStepId: (stepId: STEP_ID) => void; + }) => { + const isEntitySelected = (entities: any[], selectedEntityId: ID | undefined) => { + return entities.some((entity) => entity.entity_id === selectedEntityId); + }; + + const filterEntities = (entities: any[], searchTerm: string, selectedEntityId: ID | undefined) => { + return entities.filter( + (entity) => + entity.entity_id === selectedEntityId || + entity.name.toLowerCase().includes(searchTerm.toLowerCase()) || + (entity.accountName && entity.accountName.toLowerCase().includes(searchTerm.toLowerCase())), + ); + }; + + return ( + <> +
+ Travel Time: {Math.floor((travelTime || 0) / 60)} hrs {(travelTime || 0) % 60} mins +
+
+ {/* From column */} +
+ From + + {entitiesListWithAccountNames + .filter(({ name }) => name !== "Other Realms") + .map(({ entities, name: title }, index) => { + const filteredEntities = filterEntities(entities, fromSearchTerm, selectedEntityIdFrom?.entityId); + if (filteredEntities.length === 0) return null; + return ( + + setSelectedEntityIdFrom({ name, entityId })} + selectedCounterpartyId={selectedEntityIdTo?.entityId!} + selectedEntityId={selectedEntityIdFrom?.entityId!} + entities={filteredEntities} + /> + + ); + })} +
+ {/* To column */} +
+ To +
+
filterBy(!filtered)}> + +
Tribe Only
+
+
+ + + {entitiesListWithAccountNames.map(({ entities, name: title }, index) => ( + + setSelectedEntityIdTo({ name, entityId })} + selectedCounterpartyId={selectedEntityIdFrom?.entityId!} + selectedEntityId={selectedEntityIdTo?.entityId!} + entities={filterEntities(entities, toSearchTerm, selectedEntityIdTo?.entityId)} + /> + + ))} +
+
+
+ +
+ + ); + }, +); + export const TransferBetweenEntities = ({ entitiesList, filtered, @@ -125,19 +245,6 @@ export const TransferBetweenEntities = ({ setSelectedStepId(STEP_ID.SELECT_ENTITIES); }; - const isEntitySelected = (entities: any[], selectedEntityId: ID | undefined) => { - return entities.some((entity) => entity.entity_id === selectedEntityId); - }; - - const filterEntities = (entities: any[], searchTerm: string, selectedEntityId: ID | undefined) => { - return entities.filter( - (entity) => - entity.entity_id === selectedEntityId || - entity.name.toLowerCase().includes(searchTerm.toLowerCase()) || - (entity.accountName && entity.accountName.toLowerCase().includes(searchTerm.toLowerCase())), - ); - }; - const entitiesListWithAccountNames = useMemo(() => { return entitiesList.map(({ entities, name }) => ({ entities: entities.map((entity) => ({ @@ -167,87 +274,21 @@ export const TransferBetweenEntities = ({
{currentStep?.id === STEP_ID.SELECT_ENTITIES && ( - <> -
- Travel Time: {Math.floor((travelTime || 0) / 60)} hrs {(travelTime || 0) % 60} mins -
-
-
- From - setFromSearchTerm(fromSearchTerm)} - className="my-2" - /> - {entitiesListWithAccountNames - .filter(({ name }) => name !== "Other Realms") - .map(({ entities, name: title }, index) => { - const filteredEntities = filterEntities(entities, fromSearchTerm, selectedEntityIdFrom?.entityId); - if (filteredEntities.length === 0) return null; - return ( - - setSelectedEntityIdFrom({ name, entityId })} - selectedCounterpartyId={selectedEntityIdTo?.entityId!} - selectedEntityId={selectedEntityIdFrom?.entityId!} - entities={filteredEntities} - /> - - ); - })} -
-
- To -
- {" "} -
filterBy(!filtered)}> - -
Tribe Only
-
-
- - setToSearchTerm(toSearchTerm)} - className="my-2" - /> - {entitiesListWithAccountNames.map(({ entities, name: title }, index) => ( - - setSelectedEntityIdTo({ name, entityId })} - selectedCounterpartyId={selectedEntityIdFrom?.entityId!} - selectedEntityId={selectedEntityIdTo?.entityId!} - entities={filterEntities(entities, toSearchTerm, selectedEntityIdTo?.entityId)} - /> - - ))} -
-
-
- -
- + )} {currentStep?.id === STEP_ID.SELECT_RESOURCES && ( @@ -292,16 +333,19 @@ export const TransferBetweenEntities = ({
)} - - {currentStep?.id === STEP_ID.SUCCESS && ( -
-

Transfer successful!

-

Check transfers in the right sidebar transfer menu.

- -
- )} + {currentStep?.id === STEP_ID.SUCCESS && }
); }; + +const FinalTransfer = memo(({ onNewTrade }: { onNewTrade: () => void }) => { + return ( +
+

Transfer successful!

+

Check transfers in the right sidebar transfer menu.

+ +
+ ); +}); diff --git a/client/src/ui/modules/navigation/QuestMenu.tsx b/client/src/ui/modules/navigation/QuestMenu.tsx index 6258e7f60..2b86307ff 100644 --- a/client/src/ui/modules/navigation/QuestMenu.tsx +++ b/client/src/ui/modules/navigation/QuestMenu.tsx @@ -9,9 +9,9 @@ import Button from "@/ui/elements/Button"; import { ResourceCost } from "@/ui/elements/ResourceCost"; import { QuestType } from "@bibliothecadao/eternum"; import clsx from "clsx"; -import { useState } from "react"; +import { memo, useState } from "react"; -export const QuestsMenu = () => { +export const QuestsMenu = memo(() => { const { account: { account }, setup: { @@ -201,7 +201,7 @@ export const QuestsMenu = () => {
) ); -}; +}); const QuestRewards = ({ prizes }: { prizes: Prize[] | undefined }) => { const { getQuestResources } = useRealm(); From bdc50a889a03fefbeb1eaa9f6480ff05ed575d38 Mon Sep 17 00:00:00 2001 From: bal7hazar Date: Sun, 15 Dec 2024 22:48:54 +0100 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=90=9B=20Fix=20black=20screen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/three/scenes/HexagonScene.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/three/scenes/HexagonScene.ts b/client/src/three/scenes/HexagonScene.ts index ce6c45bc6..61c616ce1 100644 --- a/client/src/three/scenes/HexagonScene.ts +++ b/client/src/three/scenes/HexagonScene.ts @@ -414,7 +414,11 @@ export abstract class HexagonScene { this.updateLights(); this.updateHighlightPulse(); this.biomeModels.forEach((biome) => { - biome.updateAnimations(deltaTime); + try { + biome.updateAnimations(deltaTime); + } catch (error) { + console.error(`Error updating biome animations:`, error); + } }); } From a0ad4c611ce82e0d436fb137b179c0347f11a246 Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Mon, 16 Dec 2024 08:51:04 +1100 Subject: [PATCH 3/5] memo combat --- .../modules/military/battle-view/Battle.tsx | 216 +++++++++--------- 1 file changed, 109 insertions(+), 107 deletions(-) diff --git a/client/src/ui/modules/military/battle-view/Battle.tsx b/client/src/ui/modules/military/battle-view/Battle.tsx index bbe03bf85..4d03b4b34 100644 --- a/client/src/ui/modules/military/battle-view/Battle.tsx +++ b/client/src/ui/modules/military/battle-view/Battle.tsx @@ -9,7 +9,7 @@ import { HintModalButton } from "@/ui/elements/HintModalButton"; import { BattleSide, ID } from "@bibliothecadao/eternum"; import { ComponentValue } from "@dojoengine/recs"; import { motion } from "framer-motion"; -import { useState } from "react"; +import { memo, useState } from "react"; import { BattleActions } from "./BattleActions"; import { BattleProgress } from "./BattleProgress"; import { BattleSideView } from "./BattleSideView"; @@ -17,120 +17,122 @@ import { LockedResources } from "./LockedResources"; import { TopScreenView } from "./TopScreenView"; import { BattleTwitterShareButton } from "./battle-twitter-share-button"; -export const Battle = ({ - battleManager, - ownArmySide, - ownArmyEntityId, - battleAdjusted, - attackerArmies, - attackerHealth, - attackerTroops, - defenderArmies, - defenderHealth, - defenderTroops, - userArmiesInBattle, - structure, -}: { - battleManager: BattleManager; - ownArmySide: string; - ownArmyEntityId: ID; - battleAdjusted: ComponentValue | undefined; - attackerArmies: ArmyInfo[]; - attackerHealth: Health; - attackerTroops: ComponentValue["troops"]; - defenderArmies: (ArmyInfo | undefined)[]; - defenderHealth: Health | undefined; - defenderTroops: ComponentValue["troops"] | undefined; - userArmiesInBattle: ArmyInfo[]; - structure: Structure | undefined; -}) => { - const [showBattleDetails, setShowBattleDetails] = useState(false); +export const Battle = memo( + ({ + battleManager, + ownArmySide, + ownArmyEntityId, + battleAdjusted, + attackerArmies, + attackerHealth, + attackerTroops, + defenderArmies, + defenderHealth, + defenderTroops, + userArmiesInBattle, + structure, + }: { + battleManager: BattleManager; + ownArmySide: string; + ownArmyEntityId: ID; + battleAdjusted: ComponentValue | undefined; + attackerArmies: ArmyInfo[]; + attackerHealth: Health; + attackerTroops: ComponentValue["troops"]; + defenderArmies: (ArmyInfo | undefined)[]; + defenderHealth: Health | undefined; + defenderTroops: ComponentValue["troops"] | undefined; + userArmiesInBattle: ArmyInfo[]; + structure: Structure | undefined; + }) => { + const [showBattleDetails, setShowBattleDetails] = useState(false); - return ( -
- + return ( +
+ - -
- - - -
+ +
+ + + +
-
- -
- + army.battle_side === BattleSide[BattleSide.Attack], - )} + ownArmySide={ownArmySide} + attackingHealth={attackerHealth} + attackerArmies={attackerArmies} + defendingHealth={defenderHealth} + defenderArmies={defenderArmies} + structure={structure} /> - {showBattleDetails && battleAdjusted ? ( - + army.battle_side === BattleSide[BattleSide.Attack], + )} /> - ) : ( - + ) : ( + + )} + army.battle_side === BattleSide[BattleSide.Defence], + )} /> - )} - army.battle_side === BattleSide[BattleSide.Defence], - )} - /> +
-
- -
- ); -}; + +
+ ); + }, +); From 4da4b30e2cb8222c48ccdf73e41a3c636baac34e Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Mon, 16 Dec 2024 10:15:03 +1100 Subject: [PATCH 4/5] batch sync --- client/src/dojo/queries.ts | 65 ++++++++++++------- client/src/ui/components/entities/Entity.tsx | 6 +- .../resources/InventoryResources.tsx | 4 +- client/src/ui/layouts/World.tsx | 24 +++---- 4 files changed, 55 insertions(+), 44 deletions(-) diff --git a/client/src/dojo/queries.ts b/client/src/dojo/queries.ts index 0514e4f57..1ed01ec8e 100644 --- a/client/src/dojo/queries.ts +++ b/client/src/dojo/queries.ts @@ -55,35 +55,52 @@ export const syncEntitiesEternum = async ( export const addToSubscription = async ( client: ToriiClient, components: Component[], - entityID: string, - position?: { x: number; y: number }, + entityID: string[], + position?: { x: number; y: number }[], ) => { - const positionClause: EntityKeysClause = { - Keys: { - keys: [String(position?.x || 0), String(position?.y || 0), undefined, undefined], - pattern_matching: "FixedLen" as PatternMatching, - models: [], - }, - }; - - position && - (await getEntities( - client, - { - ...positionClause, - }, - components, - 30_000, - false, - )); + // position && + // (await getEntities( + // client, + // { + // Composite: { + // operator: "Or", + // clauses: position.map((position) => ({ + // Keys: { + // keys: [String(position?.x || 0), String(position?.y || 0), undefined, undefined], + // pattern_matching: "FixedLen" as PatternMatching, + // models: [], + // }, + // })), + // }, + // }, + // components, + // 30_000, + // false, + // )); await getEntities( client, { - Keys: { - keys: [entityID], - pattern_matching: "VariableLen", - models: [], + Composite: { + operator: "Or", + clauses: [ + ...entityID.map((id) => ({ + Keys: { + keys: [id], + pattern_matching: "VariableLen" as PatternMatching, + models: [], + }, + })), + ...(position + ? position.map((position) => ({ + Keys: { + keys: [String(position?.x || 0), String(position?.y || 0), undefined, undefined], + pattern_matching: "FixedLen" as PatternMatching, + models: [], + }, + })) + : []), + ], }, }, components, diff --git a/client/src/ui/components/entities/Entity.tsx b/client/src/ui/components/entities/Entity.tsx index b8a137fdc..9cc55d990 100644 --- a/client/src/ui/components/entities/Entity.tsx +++ b/client/src/ui/components/entities/Entity.tsx @@ -64,11 +64,9 @@ export const EntityArrival = ({ arrival, ...props }: EntityProps) => { setIsSyncing(true); const fetch = async () => { try { - await addToSubscription( - dojo.network.toriiClient, - dojo.network.contractComponents as any, + await addToSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any, [ arrival.entityId.toString(), - ); + ]); localStorage.setItem(cacheKey, now.toString()); } catch (error) { console.error("Fetch failed", error); diff --git a/client/src/ui/components/resources/InventoryResources.tsx b/client/src/ui/components/resources/InventoryResources.tsx index 8f555fda6..ce042c370 100644 --- a/client/src/ui/components/resources/InventoryResources.tsx +++ b/client/src/ui/components/resources/InventoryResources.tsx @@ -51,7 +51,9 @@ export const InventoryResources = ({ setIsSyncing(true); try { - await addToSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any, entityId.toString()); + await addToSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any, [ + entityId.toString(), + ]); localStorage.setItem(cacheKey, now.toString()); } catch (error) { console.error("Fetch failed", error); diff --git a/client/src/ui/layouts/World.tsx b/client/src/ui/layouts/World.tsx index e0909a8f1..cd0000cf5 100644 --- a/client/src/ui/layouts/World.tsx +++ b/client/src/ui/layouts/World.tsx @@ -142,15 +142,13 @@ export const World = ({ backgroundImage }: { backgroundImage: string }) => { await addToSubscription( dojo.network.toriiClient, dojo.network.contractComponents as any, - structureEntityId.toString(), - { x: position?.x || 0, y: position?.y || 0 }, + [structureEntityId.toString()], + [{ x: position?.x || 0, y: position?.y || 0 }], ); - await addToSubscription( - dojo.network.toriiClient, - dojo.network.contractComponents as any, + await addToSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any, [ ADMIN_BANK_ENTITY_ID.toString(), - ); + ]); } catch (error) { console.error("Fetch failed", error); } finally { @@ -180,15 +178,11 @@ export const World = ({ backgroundImage }: { backgroundImage: string }) => { })); const fetch = async () => { try { - await Promise.all( - filteredStructures.map((structure: PlayerStructure) => - addToSubscription( - dojo.network.toriiClient, - dojo.network.contractComponents as any, - structure.entity_id.toString(), - { x: structure.position.x, y: structure.position.y }, - ), - ), + await addToSubscription( + dojo.network.toriiClient, + dojo.network.contractComponents as any, + [...filteredStructures.map((structure) => structure.entity_id.toString())], + [...filteredStructures.map((structure) => ({ x: structure.position.x, y: structure.position.y }))], ); } catch (error) { console.error("Fetch failed", error); From 9ac89050d95a74eaaee130b1739b617dd6c7af21 Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Mon, 16 Dec 2024 10:26:19 +1100 Subject: [PATCH 5/5] batch queires --- client/src/ui/components/entities/Entity.tsx | 30 +----------- .../components/trading/ResourceArrivals.tsx | 47 +++++++++++++------ 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/client/src/ui/components/entities/Entity.tsx b/client/src/ui/components/entities/Entity.tsx index 9cc55d990..6dd2fff63 100644 --- a/client/src/ui/components/entities/Entity.tsx +++ b/client/src/ui/components/entities/Entity.tsx @@ -1,4 +1,3 @@ -import { addToSubscription } from "@/dojo/queries"; import { useDojo } from "@/hooks/context/DojoContext"; import { ArrivalInfo } from "@/hooks/helpers/use-resource-arrivals"; import { getArmyByEntityId } from "@/hooks/helpers/useArmies"; @@ -11,7 +10,7 @@ import { divideByPrecision, formatTime, getEntityIdFromKeys } from "@/ui/utils/u import { EntityType } from "@bibliothecadao/eternum"; import { useComponentValue } from "@dojoengine/react"; import clsx from "clsx"; -import React, { useEffect, useMemo, useState } from "react"; +import React, { useMemo, useState } from "react"; import { DepositResources } from "../resources/DepositResources"; const entityIcon: Record = { @@ -51,33 +50,6 @@ export const EntityArrival = ({ arrival, ...props }: EntityProps) => { return getResourcesFromBalance(arrival.entityId); }, [weight]); - useEffect(() => { - if (entityResources.length === 0) { - const cacheKey = `${CACHE_KEY}-${arrival.entityId}`; - const cachedTime = localStorage.getItem(cacheKey); - const now = Date.now(); - - if (cachedTime && now - parseInt(cachedTime) < CACHE_DURATION) { - return; - } - - setIsSyncing(true); - const fetch = async () => { - try { - await addToSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any, [ - arrival.entityId.toString(), - ]); - localStorage.setItem(cacheKey, now.toString()); - } catch (error) { - console.error("Fetch failed", error); - } finally { - setIsSyncing(false); - } - }; - fetch(); - } - }, [arrival.entityId, dojo.network.toriiClient, dojo.network.contractComponents, entityResources.length]); - const army = useMemo(() => getArmy(arrival.entityId), [arrival.entityId, entity.resources]); const renderEntityStatus = useMemo(() => { diff --git a/client/src/ui/components/trading/ResourceArrivals.tsx b/client/src/ui/components/trading/ResourceArrivals.tsx index 72dc5ddde..c2daa74a4 100644 --- a/client/src/ui/components/trading/ResourceArrivals.tsx +++ b/client/src/ui/components/trading/ResourceArrivals.tsx @@ -1,22 +1,41 @@ +import { addToSubscription } from "@/dojo/queries"; +import { useDojo } from "@/hooks/context/DojoContext"; import { ArrivalInfo } from "@/hooks/helpers/use-resource-arrivals"; import { Headline } from "@/ui/elements/Headline"; import { HintModalButton } from "@/ui/elements/HintModalButton"; -import { memo } from "react"; +import { memo, useEffect } from "react"; import { EntityArrival } from "../entities/Entity"; import { HintSection } from "../hints/HintModal"; export const AllResourceArrivals = memo( - ({ arrivals, className = "" }: { arrivals: ArrivalInfo[]; className?: string }) => ( -
- -
-
Transfers
- -
-
- {arrivals.map((arrival) => ( - - ))} -
- ), + ({ arrivals, className = "" }: { arrivals: ArrivalInfo[]; className?: string }) => { + const dojo = useDojo(); + + useEffect(() => { + const fetch = async () => { + try { + await addToSubscription(dojo.network.toriiClient, dojo.network.contractComponents as any, [ + ...arrivals.map((arrival) => arrival.entityId.toString()), + ]); + } catch (error) { + console.error("Fetch failed", error); + } + }; + fetch(); + }, [arrivals, dojo.network.toriiClient, dojo.network.contractComponents]); + + return ( +
+ +
+
Transfers
+ +
+
+ {arrivals.map((arrival) => ( + + ))} +
+ ); + }, );