a much better layout, and general concept for the dev tool
This commit is contained in:
111
package-lock.json
generated
111
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -1,10 +1,141 @@
|
||||
'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<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(){
|
||||
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 (
|
||||
<EditorContext.Provider value={{
|
||||
currentRecordNum,
|
||||
currentDataId,
|
||||
editorMode,
|
||||
error,
|
||||
cards,
|
||||
units,
|
||||
|
||||
changeCurrentRecordNum,
|
||||
changeCurrentDataId,
|
||||
changeEditorMode,
|
||||
changeError
|
||||
}}>
|
||||
<div className={styles.maincontainer}>
|
||||
<div className={styles.leftmenucontainer}>
|
||||
<LeftMenu />
|
||||
@@ -13,6 +144,13 @@ export default function Page(){
|
||||
<Editor />
|
||||
</div>
|
||||
</div>
|
||||
{error && <Snackbar
|
||||
open={open}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleClose}
|
||||
action={action}
|
||||
/>}
|
||||
</EditorContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<Theme[]>([]); // get from the database
|
||||
|
||||
const [themes, setThemes] = React.useState<ThemeSelection[]>([]); // get from the database
|
||||
const [menuMode, setMenuMode] = React.useState<"unit" | "card">("card");
|
||||
|
||||
const { currentRecordNum, } = useEditor();
|
||||
|
||||
const handleSelectAsset = (e: React.MouseEvent<Element, MouseEvent>) => {
|
||||
console.log("Got e:", e)
|
||||
}
|
||||
return (
|
||||
<Container className={styles.leftmenu}>
|
||||
<Stack >
|
||||
Type
|
||||
<Select>
|
||||
<option value="">
|
||||
All types
|
||||
</option>
|
||||
<Typography id="assetTypeFilter">Asset Type</Typography>
|
||||
<Select className="assetTypeFilter">
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{menuMode === "card" ?
|
||||
buildingTypes.map((opt) => (<option value={opt}>{opt[0].toUpperCase() + opt.slice(1)}</option>)) :
|
||||
unitTypes.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, i) => (<MenuItem value={opt} key={i}>{opt[0].toUpperCase() + opt.slice(1)}</MenuItem>))
|
||||
}
|
||||
</Select>
|
||||
|
||||
Theme
|
||||
<Select>
|
||||
<InputLabel id="themeFilter">Theme</InputLabel>
|
||||
<Select className="themeFilter" label="Theme" >
|
||||
<MenuItem value="">All</MenuItem>
|
||||
</Select>
|
||||
|
||||
</Stack>
|
||||
<Typography >
|
||||
Current {menuMode}s created
|
||||
</Typography>
|
||||
<RichTreeView items={debugCard} onItemClick={handleSelectAsset}>
|
||||
</RichTreeView>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
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 (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user