good font

tag create workflow
This commit is contained in:
Emmaline Autumn 2024-10-19 21:18:35 -06:00
parent f6ce166b11
commit 3d9b877661
13 changed files with 307 additions and 23 deletions

View File

@ -2,6 +2,8 @@ import { BearMetalStore } from "@bearmetal/store";
import { getPackVersion } from "../util/packVersion.ts";
import type { Router } from "../router.ts";
import { getTagDir } from "./getTagDir.ts";
import { ensureDir } from "@std/fs/ensure-dir";
import { ensureFile } from "@std/fs/ensure-file";
export const createTagRoutes = (router: Router) => {
router.route("/api/pack/:namespace/tags")
@ -25,7 +27,108 @@ export const createTagRoutes = (router: Router) => {
);
return new Response(JSON.stringify(tags), { status: 200 });
} catch {
return new Response("no tags found", { status: 404 });
return new Response("[]", { status: 200 });
}
})
.post(async (req, ctx) => {
if (!ctx.params.namespace) {
return new Response("somehow hit the tags endpoint without namespace", {
status: 500,
});
}
using store = new BearMetalStore();
const version = getPackVersion(store);
const tagDir = await getTagDir(store, ctx.params.namespace, version);
await ensureDir(tagDir);
const { tag, type } = await req.json();
if (!tag || !type) {
return new Response("no tag name provided", { status: 400 });
}
const tagPath = `${tagDir}/${type}/${tag}.json`;
await ensureFile(tagPath);
return new Response(tag, { status: 200 });
});
router.route("/api/pack/:namespace/tags/:type-:tag")
.get(async (_, ctx) => {
if (!ctx.params.namespace) {
return new Response("somehow hit the tags endpoint without namespace", {
status: 500,
});
}
using store = new BearMetalStore();
const version = getPackVersion(store);
const tagDir = await getTagDir(store, ctx.params.namespace, version);
const tag = ctx.params.tag;
if (!tag) {
return new Response("no tag name provided", { status: 400 });
}
try {
const tagFile = Deno.readTextFileSync(tagDir + "/" + tag + ".json");
return new Response(tagFile, { status: 200 });
} catch {
return new Response("no tag found", { status: 404 });
}
})
.put(async (req, ctx) => {
if (!ctx.params.namespace) {
return new Response("somehow hit the tags endpoint without namespace", {
status: 500,
});
}
using store = new BearMetalStore();
const version = getPackVersion(store);
const tagDir = await getTagDir(store, ctx.params.namespace, version);
const tag = ctx.params.tag;
const type = ctx.params.type;
if (!tag || !type) {
return new Response("no tag name provided", { status: 400 });
}
const tagPath = `${tagDir}/${type}/${tag}.json`;
await ensureFile(tagPath);
await Deno.writeTextFile(tagPath, await req.text());
return new Response(tag, { status: 200 });
})
.delete(async (_, ctx) => {
if (!ctx.params.namespace) {
return new Response("somehow hit the tags endpoint without namespace", {
status: 500,
});
}
using store = new BearMetalStore();
const version = getPackVersion(store);
const tagDir = await getTagDir(store, ctx.params.namespace, version);
const tag = ctx.params.tag;
if (!tag) {
return new Response("no tag name provided", { status: 400 });
}
try {
await Deno.remove(tagDir + "/" + tag + ".json");
return new Response(tag, { status: 200 });
} catch {
return new Response("no tag found", { status: 404 });
}
});
};

View File

@ -19,8 +19,6 @@ export async function getDirName(version: number, path: string) {
const singular = makeSingular(path);
const plural = makePlural(path);
console.log("versionData", versionData);
return versionData && versionData.schema.data["<namespace>"][singular] ||
versionData.schema.data["<namespace>"][plural];
}

View File

@ -1,14 +1,17 @@
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { Home } from "./views/home.tsx";
import { Editor } from "./views/editor.tsx";
import { Provider } from "jotai";
export function App() {
return (
<Provider>
<BrowserRouter>
<Routes>
<Route path="/" Component={Home} />
<Route path="/editor/*" Component={Editor} />
</Routes>
</BrowserRouter>
</Provider>
);
}

View File

@ -1,3 +1,14 @@
import { atom } from "jotai";
import { atomWithStorage } from "jotai/utils";
export const namespaceAtom = atom<string>("");
const key = "bmp:namespace";
// const namespaceAtomPrimitive = atom<string>(localStorage.getItem(key) ??"");
// export const namespaceAtom = atom<string>(
// (get) => get(namespaceAtomPrimitive),
// (get, set, newStr) => {
// set(namespaceAtomPrimitive, newStr)
// localStorage.setItem(key, newStr)
// },
// )
export const namespaceAtom = atomWithStorage(key, "");

View File

@ -1,7 +1,7 @@
import { useState } from "preact/hooks";
import { fetchJson } from "../../util/fetchJson.ts";
import { Loader } from "../loader.tsx";
import { Modal } from "../modal.tsx";
import { Loader } from "../../components/loader.tsx";
import { Modal } from "../../components/modal.tsx";
import useSwr from "swr";
export const NamespaceModal = ({ close }: { close: () => void }) => {

View File

@ -1,3 +1,11 @@
import { Link } from "react-router-dom";
export const Selector = () => {
return <div>Selector</div>;
return (
<ul class="flex flex-col gap-2">
<li>
<Link to="/editor/tags">Tag Editor</Link>
</li>
</ul>
);
};

View File

@ -0,0 +1,53 @@
import { Link, Route, Routes } from "react-router-dom";
import useSWR from "swr";
import { fetchJson } from "../../../util/fetchJson.ts";
import { useAtom } from "jotai";
import { namespaceAtom } from "../../../atoms/namespace.ts";
import { Loader } from "../../../components/loader.tsx";
import { useState } from "preact/hooks";
import { NewTagModal } from "./newTagModal.tsx";
export const TagEditor = () => {
return (
<Routes>
<Route index Component={TagList} />
</Routes>
);
};
function TagList() {
const [namespace, _setNamespace] = useAtom(namespaceAtom);
const { data, isLoading } = useSWR<string[]>(
`/api/pack/${namespace}/tags`,
fetchJson,
);
const [showNewTagModal, setShowNewTagModal] = useState(false);
if (isLoading || !data) {
return <Loader msg="Geez, when was the last time you swept?" />;
}
return (
<div>
<ul class="flex flex-col gap-2">
<li>
<button onClick={() => setShowNewTagModal(true)}>New Tag</button>
{showNewTagModal && (
<NewTagModal
close={() => {
setShowNewTagModal(false);
}}
/>
)}
</li>
{data.map((tag) => (
<li class="flex gap-2">
<Link to={`/editor/tags/${tag}`}>{tag}</Link>
<button>Delete</button>
</li>
))}
</ul>
</div>
);
}

View File

@ -0,0 +1,66 @@
import { useState } from "preact/hooks";
import { Modal } from "../../modal.tsx";
import { useNavigate } from "react-router-dom";
import { namespaceAtom } from "../../../atoms/namespace.ts";
import { useAtom } from "jotai";
export const NewTagModal = ({ close }: { close: () => void }) => {
const [tagName, setTagName] = useState("");
const [tagType, setTagType] = useState("block");
const nav = useNavigate();
const [namespace, _setNamespace] = useAtom(namespaceAtom);
const createTag = async () => {
const res = await fetch(
`/api/pack/${namespace}/tags`,
{
method: "POST",
body: JSON.stringify({
tag: tagName,
type: tagType,
}),
},
);
if (res.status === 200) {
close();
nav(`/editor/tags/${tagType}-${tagName}`);
}
};
return (
<Modal>
<p class="mb-2">Create a new tag</p>
<form
class="flex flex-col gap-2"
onSubmit={(e) => {
e.preventDefault();
createTag();
}}
>
<label class="flex gap-2 items-center">
Tag Type{" "}
<select
class="flex-1"
name="tag-type"
value={tagType}
onChange={(e) => setTagType((e.target as any).value)}
>
<option value="block">Block</option>
<option value="entity">Entity</option>
<option value="item">Item</option>
<option value="function">Function</option>
</select>
</label>
<div class="flex gap-2">
<input
type="text"
value={tagName}
onInput={(e) => setTagName((e.target as any).value)}
placeholder="Tag name"
/>
<button type="submit">Create</button>
</div>
</form>
</Modal>
);
};

View File

@ -0,0 +1,15 @@
import { useNavigate } from "react-router-dom";
import type { FunctionComponent } from "preact";
export const EditorWrapper: FunctionComponent = ({ children }) => {
const nav = useNavigate();
return (
<div>
<button class="hollow flex items-center gap-2" onClick={() => nav(-1)}>
<span class="text-2xl"></span>
<span class="italic">back</span>
</button>
{children}
</div>
);
};

View File

@ -5,7 +5,7 @@ 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">
<div class="place-self-center bg-white dark:bg-mixed-400 w-min min-w-64 rounded-lg p-4 overflow-scroll relative">
{children}
</div>
</div>

View File

@ -2,6 +2,8 @@
@tailwind components;
@tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
@layer base {
:root {
@apply dark:bg-mixed-600 bg-primary-100 text-dark-600 dark:text-white;
@ -18,12 +20,12 @@
}
button {
@apply bg-primary-600 text-white rounded-md px-4 py-2 font-bold;
@apply bg-primary-600 text-white rounded-md px-4 py-2;
}
input,
select {
@apply bg-white text-dark-600 rounded-md px-4 py-2 font-bold;
@apply bg-white text-dark-600 rounded-md px-4 py-2;
&:not(:last-child) {
@apply mr-2;
@ -35,6 +37,10 @@
.animate-spin {
animation: spin 1s linear infinite;
}
button.hollow {
@apply bg-transparent p-0 inline;
}
}
@keyframes spin {

View File

@ -5,9 +5,11 @@ 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 { Outlet, Route, Routes } from "react-router-dom";
import { Selector } from "../components/editor/selector.tsx";
import { NamespaceModal } from "../components/editor/namespaceModal.tsx";
import { EditorWrapper } from "../components/editor/wrapper.tsx";
import { TagEditor } from "../components/editor/tags/editor.tsx";
export const Editor = () => {
const [packName, setPackName] = useState("");
@ -105,14 +107,29 @@ export const Editor = () => {
)}
</div>
</div>
<div>
<div class="p-4 w-max">
{!namespace ? <div>No namespace set</div> : (
<div>
<h3 class="text-lg">Namespace: {namespace}</h3>
<>
<h3 class="text-lg font-bold">
Namespace: <span class="italic">{namespace}</span>
</h3>
<Routes>
<Route path="/editor" Component={Selector} />
<Route index element={<Selector />} />
<Route
element={
<EditorWrapper>
<Outlet />
</EditorWrapper>
}
>
<Route
path="tags/*"
element={<TagEditor />}
/>
</Route>
</Routes>
</div>
</>
)}
</div>
</div>

View File

@ -6,6 +6,10 @@ module.exports = {
],
theme: {
extend: {
fontFamily: {
sans: ["Roboto", "sans-serif"],
mono: ["Roboto Mono", "monospace"],
},
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":