a much better layout, and general concept for the dev tool

This commit is contained in:
HP
2026-01-17 15:55:26 -05:00
parent 53278d94be
commit 9696f6cc33
4 changed files with 447 additions and 26 deletions

111
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"@fontsource/roboto": "^5.2.9", "@fontsource/roboto": "^5.2.9",
"@mui/icons-material": "^7.3.7", "@mui/icons-material": "^7.3.7",
"@mui/material": "^7.3.7", "@mui/material": "^7.3.7",
"@mui/x-tree-view": "^8.25.0",
"@react-three/drei": "^10.7.7", "@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.5.0", "@react-three/fiber": "^9.5.0",
"colyseus.js": "^0.16.22", "colyseus.js": "^0.16.22",
@@ -20,6 +21,7 @@
"pg": "^8.17.0", "pg": "^8.17.0",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"react-draggable": "^4.5.0",
"three": "^0.182.0" "three": "^0.182.0"
}, },
"devDependencies": { "devDependencies": {
@@ -271,6 +273,28 @@
"node": ">=6.9.0" "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": { "node_modules/@colyseus/httpie": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@colyseus/httpie/-/httpie-2.0.1.tgz", "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": "^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": { "node_modules/@fontsource/roboto": {
"version": "5.2.9", "version": "5.2.9",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.9.tgz", "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.9.tgz",
@@ -1548,6 +1578,67 @@
"integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==", "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==",
"license": "MIT" "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": { "node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12", "version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
@@ -6046,6 +6137,20 @@
"react": "^19.2.3" "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": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -6136,6 +6241,12 @@
"node": ">=0.10.0" "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": { "node_modules/resolve": {
"version": "1.22.11", "version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",

View File

@@ -14,6 +14,7 @@
"@fontsource/roboto": "^5.2.9", "@fontsource/roboto": "^5.2.9",
"@mui/icons-material": "^7.3.7", "@mui/icons-material": "^7.3.7",
"@mui/material": "^7.3.7", "@mui/material": "^7.3.7",
"@mui/x-tree-view": "^8.25.0",
"@react-three/drei": "^10.7.7", "@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.5.0", "@react-three/fiber": "^9.5.0",
"colyseus.js": "^0.16.22", "colyseus.js": "^0.16.22",
@@ -21,6 +22,7 @@
"pg": "^8.17.0", "pg": "^8.17.0",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"react-draggable": "^4.5.0",
"three": "^0.182.0" "three": "^0.182.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,18 +1,156 @@
'use client' 'use client'
import React from "react"; import React, { SyntheticEvent } from "react";
import styles from "@/styles/dev-tools.module.css" 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<EditorContextType>({
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(){ export default function Page(){
const [currentRecordNum, setCurrentRecordNum] = React.useState<number>(0);
const [currentDataId, setCurrentDataId] = React.useState<string>("");
const [editorMode, setEditorMode] = React.useState<EditorMode>("insert");
const [cards, setCards] = React.useState<Card[] | null>(null);
const [units, setUnits] = React.useState<Unit[] | null>(null);
const [error, setError] = React.useState<string | null>(null);
const [open, setOpen] = React.useState<boolean>(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 = (
<React.Fragment>
<Typography>
{error}
</Typography>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={handleClose}
>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
);
return ( return (
<div className={styles.maincontainer}> <EditorContext.Provider value={{
<div className={styles.leftmenucontainer}> currentRecordNum,
<LeftMenu /> currentDataId,
editorMode,
error,
cards,
units,
changeCurrentRecordNum,
changeCurrentDataId,
changeEditorMode,
changeError
}}>
<div className={styles.maincontainer}>
<div className={styles.leftmenucontainer}>
<LeftMenu />
</div>
<div className={styles.editorcontainer}>
<Editor />
</div>
</div> </div>
<div className={styles.editorcontainer}> {error && <Snackbar
<Editor /> open={open}
</div> autoHideDuration={6000}
</div> onClose={handleClose}
action={action}
/>}
</EditorContext.Provider>
) )
} }
@@ -25,42 +163,199 @@ function DynamicCard() {
} }
interface Theme{ interface ThemeSelection {
value: string; value: string;
label: 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(){ function LeftMenu(){
const unitTypes: string[] = ["minion", "enemy", "hero", "ally"];
const buildingTypes: string[] = ["enemy", "minion", "terrain", "support"]; const [themes, setThemes] = React.useState<ThemeSelection[]>([]); // get from the database
const [themes, setThemes] = React.useState<Theme[]>([]); // get from the database
const [menuMode, setMenuMode] = React.useState<"unit" | "card">("card"); const [menuMode, setMenuMode] = React.useState<"unit" | "card">("card");
const { currentRecordNum, } = useEditor();
const handleSelectAsset = (e: React.MouseEvent<Element, MouseEvent>) => {
console.log("Got e:", e)
}
return ( return (
<Container className={styles.leftmenu}> <Container className={styles.leftmenu}>
<Stack> <Stack >
Type <Typography id="assetTypeFilter">Asset Type</Typography>
<Select> <Select className="assetTypeFilter">
<option value=""> <MenuItem value="">All</MenuItem>
All types
</option>
{menuMode === "card" ? {menuMode === "card" ?
buildingTypes.map((opt) => (<option value={opt}>{opt[0].toUpperCase() + opt.slice(1)}</option>)) : buildingTypes.map((opt, i) => (<MenuItem value={opt} key={i}>{opt[0].toUpperCase() + opt.slice(1)}</MenuItem>)) :
unitTypes.map((opt) => (<option value={opt}>{opt[0].toUpperCase() + opt.slice(1)}</option>)) unitTypes.map((opt, i) => (<MenuItem value={opt} key={i}>{opt[0].toUpperCase() + opt.slice(1)}</MenuItem>))
} }
</Select> </Select>
Theme <InputLabel id="themeFilter">Theme</InputLabel>
<Select> <Select className="themeFilter" label="Theme" >
<MenuItem value="">All</MenuItem>
</Select> </Select>
</Stack> </Stack>
<Typography >
Current {menuMode}s created
</Typography>
<RichTreeView items={debugCard} onItemClick={handleSelectAsset}>
</RichTreeView>
</Container> </Container>
) )
} }
function Editor(){ function Editor(){
const { editorMode } = useEditor();
const canvasWrapperRef = React.useRef<HTMLDivElement | null>(null);
const canvasRef = React.useRef<HTMLCanvasElement | null>(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 ( return (
<Container className={styles.editor} maxWidth={false} disableGutters> <Container className={styles.editor} maxWidth={false} disableGutters>
yes
{editorMode === "insert" ? <CreateNewAsset type="building"/> : null}
<div
ref={canvasWrapperRef}
onDragOver={(e: React.DragEvent) => {
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" }}
>
<canvas
ref={canvasRef}
onDragOver={onDragOver}
onDrop={onDrop}
style={{ height: "100%", width: "100%" }}
/>
</div>
</Container> </Container>
) )
} }
function CreateNewAsset({ type }: { type: string }) {
return (
<div style={{ backgroundColor: "#F1E9DB", borderRadius: 20, height: 40, display: "flex", alignItems: "center", justifyContent: "center", columnGap: 20 }}>
{type === "building"
? buildingTypes.map((buildingType, i) => {
const label = buildingType[0].toUpperCase() + buildingType.slice(1);
return (
<div
key={i}
draggable
onDragStart={(e) => {
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" }}
>
<Chip sx={{ backgroundColor: "#5DB7DE" }} label={label} />
</div>
);
})
: unitTypes.map((unitType, i) => {
const label = unitType[0].toUpperCase() + unitType.slice(1);
return (
<div
key={i}
draggable
onDragStart={(e) => {
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" }}
>
<Chip sx={{ backgroundColor: "#5DB7DE" }} label={label} />
</div>
);
})}
</div>
);
}

View File

@@ -5,24 +5,34 @@
display: grid; display: grid;
grid-template-columns: 20% 1fr; grid-template-columns: 20% 1fr;
grid-template-areas: "stack"; grid-template-areas: "stack";
background-color: #01110;
} }
.leftmenucontainer { .leftmenucontainer {
grid-column: 1 / 2; grid-column: 1 / 2;
grid-row: 1 / 4; grid-row: 1 / 4;
width: 100%; width: 100%;
padding: 20px;
} }
.leftmenu { .leftmenu {
background-color: #235b79; background-color: #A39B8B;
height: 100%; height: 100%;
width: 100%; width: 100%;
border-radius: 40;
padding: 10px;
} }
.editor { .editor {
background-color: #1c3464; background-color: #716A5C;
height: 100%; height: 100%;
width: 100%; width: 100%;
border-radius: 40;
padding: 20px;
} }
.editorcontainer { .editorcontainer {
@@ -30,4 +40,7 @@
grid-column: 2 / 3; grid-column: 2 / 3;
height: 100%; height: 100%;
width: 100%; width: 100%;
padding: 20px;
} }