workflow for creating pack,
changeable pack version
This commit is contained in:
parent
70b489213c
commit
e03e8809b7
@ -19,6 +19,7 @@
|
|||||||
"@preact/preset-vite": "npm:@preact/preset-vite@^2.9.1",
|
"@preact/preset-vite": "npm:@preact/preset-vite@^2.9.1",
|
||||||
"@std/fs": "jsr:@std/fs@^1.0.4",
|
"@std/fs": "jsr:@std/fs@^1.0.4",
|
||||||
"@std/http": "jsr:@std/http@^1.0.8",
|
"@std/http": "jsr:@std/http@^1.0.8",
|
||||||
|
"@std/path": "jsr:@std/path@^1.0.6",
|
||||||
"autoprefixer": "npm:autoprefixer@^10.4.20",
|
"autoprefixer": "npm:autoprefixer@^10.4.20",
|
||||||
"babel-plugin-transform-hook-names": "npm:babel-plugin-transform-hook-names@^1.0.2",
|
"babel-plugin-transform-hook-names": "npm:babel-plugin-transform-hook-names@^1.0.2",
|
||||||
"less": "npm:less@^4.2.0",
|
"less": "npm:less@^4.2.0",
|
||||||
|
1
deno.lock
generated
1
deno.lock
generated
@ -1394,6 +1394,7 @@
|
|||||||
"jsr:@bearmetal/store@^0.0.5",
|
"jsr:@bearmetal/store@^0.0.5",
|
||||||
"jsr:@std/fs@^1.0.4",
|
"jsr:@std/fs@^1.0.4",
|
||||||
"jsr:@std/http@^1.0.8",
|
"jsr:@std/http@^1.0.8",
|
||||||
|
"jsr:@std/path@^1.0.6",
|
||||||
"npm:@babel/plugin-transform-react-jsx-development@^7.25.7",
|
"npm:@babel/plugin-transform-react-jsx-development@^7.25.7",
|
||||||
"npm:@deno/vite-plugin@1",
|
"npm:@deno/vite-plugin@1",
|
||||||
"npm:@preact/preset-vite@^2.9.1",
|
"npm:@preact/preset-vite@^2.9.1",
|
||||||
|
62
pack_versions/48.json
Normal file
62
pack_versions/48.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"version": "48",
|
||||||
|
"schema": {
|
||||||
|
"pack.mcmeta": "json",
|
||||||
|
"pack.png": "image/png",
|
||||||
|
"data": {
|
||||||
|
"<namespace>": {
|
||||||
|
"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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
server/main.ts
100
server/main.ts
@ -1,7 +1,7 @@
|
|||||||
import { SockpuppetPlus } from "@cgg/sockpuppet";
|
import { SockpuppetPlus } from "@cgg/sockpuppet";
|
||||||
import { serveDir, serveFile } from "@std/http/file-server";
|
import { serveDir, serveFile } from "@std/http/file-server";
|
||||||
import { BearMetalStore } from "@bearmetal/store";
|
import { BearMetalStore } from "@bearmetal/store";
|
||||||
import { ensureDir } from "@std/fs";
|
import { ensureDir, ensureFile, exists } from "@std/fs";
|
||||||
import { Router } from "./router.ts";
|
import { Router } from "./router.ts";
|
||||||
|
|
||||||
const installPath = Deno.env.get("BMP_INSTALL_DIR") || "./";
|
const installPath = Deno.env.get("BMP_INSTALL_DIR") || "./";
|
||||||
@ -92,8 +92,6 @@ router.route("/api/world")
|
|||||||
}
|
}
|
||||||
const realWorldPath = Deno.realPathSync(worldPath);
|
const realWorldPath = Deno.realPathSync(worldPath);
|
||||||
store.set("world", realWorldPath);
|
store.set("world", realWorldPath);
|
||||||
store.set("packlocation", realWorldPath + "/datapacks/bmp_dev");
|
|
||||||
await ensureDir(store.get("packlocation") as string);
|
|
||||||
return new Response(null, { status: 200 });
|
return new Response(null, { status: 200 });
|
||||||
})
|
})
|
||||||
.get((req) => {
|
.get((req) => {
|
||||||
@ -103,6 +101,88 @@ router.route("/api/world")
|
|||||||
return serveFile(req, worldPath);
|
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) => {
|
sockpuppet.addHandler((req: Request) => {
|
||||||
if (new URL(req.url).pathname.startsWith("/api")) return;
|
if (new URL(req.url).pathname.startsWith("/api")) return;
|
||||||
|
|
||||||
@ -112,3 +192,17 @@ sockpuppet.addHandler((req: Request) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
sockpuppet.addHandler(router.handle);
|
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}"}}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
11
src/components/labelledHr.tsx
Normal file
11
src/components/labelledHr.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { FunctionComponent } from "preact";
|
||||||
|
|
||||||
|
export const LabelledHr: FunctionComponent = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div class="flex gap-4 items-center my-4">
|
||||||
|
<hr class="w-full" />
|
||||||
|
<label>{children}</label>
|
||||||
|
<hr class="w-full" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
9
src/components/loader.tsx
Normal file
9
src/components/loader.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export const Loader = ({ msg }: { msg?: string }) => {
|
||||||
|
return (
|
||||||
|
<div class="flex justify-center items-center gap-4">
|
||||||
|
{!!msg && <p>{msg}</p>}
|
||||||
|
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-primary-600">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
14
src/components/modal.tsx
Normal file
14
src/components/modal.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { FunctionComponent } from "preact";
|
||||||
|
import { Portal } from "./portal.tsx";
|
||||||
|
|
||||||
|
export const Modal: FunctionComponent = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<div class="fixed inset-0 z-10 overflow-y-auto bg-black/50 grid">
|
||||||
|
<div class="place-self-center bg-white dark:bg-mixed-400 w-min min-w-64 rounded-lg p-4 overflow-scroll">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
53
src/components/packsList.tsx
Normal file
53
src/components/packsList.tsx
Normal file
@ -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<StateUpdater<string>> },
|
||||||
|
) => {
|
||||||
|
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 ? <Loader /> : (
|
||||||
|
<ul>
|
||||||
|
{packs.length
|
||||||
|
? packs.map((pack) => (
|
||||||
|
<li
|
||||||
|
class="cursor-pointer even:bg-black/5"
|
||||||
|
onClick={() => setPackNameThing(pack.name)}
|
||||||
|
>
|
||||||
|
{pack.name}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
: <li>No packs found</li>}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
26
src/components/portal.tsx
Normal file
26
src/components/portal.tsx
Normal file
@ -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<IProps> = (
|
||||||
|
{ children, className = "root-portal", el = "div" },
|
||||||
|
) => {
|
||||||
|
const [container, setContainer] = useState<HTMLElement>();
|
||||||
|
|
||||||
|
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) : <></>;
|
||||||
|
};
|
@ -29,4 +29,16 @@
|
|||||||
@apply mr-2;
|
@apply mr-2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.animate-spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
@ -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 = () => {
|
export const Editor = () => {
|
||||||
return <div>Editor</div>;
|
const [packName, setPackName] = useState("");
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [namespaces, setNamespaces] = useState<string[]>([]);
|
||||||
|
const [packVersion, setPackVersion] = useState("");
|
||||||
|
const [packVersionList, setPackVersionList] = useState<string[]>([]);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Modal>
|
||||||
|
{loading
|
||||||
|
? <p>Just a sec, trying to put the cats back in the bag.</p>
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<form
|
||||||
|
method="POST"
|
||||||
|
onSubmit={setPackNameThing}
|
||||||
|
>
|
||||||
|
<label class="w-full">Set pack name</label>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<input type="text" name="packName" />
|
||||||
|
<button type="submit">Set</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<LabelledHr>OR</LabelledHr>
|
||||||
|
<PacksList setPackName={setPackName} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div class="flex h-full">
|
||||||
|
<div class="w-1/4 p-4 bg-primary-600">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="text-2xl">BearMetalPacker</h1>
|
||||||
|
<h2 class="text-xl">{packName}</h2>
|
||||||
|
<p class="bg-lime-700 rounded-full shadow-md">
|
||||||
|
Datapack Version:{" "}
|
||||||
|
<span
|
||||||
|
class="relative cursor-pointer rounded-full bg-lime-200 text-black px-2 inline-block"
|
||||||
|
onClick={() => setShowVersions(!showVersions)}
|
||||||
|
>
|
||||||
|
{packVersion}
|
||||||
|
{showVersions && (
|
||||||
|
<ul class="absolute top-full left-0 bg-lime-50 rounded-md shadow-md p-2 min-w 12">
|
||||||
|
{packVersionList.map((version) => (
|
||||||
|
<li onClick={() => updatePackVersion(version)}>
|
||||||
|
{version}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<LabelledHr>Namespaces</LabelledHr>
|
||||||
|
<div>
|
||||||
|
<ul class="w-full">
|
||||||
|
{namespaces.map((namespace) => (
|
||||||
|
<li class="cursor-pointer even:bg-black/5" // onClick={() => setNamespace(namespace)}
|
||||||
|
>
|
||||||
|
{namespace}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user