good font
tag create workflow
This commit is contained in:
parent
f6ce166b11
commit
3d9b877661
@ -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 });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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, "");
|
||||
|
@ -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 }) => {
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
53
src/components/editor/tags/editor.tsx
Normal file
53
src/components/editor/tags/editor.tsx
Normal 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>
|
||||
);
|
||||
}
|
66
src/components/editor/tags/newTagModal.tsx
Normal file
66
src/components/editor/tags/newTagModal.tsx
Normal 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>
|
||||
);
|
||||
};
|
15
src/components/editor/wrapper.tsx
Normal file
15
src/components/editor/wrapper.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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":
|
||||
|
Loading…
x
Reference in New Issue
Block a user