diff --git a/.gitignore b/.gitignore index 3e549fa..7e0d6d2 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ app/next-env.d.ts app/tools/data/* app/data/misc/* app/data/commander/* +app/data/card-commander/* app/data/bset/* app/tools/json/* app/.pg diff --git a/app/.dockerignore b/app/.dockerignore index 73a7da9..d1a4242 100644 --- a/app/.dockerignore +++ b/app/.dockerignore @@ -23,5 +23,6 @@ tools/data/* tools/json data/misc/* data/commander/* +data/card-commander/* data/bset/* .pg/ diff --git a/app/app/bset/[bset]/page_content.tsx b/app/app/bset/[bset]/page_content.tsx index 893e554..dfc3808 100644 --- a/app/app/bset/[bset]/page_content.tsx +++ b/app/app/bset/[bset]/page_content.tsx @@ -2,7 +2,7 @@ import { useEffect, useState, useRef } from 'react' import { CardGroup } from '@/components/ui/card-group' -import {PlaneswalkerIcon, SorceryIcon, InstantIcon, CreatureIcon, EnchantmentIcon, LandIcon, ArtifactIcon} from '@/components/ui/symbols-icons' +import {CommanderIcon, PlaneswalkerIcon, SorceryIcon, InstantIcon, CreatureIcon, EnchantmentIcon, LandIcon, ArtifactIcon} from '@/components/ui/symbols-icons' interface PageContentProps { bset: string @@ -69,7 +69,7 @@ export default function PageContent({bset}: PageContentProps) {
- + @@ -80,7 +80,7 @@ export default function PageContent({bset}: PageContentProps) {
- Commandants + Commandants Planeswalker Créatures Rituels diff --git a/app/app/commander/[color]/page_content.tsx b/app/app/commander/[color]/page_content.tsx index a1a5281..10138d3 100644 --- a/app/app/commander/[color]/page_content.tsx +++ b/app/app/commander/[color]/page_content.tsx @@ -17,7 +17,7 @@ export default function PageContent({color}: PageContentProps) { fetch('/api/json/commander/'+color+'.json').then((res) => { if(res.status == 200) { res.json().then((data) => { - const limit = 20 + const limit = 100 setCommanderCardList(data.slice(0,limit)) setLoading(false) console.log(data) @@ -29,7 +29,7 @@ export default function PageContent({color}: PageContentProps) {
{ loading && ( )} - { !loading && ( )} + { !loading && ( )}
); diff --git a/app/app/commander/card/[card_name]/page.tsx b/app/app/commander/card/[card_name]/page.tsx new file mode 100644 index 0000000..a7f736e --- /dev/null +++ b/app/app/commander/card/[card_name]/page.tsx @@ -0,0 +1,10 @@ +'use server' + +import PageContent from './page_content' + +export default async function Home({ params }: { params: { card_name: string } } ) { + const card_name = (await params).card_name + return ( + + ); +} diff --git a/app/app/commander/card/[card_name]/page_content.tsx b/app/app/commander/card/[card_name]/page_content.tsx new file mode 100644 index 0000000..e1211af --- /dev/null +++ b/app/app/commander/card/[card_name]/page_content.tsx @@ -0,0 +1,118 @@ +'use client' + +import { useEffect, useRef, useState } from 'react' +import { CardGroup } from '@/components/ui/card-group' + +import { Spinner } from '@/components/ui/spinner' +import {PlaneswalkerIcon, SorceryIcon, InstantIcon, CreatureIcon, EnchantmentIcon, LandIcon, ArtifactIcon} from '@/components/ui/symbols-icons' +import { MTGCard } from '@/components/ui/mtg-card' + +interface PageContentProps { + card_name: string +} + +interface commanderCardData { + name: string, + normal_image: string, + nbr_decks: number, + total_decks: number, + price: string, + cardmarket_uri: string, +} + +export default function PageContent({card_name}: PageContentProps) { + const [highSynergyList, setHighSynergyList] = useState([]) + const [creatureList, setCreatureList] = useState([]) + const [planeswalkerList, setPlaneswalkerList] = useState([]) + const [instantList, setInstantList] = useState([]) + const [sorceryList, setSorceryList] = useState([]) + const [artifactList, setArtifactList] = useState([]) + const [enchantmentList, setEnchantmentList] = useState([]) + const [landList, setLandList] = useState([]) + const [commanderCard, setCommanderCard] = useState(null) + + const [loading, setLoading] = useState(true) + + const [scrollState, setScrollState] = useState("commander") + const CardListRef = useRef(null) + + useEffect(() => { + fetch('/api/json/card-commander/'+card_name+'.json').then((res) => { + if(res.status == 200) { + res.json().then((data) => { + setCommanderCard(data["card_data"]) + setHighSynergyList(data["high_synergy"]) + setCreatureList(data["creature"]) + setPlaneswalkerList(data["planeswalker"]) + setSorceryList(data["sorcery"]) + setInstantList(data["instant"]) + setEnchantmentList(data["enchantment"]) + setLandList(data["land"]) + setArtifactList(data["artifact"]) + setLoading(false) + console.log(data) + }) + } + }) + }, []) + + useEffect(() => { + const handleScroll = () => { + const windowHeight = window.innerHeight + const TOP_MARGIN = 0.1 + const BOTTOM_MARGIN = 0.2 + const card_children = CardListRef.current?.children + if (card_children) { + for (const child of card_children){ + const targetBounds = child.getBoundingClientRect() + if( targetBounds.bottom > windowHeight * TOP_MARGIN && targetBounds.top < windowHeight * ( 1 - BOTTOM_MARGIN ) ) { + setScrollState(child.id) + break + } + } + } + }; + + // Add event listener to the window + window.addEventListener('scroll', handleScroll); + + // Remove event listener when the component is unmounted + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, []); + return ( +
+
+
+ { loading && ( )} + { !loading && ( +
+
+ +
+ + + + + + + + +
+ )} +
+
+ +
+ ); +} diff --git a/app/app/commander/top/page.tsx b/app/app/commander/top/page.tsx index 1851743..58e6c3e 100644 --- a/app/app/commander/top/page.tsx +++ b/app/app/commander/top/page.tsx @@ -12,7 +12,7 @@ export default function Home() { fetch('/api/json/commander/top.json').then((res) => { if(res.status == 200) { res.json().then((data) => { - const limit = 20 + const limit = 100 setCommanderCardList(data.slice(0,limit)) setLoading(false) console.log(data) @@ -24,7 +24,7 @@ export default function Home() {
{ loading && ( )} - { !loading && ( )} + { !loading && ( )}
); diff --git a/app/components/ui/card-group.tsx b/app/components/ui/card-group.tsx index a18aad1..b2bef28 100644 --- a/app/components/ui/card-group.tsx +++ b/app/components/ui/card-group.tsx @@ -1,18 +1,19 @@ import * as React from "react" import { cn } from "@/lib/utils" -import { MTGCardProps } from "@/components/ui/mtg-card" import { MTGCard } from '@/components/ui/mtg-card' interface carte_from_stats { id: string, name: string, normal_image: string, + url: string, sanitized_name: string, nbr_decks: number, total_decks: number, percent_decks: number, price: string, + synergy?: number cardmarket_uri: string } @@ -23,11 +24,12 @@ interface CardGroupProps { showPrice?: boolean, showStats?: boolean, showPercent?: boolean, + commanderUrl?: boolean, id?: string, Icon?: any } -const CardGroup = ({ className, groupName, cards, showPrice=true, showStats=true,showPercent=true, id, Icon}: CardGroupProps) => { +const CardGroup = ({ className, groupName, cards, showPrice=true, showStats=true,showPercent=true, commanderUrl=false, id, Icon}: CardGroupProps) => { return (
@@ -39,9 +41,11 @@ const CardGroup = ({ className, groupName, cards, showPrice=true, showStats=true
- - {cards.map((card: carte_from_stats) => ( - + {cards.length == 0 && ( + Pas de cartes... + )} + {cards.length > 0 && cards.map((card: carte_from_stats) => ( + ))}
diff --git a/app/components/ui/mtg-card.tsx b/app/components/ui/mtg-card.tsx index 92f83f3..19e332d 100644 --- a/app/components/ui/mtg-card.tsx +++ b/app/components/ui/mtg-card.tsx @@ -4,33 +4,35 @@ import { cn } from "@/lib/utils" import { Spinner } from "./spinner" interface MTGCardProps { - className?: string, - imageURI: string, - cardname: string, - url: string, - nbrDecks?: number, - totalDecks?: number, - percentDecks?: number, - price?: string, + className?: string, + imageURI: string, + cardname: string, + url?: string, + nbrDecks?: number, + totalDecks?: number, + percentDecks?: number, + synergy?: number, + price?: string, cardmarketURI?: string } -const MTGCard = ({ className, imageURI, cardname, url, nbrDecks, totalDecks, percentDecks, price, cardmarketURI }: MTGCardProps) => { +const MTGCard = ({ className, imageURI, cardname, url, nbrDecks, totalDecks, percentDecks, price, synergy, cardmarketURI }: MTGCardProps) => { const [loaded, setLoaded] = React.useState(false) return ( -
{!loaded &&
} - {setLoaded(true)}} loading="lazy" /> + {setLoaded(true)}} loading="lazy" />
{ price != undefined && ( {price}€ @@ -42,8 +44,9 @@ const MTGCard = ({ className, imageURI, cardname, url, nbrDecks, totalDecks, per { percentDecks != undefined && ( sur {totalDecks} ({percentDecks}%))} )} + { synergy != undefined && ( {synergy}% Synergie )}
-
+ )} MTGCard.displayName = "MTGCard" diff --git a/app/components/ui/symbols-icons.tsx b/app/components/ui/symbols-icons.tsx index a39a235..d16c311 100644 --- a/app/components/ui/symbols-icons.tsx +++ b/app/components/ui/symbols-icons.tsx @@ -4,6 +4,12 @@ interface SymbolsIconProps { className: string } +const CommanderIcon = ({ className }: SymbolsIconProps) => { + return ( + + )} +CommanderIcon.displayName = "CommanderIcon" + const PlaneswalkerIcon = ({ className }: SymbolsIconProps) => { return ( @@ -46,4 +52,4 @@ const LandIcon = ({ className }: SymbolsIconProps) => { )} LandIcon.displayName = "LandIcon" -export { PlaneswalkerIcon, SorceryIcon, InstantIcon, CreatureIcon, ArtifactIcon, EnchantmentIcon, LandIcon } +export { CommanderIcon, PlaneswalkerIcon, SorceryIcon, InstantIcon, CreatureIcon, ArtifactIcon, EnchantmentIcon, LandIcon } diff --git a/app/public/assets/commander.svg b/app/public/assets/commander.svg new file mode 100644 index 0000000..98b17f6 --- /dev/null +++ b/app/public/assets/commander.svg @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/app/tools/createJson.mjs b/app/tools/createJson.mjs index 3b63de8..312e79f 100644 --- a/app/tools/createJson.mjs +++ b/app/tools/createJson.mjs @@ -61,7 +61,19 @@ async function createJson() { include: { cards: { include: { - decks: true, + decks: { + include: { + deck: { + include: { + commander: { + include: { + decks_as_commander: true, + } + }, + }, + }, + } + }, decks_as_commander: true, } } @@ -88,6 +100,7 @@ async function createJson() { commanderData[colorName] = [] } let bset_cards_data_export = {} + let commander_details_export = {} bsets.forEach((bset) => { let icons = [] @@ -124,6 +137,7 @@ async function createJson() { let card_object = { "name": card.name, "sanitized_name": card.sanitized_name, + "url": card.set_code + "-" + card.sanitized_name, "normal_image": card.normal_image, "small_image": card.small_image, "type": card.type, @@ -135,19 +149,46 @@ async function createJson() { "color_identity": card.color_identity, } - card_object.percent_decks = (card_object.total_decks != 0) ? parseInt(10000 * (card_object.nbr_decks / card_object.total_decks)) / 100 : 0 + card_object.percent_decks = (card_object.total_decks != 0) ? parseInt(100 * (card_object.nbr_decks / card_object.total_decks)) : 0 bset_cards_data_export[bset.sanitized_name][card.type].push(card_object) + + card.decks.forEach((deck) => { + const card_url_name = deck.deck.commander.set_code + "-" + deck.deck.commander.sanitized_name + if(!Object.keys(commander_details_export).includes(card_url_name)) { + commander_details_export[card_url_name] = { + "card_data": deck.deck.commander, + "creature": {}, + "land": {}, + "enchantment": {}, + "sorcery": {}, + "instant": {}, + "planeswalker": {}, + "artifact": {}, + } + commander_details_export[card_url_name].card_data.nbr_decks = deck.deck.commander.decks_as_commander.length + } + + if(!Object.keys(commander_details_export[card_url_name][card.type]).includes(card.sanitized_name)) { + let card_synergy_object = structuredClone(card_object) + card_synergy_object.commander_total = 0 + commander_details_export[card_url_name][card.type][card.sanitized_name] = card_synergy_object + } + + commander_details_export[card_url_name][card.type][card.sanitized_name].commander_total += 1 + }) + if(card.can_be_commander) { let commander_card_object = structuredClone(card_object) commander_card_object.total_decks = bset.decks.length commander_card_object.nbr_decks = card.decks_as_commander != undefined ? card.decks_as_commander.length : 0 - commander_card_object.percent_decks = (commander_card_object.total_decks != 0) ? parseInt(10000 * (commander_card_object.nbr_decks / commander_card_object.total_decks)) / 100 : 0 + commander_card_object.percent_decks = (commander_card_object.total_decks != 0) ? parseInt(100 * (commander_card_object.nbr_decks / commander_card_object.total_decks)) : 0 bset_cards_data_export[bset.sanitized_name]["commander"].push(commander_card_object) const colorName = getColorName(card.color_identity) + if(colorName != "") { commanderData[colorName].push(commander_card_object) commanderData["top"].push(commander_card_object) @@ -164,12 +205,31 @@ async function createJson() { writeFileSync(import.meta.dirname + "/../data/commander/" + index + ".json",JSON.stringify(JSONToWrite), 'utf8') } + for (const index of Object.keys(commander_details_export)) { + let JSONToWrite = structuredClone(commander_details_export[index]) + JSONToWrite["high_synergy"] = [] + for (const type of ["creature","land","enchantment","sorcery","instant","planeswalker","artifact"]) { + JSONToWrite[type] = [] + for (const card_key of Object.keys(commander_details_export[index][type])) { + let card_object_synergy = commander_details_export[index][type][card_key] + card_object_synergy.synergy = parseInt(100 * (card_object_synergy.commander_total / commander_details_export[index].card_data.nbr_decks) - card_object_synergy.percent_decks) + JSONToWrite[type].push(card_object_synergy) + } + JSONToWrite[type].sort((a,b) => b.synergy - a.synergy || b.percent_decks - a.percent_decks || b.nbr_decks - a.nbr_decks) + JSONToWrite["high_synergy"] = [...JSONToWrite["high_synergy"], ...JSONToWrite[type]] + } + + JSONToWrite["high_synergy"].sort((a,b) => b.synergy - a.synergy || b.percent_decks - a.percent_decks || b.nbr_decks - a.nbr_decks) + JSONToWrite["high_synergy"] = JSONToWrite["high_synergy"].slice(0,15) + writeFileSync(import.meta.dirname + "/../data/card-commander/" + index + ".json",JSON.stringify(JSONToWrite), 'utf8') + } + for (const index of Object.keys(bset_cards_data_export)) { let JSONToWrite = bset_cards_data_export[index] for (const type of Object.keys(JSONToWrite)) { JSONToWrite[type] = JSONToWrite[type].sort((a,b) => b.percent_decks - a.percent_decks || b.nbr_decks - a.nbr_decks) } - writeFileSync(import.meta.dirname + "/../data/bset/" + index + ".json",JSON.stringify(JSONToWrite), 'utf8') + writeFileSync(import.meta.dirname + "/../data/bset/" + index + ".json",JSON.stringify(JSONToWrite), 'utf8') } const end = performance.now() console.log(`Time taken to generate stats is ${(end - start)/1000}s.`);