Compare commits

..

No commits in common. "269a844a6833b0787ab83c0fe7ad83110c322e5a" and "56f0442d33060923fd93cab1bb9e5826db9e70fc" have entirely different histories.

46 changed files with 149 additions and 4550 deletions

View File

@ -1,23 +0,0 @@
"use server";
import { prisma } from "@/prisma/prismaClient";
import { redirect } from "next/navigation";
export const createSchema = async (form: FormData) => {
const name = form.get("name")?.toString();
const gsId = form.get("gsId")?.toString();
if (!name || !gsId) return;
const { id } = await prisma.schema.create({
data: {
name,
schema: "{}",
types: "{}",
version: 0,
gameSystemId: gsId,
},
select: { id: true },
});
redirect(`/game-systems/${gsId}/schema/${id}`);
};

View File

@ -1,20 +0,0 @@
"use server";
import { prisma } from "@/prisma/prismaClient";
export const findSchema = async (id: string) => {
const schema = await prisma.schema.findFirst({
where: {
id,
},
include: {
gameSystem: {
select: {
id: true,
name: true,
},
},
},
});
return schema;
};

View File

@ -1,5 +1,5 @@
import { Sticky } from "@/lib/sticky";
import { prisma } from "@/prisma/prismaClient"; import { prisma } from "@/prisma/prismaClient";
import Link from "next/link";
export default async function GameSystem( export default async function GameSystem(
{ params: { id } }: { params: { id: string } }, { params: { id } }: { params: { id: string } },
@ -35,25 +35,15 @@ export default async function GameSystem(
<h1>{gameSystem?.name}</h1> <h1>{gameSystem?.name}</h1>
</section> </section>
<section> <section>
<> <ul>
<div> {gameSystem?.schemas.map((schema) => (
<Link <li key={schema.id}>{schema.name}</li>
className="btn-primary mb-6 block w-min whitespace-nowrap" ))}
href={`/game-systems/${id}/schema/create`} </ul>
>
Create New Schema
</Link>
</div>
<ul>
{gameSystem?.schemas.map((schema) => (
<li key={schema.id}>{schema.name}</li>
))}
{!gameSystem?.schemas.length && (
<li>No schemas for {gameSystem?.name}</li>
)}
</ul>
</>
</section> </section>
<Sticky sidedness={-1}>
<h1>HELLO!</h1>
</Sticky>
</> </>
); );
} }

View File

@ -1,21 +0,0 @@
import { Heading } from "@/components/heading";
import { SchemaBuilder } from "@/components/schema";
import { prisma } from "@/prisma/prismaClient";
export default async function CreateSchemaForGameSystem(
{ params }: { params: { id: string } },
) {
const gs = await prisma.gameSystem.findFirst({
where: { id: params.id },
select: { name: true },
});
return (
<>
<Heading title={gs?.name || ""} strapline="Schemas" />
<section>
<SchemaBuilder></SchemaBuilder>
</section>
</>
);
}

View File

@ -9,13 +9,9 @@
body { body {
@apply dark:bg-mixed-100 bg-primary-600; @apply dark:bg-mixed-100 bg-primary-600;
} }
input, input {
select {
@apply py-2 px-4 rounded-full dark:bg-mixed-200 bg-mixed-600 placeholder:text-dark-500; @apply py-2 px-4 rounded-full dark:bg-mixed-200 bg-mixed-600 placeholder:text-dark-500;
} }
textarea {
@apply dark:bg-mixed-200 bg-primary-600 rounded-md p-1;
}
h1, h1,
h2, h2,
h3, h3,
@ -47,18 +43,12 @@
/* @apply bg-mixed-200 rounded-3xl p-6 shadow-2xl */; /* @apply bg-mixed-200 rounded-3xl p-6 shadow-2xl */;
} }
.btn {
@apply rounded-full;
}
.btn-primary { .btn-primary {
@apply dark:bg-primary-500 bg-primary-100 py-4 px-6 dark:text-mixed-100 text-white font-bold text-lg btn; @apply dark:bg-primary-500 bg-primary-100 py-4 px-6 dark:text-mixed-100 text-white rounded-full font-bold text-lg;
} }
.btn-secondary { .btn-secondary {
@apply dark:text-primary-500 text-primary-100 py-4 px-6 font-bold text-lg; @apply dark:text-primary-500 text-primary-100 py-4 px-6 font-bold text-lg;
} }
.btn-small {
@apply px-2 py-1;
}
.p { .p {
@apply pb-1; @apply pb-1;
} }

View File

@ -10,7 +10,6 @@ import {
} from "@heroicons/react/24/solid"; } from "@heroicons/react/24/solid";
import Link from "next/link"; import Link from "next/link";
import { DevToolboxContextProvider } from "@/components/devtools/context"; import { DevToolboxContextProvider } from "@/components/devtools/context";
import { RecoilRootClient } from "@/components/recoilRoot";
const roboto = Roboto({ subsets: ["latin"], weight: "400" }); const roboto = Roboto({ subsets: ["latin"], weight: "400" });
@ -73,15 +72,13 @@ export default function RootLayout({
))} ))}
</ul> </ul>
</nav> </nav>
<RecoilRootClient> <DevToolboxContextProvider
<DevToolboxContextProvider isDev={process.env.NODE_ENV !== "production"}
isDev={process.env.NODE_ENV !== "production"} >
> <main className="p-8 w-full overflow-visible">
<main className="p-8 w-full overflow-visible"> {children}
{children} </main>
</main> </DevToolboxContextProvider>
</DevToolboxContextProvider>
</RecoilRootClient>
<div id="root-portal"></div> <div id="root-portal"></div>
</body> </body>
</html> </html>

View File

@ -1,6 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect class="anvil-base" x="4.5" y="9.5" width="10" height="2" rx="0.5" stroke="inherit" fill="none" />
<path class="anvil-body"
d="M6 1H2C3 2 5.5 2.5 7 3C8.5 3.5 8.54315 4.2918 7 6L6 7V8H13V7C13 7 11.5 6 11 5C10.5 4 11 2.5 15 2V0.5H6V1Z"
stroke="inherit" fill="none" />
</svg>

Before

Width:  |  Height:  |  Size: 385 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" stroke="#fff" fill="#fff" height="28" width="28"><circle stroke-width="3" fill="none" stroke="inherit" r="12.5" cy="14" cx="14"/><path fill="inherit" d="M12.004 17.816v-.867c0-.531.074-1 .223-1.406.148-.414.386-.805.714-1.172.328-.375.762-.758 1.301-1.148.485-.344.871-.653 1.16-.926.297-.274.512-.543.645-.809.14-.273.21-.582.21-.925 0-.508-.187-.895-.562-1.16-.375-.266-.898-.4-1.57-.4s-1.34.106-2.004.317c-.656.211-1.324.489-2.004.832L8.84 7.586c.781-.438 1.629-.79 2.543-1.055.914-.273 1.914-.41 3-.41 1.672 0 2.965.402 3.879 1.207.922.797 1.382 1.813 1.382 3.047 0 .656-.105 1.227-.316 1.71a4.165 4.165 0 01-.937 1.337c-.414.406-.934.836-1.559 1.289-.469.344-.828.633-1.078.867-.25.235-.422.469-.516.703a2.356 2.356 0 00-.129.832v.703zm-.375 4.008c0-.734.2-1.25.598-1.547.406-.297.894-.445 1.464-.445.555 0 1.032.148 1.43.445.406.297.61.813.61 1.547 0 .703-.204 1.211-.61 1.524-.398.312-.875.468-1.43.468-.57 0-1.058-.156-1.464-.468-.399-.313-.598-.82-.598-1.524z"/></svg>

Before

Width:  |  Height:  |  Size: 1017 B

View File

@ -1,70 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="Help Icon.svg"
id="svg6"
version="1.1"
viewBox="0 0 28 28"
stroke="#ffffff"
fill="#ffffff"
height="28"
width="28">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
inkscape:current-layer="svg6"
inkscape:window-maximized="1"
inkscape:window-y="1432"
inkscape:window-x="-8"
inkscape:cy="14"
inkscape:cx="14"
inkscape:zoom="27.222222"
fit-margin-bottom="0"
fit-margin-right="0"
fit-margin-left="0"
fit-margin-top="0"
showgrid="false"
id="namedview8"
inkscape:window-height="1369"
inkscape:window-width="3440"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<circle
id="circle2"
stroke-width="3"
fill="none"
stroke="inherit"
r="12.5"
cy="14"
cx="14" />
<!-- <path d="M0.5,13.5a12.5,12.5 0 1,0 25,0a12.5,12.5 0 1,0 -25,0" fill="none" stroke="inherit" /> -->
<path
id="path4"
fill="inherit"
d="m 12.0039,17.8164 v -0.8672 c 0,-0.5312 0.0742,-1 0.2227,-1.4062 0.1484,-0.4141 0.3867,-0.8047 0.7148,-1.1719 0.3281,-0.375 0.7617,-0.7578 1.3008,-1.1484 0.4844,-0.3438 0.8711,-0.6524 1.1601,-0.9258 0.2969,-0.2735 0.5118,-0.543 0.6446,-0.8086 0.1406,-0.2735 0.2109,-0.5821 0.2109,-0.9258 0,-0.50781 -0.1875,-0.89453 -0.5625,-1.16016 -0.375,-0.26562 -0.8984,-0.39843 -1.5703,-0.39843 -0.6719,0 -1.3398,0.10547 -2.0039,0.3164 -0.6563,0.21094 -1.3242,0.48828 -2.00391,0.83203 L 8.83984,7.58594 c 0.78125,-0.4375 1.62891,-0.78906 2.54296,-1.05469 0.9141,-0.27344 1.9141,-0.41016 3,-0.41016 1.6719,0 2.9649,0.40235 3.8789,1.20703 0.9219,0.79688 1.3828,1.8125 1.3828,3.04688 0,0.6562 -0.1054,1.2266 -0.3164,1.7109 -0.2031,0.4766 -0.5156,0.9219 -0.9375,1.336 -0.414,0.4062 -0.9336,0.8359 -1.5586,1.289 -0.4687,0.3438 -0.8281,0.6329 -1.0781,0.8672 -0.25,0.2344 -0.4219,0.4688 -0.5156,0.7031 -0.086,0.2266 -0.1289,0.504 -0.1289,0.8321 v 0.7031 z m -0.375,4.0078 c 0,-0.7344 0.1992,-1.25 0.5977,-1.5469 0.4062,-0.2968 0.8945,-0.4453 1.4648,-0.4453 0.5547,0 1.0313,0.1485 1.4297,0.4453 0.4062,0.2969 0.6094,0.8125 0.6094,1.5469 0,0.7031 -0.2032,1.211 -0.6094,1.5235 -0.3984,0.3125 -0.875,0.4687 -1.4297,0.4687 -0.5703,0 -1.0586,-0.1562 -1.4648,-0.4687 -0.3985,-0.3125 -0.5977,-0.8204 -0.5977,-1.5235 z" />
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="Help Icon.svg" id="svg6" version="1.1" viewBox="0 0 28 28" stroke="#ffffff" fill="#ffffff">
<metadata id="metadata12">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs10" />
<sodipodi:namedview inkscape:current-layer="svg6" inkscape:window-maximized="1" inkscape:window-y="1432"
inkscape:window-x="-8" inkscape:cy="14" inkscape:cx="14" inkscape:zoom="27.222222" fit-margin-bottom="0"
fit-margin-right="0" fit-margin-left="0" fit-margin-top="0" showgrid="false" id="namedview8"
inkscape:window-height="1369" inkscape:window-width="3440" inkscape:pageshadow="2" inkscape:pageopacity="0"
guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666"
pagecolor="#ffffff" />
<circle id="circle2" stroke-width="3" fill="none" stroke="inherit" r="12.5" cy="14" cx="14" />
<!-- <path d="M0.5,13.5a12.5,12.5 0 1,0 25,0a12.5,12.5 0 1,0 -25,0" fill="none" stroke="inherit" /> -->
<path id="path4" fill="inherit"
d="m 12.0039,17.8164 v -0.8672 c 0,-0.5312 0.0742,-1 0.2227,-1.4062 0.1484,-0.4141 0.3867,-0.8047 0.7148,-1.1719 0.3281,-0.375 0.7617,-0.7578 1.3008,-1.1484 0.4844,-0.3438 0.8711,-0.6524 1.1601,-0.9258 0.2969,-0.2735 0.5118,-0.543 0.6446,-0.8086 0.1406,-0.2735 0.2109,-0.5821 0.2109,-0.9258 0,-0.50781 -0.1875,-0.89453 -0.5625,-1.16016 -0.375,-0.26562 -0.8984,-0.39843 -1.5703,-0.39843 -0.6719,0 -1.3398,0.10547 -2.0039,0.3164 -0.6563,0.21094 -1.3242,0.48828 -2.00391,0.83203 L 8.83984,7.58594 c 0.78125,-0.4375 1.62891,-0.78906 2.54296,-1.05469 0.9141,-0.27344 1.9141,-0.41016 3,-0.41016 1.6719,0 2.9649,0.40235 3.8789,1.20703 0.9219,0.79688 1.3828,1.8125 1.3828,3.04688 0,0.6562 -0.1054,1.2266 -0.3164,1.7109 -0.2031,0.4766 -0.5156,0.9219 -0.9375,1.336 -0.414,0.4062 -0.9336,0.8359 -1.5586,1.289 -0.4687,0.3438 -0.8281,0.6329 -1.0781,0.8672 -0.25,0.2344 -0.4219,0.4688 -0.5156,0.7031 -0.086,0.2266 -0.1289,0.504 -0.1289,0.8321 v 0.7031 z m -0.375,4.0078 c 0,-0.7344 0.1992,-1.25 0.5977,-1.5469 0.4062,-0.2968 0.8945,-0.4453 1.4648,-0.4453 0.5547,0 1.0313,0.1485 1.4297,0.4453 0.4062,0.2969 0.6094,0.8125 0.6094,1.5469 0,0.7031 -0.2032,1.211 -0.6094,1.5235 -0.3984,0.3125 -0.875,0.4687 -1.4297,0.4687 -0.5703,0 -1.0586,-0.1562 -1.4648,-0.4687 -0.3985,-0.3125 -0.5977,-0.8204 -0.5977,-1.5235 z" />
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,8 +0,0 @@
<svg width="35" height="35" viewBox="0 0 23 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 12H21V32C21 33.1046 20.1046 34 19 34H5C3.89543 34 3 33.1046 3 32V12Z" stroke="inherit" fill="none" stroke-width="2"/>
<path d="M7 16L7 29" stroke="inherit" fill="none" stroke-width="2" stroke-linecap="round"/>
<path d="M12 16V29" stroke="inherit" fill="none" stroke-width="2" stroke-linecap="round"/>
<path d="M17 16V29" stroke="inherit" fill="none" stroke-width="2" stroke-linecap="round"/>
<path d="M8.59244 1.36064L12.5317 0.666048C12.8036 0.618097 13.063 0.799681 13.1109 1.07163L13.3714 2.54884L8.44734 3.41708L8.18687 1.93987C8.13891 1.66792 8.3205 1.40859 8.59244 1.36064Z" stroke="inherit" fill="none"/>
<path d="M2.05644 4.54394L19.783 1.41827C20.5988 1.27442 21.3768 1.81917 21.5207 2.63501L21.9548 5.09703L1.27382 8.74365L0.8397 6.28163C0.695845 5.46578 1.2406 4.6878 2.05644 4.54394Z" stroke="inherit" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 943 B

View File

@ -1,12 +0,0 @@
<svg class="trash-can" width="35" height="30" viewBox="0 0 22 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- body -->
<path d="M2 7H20V27C20 28.1046 19.1046 29 18 29H4C2.89543 29 2 28.1046 2 27V7Z" stroke="inherit" fill="none" stroke-width="2"/>
<!-- body lines -->
<path d="M6 11L6 24" stroke="inherit" fill="none" stroke-width="2" stroke-linecap="round"/>
<path d="M11 11V24" stroke="inherit" fill="none" stroke-width="2" stroke-linecap="round"/>
<path d="M16 11V24" stroke="inherit" fill="none" stroke-width="2" stroke-linecap="round"/>
<!-- handle -->
<path class="trash-lid" d="M9 0.5H13C13.2761 0.5 13.5 0.723858 13.5 1V2.5H8.5V1C8.5 0.723858 8.72386 0.5 9 0.5Z" stroke="inherit" fill="none"/>
<!-- cap -->
<path class="trash-lid" d="M2 2.5H20C20.8284 2.5 21.5 3.17157 21.5 4V6.5H0.5V4C0.5 3.17157 1.17157 2.5 2 2.5Z" stroke="inherit" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 872 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -1,6 +0,0 @@
.page {
transition:
transform 500ms,
opacity 300ms,
z-index 0ms 500ms
}

View File

@ -1,46 +0,0 @@
import React, { FC, PropsWithChildren, ReactNode, useState } from "react";
import "./index.css";
interface IProps {
currentPage: number;
}
const AnimatedPageContainer: FC<PropsWithChildren<IProps>> = (
{ children, currentPage },
) => {
const [uuid] = useState(crypto.randomUUID());
const renderChild = (child: ReactNode, index: number) => {
const isActive = index === currentPage;
let position = "active";
switch ((index - currentPage) / Math.abs(index - currentPage)) {
case 1:
position = "right";
break;
case -1:
position = "left";
break;
default:
position = "active";
}
return (
<div
key={`page container ${uuid}: ${index}`}
data-active={isActive}
data-position={position}
className="data-[active=true]:opacity-100 data-[active=true]:static opacity-0 top-0 left-0 absolute page data-[position=left]:-translate-x-96 data-[position=right]:translate-x-96 translate-x-0"
>
{child}
</div>
);
};
return (
<div className="relative overflow-hidden p-2">
{React.Children.map(children, renderChild)}
</div>
);
};
export default AnimatedPageContainer;

View File

@ -1,23 +0,0 @@
import { FC } from "react";
import Help from "../../assets/icons/Help Icon.svg";
import Trash from "../../assets/icons/Trash Icon.svg";
import Trash_hover from "../../assets/icons/Trash Icon Open.svg";
import Anvil from "../../assets/icons/Anvil Icon.svg";
const library = {
Help,
Trash,
Trash_hover,
Anvil,
};
interface IProps {
className: string;
icon: keyof typeof library;
}
export const Icon: FC<IProps> = ({ className, icon }) => {
const ICON = library[icon];
return <ICON className={className} />;
};

View File

@ -1,15 +0,0 @@
import { FC, PropsWithChildren } from "react";
import { Poppable } from "@/lib/poppables/components/poppable";
import { Icon } from "@/components/Icon";
export const HelpPopper: FC<PropsWithChildren> = ({ children }) => {
return (
<Poppable
content={children}
preferredAlign="centered"
preferredEdge="bottom"
>
<Icon icon="Help" className="svg-white w-4 h-4" />
</Poppable>
);
};

View File

@ -1,14 +0,0 @@
import { FC, PropsWithChildren } from "react";
import { Poppable } from "@/lib/poppables/components/poppable";
export const Truncate: FC<PropsWithChildren> = ({ children }) => {
return (
<Poppable
content={children}
preferredAlign="centered"
preferredEdge="top"
>
<p className="truncate max-w-full underline">{children}</p>
</Poppable>
);
};

View File

@ -1,15 +0,0 @@
import { FC } from "react";
interface Props {
strapline?: string;
title: string;
}
export const Heading: FC<Props> = ({ strapline, title }) => {
return (
<section className="heading">
{!!strapline && <h2 className="strapline">{strapline}</h2>}
<h1>{title}</h1>
</section>
);
};

View File

@ -1,5 +0,0 @@
"use client";
import { RecoilRoot } from "recoil";
export const RecoilRootClient = RecoilRoot;

View File

@ -1,135 +0,0 @@
import { FC, useCallback, useEffect, useState } from "react";
import { useObjectStateWrapper } from "../../hooks/useObjectState";
import { ValueField } from "./value-field";
import { HelpPopper } from "../Poppables/help";
import { Icon } from "../Icon";
import { RESERVED_FIELDS } from "../../constants/ReservedFields";
import {
fieldTypeOptions,
FieldTypes,
fieldTypesWithValues,
} from "./fieldtypes";
interface IProps {
update: (arg: FieldType) => void;
field: FieldType;
fieldName: string;
deleteField: (arg: string) => void;
}
export const FieldEditor: FC<IProps> = (
{ update, field, fieldName, deleteField },
) => {
const { bindProperty, bindPropertyCheck } = useObjectStateWrapper(
field,
(e) => update(typeof e === "function" ? e(field) : e),
);
const shouldShowValueField = useCallback(
() => fieldTypesWithValues.includes(field.type) || field.isConstant,
[field.isConstant, field.type],
);
const [reserved, setReserved] = useState<FieldType | undefined>(
RESERVED_FIELDS[fieldName],
);
useEffect(() => {
setReserved(RESERVED_FIELDS[fieldName]);
}, [fieldName]);
// useEffect(() => {
// console.log(field.value);
// }, [field])
return (
<li className="odd:bg-black/50">
<div className="flex gap-2 items-center">
<p>{fieldName}</p>
{reserved && (
<HelpPopper>
<p className="text-xs">
This is a reserved field name, these exist for internal purposes,
but are still useful when creating a type, and as such have
specific settings that you cannot override. If you need control
over the field properties, please use a different field name
</p>
</HelpPopper>
)}
</div>
{!reserved && (
<div className=" flex gap-x-4 items-center p-2 w-full">
<label className="w-min">
Field Type:&nbsp;
<select
className="capitalize"
{...bindProperty("type")}
disabled={!!reserved}
>
{fieldTypeOptions.map((o) => (
<option
key={"fieldtypes" + o}
className="capitalize"
value={FieldTypes[o]}
>
{o}
</option>
))}
</select>
</label>
{shouldShowValueField() && (
<ValueField type={field.type} bind={bindProperty("value")} />
)}
<span className="flex items-center gap-2">
<label>
<input type="checkbox" {...bindPropertyCheck("isConstant")} />
{" "}
Is constant
</label>
<HelpPopper>
<p className="text-sm">
Constant values can&apos;t be overwritten in publications. When
a dice field is set to a constant value, it instead rolls a dice
of that value whenever this field is displayed (unless
exported). This could be useful for a randomly generated
scenario or for cards being drawn as the dice value will
automatically be determined by the dice roll.
</p>
</HelpPopper>
</span>
<label className="w-min">
Minimum:
<input
className="w-12 min-w-min"
type="number"
{...bindProperty("minimum")}
/>
</label>
<label className="w-min">
Limit:
<input className="w-12 min-w-min" type="number" {...bindProperty("limit")} />
</label>
<HelpPopper>
<p className="text-sm">
Minimum and Limit apply to the number of entries allowed for this
field, not the maximum and minimum value. Set the minimum to 0 to
make a field optional. Set the limit to 0 to allow for unlimited
entries.
</p>
</HelpPopper>
<button
className="no-default self-end ml-auto"
onClick={() => deleteField(fieldName)}
>
<Icon
className="svg-red-700 hover:svg-red-500 trash-can w-6 h-6"
icon="Trash"
>
</Icon>
</button>
</div>
)}
</li>
);
};

View File

@ -1,31 +0,0 @@
import { useRecoilValue } from "recoil";
import { SchemaEditAtom } from "../../recoil/atoms/schema";
import { TEMPLATE_TYPES } from "../../constants/TemplateTypes";
import { FC, PropsWithChildren } from "react";
interface IProps {
bind: InputBinder;
}
export const FieldTypeInput: FC<PropsWithChildren<IProps>> = ({ bind }) => {
const schema = useRecoilValue(SchemaEditAtom);
return (
<label className="w-min">
Type:
<input type="text" {...bind} list="type-editor-type-list" />
<datalist id="type-editor-type-list">
{Object.keys(TEMPLATE_TYPES).map((k) => (
<option key={"templatetypes" + k} className="capitalize" value={k}>
{k}
</option>
))}
{Object.keys(schema.types).map((k) => (
<option key={"schematypes" + k} className="capitalize" value={k}>
{k}
</option>
))}
</datalist>
</label>
);
};

View File

@ -1,26 +0,0 @@
export const fieldTypeOptions: (keyof typeof FieldTypes)[] = [
"number",
"text",
"long text",
"checkbox",
"type",
"dice",
"select",
"any",
];
export enum FieldTypes {
number = "number",
text = "text",
"long text" = "long text",
checkbox = "checkbox",
type = "@type",
dice = "dice",
any = "@select",
select = "select",
}
export const fieldTypesWithValues = [
FieldTypes.dice,
FieldTypes.type,
FieldTypes.select,
FieldTypes.any,
];

View File

@ -1,199 +0,0 @@
"use client";
import { FC, useCallback, useState } from "react";
import AnimatedPageContainer from "@/components/AnimatedPageContainer";
import { TypeEditor } from "./type-editor";
import { useObjectStateWrapper } from "@/hooks/useObjectState";
import { useInput } from "../../hooks/useInput";
import { useRecoilState, useResetRecoilState } from "recoil";
import { SchemaEditAtom } from "@/recoil/atoms/schema";
import { SchemaViewer } from "./schema-viewer";
import { TemplateEditor } from "./template-editor";
import { Icon } from "@/components/Icon";
import { useParams } from "next/navigation";
import { FieldTypes } from "./fieldtypes";
export const SchemaBuilder: FC = () => {
const [schema, setSchema] = useRecoilState(SchemaEditAtom);
const resetSchema = useResetRecoilState(SchemaEditAtom);
const { update: updateSchema, bindProperty: bindSchemaProperty } =
useObjectStateWrapper<Schema>(schema, setSchema);
const { schemaId } = useParams<{ schemaId: string }>();
const { value: typeName, bind: bindTypeName, reset: resetTypeName } =
useInput("");
const [pageNumber, setPageNumber] = useState(0);
const [lastSaved, setLastSaved] = useState(schema);
const [selectedType, setSelectedType] = useState("");
const saveType = useCallback((name: string, type: TypeType) => {
updateSchema((e) => ({
types: {
...e.types,
[name]: type,
},
}));
resetTypeName();
setPageNumber(0);
setSelectedType("");
}, [resetTypeName, updateSchema]);
const saveSchema = useCallback(async () => {
setLastSaved(schema);
// const sid = await GameSystemsService.saveSchema(schema);
// if (schemaId === 'new') navigate('/schema/'+sid)
}, [schema]);
const selectTypeForEdit = useCallback((typeKey: string) => {
setSelectedType(typeKey);
setPageNumber(1);
}, []);
const {
value: schemaFieldName,
bind: bindSchemaFieldName,
reset: resetSchemaFieldName,
} = useInput("", { disallowSpaces: true });
const addSchemaField = useCallback(() => {
updateSchema((s) => ({
schema: {
...s.schema,
[schemaFieldName]: {
display: "",
type: FieldTypes.any,
},
},
}));
resetSchemaFieldName();
}, [resetSchemaFieldName, schemaFieldName, updateSchema]);
const updateSchemaField = useCallback((key: string, template: Template) => {
updateSchema((s) => ({
schema: {
...s.schema,
[key]: template,
},
}));
}, [updateSchema]);
const deleteType = useCallback((key: string) => {
updateSchema((s) => {
const types = { ...s.types };
delete types[key];
return { types };
});
}, [updateSchema]);
return (
<div className="flex gap-4 p-8">
<div className="panel w-2/3 h-full flex flex-col gap-4">
<div>
<input
type="text"
{...bindSchemaProperty("name")}
placeholder="Schema Name"
/>
</div>
<div>
<p className="subheader mb-2">Add Schema Field</p>
<div className="mb-2">
<input type="text" {...bindSchemaFieldName} />
<button onClick={addSchemaField} disabled={!schemaFieldName}>
Add
</button>
</div>
<ul className="rounded-lg overflow-hidden">
{Object.entries(schema.schema).map((
[schemaFieldKey, schemaField],
) => (
<TemplateEditor
key={schemaFieldKey}
templateKey={schemaFieldKey}
template={schemaField}
update={updateSchemaField}
/>
))}
</ul>
</div>
<hr />
<div>
<AnimatedPageContainer currentPage={pageNumber}>
<div>
<p className="subheader mb-2">Add a type</p>
<input type="text" {...bindTypeName} />
<button
className="interactive"
disabled={!typeName}
onClick={() => setPageNumber(1)}
>
Configure
</button>
</div>
<TypeEditor
name={selectedType || typeName}
saveType={saveType}
type={selectedType
? schema.types[selectedType as keyof typeof schema.types]
: undefined}
/>
</AnimatedPageContainer>
<ul className="mt-3 w-96">
{Object.keys(schema.types).map((t) => (
<li
key={"type" + t}
className="odd:bg-black/50 flex justify-between p-2"
>
{t}
<div className="flex gap-3">
<button
title="Edit"
className="no-default"
onClick={() => selectTypeForEdit(t)}
>
<Icon
icon="Anvil"
className="anvil svg-olive-drab hover:svg-olive-drab-100 w-6 h-6"
/>
</button>
<button
title="Delete"
className="no-default"
onClick={() => deleteType(t)}
>
<Icon
icon="Trash"
className="trash-can svg-red-700 hover:svg-red-500 w-6 h-6"
/>
</button>
</div>
</li>
))}
</ul>
</div>
</div>
<div className="panel basis-1/3">
<div className="flex gap-2 mb-2">
<button
className="btn btn-small bg-green-800"
onClick={saveSchema}
disabled={lastSaved === schema}
>
Save Schema
</button>
<button
className="bg-red-800 btn btn-small"
onClick={() => setSchema(lastSaved)}
disabled={lastSaved === schema}
>
Discard Changes
</button>
</div>
<SchemaViewer schema={schema} onTypeClick={selectTypeForEdit} />
</div>
</div>
);
};

View File

@ -1,94 +0,0 @@
import { FC, useCallback } from "react";
import { Truncate } from "@/components/Poppables/truncation";
import { Accordion, AccordionContent } from "../../lib/accordion";
import { FieldTypes, fieldTypesWithValues } from "./fieldtypes";
interface IProps {
schema: Schema;
onTypeClick?: (arg: string, arg1: TypeType) => void;
}
export const SchemaViewer: FC<IProps> = ({ schema, onTypeClick }) => {
const createValueLable = useCallback((field: FieldType) => {
if (field.isConstant) {
if (field.type === FieldTypes.dice) return "Auto-rolled";
return "Constant value:";
}
switch (field.type) {
case FieldTypes.type:
return "Type:";
case FieldTypes.dice:
return "Dice:";
case FieldTypes.select:
return "Options:";
default:
return "";
}
}, []);
return (
<>
{/* <div className="whitespace-pre-wrap">{JSON.stringify(schema, null, 2)}</div> */}
<div>
<p className="font-bold text-lg">{schema.name}</p>
<hr />
<p className="font-bold italic">Templates</p>
<ul>
{Object.entries(schema.schema).map(([templateKey, template]) => (
<li key={templateKey}>
<p className="font-bold">{templateKey}</p>
<p className="font-thin text-xs">{template.type}</p>
</li>
))}
</ul>
<hr />
<p className="font-bold italic">Types</p>
<ul className="rounded-lg overflow-hidden grid">
{Object.entries(schema.types).map(([typeKey, type]) => (
<li
key={"type viewer" + typeKey}
// onClick={() => onTypeClick && onTypeClick(typeKey, type)}
data-clickable={!!onTypeClick}
className="odd:bg-black/50 p-2 group overflow-hidden"
>
<Accordion
title={
<p className="group-data-[expanded]/controlled:mb-2 transition-all font-bold">
{typeKey}
</p>
}
>
<AccordionContent>
<div className="grid grid-cols-2 gap-2">
{Object.entries(type).map(([fieldKey, field]) => (
<div
key={"field viewer" + fieldKey}
className="rounded-lg border border-olive-drab p-2"
>
<p className="font-bold">{fieldKey}</p>
<p className="font-thin capitalize text-xs">
{field.type}
</p>
<p className="font-thin capitalize text-xs">
Maximum entries:{" "}
{field.limit === 0 ? "unlimited " : field.limit}
</p>
{(field.isConstant ||
fieldTypesWithValues.includes(field.type)) && (
<p className="font-thin capitalize text-xs">
{createValueLable(field)}{" "}
<Truncate>{field.value}</Truncate>
</p>
)}
</div>
))}
</div>
</AccordionContent>
</Accordion>
</li>
))}
</ul>
</div>
</>
);
};

View File

@ -1,75 +0,0 @@
import { FC, useCallback } from "react";
import { useObjectStateWrapper } from "@/hooks/useObjectState";
import { TEMPLATE_TYPES } from "@/constants/TemplateTypes";
import { SchemaEditAtom } from "@/recoil/atoms/schema";
import { useRecoilState } from "recoil";
import { Icon } from "@/components/Icon";
interface IProps {
templateKey: string;
update: (arg0: string, arg1: Template) => void;
template: Template;
}
export const TemplateEditor: FC<IProps> = (
{ templateKey, update, template },
) => {
const [schema, setSchema] = useRecoilState(SchemaEditAtom);
const updateTemplate = useCallback(
(t: Template | ((arg: Template) => Template)) => {
update(templateKey, typeof t === "function" ? t(template) : t);
},
[templateKey, update, template],
);
const { bindProperty } = useObjectStateWrapper(
template,
updateTemplate,
);
const deleteTemplate = useCallback(() => {
setSchema((s: Schema) => {
const templates = { ...s.schema };
delete templates[templateKey];
return {
...s,
schema: templates,
};
});
}, [setSchema, templateKey]);
return (
<li className="odd:bg-black/50 p-2">
<p className="font-bold">{templateKey}</p>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4 pl-2">
<label className="w-min">
Type:
<input
type="text"
{...bindProperty("type", { disallowSpaces: true })}
list="type-editor-type-list"
/>
<datalist id="type-editor-type-list">
{Object.keys(TEMPLATE_TYPES).map((k) => (
<option key={"templatetype" + k} value={k}>{k}</option>
))}
{Object.keys(schema.types).map((k) => (
<option key={"schematype" + k} value={k}>{k}</option>
))}
</datalist>
</label>
<textarea {...bindProperty("display")} cols={30} rows={10}></textarea>
</div>
<button
className="no-default"
onClick={deleteTemplate}
>
<Icon
icon="Trash"
className="svg-red-700 hover:svg-red-500 trash-can w-6 h-6"
/>
</button>
</div>
</li>
);
};

View File

@ -1,104 +0,0 @@
import {
FC,
FormEvent,
PropsWithChildren,
useCallback,
useEffect,
} from "react";
import { useObjectState } from "../../hooks/useObjectState";
import { useInput } from "../../hooks/useInput";
import { FieldEditor } from "./field-editor";
import { FieldTypes } from "./fieldtypes";
interface IProps {
name: string;
saveType: (arg0: string, arg1: TypeType) => void;
type?: TypeType;
}
const constantProperties = ["metadata"];
export const TypeEditor: FC<PropsWithChildren<IProps>> = (
{ saveType, name, type: passedType },
) => {
const {
update: updateType,
reset: resetType,
state: type,
setState: setType,
} = useObjectState<TypeType>({});
const {
value: propertyName,
bind: bindPropertyName,
reset: resetPropertyName,
} = useInput("", { disallowSpaces: true });
const save = () => {
saveType(name, type);
resetType();
};
const addField = useCallback((e: FormEvent) => {
e.preventDefault();
updateType({
[propertyName]: {
type: FieldTypes.number,
value: "",
isConstant: false,
limit: 1,
minimum: 1,
},
});
resetPropertyName();
}, [propertyName, updateType, resetPropertyName]);
const updateField = useCallback(
(k: keyof typeof type) => (field: FieldType) => {
updateType({ [k]: field });
},
[updateType],
);
useEffect(() => {
passedType && setType(passedType);
}, [passedType, setType]);
const deleteField = useCallback((name: string) => {
setType((t) => {
const fields = { ...t };
delete fields[name];
return fields;
});
}, [setType]);
return (
<div>
<p className="subheader">
{passedType ? "Editing" : "Creating"} type &quot;{name}&quot;
</p>
<form onSubmit={addField}>
<input type="text" {...bindPropertyName} />
<button disabled={!propertyName}>Add Field</button>
</form>
<ul className="rounded-lg overflow-hidden">
{Object.entries(type).reverse().filter(([k]) =>
!constantProperties.includes(k)
).map(([key, value]) => (
<FieldEditor
key={"field-editor" + key}
field={value}
update={updateField(key)}
fieldName={key}
deleteField={deleteField}
/>
))}
</ul>
<div>
<button onClick={save} disabled={!Object.keys(type).length}>
Save Type
</button>
</div>
</div>
);
};

View File

@ -1,129 +0,0 @@
import { ChangeEvent, FC, useRef } from "react";
import { FieldTypeInput } from "./field-type-input";
import { useInput } from "../../hooks/useInput";
import { HelpPopper } from "../Poppables/help";
import { FieldTypes } from "./fieldtypes";
interface IValueProps {
type: FieldTypes;
bind: InputBinder;
}
const DICE_SIDES = [3, 4, 6, 8, 10, 12, 20, 100];
export const ValueField: FC<IValueProps> = ({ type, bind }) => {
const { value: diceCount, bind: bindDiceCount } = useInput(1);
const { value: diceSides, bind: bindDiceSides } = useInput("");
const diceInputRef = useRef<HTMLInputElement>(null);
switch (type) {
case FieldTypes.dice: {
const onChange = (
handler: (
arg: ChangeEvent<HTMLInputElement | HTMLSelectElement>,
) => void,
) =>
(e: ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
handler(e);
setTimeout(() => {
if (!diceInputRef.current) return;
e.target = diceInputRef.current;
bind.onChange(e);
}, 0);
};
return (
<>
<label className="w-min">
Count:&nbsp;
<input
className="w-12"
type="number"
{...bindDiceCount}
onChange={onChange(bindDiceCount.onChange)}
/>
</label>
<label className="w-min">
Sides:&nbsp;
<select
{...bindDiceSides}
onChange={onChange(bindDiceSides.onChange)}
>
<option value=""></option>
{DICE_SIDES.map((d) => (
<option key={"dice sides" + d} value={"d" + d}>{d}</option>
))}
</select>
</label>
<input
ref={diceInputRef}
className="hidden"
type="text"
name={bind.name}
value={diceCount + diceSides}
readOnly
/>
</>
);
}
case FieldTypes.type:
return <FieldTypeInput bind={bind} />;
case FieldTypes.number:
return (
<label className="w-min">
Value:<input className="w-16" type="number" {...bind} />
</label>
);
case FieldTypes.text:
return (
<label className="w-min">
Value:<input type="text" {...bind} />
</label>
);
case FieldTypes.select:
return (
<>
<label className="w-min">
<div className="flex gap-2 items-center">
Values:
<HelpPopper>
<p className="text-xs">
A comma separated list (no spaces, spaces are reserved for
values) of options that can be chosen while creating
publications. Ex: earthquake,wind storm,fire tornado,rainbow.
Alternatively, you can specify a display value and an actual
value separated with a colon. This is useful for when you want
to create a reference in a publication with a dropdown field.
Ex: Rapid Fire:^core.weaponAbilities[name=rapid
fire],Heavy:^core.weaponAbilities[name=heavy]
</p>
</HelpPopper>
</div>
<input type="text" {...bind} />
</label>
</>
);
case FieldTypes.any:
return (
<>
<label className="w-min">
<div className="flex gap-2 items-center">
Type options:
<HelpPopper>
<p className="text-xs">
A comma separated list (no spaces, spaces are reserved for
values) of options that are names of types that can be
selected when creating a publication, Ex: dice,number,text. Do
not leave this blank, allowing for any type to be selected
makes querying gross.
</p>
</HelpPopper>
</div>
<input type="text" {...bind} />
</label>
</>
);
default:
return <></>;
}
};

View File

@ -2,7 +2,7 @@ import { createElements } from "@/lib/tcmd";
import React, { FC, Suspense, useEffect, useMemo, useState } from "react"; import React, { FC, Suspense, useEffect, useMemo, useState } from "react";
import { MDSkeletonLoader } from "../loader"; import { MDSkeletonLoader } from "../loader";
import { DevTool } from "../devtools/DevTool"; import { DevTool } from "../devtools/Toolbox";
interface Props { interface Props {
body: string; body: string;

View File

@ -1,25 +0,0 @@
import { FieldTypes } from "@/components/schema/fieldtypes";
export const RESERVED_FIELDS: Record<string, FieldType> = {
maximum: {
isConstant: false,
limit: 1,
minimum: 1,
type: FieldTypes.number,
value: "",
},
minimum: {
isConstant: false,
limit: 1,
minimum: 1,
type: FieldTypes.number,
value: "",
},
relative: {
isConstant: true,
limit: 1,
minimum: 1,
type: FieldTypes.text,
value: "$",
},
};

View File

@ -1,95 +0,0 @@
import { FieldTypes } from "@/components/schema/fieldtypes";
export const TEMPLATE_TYPES: Record<string, TypeType> = {
section: {
name: {
isConstant: false,
limit: 1,
minimum: 1,
type: FieldTypes.text,
value: "",
},
body: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes["long text"],
value: "",
},
},
steps: {
steps: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes.type,
value: "section",
},
},
image: {
name: {
isConstant: false,
limit: 1,
minimum: 1,
type: FieldTypes.text,
value: "",
},
link: {
isConstant: false,
limit: 1,
minimum: 1,
type: FieldTypes.text,
value: "",
},
},
list: {
items: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes["long text"],
value: "",
},
},
table_column: {
name: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes.any,
value: "",
},
value: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes.any,
value: "",
},
},
table_row: {
columns: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes.type,
value: "tableColumn",
},
},
table: {
rows: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes.type,
value: "tableRow",
},
header: {
isConstant: false,
limit: 1,
minimum: 0,
type: FieldTypes.type,
value: "tableRow",
},
},
};

7
hooks/types.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
export type InputBinder = {
name: string;
value: string | number;
onChange: (
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
) => void;
};

View File

@ -17,7 +17,7 @@ export const Portal: FC<PropsWithChildren<IProps>> = (
document.body.appendChild(container); document.body.appendChild(container);
setContainer(container); setContainer(container);
return () => { return () => {
document.body.contains(container) && document.body.removeChild(container); document.body.removeChild(container);
}; };
}, [className, el]); }, [className, el]);

View File

@ -8,7 +8,7 @@ export const TokenRenderers = new Map<string, TokenRenderer<any>>();
export function buildIdentifierMap(): [ export function buildIdentifierMap(): [
TokenIdentifierMap, TokenIdentifierMap,
IdentifierRegistration IdentifierRegistration,
] { ] {
const TokenIdentifiers = new Map<string, TokenIdentifier<any>>(); const TokenIdentifiers = new Map<string, TokenIdentifier<any>>();
@ -16,7 +16,7 @@ export function buildIdentifierMap(): [
type: string, type: string,
match: RegExp, match: RegExp,
parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>, parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>,
renderFunction: TokenRenderer<M> renderFunction: TokenRenderer<M>,
): void; ): void;
function registerIdentifier<M>( function registerIdentifier<M>(
type: string, type: string,
@ -24,7 +24,7 @@ export function buildIdentifierMap(): [
parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>, parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>,
renderFunction: TokenRenderer<M>, renderFunction: TokenRenderer<M>,
openTagRx: RegExp, openTagRx: RegExp,
closeTagRx: RegExp closeTagRx: RegExp,
): void; ): void;
function registerIdentifier<M = Record<string, string>>( function registerIdentifier<M = Record<string, string>>(
type: string, type: string,
@ -32,7 +32,7 @@ export function buildIdentifierMap(): [
parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>, parseFunction: (s: string, rx: RegExp) => IdentifiedToken<M>,
renderFunction: TokenRenderer<M>, renderFunction: TokenRenderer<M>,
openTagRx?: RegExp, openTagRx?: RegExp,
closeTagRx?: RegExp closeTagRx?: RegExp,
) { ) {
TokenIdentifiers.set(type, { TokenIdentifiers.set(type, {
rx: match, rx: match,
@ -45,18 +45,17 @@ export function buildIdentifierMap(): [
return { ...token, ...identifiedToken } as Token<M>; return { ...token, ...identifiedToken } as Token<M>;
}, },
search: search: openTagRx && closeTagRx
openTagRx && closeTagRx ? (s, start, end) => {
? (s, start, end) => { return search(
return search( s,
s, start,
start, end,
end, new RegExp(openTagRx, "g"),
new RegExp(openTagRx, "g"), new RegExp(closeTagRx, "g"),
new RegExp(closeTagRx, "g") );
); }
} : undefined,
: undefined,
}); });
TokenRenderers.set(type, renderFunction); TokenRenderers.set(type, renderFunction);
} }
@ -68,6 +67,7 @@ export const buildOnlyDefaultElements = () => {
const [TokenIdentifiers, registerIdentifier] = buildIdentifierMap(); const [TokenIdentifiers, registerIdentifier] = buildIdentifierMap();
TokenRenderers.set("text", (t: Token<any>) => { TokenRenderers.set("text", (t: Token<any>) => {
debugger;
return ( return (
<span className="whitespace-pre-wrap"> <span className="whitespace-pre-wrap">
{t.content.replaceAll(/\\n ?/g, "\n")} {t.content.replaceAll(/\\n ?/g, "\n")}
@ -105,26 +105,20 @@ export const buildOnlyDefaultElements = () => {
const { children, metadata } = token; const { children, metadata } = token;
return ( return (
<div <div
style={ style={{
{ "--grid-cols": metadata.columns,
"--grid-cols": metadata.columns, } as React.CSSProperties}
} as React.CSSProperties
}
className="grid grid-cols-dynamic gap-x-8 mb-6" className="grid grid-cols-dynamic gap-x-8 mb-6"
> >
{children?.map((c) => { {children?.map((c) => {
const Comp = c.metadata.span ? Fragment : "div"; const Comp = c.metadata.span ? Fragment : "div";
return ( return <Comp className="p" key={c.uuid}>{c.render(c)}</Comp>;
<Comp className="p" key={c.uuid}>
{c.render(c)}
</Comp>
);
})} })}
</div> </div>
); );
}, },
/(?<!\/)(?:\[\])+/g, /(?<!\/)(?:\[\])+/g,
/\/\[\]/g /\/\[\]/g,
); );
// card // card
@ -155,21 +149,18 @@ export const buildOnlyDefaultElements = () => {
return ( return (
<div <div
data-block={!!metadata.isBlock} data-block={!!metadata.isBlock}
style={ style={{
{ "--v-span": metadata.span || 1,
"--v-span": metadata.span || 1, } as React.CSSProperties}
} as React.CSSProperties
}
className="data-[block=false]:card data-[block=false]:mb-6 col-span-2" className="data-[block=false]:card data-[block=false]:mb-6 col-span-2"
> >
{children?.map((e) => ( {children?.map((e) => <Fragment key={e.uuid}>{e.render(e)}
<Fragment key={e.uuid}>{e.render(e)}</Fragment> </Fragment>)}
))}
</div> </div>
); );
}, },
/\[\[/g, /\[\[/g,
/\]\]/g /\]\]/g,
); );
// fenced code block // fenced code block
@ -191,16 +182,16 @@ export const buildOnlyDefaultElements = () => {
{token.content} {token.content}
</pre> </pre>
); );
} },
); );
// list // list
registerIdentifier( registerIdentifier(
"list", "list",
/(?<=\n\n|^) *-\s([\s\S]*?)(?=\n\n|$)/g, /^\s*-\s([\s\S]*?)\n\n/gm,
(s, rx) => { (s, rx) => {
return { return {
content: s.match(new RegExp(rx, ""))?.at(0) || "Unable to parse list", content: s.match(new RegExp(rx, ""))?.at(1) || "Unable to parse list",
raw: s, raw: s,
metadata: { metadata: {
initialDepth: initialDepth:
@ -230,60 +221,17 @@ export const buildOnlyDefaultElements = () => {
</ul> </ul>
</> </>
); );
}
);
// ordered-list
registerIdentifier(
"ordered-list",
/(?<=\n\n|^)\s*\d+\.\s([\s\S]*?)(?=\n\n|$)/g,
(s, rx) => {
// debugger;
return {
content:
s.match(new RegExp(rx, ""))?.at(0) || "Unable to parse ordered list",
raw: s,
metadata: {
// initialDepth:
// s.replace("\n", "").split(/\d+\./).at(0)?.length.toString() || "1",
},
uuid: crypto.randomUUID(),
rendersChildrenOnly,
};
}, },
(token) => {
const { children } = token;
debugger;
return (
<>
<ol
// data-depth={(Number(metadata.initialDepth) / 2) % 3}
className="ml-6 list-decimal"
>
{children?.map((c) => {
return (
<li key={c.uuid}>
{c.children?.map((c: Token<any>) => (
<Fragment key={c.uuid}>{c.render(c)}</Fragment>
))}
</li>
);
})}
</ol>
</>
);
}
); );
// ordered list-item
// list-item // list-item
registerIdentifier( registerIdentifier(
"list-item", "list-item",
/(?<=^|\n) *(?:-|\d+\.)\s(.*?)(?=\n|$)/g, /^\s*-\s(.*?)$/gm,
(s, rx) => { (s, rx) => {
return { return {
content: content: s.match(new RegExp(rx, ""))?.at(1) ||
s.match(new RegExp(rx, ""))?.at(1) || "Unable to parse list-item", "Unable to parse list-item",
raw: s, raw: s,
metadata: { metadata: {
initialDepth: initialDepth:
@ -297,11 +245,13 @@ export const buildOnlyDefaultElements = () => {
return ( return (
<li data-depth={metadata.initialDepth} className="ml-2"> <li data-depth={metadata.initialDepth} className="ml-2">
{children?.map((c) => ( {children?.map((c) => (
<Fragment key={c.uuid}>{c.render(c)}</Fragment> <Fragment key={c.uuid}>
(c.render(c))
</Fragment>
))} ))}
</li> </li>
); );
} },
); );
// heading // heading
@ -309,8 +259,8 @@ export const buildOnlyDefaultElements = () => {
"heading", "heading",
/^#+\s(.*?)$/gm, /^#+\s(.*?)$/gm,
(s, rx) => { (s, rx) => {
const content = const content = s.match(new RegExp(rx, ""))?.at(1) ||
s.match(new RegExp(rx, ""))?.at(1) || "Unable to parse heading"; "Unable to parse heading";
return { return {
content: content, content: content,
raw: s, raw: s,
@ -337,7 +287,7 @@ export const buildOnlyDefaultElements = () => {
{token.content} {token.content}
</div> </div>
); );
} },
); );
// image // image
@ -369,12 +319,13 @@ export const buildOnlyDefaultElements = () => {
USE_PROFILES: { svg: true }, USE_PROFILES: { svg: true },
}), }),
}} }}
></div> >
</div>
); );
} }
// eslint-disable-next-line @next/next/no-img-element // eslint-disable-next-line @next/next/no-img-element
return <img src={metadata.src} alt={token.content} />; return <img src={metadata.src} alt={token.content} />;
} },
); );
// anchor // anchor
@ -409,16 +360,14 @@ export const buildOnlyDefaultElements = () => {
const { metadata } = token; const { metadata } = token;
return ( return (
<Link <Link
className={ className={metadata.classes ||
metadata.classes || "dark:text-primary-600 underline dark:no-underline"}
"dark:text-primary-600 underline dark:no-underline"
}
href={metadata.href} href={metadata.href}
> >
{token.content} {token.content}
</Link> </Link>
); );
} },
); );
// inline-code // inline-code
@ -427,8 +376,8 @@ export const buildOnlyDefaultElements = () => {
/(?<=\s|^)`(.*?)`(?=[\s,.!?)]|$)/gi, /(?<=\s|^)`(.*?)`(?=[\s,.!?)]|$)/gi,
(s, rx) => { (s, rx) => {
return { return {
content: content: s.match(new RegExp(rx, "i"))?.at(1) ||
s.match(new RegExp(rx, "i"))?.at(1) || "Unable to parse inline-code", "Unable to parse inline-code",
raw: s, raw: s,
metadata: {}, metadata: {},
uuid: crypto.randomUUID(), uuid: crypto.randomUUID(),
@ -441,7 +390,7 @@ export const buildOnlyDefaultElements = () => {
{token.content} {token.content}
</span> </span>
); );
} },
); );
// bold // bold
@ -459,7 +408,7 @@ export const buildOnlyDefaultElements = () => {
}, },
(token) => { (token) => {
return <span className="font-bold">{token.content}</span>; return <span className="font-bold">{token.content}</span>;
} },
); );
// italic // italic
@ -468,8 +417,8 @@ export const buildOnlyDefaultElements = () => {
/(?<!\*)\*([^\*]+?)\*(?!\*)/g, /(?<!\*)\*([^\*]+?)\*(?!\*)/g,
(s, rx) => { (s, rx) => {
return { return {
content: content: s.match(new RegExp(rx, "i"))?.at(1) ||
s.match(new RegExp(rx, "i"))?.at(1) || "Unable to parse italic", "Unable to parse italic",
raw: s, raw: s,
metadata: {}, metadata: {},
uuid: crypto.randomUUID(), uuid: crypto.randomUUID(),
@ -478,7 +427,7 @@ export const buildOnlyDefaultElements = () => {
}, },
(token) => { (token) => {
return <span className="italic">{token.content}</span>; return <span className="italic">{token.content}</span>;
} },
); );
// popover // popover
@ -500,11 +449,9 @@ export const buildOnlyDefaultElements = () => {
const { children, metadata, uuid } = token; const { children, metadata, uuid } = token;
return ( return (
<Poppable <Poppable
content={ content={children?.map((c) => (
children?.map((c) => ( <Fragment key={uuid}>{c.render(c)}</Fragment>
<Fragment key={uuid}>{c.render(c)}</Fragment> )) || token.content}
)) || token.content
}
preferredAlign="centered" preferredAlign="centered"
preferredEdge="bottom" preferredEdge="bottom"
className="cursor-pointer mx-2" className="cursor-pointer mx-2"
@ -514,7 +461,7 @@ export const buildOnlyDefaultElements = () => {
</span> </span>
</Poppable> </Poppable>
); );
} },
); );
registerIdentifier( registerIdentifier(
@ -543,7 +490,7 @@ export const buildOnlyDefaultElements = () => {
</Accordion> </Accordion>
</div> </div>
); );
} },
); );
registerIdentifier( registerIdentifier(
@ -560,6 +507,8 @@ export const buildOnlyDefaultElements = () => {
(token) => { (token) => {
const { children } = token; const { children } = token;
debugger;
return ( return (
<div className="p"> <div className="p">
{children?.map((e) => { {children?.map((e) => {
@ -567,7 +516,7 @@ export const buildOnlyDefaultElements = () => {
})} })}
</div> </div>
); );
} },
); );
registerIdentifier( registerIdentifier(
@ -584,7 +533,7 @@ export const buildOnlyDefaultElements = () => {
}, },
() => { () => {
return <div className="w-full border-b border-mixed-500 my-3"></div>; return <div className="w-full border-b border-mixed-500 my-3"></div>;
} },
); );
registerIdentifier( registerIdentifier(
@ -601,7 +550,7 @@ export const buildOnlyDefaultElements = () => {
}, },
() => { () => {
return <></>; return <></>;
} },
); );
registerIdentifier( registerIdentifier(
@ -619,7 +568,7 @@ export const buildOnlyDefaultElements = () => {
}, },
(token) => { (token) => {
return <>{token.raw}</>; return <>{token.raw}</>;
} },
); );
registerIdentifier( registerIdentifier(
@ -673,7 +622,7 @@ export const buildOnlyDefaultElements = () => {
} }
const maxColumns = Math.max( const maxColumns = Math.max(
...[...headerRows, ...bodyRows, ...footerRows].map((r) => r.length) ...[...headerRows, ...bodyRows, ...footerRows].map((r) => r.length),
); );
return { return {
@ -731,12 +680,8 @@ export const buildOnlyDefaultElements = () => {
<td <td
key={r.join() + i + c} key={r.join() + i + c}
className="data-[center=true]:text-center" className="data-[center=true]:text-center"
data-center={ data-center={!!(columnPattern?.at(i) &&
!!( columnPattern.at(i)?.includes("^"))}
columnPattern?.at(i) &&
columnPattern.at(i)?.includes("^")
)
}
> >
{child?.render(child) || c} {child?.render(child) || c}
</td> </td>
@ -764,7 +709,7 @@ export const buildOnlyDefaultElements = () => {
)} )}
</table> </table>
); );
} },
); );
return TokenIdentifiers; return TokenIdentifiers;
@ -773,7 +718,7 @@ export const buildOnlyDefaultElements = () => {
function findMatchingClosedParenthesis( function findMatchingClosedParenthesis(
str: string, str: string,
openRegex: RegExp, openRegex: RegExp,
closedRegex: RegExp closedRegex: RegExp,
): number | null { ): number | null {
let openings = 0; let openings = 0;
let closings = 0; let closings = 0;
@ -829,7 +774,7 @@ function search(
start: number, start: number,
end: number, end: number,
openRx: RegExp, openRx: RegExp,
closeRx: RegExp closeRx: RegExp,
): SearchResult { ): SearchResult {
const oldEnd = end; const oldEnd = end;
@ -837,7 +782,7 @@ function search(
s, s,
// s.substring(0, end - start), // s.substring(0, end - start),
openRx, openRx,
closeRx closeRx,
); );
if (newEnd === null) throw Error("There was an issue finding a closing tag"); if (newEnd === null) throw Error("There was an issue finding a closing tag");

View File

@ -5,7 +5,6 @@ import { buildOnlyDefaultElements, TokenRenderers } from "./TokenIdentifiers";
export const createElements = (body: string): Token[] => { export const createElements = (body: string): Token[] => {
const tokens = tokenize(body); const tokens = tokenize(body);
console.log(tokens);
return buildAbstractSyntaxTree(tokens).map((t) => t.token); return buildAbstractSyntaxTree(tokens).map((t) => t.token);
}; };
@ -110,7 +109,7 @@ type ParentChildMap = {
}; };
const parentChildMap: ParentChildMap = { const parentChildMap: ParentChildMap = {
list: ["list-item"], "list": ["list-item"],
// Add more mappings as needed... // Add more mappings as needed...
}; };
@ -129,8 +128,10 @@ function filterOverlappingPBlocks(blocks: TokenMarker[]): TokenMarker[] {
for (const otherBlock of blocks) { for (const otherBlock of blocks) {
if ( if (
otherBlock !== block && otherBlock !== block &&
(otherBlock.start === block.start || (
(otherBlock.end === block.end && otherBlock.start < block.start)) otherBlock.start === block.start ||
(otherBlock.end === block.end && otherBlock.start < block.start)
)
) { ) {
return false; return false;
} }
@ -154,32 +155,29 @@ const contentToChildren = (token: Token) => {
} }
token.children = zipArrays( token.children = zipArrays(
content.split(splitMarker).map( content.split(splitMarker).map((c): Token => ({
(c): Token => ({ content: c.replaceAll("\n", " "),
content: c.replaceAll("\n", " "), metadata: {},
metadata: {}, raw: c,
raw: c, type: token.rendersChildrenOnly ? "p" : "text",
type: token.rendersChildrenOnly ? "p" : "text", uuid: crypto.randomUUID(),
uuid: crypto.randomUUID(), rendersContentOnly: token.rendersChildrenOnly ? false : true,
rendersContentOnly: token.rendersChildrenOnly ? false : true, render: TokenRenderers.get(token.rendersChildrenOnly ? "p" : "text")!,
render: TokenRenderers.get(token.rendersChildrenOnly ? "p" : "text")!, children: token.rendersChildrenOnly && c.replaceAll("\n", "")
children: ? [
token.rendersChildrenOnly && c.replaceAll("\n", "") {
? [ content: c.replaceAll("\n", " "),
{ metadata: {},
content: c.replaceAll("\n", " "), raw: c,
metadata: {}, type: "text",
raw: c, uuid: crypto.randomUUID(),
type: "text", render: TokenRenderers.get("text")!,
uuid: crypto.randomUUID(), rendersContentOnly: true,
render: TokenRenderers.get("text")!, },
rendersContentOnly: true, ]
}, : undefined,
] })),
: undefined, token.children || [],
})
),
token.children || []
).filter((c) => c.children?.length || (c.rendersContentOnly && c.content)); ).filter((c) => c.children?.length || (c.rendersContentOnly && c.content));
}; };

View File

@ -1,177 +0,0 @@
type QueryableObject = Record<string, any>;
class TtcQueryParser {
private data: QueryableObject;
private relativeMap: Map<QueryableObject, QueryableObject>;
constructor(data: QueryableObject) {
this.data = data;
this.relativeMap = new Map();
this.buildRelativeMap(this.data, null);
}
public search(
query: string,
currentObject: QueryableObject = this.data
): any[] {
// Normalize the query string by trimming whitespace
query = query.trim();
// Determine the base structure to search
let result: any[] = [];
// Perform initial parsing based on the query syntax
if (query.startsWith("^")) {
result = this.queryCurrentObject(query, currentObject);
} else if (query.startsWith("$")) {
result = this.queryRelativeObject(query, currentObject);
} else {
result = this.queryPublication(query);
}
return result;
}
private queryPublication(query: string): any[] {
// Example implementation for searching publication
const publicationMatch = query.match(/^(\w+)(\[(\w+)\])?(\..+)?/);
if (publicationMatch) {
const subQuery = publicationMatch[4];
// Search within the publication data
return this.searchInObject(this.data, subQuery);
}
return [];
}
private queryCurrentObject(
query: string,
currentObject: QueryableObject
): any[] {
// Example implementation for querying the current object
const subQuery = query.substring(1); // Remove '^'
return this.searchInObject(currentObject, subQuery);
}
private queryRelativeObject(
query: string,
currentObject: QueryableObject
): any[] {
const relativeObject = this.relativeMap.get(currentObject);
if (!relativeObject) {
throw new Error("Relative object not found.");
}
const subQuery = query.substring(1); // Remove '$'
return this.searchInObject(relativeObject, subQuery);
}
private buildRelativeMap(
obj: QueryableObject,
relative: QueryableObject | null
): void {
if (obj.relative) {
relative = obj;
}
this.relativeMap.set(obj, relative || obj);
for (const key in obj) {
if (typeof obj[key] === "object" && obj[key] !== null) {
this.buildRelativeMap(obj[key], relative);
}
}
}
private searchInObject(obj: any, subQuery?: string): any[] {
// Handle subqueries and search in the provided object
if (!subQuery) {
return [obj]; // Return the entire object if no subquery is present
}
// Split the subquery based on dot (.) to navigate through the object
const keys = subQuery.split(".");
let current: any = obj;
for (const key of keys) {
if (current && typeof current === "object") {
if (key.includes("[") && key.includes("]")) {
const [prop, selector] = key.split("[");
const index = selector.slice(0, -1);
if (Array.isArray(current[prop])) {
if (!isNaN(Number(index))) {
current = current[prop][Number(index)];
} else {
const [k, comparator, value] = this.parseSelector(index);
current = this.applySelector(current[prop], k, comparator, value);
}
} else {
return [];
}
} else {
current = current.map((e: any) => e[key]);
}
} else {
return [];
}
}
return Array.isArray(current) ? current : [current];
}
private parseSelector(selector: string): [string, string, string] {
const match = selector.match(/(.+)(=|==|>|<|>=|<=|!!|!|!=)(.+)/);
if (match) {
return [match[1], match[2], match[3]];
}
return ["", "=", selector];
}
private applySelector(
array: any[],
key: string,
comparator: string,
value: string
): any[] {
switch (comparator) {
case "=":
return array.filter((item) => (key ? item[key] : item).includes(value));
case "==":
return array.filter((item) => (key ? item[key] : item) === value);
case ">":
return array.filter((item) => (key ? item[key] : item) > value);
case "<":
return array.filter((item) => (key ? item[key] : item) < value);
case ">=":
return array.filter((item) => (key ? item[key] : item) >= value);
case "<=":
return array.filter((item) => (key ? item[key] : item) <= value);
case "!!":
return array.filter((item) => (key ? item[key] : item));
case "!=":
return array.filter(
(item) => !(key ? item[key] : item).includes(value)
);
case "!":
return array.filter((item) => !(key ? item[key] : item));
default:
return [];
}
}
}
// Example usage:
const data = {
relative: true,
weapon_abilities: [
{ name: "Rapid Fire", body: "Shoot again" },
{ name: "Sustained Hits", body: "More damage", relative: true },
],
};
const parser = new TtcQueryParser(data);
// Example queries
console.log(parser.search("weapon_abilities[name=Rapid Fire].body")); // Example output
// console.log(parser.search("^weapon_abilities[name=Rapid Fire]", data)); // Example output
console.log(parser.search("$weapon_abilities[name=Rapid Fire].body", data)); // Example output

View File

@ -366,7 +366,7 @@ While in a Query Block Template, all resolvers will use the queried value as the
### Query Block ### Query Block
Similar to the Query Block Template, the query block collects the values of the query and passes it to the markdown inside of it. However, instead of rendering the whole block, it instead renders the markdown a single time. It has not been fully implemented yet Similar to the Query Block Template, the query block passes the values of the query and passes it to the markdown inside of it. However, instead of rendering the whole block, it instead renders the markdown a single time. It has not been fully implemented yet
Unlike the Query Block Template, it does not change the `_` variable to the queried value. For resolvers to work with the queried data, you need to access it with the `$$` variable. `$$` will be iterated over and will act similar to the Query Block Template, but on a per-line basis, meaning that if a line contains the `$$` variable, it will render that line of markdown for each result of the query. This is primarily useful for something like a List or a Table. Unlike the Query Block Template, it does not change the `_` variable to the queried value. For resolvers to work with the queried data, you need to access it with the `$$` variable. `$$` will be iterated over and will act similar to the Query Block Template, but on a per-line basis, meaning that if a line contains the `$$` variable, it will render that line of markdown for each result of the query. This is primarily useful for something like a List or a Table.

View File

@ -1,82 +0,0 @@
---
title: THe basics of schemas and objects
author: Emmaline Autumn
date: June 11th, 2024
updated: June 11th, 2024
---
# Table of Contents
- [Table of Contents](#table-of-contents)
- [What is a schema?](#what-is-a-schema)
- [Schema Templates](#schema-templates)
- [Schema and Type Fields](#schema-and-type-fields)
- [Schema Types](#schema-types)
- [Default Types](#default-types)
- [Special Types](#special-types)
- [How to make a schema](#how-to-make-a-schema)
---
# What is a schema?
A schema is a representation of the structure of a collection of data. In TTC, a schema is how we define the structure of game objects. Schemas have three parts: a template, fields, and types.
Schemas are effectively describing a relationship of parent objects and child objects as a collection of key-value pairs.
## Schema Templates
The template is how the object will be represented when rendered in markdown. It works in a similar way to Query Block Templates, with access to the `_` root variable.
By default, there is a simple template that renders them out as a key-value pair. The default template looks like this:
[][][]
```
??<<_.!!>>[[
# ??<<_.key>> $${{_.value}}
]]
```
/[]
## Schema and Type Fields
Schema fields are the description of the parent-child structure. Fields are named and can be left blank or filled in at any point of a publication's lifecycle.
If a field is rendered, it checks if it has a value in the publication or not. If it does, it will render the type's template. If it does not have a value, it will render an appropriate input for the type of the field. The exception is "required" fields. These require that the base (first) publication using that schema is required to have a value.
Fields by default are limited to a maximum of 1 entry, but you can increase the limit or set it to unlimited.
## Schema Types
Schema types are almost like mini-schemas that go inside of a schema. A schema holds all objects related to a discipline inside the rules (e.g. a Codex in Warhammer 40k, or an expansion), but a type is used to describe the fields inside of that schema (e.g. a datasheet for a unit in Warhammer 40k).
Types can be made up of other types as well. For example, the built in table type is made up of table rows which are made up of table columns.These compound types allow you to make better, more complex schemas that can do more.
Types also have a template. By default, it uses the same template as the schema to just render all of its contents, but you can customize it to your needs using ttcMD.
### Default Types
There are 5 default schema field types: section, steps, image, list, and table. These types have basic templates that you can't change that help build more complex types or even just build quick and dirty layouts.
There are also type field types, such as text, number, long text, and checkbox.
Each type has different limitations that you can enforce in the publications. Number has a minimum and a maximum, text has a maximum length.
### Special Types
Special types have specific programmed behaviors. These include decks of cards, dice, select and any. I am trying to figure out a good way to make it possible for you to make your own behaviors, but that is not top priority at the moment.
# How to make a schema
1. Inside of a game system, you will click "create new schema." This will take you to the schema editor. The first thing to do is to give your schema a name. This should be unique in the context of the game system.
2. You will then either create your custom types or add fields to the schema below. Let's start by creating our own type by giving it an name and clicking "configure." We will move into an area of the editor that allows us to add fields. Let's give this type two fields, name them count and description.
3. For count, let's select a number for the field type and set the minimum to 0. Since we only want 1 count, let's set the Limit field to 1 and the Minimum field to 1.
4. For the description, let's make it constant. You'll notice that there is now an additional field that you can use to input a constant value, let's describe this type as "A sample schema type that holds a count."
5. Now that we've finished with this type, click "save type." It will be added to the list of Types in the schema viewer on the side where you can review.
6. Go up to the schema fields and let's add a field by typing the name for the field and clicking add. Let's call ours "Population"
7. In the list below, our new field has been added. We can now change the type of that field by typing it into the Type field. It will populate suggestions as you type.
8. Now you can set the limits of the field.
9. Lastly, and most importantly, click "Save Schema" at the top right.

View File

@ -1,15 +1,4 @@
| test | Table | header |
1. hello | ---- | ----- | ------ |
2. everybody | test | table | row |
3. my | shorter | *row* |
4. name
5. is
6. welcome
- hello
- everybody
- yes you
- my
- name
- is
- welcome

View File

@ -1,15 +1,6 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: "standalone", output: "standalone",
webpack(config) {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ["@svgr/webpack"],
});
return config;
},
}; };
export default nextConfig; export default nextConfig;

2776
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,11 +15,9 @@
"next": "14.1.0", "next": "14.1.0",
"prisma": "^5.11.0", "prisma": "^5.11.0",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18"
"recoil": "^0.7.7"
}, },
"devDependencies": { "devDependencies": {
"@svgr/webpack": "^8.1.0",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",

View File

@ -1,8 +0,0 @@
/*
Warnings:
- Added the required column `types` to the `Schema` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE `Schema` ADD COLUMN `types` JSON NOT NULL;

View File

@ -14,10 +14,8 @@ datasource db {
} }
model GameSystem { model GameSystem {
id String @id @default(cuid()) id String @id @default(cuid())
schemas Schema[] schemas Schema[]
author User @relation(fields: [authorId], references: [id])
authorId String
name String @unique name String @unique
created DateTime @default(now()) created DateTime @default(now())
@ -28,14 +26,10 @@ model Schema {
gameSystem GameSystem @relation(fields: [gameSystemId], references: [id]) gameSystem GameSystem @relation(fields: [gameSystemId], references: [id])
gameSystemId String gameSystemId String
publications Publication[] publications Publication[]
author User @relation(fields: [authorId], references: [id])
authorId String
originalId String name String
name String schema Json
schema Json version Int
types Json
version Int
} }
model Publication { model Publication {
@ -43,8 +37,6 @@ model Publication {
schema Schema @relation(fields: [schemaId], references: [id]) schema Schema @relation(fields: [schemaId], references: [id])
schemaId String schemaId String
tags Tag[] tags Tag[]
author User @relation(fields: [authorId], references: [id])
authorId String
name String name String
data Json data Json
@ -55,13 +47,3 @@ model Tag {
publication Publication @relation(fields: [publicationId], references: [id]) publication Publication @relation(fields: [publicationId], references: [id])
publicationId String publicationId String
} }
model User {
id String @id @default(cuid())
schemas Schema[]
gameSystems GameSystem[]
publications Publication[]
username String
email String @unique
}

View File

@ -1,6 +0,0 @@
import { atom } from "recoil";
export const SchemaEditAtom = atom<Schema>({
key: "schema-edit",
default: { name: "", types: {}, schema: {}, id: "" },
});

37
types.d.ts vendored
View File

@ -1,4 +1,3 @@
// MD Parser
type IdentifiedToken<M> = { type IdentifiedToken<M> = {
metadata: M; metadata: M;
children?: Token[]; children?: Token[];
@ -55,39 +54,3 @@ type IdentifierRegistration = <N = Record<string, string>>(
openTagRx?: RegExp, openTagRx?: RegExp,
closeTagRx?: RegExp, closeTagRx?: RegExp,
) => void; ) => void;
// Schema
type MetadataType = {
[key: string]: string;
};
type FieldType = {
type: FieldTypes;
value: string;
isConstant: boolean;
limit: number;
minimum: number;
};
type TypeType = Record<string, FieldType>;
type Template = {
type: string;
display: string;
};
type Schema = {
id: string;
name: string;
schema: Record<string, Template>;
types: Record<string, TypeType>;
};
// Input Binder
type InputBinder = {
name: string;
value: string | number;
onChange: (
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
) => void;
};