diff --git a/components/BoardCanvas/Tokens/index.tsx b/components/BoardCanvas/Tokens/index.tsx new file mode 100644 index 0000000..0ada566 --- /dev/null +++ b/components/BoardCanvas/Tokens/index.tsx @@ -0,0 +1,28 @@ +const Image = ({ imageUrl }: { imageUrl: string }) => + ``; + +const Circle = ({ + color, + size, +}: { + color?: string; + size?: number; +}) => ` + +`; + +export const TokenIcon = { + Image, + Circle, +}; + +/** + * WIP + * + * - cannot have more than 2 props with defaults. If one is not empty, then the default on the other will break + * - need a cleaner version for adding svgs with this function wrapper. Too much repetition + * - how can I create an enum for this + * + * */ diff --git a/components/BoardCanvas/defaultTokenImages.ts b/components/BoardCanvas/defaultTokenImages.ts new file mode 100644 index 0000000..d84961e --- /dev/null +++ b/components/BoardCanvas/defaultTokenImages.ts @@ -0,0 +1,21 @@ +/** + * Default Tokens for the board! + * + * To add to this list, add the file to /public/tokens/... + * + * Then add the filename to IMAGES + * */ + +const IMAGES = [ + "Alien.svg", + "BrickWall.svg", + "CloudFog.svg", + "Fire.svg", + "Flag.svg", + "HandShield.svg", + "ShieldHalf.svg", + "Totem.svg", + "Trap.svg", +]; + +export const DEFAULT_TOKEN_IMAGES = IMAGES.map((img) => `/tokens/${img}`); diff --git a/components/BoardCanvas/index.tsx b/components/BoardCanvas/index.tsx index a123068..5c5c380 100644 --- a/components/BoardCanvas/index.tsx +++ b/components/BoardCanvas/index.tsx @@ -53,7 +53,14 @@ export const BoardCanvas: React.FC = ({ backgroundColor: "ghostwhite", }} > - + ); diff --git a/components/BoardCanvas/useCanvas.ts b/components/BoardCanvas/useCanvas.tsx similarity index 78% rename from components/BoardCanvas/useCanvas.ts rename to components/BoardCanvas/useCanvas.tsx index 4edc065..40ae561 100644 --- a/components/BoardCanvas/useCanvas.ts +++ b/components/BoardCanvas/useCanvas.tsx @@ -1,6 +1,7 @@ import * as d3 from "d3"; import { MutableRefObject, RefObject, useEffect } from "react"; import { PositionType } from "../Positions/position.type"; +import { TokenIcon } from "./Tokens"; type CanvasProps = { canvasRef: RefObject; @@ -44,14 +45,16 @@ export const useCanvas = ({ } const g = d3.select(gRef.current); - g.selectAll("circle") + g.selectAll("g") .data(data) - .join("circle") - .attr("cx", ({ x }) => x) - .attr("cy", ({ y }) => y) - .attr("r", ({ r }) => (r ? r : 15)) - .attr("fill", ({ color }) => color ?? "black") - // TODO: replace this to limit which token the user can control + .join((enter) => enter.append("g")) + .attr("transform", (d) => `translate(${d.x}, ${d.y})`) + .html((props) => + props?.imageUrl + ? TokenIcon.Image({ imageUrl: props.imageUrl }) + : TokenIcon.Circle({ color: props.color, size: props.r }), + ) + // NOTE: Bellow attr & filter shows and limits user to moving their own tokens .attr("opacity", ({ id }) => (id.includes(self as string) ? 1 : 0.75)) .filter(({ id }) => { const isSidekick = id.includes("_"); @@ -60,7 +63,7 @@ export const useCanvas = ({ }) .call( d3 - .drag() + .drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended) @@ -82,7 +85,13 @@ export const useCanvas = ({ }), ); - function dragstarted(e: { target: any }) { + function dragstarted(e: any, d: PositionType) { + const isSelf = d?.id === self; + const isSidekick = d?.id.includes("_"); + const isSidekickSelf = d?.id.split("_")[0] === self; + if (isSidekick && !isSidekickSelf) return; + if (!isSelf) return; + d3.select(e.target).raise(); g.attr("cursor", "grabbing"); //@ts-expect-error: implicit any @@ -98,11 +107,6 @@ export const useCanvas = ({ event: DragEvent & { subject: PositionType }, d: PositionType, ) { - //@ts-expect-error: implicit any - d3.select(this) - .attr("cx", (d.x = event.x)) - .attr("cy", (d.y = event.y)); - if (!move) return; move({ diff --git a/components/Game/game.modal-template.tsx b/components/Game/game.modal-template.tsx index 9761816..b68c10b 100644 --- a/components/Game/game.modal-template.tsx +++ b/components/Game/game.modal-template.tsx @@ -121,7 +121,6 @@ export const ModalContainer: React.FC = ({ }), ); - console.log({ modalType, isOpen }); return ( <> !isCommit && onClose()}> diff --git a/components/Positions/position.modal.tsx b/components/Positions/position.modal.tsx index 7320b6a..974097d 100644 --- a/components/Positions/position.modal.tsx +++ b/components/Positions/position.modal.tsx @@ -10,15 +10,30 @@ import { Flex, ModalFooter, Button, + VStack, + Divider, + Grid, + Input, + Menu, + MenuButton, + MenuList, + MenuItem, + Image, + FormLabel, + HStack, } from "@chakra-ui/react"; -import { FC, useCallback, useState } from "react"; +import { Dispatch, FC, SetStateAction, useCallback, useState } from "react"; import { MoonIcon, PlusSquareIcon, SunIcon } from "@chakra-ui/icons"; import { useWebGame } from "@/lib/contexts/WebGameProvider"; +import { MdUpload as IconUpload } from "react-icons/md"; + //@ts-ignore import { CirclePicker } from "react-color"; -import { PositionType, Size } from "./position.type"; +import { PositionType } from "./position.type"; import { useRouter } from "next/router"; +import { toast } from "react-hot-toast"; +import { DEFAULT_TOKEN_IMAGES } from "../BoardCanvas/defaultTokenImages"; export const PositionModal: FC<{ isOpen: boolean; @@ -37,15 +52,15 @@ export const PositionModal: FC<{ name as keyof typeof gamePositions.content ] as PositionType; + const [images, setImages] = useState<{ id: string; url: string }[]>([]); + const [selectedColor, setSelectedColor] = useState( selectedPosition?.color ?? "#000", ); - const [selectedSize, setSelectedSize] = useState("lg"); const [sidekicks, setSidekicks] = useState< PositionType["sidekicks"] | undefined >(selectedPosition?.sidekicks); - const setSize = (size: Size) => - size === "lg" ? 2 : size === "md" ? 1.65 : 1.35; + const handleColorChange = ({ hex }: { hex: string }) => setSelectedColor(hex); const _setGamePosition = (props: PositionType) => { @@ -60,10 +75,18 @@ export const PositionModal: FC<{ setGamePosition({ ...selected, color: selectedColor, - r: selectedSize === "lg" ? 20 : selectedSize === "md" ? 15 : 10, + imageUrl: + images?.find((img) => img.id === selected?.id)?.url ?? + selected?.imageUrl ?? + undefined, sidekicks: sidekicks?.map((kick) => ({ ...kick, color: selectedColor, + r: kick?.r ?? 50, + imageUrl: + images?.find((img) => img.id === kick?.id)?.url ?? + kick?.imageUrl ?? + undefined, })), }); } @@ -82,10 +105,21 @@ export const PositionModal: FC<{ }, ]; }); + + toast.success("Preparing new token. Click apply to confirm changes"); } + const gameKickIds = selectedPosition?.sidekicks?.map((kick) => kick.id); + return ( - + { + setSidekicks(selectedPosition?.sidekicks); + setImages([]); + onClose(); + }} + > @@ -98,46 +132,117 @@ export const PositionModal: FC<{ - - - setSelectedSize((prev) => - prev === "lg" ? "md" : prev === "md" ? "sm" : "lg", - ) - } - /> - {sidekicks?.map((kick) => ( - + + + - ))} + {selectedPosition?.sidekicks?.map((kick) => ( + + ))} + + {sidekicks + ?.filter((kick) => !gameKickIds?.includes(kick.id)) + .map((kick) => ( + + ))} + + + - - - - - + + Clicking apply will reset token positions + ); }; + +const TokenPreview = ({ + token, + selectedColor, + setImages, +}: { + token: PositionType; + selectedColor: string; + setImages: Dispatch>; +}) => { + const setImage = (url: string) => { + setImages((prev) => { + return [...prev?.filter((p) => p.id !== token.id), { id: token.id, url }]; + }); + toast.success("New Image Prepared. Click Apply to confirm changes"); + }; + + const [imageUrl, setImageUrl] = useState(""); + + return ( + + {token?.imageUrl ? ( + + ) : ( + + )} + + + Tokens + + + Add Token (via url) + + {imageUrl && } + + setImageUrl(e.target.value)} + /> + + + + + Default Tokens + + {DEFAULT_TOKEN_IMAGES.map((img) => ( + setImage(img)}> + + + ))} + + + + ); +}; diff --git a/components/Positions/position.type.ts b/components/Positions/position.type.ts index 3abe296..46e02df 100644 --- a/components/Positions/position.type.ts +++ b/components/Positions/position.type.ts @@ -7,6 +7,7 @@ export type PositionType = { tokenSize?: Size; color?: string; sidekicks?: Sidekick[]; + imageUrl?: string; }; type Sidekick = Omit; diff --git a/public/tokens/Alien.svg b/public/tokens/Alien.svg new file mode 100644 index 0000000..96bfd17 --- /dev/null +++ b/public/tokens/Alien.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/tokens/BrickWall.svg b/public/tokens/BrickWall.svg new file mode 100644 index 0000000..2955fe7 --- /dev/null +++ b/public/tokens/BrickWall.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/tokens/CloudFog.svg b/public/tokens/CloudFog.svg new file mode 100644 index 0000000..f285ca3 --- /dev/null +++ b/public/tokens/CloudFog.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/tokens/Fire.svg b/public/tokens/Fire.svg new file mode 100644 index 0000000..e20cf65 --- /dev/null +++ b/public/tokens/Fire.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/tokens/Flag.svg b/public/tokens/Flag.svg new file mode 100644 index 0000000..12b1710 --- /dev/null +++ b/public/tokens/Flag.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/tokens/HandShield.svg b/public/tokens/HandShield.svg new file mode 100644 index 0000000..4775a39 --- /dev/null +++ b/public/tokens/HandShield.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/tokens/ShieldHalf.svg b/public/tokens/ShieldHalf.svg new file mode 100644 index 0000000..3061fd3 --- /dev/null +++ b/public/tokens/ShieldHalf.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/tokens/Totem.svg b/public/tokens/Totem.svg new file mode 100644 index 0000000..0e0c879 --- /dev/null +++ b/public/tokens/Totem.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/tokens/Trap.svg b/public/tokens/Trap.svg new file mode 100644 index 0000000..5d8c5f5 --- /dev/null +++ b/public/tokens/Trap.svg @@ -0,0 +1,5 @@ + + +