From 3d9b87766115d62427bbbd1e8ccca8364c336745 Mon Sep 17 00:00:00 2001 From: Emma Date: Sat, 19 Oct 2024 21:18:35 -0600 Subject: [PATCH] good font tag create workflow --- server/tags/routes.ts | 105 ++++++++++++++++++++- server/util/packVersion.ts | 2 - src/app.tsx | 15 +-- src/atoms/namespace.ts | 15 ++- src/components/editor/namespaceModal.tsx | 4 +- src/components/editor/selector.tsx | 10 +- src/components/editor/tags/editor.tsx | 53 +++++++++++ src/components/editor/tags/newTagModal.tsx | 66 +++++++++++++ src/components/editor/wrapper.tsx | 15 +++ src/components/modal.tsx | 2 +- src/index.less | 10 +- src/views/editor.tsx | 29 ++++-- tailwind.config.js | 4 + 13 files changed, 307 insertions(+), 23 deletions(-) create mode 100644 src/components/editor/tags/editor.tsx create mode 100644 src/components/editor/tags/newTagModal.tsx create mode 100644 src/components/editor/wrapper.tsx diff --git a/server/tags/routes.ts b/server/tags/routes.ts index 41dfa94..bbc2f0b 100644 --- a/server/tags/routes.ts +++ b/server/tags/routes.ts @@ -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 }); } }); }; diff --git a/server/util/packVersion.ts b/server/util/packVersion.ts index 482b8d0..a2e65d2 100644 --- a/server/util/packVersion.ts +++ b/server/util/packVersion.ts @@ -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[""][singular] || versionData.schema.data[""][plural]; } diff --git a/src/app.tsx b/src/app.tsx index 859574e..5a12889 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -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 ( - - - - - - + + + + + + + + ); } diff --git a/src/atoms/namespace.ts b/src/atoms/namespace.ts index cde3e5e..518a128 100644 --- a/src/atoms/namespace.ts +++ b/src/atoms/namespace.ts @@ -1,3 +1,14 @@ -import { atom } from "jotai"; +import { atomWithStorage } from "jotai/utils"; -export const namespaceAtom = atom(""); +const key = "bmp:namespace"; +// const namespaceAtomPrimitive = atom(localStorage.getItem(key) ??""); + +// export const namespaceAtom = atom( +// (get) => get(namespaceAtomPrimitive), +// (get, set, newStr) => { +// set(namespaceAtomPrimitive, newStr) +// localStorage.setItem(key, newStr) +// }, +// ) + +export const namespaceAtom = atomWithStorage(key, ""); diff --git a/src/components/editor/namespaceModal.tsx b/src/components/editor/namespaceModal.tsx index 110f7fb..2bf3be5 100644 --- a/src/components/editor/namespaceModal.tsx +++ b/src/components/editor/namespaceModal.tsx @@ -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 }) => { diff --git a/src/components/editor/selector.tsx b/src/components/editor/selector.tsx index 1d4b6d3..7250e36 100644 --- a/src/components/editor/selector.tsx +++ b/src/components/editor/selector.tsx @@ -1,3 +1,11 @@ +import { Link } from "react-router-dom"; + export const Selector = () => { - return
Selector
; + return ( +
    +
  • + Tag Editor +
  • +
+ ); }; diff --git a/src/components/editor/tags/editor.tsx b/src/components/editor/tags/editor.tsx new file mode 100644 index 0000000..129cc5b --- /dev/null +++ b/src/components/editor/tags/editor.tsx @@ -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 ( + + + + ); +}; + +function TagList() { + const [namespace, _setNamespace] = useAtom(namespaceAtom); + const { data, isLoading } = useSWR( + `/api/pack/${namespace}/tags`, + fetchJson, + ); + + const [showNewTagModal, setShowNewTagModal] = useState(false); + + if (isLoading || !data) { + return ; + } + + return ( +
+
    +
  • + + {showNewTagModal && ( + { + setShowNewTagModal(false); + }} + /> + )} +
  • + {data.map((tag) => ( +
  • + {tag} + +
  • + ))} +
+
+ ); +} diff --git a/src/components/editor/tags/newTagModal.tsx b/src/components/editor/tags/newTagModal.tsx new file mode 100644 index 0000000..38fdc17 --- /dev/null +++ b/src/components/editor/tags/newTagModal.tsx @@ -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 ( + +

Create a new tag

+
{ + e.preventDefault(); + createTag(); + }} + > + +
+ setTagName((e.target as any).value)} + placeholder="Tag name" + /> + +
+
+
+ ); +}; diff --git a/src/components/editor/wrapper.tsx b/src/components/editor/wrapper.tsx new file mode 100644 index 0000000..122f5d0 --- /dev/null +++ b/src/components/editor/wrapper.tsx @@ -0,0 +1,15 @@ +import { useNavigate } from "react-router-dom"; +import type { FunctionComponent } from "preact"; + +export const EditorWrapper: FunctionComponent = ({ children }) => { + const nav = useNavigate(); + return ( +
+ + {children} +
+ ); +}; diff --git a/src/components/modal.tsx b/src/components/modal.tsx index 4bad6e8..f25fc83 100644 --- a/src/components/modal.tsx +++ b/src/components/modal.tsx @@ -5,7 +5,7 @@ export const Modal: FunctionComponent = ({ children }) => { return (
-
+
{children}
diff --git a/src/index.less b/src/index.less index 522b44c..332647b 100644 --- a/src/index.less +++ b/src/index.less @@ -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 { diff --git a/src/views/editor.tsx b/src/views/editor.tsx index b3e15a5..87ab6ed 100644 --- a/src/views/editor.tsx +++ b/src/views/editor.tsx @@ -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 = () => { )}
-
+
{!namespace ?
No namespace set
: ( -
-

Namespace: {namespace}

+ <> +

+ Namespace: {namespace} +

- + } /> + + + + + } + > + } + /> + -
+ )}
diff --git a/tailwind.config.js b/tailwind.config.js index e5e13c2..9e0116b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -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":