diff --git a/src/components/scene/banknote.tsx b/src/components/scene/banknote.tsx new file mode 100644 index 0000000..8a068ba --- /dev/null +++ b/src/components/scene/banknote.tsx @@ -0,0 +1,80 @@ +import * as THREE from "three"; +import { useLoader } from "@react-three/fiber"; +import { CoinProps } from "@/types/coin"; +import { useMemo } from "react"; + +export const BanknoteInstance = ({ + color, + obverse, + reverse, +}: CoinProps) => { + const obverseTexture: THREE.Texture = useLoader( + THREE.TextureLoader, + obverse.picture, + ); + const reverseTexture: THREE.Texture = useLoader( + THREE.TextureLoader, + reverse.picture, + ); + + const width = useMemo(() => { + try { + return 2*obverseTexture.source.data.width/obverseTexture.source.data.height + } catch { + return 4 + } + }, [obverseTexture]) + + return ( + <> + + + + + + + + + + + + + + + ); +}; diff --git a/src/pages/showcase-banknotes/[country].tsx b/src/pages/showcase-banknotes/[country].tsx new file mode 100644 index 0000000..968000a --- /dev/null +++ b/src/pages/showcase-banknotes/[country].tsx @@ -0,0 +1,234 @@ +import Head from "next/head"; +import { GetStaticPaths, GetStaticProps, NextPage } from "next"; +import { CoinProps } from "@/types/coin"; +import { Canvas, MeshProps, useFrame } from "@react-three/fiber"; +import { Dispatch, SetStateAction, Suspense, useRef, useState } from "react"; +import { Mesh } from "three"; +import { OrbitControls } from "@react-three/drei"; +import path from "path"; +import { readFile, readdir } from "fs/promises"; +import { BanknoteInstance } from "@/components/scene/banknote"; + +type Props = { + banknotes: CoinProps[]; +}; + +const SpinninBanknote = ({ + isSelected, + coinProps, + ...props +}: { coinProps: CoinProps; isSelected?: boolean } & MeshProps) => { + const ref = useRef(null!); + + useFrame((_state, delta) => { + if (isSelected) { + ref.current.rotation.y += delta; + } + }); + + return ( + + + + + + ); +}; + +const Row = ({ + banknotes, + selectedIndex, +}: Props & { selectedIndex: number }) => { + const ref = useRef(null!); + + useFrame(() => { + const xPosition = Math.round(10 * ref.current.position.x) / 10; + const target = -selectedIndex * 8; + + if (xPosition > target) { + ref.current.position.x -= Math.abs(xPosition - target) / 16; + } + + if (xPosition < target) { + ref.current.position.x += Math.abs(xPosition - target) / 16; + } + }); + + return ( + + {banknotes.map((coin, index) => ( + + + + ))} + + ); +}; + +const InfoBox = ({ + coin, + maxIndex, + selectIndex, +}: { + coin: CoinProps; + maxIndex: number; + selectIndex: Dispatch>; +}) => { + return ( +
+
+ {coin.issuer.name} +

{coin.title}

+

{coin.min_year + " - " + coin.max_year}

+ {(coin.count ?? 1) + " in collection"} +
+ +
+ + + +
+
+ ); +}; + +const CountryPage: NextPage = ({ banknotes }) => { + const [selectedIndex, selectIndex] = useState(0); + + return ( + <> + + Coins + + + + +
+ + + + + + + + + + + +
+ + ); +}; + +export const getStaticProps: GetStaticProps = async (ctx) => { + console.log(ctx.params?.country); + if (typeof ctx.params?.country !== "string") { + return { + notFound: true, + }; + } + + const coinIds = await readdir( + path.join("./public/banknotes", ctx.params?.country), + ); + const banknotes: CoinProps[] = []; + + for (const coinId of coinIds) { + const coin = await readFile( + path.join("./public/banknotes", ctx.params.country, coinId), + { encoding: "utf-8" }, + ); + + banknotes.push(JSON.parse(coin)); + } + + return { + props: { + banknotes: banknotes.sort((a, b) => a.min_year - b.min_year), + }, + }; +}; + +export const getStaticPaths: GetStaticPaths = async () => { + const countries = await readdir("./public/banknotes"); + + return { + fallback: false, + paths: countries.map((country) => ({ params: { country } })), + }; +}; + +export default CountryPage; diff --git a/src/pages/showcase-banknotes/index.tsx b/src/pages/showcase-banknotes/index.tsx new file mode 100644 index 0000000..512670c --- /dev/null +++ b/src/pages/showcase-banknotes/index.tsx @@ -0,0 +1,57 @@ +import Head from "next/head"; +import { GetStaticProps, NextPage } from "next"; +import { readdir } from "fs/promises"; + +type Props = { + countries: string[]; +}; + +const CountriesPage: NextPage = ({ countries }) => { + return ( + <> + + Coins + + + + +
+ {countries.map((country) => ( + + {country} + + ))} +
+ + ); +}; + +export const getStaticProps: GetStaticProps = async () => { + const countries = await readdir("./public/banknotes"); + + if (!Array.isArray(countries)) { + return { + notFound: true, + }; + } + + return { + props: { + countries, + }, + }; +}; + +export default CountriesPage; diff --git a/src/pages/showcase/index.tsx b/src/pages/showcase/index.tsx index c1fc02e..f37e6ce 100644 --- a/src/pages/showcase/index.tsx +++ b/src/pages/showcase/index.tsx @@ -23,6 +23,7 @@ const CountriesPage: NextPage = ({ countries }) => { position: "relative", display: "flex", flexDirection: "column", + minHeight: "100vh", gap: 16, padding: "32px 16px", }}