namespace tracking and creation
This commit is contained in:
parent
0941690f91
commit
0517e7c2e2
@ -120,6 +120,33 @@ router.route("/api/pack")
|
||||
return new Response(JSON.stringify({ packName }), { status: 200 });
|
||||
});
|
||||
|
||||
router.route("/api/pack/namespaces")
|
||||
.get(async () => {
|
||||
using store = new BearMetalStore();
|
||||
|
||||
const namespaces = Array.from(Deno.readDirSync(store.get("packlocation")))
|
||||
.filter((dir) => dir.isDirectory).map((dir) => dir.name);
|
||||
return new Response(JSON.stringify(namespaces), { status: 200 });
|
||||
})
|
||||
.post(async (req) => {
|
||||
using store = new BearMetalStore();
|
||||
|
||||
const namespace = await req.text();
|
||||
if (!namespace) {
|
||||
return new Response("Namespace is required", { status: 400 });
|
||||
}
|
||||
|
||||
const namespaceRx = /^[a-zA-Z0-9_\-]+$/;
|
||||
if (!namespaceRx.test(namespace)) {
|
||||
return new Response(
|
||||
"Namespace must only contain letters, numbers, underscores, and dashes",
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
await ensureDir(store.get("packlocation") + "/" + namespace);
|
||||
return new Response(null, { status: 200 });
|
||||
});
|
||||
|
||||
router.route("/api/packs")
|
||||
.get(async () => {
|
||||
using store = new BearMetalStore();
|
||||
|
@ -7,7 +7,7 @@ export function App() {
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" Component={Home} />
|
||||
<Route path="/editor" Component={Editor} />
|
||||
<Route path="/editor/*" Component={Editor} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
3
src/atoms/namespace.ts
Normal file
3
src/atoms/namespace.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { atom } from "jotai";
|
||||
|
||||
export const namespaceAtom = atom<string>("");
|
62
src/components/editor/namespaceModal.tsx
Normal file
62
src/components/editor/namespaceModal.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { fetchJson } from "../../util/fetchJson.ts";
|
||||
import { Loader } from "../loader.tsx";
|
||||
import { Modal } from "../modal.tsx";
|
||||
import useSwr from "swr";
|
||||
|
||||
export const NamespaceModal = ({ close }: { close: () => void }) => {
|
||||
const { data: namespaces, isLoading } = useSwr<string[]>(
|
||||
"/api/pack/namespaces",
|
||||
fetchJson,
|
||||
);
|
||||
const [namespace, setNamespace] = useState("");
|
||||
const [invalid, setInvalid] = useState("");
|
||||
|
||||
const createNamespace = async (ns?: string) => {
|
||||
const res = await fetch("/api/pack/namespaces", {
|
||||
method: "POST",
|
||||
body: ns ?? namespace,
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
return close();
|
||||
}
|
||||
|
||||
setInvalid(await res.text());
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal>
|
||||
{isLoading
|
||||
? <Loader msg="Checking existing namespaces..." />
|
||||
: (
|
||||
<div class="flex flex-col gap-2">
|
||||
<p>Create a new namespace</p>
|
||||
{!namespaces?.includes("minecraft") &&
|
||||
(
|
||||
<button
|
||||
class="w-full"
|
||||
onClick={() => createNamespace("minecraft")}
|
||||
>
|
||||
Create default minecraft namespace
|
||||
</button>
|
||||
)}
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
createNamespace();
|
||||
}}
|
||||
class="flex gap-2"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={namespace}
|
||||
onInput={(e) => setNamespace((e.target as any).value)}
|
||||
/>
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
3
src/components/editor/selector.tsx
Normal file
3
src/components/editor/selector.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export const Selector = () => {
|
||||
return <div>Selector</div>;
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { classList } from "../classes.ts";
|
||||
import { classList } from "../util/classes.ts";
|
||||
|
||||
interface IProps {
|
||||
msg?: string;
|
||||
|
2
src/util/fetchJson.ts
Normal file
2
src/util/fetchJson.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const fetchJson = (url: string, init?: RequestInit) =>
|
||||
fetch(url, init).then((res) => res.json());
|
@ -3,11 +3,23 @@ import { Modal } from "../components/modal.tsx";
|
||||
import { LabelledHr } from "../components/labelledHr.tsx";
|
||||
import { PacksList } from "../components/packsList.tsx";
|
||||
import { PackInfo } from "../components/packInfo.tsx";
|
||||
import { useAtom } from "jotai";
|
||||
import { namespaceAtom } from "../atoms/namespace.ts";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { Selector } from "../components/editor/selector.tsx";
|
||||
import { NamespaceModal } from "../components/editor/namespaceModal.tsx";
|
||||
|
||||
export const Editor = () => {
|
||||
const [packName, setPackName] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [namespaces, setNamespaces] = useState<string[]>([]);
|
||||
const [namespace, setNamespace] = useAtom(namespaceAtom);
|
||||
|
||||
const fetchNamespaces = async () => {
|
||||
const res = await fetch("/api/pack/namespaces");
|
||||
const json = await res.json();
|
||||
setNamespaces(json);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "BearMetal Packer";
|
||||
@ -15,6 +27,7 @@ export const Editor = () => {
|
||||
setPackName(json.packName);
|
||||
setLoading(false);
|
||||
});
|
||||
fetchNamespaces();
|
||||
}, []);
|
||||
|
||||
const setPackNameThing = async (event: SubmitEvent) => {
|
||||
@ -56,9 +69,11 @@ export const Editor = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const [showNamespaceModal, setShowNamespaceModal] = useState(false);
|
||||
|
||||
return (
|
||||
<div class="flex h-full">
|
||||
<div class="w-1/4 p-4 bg-primary-600">
|
||||
<div class="w-1/4 p-4 bg-mixed-400">
|
||||
<div class="text-center">
|
||||
<h1 class="text-2xl">BearMetalPacker</h1>
|
||||
<PackInfo packName={packName} />
|
||||
@ -67,14 +82,39 @@ export const Editor = () => {
|
||||
<div>
|
||||
<ul class="w-full">
|
||||
{namespaces.map((namespace) => (
|
||||
<li class="cursor-pointer even:bg-black/5" // onClick={() => setNamespace(namespace)}
|
||||
<li
|
||||
class="text-lg cursor-pointer even:bg-black/5"
|
||||
onClick={() => setNamespace(namespace)}
|
||||
>
|
||||
{namespace}
|
||||
</li>
|
||||
))}
|
||||
<li class="mt-4">
|
||||
<button onClick={() => setShowNamespaceModal(true)}>
|
||||
New Namespace
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
{showNamespaceModal && (
|
||||
<NamespaceModal
|
||||
close={() => {
|
||||
setShowNamespaceModal(false);
|
||||
fetchNamespaces();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{!namespace ? <div>No namespace set</div> : (
|
||||
<div>
|
||||
<h3 class="text-lg">Namespace: {namespace}</h3>
|
||||
<Routes>
|
||||
<Route path="/editor" Component={Selector} />
|
||||
</Routes>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user