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 { getPackVersion } from "../util/packVersion.ts";
|
||||||
import type { Router } from "../router.ts";
|
import type { Router } from "../router.ts";
|
||||||
import { getTagDir } from "./getTagDir.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) => {
|
export const createTagRoutes = (router: Router) => {
|
||||||
router.route("/api/pack/:namespace/tags")
|
router.route("/api/pack/:namespace/tags")
|
||||||
@ -25,7 +27,108 @@ export const createTagRoutes = (router: Router) => {
|
|||||||
);
|
);
|
||||||
return new Response(JSON.stringify(tags), { status: 200 });
|
return new Response(JSON.stringify(tags), { status: 200 });
|
||||||
} catch {
|
} 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 singular = makeSingular(path);
|
||||||
const plural = makePlural(path);
|
const plural = makePlural(path);
|
||||||
|
|
||||||
console.log("versionData", versionData);
|
|
||||||
|
|
||||||
return versionData && versionData.schema.data["<namespace>"][singular] ||
|
return versionData && versionData.schema.data["<namespace>"][singular] ||
|
||||||
versionData.schema.data["<namespace>"][plural];
|
versionData.schema.data["<namespace>"][plural];
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||||
import { Home } from "./views/home.tsx";
|
import { Home } from "./views/home.tsx";
|
||||||
import { Editor } from "./views/editor.tsx";
|
import { Editor } from "./views/editor.tsx";
|
||||||
|
import { Provider } from "jotai";
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
|
<Provider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" Component={Home} />
|
<Route path="/" Component={Home} />
|
||||||
<Route path="/editor/*" Component={Editor} />
|
<Route path="/editor/*" Component={Editor} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</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 { useState } from "preact/hooks";
|
||||||
import { fetchJson } from "../../util/fetchJson.ts";
|
import { fetchJson } from "../../util/fetchJson.ts";
|
||||||
import { Loader } from "../loader.tsx";
|
import { Loader } from "../../components/loader.tsx";
|
||||||
import { Modal } from "../modal.tsx";
|
import { Modal } from "../../components/modal.tsx";
|
||||||
import useSwr from "swr";
|
import useSwr from "swr";
|
||||||
|
|
||||||
export const NamespaceModal = ({ close }: { close: () => void }) => {
|
export const NamespaceModal = ({ close }: { close: () => void }) => {
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
export const Selector = () => {
|
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 (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<div class="fixed inset-0 z-10 overflow-y-auto bg-black/50 grid">
|
<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}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@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 {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
@apply dark:bg-mixed-600 bg-primary-100 text-dark-600 dark:text-white;
|
@apply dark:bg-mixed-600 bg-primary-100 text-dark-600 dark:text-white;
|
||||||
@ -18,12 +20,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
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,
|
input,
|
||||||
select {
|
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) {
|
&:not(:last-child) {
|
||||||
@apply mr-2;
|
@apply mr-2;
|
||||||
@ -35,6 +37,10 @@
|
|||||||
.animate-spin {
|
.animate-spin {
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.hollow {
|
||||||
|
@apply bg-transparent p-0 inline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
|
@ -5,9 +5,11 @@ import { PacksList } from "../components/packsList.tsx";
|
|||||||
import { PackInfo } from "../components/packInfo.tsx";
|
import { PackInfo } from "../components/packInfo.tsx";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { namespaceAtom } from "../atoms/namespace.ts";
|
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 { Selector } from "../components/editor/selector.tsx";
|
||||||
import { NamespaceModal } from "../components/editor/namespaceModal.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 = () => {
|
export const Editor = () => {
|
||||||
const [packName, setPackName] = useState("");
|
const [packName, setPackName] = useState("");
|
||||||
@ -105,14 +107,29 @@ export const Editor = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="p-4 w-max">
|
||||||
{!namespace ? <div>No namespace set</div> : (
|
{!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>
|
<Routes>
|
||||||
<Route path="/editor" Component={Selector} />
|
<Route index element={<Selector />} />
|
||||||
|
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
<EditorWrapper>
|
||||||
|
<Outlet />
|
||||||
|
</EditorWrapper>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route
|
||||||
|
path="tags/*"
|
||||||
|
element={<TagEditor />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,10 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["Roboto", "sans-serif"],
|
||||||
|
mono: ["Roboto Mono", "monospace"],
|
||||||
|
},
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||||
"gradient-conic":
|
"gradient-conic":
|
||||||
|
Loading…
x
Reference in New Issue
Block a user