From e03e8809b7601100ed3b42e4cf5143e50166c05e Mon Sep 17 00:00:00 2001 From: Emma Date: Sat, 19 Oct 2024 01:08:54 -0600 Subject: [PATCH] workflow for creating pack, changeable pack version --- deno.json | 1 + deno.lock | 1 + pack_versions/48.json | 62 ++++++++++++++++++ server/main.ts | 100 ++++++++++++++++++++++++++++- src/components/labelledHr.tsx | 11 ++++ src/components/loader.tsx | 9 +++ src/components/modal.tsx | 14 ++++ src/components/packsList.tsx | 53 +++++++++++++++ src/components/portal.tsx | 26 ++++++++ src/index.less | 12 ++++ src/views/editor.tsx | 117 +++++++++++++++++++++++++++++++++- 11 files changed, 402 insertions(+), 4 deletions(-) create mode 100644 pack_versions/48.json create mode 100644 src/components/labelledHr.tsx create mode 100644 src/components/loader.tsx create mode 100644 src/components/modal.tsx create mode 100644 src/components/packsList.tsx create mode 100644 src/components/portal.tsx diff --git a/deno.json b/deno.json index f88f14b..504974d 100644 --- a/deno.json +++ b/deno.json @@ -19,6 +19,7 @@ "@preact/preset-vite": "npm:@preact/preset-vite@^2.9.1", "@std/fs": "jsr:@std/fs@^1.0.4", "@std/http": "jsr:@std/http@^1.0.8", + "@std/path": "jsr:@std/path@^1.0.6", "autoprefixer": "npm:autoprefixer@^10.4.20", "babel-plugin-transform-hook-names": "npm:babel-plugin-transform-hook-names@^1.0.2", "less": "npm:less@^4.2.0", diff --git a/deno.lock b/deno.lock index dc5d8a0..2104d42 100644 --- a/deno.lock +++ b/deno.lock @@ -1394,6 +1394,7 @@ "jsr:@bearmetal/store@^0.0.5", "jsr:@std/fs@^1.0.4", "jsr:@std/http@^1.0.8", + "jsr:@std/path@^1.0.6", "npm:@babel/plugin-transform-react-jsx-development@^7.25.7", "npm:@deno/vite-plugin@1", "npm:@preact/preset-vite@^2.9.1", diff --git a/pack_versions/48.json b/pack_versions/48.json new file mode 100644 index 0000000..b4e81fc --- /dev/null +++ b/pack_versions/48.json @@ -0,0 +1,62 @@ +{ + "version": "48", + "schema": { + "pack.mcmeta": "json", + "pack.png": "image/png", + "data": { + "": { + "function": "function", + "structure": { + "DataVersion": "int", + "size": ["int", "int", "int"], + "palette": [{ + "name": "blockId", + "properties": ["string"] + }], + "palettes": [ + [{ + "name": "blockId", + "properties": ["string"] + }] + ], + "blocks": [{ + "state": "int", + "pos": ["int", "int", "int"], + "nbt": "nbt" + }], + "entities": [{ + "pos": ["double", "double", "double"], + "blockPos": ["int", "int", "int"], + "nbt": "nbt" + }] + }, + "tags": "tags", + "advancment": { + "parent": "advancment", + "display": { + "icon": { + "id": "itemId", + "count": "int", + "components": ["itemComponent"] + }, + "title": "jsonString", + "description": "jsonString", + "frame": "frame", + "background": "resource", + "show_toast": "bool", + "announce_to_chat": "bool", + "hidden": "bool" + }, + "criteria": "criteria", + "requirements": ["criterion_name"], + "rewards": { + "experience": "int", + "function": "function", + "loot": ["loot_table"], + "recipes": ["recipe"] + } + } + } + } + } +} diff --git a/server/main.ts b/server/main.ts index 14f93b0..f5339f2 100644 --- a/server/main.ts +++ b/server/main.ts @@ -1,7 +1,7 @@ import { SockpuppetPlus } from "@cgg/sockpuppet"; import { serveDir, serveFile } from "@std/http/file-server"; import { BearMetalStore } from "@bearmetal/store"; -import { ensureDir } from "@std/fs"; +import { ensureDir, ensureFile, exists } from "@std/fs"; import { Router } from "./router.ts"; const installPath = Deno.env.get("BMP_INSTALL_DIR") || "./"; @@ -92,8 +92,6 @@ router.route("/api/world") } const realWorldPath = Deno.realPathSync(worldPath); store.set("world", realWorldPath); - store.set("packlocation", realWorldPath + "/datapacks/bmp_dev"); - await ensureDir(store.get("packlocation") as string); return new Response(null, { status: 200 }); }) .get((req) => { @@ -103,6 +101,88 @@ router.route("/api/world") return serveFile(req, worldPath); }); +router.route("/api/pack") + .get(() => { + using store = new BearMetalStore(); + return new Response( + JSON.stringify({ packName: store.get("packname") as string }), + { status: 200 }, + ); + }) + .post(async (req) => { + using store = new BearMetalStore(); + const formData = await req.formData(); + const packName = formData.get("packName") as string; + if (!packName) return new Response(null, { status: 400 }); + createPack(store, packName); + + store.set("packname", packName); + return new Response(JSON.stringify({ packName }), { status: 200 }); + }); + +router.route("/api/packs") + .get(async () => { + using store = new BearMetalStore(); + const packs: { name: string; path: string }[] = []; + + const world = store.get("world") as string; + for await (const pack of Deno.readDir(world + "/datapacks")) { + if ( + pack.isDirectory && + await exists(world + "/datapacks/" + pack.name + "/bmp_dev") + ) { + packs.push({ + name: pack.name, + path: Deno.realPathSync(world + "/datapacks/" + pack.name), + }); + } + } + + return new Response("No BMP packs found", { status: 400 }); + }); + +router.route("/api/pack/version") + .get(() => { + using store = new BearMetalStore(); + const packMeta = Deno.readTextFileSync( + store.get("packlocation") + "/pack.mcmeta", + ); + const packMetaJson = JSON.parse(packMeta); + return new Response(packMetaJson.pack.pack_format.toString(), { + status: 200, + }); + }) + .post(async (req) => { + using store = new BearMetalStore(); + const version = await req.text(); + if (!version) return new Response(null, { status: 400 }); + + try { + store.set("version", version); + const packMeta = Deno.readTextFileSync( + store.get("packlocation") + "/pack.mcmeta", + ); + const packMetaJson = JSON.parse(packMeta); + packMetaJson.pack.pack_format = parseInt(version); + await Deno.writeTextFile( + store.get("packlocation") + "/pack.mcmeta", + JSON.stringify(packMetaJson), + ); + } catch (e: any) { + return new Response(e, { status: 500 }); + } + return new Response(null, { status: 200 }); + }); + +router.route("/api/versions") + .get(() => { + const versions = Array.from(Deno.readDirSync(installPath + "pack_versions")) + .filter((v) => v.isFile).map((version) => + version.name.replace(".json", "") + ); + return new Response(JSON.stringify(versions), { status: 200 }); + }); + sockpuppet.addHandler((req: Request) => { if (new URL(req.url).pathname.startsWith("/api")) return; @@ -112,3 +192,17 @@ sockpuppet.addHandler((req: Request) => { }); sockpuppet.addHandler(router.handle); + +async function createPack(store: BearMetalStore, packName: string) { + const realWorldPath = store.get("world"); + store.set("packlocation", realWorldPath + "/datapacks/" + packName); + await ensureDir(store.get("packlocation")); + await ensureFile(store.get("packlocation") + "/bmp_dev"); + await ensureFile(store.get("packlocation") + "/pack.mcmeta"); + if (!Deno.readTextFileSync(store.get("packlocation") + "/pack.mcmeta")) { + await Deno.writeTextFile( + store.get("packlocation") + "/pack.mcmeta", + `{"pack":{"pack_format":48,"description":"${packName}"}}`, + ); + } +} diff --git a/src/components/labelledHr.tsx b/src/components/labelledHr.tsx new file mode 100644 index 0000000..eb01fdd --- /dev/null +++ b/src/components/labelledHr.tsx @@ -0,0 +1,11 @@ +import type { FunctionComponent } from "preact"; + +export const LabelledHr: FunctionComponent = ({ children }) => { + return ( +
+
+ +
+
+ ); +}; diff --git a/src/components/loader.tsx b/src/components/loader.tsx new file mode 100644 index 0000000..ade0d1f --- /dev/null +++ b/src/components/loader.tsx @@ -0,0 +1,9 @@ +export const Loader = ({ msg }: { msg?: string }) => { + return ( +
+ {!!msg &&

{msg}

} +
+
+
+ ); +}; diff --git a/src/components/modal.tsx b/src/components/modal.tsx new file mode 100644 index 0000000..4bad6e8 --- /dev/null +++ b/src/components/modal.tsx @@ -0,0 +1,14 @@ +import type { FunctionComponent } from "preact"; +import { Portal } from "./portal.tsx"; + +export const Modal: FunctionComponent = ({ children }) => { + return ( + +
+
+ {children} +
+
+
+ ); +}; diff --git a/src/components/packsList.tsx b/src/components/packsList.tsx new file mode 100644 index 0000000..2a071e8 --- /dev/null +++ b/src/components/packsList.tsx @@ -0,0 +1,53 @@ +import { + type Dispatch, + type StateUpdater, + useEffect, + useState, +} from "preact/hooks"; +import { Loader } from "./loader.tsx"; + +export const PacksList = ( + { setPackName }: { setPackName: Dispatch> }, +) => { + const [packs, setPacks] = useState<{ name: string; path: string }[]>([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch("/api/packs").then((res) => res.json()).then((json) => { + setPacks(json); + }).finally(() => setLoading(false)); + }, []); + + const setPackNameThing = async (name: string) => { + setLoading(true); + const body = new FormData(); + body.set("packName", name); + const res = await fetch("/api/pack", { + method: "POST", + body, + }); + + const json = await res.json(); + + if (res.status === 200) { + document.title = json.packName; + setPackName(json.packName); + } + setLoading(false); + }; + + return loading ? : ( +
    + {packs.length + ? packs.map((pack) => ( +
  • setPackNameThing(pack.name)} + > + {pack.name} +
  • + )) + :
  • No packs found
  • } +
+ ); +}; diff --git a/src/components/portal.tsx b/src/components/portal.tsx new file mode 100644 index 0000000..ba2c87c --- /dev/null +++ b/src/components/portal.tsx @@ -0,0 +1,26 @@ +import { useEffect, useState } from "preact/hooks"; +import { createPortal } from "preact/compat"; +import type { FunctionComponent } from "preact"; + +interface IProps { + className?: string; + el?: string; +} + +export const Portal: FunctionComponent = ( + { children, className = "root-portal", el = "div" }, +) => { + const [container, setContainer] = useState(); + + useEffect(() => { + const container = document.createElement(el); + container.classList.add(className); + document.body.appendChild(container); + setContainer(container); + return () => { + document.body.removeChild(container); + }; + }, [className, el]); + + return container ? createPortal(children, container) : <>; +}; diff --git a/src/index.less b/src/index.less index 1430a9d..522b44c 100644 --- a/src/index.less +++ b/src/index.less @@ -29,4 +29,16 @@ @apply mr-2; } } +} + +@layer utilities { + .animate-spin { + animation: spin 1s linear infinite; + } +} + +@keyframes spin { + to { + transform: rotate(360deg); + } } \ No newline at end of file diff --git a/src/views/editor.tsx b/src/views/editor.tsx index 2cfba90..9f1af21 100644 --- a/src/views/editor.tsx +++ b/src/views/editor.tsx @@ -1,3 +1,118 @@ +import { useEffect, useState } from "preact/hooks"; +import { Modal } from "../components/modal.tsx"; +import { LabelledHr } from "../components/labelledHr.tsx"; +import { PacksList } from "../components/packsList.tsx"; + export const Editor = () => { - return
Editor
; + const [packName, setPackName] = useState(""); + const [loading, setLoading] = useState(true); + const [namespaces, setNamespaces] = useState([]); + const [packVersion, setPackVersion] = useState(""); + const [packVersionList, setPackVersionList] = useState([]); + + useEffect(() => { + document.title = "BearMetal Packer"; + fetch("/api/pack").then((res) => res.json()).then((json) => { + setPackName(json.packName); + setLoading(false); + }); + fetch("/api/pack/version").then((res) => res.text()).then((text) => { + setPackVersion(text); + }); + fetch("/api/versions").then((res) => res.json()).then((json) => { + setPackVersionList(json); + }); + }, []); + + const setPackNameThing = async (event: SubmitEvent) => { + setLoading(true); + const res = await fetch("/api/pack", { + method: "POST", + body: new FormData(event.target as HTMLFormElement), + }); + + if (res.status === 200) { + document.title = packName; + setPackName(packName); + } + setLoading(false); + }; + + if (!packName) { + return ( + + {loading + ?

Just a sec, trying to put the cats back in the bag.

+ : ( + <> +
+ +
+ + +
+
+ OR + + + )} +
+ ); + } + + const [showVersions, setShowVersions] = useState(false); + + const updatePackVersion = async (version: string) => { + const res = await fetch("/api/pack/version", { + method: "POST", + body: version, + }); + + if (res.status === 200) { + setPackVersion(version); + } + }; + + return ( +
+
+
+

BearMetalPacker

+

{packName}

+

+ Datapack Version:{" "} + setShowVersions(!showVersions)} + > + {packVersion} + {showVersions && ( +

    + {packVersionList.map((version) => ( +
  • updatePackVersion(version)}> + {version} +
  • + ))} +
+ )} + +

+
+ Namespaces +
+
    + {namespaces.map((namespace) => ( +
  • setNamespace(namespace)} + > + {namespace} +
  • + ))} +
+
+
+
+ ); };