From aa4578cc2e45df5a4b4ef2769c805ce2c51fc8c8 Mon Sep 17 00:00:00 2001 From: HP <*bang*> Date: Mon, 19 Jan 2026 03:06:22 -0500 Subject: [PATCH] organized the dev tools dahsboard componetns and connected them tothe backend slightly. Still working on how to display combos as a tree, then the inspector should follow. --- src/app/api/dev/vor/cards/[data_id]/route.ts | 38 +++ src/app/api/dev/vor/cards/route.ts | 18 ++ src/app/api/products.ts | 3 +- src/app/dev-tools/page.tsx | 322 ++++--------------- src/components/dev-tools/Editor.tsx | 63 ++++ src/components/dev-tools/LeftMenu.tsx | 62 ++++ src/contexts/EditorContext.tsx | 28 ++ src/hooks/useEditor.ts | 11 + src/sql/cards.sql | 16 +- src/types/dev-tools-extra.ts | 5 + src/types/dev-tools.d.ts | 28 ++ 11 files changed, 325 insertions(+), 269 deletions(-) create mode 100644 src/app/api/dev/vor/cards/[data_id]/route.ts create mode 100644 src/app/api/dev/vor/cards/route.ts create mode 100644 src/components/dev-tools/Editor.tsx create mode 100644 src/components/dev-tools/LeftMenu.tsx create mode 100644 src/contexts/EditorContext.tsx create mode 100644 src/hooks/useEditor.ts create mode 100644 src/types/dev-tools-extra.ts create mode 100644 src/types/dev-tools.d.ts diff --git a/src/app/api/dev/vor/cards/[data_id]/route.ts b/src/app/api/dev/vor/cards/[data_id]/route.ts new file mode 100644 index 0000000..cd55639 --- /dev/null +++ b/src/app/api/dev/vor/cards/[data_id]/route.ts @@ -0,0 +1,38 @@ +import { NextRequest, NextResponse } from "next/server"; +import { Pool } from "pg"; + +const db = new Pool({ + connectionString: process.env.DB_STRING! +}) + +export async function GET( + req: NextRequest, + { params }: { params: Promise<{ data_id: string }> } +){ + try{ + const { data_id } = await params; + if(!data_id){ + return NextResponse.json({ message: "Invalid search parameter:" + data_id }, { status: 400, statusText: "Malformed request" }) + } + + const combosRes = await db.query( + `WITH RECURSIVE t AS ( + SELECT parent_data_id, combo_data_id, result_data_id, 1 AS depth + FROM combos + WHERE parent_data_id = $1 + UNION ALL + SELECT c.parent_data_id, c.combo_data_id, c.result_data_id, t.depth + 1 + FROM combos c + JOIN t on c.parent_data_id = t.result_data_id + WHERE t.depth < 3 + ) + SELECT * FROM t ORDER BY depth, parent_data_id, combo_data_id;`, [data_id] + ); + return NextResponse.json(combosRes.rows ?? []) + + }catch(error){ + return NextResponse.json({ message: "Could not GET combos: " + error }, { status: 500, statusText: "Serverside exception" }) + } + + +} diff --git a/src/app/api/dev/vor/cards/route.ts b/src/app/api/dev/vor/cards/route.ts new file mode 100644 index 0000000..119722b --- /dev/null +++ b/src/app/api/dev/vor/cards/route.ts @@ -0,0 +1,18 @@ +import { NextResponse } from "next/server" +import { Pool } from "pg" + +const db = new Pool({ + connectionString: process.env.DB_STRING! +}) + +export async function GET(){ + try{ + const result = await db.query("SELECT * FROM cards;") + console.log("Got some combos") + return NextResponse.json(result.rows ?? []) + }catch(err){ + console.error("Could not GET cards:", err) + return NextResponse.json( { message: "Serverside exception occurred " + err }, { status: 500 } ) + } +} + diff --git a/src/app/api/products.ts b/src/app/api/products.ts index 997ca9e..05533b4 100644 --- a/src/app/api/products.ts +++ b/src/app/api/products.ts @@ -19,8 +19,7 @@ export async function GET(req: NextRequest){ export async function POST(req: NextRequest){ try{ - const body = (await req.json()) as { name: string, expiration_date: string } - console.log("GOT BODY:", body) + const { name, expiration_date } = (await req.json()) as { name: string, expiration_date: string } const result = await db.query("INSERT INTO products (name, exp_date) VALUES ($1, $2) RETURNING record_num;", [name, expiration_date]); return NextResponse.json(result.rows[0].record_num ?? []); } catch(error){ diff --git a/src/app/dev-tools/page.tsx b/src/app/dev-tools/page.tsx index 8ee7d7d..5d12b36 100644 --- a/src/app/dev-tools/page.tsx +++ b/src/app/dev-tools/page.tsx @@ -1,67 +1,20 @@ 'use client' -import React, { SyntheticEvent } from "react"; +import React from "react"; import styles from "@/styles/dev-tools.module.css" -import { Container, Select, Stack, Chip, MenuItem, InputLabel, Snackbar, IconButton, SnackbarCloseReason, Typography } from "@mui/material" -import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { IconButton, Snackbar, SnackbarCloseReason, Typography } from "@mui/material" 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"]; +import { Card, Combo, EditorMode, Unit } from "@/types/dev-tools"; +import { EditorContext } from "@/contexts/EditorContext"; +import LeftMenu from "@/components/dev-tools/LeftMenu"; +import { useEditor } from "@/hooks/useEditor"; +import Editor from "@/components/dev-tools/Editor"; 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 [currentCombos, setCurrentCombos] = React.useState(null); const [units, setUnits] = React.useState(null); const [error, setError] = React.useState(null); const [open, setOpen] = React.useState(false); @@ -73,27 +26,70 @@ export default function Page(){ setEditorMode(m) } const changeError = (e: string) => setError(e); - // get all of the cards, or units + React.useEffect(() => { const getCards = async () => { try{ // fetch our db + const res = await fetch("/api/dev/vor/cards", { + method: "GET", + }) + const body = await res.json() + if(!res.ok){ + console.error("Could not get vor cards:", body) + setCards(null); + } else{ + setCards(body) + } }catch (error) { - + console.error("Could not get vor cards:", error) + setCards(null); } } - const getUnits = async () => { try{ - + // fetch our db + const res = await fetch("/api/dev/vor/units", { + method: "GET", + }) + const body = await res.json() + if(!res.ok){ + console.error("Could not get vor units:", body) + setUnits(null); + } else{ + setUnits(body) + } }catch (error) { - + console.error("Could not get vor units:", error) + setUnits(null); } } getCards(); getUnits(); }, []) + React.useEffect(() => { + const getCombos = async () => { + // first check if the user has unconfirmed changes, if not then go for the query + try{ + const res = await fetch(`/api/dev/vor/${currentDataId}/combos`, { + method: "GET", + }) + const body = await res.json(); + if(res.ok){ + setCurrentCombos(body) + }else{ + setCurrentCombos(null) + console.error("Failed to get combos for:", body) + } + } catch(error){ + setCurrentCombos(null) + console.error("Could not get combos for:", error) + } + } + + },[currentDataId]) + const handleClose = ( event: React.SyntheticEvent | Event, reason?: SnackbarCloseReason, @@ -130,6 +126,7 @@ export default function Page(){ error, cards, units, + currentCombos, changeCurrentRecordNum, changeCurrentDataId, @@ -154,208 +151,3 @@ export default function Page(){ ) } -function DynamicCard() { - // to add a new kind of data type "map", "string", "bool", etc. they add a prop for it. - return ( - <> - - ) -} - - -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 [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 ( - - - Asset Type - - - Theme - - - - - 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 ( - - - {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/components/dev-tools/Editor.tsx b/src/components/dev-tools/Editor.tsx new file mode 100644 index 0000000..9b07667 --- /dev/null +++ b/src/components/dev-tools/Editor.tsx @@ -0,0 +1,63 @@ +import React from "react"; +import styles from "@/styles/dev-tools.module.css" +import { Container, Chip } from "@mui/material" +import { buildingTypes, unitTypes } from "@/types/dev-tools-extra"; +import { useEditor } from "@/hooks/useEditor"; + +export default function Editor(){ + // TODO: create an inspector (in HTML) and a node view (in canvas) + const { editorMode, currentDataId } = useEditor(); + + const canvasWrapperRef = React.useRef(null); + const canvasRef = React.useRef(null); + + return ( + + + {editorMode === "insert" ? : null} +
+ +
+ +
+ ) +} + + + +function CreateNewAsset({ type }: { type: string }) { + return ( +
+ {type === "building" + ? buildingTypes.map((buildingType, i) => { + const label = buildingType[0].toUpperCase() + buildingType.slice(1); + + return ( +
+ +
+ ); + }) + : unitTypes.map((unitType, i) => { + const label = unitType[0].toUpperCase() + unitType.slice(1); + return ( +
+ +
+ ); + })} +
+ ); +} diff --git a/src/components/dev-tools/LeftMenu.tsx b/src/components/dev-tools/LeftMenu.tsx new file mode 100644 index 0000000..0c1740a --- /dev/null +++ b/src/components/dev-tools/LeftMenu.tsx @@ -0,0 +1,62 @@ +import React from "react"; +import styles from "@/styles/dev-tools.module.css" +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { Container, Select, Stack, MenuItem, InputLabel, Typography } from "@mui/material" +import { buildingTypes, unitTypes } from "@/types/dev-tools-extra"; +import { useEditor } from "@/hooks/useEditor"; + +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" + } + ] + } + ] + }, +] + +export default function LeftMenu(){ + + //const [themes, setThemes] = React.useState([]); // get from the database + const [menuMode, setMenuMode] = React.useState<"unit" | "card">("card"); + const { changeCurrentDataId } = useEditor(); + + const handleSelectAsset = (_e: React.MouseEvent, id: string) => { + changeCurrentDataId!(id); + } + return ( + + + Asset Type + + + Theme + + + + + Current {menuMode}s created + + + + + ) +} + diff --git a/src/contexts/EditorContext.tsx b/src/contexts/EditorContext.tsx new file mode 100644 index 0000000..f1a1794 --- /dev/null +++ b/src/contexts/EditorContext.tsx @@ -0,0 +1,28 @@ +import { Card, Unit, Combo } from "@/types/dev-tools" +import { createContext } from "react" + +export interface EditorContextType { + editorMode: "insert" | "update"; + currentRecordNum: number | null; + currentDataId: string | null; + currentCombos: Combo[] | 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; +} + + +export const EditorContext = createContext({ + currentRecordNum: null, + currentDataId: null, + editorMode: "insert", + cards: null, + units: null, + currentCombos: null, + error: null, +}) diff --git a/src/hooks/useEditor.ts b/src/hooks/useEditor.ts new file mode 100644 index 0000000..7ea2afc --- /dev/null +++ b/src/hooks/useEditor.ts @@ -0,0 +1,11 @@ +import { EditorContext } from "@/contexts/EditorContext" +import { useContext } from "react"; + +export function useEditor() { + const ctx = useContext(EditorContext); + if(!ctx){ + throw new Error("Error: use Editor must be used within an EditorContext") + } + + return ctx; +} diff --git a/src/sql/cards.sql b/src/sql/cards.sql index cfa3d84..e6fc0f7 100644 --- a/src/sql/cards.sql +++ b/src/sql/cards.sql @@ -16,8 +16,6 @@ CREATE TABLE cards ( created_timestamp TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); - - DROP TABLE IF EXISTS units; DROP SEQUENCE IF EXISTS units_record_num_sequence; DROP SEQUENCE IF EXISTS units_unit_id_sequence; @@ -42,6 +40,20 @@ CREATE TABLE units ( created_timestamp TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); +DROP TABLE IF EXISTS combos; +DROP SEQUENCE IF EXISTS combos_record_num_sequence; + +CREATE SEQUENCE combos_record_num_sequence; + +CREATE TABLE combos ( + record_num INTEGER PRIMARY KEY DEFAULT nextval('combos_record_num_sequence'), + parent_data_id VARCHAR(100) NOT NULL, + combo_data_id VARCHAR(100) NOT NULL, + result_data_id VARCHAR(100) NOT NULL, + CHECK (parent_data_id <> result_data_id) +); + + DROP TABLE IF EXISTS resources; DROP SEQUENCE IF EXISTS resources_record_num_sequence; DROP SEQUENCE IF EXISTS resources_resource_id_sequence; diff --git a/src/types/dev-tools-extra.ts b/src/types/dev-tools-extra.ts new file mode 100644 index 0000000..78224e6 --- /dev/null +++ b/src/types/dev-tools-extra.ts @@ -0,0 +1,5 @@ + +export const unitTypes: string[] = ["minion", "enemy", "hero", "ally"]; +export const buildingTypes: string[] = ["enemy", "minion", "terrain", "support"]; + + diff --git a/src/types/dev-tools.d.ts b/src/types/dev-tools.d.ts new file mode 100644 index 0000000..a6682b1 --- /dev/null +++ b/src/types/dev-tools.d.ts @@ -0,0 +1,28 @@ + +export type EditorMode = "insert" | "update"; + +export interface Combo { + parent_data_id: string; + combo_data_id: string; + result_data_id: string; +} + +export interface DBObject { + record_num: number + data_id: string; + change_timestamp: string; +} + +export interface Unit extends DBObject { + +} + +export interface Card extends DBObject { + +} + + +export interface ThemeSelection { + value: string; + label: string; +}