From bddea36032313c99f101de73eb3a84321de4fcbb Mon Sep 17 00:00:00 2001 From: eviterin Date: Sat, 2 Mar 2024 17:40:01 +0100 Subject: [PATCH 01/24] Deck struct now also consists of name. Named airdropped deck. --- packages/contracts/src/DeckAirdrop.sol | 2 +- packages/contracts/src/Inventory.sol | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/contracts/src/DeckAirdrop.sol b/packages/contracts/src/DeckAirdrop.sol index 344d05f2..af0b333e 100644 --- a/packages/contracts/src/DeckAirdrop.sol +++ b/packages/contracts/src/DeckAirdrop.sol @@ -61,6 +61,6 @@ contract DeckAirdrop is Ownable { cards[i] = first + i; } - inventory.addDeck(msg.sender, Inventory.Deck(cards)); + inventory.addDeck(msg.sender, Inventory.Deck("Starter Deck", cards)); } } diff --git a/packages/contracts/src/Inventory.sol b/packages/contracts/src/Inventory.sol index ca8b0bc0..ef1fa7dd 100644 --- a/packages/contracts/src/Inventory.sol +++ b/packages/contracts/src/Inventory.sol @@ -78,6 +78,7 @@ contract Inventory is Ownable { // We need a struct because Solidity is unable to copy an array from memory to storage // directly, but can do it when the array is embedded in a struct. struct Deck { + string name; uint256[] cards; } From ceaa86ad8f8a5d1a62c7f6d0125e1cfffc271435 Mon Sep 17 00:00:00 2001 From: eviterin Date: Sat, 2 Mar 2024 18:09:00 +0100 Subject: [PATCH 02/24] Added ability to fetch all decks of a given player from chain --- packages/contracts/src/Inventory.sol | 7 ++++ packages/webapp/src/actions/getDeck.ts | 49 +++++++++++++++++++++++ packages/webapp/src/pages/collection.tsx | 50 ++++++++++++++++++++++-- 3 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 packages/webapp/src/actions/getDeck.ts diff --git a/packages/contracts/src/Inventory.sol b/packages/contracts/src/Inventory.sol index ef1fa7dd..2e545527 100644 --- a/packages/contracts/src/Inventory.sol +++ b/packages/contracts/src/Inventory.sol @@ -337,6 +337,13 @@ contract Inventory is Ownable { { return decks[player][deckID].cards; } + + // --------------------------------------------------------------------------------------------- + + // Returns the decks of a given player. + function getAllDecks(address player) external view returns (Deck[] memory) { + return decks[player]; + } // --------------------------------------------------------------------------------------------- diff --git a/packages/webapp/src/actions/getDeck.ts b/packages/webapp/src/actions/getDeck.ts new file mode 100644 index 00000000..c0f34c9c --- /dev/null +++ b/packages/webapp/src/actions/getDeck.ts @@ -0,0 +1,49 @@ +import { defaultErrorHandling } from "src/actions/errors" +import { contractWriteThrowing } from "src/actions/libContractWrite" +import { Address } from "src/chain" +import { deployment } from "src/deployment" +import { inventoryABI } from "src/generated" + +// ================================================================================================= + +export type getAllDecksArgs = { + playerAddress: Address + onSuccess: () => void +} + +// ------------------------------------------------------------------------------------------------- + +/** + * Fetches all decks of the given player by sending the `getAllDecks` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function getAllDecks(args: getAllDecksArgs): Promise { + try { + return await getAllDecksImpl(args) + } catch (err) { + defaultErrorHandling("getAllDecks", err) + return false + } +} + +// ------------------------------------------------------------------------------------------------- + +async function getAllDecksImpl(args: getAllDecksArgs): Promise { + try { + const result = await contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "getAllDecks", + args: [args.playerAddress], + }) + + args.onSuccess() + return result + } catch (error) { + console.error("Error fetching decks:", error) + return null + } + } + +// ================================================================================================= \ No newline at end of file diff --git a/packages/webapp/src/pages/collection.tsx b/packages/webapp/src/pages/collection.tsx index 0cbeb5ec..0d345c98 100644 --- a/packages/webapp/src/pages/collection.tsx +++ b/packages/webapp/src/pages/collection.tsx @@ -2,8 +2,7 @@ import React, { useEffect, useMemo, useState } from "react" import Head from "next/head" import { useRouter } from "next/router" -import debounce from "lodash/debounce" -import { navigate } from "utils/navigate" +import React, { useState, useMemo, useEffect, useCallback } from "react" import { useAccount } from "wagmi" import { Address } from "src/chain" @@ -16,7 +15,19 @@ import { Navbar } from "src/components/navbar" import { deployment } from "src/deployment" import { useInventoryCardsCollectionGetCollection } from "src/generated" import { FablePage } from "src/pages/_app" -import { Card, Deck } from "src/store/types" +import { useRouter } from "next/router" +import { navigate } from "utils/navigate" + +import FilterPanel from "src/components/collection/filterPanel" +import CardCollectionDisplay from "src/components/collection/cardCollectionDisplay" +import DeckList from "src/components/collection/deckList" +import DeckPanel from "src/components/collection/deckPanel" +import FilterPanel from 'src/components/collection/filterPanel' +import CardCollectionDisplay from 'src/components/collection/cardCollectionDisplay' +import DeckList from 'src/components/collection/deckList' +import DeckPanel from 'src/components/collection/deckPanel' +import { getAllDecks } from "src/actions/getDeck" +import * as store from "src/store/hooks" // NOTE(norswap & geniusgarlic): Just an example, when the game actually has effects & types, // fetch those from the chain instead of hardcoding them here. @@ -37,6 +48,10 @@ const Collection: FablePage = ({ isHydrated }) => { const [effectMap, setEffectMap] = useState(initialEffectMap) const [typeMap, setTypeMap] = useState(initialTypeMap) const [selectedCard, setSelectedCard] = useState(null) + const router = useRouter() + const { address } = useAccount() + const [ isEditing, setIsEditing ] = useState(false) + const playerAddress = store.usePlayerAddress() // Deck Collection Display const [editingDeckIndex, setEditingDeckIndex] = useState(null) @@ -45,6 +60,11 @@ const Collection: FablePage = ({ isHydrated }) => { // Deck Construction Panel const [currentDeck, setCurrentDeck] = useState(null) const [selectedCards, setSelectedCards] = useState([]) + + // Deck Collection Display + const [ editingDeckIndex, setEditingDeckIndex ] = useState(null) + const [decks, setDecks] = useState([]) + const [ isLoadingDecks, setIsLoadingDecks ] = useState(false) // Used to indicate that decks are being loaded from chain const activeEffects = Object.keys(effectMap).filter((key) => effectMap[key]) const activeTypes = Object.keys(typeMap).filter((key) => typeMap[key]) @@ -80,6 +100,30 @@ const Collection: FablePage = ({ isHydrated }) => { setEffectMap({ ...effectMap, [effect]: !effectMap[effect] }) } + const loadDecks = useCallback(() => { + if (playerAddress) { + setIsLoadingDecks(true) + getAllDecks({ + playerAddress: playerAddress, + onSuccess: () => { + }, + }).then(response => { + if(!response.simulatedResult) return + const receivedDecks = response.simulatedResult as Deck[] + setDecks(receivedDecks) + }).catch(error => { + console.error("Error fetching decks:", error) + }).finally(() => { + setIsLoadingDecks(false) + }) + } + }, [playerAddress, setIsLoadingDecks]) + + useEffect(() => { + loadDecks() + }, [loadDecks]) + + const handleTypeClick = (typeIndex: number) => { const type = types[typeIndex] setTypeMap({ ...typeMap, [type]: !typeMap[type] }) From 032db1df1e6ac9c91e5b97b3920454a14b6973f0 Mon Sep 17 00:00:00 2001 From: eviterin Date: Sat, 2 Mar 2024 18:24:37 +0100 Subject: [PATCH 03/24] Can now save deck onchain --- packages/webapp/src/actions/setDeck.ts | 66 +++++++++++++++++ packages/webapp/src/pages/collection.tsx | 90 ++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 packages/webapp/src/actions/setDeck.ts diff --git a/packages/webapp/src/actions/setDeck.ts b/packages/webapp/src/actions/setDeck.ts new file mode 100644 index 00000000..f87d70bf --- /dev/null +++ b/packages/webapp/src/actions/setDeck.ts @@ -0,0 +1,66 @@ +import { defaultErrorHandling } from "src/actions/errors" +import { contractWriteThrowing } from "src/actions/libContractWrite" +import { Address } from "src/chain" +import { deployment } from "src/deployment" +import { inventoryABI } from "src/generated" +import { checkFresh, freshWrap } from "src/store/checkFresh" +import { Deck } from "src/store/types" + +// ================================================================================================= + +export type SaveArgs = { + deck: Deck + playerAddress: Address + onSuccess: () => void +} + +export type ModifyArgs = { +} + +// ------------------------------------------------------------------------------------------------- + +/** + * Saves a deck created by the player by sending the `saveDeck` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function save(args: SaveArgs): Promise { + try { + return await saveImpl(args) + } catch (err) { + return defaultErrorHandling("save", err) + } +} + +/** + * Modifies a deck owned by the player by sending the `modifyDeck` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function modify(args: ModifyArgs): Promise { +} + +// ------------------------------------------------------------------------------------------------- + +async function saveImpl(args: SaveArgs): Promise { + const cardBigInts = args.deck.cards.map(card => card.id) + + checkFresh(await freshWrap( + contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "addDeck", + args: [ + args.playerAddress, + { name: args.deck.name, cards: cardBigInts } + ], + }))) + + args.onSuccess() + return true +} + +async function modifyImpl(args: ModifyArgs): Promise { +} + +// ================================================================================================= \ No newline at end of file diff --git a/packages/webapp/src/pages/collection.tsx b/packages/webapp/src/pages/collection.tsx index 0d345c98..b7e0ed49 100644 --- a/packages/webapp/src/pages/collection.tsx +++ b/packages/webapp/src/pages/collection.tsx @@ -27,6 +27,7 @@ import CardCollectionDisplay from 'src/components/collection/cardCollectionDispl import DeckList from 'src/components/collection/deckList' import DeckPanel from 'src/components/collection/deckPanel' import { getAllDecks } from "src/actions/getDeck" +import { save, modify } from "src/actions/setDeck" import * as store from "src/store/hooks" // NOTE(norswap & geniusgarlic): Just an example, when the game actually has effects & types, @@ -43,6 +44,7 @@ const Collection: FablePage = ({ isHydrated }) => { const { address } = useAccount() const [isEditing, setIsEditing] = useState(false) +<<<<<<< HEAD // Filter Panel / Sorting Panel const [searchInput, setSearchInput] = useState("") const [effectMap, setEffectMap] = useState(initialEffectMap) @@ -52,6 +54,13 @@ const Collection: FablePage = ({ isHydrated }) => { const { address } = useAccount() const [ isEditing, setIsEditing ] = useState(false) const playerAddress = store.usePlayerAddress() +======= + const router = useRouter() + const { address } = useAccount() + const [ isEditing, setIsEditing ] = useState(false) + const playerAddress = store.usePlayerAddress() + const [ isSaving, setIsSaving ] = useState(false) // Used to flag for when putting modifications on chain +>>>>>>> 89335b8 (Can now save deck onchain) // Deck Collection Display const [editingDeckIndex, setEditingDeckIndex] = useState(null) @@ -89,6 +98,87 @@ const Collection: FablePage = ({ isHydrated }) => { card.lore.name.toLowerCase().includes(searchInput.toLowerCase()) ) }) + const activeEffects = Object.keys(effectMap).filter(key => effectMap[key]) + const activeTypes = Object.keys(typeMap).filter(key => typeMap[key]) + + const { data: unfilteredCards } = useInventoryCardsCollectionGetCollection({ + address: deployment.InventoryCardsCollection, + args: [address as Address] // TODO not ideal but safe in practice + }) as { + // make the wagmi type soup understandable, there are many more fields in reality + data: readonly Card[], + } + + const cards: Card[] = (unfilteredCards || []).filter(card => { + // TODO(norswap): it would look like this if the card had effects & types + // const cardEffects = card.stats.effects || [] + // const cardTypes = card.stats.types || [] + const cardEffects: Effect[] = [] + const cardTypes: Effect[] = [] + return activeEffects.every(effect => cardEffects.includes(effect)) + && activeTypes.every(type => cardTypes.includes(type)) + && card.lore.name.toLowerCase().includes(searchInput.toLowerCase()) + }) + + const handleInputChangeBouncy = (event: React.ChangeEvent) => { + setSearchInput(event.target.value) + } + const handleInputChange = useMemo(() => debounce(handleInputChangeBouncy, 300), []) + + const handleEffectClick = (effectIndex: number) => { + const effect = effects[effectIndex] + setEffectMap({...effectMap, [effect]: !effectMap[effect]}) + } + + const handleTypeClick = (typeIndex: number) => { + const type = types[typeIndex] + setTypeMap({...typeMap, [type]: !typeMap[type]}) + } + + const handleDeckSelect = (deckID: number) => { + const selectedDeck = decks[deckID] + setCurrentDeck(selectedDeck) + setEditingDeckIndex(deckID) + setIsEditing(true) + setSelectedCards(selectedDeck.cards) + } + + const handleSaveDeck = async (updatedDeck: Deck) => { + const updatedDecks = [...(decks || [])] + setIsSaving(true) + + if (editingDeckIndex !== null) { + // Update existing deck + updatedDecks[editingDeckIndex] = updatedDeck + } else { + // Add the new deck to the list + await saveOnchain(updatedDeck) + updatedDecks.push(updatedDeck) + } + + setIsSaving(false) + + setDecks(updatedDecks) + setIsEditing(false) + setSelectedCards([]) + void navigate(router, '/collection') + } + + function saveOnchain(deck: Deck): Promise { + return new Promise((resolve) => { + save({ + deck, + playerAddress: playerAddress!, + onSuccess: () => { resolve() } + }) + }) + } + + const handleCancelEditing = () => { + setIsEditing(false) + setSelectedCards([]) + void navigate(router, '/collection') + } const handleInputChangeBouncy = (event: React.ChangeEvent) => { setSearchInput(event.target.value) From 08a9b2e28a21bd1523d9dcae750a5b4952ce0337 Mon Sep 17 00:00:00 2001 From: eviterin Date: Sat, 2 Mar 2024 19:25:20 +0100 Subject: [PATCH 04/24] Can now load decks --- packages/webapp/src/pages/collection.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/webapp/src/pages/collection.tsx b/packages/webapp/src/pages/collection.tsx index b7e0ed49..1f75de9a 100644 --- a/packages/webapp/src/pages/collection.tsx +++ b/packages/webapp/src/pages/collection.tsx @@ -140,7 +140,16 @@ const Collection: FablePage = ({ isHydrated }) => { setCurrentDeck(selectedDeck) setEditingDeckIndex(deckID) setIsEditing(true) - setSelectedCards(selectedDeck.cards) + + const cardObjects: Card[] = [] + selectedDeck.cards.forEach(card => { + const cID = Number(card) + const co = cards.find(c => Number(c.id) === cID) + if(co) { cardObjects.push(co) } + }) + console.log(cardObjects) + + setSelectedCards(cardObjects) } const handleSaveDeck = async (updatedDeck: Deck) => { @@ -162,6 +171,7 @@ const Collection: FablePage = ({ isHydrated }) => { setIsEditing(false) setSelectedCards([]) void navigate(router, '/collection') + loadDecks() } function saveOnchain(deck: Deck): Promise { From 157e8ad9c550c3b41f22dcc48f369e03ae684cdb Mon Sep 17 00:00:00 2001 From: eviterin Date: Sun, 3 Mar 2024 13:40:05 +0100 Subject: [PATCH 05/24] Now shows modal while saving deck --- packages/webapp/src/pages/collection.tsx | 185 +++++++---------------- 1 file changed, 55 insertions(+), 130 deletions(-) diff --git a/packages/webapp/src/pages/collection.tsx b/packages/webapp/src/pages/collection.tsx index 1f75de9a..f9a4f1e9 100644 --- a/packages/webapp/src/pages/collection.tsx +++ b/packages/webapp/src/pages/collection.tsx @@ -18,10 +18,6 @@ import { FablePage } from "src/pages/_app" import { useRouter } from "next/router" import { navigate } from "utils/navigate" -import FilterPanel from "src/components/collection/filterPanel" -import CardCollectionDisplay from "src/components/collection/cardCollectionDisplay" -import DeckList from "src/components/collection/deckList" -import DeckPanel from "src/components/collection/deckPanel" import FilterPanel from 'src/components/collection/filterPanel' import CardCollectionDisplay from 'src/components/collection/cardCollectionDisplay' import DeckList from 'src/components/collection/deckList' @@ -29,6 +25,7 @@ import DeckPanel from 'src/components/collection/deckPanel' import { getAllDecks } from "src/actions/getDeck" import { save, modify } from "src/actions/setDeck" import * as store from "src/store/hooks" +import { LoadingModal } from "src/components/modals/loadingModal" // NOTE(norswap & geniusgarlic): Just an example, when the game actually has effects & types, // fetch those from the chain instead of hardcoding them here. @@ -42,33 +39,18 @@ const initialTypeMap = Object.assign({}, ...types.map((name) => ({ [name]: false const Collection: FablePage = ({ isHydrated }) => { const router = useRouter() const { address } = useAccount() - const [isEditing, setIsEditing] = useState(false) -<<<<<<< HEAD // Filter Panel / Sorting Panel const [searchInput, setSearchInput] = useState("") const [effectMap, setEffectMap] = useState(initialEffectMap) const [typeMap, setTypeMap] = useState(initialTypeMap) const [selectedCard, setSelectedCard] = useState(null) - const router = useRouter() - const { address } = useAccount() - const [ isEditing, setIsEditing ] = useState(false) const playerAddress = store.usePlayerAddress() -======= - const router = useRouter() - const { address } = useAccount() - const [ isEditing, setIsEditing ] = useState(false) - const playerAddress = store.usePlayerAddress() - const [ isSaving, setIsSaving ] = useState(false) // Used to flag for when putting modifications on chain ->>>>>>> 89335b8 (Can now save deck onchain) - - // Deck Collection Display - const [editingDeckIndex, setEditingDeckIndex] = useState(null) - const [decks, setDecks] = useState([]) // Deck Construction Panel const [currentDeck, setCurrentDeck] = useState(null) const [selectedCards, setSelectedCards] = useState([]) + const [isSaving, setIsSaving] = useState(false) // Deck Collection Display const [ editingDeckIndex, setEditingDeckIndex ] = useState(null) @@ -98,27 +80,6 @@ const Collection: FablePage = ({ isHydrated }) => { card.lore.name.toLowerCase().includes(searchInput.toLowerCase()) ) }) - const activeEffects = Object.keys(effectMap).filter(key => effectMap[key]) - const activeTypes = Object.keys(typeMap).filter(key => typeMap[key]) - - const { data: unfilteredCards } = useInventoryCardsCollectionGetCollection({ - address: deployment.InventoryCardsCollection, - args: [address as Address] // TODO not ideal but safe in practice - }) as { - // make the wagmi type soup understandable, there are many more fields in reality - data: readonly Card[], - } - - const cards: Card[] = (unfilteredCards || []).filter(card => { - // TODO(norswap): it would look like this if the card had effects & types - // const cardEffects = card.stats.effects || [] - // const cardTypes = card.stats.types || [] - const cardEffects: Effect[] = [] - const cardTypes: Effect[] = [] - return activeEffects.every(effect => cardEffects.includes(effect)) - && activeTypes.every(type => cardTypes.includes(type)) - && card.lore.name.toLowerCase().includes(searchInput.toLowerCase()) - }) const handleInputChangeBouncy = (event: React.ChangeEvent) => { setSearchInput(event.target.value) @@ -151,7 +112,7 @@ const Collection: FablePage = ({ isHydrated }) => { setSelectedCards(cardObjects) } - + const handleSaveDeck = async (updatedDeck: Deck) => { const updatedDecks = [...(decks || [])] setIsSaving(true) @@ -184,21 +145,6 @@ const Collection: FablePage = ({ isHydrated }) => { }) } - const handleCancelEditing = () => { - setIsEditing(false) - setSelectedCards([]) - void navigate(router, '/collection') - } - - const handleInputChangeBouncy = (event: React.ChangeEvent) => { - setSearchInput(event.target.value) - } - const handleInputChange = useMemo(() => debounce(handleInputChangeBouncy, 300), []) - - const handleEffectClick = (effectIndex: number) => { - const effect = effects[effectIndex] - setEffectMap({ ...effectMap, [effect]: !effectMap[effect] }) - } const loadDecks = useCallback(() => { if (playerAddress) { @@ -223,35 +169,7 @@ const Collection: FablePage = ({ isHydrated }) => { loadDecks() }, [loadDecks]) - - const handleTypeClick = (typeIndex: number) => { - const type = types[typeIndex] - setTypeMap({ ...typeMap, [type]: !typeMap[type] }) - } - - const handleDeckSelect = (deckID: number) => { - const selectedDeck = decks[deckID] - setCurrentDeck(selectedDeck) - setEditingDeckIndex(deckID) - setIsEditing(true) - setSelectedCards(selectedDeck.cards) - } - - const handleSaveDeck = (updatedDeck: Deck) => { - const updatedDecks = [...(decks || [])] - if (editingDeckIndex !== null) { - // Update existing deck - updatedDecks[editingDeckIndex] = updatedDeck - } else { - // Add the new deck to the list - updatedDecks.push(updatedDeck) - } - setDecks(updatedDecks) - setIsEditing(false) - setSelectedCards([]) - void navigate(router, "/collection") - } - + const handleCancelEditing = () => { setIsEditing(false) setSelectedCards([]) @@ -308,51 +226,58 @@ const Collection: FablePage = ({ isHydrated }) => { {jotaiDebug()}
-
- {/* Left Panel - Search and Filters */} -
- -
- - {/* Middle Panel - Card Collection Display */} -
- -
- - {/* Right Panel - Deck List */} -
- {isEditing && currentDeck ? ( - - ) : ( - - )} -
+
+ {/* Left Panel - Search and Filters */} +
+ +
+ + {/* Middle Panel - Card Collection Display */} +
+ +
+ + {/* Right Panel - Deck List */} + {isSaving ? ( + + ) : ( +
+ {isEditing && currentDeck ? ( + + ) : ( + + )}
-
- - ) + )} + + + + ) } export default Collection From 6bdbd822a646361fde105561e17a16358f0266d6 Mon Sep 17 00:00:00 2001 From: eviterin Date: Sun, 3 Mar 2024 14:38:48 +0100 Subject: [PATCH 06/24] Fixed a bug where player could save a deck and then try loading it before it had been published on chain --- packages/webapp/src/pages/collection.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/webapp/src/pages/collection.tsx b/packages/webapp/src/pages/collection.tsx index f9a4f1e9..669bdb62 100644 --- a/packages/webapp/src/pages/collection.tsx +++ b/packages/webapp/src/pages/collection.tsx @@ -128,7 +128,6 @@ const Collection: FablePage = ({ isHydrated }) => { setIsSaving(false) - setDecks(updatedDecks) setIsEditing(false) setSelectedCards([]) void navigate(router, '/collection') From 4123b000a2a46920d0c62744e35951a7d705e278 Mon Sep 17 00:00:00 2001 From: eviterin Date: Sun, 3 Mar 2024 15:02:40 +0100 Subject: [PATCH 07/24] Modify deck support --- packages/webapp/src/actions/setDeck.ts | 26 ++++++++++++++++++++++++ packages/webapp/src/pages/collection.tsx | 24 +++++++++++++++------- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/packages/webapp/src/actions/setDeck.ts b/packages/webapp/src/actions/setDeck.ts index f87d70bf..01f8ffdb 100644 --- a/packages/webapp/src/actions/setDeck.ts +++ b/packages/webapp/src/actions/setDeck.ts @@ -15,7 +15,12 @@ export type SaveArgs = { } export type ModifyArgs = { + deck: Deck + playerAddress: Address + index: number + onSuccess: () => void } + // ------------------------------------------------------------------------------------------------- @@ -38,6 +43,11 @@ export async function save(args: SaveArgs): Promise { * Returns `true` iff the transaction is successful. */ export async function modify(args: ModifyArgs): Promise { + try { + return await modifyImpl(args) + } catch (err) { + return defaultErrorHandling("modify", err) + } } // ------------------------------------------------------------------------------------------------- @@ -61,6 +71,22 @@ async function saveImpl(args: SaveArgs): Promise { } async function modifyImpl(args: ModifyArgs): Promise { + const cardBigInts = args.deck.cards.map(card => card.id) + console.log("INDEX: " + args.index) + checkFresh(await freshWrap( + contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "replaceDeck", + args: [ + args.playerAddress, + args.index, + { name: args.deck.name, cards: cardBigInts } + ], + }))) + + args.onSuccess() + return true } // ================================================================================================= \ No newline at end of file diff --git a/packages/webapp/src/pages/collection.tsx b/packages/webapp/src/pages/collection.tsx index 669bdb62..40d3f66a 100644 --- a/packages/webapp/src/pages/collection.tsx +++ b/packages/webapp/src/pages/collection.tsx @@ -119,6 +119,7 @@ const Collection: FablePage = ({ isHydrated }) => { if (editingDeckIndex !== null) { // Update existing deck + await modifyOnchain(updatedDeck, editingDeckIndex) updatedDecks[editingDeckIndex] = updatedDeck } else { // Add the new deck to the list @@ -144,6 +145,22 @@ const Collection: FablePage = ({ isHydrated }) => { }) } + function modifyOnchain(deck: Deck, editingDeckIndex: number): Promise { + return new Promise((resolve) => { + modify({ + deck, + playerAddress: playerAddress!, + index: BigInt(editingDeckIndex), + onSuccess: () => { resolve() } + }) + }) + } + + const handleCancelEditing = () => { + setIsEditing(false) + setSelectedCards([]) + void navigate(router, '/collection') + } const loadDecks = useCallback(() => { if (playerAddress) { @@ -168,13 +185,6 @@ const Collection: FablePage = ({ isHydrated }) => { loadDecks() }, [loadDecks]) - - const handleCancelEditing = () => { - setIsEditing(false) - setSelectedCards([]) - void navigate(router, "/collection") - } - const addToDeck = (card: Card) => { setSelectedCards((prevSelectedCards) => { // Add or remove card from the selectedCards From e0f5d3c8cdefe7244eab630c306bf387a7e2488a Mon Sep 17 00:00:00 2001 From: eviterin Date: Sun, 3 Mar 2024 16:56:22 +0100 Subject: [PATCH 08/24] =?UTF-8?q?Moved=20some=20logic=20related=20to=20dec?= =?UTF-8?q?ks=20from=20=C2=B4collection=C2=B4=20to=20=C2=B4decklist=C2=B4.?= =?UTF-8?q?=20added=20function=20to=20fetch=20all=20deck=20names=20of=20a?= =?UTF-8?q?=20given=20player?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/webapp/src/actions/getDeck.ts | 41 +++++- .../src/components/collection/deckList.tsx | 117 ++++++++++++------ packages/webapp/src/pages/collection.tsx | 74 ++++------- 3 files changed, 138 insertions(+), 94 deletions(-) diff --git a/packages/webapp/src/actions/getDeck.ts b/packages/webapp/src/actions/getDeck.ts index c0f34c9c..70bbb4ac 100644 --- a/packages/webapp/src/actions/getDeck.ts +++ b/packages/webapp/src/actions/getDeck.ts @@ -6,7 +6,7 @@ import { inventoryABI } from "src/generated" // ================================================================================================= -export type getAllDecksArgs = { +export type GetDeckArgs = { playerAddress: Address onSuccess: () => void } @@ -18,7 +18,7 @@ export type getAllDecksArgs = { * * Returns `true` iff the transaction is successful. */ -export async function getAllDecks(args: getAllDecksArgs): Promise { +export async function getAllDecks(args: GetDeckArgs): Promise { try { return await getAllDecksImpl(args) } catch (err) { @@ -29,7 +29,23 @@ export async function getAllDecks(args: getAllDecksArgs): Promise { // ------------------------------------------------------------------------------------------------- -async function getAllDecksImpl(args: getAllDecksArgs): Promise { +/** + * Fetches deck count of the given player by sending the `getNumDecks` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function getNumDecks(args: GetDeckArgs): Promise { + try { + return await getNumDecksImpl(args) + } catch (err) { + defaultErrorHandling("getNumDecks", err) + return false + } +} + +// ------------------------------------------------------------------------------------------------- + +async function getAllDecksImpl(args: GetDeckArgs): Promise { try { const result = await contractWriteThrowing({ contract: deployment.Inventory, @@ -46,4 +62,23 @@ async function getAllDecksImpl(args: getAllDecksArgs): Promise { } } +// ------------------------------------------------------------------------------------------------- + +async function getNumDecksImpl(args: GetDeckArgs): Promise { + try { + const result = await contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "getNumDecks", + args: [args.playerAddress], + }) + + args.onSuccess() + return result + } catch (error) { + console.error("Error fetching decks:", error) + return null + } +} + // ================================================================================================= \ No newline at end of file diff --git a/packages/webapp/src/components/collection/deckList.tsx b/packages/webapp/src/components/collection/deckList.tsx index 89abdaa3..4c3eaaa8 100644 --- a/packages/webapp/src/components/collection/deckList.tsx +++ b/packages/webapp/src/components/collection/deckList.tsx @@ -1,41 +1,76 @@ -import React from "react" - -import Link from "src/components/link" -import { Button } from "src/components/ui/button" -import { Deck } from "src/store/types" - -interface DeckCollectionDisplayProps { - decks: Deck[] - onDeckSelect: (deckID: number) => void -} - -const DeckCollectionDisplay: React.FC = ({ decks, onDeckSelect }) => { - return ( -
- {/* New Deck Button */} -
- -
- - {/* Deck Buttons */} - {decks.map((deck, deckID) => ( - - ))} -
- ) -} - -export default DeckCollectionDisplay +import React, { useEffect, useState, useCallback } from "react" +import Link from "src/components/link" +import { Deck } from 'src/store/types' +import { Button } from "src/components/ui/button" +import { getAllDecks, getNumDecks } from "src/actions/getDeck" +import * as store from "src/store/hooks" +import { Deck } from "src/store/types" + +interface DeckCollectionDisplayProps { + decks: Deck[] + setDecks: React.Dispatch> + onDeckSelect: (deckID: number) => void +} + +const DeckCollectionDisplay: React.FC = ({ decks, setDecks, onDeckSelect }) => { + const playerAddress = store.usePlayerAddress() + const [ isLoadingDecks, setIsLoadingDecks ] = useState(true) + + function deckCount(): Promise { + return new Promise((resolve) => { + getNumDecks({ + playerAddress: playerAddress!, + onSuccess: () => { } + }) + }) + } + + const loadDecks = useCallback(() => { + if (playerAddress) { + setIsLoadingDecks(true) + getAllDecks({ + playerAddress: playerAddress, + onSuccess: () => { + }, + }).then(response => { + if(!response.simulatedResult) return + const receivedDecks = response.simulatedResult as Deck[] + setDecks(receivedDecks) + }).catch(error => { + console.error("Error fetching decks:", error) + }).finally(() => { + setIsLoadingDecks(false) + }) + } + }, [playerAddress, setIsLoadingDecks]) + + + useEffect(() => { + loadDecks() + }, [loadDecks]) + + return ( +
+ {/* New Deck Button */} + + + {/* Deck Buttons */} + {decks.map((deck, deckID) => ( + + ))} +
+ ) +} + +export default DeckCollectionDisplay diff --git a/packages/webapp/src/pages/collection.tsx b/packages/webapp/src/pages/collection.tsx index 40d3f66a..8ac90b9f 100644 --- a/packages/webapp/src/pages/collection.tsx +++ b/packages/webapp/src/pages/collection.tsx @@ -22,7 +22,6 @@ import FilterPanel from 'src/components/collection/filterPanel' import CardCollectionDisplay from 'src/components/collection/cardCollectionDisplay' import DeckList from 'src/components/collection/deckList' import DeckPanel from 'src/components/collection/deckPanel' -import { getAllDecks } from "src/actions/getDeck" import { save, modify } from "src/actions/setDeck" import * as store from "src/store/hooks" import { LoadingModal } from "src/components/modals/loadingModal" @@ -51,6 +50,7 @@ const Collection: FablePage = ({ isHydrated }) => { const [currentDeck, setCurrentDeck] = useState(null) const [selectedCards, setSelectedCards] = useState([]) const [isSaving, setIsSaving] = useState(false) + const [isEditing, setIsEditing] = useState(false) // Deck Collection Display const [ editingDeckIndex, setEditingDeckIndex ] = useState(null) @@ -96,35 +96,15 @@ const Collection: FablePage = ({ isHydrated }) => { setTypeMap({...typeMap, [type]: !typeMap[type]}) } - const handleDeckSelect = (deckID: number) => { - const selectedDeck = decks[deckID] - setCurrentDeck(selectedDeck) - setEditingDeckIndex(deckID) - setIsEditing(true) - - const cardObjects: Card[] = [] - selectedDeck.cards.forEach(card => { - const cID = Number(card) - const co = cards.find(c => Number(c.id) === cID) - if(co) { cardObjects.push(co) } - }) - console.log(cardObjects) - - setSelectedCards(cardObjects) - } - const handleSaveDeck = async (updatedDeck: Deck) => { - const updatedDecks = [...(decks || [])] setIsSaving(true) if (editingDeckIndex !== null) { // Update existing deck await modifyOnchain(updatedDeck, editingDeckIndex) - updatedDecks[editingDeckIndex] = updatedDeck } else { // Add the new deck to the list await saveOnchain(updatedDeck) - updatedDecks.push(updatedDeck) } setIsSaving(false) @@ -132,7 +112,6 @@ const Collection: FablePage = ({ isHydrated }) => { setIsEditing(false) setSelectedCards([]) void navigate(router, '/collection') - loadDecks() } function saveOnchain(deck: Deck): Promise { @@ -161,30 +140,7 @@ const Collection: FablePage = ({ isHydrated }) => { setSelectedCards([]) void navigate(router, '/collection') } - - const loadDecks = useCallback(() => { - if (playerAddress) { - setIsLoadingDecks(true) - getAllDecks({ - playerAddress: playerAddress, - onSuccess: () => { - }, - }).then(response => { - if(!response.simulatedResult) return - const receivedDecks = response.simulatedResult as Deck[] - setDecks(receivedDecks) - }).catch(error => { - console.error("Error fetching decks:", error) - }).finally(() => { - setIsLoadingDecks(false) - }) - } - }, [playerAddress, setIsLoadingDecks]) - - useEffect(() => { - loadDecks() - }, [loadDecks]) - + const addToDeck = (card: Card) => { setSelectedCards((prevSelectedCards) => { // Add or remove card from the selectedCards @@ -209,6 +165,23 @@ const Collection: FablePage = ({ isHydrated }) => { }) } + const handleDeckSelect = (deckID: number) => { + const selectedDeck = decks[deckID] + setCurrentDeck(selectedDeck) + setEditingDeckIndex(deckID) + setIsEditing(true) + + const cardObjects: Card[] = [] + selectedDeck.cards.forEach(card => { + const cID = Number(card) + const co = cards.find(c => Number(c.id) === cID) + if(co) { cardObjects.push(co) } + }) + console.log(cardObjects) + + setSelectedCards(cardObjects) + } + // Sets up an event listener for route changes when deck editor is rendered. useEffect(() => { const handleRouteChange = () => { @@ -249,7 +222,7 @@ const Collection: FablePage = ({ isHydrated }) => { selectedCard={selectedCard} /> - + {/* Middle Panel - Card Collection Display */}
{ /> ) : ( + onDeckSelect={handleDeckSelect} + decks={decks} + setDecks={setDecks} + /> )}
)} From 7d0bf6d41dafdfb8ed1447e0ca7f138da8de27ca Mon Sep 17 00:00:00 2001 From: eviterin Date: Mon, 4 Mar 2024 16:28:12 +0100 Subject: [PATCH 09/24] Added two new functions. One to fetch all deck names a player owns and one to fetch the actual deck. --- packages/contracts/src/Inventory.sol | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/contracts/src/Inventory.sol b/packages/contracts/src/Inventory.sol index 2e545527..b663e69a 100644 --- a/packages/contracts/src/Inventory.sol +++ b/packages/contracts/src/Inventory.sol @@ -337,6 +337,18 @@ contract Inventory is Ownable { { return decks[player][deckID].cards; } + + // --------------------------------------------------------------------------------------------- + + // Returns the list of cards in the given deck of the given player. + function getDeckReal(address player, uint8 deckID) + external + view + exists(player, deckID) + returns (Deck memory) + { + return decks[player][deckID]; + } // --------------------------------------------------------------------------------------------- @@ -354,6 +366,20 @@ contract Inventory is Ownable { // --------------------------------------------------------------------------------------------- + // Returns the names of all decks for a given player. + function getDeckNames(address player) external view returns (string[] memory) { + uint256 deckCount = decks[player].length; + string[] memory deckNames = new string[](deckCount); + + for (uint256 i = 0; i < deckCount; i++) { + deckNames[i] = decks[player][i].name; + } + + return deckNames; + } + + // --------------------------------------------------------------------------------------------- + function getCardTypes(uint256[] memory cardIDArr) public view returns (uint256[] memory) { uint256 len = cardIDArr.length; uint256[] memory cardTypeArr = new uint256[](len); From a906dac0cdc05d2476d0917cc47f43e280e22b01 Mon Sep 17 00:00:00 2001 From: eviterin Date: Mon, 4 Mar 2024 16:29:17 +0100 Subject: [PATCH 10/24] Implemented interface to call the added functions in --- packages/webapp/src/actions/getDeck.ts | 74 ++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/packages/webapp/src/actions/getDeck.ts b/packages/webapp/src/actions/getDeck.ts index 70bbb4ac..524f16b3 100644 --- a/packages/webapp/src/actions/getDeck.ts +++ b/packages/webapp/src/actions/getDeck.ts @@ -11,6 +11,12 @@ export type GetDeckArgs = { onSuccess: () => void } +export type GetDeckAtArgs = { + playerAddress: Address + onSuccess: () => void + index: number +} + // ------------------------------------------------------------------------------------------------- /** @@ -27,6 +33,20 @@ export async function getAllDecks(args: GetDeckArgs): Promise { } } +/** + * Fetches the deck of the given player of a given ID by sending the `getDeckReal` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function getDeck(args: GetDeckAtArgs): Promise { + try { + return await getDeckImpl(args) + } catch (err) { + defaultErrorHandling("getDeck", err) + return false + } +} + // ------------------------------------------------------------------------------------------------- /** @@ -45,6 +65,22 @@ export async function getNumDecks(args: GetDeckArgs): Promise { // ------------------------------------------------------------------------------------------------- +/** + * Fetches deck count of the given player by sending the `getNumDecks` transaction. + * + * Returns `true` iff the transaction is successful. + */ +export async function getDeckNames(args: GetDeckArgs): Promise { + try { + return await getDeckNamesImpl(args) + } catch (err) { + defaultErrorHandling("getDeckNames", err) + return false + } +} + +// ------------------------------------------------------------------------------------------------- + async function getAllDecksImpl(args: GetDeckArgs): Promise { try { const result = await contractWriteThrowing({ @@ -64,6 +100,25 @@ async function getAllDecksImpl(args: GetDeckArgs): Promise { // ------------------------------------------------------------------------------------------------- +async function getDeckImpl(args: GetDeckAtArgs): Promise { + try { + const result = await contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "getDeckReal", + args: [args.playerAddress, args.index], + }) + + args.onSuccess() + return result + } catch (error) { + console.error("Error fetching deck:", error) + return null + } +} + +// ------------------------------------------------------------------------------------------------- + async function getNumDecksImpl(args: GetDeckArgs): Promise { try { const result = await contractWriteThrowing({ @@ -81,4 +136,23 @@ async function getNumDecksImpl(args: GetDeckArgs): Promise { } } +// ------------------------------------------------------------------------------------------------- + +async function getDeckNamesImpl(args: GetDeckArgs): Promise { + try { + const result = await contractWriteThrowing({ + contract: deployment.Inventory, + abi: inventoryABI, + functionName: "getDeckNames", + args: [args.playerAddress], + }) + + args.onSuccess() + return result + } catch (error) { + console.error("Error fetching decks:", error) + return null + } +} + // ================================================================================================= \ No newline at end of file From 29a78eaf6d688e13f6e91183d75363adcfb684da Mon Sep 17 00:00:00 2001 From: eviterin Date: Mon, 4 Mar 2024 16:31:52 +0100 Subject: [PATCH 11/24] No longer loads all the deck of the player, but instead loads all the deck names of a player. Only once a deck has been selected do we fetch all the cards of that deck. --- .../src/components/collection/deckList.tsx | 29 ++++---- packages/webapp/src/pages/collection.tsx | 68 ++++++++++++------- 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/packages/webapp/src/components/collection/deckList.tsx b/packages/webapp/src/components/collection/deckList.tsx index 4c3eaaa8..2dd57658 100644 --- a/packages/webapp/src/components/collection/deckList.tsx +++ b/packages/webapp/src/components/collection/deckList.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState, useCallback } from "react" import Link from "src/components/link" import { Deck } from 'src/store/types' import { Button } from "src/components/ui/button" -import { getAllDecks, getNumDecks } from "src/actions/getDeck" +import { getAllDecks, getNumDecks, getDeckNames } from "src/actions/getDeck" import * as store from "src/store/hooks" import { Deck } from "src/store/types" @@ -14,7 +14,7 @@ interface DeckCollectionDisplayProps { const DeckCollectionDisplay: React.FC = ({ decks, setDecks, onDeckSelect }) => { const playerAddress = store.usePlayerAddress() - const [ isLoadingDecks, setIsLoadingDecks ] = useState(true) + const [ deckNames, setDeckNames] = useState([]) function deckCount(): Promise { return new Promise((resolve) => { @@ -25,29 +25,28 @@ const DeckCollectionDisplay: React.FC = ({ decks, se }) } - const loadDecks = useCallback(() => { + const loadDeckNames = useCallback(() => { if (playerAddress) { - setIsLoadingDecks(true) - getAllDecks({ + getDeckNames({ playerAddress: playerAddress, onSuccess: () => { }, }).then(response => { if(!response.simulatedResult) return - const receivedDecks = response.simulatedResult as Deck[] - setDecks(receivedDecks) + console.log(response) + const receivedDecks = response.simulatedResult as string[] + setDeckNames(receivedDecks) }).catch(error => { console.error("Error fetching decks:", error) - }).finally(() => { - setIsLoadingDecks(false) }) } - }, [playerAddress, setIsLoadingDecks]) + }, [playerAddress]) useEffect(() => { - loadDecks() - }, [loadDecks]) + loadDeckNames() + //loadDecks() + }, [loadDeckNames]) return (
@@ -59,14 +58,14 @@ const DeckCollectionDisplay: React.FC = ({ decks, se {/* Deck Buttons */} - {decks.map((deck, deckID) => ( + {deckNames.map((deckname, deckID) => ( ))}
diff --git a/packages/webapp/src/pages/collection.tsx b/packages/webapp/src/pages/collection.tsx index 8ac90b9f..53bf6523 100644 --- a/packages/webapp/src/pages/collection.tsx +++ b/packages/webapp/src/pages/collection.tsx @@ -17,7 +17,7 @@ import { useInventoryCardsCollectionGetCollection } from "src/generated" import { FablePage } from "src/pages/_app" import { useRouter } from "next/router" import { navigate } from "utils/navigate" - +import { getDeck } from "src/actions/getDeck" import FilterPanel from 'src/components/collection/filterPanel' import CardCollectionDisplay from 'src/components/collection/cardCollectionDisplay' import DeckList from 'src/components/collection/deckList' @@ -51,7 +51,7 @@ const Collection: FablePage = ({ isHydrated }) => { const [selectedCards, setSelectedCards] = useState([]) const [isSaving, setIsSaving] = useState(false) const [isEditing, setIsEditing] = useState(false) - + // Deck Collection Display const [ editingDeckIndex, setEditingDeckIndex ] = useState(null) const [decks, setDecks] = useState([]) @@ -166,21 +166,41 @@ const Collection: FablePage = ({ isHydrated }) => { } const handleDeckSelect = (deckID: number) => { - const selectedDeck = decks[deckID] - setCurrentDeck(selectedDeck) - setEditingDeckIndex(deckID) - setIsEditing(true) + if (isLoadingDeck) return; + if (playerAddress) { + setIsLoadingDeck(true) + getDeck({ + playerAddress: playerAddress, + index: deckID, + onSuccess: () => { + }, + }).then(response => { + if(!response.simulatedResult) return; + - const cardObjects: Card[] = [] - selectedDeck.cards.forEach(card => { - const cID = Number(card) - const co = cards.find(c => Number(c.id) === cID) - if(co) { cardObjects.push(co) } - }) - console.log(cardObjects) + const cardsReceived = response.simulatedResult.cards; + const cardObjects: Card[] = [] + cardsReceived.forEach(card => { + const cID = Number(card) + const co = cards.find(c => Number(c.id) === cID) + if(co) { cardObjects.push(co) } + }) - setSelectedCards(cardObjects) - } + console.log(cardObjects) + setSelectedCards(cardObjects); + + const deckName = response.simulatedResult.name; + setCurrentDeck({ name: deckName, cards: cardObjects }) + setEditingDeckIndex(deckID) + setIsEditing(true) + + }).catch(error => { + console.error("Error fetching deck:", error); + }).finally(_ => { + setIsLoadingDeck(false) + }) + } + } // Sets up an event listener for route changes when deck editor is rendered. useEffect(() => { @@ -237,7 +257,7 @@ const Collection: FablePage = ({ isHydrated }) => { {/* Right Panel - Deck List */} {isSaving ? ( - + ) : (
{isEditing && currentDeck ? ( @@ -250,14 +270,14 @@ const Collection: FablePage = ({ isHydrated }) => { /> ) : ( - )} -
- )} - + onDeckSelect={handleDeckSelect} + decks={decks} + setDecks={setDecks} + /> + )} + + )} + ) From 5288304da4872dbaad25e7a0ae34a3b339e1981e Mon Sep 17 00:00:00 2001 From: eviterin Date: Mon, 4 Mar 2024 17:15:47 +0100 Subject: [PATCH 12/24] Show loading modal when loading deck --- packages/webapp/src/pages/collection.tsx | 123 +++++++++++------------ 1 file changed, 61 insertions(+), 62 deletions(-) diff --git a/packages/webapp/src/pages/collection.tsx b/packages/webapp/src/pages/collection.tsx index 53bf6523..8e03ee89 100644 --- a/packages/webapp/src/pages/collection.tsx +++ b/packages/webapp/src/pages/collection.tsx @@ -55,7 +55,7 @@ const Collection: FablePage = ({ isHydrated }) => { // Deck Collection Display const [ editingDeckIndex, setEditingDeckIndex ] = useState(null) const [decks, setDecks] = useState([]) - const [ isLoadingDecks, setIsLoadingDecks ] = useState(false) // Used to indicate that decks are being loaded from chain + const [ isLoadingDeck, setIsLoadingDeck ] = useState(false) const activeEffects = Object.keys(effectMap).filter((key) => effectMap[key]) const activeTypes = Object.keys(typeMap).filter((key) => typeMap[key]) @@ -221,66 +221,65 @@ const Collection: FablePage = ({ isHydrated }) => { }, [router.events, router.query.newDeck]) return ( - <> - - 0xFable: My Collection - - {jotaiDebug()} -
- -
- {/* Left Panel - Search and Filters */} -
- -
- - {/* Middle Panel - Card Collection Display */} -
- -
- - {/* Right Panel - Deck List */} - {isSaving ? ( - - ) : ( -
- {isEditing && currentDeck ? ( - - ) : ( - - )} + <> + + 0xFable: My Collection + + {jotaiDebug()} + {isLoadingDeck ? ( + + ) : ( +
+ +
+ {/* Left Panel - Search and Filters */} +
+ +
+ {/* Middle Panel - Card Collection Display */} +
+ +
+ {/* Right Panel - Deck List */} + {isSaving ? ( + + ) : ( +
+ {isEditing && currentDeck ? ( + + ) : ( + + )} +
+ )}
- )} -
-
- - ) -} - + + )} + + )} export default Collection From af4f3dac9337de6ee09acce261d3b8fa8079275a Mon Sep 17 00:00:00 2001 From: eviterin Date: Fri, 8 Mar 2024 20:23:07 +0100 Subject: [PATCH 13/24] Added a meter that displays deck validity --- .../src/components/collection/deckPanel.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/webapp/src/components/collection/deckPanel.tsx b/packages/webapp/src/components/collection/deckPanel.tsx index 20ced2c4..35faa3cc 100644 --- a/packages/webapp/src/components/collection/deckPanel.tsx +++ b/packages/webapp/src/components/collection/deckPanel.tsx @@ -20,6 +20,9 @@ const DeckConstructionPanel: React.FC = ({ onSave, onCancel, }) => { + const MAX_CARDS = 10//40 + const MIN_CARDS = 4//10 + const [deckName, setDeckName] = useState(deck.name) const [deckNameValid, setIsDeckNameValid] = useState(false) @@ -56,6 +59,20 @@ const DeckConstructionPanel: React.FC = ({ /> + {/* Counter Row */} +
+
+
+
+
+
+
+
+ {selectedCards.length}/{MAX_CARDS} +
+
+ {/* Save and Cancel Buttons */}
+ {/* Loading Button */} + {isLoadingDecks && ( + + )} + {/* Deck Buttons */} {deckNames.map((deckname, deckID) => (
From 0b5d9a129b068fb4ba6540b9060e7a94a6ea0e37 Mon Sep 17 00:00:00 2001 From: eviterin Date: Tue, 26 Mar 2024 19:29:54 +0100 Subject: [PATCH 22/24] Fixed a bug where the loading indicator would be prematurely hidden. Also changed it's color --- packages/webapp/src/components/collection/deckList.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/webapp/src/components/collection/deckList.tsx b/packages/webapp/src/components/collection/deckList.tsx index 24676a9e..f3a2f493 100644 --- a/packages/webapp/src/components/collection/deckList.tsx +++ b/packages/webapp/src/components/collection/deckList.tsx @@ -37,10 +37,9 @@ const DeckCollectionDisplay: React.FC = ({ decks, se if(!response.simulatedResult) return const receivedDecks = response.simulatedResult as string[] setDeckNames(receivedDecks) + setIsLoadingDecks(false) }).catch(error => { console.error("Error fetching decks:", error) - }).finally(() => { - setIsLoadingDecks(false) }) } }, [playerAddress]) @@ -61,7 +60,7 @@ const DeckCollectionDisplay: React.FC = ({ decks, se {/* Loading Button */} {isLoadingDecks && ( - - - {/* Loading Button */} - {isLoadingDecks && ( - - )} - - {/* Deck Buttons */} - {deckNames.map((deckname, deckID) => ( - - ))} - - ) -} - -export default DeckCollectionDisplay +import React, { useCallback, useEffect, useState } from "react" + +import { getDeckNames } from "src/actions/getDeck" +import Link from "src/components/link" +import { Button } from "src/components/ui/button" +import * as store from "src/store/hooks" + +interface DeckCollectionDisplayProps { + onDeckSelect: (deckID: number) => void +} + +const DeckCollectionDisplay: React.FC = ({ onDeckSelect }) => { + const playerAddress = store.usePlayerAddress() + const [deckNames, setDeckNames] = useState([]) + const [isLoadingDecks, setIsLoadingDecks] = useState(false) + + const loadDeckNames = useCallback(() => { + if (playerAddress) { + setIsLoadingDecks(true) + getDeckNames({ + playerAddress: playerAddress, + onSuccess: () => {}, + }) + .then((response) => { + if (!response.simulatedResult) return + const receivedDecks = response.simulatedResult as string[] + setDeckNames(receivedDecks) + setIsLoadingDecks(false) + }) + .catch((error) => { + console.error("Error fetching decks:", error) + }) + } + }, [playerAddress]) + + useEffect(() => { + loadDeckNames() + }, [loadDeckNames]) + + return ( +
+ {/* New Deck Button */} + + + {/* Loading Button */} + {isLoadingDecks && ( + + )} + + {/* Deck Buttons */} + {deckNames.map((deckname, deckID) => ( + + ))} +
+ ) +} + +export default DeckCollectionDisplay diff --git a/packages/webapp/src/components/collection/deckPanel.tsx b/packages/webapp/src/components/collection/deckPanel.tsx index 46c23f45..9699d065 100644 --- a/packages/webapp/src/components/collection/deckPanel.tsx +++ b/packages/webapp/src/components/collection/deckPanel.tsx @@ -62,14 +62,15 @@ const DeckConstructionPanel: React.FC = ({ {/* Counter Row */}
-
-
+
+
-
- {selectedCards.length}/{MAX_CARDS} + {selectedCards.length}/{MAX_CARDS}
@@ -77,7 +78,7 @@ const DeckConstructionPanel: React.FC = ({