Compare commits

..

10 Commits

64 changed files with 2350 additions and 215 deletions

22
.vscode/settings.json vendored
View File

@@ -1,26 +1,6 @@
{
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#83a0ea",
"activityBar.background": "#83a0ea",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#fae4ea",
"activityBarBadge.foreground": "#15202b",
"commandCenter.border": "#e7e7e799",
"sash.hoverBorder": "#83a0ea",
"statusBar.background": "#577ee3",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#83a0ea",
"statusBarItem.remoteBackground": "#577ee3",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#577ee3",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#577ee399",
"titleBar.inactiveForeground": "#e7e7e799"
},
"peacock.remoteColor": "#577ee3",
"deno.enable": true,
"deno.unstable": true,
"deno.config": "./deno.jsonc",
"svg.preview.background": "black"
"svg.preview.background": "white"
}

View File

@@ -51,7 +51,7 @@ import {parse, stringify} from 'yaml';
} else if (confirm('Permissions not specified, would you like to add specific permissions?')) {
let permsPrompt: string | null = '';
while (!permsPrompt) {
permsPrompt = prompt('Please enter the permission short code or names of permissions you would like');
permsPrompt = prompt('Please enter the permission short code or names of permissions you would like:');
if (!permsPrompt) continue
let permNames;
if (permsPrompt?.match(' ')) {

View File

@@ -11,7 +11,7 @@
"middleware/": "./middleware/"
},
"tasks": {
"dev": "deno run -A startDev.ts",
"dev": "deno run -A --watch startDev.ts",
"createService": "deno run -A createService.ts"
}
}

32
deno.lock generated
View File

@@ -580,6 +580,7 @@
"npm": {
"specifiers": {
"bcryptjs": "bcryptjs@2.4.3",
"create-vite-extra": "create-vite-extra@1.1.0",
"mongoose": "mongoose@7.0.4",
"play-sound": "play-sound@1.1.5",
"yaml": "yaml@2.2.2"
@@ -608,6 +609,14 @@
"integrity": "sha512-HevkSpDbpUfsrHWmWiAsNavANKYIErV2ePXllp1bwq5CDreAaFVj6RVlZpJnxK4WWDCJ/5jMUpaY6G526q3Hjg==",
"dependencies": {}
},
"create-vite-extra@1.1.0": {
"integrity": "sha512-Gi5ZLFhgULy6DU9QpZZ6ko4STOrg5dT4/2sCqszjoMZTSyQl1B95eiZaeByBGt+HCSffXAFvhSzkt5iqlqW6eA==",
"dependencies": {
"kolorist": "kolorist@1.8.0",
"minimist": "minimist@1.2.8",
"prompts": "prompts@2.4.2"
}
},
"debug@4.3.4": {
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
@@ -626,10 +635,22 @@
"integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==",
"dependencies": {}
},
"kleur@3.0.3": {
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
"dependencies": {}
},
"kolorist@1.8.0": {
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
"dependencies": {}
},
"memory-pager@1.5.0": {
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
"dependencies": {}
},
"minimist@1.2.8": {
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dependencies": {}
},
"mongodb-connection-string-url@2.6.0": {
"integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
"dependencies": {
@@ -682,6 +703,13 @@
"find-exec": "find-exec@1.0.2"
}
},
"prompts@2.4.2": {
"integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
"dependencies": {
"kleur": "kleur@3.0.3",
"sisteransi": "sisteransi@1.0.5"
}
},
"punycode@2.3.0": {
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"dependencies": {}
@@ -696,6 +724,10 @@
"integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==",
"dependencies": {}
},
"sisteransi@1.0.5": {
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"dependencies": {}
},
"smart-buffer@4.2.0": {
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"dependencies": {}

View File

@@ -22,3 +22,7 @@ services:
build:
context: ./
dockerfile: ./game-systems-service/Dockerfile
warstone-web:
build:
context: ./
dockerfile: ./warstone-web-service/Dockerfile

7
middleware/debugLog.ts Normal file
View File

@@ -0,0 +1,7 @@
import { Context, Middleware } from "oak";
export const debugLog = (message: string | ((arg: Context) => string)): Middleware =>
async (ctx, next) => {
console.log(typeof message === 'function' ? message(ctx) : message);
await next();
}

View File

@@ -10,5 +10,12 @@ module.exports = {
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': 'warn',
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
},
}

BIN
project-warstone/bun.lockb Executable file

Binary file not shown.

View File

@@ -7,17 +7,21 @@
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
"preview": "vite preview",
"build-dev": "vite build"
},
"dependencies": {
"@types/react-router-dom": "^5.3.3",
"@types/recoilize": "^0.8.0",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.24",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.12.0",
"react-router-dom": "^6.21.0",
"recoil": "^0.7.7",
"tailwindcss": "^3.3.2"
"recoilize": "^3.2.0",
"tailwindcss": "^3.3.2",
"vite-plugin-svgr": "^3.2.0"
},
"devDependencies": {
"@types/react": "^18.0.37",

View File

@@ -0,0 +1,57 @@
Start query: ?
- by default, queries search the current publication
- you can specify a publication by following this with the name, by default it will use the latest release of a publication
- you can specify the version by including it in a pair of square brackets
Query current object: ^
- refers specifically to the object based off of this specific type
Query relative object: $
- refers to the last relative object in the hierarchy
Access child: .
- this also maps over all items in a list and returns a list of the relevant children
- if the parent is a list, it will return a list of just the specified child
Select: []
- Selects any of the items that matches the provided selector
- Providing a number selects the ordinal item in the list
- Providing a comparator selects items that match the comparator
- Selectors can be separated by either commas for "and" or slashes for "or", but not both
Comparators:
= is similar to
== is exactly the same
> greater than
< less than
>= greater than or equal to
<= less than or equal to
!! is true/exists
! is not
/ or, allows matching one or more comparators at the same time
Combiner: ()
- Will only select items that match all of the selectors
- Selectors can be separated by either commas for "and" or slashes for "or", but not both
--Templating--
insertion: {{}}
- Allows for queries to be wrapped in a templated string
- Can be either a direct query or used in combination with the "_" character to reference a single query
single query: <<?
- Used the same as start query, but appended to the end of a templated string
- Used in conjunction with "_" to reference the queried value
query reference: _
- used in conjunction with single query to reference the queried value
separator: ::;;
- used at the end of a template to indicate how to separate results when the results are more than one.
- Whatever text is placed between in the middle is treated as the full separator, or in other words, if the separator doesn't have any spaces, then there will be no spaces between items in the final text
- If no separator is provided, it will default to comma and space separation
- placed at the end of the template but before the single query
- should support any standard escaped characters such as newline
Examples:
?core.weapon_abilities[name=Rapid Fire].body
- this searches the publication 'core' object for weapon_abilities that have the name
?core[v1].weapon_abilities[name=Rapid Fire].body
- this searches version 'v1' of the publication 'core' object for weapon_abilities that have the name
Sustained Hits {{?^sustained_hits}}
- This formats the string with the queried values, which start the query within the same object that the field exists in. This would result in the string 'Sustained Hits 1'
Anti-{{_.keyword}} {{_.value}}<<?^anti_abilities
- This formats the string with the queried values, which start the query within the same object that the field exists in. This would result in the string 'Anti-Infantry 2+' or 'Anti-Infantry 2+, Anti-Monster 4+' if there are more than one matching values
Anti-{{_.keyword}} {{_.value}}:://;;<<?^anti_abilities
- This formats the string with the queried values, which start the query within the same object that the field exists in. This would result in the string 'Anti-Infantry 2+' or 'Anti-Infantry 2+//Anti-Monster 4+' if there are more than one matching values

View File

@@ -1,12 +1,23 @@
import { RecoilRoot } from 'recoil'
import { TextMapper } from './components/Importer/text-mapper'
import { SchemaBuilder } from './components/SchemaBuilder'
import { GameSystemEditor } from './components/GameSystemEditor'
import RecoilizeDebugger from 'recoilize';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { SchemaList } from './components/schemalist';
function App() {
return (
<RecoilRoot>
{/* <TextMapper /> */}
<SchemaBuilder />
<BrowserRouter>
<Routes>
<Route Component={SchemaList} path='/schemas' />
<Route Component={SchemaBuilder} path='/schema/:id' />
{/* <Route Component={SchemaBuilder} path='/schema/new' /> */}
<Route Component={GameSystemEditor} path='/game-system/:id' />
{/* <Route Component={GameSystemEditor} path='/game-system/new' /> */}
</Routes>
</BrowserRouter>
<RecoilizeDebugger />
</RecoilRoot>
)
}

View File

@@ -0,0 +1,6 @@
<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>

After

Width:  |  Height:  |  Size: 385 B

View File

@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 1017 B

View File

@@ -0,0 +1,70 @@
<?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>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,70 @@
<?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>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,8 @@
<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>

After

Width:  |  Height:  |  Size: 943 B

View File

@@ -0,0 +1,12 @@
<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>

After

Width:  |  Height:  |  Size: 872 B

View File

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

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect, ReactNode, useRef, useLayoutEffect, useCallback } from 'react';
import { FCC } from '../../types';
import './index.css';
interface IProps {
currentPage: number;
@@ -10,12 +11,24 @@ const AnimatedPageContainer: FCC<IProps> = ({ children, currentPage }) => {
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}
className="data-[active=true]:opacity-100 data-[active=true]:static opacity-0 top-0 left-0 absolute transition-opacity duration-300 data-[active=false]:-z-10"
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>
@@ -23,7 +36,7 @@ const AnimatedPageContainer: FCC<IProps> = ({ children, currentPage }) => {
};
return (
<div className="relative">
<div className="relative overflow-hidden">
{React.Children.map(children, renderChild)}
</div>
);

View File

@@ -0,0 +1,86 @@
import { FC, useCallback, useEffect, useState } from 'react'
import { GameSystem } from '../../types/gameSystem';
import { useObjectState } from '../../hooks/useObjectState';
import { GameSystemsService } from '../../services/game-systems';
export const GameSystemEditor: FC = () => {
const { state: gameSystem, update: updateGameSystem, bindProperty: bindGameSystemProperty, setState: setGameSystem } = useObjectState<GameSystem>({
id: '',
schema: {
id: '',
name: '',
templates: {},
types: {}
},
schemaId: '286f4c18-d280-444b-8d7e-9a3dd09f64ef',
name: 'Asshammer 40x a day',
accolades: [],
});
const [schemas, setSchemas] = useState<[string,string][]>([]);
const [lastSaved, setLastSaved] = useState(gameSystem);
const fetchSchema = useCallback(async (id: string) => {
try {
const schema = await GameSystemsService.getSchema(id);
updateGameSystem({
schema
});
} catch (e) {
console.log('failed to fetch schema:', e)
}
}, [updateGameSystem])
useEffect(() => {
if (gameSystem.schemaId === gameSystem.schema.id) return;
fetchSchema(gameSystem.schemaId);
}, [fetchSchema, gameSystem.schema.id, gameSystem.schemaId]);
useEffect(() => {
GameSystemsService.getSchemaList()
.then(schemas => setSchemas(schemas));
}, []);
const saveGameSystem = useCallback(() => {
GameSystemsService.saveGameSystem(gameSystem);
setLastSaved(gameSystem);
}, [gameSystem])
const fetchGameSystem = useCallback(async () => {
const gs = await GameSystemsService.getGameSystem('');
setGameSystem(gs);
setLastSaved(gs);
}, [setGameSystem]);
useEffect(() => {
fetchGameSystem()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="container p-8">
<div className="flex gap-1">
<input className="no-default text-white bg-transparent header border-b-2 border-falcon w-1/2" type="text" {...bindGameSystemProperty('name')} placeholder="Game Name" />
<select className="no-default text-white bg-transparent header border-b-2 border-falcon" {...bindGameSystemProperty('schemaId')}>
<option value=""></option>
{schemas.map(s => (
<option value={s[0]}>{s[1]}</option>
))}
</select>
<button onClick={saveGameSystem} disabled={lastSaved === gameSystem}>Save Game System</button>
</div>
<div>
<p>Accolades</p>
<div className="grid grid-cols-3">
{gameSystem.accolades.map(a => (
<div>
<p>{a.name}</p>
<p>{a.description}</p>
</div>
))}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,25 @@
import { FC } from 'react'
import { ReactComponent as help } from '../../assets/icons/Help Icon.svg';
import { ReactComponent as trash } from '../../assets/icons/Trash Icon.svg';
import { ReactComponent as trash_hover } from '../../assets/icons/Trash Icon Open.svg';
import { ReactComponent as 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

@@ -0,0 +1,16 @@
import { FC, PropsWithChildren } from 'react'
import { Poppable } from '../../lib/poppables/components/poppable'
import { Icon } from '../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

@@ -0,0 +1,15 @@
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

@@ -0,0 +1,19 @@
import {FC} from 'react'
import { Publication } from '../../types/publication';
import { useObjectState } from '../../hooks/useObjectState';
export const PublicationEditor: FC = () => {
const {value: publication, setValue: setPublication, bindProperty: bindPublication, update: updatePublication, reset: resetPublication} = useObjectState<Publication>({
gameSystemId: '',
name: '',
status: 'private',
templates: {},
version: '1'
});
return (
<div className="container p-8">
</div>
)
}

View File

@@ -1,53 +1,80 @@
import { FC, useCallback, useEffect } from 'react'
import { FC, useCallback, useEffect, useState } from 'react'
import { FieldType, FieldTypes, fieldTypeOptions, fieldTypesWithValues } from '../../types/schema'
import { useObjectStateWrapper } from '../../hooks/useObjectState';
import { FieldTypeInput } from './field-type-input';
import { InputBinder } from '../../types/inputBinder';
import { ValueField } from './value-field';
import { HelpPopper } from '../Poppables/help';
import { Icon } from '../Icon';
import { RESERVED_FIELDS } from '../../constants/ReservedFields';
interface IProps {
update: (arg: FieldType) => void;
field: FieldType;
fieldName: string;
deleteField: (arg: string) => void;
}
export const FieldEditor: FC<IProps> = ({ update, field, fieldName }) => {
const { update: updateField, bindProperty, bindPropertyCheck } = useObjectStateWrapper(field, (e) => update(typeof e === 'function' ? e(field) : e))
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(() => {
console.log(field.value);
}, [field])
setReserved(RESERVED_FIELDS[fieldName]);
}, [fieldName])
// useEffect(() => {
// console.log(field.value);
// }, [field])
return (
<li className="odd:bg-black/50 flex gap-4 items-center p-2">
<p>{fieldName}</p>
<select className="capitalize" {...bindProperty('type')}>
{fieldTypeOptions.map(o => (
<option className="capitalize" value={FieldTypes[o]}>{o}</option>
))}
</select>
{shouldShowValueField() && (
// <>
// {field.type === FieldTypes.dice && (
// <label>
// Sides:&nbsp;
// <select {...bindProperty('value')}>
// {diceSides.map(d => (
// <option value={'d' + d}>{d}</option>
// ))}
// </select>
// </label>
// )}
// {field.type === FieldTypes.type && (
// <FieldTypeInput bind={bindProperty('value')} />
<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 className="capitalize" value={FieldTypes[o]}>{o}</option>
))}
</select>
</label>
// )}
// </>
<ValueField type={field.type} bind={bindProperty('value')} />
{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'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" type="number" {...bindProperty('minimum')} />
</label>
<label className="w-min">
Limit:
<input className="w-12" 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>
)}
<label><input type="checkbox" {...bindPropertyCheck('isConstant')} /> Is constant</label>
</li>
)
}

View File

@@ -2,22 +2,27 @@ import { useRecoilValue } from 'recoil';
import { SchemaEditAtom } from '../../recoil/atoms/schema';
import { FCC } from '../../types'
import { InputBinder } from '../../types/inputBinder'
import { TEMPLATE_TYPES } from '../../constants/TemplateTypes';
interface IProps {
bind: InputBinder
}
export const FieldTypeInput: FCC<IProps> = ({ bind }) => {
const Schema = useRecoilValue(SchemaEditAtom);
const schema = useRecoilValue(SchemaEditAtom);
return (
<>
<label className="w-min">
Type:
<input type="text" {...bind} list="type-editor-type-list" />
<optgroup id="type-editor-type-list">
{Object.keys(Schema.types).map(k => (
<datalist id="type-editor-type-list">
{Object.keys(TEMPLATE_TYPES).map(k => (
<option className="capitalize" value={k}>{k}</option>
))}
</optgroup>
</>
{Object.keys(schema.types).map(k => (
<option className="capitalize" value={k}>{k}</option>
))}
</datalist>
</label>
)
}

View File

@@ -1,44 +1,165 @@
import { FC, useCallback, useState } from 'react'
import { FC, useCallback, useEffect, useState } from 'react'
import AnimatedPageContainer from '../AnimatedPageContainer';
import { TypeEditor } from './type-editor';
import { useObjectState, useObjectStateWrapper } from '../../hooks/useObjectState';
import { Schema, TypeType } from '../../types/schema';
import { FieldTypes, Schema, Template, TypeType } from '../../types/schema';
import { useInput } from '../../hooks/useInput';
import { useRecoilState } from 'recoil';
import { useRecoilState, useResetRecoilState } from 'recoil';
import { SchemaEditAtom } from '../../recoil/atoms/schema';
import { GameSystemsService } from '../../services/game-systems';
import { SchemaViewer } from './schema-viewer';
import { TemplateEditor } from './template-editor';
import { Icon } from '../Icon';
import { useNavigate, useParams } from 'react-router-dom';
export const SchemaBuilder: FC = () => {
const [schema, setSchema] = useRecoilState(SchemaEditAtom);
const {update: updateSchema} = useObjectStateWrapper<Schema>(schema, setSchema);
const resetSchema = useResetRecoilState(SchemaEditAtom);
const { update: updateSchema, bindProperty:bindSchemaProperty } = useObjectStateWrapper<Schema>(schema, setSchema);
const {value: typeName, bind: bindTypeName, reset: resetTypeName} = useInput('');
const navigate = useNavigate();
const [pageNumber, setPageNumber] = useState(1);
const {id} = useParams<{id: string}>()
const { value: typeName, bind: bindTypeName, reset: resetTypeName } = useInput('');
const [pageNumber, setPageNumber] = useState(0);
const [lastSaved, setLastSaved] = useState(schema);
const fetchSchema = useCallback(async () => {
if (!id) return;
if (id === 'new') return resetSchema();
const fetchedSchema = await GameSystemsService.getSchema(id)
// if (fetchedSchema.name === schema.name) return;
if (!fetchedSchema.templates) fetchedSchema.templates = {}
setSchema(fetchedSchema);
setLastSaved(fetchedSchema);
}, [])
useEffect(() => {
fetchSchema();
}, [fetchSchema])
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 (id === 'new') navigate('/schema/'+sid)
}, [schema])
const selectTypeForEdit = useCallback((typeKey: string) => {
setSelectedType(typeKey);
setPageNumber(1);
}, [])
const { value: templateName, bind: bindTemplateName, reset: resetTemplateName } = useInput('', { disallowSpaces: true });
const addTemplate = useCallback(() => {
updateSchema(s => ({
templates: {
...s.templates,
[templateName]: {
publishable: false,
type: FieldTypes.any
}
}
}));
resetTemplateName();
}, [resetTemplateName, templateName, updateSchema])
const updateTemplate = useCallback((key: string, template: Template) => {
updateSchema(s => ({
templates: {
...s.templates,
[key]: template
}
}))
}, [updateSchema])
const deleteType = useCallback((key: string) => {
updateSchema(s => {
const types = { ...s.types };
delete types[key];
return { types }
})
}, [updateSchema])
return (
<div className="container flex gap-4 p-8">
<div className="panel w-2/3 h-full">
<AnimatedPageContainer currentPage={pageNumber}>
<div>
<p className="subheader">Add A Type</p>
<input type="text" {...bindTypeName} />
<button className="interactive" disabled={!typeName} onClick={() => setPageNumber(1)}>Configure</button>
<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 a template</p>
<div className="mb-2">
<input type="text" {...bindTemplateName} />
<button onClick={addTemplate} disabled={!templateName}>Add</button>
</div>
<TypeEditor name={typeName} saveType={saveType} />
</AnimatedPageContainer>
<ul className="rounded-lg overflow-hidden">
{Object.entries(schema.templates).map(([templateKey, template]) => (
<TemplateEditor key={templateKey} templateKey={templateKey} template={template} update={updateTemplate} />
))}
</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 w-1/3 whitespace-pre-wrap">
{JSON.stringify(schema, null, 2)}
<div className="panel basis-1/3">
<div className="flex gap-2 mb-2">
<button
onClick={saveSchema}
disabled={lastSaved === schema}
>
Save Schema
</button>
<button
className="bg-red-800"
onClick={() => setSchema(lastSaved)}
disabled={lastSaved === schema}
>
Discard Changes
</button>
</div>
<SchemaViewer schema={schema} onTypeClick={selectTypeForEdit} />
</div>
</div>
)

View File

@@ -0,0 +1,74 @@
import { FC, useCallback } from 'react'
import { FieldType, FieldTypes, Schema, TypeType, fieldTypesWithValues } from '../../types/schema'
import { Truncate } from '../Poppables/truncation';
import { Accordion, AccordionContent } from '../../lib/accordion';
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.templates).map(([templateKey, template]) => (
<li>
<p className="font-bold">{templateKey}</p>
<p className="font-thin text-xs">{template.type}</p>
{template.publishable && <p className="font-thin text-xs">This template can create publications</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

@@ -0,0 +1,64 @@
import { FC, useCallback, useState } from 'react'
import { Template } from '../../types/schema';
import { useObjectStateWrapper } from '../../hooks/useObjectState';
import { TEMPLATE_TYPES } from '../../constants/TemplateTypes';
import { SchemaEditAtom } from '../../recoil/atoms/schema';
import { useRecoilState, useRecoilValue } from 'recoil';
import { Icon } from '../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, bindPropertyCheck } = useObjectStateWrapper(template, updateTemplate)
const deleteTemplate = useCallback(() => {
setSchema(s => {
const templates = { ...s.templates };
delete templates[templateKey]
return {
...s,
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>
<label>
<input type="checkbox" {...bindPropertyCheck('publishable')} />
&nbsp;Can create publications
</label>
</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,4 +1,4 @@
import { FC, useCallback } from 'react'
import { FormEvent, useCallback, useEffect } from 'react'
import { FCC } from '../../types'
import { FieldType, FieldTypes, TypeType } from '../../types/schema'
import { useObjectState } from '../../hooks/useObjectState';
@@ -8,45 +8,66 @@ import { FieldEditor } from './field-editor';
interface IProps {
name: string;
saveType: (arg0: string, arg1: TypeType) => void;
type?: TypeType
}
const constantProperties = ['metadata'];
export const TypeEditor: FCC<IProps> = ({ saveType, name }) => {
const { update: updateType, reset: resetType, state: type } = useObjectState<TypeType>({});
export const TypeEditor: FCC<IProps> = ({ saveType, name, type: passedType }) => {
const { update: updateType, reset: resetType, state: type, setState: setType } = useObjectState<TypeType>({});
const { value: propertyName, setValue: setPropertyName, bind: bindPropertyName, reset: resetPropertyName } = useInput('');
const { value: propertyName, bind: bindPropertyName, reset: resetPropertyName } = useInput('', { disallowSpaces: true });
const save = () => {
saveType(name, type);
resetType();
}
const addField = useCallback(() => {
const addField = useCallback((e: FormEvent) => {
e.preventDefault();
updateType({
[propertyName]: {
type: FieldTypes.number,
value: '',
isConstant: false,
limit: 1,
minimum: 1,
}
})
}, [propertyName, updateType])
});
resetPropertyName();
}, [propertyName, updateType, resetPropertyName]);
const updateField = useCallback((k: keyof typeof type) => (field: FieldType) => {
updateType({ [k]: field })
}, [updateType])
}, [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">Creating type "{name}"</p>
<input type="text" {...bindPropertyName} />
<button disabled={!propertyName} onClick={addField}>Add Field</button>
<ul>
{Object.entries(type).filter(([k]) => !constantProperties.includes(k)).map(([key, value]) => (
<FieldEditor field={value} update={updateField(key)} fieldName={key} />
<p className="subheader">{passedType ? 'Editing' : 'Creating'} type "{name}"</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 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,37 +1,101 @@
import { FC } from 'react';
import { ChangeEvent, EventHandler, FC, useCallback, useEffect, useRef } from 'react';
import { FieldTypes } from '../../types/schema';
import { InputBinder } from '../../types/inputBinder';
import { FieldTypeInput } from './field-type-input';
import { useInput } from '../../hooks/useInput';
import { HelpPopper } from '../Poppables/help';
interface IValueProps {
type: FieldTypes;
bind: InputBinder;
}
const diceSides = [3, 4, 6, 8, 10, 12, 20, 100];
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);
export const ValueField: FC<IValueProps> = ({type, bind}) => {
switch (type) {
case FieldTypes.dice:
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>
Sides:&nbsp;
<select {...bind}>
{diceSides.map(d => (
<option value={'d' + d}>{d}</option>
))}
</select>
</label>
<>
<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 (
<input type="number" {...bind} />
<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 <></>;
return <></>;
}
}

View File

@@ -0,0 +1,20 @@
import { FC, useEffect, useState } from "react";
import { GameSystemsService } from "../services/game-systems";
import { Link } from "react-router-dom";
export const SchemaList: FC = () => {
const [schemas, setSchemas] = useState<[string,string][]>([]);
useEffect(() => {
GameSystemsService.getSchemaList().then(l => {
setSchemas(l)
});
}, [])
return (
<div className="container pt-12 flex flex-col gap-6">
<Link to="/schema/new"><button>New Schema</button></Link>
<ul className="panel">
{schemas.map(([id,name])=> <li key={id}><Link to={'/schema/' + id}><span className="w-full">{name || 'Unnamed Schema'}</span></Link></li>)}
</ul>
</div>
)
}

View File

@@ -0,0 +1,25 @@
import { FieldType, FieldTypes } from '../types/schema';
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

@@ -0,0 +1,95 @@
import { FieldTypes, TypeType } from "../types/schema";
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",
},
},
};

View File

@@ -0,0 +1,12 @@
import { useEffect, useState } from 'react';
export const useDebounce = (value: any, delay: number) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
};

View File

@@ -1,6 +1,11 @@
import { useState, ChangeEvent } from 'react';
export const useInput = <T extends string | number>(initialValue: T) => {
type InputHookConfig = {
disallowSpaces?: boolean;
spaceReplacer?: string;
}
export const useInput = <T extends string | number>(initialValue: T, config?: InputHookConfig) => {
const [value, setValue] = useState<T>(initialValue);
return {
@@ -10,11 +15,11 @@ export const useInput = <T extends string | number>(initialValue: T) => {
bind: {
value: value,
onChange: (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
const changed: string | number = typeof initialValue === 'number' ? parseInt(event.target.value) : event.target.value;
setValue(changed as T);
}
const changed: string | number = typeof initialValue === 'number' ? parseInt(event.target.value) : config?.disallowSpaces ? event.target.value.replace(' ', config.spaceReplacer || '_') : event.target.value;
setValue(changed as T);
}
};
}
};
};
export const useCheckbox = (initial: boolean) => {

View File

@@ -1,38 +1,58 @@
import React, { ChangeEvent, useCallback, useState } from 'react';
import { InputBinder } from '../types/inputBinder';
import React, { ChangeEvent, useCallback, useState } from "react";
import { InputBinder } from "../types/inputBinder";
type ObjectStateHookConfig = {
disallowSpaces?: boolean;
spaceReplacer?: string;
};
export const useObjectState = <T extends object>(initial: T) => {
const [state, setState] = useState<T>(initial || {} as T);
const bindProperty = useCallback(<K extends keyof T>(property: K) => ({
value: state[property] ?? '',
name: property,
onChange: (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) =>
setState(value => (
{
...value,
[event.target.name]: (
(typeof value[property] === 'number') ?
Number(event.target.value) || 0 :
event.target.value)
}
))
}), [state])
const bindProperty = useCallback(
<K extends keyof T>(property: K, config?: ObjectStateHookConfig) => ({
value: state[property] ?? "",
name: property,
onChange: (
event: ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>,
) =>
setState((value) => (
{
...value,
[event.target.name]: (
(typeof value[property] === "number")
? Number(event.target.value) || 0
: config?.disallowSpaces
? event.target.value.replace(" ", config.spaceReplacer || "_")
: event.target.value
),
}
)),
}),
[state],
);
const bindPropertyCheck = useCallback(<K extends keyof T>(property: K) => ({
checked: !!state[property],
name: property,
onChange: (event: ChangeEvent<HTMLInputElement>) => setState(value => ({
...value, [event.target.name]: (event.target.checked)
})),
readOnly: true
}), [state])
onChange: (event: ChangeEvent<HTMLInputElement>) =>
setState((value) => ({
...value,
[event.target.name]: (event.target.checked),
})),
readOnly: true,
}), [state]);
const update = useCallback((updates: Partial<T>) => setState(s => ({ ...s, ...updates })), [])
const update = useCallback(
(updates: Partial<T>) => setState((s) => ({ ...s, ...updates })),
[],
);
const reset = useCallback(() => {
setState(initial);
}, [initial])
}, [initial]);
return {
bindProperty,
@@ -40,45 +60,67 @@ export const useObjectState = <T extends object>(initial: T) => {
update,
state,
setState,
reset
}
}
reset,
};
};
export const useObjectStateWrapper = <T extends object>(state: T, setState: React.Dispatch<React.SetStateAction<T>>) => {
const bindProperty = useCallback(<K extends keyof T>(property: K): InputBinder => ({
value: state[property] ?? '',
name: property.toString(),
onChange: (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) =>
setState(value => (
{
...value,
[event.target.name]: (
(typeof value[property] === 'number') ?
Number(event.target.value) || 0 :
event.target.value)
}
))
}), [setState, state])
export const useObjectStateWrapper = <T extends object>(
state: T,
setState: React.Dispatch<React.SetStateAction<T>>,
) => {
const bindProperty = useCallback(
<K extends keyof T>(
property: K,
config?: ObjectStateHookConfig,
): InputBinder => ({
value: state[property]?.toString() ?? "",
name: property.toString(),
onChange: (
event: ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>,
) =>
setState((value) => (
{
...value,
[event.target.name]: (
(typeof value[property] === "number")
? Number(event.target.value) || 0
: config?.disallowSpaces
? event.target.value.replace(" ", config.spaceReplacer || "_")
: event.target.value
),
}
)),
}),
[setState, state],
);
const bindPropertyCheck = useCallback(<K extends keyof T>(property: K) => ({
checked: !!state[property],
name: property,
onChange: (event: ChangeEvent<HTMLInputElement>) => setState(value => ({
...value, [event.target.name]: (event.target.checked)
})),
readOnly: true
}), [setState, state])
onChange: (event: ChangeEvent<HTMLInputElement>) =>
setState((value) => ({
...value,
[event.target.name]: (event.target.checked),
})),
readOnly: true,
}), [setState, state]);
const update = useCallback((updates: Partial<T> | ((arg: T) => Partial<T>)) =>
setState(s => ({ ...s, ...(typeof updates === 'function' ? updates(s) : updates) }
)), [setState])
const update = useCallback(
(updates: Partial<T> | ((arg: T) => Partial<T>)) =>
setState((s) => ({
...s,
...(typeof updates === "function" ? updates(s) : updates),
})),
[setState],
);
return {
bindProperty,
bindPropertyCheck,
update,
state,
setState
}
}
setState,
};
};

View File

@@ -1,3 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Chakra+Petch:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&family=Open+Sans:ital,wght@0,300;0,400;0,500;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&family=Orbitron:wght@400;500;600;700;800;900&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -6,8 +8,23 @@
body {
@apply bg-bastille text-white
}
input, select {
@apply p-1 rounded-lg text-cinder-500
input,
select {
@apply p-1
}
option {
@apply text-cinder-500
}
input:not(.no-default),
select:not(.no-default) {
@apply rounded-lg text-cinder-500 interactive
}
* {
font-family: 'Open Sans', sans-serif;
}
}
@@ -15,25 +32,77 @@
.panel {
@apply bg-cinder shadow-xl p-8 rounded-xl
}
.header {
@apply text-2xl font-bold
}
.subheader {
@apply text-xl font-bold
}
button {
@apply interactive bg-olive-drab p-1
}
}
@layer utilities {
button:not(.no-default) {
@apply interactive p-1
}
button:not([class*="bg-"]):not(.no-default) {
@apply bg-olive-drab
}
.interactive {
@apply border-2 rounded-lg border-falcon cursor-pointer
}
.interactive svg {
@apply fill-falcon
}
.interactive:disabled {
@apply border-falcon-300 brightness-50 cursor-default
}
.fade-in {
animation: fade 300ms forwards ease-in;
animation-delay: 300ms;
}
.fade-out {
animation: fade 300ms forwards ease-in reverse;
}
.trash-can,
.anvil {
overflow: visible;
}
.trash-can path.trash-lid,
.anvil .anvil-base,
.anvil .anvil-body {
transition: 300ms transform, 300ms rotate, 300ms fill, 300ms stroke;
}
.trash-can:hover path.trash-lid {
transform: translate(-5%, -10%);
rotate: -10deg;
}
.anvil:hover .anvil-base {
transform: translate(0px, 5%);
}
.anvil:hover .anvil-body {
transform: translate(0px, -5%);
}
}
@layer utilities {}
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

View File

@@ -0,0 +1,68 @@
import { FC, PropsWithChildren, ReactNode, useCallback, useState } from 'react'
import { FCC } from '../../types';
interface IProps {
expandOnHover?: boolean;
expanded?: boolean;
title?: ReactNode;
}
export const Accordion: FCC<IProps> = ({ children, expandOnHover, expanded, title }) => {
const [open, setOpen] = useState(false);
return (
<div
data-expanded={open || expanded}
data-expandonhover={expandOnHover}
className={(expandOnHover ? 'group/hover' : 'group/controlled') + ' group'}
onClick={() => !title && !expandOnHover && setOpen(!open)}
>
{!!title && (
<div className="flex justify-between cursor-pointer" onClick={() => !expandOnHover && setOpen(!open)}>
{title}
<div
className={`
group-hover/hover:-rotate-180
group-data-[expanded]:-rotate-180
transition-transform
duration-500
grid
rounded-full
h-min
mr-2
mt-1
scale-y-50
`}
>
<span className="block w-2 h-2 rotate-45 border-r-2 border-b-2 place-self-center"></span>
<span className="block w-2 h-2 rotate-45 border-r-2 border-b-2 place-self-center"></span>
</div>
</div>
)}
{children}
</div>
)
}
export const AccordionContent: FC<PropsWithChildren> = ({ children }) => {
const [height, setHeight] = useState(0);
const updateRef = useCallback((node: HTMLDivElement | null) => {
if (node) {
setHeight(node.clientHeight)
} else {
setHeight(0)
}
}, [])
const Child = () => <div className="absolute bottom-0 w-full" ref={updateRef}>
{children}
</div>
return (
<div className="relative overflow-hidden">
{<Child />}
<span style={{ ['--v-height' as never]: height + 'px' }} className="w-0 block h-0 group-hover/hover:h-variable group-data-[expanded]/controlled:h-variable transition-all duration-700" />
</div>
);
}

View File

@@ -0,0 +1,117 @@
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react';
import { FCC } from '../../../types';
import { bulkRound } from '../../utils/bulkRound';
import { clamp } from '../../utils/clamp';
import { Portal } from '../../portal/components';
type edge = 'top' | 'bottom' | 'left' | 'right';
type alignment = edge | 'centered';
interface IProps {
preferredEdge: edge;
preferredAlign: alignment;
relativeElement: HTMLElement;
spacing?: number;
isClosing: boolean;
isClosed: boolean;
setHover: Dispatch<SetStateAction<boolean>>;
}
type position = { top: number, left: number, width?: number };
export const PoppableContent: FCC<IProps> = ({ preferredAlign, preferredEdge, children, relativeElement, spacing = 10, setHover, isClosing, isClosed }) => {
const [popRef, setPopRef] = useState<HTMLDivElement>();
const updateRef = useCallback((node: HTMLDivElement) => {
if (!node) return;
setPopRef(node);
}, [])
const getAlignment = useCallback((relX: number, relY: number, relWidth: number, relHeight: number, popWidth: number, popHeight: number, edge: edge, align: alignment): position => {
const pos = {
top: relY,
left: relX,
}
switch (align) {
case 'centered':
pos.top = relY + (relHeight / 2) - (popHeight / 2);
pos.left = relX + (relWidth / 2) - (popWidth / 2);
break;
case 'top':
pos.top = relY;
break;
case 'bottom':
pos.top = relY + relHeight - popHeight;
break;
case 'left':
pos.left = relX;
break;
case 'right':
pos.left = relX + relWidth - popWidth;
break;
}
return pos;
}, [])
const getPosition = useCallback((popWidth: number, popHeight: number, edge: edge, align: alignment) => {
const rel = relativeElement.getBoundingClientRect();
const [relX, relY, relWidth, relHeight] = bulkRound(rel.x, rel.y, rel.width, rel.height);
const pos: position = { top: 100, left: 100 };
const alignment = getAlignment(relX, relY, relWidth, relHeight, popWidth, popHeight, edge, align);
switch (edge) {
case 'top':
pos.top = relY - popHeight - spacing + document.documentElement.scrollTop;
pos.left = alignment.left;
break;
case 'bottom':
pos.top = relY + relHeight + spacing + document.documentElement.scrollTop;
pos.left = alignment.left;
break;
case 'left':
pos.left = relX - popWidth - spacing;
pos.top = alignment.top + document.documentElement.scrollTop;
break;
case 'right':
pos.left = relX + relWidth + spacing;
pos.top = alignment.top + document.documentElement.scrollTop;
break;
}
return pos;
}, [getAlignment, relativeElement, spacing])
const getClampedPosition = useCallback(() => {
if (!popRef) return { opacity: 0 }
const pop = popRef.getBoundingClientRect();
const [popWidth, popHeight] = bulkRound(pop.width, pop.height);
const pos = getPosition(popWidth, popHeight, preferredEdge, preferredAlign);
const { innerHeight, innerWidth } = window;
pos.top = ['left', 'right'].includes(preferredEdge) ? clamp(pos.top, spacing, innerHeight - popHeight - spacing) : pos.top;
pos.left = ['top', 'bottom'].includes(preferredEdge) ? clamp(pos.left, spacing, innerWidth - popWidth - spacing): pos.left;
return pos;
}, [popRef, getPosition, preferredEdge, preferredAlign, spacing])
return (
<Portal>
<div
ref={updateRef}
style={getClampedPosition()}
data-fading={isClosing}
data-visible={!isClosing}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
// className="absolute w-[400px] border"
className="bg-cinder border-2 border-falcon p-2 rounded-lg absolute transition-opacity data-[visible=true]:z-10 data-[visible=true]:opacity-100 data-[visible=false]:opacity-0 -z-10 max-w-[400px]"
>
{children}
</div>
</Portal>
)
}

View File

@@ -0,0 +1,47 @@
import { FC, ReactNode, useCallback, useEffect, useState } from 'react'
import { PoppableContent } from './poppable-content';
import { FCC } from '../../../types';
import { useDebounce } from '../../../hooks/useDebounce';
interface IProps {
content: ReactNode;
className?: string;
preferredEdge: 'top' | 'bottom' | 'left' | 'right';
preferredAlign: 'centered' | 'top' | 'bottom' | 'left' | 'right';
spacing?: number;
}
export const Poppable: FCC<IProps> = ({ className, content, children, preferredEdge, preferredAlign, spacing }) => {
const [isHovered, setIsHovered] = useState(false);
const closing = useDebounce(!isHovered, 1000);
const closed = useDebounce(closing, 300);
const [ref, setRef] = useState<HTMLElement>();
const updateRef = useCallback((node: HTMLElement) => {
if (!node) return;
setRef(node)
}, [])
return (
<>
<span
ref={updateRef}
className={className}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{children}
</span>
{!!ref && <PoppableContent
preferredAlign={preferredAlign}
preferredEdge={preferredEdge}
spacing={spacing}
isClosing={closing}
isClosed={closed}
relativeElement={ref}
setHover={setIsHovered}
>{content}</PoppableContent>}
</>
)
}

View File

@@ -0,0 +1,26 @@
import { useEffect, useState } from 'react';
import { FCC } from '../../../types';
import { createPortal } from 'react-dom';
interface IProps {
className?: string;
el?: string;
}
export const Portal: FCC<IProps> = ({ children, className = 'root-portal', el = 'div' }) => {
const [container] = useState(() => {
// This will be executed only on the initial render
// https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
return document.createElement(el);
});
useEffect(() => {
container.classList.add(className);
document.body.appendChild(container);
return () => {
document.body.removeChild(container);
}
}, [className, container]);
return createPortal(children, container);
}

View File

@@ -0,0 +1 @@
export const bulkRound = (...args: number[]): number[] => args.map(n => Math.round(n));

View File

@@ -0,0 +1,2 @@
export const clamp = (value: number, min: number, max: number) =>
Math.max(Math.min(value, max), min)

View File

@@ -1,7 +1,7 @@
import { atom } from 'recoil';
import { Schema, TypeType } from '../../types/schema';
import { Schema } from '../../types/schema';
export const SchemaEditAtom = atom<Schema>({
key: 'schema-edit',
default: {name: '', types: {}}
default: {name: '', types: {}, templates: {}, id: ''}
});

View File

@@ -1,24 +1,45 @@
import { Schema } from '../types/schema';
import { GameSystem } from "../types/gameSystem";
import { Schema } from "../types/schema";
import { idb } from "./indexeddb";
const emptySchema = {
name: "",
types: {},
templates: {},
id: crypto.randomUUID(),
};
export const GameSystemsService = {
// todo - connect to service to save schema for game
saveSchema: async (schema: Schema) => {
localStorage.setItem('schema', JSON.stringify(schema));
return { status: 200 }
// localStorage.setItem('schema ' + schema.id, JSON.stringify(schema));
try {
return await idb.createOrUpdate({
storeName: "schema",
...schema,
});
} catch (e) {
console.log(e);
}
},
// todo - connect to service to fetch schema for game
getSchema: async (id: string) => {
const schema = localStorage.getItem('schema');
if (schema)
return {
status: 200,
json: async () => JSON.parse(schema)
}
return {
status: 404
getSchema: async (
id: string,
): Promise<Schema> => {
try {
const schema = await idb.read("schema", id);
return schema;
} catch (e) {
throw e;
}
}
}
},
getSchemaList: async () => await idb.listAll<string>("schema", "name"),
saveGameSystem: async (gs: GameSystem) => {
localStorage.setItem("game-system " + gs.id, JSON.stringify(gs));
return { status: 200 };
},
getGameSystem: async (
id: string,
): Promise<GameSystem> => await idb.read("game-system", id),
};

View File

@@ -0,0 +1,7 @@
import { IndexedDBService } from "../utils/indexeddb";
export const idb = new IndexedDBService("commander", 1, [
"game-system",
"publication",
"schema",
]);

7
project-warstone/src/svg.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
declare module "*.svg" {
import * as React from "react";
export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
const src: string;
export default src;
}

View File

@@ -0,0 +1,15 @@
import { Schema } from './schema';
export type Accolade = {
id: string;
name: string;
description: string;
}
export type GameSystem = {
id: string;
schemaId: string;
schema: Schema;
name: string;
accolades: Accolade[];
}

View File

@@ -0,0 +1 @@
export type WithId<T> = {id: string} & T;

View File

@@ -0,0 +1,9 @@
import { WithId } from './gemerics';
export type Publication = {
name: string;
version: string;
gameSystemId: string;
status: 'published' | 'private';
templates: Record<string, WithId<any>[]>;
}

View File

@@ -1,5 +1,5 @@
export type MetadataType = {
[key: string]: string
[key: string]: string;
}
export type FieldType = {
@@ -7,22 +7,33 @@ export type FieldType = {
value: string;
isConstant: boolean;
limit: number;
minimum: number;
};
export type TypeType = Record<string, FieldType>;
export type Template = {
type: string;
publishable: boolean;
}
export type Schema = {
id: string;
name: string;
types: Record<string, TypeType>
templates: Record<string, Template>;
types: Record<string, TypeType>;
}
export enum FieldTypes {
number = 'number',
text = 'text',
'long text' = 'long text',
checkbox = 'checkbox',
type = '@type',
dice = 'dice'
dice = 'dice',
any = '@select',
select = 'select'
}
export const fieldTypeOptions: (keyof typeof FieldTypes)[] = ['number', 'text', 'checkbox', 'type', 'dice']
export const fieldTypesWithValues = [FieldTypes.dice, FieldTypes.type];
export const fieldTypeOptions: (keyof typeof FieldTypes)[] = ['number', 'text', 'long text', 'checkbox', 'type', 'dice', 'select', 'any']
export const fieldTypesWithValues = [FieldTypes.dice, FieldTypes.type, FieldTypes.select, FieldTypes.any];

View File

@@ -0,0 +1,4 @@
export const urlBuilder = (path: string) => {
const isDev = import.meta.env.DEV;
return encodeURI(isDev ? '/dev' : '' + path)
}

View File

@@ -0,0 +1,175 @@
type Data = Record<string, any> & { storeName: string };
export class IndexedDBService {
private dbName: string;
private dbVersion: number;
private storeNames: string[];
private db: IDBDatabase | null = null;
constructor(dbName: string, dbVersion: number, storeNames: string[]) {
this.dbName = dbName;
this.dbVersion = dbVersion;
this.storeNames = storeNames;
}
private async openDB(): Promise<IDBDatabase> {
if (this.db) return this.db;
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.dbVersion);
request.onerror = () => {
reject(new Error("Failed to open the database."));
};
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
// Create all the required object stores during the upgrade
for (const storeName of this.storeNames) {
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName, { keyPath: "uuid" });
}
}
};
request.onblocked = () => {
reject(new Error("Database is blocked and cannot be accessed."));
};
});
}
private generateUUID(): string {
return crypto.randomUUID();
}
public async create(data: Data): Promise<string> {
const db = await this.openDB();
const uuid = this.generateUUID();
return new Promise((resolve, reject) => {
const transaction = db.transaction(data.storeName, "readwrite");
const objectStore = transaction.objectStore(data.storeName);
const request = objectStore.add({ ...data, uuid });
request.onsuccess = () => {
resolve(uuid);
};
request.onerror = () => {
reject(new Error("Failed to add data to IndexedDB."));
};
});
}
public async read(storeName: string, uuid: string): Promise<any | null> {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, "readonly");
const objectStore = transaction.objectStore(storeName);
const request = objectStore.get(uuid);
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
reject(new Error("Failed to read data from IndexedDB."));
};
});
}
public async update(
storeName: string,
uuid: string,
newData: any,
): Promise<void> {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, "readwrite");
const objectStore = transaction.objectStore(storeName);
const request = objectStore.put({ ...newData, uuid });
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
reject(new Error("Failed to update data in IndexedDB."));
};
});
}
public async delete(storeName: string, uuid: string): Promise<void> {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, "readwrite");
const objectStore = transaction.objectStore(storeName);
const request = objectStore.delete(uuid);
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
reject(new Error("Failed to delete data from IndexedDB."));
};
});
}
public async listAll<T = any>(
storeName: string,
fieldName: string,
): Promise<[string, T][]> {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, "readonly");
const objectStore = transaction.objectStore(storeName);
const request = objectStore.openCursor();
const results: [string, any][] = [];
request.onsuccess = (event) => {
const cursor: IDBCursorWithValue = (event.target as IDBRequest).result;
if (cursor) {
const data = cursor.value;
if (fieldName in data) {
results.push([data.uuid, data[fieldName]]);
}
cursor.continue();
} else {
resolve(results);
}
};
request.onerror = () => {
reject(new Error("Failed to list data from IndexedDB."));
};
});
}
public async createOrUpdate(data: Data): Promise<string> {
// If the provided data already has a UUID, check if it exists in the database
if (data.uuid) {
const existingData = await this.read(data.storeName, data.uuid);
if (existingData) {
await this.update(data.storeName, data.uuid, data);
return data.uuid;
}
}
// Generate a new UUID for the data and use the create method
const uuid = this.generateUUID();
await this.create({ ...data, uuid, storeName: data.storeName });
return uuid;
}
}

View File

@@ -1,3 +1,7 @@
import plugin from 'tailwindcss/plugin';
import flattenColorPalette from 'tailwindcss/lib/util/flattenColorPalette'
import {parseColor} from 'tailwindcss/lib/util/color'
/** @type {import('tailwindcss').Config} */
export default {
content: [
@@ -46,9 +50,32 @@ export default {
},
container: {
center: true
},
height: {
variable: 'var(--v-height)'
}
},
},
plugins: [],
plugins: [
plugin(({matchUtilities, theme}) => {
matchUtilities({
'svg': (value) => {
const ignored = ['inherit', 'currentColor',]
if (ignored.includes(value)) {
return {
fill: value,
stroke: value,
}
}
const {color, alpha} = parseColor(value)
return {
fill: `rgba(${color[0]} ${color[1]} ${color[2]} / ${alpha || 1})`,
stroke: `rgba(${color[0]} ${color[1]} ${color[2]} / ${alpha || 1})`
}
}
}, {values: flattenColorPalette(theme('colors')), type: 'color'})
})
],
}

View File

@@ -18,8 +18,9 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
},
"include": ["src"],
"include": ["src", "src/svg.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -1,11 +1,20 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import svgr from 'vite-plugin-svgr'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [react(), svgr()],
appType: 'spa',
preview: {
port: 6969
},
server: {
proxy: {
'/dev': {
target: 'http://localhost:3000',
rewrite: (p) => p.replace('/dev', '')
}
}
}
})

View File

@@ -7,6 +7,195 @@
resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
"@ampproject/remapping@^2.2.0":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
dependencies:
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658"
integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==
dependencies:
"@babel/highlight" "^7.22.5"
"@babel/compat-data@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.5.tgz#b1f6c86a02d85d2dd3368a2b67c09add8cd0c255"
integrity sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==
"@babel/core@^7.21.3":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.5.tgz#d67d9747ecf26ee7ecd3ebae1ee22225fe902a89"
integrity sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.22.5"
"@babel/generator" "^7.22.5"
"@babel/helper-compilation-targets" "^7.22.5"
"@babel/helper-module-transforms" "^7.22.5"
"@babel/helpers" "^7.22.5"
"@babel/parser" "^7.22.5"
"@babel/template" "^7.22.5"
"@babel/traverse" "^7.22.5"
"@babel/types" "^7.22.5"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.2"
semver "^6.3.0"
"@babel/generator@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.5.tgz#1e7bf768688acfb05cf30b2369ef855e82d984f7"
integrity sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==
dependencies:
"@babel/types" "^7.22.5"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-compilation-targets@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz#fc7319fc54c5e2fa14b2909cf3c5fd3046813e02"
integrity sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==
dependencies:
"@babel/compat-data" "^7.22.5"
"@babel/helper-validator-option" "^7.22.5"
browserslist "^4.21.3"
lru-cache "^5.1.1"
semver "^6.3.0"
"@babel/helper-environment-visitor@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98"
integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==
"@babel/helper-function-name@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be"
integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==
dependencies:
"@babel/template" "^7.22.5"
"@babel/types" "^7.22.5"
"@babel/helper-hoist-variables@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-module-imports@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c"
integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-module-transforms@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz#0f65daa0716961b6e96b164034e737f60a80d2ef"
integrity sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==
dependencies:
"@babel/helper-environment-visitor" "^7.22.5"
"@babel/helper-module-imports" "^7.22.5"
"@babel/helper-simple-access" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.5"
"@babel/template" "^7.22.5"
"@babel/traverse" "^7.22.5"
"@babel/types" "^7.22.5"
"@babel/helper-simple-access@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de"
integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-split-export-declaration@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz#88cf11050edb95ed08d596f7a044462189127a08"
integrity sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-string-parser@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
"@babel/helper-validator-identifier@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193"
integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==
"@babel/helper-validator-option@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac"
integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==
"@babel/helpers@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.5.tgz#74bb4373eb390d1ceed74a15ef97767e63120820"
integrity sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==
dependencies:
"@babel/template" "^7.22.5"
"@babel/traverse" "^7.22.5"
"@babel/types" "^7.22.5"
"@babel/highlight@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031"
integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==
dependencies:
"@babel/helper-validator-identifier" "^7.22.5"
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.5.tgz#721fd042f3ce1896238cf1b341c77eb7dee7dbea"
integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==
"@babel/template@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec"
integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==
dependencies:
"@babel/code-frame" "^7.22.5"
"@babel/parser" "^7.22.5"
"@babel/types" "^7.22.5"
"@babel/traverse@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.5.tgz#44bd276690db6f4940fdb84e1cb4abd2f729ccd1"
integrity sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==
dependencies:
"@babel/code-frame" "^7.22.5"
"@babel/generator" "^7.22.5"
"@babel/helper-environment-visitor" "^7.22.5"
"@babel/helper-function-name" "^7.22.5"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.5"
"@babel/parser" "^7.22.5"
"@babel/types" "^7.22.5"
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.21.3", "@babel/types@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe"
integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.5"
to-fast-properties "^2.0.0"
"@esbuild/android-arm64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd"
@@ -168,7 +357,7 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
"@jridgewell/gen-mapping@^0.3.2":
"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
@@ -197,7 +386,7 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.9":
"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.18"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6"
integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==
@@ -231,6 +420,97 @@
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.6.3.tgz#8205baf6e17ef93be35bf62c37d2d594e9be0dad"
integrity sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q==
"@rollup/pluginutils@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33"
integrity sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==
dependencies:
"@types/estree" "^1.0.0"
estree-walker "^2.0.2"
picomatch "^2.3.1"
"@svgr/babel-plugin-add-jsx-attribute@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-7.0.0.tgz#80856c1b7a3b7422d232f6e079f0beb90c4a13e9"
integrity sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q==
"@svgr/babel-plugin-remove-jsx-attribute@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-7.0.0.tgz#91da77a009dc38e8d30da45d9b62ef8736f2d90a"
integrity sha512-iiZaIvb3H/c7d3TH2HBeK91uI2rMhZNwnsIrvd7ZwGLkFw6mmunOCoVnjdYua662MqGFxlN9xTq4fv9hgR4VXQ==
"@svgr/babel-plugin-remove-jsx-empty-expression@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-7.0.0.tgz#5154ff1213509e36ab315974c8c2fd48dafb827b"
integrity sha512-sQQmyo+qegBx8DfFc04PFmIO1FP1MHI1/QEpzcIcclo5OAISsOJPW76ZIs0bDyO/DBSJEa/tDa1W26pVtt0FRw==
"@svgr/babel-plugin-replace-jsx-attribute-value@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-7.0.0.tgz#7e72f44ee57fdbcb02fb0d4a7629466c5242725e"
integrity sha512-i6MaAqIZXDOJeikJuzocByBf8zO+meLwfQ/qMHIjCcvpnfvWf82PFvredEZElErB5glQFJa2KVKk8N2xV6tRRA==
"@svgr/babel-plugin-svg-dynamic-title@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-7.0.0.tgz#8caf0449c678ea29be756b89960b2b16c9f33f00"
integrity sha512-BoVSh6ge3SLLpKC0pmmN9DFlqgFy4NxNgdZNLPNJWBUU7TQpDWeBuyVuDW88iXydb5Cv0ReC+ffa5h3VrKfk1w==
"@svgr/babel-plugin-svg-em-dimensions@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-7.0.0.tgz#4db6b5af6d29e93db236b1a013fa953754071d41"
integrity sha512-tNDcBa+hYn0gO+GkP/AuNKdVtMufVhU9fdzu+vUQsR18RIJ9RWe7h/pSBY338RO08wArntwbDk5WhQBmhf2PaA==
"@svgr/babel-plugin-transform-react-native-svg@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-7.0.0.tgz#236995e58b5e36ff06365d5310509ce5391aeec9"
integrity sha512-qw54u8ljCJYL2KtBOjI5z7Nzg8LnSvQOP5hPKj77H4VQL4+HdKbAT5pnkkZLmHKYwzsIHSYKXxHouD8zZamCFQ==
"@svgr/babel-plugin-transform-svg-component@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-7.0.0.tgz#a9b62730acf10d22a2aa57e0f701c0ecbc270430"
integrity sha512-CcFECkDj98daOg9jE3Bh3uyD9kzevCAnZ+UtzG6+BQG/jOQ2OA3jHnX6iG4G1MCJkUQFnUvEv33NvQfqrb/F3A==
"@svgr/babel-preset@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-7.0.0.tgz#55aaca4cec2ff6515a571715b6b6fa98675b66d9"
integrity sha512-EX/NHeFa30j5UjldQGVQikuuQNHUdGmbh9kEpBKofGUtF0GUPJ4T4rhoYiqDAOmBOxojyot36JIFiDUHUK1ilQ==
dependencies:
"@svgr/babel-plugin-add-jsx-attribute" "^7.0.0"
"@svgr/babel-plugin-remove-jsx-attribute" "^7.0.0"
"@svgr/babel-plugin-remove-jsx-empty-expression" "^7.0.0"
"@svgr/babel-plugin-replace-jsx-attribute-value" "^7.0.0"
"@svgr/babel-plugin-svg-dynamic-title" "^7.0.0"
"@svgr/babel-plugin-svg-em-dimensions" "^7.0.0"
"@svgr/babel-plugin-transform-react-native-svg" "^7.0.0"
"@svgr/babel-plugin-transform-svg-component" "^7.0.0"
"@svgr/core@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/core/-/core-7.0.0.tgz#def863d2670c682615583c80b408e83c095c2233"
integrity sha512-ztAoxkaKhRVloa3XydohgQQCb0/8x9T63yXovpmHzKMkHO6pkjdsIAWKOS4bE95P/2quVh1NtjSKlMRNzSBffw==
dependencies:
"@babel/core" "^7.21.3"
"@svgr/babel-preset" "^7.0.0"
camelcase "^6.2.0"
cosmiconfig "^8.1.3"
"@svgr/hast-util-to-babel-ast@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-7.0.0.tgz#d457dfbe74ebc1e5a6daf97ded49e9576a3a00cf"
integrity sha512-42Ej9sDDEmsJKjrfQ1PHmiDiHagh/u9AHO9QWbeNx4KmD9yS5d1XHmXUNINfUcykAU+4431Cn+k6Vn5mWBYimQ==
dependencies:
"@babel/types" "^7.21.3"
entities "^4.4.0"
"@svgr/plugin-jsx@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-7.0.0.tgz#b9e0c7d05bc890d70163ac0490ba8c41f1afab90"
integrity sha512-SWlTpPQmBUtLKxXWgpv8syzqIU8XgFRvyhfkam2So8b3BE0OS0HPe5UfmlJ2KIC+a7dpuuYovPR2WAQuSyMoPw==
dependencies:
"@babel/core" "^7.21.3"
"@svgr/babel-preset" "^7.0.0"
"@svgr/hast-util-to-babel-ast" "^7.0.0"
svg-parser "^2.0.4"
"@swc/core-darwin-arm64@1.3.62":
version "1.3.62"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.62.tgz#dafb50bf784c6b7b40dce6d8cf0605f6729812cb"
@@ -297,6 +577,11 @@
"@swc/core-win32-ia32-msvc" "1.3.62"
"@swc/core-win32-x64-msvc" "1.3.62"
"@types/estree@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
"@types/history@^4.7.11":
version "4.7.11"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
@@ -345,6 +630,11 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/recoilize@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@types/recoilize/-/recoilize-0.8.0.tgz#7351f7a048bcef9c712f39d05696ce7fd2a414fe"
integrity sha512-h4Rm8J/8XH1wZIZC5gOeN9EDJWBhK2sLEs5voSHyZKB5cynIDbohuYS6rEdv2kNPrwoYQyt7D3yeXI9ODvZDlw==
"@types/scheduler@*":
version "0.16.3"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
@@ -471,6 +761,13 @@ ansi-regex@^5.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
@@ -543,7 +840,7 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
browserslist@^4.21.5:
browserslist@^4.21.3, browserslist@^4.21.5:
version "4.21.7"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.7.tgz#e2b420947e5fb0a58e8f4668ae6e23488127e551"
integrity sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==
@@ -563,11 +860,25 @@ camelcase-css@^2.0.1:
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
camelcase@^6.2.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001489:
version "1.0.30001495"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001495.tgz#64a0ccef1911a9dcff647115b4430f8eff1ef2d9"
integrity sha512-F6x5IEuigtUfU5ZMQK2jsy5JqUUlEFRVZq8bO2a+ysq5K7jD6PPc9YXZj78xDNS3uNchesp1Jw47YXEqr+Viyg==
chalk@^2.0.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.0.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
@@ -591,6 +902,13 @@ chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@@ -598,6 +916,11 @@ color-convert@^2.0.1:
dependencies:
color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
@@ -613,6 +936,21 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
convert-source-map@^1.7.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
cosmiconfig@^8.1.3:
version "8.2.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.2.0.tgz#f7d17c56a590856cd1e7cee98734dca272b0d8fd"
integrity sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==
dependencies:
import-fresh "^3.2.1"
js-yaml "^4.1.0"
parse-json "^5.0.0"
path-type "^4.0.0"
cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -632,7 +970,7 @@ csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -673,6 +1011,18 @@ electron-to-chromium@^1.4.411:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.421.tgz#2b8c0ef98ba00d4aef4c664933d570922da52161"
integrity sha512-wZOyn3s/aQOtLGAwXMZfteQPN68kgls2wDAnYOA8kCjBvKVrW5RwmWVspxJYTqrcN7Y263XJVsC66VCIGzDO3g==
entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
dependencies:
is-arrayish "^0.2.1"
esbuild@^0.17.5:
version "0.17.19"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.19.tgz#087a727e98299f0462a3d0bcdd9cd7ff100bd955"
@@ -706,6 +1056,11 @@ escalade@^3.1.1:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
@@ -820,6 +1175,11 @@ estraverse@^5.1.0, estraverse@^5.2.0:
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
estree-walker@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@@ -913,6 +1273,11 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -951,6 +1316,11 @@ glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
globals@^13.19.0:
version "13.20.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82"
@@ -985,6 +1355,11 @@ hamt_plus@1.0.2:
resolved "https://registry.yarnpkg.com/hamt_plus/-/hamt_plus-1.0.2.tgz#e21c252968c7e33b20f6a1b094cd85787a265601"
integrity sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
@@ -1028,6 +1403,11 @@ inherits@2:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@@ -1074,7 +1454,7 @@ jiti@^1.18.2:
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.18.2.tgz#80c3ef3d486ebf2450d9335122b32d121f2a83cd"
integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==
"js-tokens@^3.0.0 || ^4.0.0":
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
@@ -1086,6 +1466,16 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json-parse-even-better-errors@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@@ -1096,6 +1486,11 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
json5@^2.2.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
levn@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
@@ -1133,6 +1528,13 @@ loose-envify@^1.1.0:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
dependencies:
yallist "^3.0.2"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
@@ -1254,6 +1656,16 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parse-json@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
dependencies:
"@babel/code-frame" "^7.0.0"
error-ex "^1.3.1"
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@@ -1418,6 +1830,11 @@ recoil@^0.7.7:
dependencies:
hamt_plus "1.0.2"
recoilize@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/recoilize/-/recoilize-3.2.0.tgz#aaf4c76390293a105a73b74dced7a49dfbeb1c84"
integrity sha512-2J0SEzefl8rKe5iaJ2vVg6AgWy5aQ8NFPfZbZfXagycF5ohUzCQA2c2SZDz5jtZfYmendR8hsILstPyofxfg7g==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -1465,6 +1882,11 @@ scheduler@^0.23.0:
dependencies:
loose-envify "^1.1.0"
semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.3.7:
version "7.5.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec"
@@ -1519,6 +1941,13 @@ sucrase@^3.32.0:
pirates "^4.0.1"
ts-interface-checker "^0.1.9"
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@@ -1531,6 +1960,11 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
svg-parser@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==
tailwindcss@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.2.tgz#2f9e35d715fdf0bbf674d90147a0684d7054a2d3"
@@ -1579,6 +2013,11 @@ thenify-all@^1.0.0:
dependencies:
any-promise "^1.0.0"
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -1640,6 +2079,15 @@ util-deprecate@^1.0.2:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
vite-plugin-svgr@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-3.2.0.tgz#920375aaf6635091c9ac8e467825f92d32544476"
integrity sha512-Uvq6niTvhqJU6ga78qLKBFJSDvxWhOnyfQSoKpDPMAGxJPo5S3+9hyjExE5YDj6Lpa4uaLkGc1cBgxXov+LjSw==
dependencies:
"@rollup/pluginutils" "^5.0.2"
"@svgr/core" "^7.0.0"
"@svgr/plugin-jsx" "^7.0.0"
vite@^4.3.9:
version "4.3.9"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.9.tgz#db896200c0b1aa13b37cdc35c9e99ee2fdd5f96d"
@@ -1668,6 +2116,11 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"

View File

@@ -1,7 +1,6 @@
import { proxy } from "https://deno.land/x/oak_http_proxy@2.1.0/mod.ts";
import { fileExists } from "./lib/fileExists.ts";
import { Application, Context, Router } from "oak";
import { path } from "https://deno.land/x/compress@v0.4.1/deps.ts";
const app = new Application();
@@ -21,6 +20,7 @@ for await (const dirEntry of Deno.readDir(Deno.cwd())) {
args: [
'run',
...perms,
'--watch',
filename,
port
]
@@ -28,8 +28,8 @@ for await (const dirEntry of Deno.readDir(Deno.cwd())) {
const prefix = Deno.readTextFileSync(prefixfile);
const routes = new Router()
.all(`/${prefix}/(.*)`, proxy((ctx: Context) => `http://localhost:${port}${ctx.request.url.pathname}`, {}))
.all(prefix ? `/${prefix}/(.*)` : '/(.*)', proxy((ctx: Context) => `http://localhost:${port}${ctx.request.url.pathname}`))
app.use(routes.allowedMethods());
app.use(routes.routes());

View File

@@ -0,0 +1,15 @@
FROM denoland/deno:1.33.2
EXPOSE 6904
WORKDIR /warstone-web
ADD ./warstone-web-service/ .
ADD ./deno.jsonc .
ADD ./secrets.json .
ADD ./key.txt .
ADD ./common ./common
ADD ./lib ./lib
ADD ./middleware ./middleware
CMD ["run", "[object Object]", "[object Object]", main.ts, "6904"]

View File

@@ -0,0 +1,23 @@
import { CGGService } from 'cgg/Application.ts';
import { Router, send } from 'oak';
const prefix = ''
const app = new CGGService({ prefix: `/${prefix}` });
// app.route(new Router()
// .get('/', ctx => ctx.response.body = 'warstone-web service')
// );
const ROOT_DIR = "./project-warstone/dist";
app.use(async (ctx) => {
if (ctx.request.url.pathname.includes('favicon')) return ctx.response.status = 404;
const filePath = ctx.request.url.pathname.replace(prefix, "");
await send(ctx, filePath, {
root: ROOT_DIR,
index: 'index.html'
});
});
app.start();
console.log('warstone-web service running on ' + Deno.args.at(0));

View File

@@ -0,0 +1,2 @@
--allow-read
--allow-net

View File

@@ -0,0 +1 @@
6904

View File