diff --git a/package-lock.json b/package-lock.json index 88e4bf2..3d72b98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@fontsource/roboto": "^5.2.9", "@mui/icons-material": "^7.3.7", "@mui/material": "^7.3.7", + "@mui/x-tree-view": "^8.25.0", "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", "colyseus.js": "^0.16.22", @@ -20,6 +21,7 @@ "pg": "^8.17.0", "react": "19.2.3", "react-dom": "19.2.3", + "react-draggable": "^4.5.0", "three": "^0.182.0" }, "devDependencies": { @@ -271,6 +273,28 @@ "node": ">=6.9.0" } }, + "node_modules/@base-ui/utils": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.2.4.tgz", + "integrity": "sha512-smZwpMhjO29v+jrZusBSc5T+IJ3vBb9cjIiBjtKcvWmRj9Z4DWGVR3efr1eHR56/bqY5a4qyY9ElkOY5ljo3ng==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@floating-ui/utils": "^0.2.10", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "@types/react": "^17 || ^18 || ^19", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@colyseus/httpie": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@colyseus/httpie/-/httpie-2.0.1.tgz", @@ -634,6 +658,12 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@fontsource/roboto": { "version": "5.2.9", "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.9.tgz", @@ -1548,6 +1578,67 @@ "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==", "license": "MIT" }, + "node_modules/@mui/x-internals": { + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.25.0.tgz", + "integrity": "sha512-RKexkVaK3xvAeLBNeLAw6oJCsQrXkx7TYSRoSUmmJveydqOqoBbimv+nbc8PmL4UL0ShVNkaFL1YWY7kYCCXUA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@mui/x-tree-view": { + "version": "8.25.0", + "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-8.25.0.tgz", + "integrity": "sha512-0QBpK4d443dFrCIiR3QyNPFwh3vuYnaSIvIPGE3hUX+K7O+WSw7b62jcTOUJ5gKmdbKPef77dqC7AQj4JLNFkQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@base-ui/utils": "^0.2.3", + "@mui/utils": "^7.3.5", + "@mui/x-internals": "8.25.0", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -6046,6 +6137,20 @@ "react": "^19.2.3" } }, + "node_modules/react-draggable": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.5.0.tgz", + "integrity": "sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6136,6 +6241,12 @@ "node": ">=0.10.0" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", diff --git a/package.json b/package.json index 6f8cd6f..9e0670e 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@fontsource/roboto": "^5.2.9", "@mui/icons-material": "^7.3.7", "@mui/material": "^7.3.7", + "@mui/x-tree-view": "^8.25.0", "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", "colyseus.js": "^0.16.22", @@ -21,6 +22,7 @@ "pg": "^8.17.0", "react": "19.2.3", "react-dom": "19.2.3", + "react-draggable": "^4.5.0", "three": "^0.182.0" }, "devDependencies": { diff --git a/src/app/dev-tools/page.tsx b/src/app/dev-tools/page.tsx index 9f7fa2b..8ee7d7d 100644 --- a/src/app/dev-tools/page.tsx +++ b/src/app/dev-tools/page.tsx @@ -1,18 +1,156 @@ 'use client' -import React from "react"; +import React, { SyntheticEvent } from "react"; import styles from "@/styles/dev-tools.module.css" -import { Container, Select, Stack } from "@mui/material" +import { Container, Select, Stack, Chip, MenuItem, InputLabel, Snackbar, IconButton, SnackbarCloseReason, Typography } from "@mui/material" +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import CloseIcon from '@mui/icons-material/Close'; +import Draggable from 'react-draggable'; + +type EditorMode = "insert" | "update"; + +interface DBObject { + record_num: number + data_id: string; + change_timestamp: string; +} + +interface Unit extends DBObject { + +} + +interface Card extends DBObject { + +} + +interface EditorContextType { + editorMode: "insert" | "update"; + currentRecordNum: number | null; + currentDataId: string | null; + cards: Card[] | null; + units: Unit[] | null; + error: string | null, + + changeEditorMode?: (m: string) => void; + changeCurrentRecordNum?: (n: number) => void; + changeCurrentDataId?: (n: string) => void; + changeError?: (e: string) => void; +} +const EditorContext = React.createContext({ + currentRecordNum: null, + currentDataId: null, + editorMode: "insert", + cards: null, + units: null, + error: null, +}) + +function useEditor() { + const ctx = React.useContext(EditorContext); + if(!ctx){ + throw new Error("Error: use Editor must be used within an EditorContext") + } + + return ctx; +} + + +const unitTypes: string[] = ["minion", "enemy", "hero", "ally"]; +const buildingTypes: string[] = ["enemy", "minion", "terrain", "support"]; + export default function Page(){ + const [currentRecordNum, setCurrentRecordNum] = React.useState(0); + const [currentDataId, setCurrentDataId] = React.useState(""); + const [editorMode, setEditorMode] = React.useState("insert"); + const [cards, setCards] = React.useState(null); + const [units, setUnits] = React.useState(null); + const [error, setError] = React.useState(null); + const [open, setOpen] = React.useState(false); + + const changeCurrentRecordNum = (n: number) => setCurrentRecordNum(n) + const changeCurrentDataId = (id: string) => setCurrentDataId(id) + const changeEditorMode = (m: string) =>{ + if(m !== "insert" && m !== "update") return + setEditorMode(m) + } + const changeError = (e: string) => setError(e); + // get all of the cards, or units + React.useEffect(() => { + const getCards = async () => { + try{ + // fetch our db + }catch (error) { + + } + } + + const getUnits = async () => { + try{ + + }catch (error) { + + } + } + getCards(); + getUnits(); + }, []) + + const handleClose = ( + event: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason, + ) => { + if (reason === 'clickaway') { + return; + } + + setOpen(false); + }; + + const action = ( + + + {error} + + + + + + ); + return ( -
-
- + +
+
+ +
+
+ +
-
- -
-
+ {error && } + ) } @@ -25,42 +163,199 @@ function DynamicCard() { } -interface Theme{ +interface ThemeSelection { value: string; label: string; } + + +const debugCard = [ + { + id: "terrain_forest", + label: "Forest", + children: [ + { + id: "terrain_haunted_forest", + label: "Haunted Forest", + children: [ + { + id: 'terrain_undead_candy_forest', + label: "Undead Candy Forest" + } + ] + } + ] + }, + { + id: "support_blacksmith", + label: "Forest", + children: [ + { + id: "support_terrain_haunted_forest", + label: "Haunted Forest", + children: [ + { + id: 'SUppror_terrain_undead_candy_forest', + label: "Undead Candy Forest" + } + ] + } + ] + }, + { + id: "enemy_graveyard", + label: "Forest", + children: [ + { + id: "enemy_terrain_haunted_forest", + label: "Haunted Forest", + children: [ + { + id: 'enemy_terrain_undead_candy_forest', + label: "Undead Candy Forest" + } + ] + } + ] + } +] + function LeftMenu(){ - const unitTypes: string[] = ["minion", "enemy", "hero", "ally"]; - const buildingTypes: string[] = ["enemy", "minion", "terrain", "support"]; - const [themes, setThemes] = React.useState([]); // get from the database + + const [themes, setThemes] = React.useState([]); // get from the database const [menuMode, setMenuMode] = React.useState<"unit" | "card">("card"); + + const { currentRecordNum, } = useEditor(); + + const handleSelectAsset = (e: React.MouseEvent) => { + console.log("Got e:", e) + } return ( - - Type - + All {menuMode === "card" ? - buildingTypes.map((opt) => ()) : - unitTypes.map((opt) => ()) + buildingTypes.map((opt, i) => ({opt[0].toUpperCase() + opt.slice(1)})) : + unitTypes.map((opt, i) => ({opt[0].toUpperCase() + opt.slice(1)})) } - Theme - + All + + + Current {menuMode}s created + + + ) } function Editor(){ + const { editorMode } = useEditor(); + + const canvasWrapperRef = React.useRef(null); + const canvasRef = React.useRef(null); + + const onMouseUp = (e: React.MouseEvent) => { + + console.log("event for mouse up", e) + } + + const onMouseDown = (e: React.MouseEvent) => { + + console.log("event for mouse down", e) + } + + const onDragOver = (e: React.DragEvent) => { + e.preventDefault() + //console.log("event for drag over:", e) + } + + const onDrop = (e: React.DragEvent) => { + e.preventDefault(); + const raw = e.dataTransfer.getData("text/plain"); + console.log("DROP canvas raw:", raw); + // create a box? + } + return ( - yes + + {editorMode === "insert" ? : null} +
{ + e.preventDefault(); + e.dataTransfer.dropEffect = "copy"; + }} + onDrop={(e: React.DragEvent) => { + e.preventDefault(); + const raw = e.dataTransfer.getData("text/plain"); + console.log("DROP wrapper raw:", raw); + }} + style={{ height: "100%", width: "100%", position: "relative" }} + > + +
+
) } + +function CreateNewAsset({ type }: { type: string }) { + return ( +
+ {type === "building" + ? buildingTypes.map((buildingType, i) => { + const label = buildingType[0].toUpperCase() + buildingType.slice(1); + + return ( +
{ + console.log("DRAG START", buildingType); + const payload = { kind: "building", type: buildingType, label, w: 160, h: 90 }; + e.dataTransfer.setData("text/plain", JSON.stringify(payload)); + e.dataTransfer.effectAllowed = "copy"; + }} + style={{ display: "inline-flex" }} + > + +
+ ); + }) + : unitTypes.map((unitType, i) => { + const label = unitType[0].toUpperCase() + unitType.slice(1); + return ( +
{ + const payload = { kind: "unit", type: unitType, label, w: 140, h: 80 }; + e.dataTransfer.setData("text/plain", JSON.stringify(payload)); + e.dataTransfer.effectAllowed = "copy"; + }} + style={{ display: "inline-flex" }} + > + +
+ ); + })} +
+ ); +} + diff --git a/src/styles/dev-tools.module.css b/src/styles/dev-tools.module.css index f632a18..f406b94 100644 --- a/src/styles/dev-tools.module.css +++ b/src/styles/dev-tools.module.css @@ -5,24 +5,34 @@ display: grid; grid-template-columns: 20% 1fr; grid-template-areas: "stack"; + + background-color: #01110; } .leftmenucontainer { grid-column: 1 / 2; grid-row: 1 / 4; width: 100%; + padding: 20px; + } .leftmenu { - background-color: #235b79; + background-color: #A39B8B; height: 100%; width: 100%; + border-radius: 40; + + padding: 10px; } .editor { - background-color: #1c3464; + background-color: #716A5C; height: 100%; width: 100%; + border-radius: 40; + + padding: 20px; } .editorcontainer { @@ -30,4 +40,7 @@ grid-column: 2 / 3; height: 100%; width: 100%; + + + padding: 20px; }