Compare commits
No commits in common. "a8a903d5818b3034c527e9392c7261b9b6e0ae5a" and "7e4d854685f97ec8cd4d0a0cf7f53aa8eb89e9ce" have entirely different histories.
a8a903d581
...
7e4d854685
2
.gitignore
vendored
2
.gitignore
vendored
@ -26,5 +26,3 @@ dist-ssr
|
|||||||
.env
|
.env
|
||||||
|
|
||||||
BearMetal/
|
BearMetal/
|
||||||
resources/
|
|
||||||
!**/*/resources/
|
|
17
deno.json
17
deno.json
@ -1,12 +1,9 @@
|
|||||||
{
|
{
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"dev": "deno run -A --node-modules-dir npm:vite & deno run -A --watch ./server/main.ts",
|
"dev": "deno run -A --node-modules-dir npm:vite & deno run --allow-net --allow-read --allow-write --allow-env --watch ./main.ts",
|
||||||
"fdev": "deno run -A --node-modules-dir npm:vite",
|
|
||||||
"build": "deno run -A --node-modules-dir npm:vite build",
|
"build": "deno run -A --node-modules-dir npm:vite build",
|
||||||
"preview": "deno run -A --node-modules-dir npm:vite preview",
|
"preview": "deno run -A --node-modules-dir npm:vite preview",
|
||||||
"serve": "deno run -A ./server/main.ts",
|
"serve": "deno run --allow-net --allow-read --allow-write --allow-env ./main.ts"
|
||||||
"bdev": "deno run -A --watch ./server/main.ts",
|
|
||||||
"tmux": "./session.sh"
|
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable", "deno.ns"],
|
"lib": ["ES2020", "DOM", "DOM.Iterable", "deno.ns"],
|
||||||
@ -15,25 +12,17 @@
|
|||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"@babel/plugin-transform-react-jsx-development": "npm:@babel/plugin-transform-react-jsx-development@^7.25.7",
|
"@babel/plugin-transform-react-jsx-development": "npm:@babel/plugin-transform-react-jsx-development@^7.25.7",
|
||||||
"@bearmetal/store": "jsr:@bearmetal/store@^0.0.5",
|
"@bearmetal/store": "jsr:@bearmetal/store@^0.0.4",
|
||||||
"@cgg/sockpuppet": "../sockpuppet.ts/server/mod.ts",
|
"@cgg/sockpuppet": "../sockpuppet.ts/server/mod.ts",
|
||||||
"@cgg/sockpuppet/client": "../sockpuppet.ts/client/mod.ts",
|
"@cgg/sockpuppet/client": "../sockpuppet.ts/client/mod.ts",
|
||||||
"@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.0",
|
"@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.0",
|
||||||
"@gfx/canvas": "jsr:@gfx/canvas@^0.5.8",
|
|
||||||
"@preact/preset-vite": "npm:@preact/preset-vite@^2.9.1",
|
"@preact/preset-vite": "npm:@preact/preset-vite@^2.9.1",
|
||||||
"@std/encoding": "jsr:@std/encoding@^1.0.5",
|
|
||||||
"@std/fs": "jsr:@std/fs@^1.0.4",
|
|
||||||
"@std/http": "jsr:@std/http@^1.0.8",
|
"@std/http": "jsr:@std/http@^1.0.8",
|
||||||
"@std/path": "jsr:@std/path@^1.0.6",
|
|
||||||
"@zip.js/zip.js": "npm:@zip.js/zip.js@^2.7.52",
|
|
||||||
"autoprefixer": "npm:autoprefixer@^10.4.20",
|
"autoprefixer": "npm:autoprefixer@^10.4.20",
|
||||||
"babel-plugin-transform-hook-names": "npm:babel-plugin-transform-hook-names@^1.0.2",
|
"babel-plugin-transform-hook-names": "npm:babel-plugin-transform-hook-names@^1.0.2",
|
||||||
"jotai": "npm:jotai@^2.10.1",
|
|
||||||
"less": "npm:less@^4.2.0",
|
|
||||||
"postcss": "npm:postcss@^8.4.47",
|
"postcss": "npm:postcss@^8.4.47",
|
||||||
"preact": "npm:preact@^10.24.3",
|
"preact": "npm:preact@^10.24.3",
|
||||||
"react-router-dom": "npm:react-router-dom@^6.27.0",
|
"react-router-dom": "npm:react-router-dom@^6.27.0",
|
||||||
"swr": "npm:swr@^2.2.5",
|
|
||||||
"tailwindcss": "npm:tailwindcss@^3.4.13",
|
"tailwindcss": "npm:tailwindcss@^3.4.13",
|
||||||
"vite": "npm:vite@^5.4.8"
|
"vite": "npm:vite@^5.4.8"
|
||||||
}
|
}
|
||||||
|
213
deno.lock
generated
213
deno.lock
generated
@ -1,125 +1,62 @@
|
|||||||
{
|
{
|
||||||
"version": "4",
|
"version": "4",
|
||||||
"specifiers": {
|
"specifiers": {
|
||||||
"jsr:@bearmetal/store@^0.0.5": "0.0.5",
|
"jsr:@bearmetal/store@^0.0.4": "0.0.4",
|
||||||
"jsr:@denosaurs/plug@1.0.5": "1.0.5",
|
|
||||||
"jsr:@gfx/canvas@~0.5.8": "0.5.8",
|
|
||||||
"jsr:@std/assert@0.214": "0.214.0",
|
|
||||||
"jsr:@std/assert@0.217": "0.217.0",
|
|
||||||
"jsr:@std/cli@^1.0.6": "1.0.6",
|
"jsr:@std/cli@^1.0.6": "1.0.6",
|
||||||
"jsr:@std/encoding@0.214": "0.214.0",
|
|
||||||
"jsr:@std/encoding@0.217.0": "0.217.0",
|
|
||||||
"jsr:@std/encoding@^1.0.5": "1.0.5",
|
"jsr:@std/encoding@^1.0.5": "1.0.5",
|
||||||
"jsr:@std/fmt@0.214": "0.214.0",
|
|
||||||
"jsr:@std/fmt@^1.0.2": "1.0.2",
|
"jsr:@std/fmt@^1.0.2": "1.0.2",
|
||||||
"jsr:@std/fs@*": "1.0.4",
|
"jsr:@std/fs@*": "1.0.4",
|
||||||
"jsr:@std/fs@0.214": "0.214.0",
|
|
||||||
"jsr:@std/fs@0.217.0": "0.217.0",
|
|
||||||
"jsr:@std/fs@^1.0.4": "1.0.4",
|
"jsr:@std/fs@^1.0.4": "1.0.4",
|
||||||
"jsr:@std/http@^1.0.8": "1.0.8",
|
"jsr:@std/http@^1.0.8": "1.0.8",
|
||||||
"jsr:@std/media-types@^1.0.3": "1.0.3",
|
"jsr:@std/media-types@^1.0.3": "1.0.3",
|
||||||
"jsr:@std/net@^1.0.4": "1.0.4",
|
"jsr:@std/net@^1.0.4": "1.0.4",
|
||||||
"jsr:@std/path@0.214": "0.214.0",
|
|
||||||
"jsr:@std/path@0.217": "0.217.0",
|
|
||||||
"jsr:@std/path@0.217.0": "0.217.0",
|
|
||||||
"jsr:@std/path@^1.0.6": "1.0.6",
|
"jsr:@std/path@^1.0.6": "1.0.6",
|
||||||
"jsr:@std/streams@^1.0.7": "1.0.7",
|
"jsr:@std/streams@^1.0.7": "1.0.7",
|
||||||
"npm:@babel/plugin-transform-react-jsx-development@^7.25.7": "7.25.7_@babel+core@7.25.8",
|
"npm:@babel/plugin-transform-react-jsx-development@^7.25.7": "7.25.7_@babel+core@7.25.8",
|
||||||
"npm:@deno/vite-plugin@1": "1.0.0_vite@5.4.9",
|
"npm:@deno/vite-plugin@1": "1.0.0_vite@5.4.9",
|
||||||
"npm:@preact/preset-vite@^2.9.1": "2.9.1_@babel+core@7.25.8_vite@5.4.9_preact@10.24.3",
|
"npm:@preact/preset-vite@^2.9.1": "2.9.1_@babel+core@7.25.8_vite@5.4.9_preact@10.24.3",
|
||||||
"npm:@types/node@*": "22.5.4",
|
"npm:@types/node@*": "22.5.4",
|
||||||
"npm:@zip.js/zip.js@^2.7.52": "2.7.52",
|
|
||||||
"npm:autoprefixer@^10.4.20": "10.4.20_postcss@8.4.47",
|
"npm:autoprefixer@^10.4.20": "10.4.20_postcss@8.4.47",
|
||||||
"npm:babel-plugin-transform-hook-names@^1.0.2": "1.0.2_@babel+core@7.25.8",
|
"npm:babel-plugin-transform-hook-names@^1.0.2": "1.0.2_@babel+core@7.25.8",
|
||||||
"npm:jotai@^2.10.1": "2.10.1",
|
|
||||||
"npm:less@^4.2.0": "4.2.0",
|
|
||||||
"npm:postcss@^8.4.47": "8.4.47",
|
"npm:postcss@^8.4.47": "8.4.47",
|
||||||
"npm:preact@^10.24.3": "10.24.3",
|
"npm:preact@^10.24.3": "10.24.3",
|
||||||
"npm:react-router-dom@^6.27.0": "6.27.0_react@18.3.1_react-dom@18.3.1__react@18.3.1",
|
"npm:react-router-dom@^6.27.0": "6.27.0_react@18.3.1_react-dom@18.3.1__react@18.3.1",
|
||||||
"npm:swr@^2.2.5": "2.2.5_react@18.3.1",
|
|
||||||
"npm:tailwindcss@*": "3.4.13_postcss@8.4.47",
|
"npm:tailwindcss@*": "3.4.13_postcss@8.4.47",
|
||||||
"npm:tailwindcss@^3.4.13": "3.4.13_postcss@8.4.47",
|
"npm:tailwindcss@^3.4.13": "3.4.13_postcss@8.4.47",
|
||||||
"npm:vite@*": "5.4.9",
|
"npm:vite@*": "5.4.9",
|
||||||
"npm:vite@^5.4.8": "5.4.9"
|
"npm:vite@^5.4.8": "5.4.9"
|
||||||
},
|
},
|
||||||
"jsr": {
|
"jsr": {
|
||||||
"@bearmetal/store@0.0.5": {
|
"@bearmetal/store@0.0.4": {
|
||||||
"integrity": "d17da24c91bcc05707deb8a55017ebdf5d8eebd2f6293dcb2bbfac57e4e3b395",
|
"integrity": "f5859476184d6f7b3957d18c7c82a37b6b89bb75e18db3186fde94ccb4253dab",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"jsr:@std/fs@^1.0.4"
|
"jsr:@std/fs@^1.0.4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@denosaurs/plug@1.0.5": {
|
|
||||||
"integrity": "04cd988da558adc226202d88c3a434d5fcc08146eaf4baf0cea0c2284b16d2bf",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/encoding@0.214",
|
|
||||||
"jsr:@std/fmt@0.214",
|
|
||||||
"jsr:@std/fs@0.214",
|
|
||||||
"jsr:@std/path@0.214"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@gfx/canvas@0.5.8": {
|
|
||||||
"integrity": "a61c80292528e7433d428556b494a0ea496dd8e6abd4a338b8b25fc04e46ea3e",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@denosaurs/plug",
|
|
||||||
"jsr:@std/encoding@0.217.0",
|
|
||||||
"jsr:@std/fs@0.217.0",
|
|
||||||
"jsr:@std/path@0.217.0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/assert@0.214.0": {
|
|
||||||
"integrity": "55d398de76a9828fd3b1aa653f4dba3eee4c6985d90c514865d2be9bd082b140"
|
|
||||||
},
|
|
||||||
"@std/assert@0.217.0": {
|
|
||||||
"integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642"
|
|
||||||
},
|
|
||||||
"@std/cli@1.0.6": {
|
"@std/cli@1.0.6": {
|
||||||
"integrity": "d22d8b38c66c666d7ad1f2a66c5b122da1704f985d3c47f01129f05abb6c5d3d"
|
"integrity": "d22d8b38c66c666d7ad1f2a66c5b122da1704f985d3c47f01129f05abb6c5d3d"
|
||||||
},
|
},
|
||||||
"@std/encoding@0.214.0": {
|
|
||||||
"integrity": "30a8713e1db22986c7e780555ffd2fefd1d4f9374d734bb41f5970f6c3352af5"
|
|
||||||
},
|
|
||||||
"@std/encoding@0.217.0": {
|
|
||||||
"integrity": "b03e8ff94c98d6b6a02c02c5cf8e5d203400155516248964fc4559abc04669dc"
|
|
||||||
},
|
|
||||||
"@std/encoding@1.0.5": {
|
"@std/encoding@1.0.5": {
|
||||||
"integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
|
"integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
|
||||||
},
|
},
|
||||||
"@std/fmt@0.214.0": {
|
|
||||||
"integrity": "40382cff88a0783b347b4d69b94cf931ab8e549a733916718cb866c08efac4d4"
|
|
||||||
},
|
|
||||||
"@std/fmt@1.0.2": {
|
"@std/fmt@1.0.2": {
|
||||||
"integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7"
|
"integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7"
|
||||||
},
|
},
|
||||||
"@std/fs@0.214.0": {
|
|
||||||
"integrity": "bc880fea0be120cb1550b1ed7faf92fe071003d83f2456a1e129b39193d85bea",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@0.214",
|
|
||||||
"jsr:@std/path@0.214"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/fs@0.217.0": {
|
|
||||||
"integrity": "0bfff5f3618d68c385b28b4ffbf3a15c98293a0f1186444458b62e0111ce77b2",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@0.217",
|
|
||||||
"jsr:@std/path@0.217"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/fs@1.0.4": {
|
"@std/fs@1.0.4": {
|
||||||
"integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c",
|
"integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"jsr:@std/path@^1.0.6"
|
"jsr:@std/path"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@std/http@1.0.8": {
|
"@std/http@1.0.8": {
|
||||||
"integrity": "6ea1b2e8d33929967754a3b6d6c6f399ad6647d7bbb5a466c1eaf9b294a6ebcd",
|
"integrity": "6ea1b2e8d33929967754a3b6d6c6f399ad6647d7bbb5a466c1eaf9b294a6ebcd",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"jsr:@std/cli",
|
"jsr:@std/cli",
|
||||||
"jsr:@std/encoding@^1.0.5",
|
"jsr:@std/encoding",
|
||||||
"jsr:@std/fmt@^1.0.2",
|
"jsr:@std/fmt",
|
||||||
"jsr:@std/media-types",
|
"jsr:@std/media-types",
|
||||||
"jsr:@std/net",
|
"jsr:@std/net",
|
||||||
"jsr:@std/path@^1.0.6",
|
"jsr:@std/path",
|
||||||
"jsr:@std/streams"
|
"jsr:@std/streams"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -129,18 +66,6 @@
|
|||||||
"@std/net@1.0.4": {
|
"@std/net@1.0.4": {
|
||||||
"integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852"
|
"integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852"
|
||||||
},
|
},
|
||||||
"@std/path@0.214.0": {
|
|
||||||
"integrity": "d5577c0b8d66f7e8e3586d864ebdf178bb326145a3611da5a51c961740300285",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@0.214"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/path@0.217.0": {
|
|
||||||
"integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@0.217"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/path@1.0.6": {
|
"@std/path@1.0.6": {
|
||||||
"integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed"
|
"integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed"
|
||||||
},
|
},
|
||||||
@ -186,7 +111,7 @@
|
|||||||
"debug",
|
"debug",
|
||||||
"gensync",
|
"gensync",
|
||||||
"json5",
|
"json5",
|
||||||
"semver@6.3.1"
|
"semver"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@babel/generator@7.25.7": {
|
"@babel/generator@7.25.7": {
|
||||||
@ -211,7 +136,7 @@
|
|||||||
"@babel/helper-validator-option",
|
"@babel/helper-validator-option",
|
||||||
"browserslist",
|
"browserslist",
|
||||||
"lru-cache@5.1.1",
|
"lru-cache@5.1.1",
|
||||||
"semver@6.3.1"
|
"semver"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@babel/helper-module-imports@7.25.7": {
|
"@babel/helper-module-imports@7.25.7": {
|
||||||
@ -469,7 +394,7 @@
|
|||||||
"kolorist",
|
"kolorist",
|
||||||
"magic-string",
|
"magic-string",
|
||||||
"node-html-parser",
|
"node-html-parser",
|
||||||
"source-map@0.7.4",
|
"source-map",
|
||||||
"stack-trace",
|
"stack-trace",
|
||||||
"vite"
|
"vite"
|
||||||
]
|
]
|
||||||
@ -565,9 +490,6 @@
|
|||||||
"undici-types"
|
"undici-types"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@zip.js/zip.js@2.7.52": {
|
|
||||||
"integrity": "sha512-+5g7FQswvrCHwYKNMd/KFxZSObctLSsQOgqBSi0LzwHo3li9Eh1w5cF5ndjQw9Zbr3ajVnd2+XyiX85gAetx1Q=="
|
|
||||||
},
|
|
||||||
"ansi-regex@5.0.1": {
|
"ansi-regex@5.0.1": {
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
||||||
},
|
},
|
||||||
@ -677,9 +599,6 @@
|
|||||||
"readdirp"
|
"readdirp"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"client-only@0.0.1": {
|
|
||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
|
||||||
},
|
|
||||||
"color-convert@1.9.3": {
|
"color-convert@1.9.3": {
|
||||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@ -704,12 +623,6 @@
|
|||||||
"convert-source-map@2.0.0": {
|
"convert-source-map@2.0.0": {
|
||||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
|
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
|
||||||
},
|
},
|
||||||
"copy-anything@2.0.6": {
|
|
||||||
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
|
|
||||||
"dependencies": [
|
|
||||||
"is-what"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"cross-spawn@7.0.3": {
|
"cross-spawn@7.0.3": {
|
||||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@ -786,12 +699,6 @@
|
|||||||
"entities@4.5.0": {
|
"entities@4.5.0": {
|
||||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
|
||||||
},
|
},
|
||||||
"errno@0.1.8": {
|
|
||||||
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
|
|
||||||
"dependencies": [
|
|
||||||
"prr"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"esbuild@0.21.5": {
|
"esbuild@0.21.5": {
|
||||||
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@ -896,9 +803,6 @@
|
|||||||
"globals@11.12.0": {
|
"globals@11.12.0": {
|
||||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
|
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
|
||||||
},
|
},
|
||||||
"graceful-fs@4.2.11": {
|
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
|
||||||
},
|
|
||||||
"has-flag@3.0.0": {
|
"has-flag@3.0.0": {
|
||||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
|
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
|
||||||
},
|
},
|
||||||
@ -911,15 +815,6 @@
|
|||||||
"he@1.2.0": {
|
"he@1.2.0": {
|
||||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||||
},
|
},
|
||||||
"iconv-lite@0.6.3": {
|
|
||||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
|
||||||
"dependencies": [
|
|
||||||
"safer-buffer"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"image-size@0.5.5": {
|
|
||||||
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ=="
|
|
||||||
},
|
|
||||||
"is-binary-path@2.1.0": {
|
"is-binary-path@2.1.0": {
|
||||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@ -947,9 +842,6 @@
|
|||||||
"is-number@7.0.0": {
|
"is-number@7.0.0": {
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||||
},
|
},
|
||||||
"is-what@3.14.1": {
|
|
||||||
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA=="
|
|
||||||
},
|
|
||||||
"isexe@2.0.0": {
|
"isexe@2.0.0": {
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||||
},
|
},
|
||||||
@ -963,9 +855,6 @@
|
|||||||
"jiti@1.21.6": {
|
"jiti@1.21.6": {
|
||||||
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w=="
|
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w=="
|
||||||
},
|
},
|
||||||
"jotai@2.10.1": {
|
|
||||||
"integrity": "sha512-4FycO+BOTl2auLyF2Chvi6KTDqdsdDDtpaL/WHQMs8f3KS1E3loiUShQzAzFA/sMU5cJ0hz/RT1xum9YbG/zaA=="
|
|
||||||
},
|
|
||||||
"js-tokens@4.0.0": {
|
"js-tokens@4.0.0": {
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||||
},
|
},
|
||||||
@ -978,21 +867,6 @@
|
|||||||
"kolorist@1.8.0": {
|
"kolorist@1.8.0": {
|
||||||
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="
|
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="
|
||||||
},
|
},
|
||||||
"less@4.2.0": {
|
|
||||||
"integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
|
|
||||||
"dependencies": [
|
|
||||||
"copy-anything",
|
|
||||||
"errno",
|
|
||||||
"graceful-fs",
|
|
||||||
"image-size",
|
|
||||||
"make-dir",
|
|
||||||
"mime",
|
|
||||||
"needle",
|
|
||||||
"parse-node-version",
|
|
||||||
"source-map@0.6.1",
|
|
||||||
"tslib"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"lilconfig@2.1.0": {
|
"lilconfig@2.1.0": {
|
||||||
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="
|
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="
|
||||||
},
|
},
|
||||||
@ -1023,13 +897,6 @@
|
|||||||
"@jridgewell/sourcemap-codec"
|
"@jridgewell/sourcemap-codec"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"make-dir@2.1.0": {
|
|
||||||
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
|
||||||
"dependencies": [
|
|
||||||
"pify@4.0.1",
|
|
||||||
"semver@5.7.2"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"merge2@1.4.1": {
|
"merge2@1.4.1": {
|
||||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
|
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
|
||||||
},
|
},
|
||||||
@ -1040,9 +907,6 @@
|
|||||||
"picomatch"
|
"picomatch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mime@1.6.0": {
|
|
||||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
|
||||||
},
|
|
||||||
"minimatch@9.0.5": {
|
"minimatch@9.0.5": {
|
||||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@ -1066,13 +930,6 @@
|
|||||||
"nanoid@3.3.7": {
|
"nanoid@3.3.7": {
|
||||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
|
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
|
||||||
},
|
},
|
||||||
"needle@3.3.1": {
|
|
||||||
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
|
|
||||||
"dependencies": [
|
|
||||||
"iconv-lite",
|
|
||||||
"sax"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node-html-parser@6.1.13": {
|
"node-html-parser@6.1.13": {
|
||||||
"integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==",
|
"integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@ -1104,9 +961,6 @@
|
|||||||
"package-json-from-dist@1.0.1": {
|
"package-json-from-dist@1.0.1": {
|
||||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
|
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
|
||||||
},
|
},
|
||||||
"parse-node-version@1.0.1": {
|
|
||||||
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA=="
|
|
||||||
},
|
|
||||||
"path-key@3.1.1": {
|
"path-key@3.1.1": {
|
||||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
|
||||||
},
|
},
|
||||||
@ -1129,9 +983,6 @@
|
|||||||
"pify@2.3.0": {
|
"pify@2.3.0": {
|
||||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="
|
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="
|
||||||
},
|
},
|
||||||
"pify@4.0.1": {
|
|
||||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
|
|
||||||
},
|
|
||||||
"pirates@4.0.6": {
|
"pirates@4.0.6": {
|
||||||
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg=="
|
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg=="
|
||||||
},
|
},
|
||||||
@ -1187,9 +1038,6 @@
|
|||||||
"preact@10.24.3": {
|
"preact@10.24.3": {
|
||||||
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="
|
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="
|
||||||
},
|
},
|
||||||
"prr@1.0.1": {
|
|
||||||
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw=="
|
|
||||||
},
|
|
||||||
"queue-microtask@1.2.3": {
|
"queue-microtask@1.2.3": {
|
||||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
|
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
|
||||||
},
|
},
|
||||||
@ -1226,7 +1074,7 @@
|
|||||||
"read-cache@1.0.0": {
|
"read-cache@1.0.0": {
|
||||||
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"pify@2.3.0"
|
"pify"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"readdirp@3.6.0": {
|
"readdirp@3.6.0": {
|
||||||
@ -1275,21 +1123,12 @@
|
|||||||
"queue-microtask"
|
"queue-microtask"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"safer-buffer@2.1.2": {
|
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
|
||||||
},
|
|
||||||
"sax@1.4.1": {
|
|
||||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
|
|
||||||
},
|
|
||||||
"scheduler@0.23.2": {
|
"scheduler@0.23.2": {
|
||||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"loose-envify"
|
"loose-envify"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"semver@5.7.2": {
|
|
||||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="
|
|
||||||
},
|
|
||||||
"semver@6.3.1": {
|
"semver@6.3.1": {
|
||||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||||
},
|
},
|
||||||
@ -1308,9 +1147,6 @@
|
|||||||
"source-map-js@1.2.1": {
|
"source-map-js@1.2.1": {
|
||||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
|
||||||
},
|
},
|
||||||
"source-map@0.6.1": {
|
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
|
||||||
},
|
|
||||||
"source-map@0.7.4": {
|
"source-map@0.7.4": {
|
||||||
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="
|
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="
|
||||||
},
|
},
|
||||||
@ -1366,14 +1202,6 @@
|
|||||||
"supports-preserve-symlinks-flag@1.0.0": {
|
"supports-preserve-symlinks-flag@1.0.0": {
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
|
||||||
},
|
},
|
||||||
"swr@2.2.5_react@18.3.1": {
|
|
||||||
"integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==",
|
|
||||||
"dependencies": [
|
|
||||||
"client-only",
|
|
||||||
"react",
|
|
||||||
"use-sync-external-store"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"tailwindcss@3.4.13_postcss@8.4.47": {
|
"tailwindcss@3.4.13_postcss@8.4.47": {
|
||||||
"integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==",
|
"integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@ -1425,9 +1253,6 @@
|
|||||||
"ts-interface-checker@0.1.13": {
|
"ts-interface-checker@0.1.13": {
|
||||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
|
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
|
||||||
},
|
},
|
||||||
"tslib@2.8.0": {
|
|
||||||
"integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA=="
|
|
||||||
},
|
|
||||||
"undici-types@6.19.8": {
|
"undici-types@6.19.8": {
|
||||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
|
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
|
||||||
},
|
},
|
||||||
@ -1439,12 +1264,6 @@
|
|||||||
"picocolors"
|
"picocolors"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"use-sync-external-store@1.2.2_react@18.3.1": {
|
|
||||||
"integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
|
|
||||||
"dependencies": [
|
|
||||||
"react"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"util-deprecate@1.0.2": {
|
"util-deprecate@1.0.2": {
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||||
},
|
},
|
||||||
@ -1488,24 +1307,16 @@
|
|||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"jsr:@bearmetal/store@^0.0.5",
|
"jsr:@bearmetal/store@^0.0.4",
|
||||||
"jsr:@gfx/canvas@~0.5.8",
|
|
||||||
"jsr:@std/encoding@^1.0.5",
|
|
||||||
"jsr:@std/fs@^1.0.4",
|
|
||||||
"jsr:@std/http@^1.0.8",
|
"jsr:@std/http@^1.0.8",
|
||||||
"jsr:@std/path@^1.0.6",
|
|
||||||
"npm:@babel/plugin-transform-react-jsx-development@^7.25.7",
|
"npm:@babel/plugin-transform-react-jsx-development@^7.25.7",
|
||||||
"npm:@deno/vite-plugin@1",
|
"npm:@deno/vite-plugin@1",
|
||||||
"npm:@preact/preset-vite@^2.9.1",
|
"npm:@preact/preset-vite@^2.9.1",
|
||||||
"npm:@zip.js/zip.js@^2.7.52",
|
|
||||||
"npm:autoprefixer@^10.4.20",
|
"npm:autoprefixer@^10.4.20",
|
||||||
"npm:babel-plugin-transform-hook-names@^1.0.2",
|
"npm:babel-plugin-transform-hook-names@^1.0.2",
|
||||||
"npm:jotai@^2.10.1",
|
|
||||||
"npm:less@^4.2.0",
|
|
||||||
"npm:postcss@^8.4.47",
|
"npm:postcss@^8.4.47",
|
||||||
"npm:preact@^10.24.3",
|
"npm:preact@^10.24.3",
|
||||||
"npm:react-router-dom@^6.27.0",
|
"npm:react-router-dom@^6.27.0",
|
||||||
"npm:swr@^2.2.5",
|
|
||||||
"npm:tailwindcss@^3.4.13",
|
"npm:tailwindcss@^3.4.13",
|
||||||
"npm:vite@^5.4.8"
|
"npm:vite@^5.4.8"
|
||||||
]
|
]
|
||||||
|
@ -5,9 +5,6 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + Preact + TS</title>
|
<title>Vite + Preact + TS</title>
|
||||||
<style lang="less">
|
|
||||||
@import './src/index.less';
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
66
main.ts
Normal file
66
main.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { SockpuppetPlus } from "@cgg/sockpuppet";
|
||||||
|
import { serveDir } from "@std/http/file-server";
|
||||||
|
import { BearMetalStore } from "@bearmetal/store";
|
||||||
|
|
||||||
|
const installPath = Deno.env.get("BMP_INSTALL_DIR") || "./";
|
||||||
|
|
||||||
|
const sockpuppet = new SockpuppetPlus();
|
||||||
|
sockpuppet.addHandler((req: Request) => {
|
||||||
|
if (new URL(req.url).pathname.startsWith("/api")) return;
|
||||||
|
|
||||||
|
return serveDir(req, {
|
||||||
|
fsRoot: installPath + "dist",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
sockpuppet.addHandler(async (req: Request) => {
|
||||||
|
if (!new URL(req.url).pathname.startsWith("/api")) return;
|
||||||
|
const store = new BearMetalStore();
|
||||||
|
|
||||||
|
console.log("API", req.url);
|
||||||
|
|
||||||
|
const API_DIR_ROUTE = new URLPattern({
|
||||||
|
pathname: "/api/dir",
|
||||||
|
search: "?list",
|
||||||
|
});
|
||||||
|
|
||||||
|
const match = API_DIR_ROUTE.exec(req.url);
|
||||||
|
if (!match) return;
|
||||||
|
|
||||||
|
switch (req.method) {
|
||||||
|
case "GET": {
|
||||||
|
const mcPath = store.get("mcPath") as string;
|
||||||
|
if (mcPath) {
|
||||||
|
for await (const file of Deno.readDir(mcPath)) {
|
||||||
|
if (file.isDirectory && file.name.startsWith("saves")) {
|
||||||
|
const worlds: string[] = [];
|
||||||
|
for await (const world of Deno.readDir(mcPath + "/" + file.name)) {
|
||||||
|
if (world.isDirectory) {
|
||||||
|
worlds.push(world.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Response(JSON.stringify({ worlds, mcPath }), {
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Response(JSON.stringify({ mcPath }), {
|
||||||
|
status: mcPath ? 200 : 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case "POST": {
|
||||||
|
const formData = await req.formData();
|
||||||
|
const dir = formData.get("dir") as string;
|
||||||
|
if (!dir) return new Response(null, { status: 400 });
|
||||||
|
|
||||||
|
Deno.env.set("MC_PATH", dir);
|
||||||
|
const mcPath = Deno.env.get("MC_PATH");
|
||||||
|
if (!mcPath) return new Response(null, { status: 500 });
|
||||||
|
|
||||||
|
return new Response(null, { status: 200 });
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return new Response(null, { status: 405 });
|
||||||
|
}
|
||||||
|
});
|
@ -1,63 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "48",
|
|
||||||
"mcVersion": "^1.21",
|
|
||||||
"schema": {
|
|
||||||
"pack.mcmeta": "json",
|
|
||||||
"pack.png": "image/png",
|
|
||||||
"data": {
|
|
||||||
"<namespace>": {
|
|
||||||
"function": "function",
|
|
||||||
"structure": {
|
|
||||||
"DataVersion": "int",
|
|
||||||
"size": ["int", "int", "int"],
|
|
||||||
"palette": [{
|
|
||||||
"name": "blockId",
|
|
||||||
"properties": ["string"]
|
|
||||||
}],
|
|
||||||
"palettes": [
|
|
||||||
[{
|
|
||||||
"name": "blockId",
|
|
||||||
"properties": ["string"]
|
|
||||||
}]
|
|
||||||
],
|
|
||||||
"blocks": [{
|
|
||||||
"state": "int",
|
|
||||||
"pos": ["int", "int", "int"],
|
|
||||||
"nbt": "nbt"
|
|
||||||
}],
|
|
||||||
"entities": [{
|
|
||||||
"pos": ["double", "double", "double"],
|
|
||||||
"blockPos": ["int", "int", "int"],
|
|
||||||
"nbt": "nbt"
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"tags": "tags",
|
|
||||||
"advancment": {
|
|
||||||
"parent": "advancment",
|
|
||||||
"display": {
|
|
||||||
"icon": {
|
|
||||||
"id": "itemId",
|
|
||||||
"count": "int",
|
|
||||||
"components": ["itemComponent"]
|
|
||||||
},
|
|
||||||
"title": "jsonString",
|
|
||||||
"description": "jsonString",
|
|
||||||
"frame": "frame",
|
|
||||||
"background": "resource",
|
|
||||||
"show_toast": "bool",
|
|
||||||
"announce_to_chat": "bool",
|
|
||||||
"hidden": "bool"
|
|
||||||
},
|
|
||||||
"criteria": "criteria",
|
|
||||||
"requirements": ["criterion_name"],
|
|
||||||
"rewards": {
|
|
||||||
"experience": "int",
|
|
||||||
"function": "function",
|
|
||||||
"loot": ["loot_table"],
|
|
||||||
"recipes": ["recipe"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
251
server/main.ts
251
server/main.ts
@ -1,251 +0,0 @@
|
|||||||
import { SockpuppetPlus } from "@cgg/sockpuppet";
|
|
||||||
import { serveDir, serveFile } from "@std/http/file-server";
|
|
||||||
import { BearMetalStore } from "@bearmetal/store";
|
|
||||||
import { ensureDir, ensureFile, exists } from "@std/fs";
|
|
||||||
import { Router } from "./router.ts";
|
|
||||||
import { getPackVersion } from "./util/packVersion.ts";
|
|
||||||
import { createTagRoutes } from "./tags/routes.ts";
|
|
||||||
import { createResourcesRoutes } from "./resources/routes.ts";
|
|
||||||
|
|
||||||
const installPath = Deno.env.get("BMP_INSTALL_DIR") || "./";
|
|
||||||
|
|
||||||
const sockpuppet = new SockpuppetPlus();
|
|
||||||
sockpuppet.addHandler((req: Request) => {
|
|
||||||
const url = new URL(req.url);
|
|
||||||
if (!url.pathname.startsWith("/images")) return;
|
|
||||||
|
|
||||||
return serveFile(req, url.searchParams.get("location") as string);
|
|
||||||
});
|
|
||||||
|
|
||||||
// sockpuppet.addHandler((req: Request) => {
|
|
||||||
// const method = req.method;
|
|
||||||
// if (method === "OPTIONS") {
|
|
||||||
// return new Response(null, {
|
|
||||||
// status: 200,
|
|
||||||
// headers: {
|
|
||||||
// "Access-Control-Allow-Origin": "*",
|
|
||||||
// "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
const router = new Router();
|
|
||||||
router.route("/api/dir")
|
|
||||||
.get(async () => {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
const mcPath = store.get("mcPath") as string;
|
|
||||||
if (mcPath) {
|
|
||||||
const worlds: {
|
|
||||||
world: string;
|
|
||||||
icon: string;
|
|
||||||
path: string;
|
|
||||||
}[] = [];
|
|
||||||
try {
|
|
||||||
for await (const file of Deno.readDir(mcPath)) {
|
|
||||||
if (file.isDirectory && file.name.startsWith("saves")) {
|
|
||||||
for await (
|
|
||||||
const world of Deno.readDir(mcPath + "/" + file.name)
|
|
||||||
) {
|
|
||||||
if (world.isDirectory) {
|
|
||||||
for await (
|
|
||||||
const f of Deno.readDir(
|
|
||||||
mcPath + "/" + file.name + "/" + world.name,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (f.name.endsWith(".dat")) {
|
|
||||||
worlds.push({
|
|
||||||
world: world.name,
|
|
||||||
icon: Deno.realPathSync(
|
|
||||||
mcPath + "/" + file.name + "/" + world.name +
|
|
||||||
"/icon.png",
|
|
||||||
),
|
|
||||||
path: Deno.realPathSync(
|
|
||||||
mcPath + "/" + file.name + "/" + world.name,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
store.set("mcPath", "");
|
|
||||||
return new Response(e, { status: 500 });
|
|
||||||
}
|
|
||||||
return new Response(JSON.stringify({ worlds, mcPath }), {
|
|
||||||
status: 200,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return new Response(JSON.stringify({ mcPath }), {
|
|
||||||
status: mcPath ? 200 : 500,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.post(async (req) => {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
const formData = await req.formData();
|
|
||||||
const dir = formData.get("mcPath") as string;
|
|
||||||
if (!dir) return new Response(null, { status: 400 });
|
|
||||||
|
|
||||||
store.set("mcPath", dir);
|
|
||||||
if (!store.get("mcPath")) return new Response(null, { status: 500 });
|
|
||||||
|
|
||||||
return new Response(null, { status: 200 });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.route("/api/world")
|
|
||||||
.post(async (req) => {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
const worldPath = await req.text();
|
|
||||||
if (!worldPath) return new Response(null, { status: 400 });
|
|
||||||
|
|
||||||
const mcPath = store.get("mcPath") as string;
|
|
||||||
if (!mcPath) {
|
|
||||||
return new Response("Tried to set world, but MC path is not set.", {
|
|
||||||
status: 500,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const realWorldPath = Deno.realPathSync(worldPath);
|
|
||||||
store.set("world", realWorldPath);
|
|
||||||
return new Response(null, { status: 200 });
|
|
||||||
})
|
|
||||||
.get((req) => {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
const worldPath = store.get("world") as string;
|
|
||||||
if (!worldPath) return new Response(null, { status: 400 });
|
|
||||||
return new Response(worldPath.split("/").pop() as string, { status: 200 });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.route("/api/pack")
|
|
||||||
.get(() => {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ packName: store.get("packname") as string }),
|
|
||||||
{ status: 200 },
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.post(async (req) => {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
const formData = await req.formData();
|
|
||||||
const packName = formData.get("packName") as string;
|
|
||||||
if (!packName) return new Response(null, { status: 400 });
|
|
||||||
createPack(store, packName);
|
|
||||||
|
|
||||||
store.set("packname", packName);
|
|
||||||
return new Response(JSON.stringify({ packName }), { status: 200 });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.route("/api/pack/namespaces")
|
|
||||||
.get(async () => {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
|
|
||||||
const namespaces = Array.from(Deno.readDirSync(store.get("packlocation")))
|
|
||||||
.filter((dir) => dir.isDirectory).map((dir) => dir.name);
|
|
||||||
return new Response(JSON.stringify(namespaces), { status: 200 });
|
|
||||||
})
|
|
||||||
.post(async (req) => {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
|
|
||||||
const namespace = await req.text();
|
|
||||||
if (!namespace) {
|
|
||||||
return new Response("Namespace is required", { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const namespaceRx = /^[a-zA-Z0-9_\-]+$/;
|
|
||||||
if (!namespaceRx.test(namespace)) {
|
|
||||||
return new Response(
|
|
||||||
"Namespace must only contain letters, numbers, underscores, and dashes",
|
|
||||||
{ status: 400 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await ensureDir(store.get("packlocation") + "/" + namespace);
|
|
||||||
return new Response(null, { status: 200 });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.route("/api/packs")
|
|
||||||
.get(async () => {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
const packs: { name: string; path: string }[] = [];
|
|
||||||
|
|
||||||
const world = store.get("world") as string;
|
|
||||||
for await (const pack of Deno.readDir(world + "/datapacks")) {
|
|
||||||
if (
|
|
||||||
pack.isDirectory &&
|
|
||||||
await exists(world + "/datapacks/" + pack.name + "/bmp_dev")
|
|
||||||
) {
|
|
||||||
packs.push({
|
|
||||||
name: pack.name,
|
|
||||||
path: Deno.realPathSync(world + "/datapacks/" + pack.name),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response("No BMP packs found", { status: 400 });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.route("/api/pack/version")
|
|
||||||
.get(() => {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
const version = getPackVersion(store);
|
|
||||||
return new Response(version.toString(), {
|
|
||||||
status: 200,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.post(async (req) => {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
const version = await req.text();
|
|
||||||
if (!version) return new Response(null, { status: 400 });
|
|
||||||
|
|
||||||
try {
|
|
||||||
store.set("version", version);
|
|
||||||
const packMeta = Deno.readTextFileSync(
|
|
||||||
store.get("packlocation") + "/pack.mcmeta",
|
|
||||||
);
|
|
||||||
const packMetaJson = JSON.parse(packMeta);
|
|
||||||
packMetaJson.pack.pack_format = parseInt(version);
|
|
||||||
await Deno.writeTextFile(
|
|
||||||
store.get("packlocation") + "/pack.mcmeta",
|
|
||||||
JSON.stringify(packMetaJson),
|
|
||||||
);
|
|
||||||
} catch (e: any) {
|
|
||||||
return new Response(e, { status: 500 });
|
|
||||||
}
|
|
||||||
return new Response(null, { status: 200 });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.route("/api/versions")
|
|
||||||
.get(() => {
|
|
||||||
const versions = Array.from(Deno.readDirSync(installPath + "pack_versions"))
|
|
||||||
.filter((v) => v.isFile).map((version) =>
|
|
||||||
version.name.replace(".json", "")
|
|
||||||
);
|
|
||||||
return new Response(JSON.stringify(versions), { status: 200 });
|
|
||||||
});
|
|
||||||
|
|
||||||
createTagRoutes(router);
|
|
||||||
createResourcesRoutes(router);
|
|
||||||
|
|
||||||
sockpuppet.addHandler((req: Request) => {
|
|
||||||
if (new URL(req.url).pathname.startsWith("/api")) return;
|
|
||||||
|
|
||||||
return serveDir(req, {
|
|
||||||
fsRoot: installPath + "dist",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
sockpuppet.addHandler(router.handle);
|
|
||||||
|
|
||||||
async function createPack(store: BearMetalStore, packName: string) {
|
|
||||||
const realWorldPath = store.get("world");
|
|
||||||
store.set("packlocation", realWorldPath + "/datapacks/" + packName);
|
|
||||||
await ensureDir(store.get("packlocation"));
|
|
||||||
await ensureFile(store.get("packlocation") + "/bmp_dev");
|
|
||||||
await ensureFile(store.get("packlocation") + "/pack.mcmeta");
|
|
||||||
if (!Deno.readTextFileSync(store.get("packlocation") + "/pack.mcmeta")) {
|
|
||||||
await Deno.writeTextFile(
|
|
||||||
store.get("packlocation") + "/pack.mcmeta",
|
|
||||||
`{"pack":{"pack_format":48,"description":"${packName}"}}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,359 +0,0 @@
|
|||||||
import { encodeBase64 } from "@std/encoding/base64";
|
|
||||||
import { readDirFiles } from "../util/readDir.ts";
|
|
||||||
import { createIsometricCube } from "./renderer.ts";
|
|
||||||
|
|
||||||
interface BlockItem {
|
|
||||||
name: string;
|
|
||||||
resourceLocation: string;
|
|
||||||
images: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const readBlocks = async (path: string) => {
|
|
||||||
const blocks: BlockItem[] =
|
|
||||||
(await readDirFiles(path + "/assets/minecraft/blockstates"))
|
|
||||||
.map((b) => ({
|
|
||||||
name: b.replace(".json", ""),
|
|
||||||
resourceLocation: "minecraft:" + b.replace(".json", ""),
|
|
||||||
images: [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
const blockTextures = await readDirFiles(
|
|
||||||
path + "/assets/minecraft/textures/block",
|
|
||||||
);
|
|
||||||
|
|
||||||
const modelPath = path + "/assets/minecraft/models/block";
|
|
||||||
for (const block of blocks) {
|
|
||||||
let name = block.name;
|
|
||||||
if (
|
|
||||||
block.name.startsWith("air") ||
|
|
||||||
block.name.startsWith("void_air") ||
|
|
||||||
block.name.startsWith("cave_air") ||
|
|
||||||
block.name.startsWith("end_gateway") ||
|
|
||||||
block.name.endsWith("_door") ||
|
|
||||||
block.name.endsWith("_trapdoor") ||
|
|
||||||
block.name.endsWith("_banner") ||
|
|
||||||
block.name.endsWith("_fence_gate") ||
|
|
||||||
block.name.endsWith("_pressure_plate") ||
|
|
||||||
block.name.endsWith("_button") ||
|
|
||||||
block.name.endsWith("lever") ||
|
|
||||||
block.name.endsWith("_sign") ||
|
|
||||||
block.name.endsWith("torch") ||
|
|
||||||
block.name.endsWith("_fence") ||
|
|
||||||
block.name.endsWith("_wall") ||
|
|
||||||
block.name.endsWith("_skull") ||
|
|
||||||
block.name.endsWith("_head") ||
|
|
||||||
block.name.includes("bubble_column") ||
|
|
||||||
block.name.includes("tripwire_hook") ||
|
|
||||||
block.name.includes("tripwire") ||
|
|
||||||
block.name == undefined ||
|
|
||||||
block.name.includes("chest") ||
|
|
||||||
block.name.includes("command")
|
|
||||||
) continue;
|
|
||||||
if (block.name.includes("water") || block.name.includes("lava")) {
|
|
||||||
const what = block.name.includes("water") ? "water" : "lava";
|
|
||||||
const itemTexLoc = path +
|
|
||||||
`/assets/minecraft/textures/block/${what}_still.png`;
|
|
||||||
const itemImage = await Deno.readFile(itemTexLoc);
|
|
||||||
block.images.push("data:image/png;base64," + encodeBase64(itemImage));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (block.name === "nether_portal") {
|
|
||||||
const itemTexLoc = path +
|
|
||||||
`/assets/minecraft/textures/block/nether_portal.png`;
|
|
||||||
const itemImage = await Deno.readFile(itemTexLoc);
|
|
||||||
block.images.push("data:image/png;base64," + encodeBase64(itemImage));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (["fire", "soul_fire"].includes(block.name)) {
|
|
||||||
const itemTexLoc = path +
|
|
||||||
`/assets/minecraft/textures/block/${block.name}_0.png`;
|
|
||||||
const itemImage = await Deno.readFile(itemTexLoc);
|
|
||||||
block.images.push("data:image/png;base64," + encodeBase64(itemImage));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (block.name.includes("cake")) {
|
|
||||||
const itemTexLoc = path +
|
|
||||||
`/assets/minecraft/textures/item/cake.png`;
|
|
||||||
const itemImage = await Deno.readFile(itemTexLoc);
|
|
||||||
block.images.push("data:image/png;base64," + encodeBase64(itemImage));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (block.name.endsWith("_bed")) {
|
|
||||||
const itemTexLoc = path + "/assets/minecraft/textures/entity/bed/" +
|
|
||||||
block.name.replace(/_bed$/, "") + ".png";
|
|
||||||
const itemImage = await Deno.readFile(itemTexLoc);
|
|
||||||
block.images.push("data:image/png;base64," + encodeBase64(itemImage));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (block.name === "nether_portal") {
|
|
||||||
const itemTexLoc = path +
|
|
||||||
`/assets/minecraft/textures/block/nether_portal.png`;
|
|
||||||
const itemImage = await Deno.readFile(itemTexLoc);
|
|
||||||
block.images.push("data:image/png;base64," + encodeBase64(itemImage));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (block.name.includes("dripleaf") && !block.name.includes("stem")) {
|
|
||||||
const itemTexLoc = path +
|
|
||||||
`/assets/minecraft/textures/block/${block.name}_top.png`;
|
|
||||||
const itemImage = await Deno.readFile(itemTexLoc);
|
|
||||||
block.images.push("data:image/png;base64," + encodeBase64(itemImage));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
"carrots",
|
|
||||||
"potatoes",
|
|
||||||
"wheat",
|
|
||||||
"beetroots",
|
|
||||||
"grass",
|
|
||||||
"kelp",
|
|
||||||
"light",
|
|
||||||
"bell",
|
|
||||||
"redstone_wire",
|
|
||||||
"sweet_berry_bush",
|
|
||||||
"cocoa",
|
|
||||||
"nether_wart",
|
|
||||||
"repeater",
|
|
||||||
"bamboo",
|
|
||||||
"pitcher_plant",
|
|
||||||
"campfire",
|
|
||||||
"soul_campfire",
|
|
||||||
].includes(block.name)
|
|
||||||
) {
|
|
||||||
const itemTexLoc = path + "/assets/minecraft/textures/item/" +
|
|
||||||
block.name
|
|
||||||
.replace("large_fern", "fern")
|
|
||||||
.replace("tall_seagrass", "seagrass")
|
|
||||||
.replace(/ss$/, "?")
|
|
||||||
.replace(/e?s$/, "")
|
|
||||||
.replace("?", "ss")
|
|
||||||
.replace("_bush", "")
|
|
||||||
.replace("berry", "berries")
|
|
||||||
.replace("cocoa", "cocoa_beans")
|
|
||||||
.replace("_wire", "") +
|
|
||||||
".png";
|
|
||||||
const itemImage = await Deno.readFile(itemTexLoc);
|
|
||||||
block.images.push("data:image/png;base64," + encodeBase64(itemImage));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
"melon_stem",
|
|
||||||
"pumpkin_stem",
|
|
||||||
"fern",
|
|
||||||
"large_fern",
|
|
||||||
"lilac",
|
|
||||||
"azure_bluet",
|
|
||||||
"red_tulip",
|
|
||||||
"orange_tulip",
|
|
||||||
"pink_tulip",
|
|
||||||
"white_tulip",
|
|
||||||
"oxeye_daisy",
|
|
||||||
"cornflower",
|
|
||||||
"lily_of_the_valley",
|
|
||||||
"wither_rose",
|
|
||||||
"rose_bush",
|
|
||||||
"peony",
|
|
||||||
"sunflower",
|
|
||||||
"torchflower_crop",
|
|
||||||
"suspicious_sand",
|
|
||||||
"suspicious_gravel",
|
|
||||||
"scaffolding",
|
|
||||||
"respawn_anchor",
|
|
||||||
"short_grass",
|
|
||||||
"tall_grass",
|
|
||||||
"seagrass",
|
|
||||||
"tall_seagrass",
|
|
||||||
"frosted_ice",
|
|
||||||
"pitcher_crop",
|
|
||||||
"iron_bars",
|
|
||||||
].includes(block.name)
|
|
||||||
) {
|
|
||||||
const textures = blockTextures.filter((t) => t.includes(block.name))
|
|
||||||
.reverse().filter((t) => !t.endsWith("mcmeta"));
|
|
||||||
const itemTexLoc = path + "/assets/minecraft/textures/block/" +
|
|
||||||
textures[0];
|
|
||||||
const itemImage = await Deno.readFile(itemTexLoc);
|
|
||||||
block.images.push("data:image/png;base64," + encodeBase64(itemImage));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (block.name.startsWith("waxed")) {
|
|
||||||
name = block.name.replace(/^waxed_?/, "");
|
|
||||||
}
|
|
||||||
if (block.name.startsWith("infested")) {
|
|
||||||
name = block.name.replace(/^infested_?/, "");
|
|
||||||
}
|
|
||||||
if (block.name.endsWith("_pane")) {
|
|
||||||
name = block.name.replace(/_pane$/, "");
|
|
||||||
}
|
|
||||||
if (block.name === "pink_petals") {
|
|
||||||
name = "pink_petals_4";
|
|
||||||
}
|
|
||||||
if (block.name.includes("candle")) {
|
|
||||||
const itemTexLoc = path +
|
|
||||||
`/assets/minecraft/textures/item/${block.name}.png`;
|
|
||||||
const itemImage = await Deno.readFile(itemTexLoc);
|
|
||||||
block.images.push("data:image/png;base64," + encodeBase64(itemImage));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (block.name.includes("cauldron")) {
|
|
||||||
const textures = blockTextures.filter((t) => t.includes("cauldron"));
|
|
||||||
for (const texture of textures) {
|
|
||||||
const texLoc = texture.replace("minecraft:", "");
|
|
||||||
const image = await Deno.readFile(
|
|
||||||
path + "/assets/minecraft/textures/block/" + texLoc,
|
|
||||||
);
|
|
||||||
const b64 = "data:image/png;base64," + encodeBase64(image);
|
|
||||||
block.images.push(await createIsometricCube(b64, b64, b64));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (block.name === "snow") {
|
|
||||||
name = "snow_height2";
|
|
||||||
}
|
|
||||||
const data = await Deno.readTextFile(
|
|
||||||
modelPath + "/" + name + ".json",
|
|
||||||
);
|
|
||||||
const modelJson = JSON.parse(data);
|
|
||||||
if (modelJson.textures && !modelJson.elements) {
|
|
||||||
if (modelJson.textures.all) {
|
|
||||||
const texLoc = modelJson.textures.all;
|
|
||||||
const image = await Deno.readFile(
|
|
||||||
path + "/assets/minecraft/textures/" + texLoc.replace(
|
|
||||||
"minecraft:",
|
|
||||||
"",
|
|
||||||
) + ".png",
|
|
||||||
);
|
|
||||||
const b64 = "data:image/png;base64," + encodeBase64(image);
|
|
||||||
block.images.push(await createIsometricCube(b64, b64, b64));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (modelJson.textures.front) {
|
|
||||||
const frontImage = await Deno.readFile(
|
|
||||||
path + "/assets/minecraft/textures/" +
|
|
||||||
modelJson.textures.front.replace(
|
|
||||||
"minecraft:",
|
|
||||||
"",
|
|
||||||
) + ".png",
|
|
||||||
);
|
|
||||||
|
|
||||||
const frontB64 = "data:image/png;base64," + encodeBase64(frontImage);
|
|
||||||
|
|
||||||
const topImage = await Deno.readFile(
|
|
||||||
path + "/assets/minecraft/textures/" +
|
|
||||||
(modelJson.textures.top ?? modelJson.textures.side).replace(
|
|
||||||
"minecraft:",
|
|
||||||
"",
|
|
||||||
) + ".png",
|
|
||||||
);
|
|
||||||
|
|
||||||
const topB64 = "data:image/png;base64," + encodeBase64(topImage);
|
|
||||||
|
|
||||||
const sideImage = await Deno.readFile(
|
|
||||||
path + "/assets/minecraft/textures/" +
|
|
||||||
modelJson.textures.side.replace(
|
|
||||||
"minecraft:",
|
|
||||||
"",
|
|
||||||
) + ".png",
|
|
||||||
);
|
|
||||||
|
|
||||||
const sideB64 = "data:image/png;base64," + encodeBase64(sideImage);
|
|
||||||
block.images.push(await createIsometricCube(frontB64, sideB64, topB64));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (modelJson.textures.side) {
|
|
||||||
const sideImage = await Deno.readFile(
|
|
||||||
path + "/assets/minecraft/textures/" +
|
|
||||||
modelJson.textures.side.replace(
|
|
||||||
"minecraft:",
|
|
||||||
"",
|
|
||||||
) + ".png",
|
|
||||||
);
|
|
||||||
|
|
||||||
const sideB64 = "data:image/png;base64," + encodeBase64(sideImage);
|
|
||||||
|
|
||||||
const topImage = await Deno.readFile(
|
|
||||||
path + "/assets/minecraft/textures/" +
|
|
||||||
(modelJson.textures.top ?? modelJson.textures.bottom ??
|
|
||||||
modelJson.textures.side).replace(
|
|
||||||
"minecraft:",
|
|
||||||
"",
|
|
||||||
) +
|
|
||||||
".png",
|
|
||||||
);
|
|
||||||
|
|
||||||
const topB64 = "data:image/png;base64," + encodeBase64(topImage);
|
|
||||||
block.images.push(await createIsometricCube(sideB64, sideB64, topB64));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const textures = blockTextures.filter((t) =>
|
|
||||||
t.includes(block.name) && !t.includes("mcmeta")
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const texture of textures) {
|
|
||||||
const texLoc = texture.replace("minecraft:", "");
|
|
||||||
const image = await Deno.readFile(
|
|
||||||
path + "/assets/minecraft/textures/block/" + texLoc,
|
|
||||||
);
|
|
||||||
const b64 = "data:image/png;base64," + encodeBase64(image);
|
|
||||||
block.images.push(await createIsometricCube(b64, b64, b64));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return blocks;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const readItems = async (path: string) => {
|
|
||||||
const items: BlockItem[] =
|
|
||||||
(await readDirFiles(path + "/assets/minecraft/models/item")).map((i) => ({
|
|
||||||
name: i.replace(".json", ""),
|
|
||||||
resourceLocation: "minecraft:" + i.replace(".json", ""),
|
|
||||||
images: [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
for (const item of items) {
|
|
||||||
let name = item.name;
|
|
||||||
if (item.name.startsWith("air")) continue;
|
|
||||||
if (item.name.startsWith("waxed")) name = item.name.replace(/^waxed_?/, "");
|
|
||||||
|
|
||||||
const data = await Deno.readFile(
|
|
||||||
path + "/assets/minecraft/models/item/" + name + ".json",
|
|
||||||
);
|
|
||||||
const json = JSON.parse(new TextDecoder().decode(data));
|
|
||||||
const texDir = path + "/assets/minecraft/textures/";
|
|
||||||
if (json.textures) {
|
|
||||||
const texLoc = json.textures.layer0;
|
|
||||||
if (texLoc) {
|
|
||||||
const data = await Deno.readFile(
|
|
||||||
texDir + texLoc.replace("minecraft:", "") + ".png",
|
|
||||||
);
|
|
||||||
item.images.push("data:image/png;base64," + encodeBase64(data));
|
|
||||||
}
|
|
||||||
} else if (json.parent) {
|
|
||||||
const parent = await Deno.readFile(
|
|
||||||
path + "/assets/minecraft/models/" +
|
|
||||||
json.parent.replace("minecraft:", "") + ".json",
|
|
||||||
);
|
|
||||||
const parentJson = JSON.parse(new TextDecoder().decode(parent));
|
|
||||||
if (parentJson.textures) {
|
|
||||||
let texLoc = parentJson.textures.all;
|
|
||||||
if (!texLoc) texLoc = parentJson.textures.side;
|
|
||||||
if (!texLoc) texLoc = parentJson.textures.top;
|
|
||||||
if (!texLoc) texLoc = parentJson.textures.bottom;
|
|
||||||
if (texLoc) {
|
|
||||||
const data = await Deno.readFile(
|
|
||||||
texDir + texLoc.replace("minecraft:", "") + ".png",
|
|
||||||
);
|
|
||||||
item.images.push("data:image/png;base64," + encodeBase64(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (import.meta.main) {
|
|
||||||
const path = "./resources/1.21.1";
|
|
||||||
console.log(await readItems(path));
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
import { createCanvas, Image } from "@gfx/canvas";
|
|
||||||
|
|
||||||
function loadImage(src: string): Promise<Image> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!src.startsWith("data:image/png;base64,")) {
|
|
||||||
console.log("src", src);
|
|
||||||
}
|
|
||||||
const img = new Image();
|
|
||||||
img.onload = () => resolve(img);
|
|
||||||
img.onerror = () => {
|
|
||||||
console.log(src);
|
|
||||||
reject();
|
|
||||||
};
|
|
||||||
img.src = src;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createIsometricCube(
|
|
||||||
face1Src: string,
|
|
||||||
face2Src: string,
|
|
||||||
face3Src: string,
|
|
||||||
width: number = 96,
|
|
||||||
) {
|
|
||||||
const canvasWidth = width; // Set a size for the canvas
|
|
||||||
const canvasHeight = Math.ceil(width / .866);
|
|
||||||
const canvas = createCanvas(canvasWidth, canvasHeight);
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [face1, face2, face3] = await Promise.all([
|
|
||||||
loadImage(face1Src),
|
|
||||||
loadImage(face2Src),
|
|
||||||
loadImage(face3Src),
|
|
||||||
]).catch();
|
|
||||||
|
|
||||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
||||||
|
|
||||||
ctx.setTransform(
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
Math.tan(degToRad(30)),
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
canvasWidth / 3.5,
|
|
||||||
);
|
|
||||||
ctx.drawImage(face1, 0, 0, canvasWidth / 2, canvasHeight / 2);
|
|
||||||
|
|
||||||
// Face 2
|
|
||||||
ctx.setTransform(
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
-Math.tan(degToRad(30)),
|
|
||||||
1,
|
|
||||||
canvasWidth / 2,
|
|
||||||
canvasHeight / 2,
|
|
||||||
);
|
|
||||||
ctx.drawImage(face2, 0, 0, canvasWidth / 2, canvasHeight / 2);
|
|
||||||
|
|
||||||
// Face 3
|
|
||||||
ctx.setTransform(
|
|
||||||
1,
|
|
||||||
-Math.tan(degToRad(60)),
|
|
||||||
Math.tan(degToRad(30)),
|
|
||||||
1,
|
|
||||||
canvasWidth / 2,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
ctx.drawImage(face3, 0, 0, canvasWidth / 2, canvasWidth / 3.5);
|
|
||||||
|
|
||||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
||||||
} catch (e) {
|
|
||||||
// console.log("error", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
const b64 = canvas.toDataURL("png");
|
|
||||||
return b64;
|
|
||||||
}
|
|
||||||
|
|
||||||
function degToRad(deg: number) {
|
|
||||||
return deg * (Math.PI / 180);
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
import type { Router } from "../router.ts";
|
|
||||||
import { readDirDirs } from "../util/readDir.ts";
|
|
||||||
import { versionCompat } from "../util/versionCompat.ts";
|
|
||||||
import { readBlocks } from "./readers.ts";
|
|
||||||
|
|
||||||
export const createResourcesRoutes = (router: Router) => {
|
|
||||||
router.route("/api/resources/:path*")
|
|
||||||
.get(async (req, ctx) => {
|
|
||||||
const path = ctx.params.path;
|
|
||||||
if (!path) {
|
|
||||||
return new Response("no path provided", { status: 400 });
|
|
||||||
}
|
|
||||||
const format = ctx.url.searchParams.get("format");
|
|
||||||
if (!format) {
|
|
||||||
return new Response("no format provided", { status: 400 });
|
|
||||||
}
|
|
||||||
const packVersion = await Deno.readTextFile(
|
|
||||||
"./pack_versions/" + format + ".json",
|
|
||||||
);
|
|
||||||
const packVersionJson = JSON.parse(packVersion);
|
|
||||||
const mcVersion = packVersionJson.mcVersion;
|
|
||||||
const resourceVersions = await readDirDirs("./resources");
|
|
||||||
console.log("resourceVersions", resourceVersions);
|
|
||||||
for (const resourceVersion of resourceVersions) {
|
|
||||||
if (versionCompat(resourceVersion, mcVersion)) {
|
|
||||||
const resourcePath = "./resources/" + resourceVersion;
|
|
||||||
const splitPath = path.split("/");
|
|
||||||
switch (splitPath[0]) {
|
|
||||||
case "block":
|
|
||||||
case "blocks": {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify(await readBlocks(resourcePath)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "item":
|
|
||||||
case "items": {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify(await readBlocks(resourcePath)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return new Response("invalid path", { status: 400 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,58 +0,0 @@
|
|||||||
import { BearMetalStore } from "@bearmetal/store";
|
|
||||||
import { ZipReader } from "@zip.js/zip.js";
|
|
||||||
import { ensureFile } from "@std/fs";
|
|
||||||
|
|
||||||
export async function unzipResources(mcVersion?: string) {
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
mcVersion = mcVersion || await currentVersion(store);
|
|
||||||
|
|
||||||
console.log("mcVersion", mcVersion);
|
|
||||||
|
|
||||||
if (!mcVersion) return;
|
|
||||||
|
|
||||||
const blob = await Deno.open(
|
|
||||||
store.get("mcPath") + "/versions/" + mcVersion + "/" + mcVersion + ".jar",
|
|
||||||
);
|
|
||||||
|
|
||||||
const zip = new ZipReader(blob);
|
|
||||||
|
|
||||||
for (const entry of await zip.getEntries()) {
|
|
||||||
if (
|
|
||||||
entry.filename.startsWith("assets/") || entry.filename.startsWith("data/")
|
|
||||||
) {
|
|
||||||
// console.log("entry", entry);
|
|
||||||
await ensureFile(`./resources/${mcVersion}/${entry.filename}`);
|
|
||||||
const writer = await Deno.open(
|
|
||||||
`./resources/${mcVersion}/${entry.filename}`,
|
|
||||||
{ write: true },
|
|
||||||
);
|
|
||||||
await entry.getData?.(writer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function currentVersion(store: BearMetalStore) {
|
|
||||||
const mcPath = store.get("mcPath");
|
|
||||||
if (!mcPath) return;
|
|
||||||
|
|
||||||
const versions = Array.from(Deno.readDirSync(mcPath + "/versions")).filter(
|
|
||||||
(d) => d.isDirectory,
|
|
||||||
).map((d) => d.name).sort();
|
|
||||||
let version = versions.pop();
|
|
||||||
let found = false;
|
|
||||||
versionC:
|
|
||||||
while (!found) {
|
|
||||||
for await (const file of Deno.readDir(mcPath + "/versions/" + version)) {
|
|
||||||
if (file.name.endsWith(".jar")) {
|
|
||||||
found = true;
|
|
||||||
break versionC;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
version = versions.pop();
|
|
||||||
}
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.main) {
|
|
||||||
unzipResources();
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
export class Router {
|
|
||||||
private routes: Map<string, Handler[]> = new Map();
|
|
||||||
|
|
||||||
public route(route: string) {
|
|
||||||
const methods: Record<string, Handler> = {
|
|
||||||
get: () => undefined,
|
|
||||||
post: () => undefined,
|
|
||||||
put: () => undefined,
|
|
||||||
delete: () => undefined,
|
|
||||||
};
|
|
||||||
this.routes.set(route, this.routes.get(route) || []);
|
|
||||||
this.routes.get(route)?.push((r, c) => {
|
|
||||||
switch (r.method) {
|
|
||||||
case "GET":
|
|
||||||
return methods.get?.(r, c);
|
|
||||||
case "POST":
|
|
||||||
return methods.post?.(r, c);
|
|
||||||
case "PUT":
|
|
||||||
return methods.put?.(r, c);
|
|
||||||
case "DELETE":
|
|
||||||
return methods.delete?.(r, c);
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
get(handler: Handler) {
|
|
||||||
methods.get = handler;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
post(handler: Handler) {
|
|
||||||
methods.post = handler;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
put(handler: Handler) {
|
|
||||||
methods.put = handler;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
delete(handler: Handler) {
|
|
||||||
methods.delete = handler;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public handle = async (req: Request): Promise<Response> => {
|
|
||||||
const url = new URL(req.url);
|
|
||||||
for (const [route, handlers] of this.routes.entries()) {
|
|
||||||
const pattern = new URLPattern({ pathname: route });
|
|
||||||
const match = pattern.exec(req.url);
|
|
||||||
if (match) {
|
|
||||||
let res;
|
|
||||||
for (const handler of handlers) {
|
|
||||||
res = await handler(req, {
|
|
||||||
url,
|
|
||||||
state: {},
|
|
||||||
params: match.pathname.groups,
|
|
||||||
});
|
|
||||||
if (res) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Response("Not found", { status: 404 });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Handler = (
|
|
||||||
req: Request,
|
|
||||||
ctx: Context,
|
|
||||||
) => Promise<Response | undefined> | Response | undefined;
|
|
@ -1,14 +0,0 @@
|
|||||||
import type { BearMetalStore } from "@bearmetal/store";
|
|
||||||
import { getDirName } from "../util/packVersion.ts";
|
|
||||||
|
|
||||||
export async function getTagDir(
|
|
||||||
store: BearMetalStore,
|
|
||||||
namespace: string,
|
|
||||||
version: number,
|
|
||||||
) {
|
|
||||||
// const versionData = JSON.parse(Deno.readTextFileSync(installPath + "pack_versions/" + version + ".json"));
|
|
||||||
|
|
||||||
const tagDir = await getDirName(version, "tags");
|
|
||||||
|
|
||||||
return `${store.get("packlocation")}/${namespace}/${tagDir}`;
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
import { BearMetalStore } from "@bearmetal/store";
|
|
||||||
import { getPackVersion } from "../util/packVersion.ts";
|
|
||||||
import type { Router } from "../router.ts";
|
|
||||||
import { getTagDir } from "./getTagDir.ts";
|
|
||||||
import { ensureDir } from "@std/fs/ensure-dir";
|
|
||||||
import { ensureFile } from "@std/fs/ensure-file";
|
|
||||||
|
|
||||||
export const createTagRoutes = (router: Router) => {
|
|
||||||
router.route("/api/pack/:namespace/tags")
|
|
||||||
.get(async (_, ctx) => {
|
|
||||||
if (!ctx.params.namespace) {
|
|
||||||
return new Response("somehow hit the tags endpoint without namespace", {
|
|
||||||
status: 500,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
|
|
||||||
const version = getPackVersion(store);
|
|
||||||
|
|
||||||
const tagDir = await getTagDir(store, ctx.params.namespace, version);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tags = Array.from(
|
|
||||||
Deno.readDirSync(
|
|
||||||
tagDir,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return new Response(JSON.stringify(tags), { status: 200 });
|
|
||||||
} catch {
|
|
||||||
return new Response("[]", { status: 200 });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.post(async (req, ctx) => {
|
|
||||||
if (!ctx.params.namespace) {
|
|
||||||
return new Response("somehow hit the tags endpoint without namespace", {
|
|
||||||
status: 500,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
|
|
||||||
const version = getPackVersion(store);
|
|
||||||
|
|
||||||
const tagDir = await getTagDir(store, ctx.params.namespace, version);
|
|
||||||
|
|
||||||
await ensureDir(tagDir);
|
|
||||||
|
|
||||||
const { tag, type } = await req.json();
|
|
||||||
if (!tag || !type) {
|
|
||||||
return new Response("no tag name provided", { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagPath = `${tagDir}/${type}/${tag}.json`;
|
|
||||||
|
|
||||||
await ensureFile(tagPath);
|
|
||||||
|
|
||||||
return new Response(tag, { status: 200 });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.route("/api/pack/:namespace/tags/:type-:tag")
|
|
||||||
.get(async (_, ctx) => {
|
|
||||||
if (!ctx.params.namespace) {
|
|
||||||
return new Response("somehow hit the tags endpoint without namespace", {
|
|
||||||
status: 500,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
|
|
||||||
const version = getPackVersion(store);
|
|
||||||
|
|
||||||
const tagDir = await getTagDir(store, ctx.params.namespace, version);
|
|
||||||
|
|
||||||
const tag = ctx.params.tag;
|
|
||||||
const type = ctx.params.type;
|
|
||||||
if (!tag || !type) {
|
|
||||||
return new Response("no tag name provided", { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tagFile = Deno.readTextFileSync(`${tagDir}/${type}/${tag}.json`);
|
|
||||||
return new Response(tagFile, { status: 200 });
|
|
||||||
} catch {
|
|
||||||
return new Response("no tag found", { status: 404 });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.put(async (req, ctx) => {
|
|
||||||
if (!ctx.params.namespace) {
|
|
||||||
return new Response("somehow hit the tags endpoint without namespace", {
|
|
||||||
status: 500,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
|
|
||||||
const version = getPackVersion(store);
|
|
||||||
|
|
||||||
const tagDir = await getTagDir(store, ctx.params.namespace, version);
|
|
||||||
|
|
||||||
const tag = ctx.params.tag;
|
|
||||||
const type = ctx.params.type;
|
|
||||||
if (!tag || !type) {
|
|
||||||
return new Response("no tag name provided", { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagPath = `${tagDir}/${type}/${tag}.json`;
|
|
||||||
|
|
||||||
await ensureFile(tagPath);
|
|
||||||
|
|
||||||
await Deno.writeTextFile(tagPath, await req.text());
|
|
||||||
|
|
||||||
return new Response(tag, { status: 200 });
|
|
||||||
})
|
|
||||||
.delete(async (_, ctx) => {
|
|
||||||
if (!ctx.params.namespace) {
|
|
||||||
return new Response("somehow hit the tags endpoint without namespace", {
|
|
||||||
status: 500,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
using store = new BearMetalStore();
|
|
||||||
|
|
||||||
const version = getPackVersion(store);
|
|
||||||
|
|
||||||
const tagDir = await getTagDir(store, ctx.params.namespace, version);
|
|
||||||
|
|
||||||
const tag = ctx.params.tag;
|
|
||||||
if (!tag) {
|
|
||||||
return new Response("no tag name provided", { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Deno.remove(tagDir + "/" + tag + ".json");
|
|
||||||
return new Response(tag, { status: 200 });
|
|
||||||
} catch {
|
|
||||||
return new Response("no tag found", { status: 404 });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,32 +0,0 @@
|
|||||||
import type { BearMetalStore } from "@bearmetal/store";
|
|
||||||
|
|
||||||
export function getPackVersion(store: BearMetalStore) {
|
|
||||||
const packMeta = Deno.readTextFileSync(
|
|
||||||
store.get("packlocation") + "/pack.mcmeta",
|
|
||||||
);
|
|
||||||
const packMetaJson = JSON.parse(packMeta);
|
|
||||||
return packMetaJson.pack.pack_format as number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getDirName(version: number, path: string) {
|
|
||||||
const { default: versionData } = await import(
|
|
||||||
"../../pack_versions/" + version + ".json",
|
|
||||||
{
|
|
||||||
with: { type: "json" },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const singular = makeSingular(path);
|
|
||||||
const plural = makePlural(path);
|
|
||||||
|
|
||||||
return versionData && versionData.schema.data["<namespace>"][singular] ||
|
|
||||||
versionData.schema.data["<namespace>"][plural];
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeSingular(word: string) {
|
|
||||||
return word.replace(/s$/, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function makePlural(word: string) {
|
|
||||||
return makeSingular(word) + "s";
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
export const readDirFiles = async (path: string) => {
|
|
||||||
return readDirFiltered(path, (file) => file.isFile);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const readDirDirs = async (path: string) => {
|
|
||||||
return readDirFiltered(path, (file) => file.isDirectory);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const readDirFiltered = async (
|
|
||||||
path: string,
|
|
||||||
filter: (file: Deno.DirEntry) => boolean,
|
|
||||||
) => {
|
|
||||||
const files: string[] = [];
|
|
||||||
for await (const file of Deno.readDir(path)) {
|
|
||||||
if (filter(file)) {
|
|
||||||
files.push(file.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files;
|
|
||||||
};
|
|
@ -1,14 +0,0 @@
|
|||||||
export const versionCompat = (version: string, targetVersion: string) => {
|
|
||||||
if (targetVersion === "*") return true;
|
|
||||||
if (targetVersion === version) return true;
|
|
||||||
if (targetVersion.startsWith("^")) {
|
|
||||||
const versionSplit = version.split(".");
|
|
||||||
const targetVersionSplit = targetVersion.split(".");
|
|
||||||
for (let i = 0; i < versionSplit.length; i++) {
|
|
||||||
if (versionSplit[i] ?? "0" > targetVersionSplit[i] ?? "0") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
16
session.sh
16
session.sh
@ -1,16 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
SESSION=BearMetalPacker
|
|
||||||
|
|
||||||
tmux new-session -d -s $SESSION
|
|
||||||
tmux new-window -t $SESSION:1 -n "packer"
|
|
||||||
|
|
||||||
tmux select-window -t $SESSION:1
|
|
||||||
tmux send-keys "deno task bdev" C-m
|
|
||||||
|
|
||||||
tmux split-window -h
|
|
||||||
tmux send-keys "deno task fdev" C-m
|
|
||||||
|
|
||||||
tmux split-window -v
|
|
||||||
|
|
||||||
tmux attach -t $SESSION
|
|
20
src/app.tsx
20
src/app.tsx
@ -1,17 +1,13 @@
|
|||||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
import { BrowserRouter,Routes,Route } from "react-router-dom";
|
||||||
import { Home } from "./views/home.tsx";
|
import { Home } from "./views/home.tsx";
|
||||||
import { Editor } from "./views/editor.tsx";
|
|
||||||
import { Provider } from "jotai";
|
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider>
|
<BrowserRouter>
|
||||||
<BrowserRouter>
|
<Routes>
|
||||||
<Routes>
|
<Route path="/" Component={Home} />
|
||||||
<Route path="/" Component={Home} />
|
</Routes>
|
||||||
<Route path="/editor/*" Component={Editor} />
|
</BrowserRouter>
|
||||||
</Routes>
|
)
|
||||||
</BrowserRouter>
|
|
||||||
</Provider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import { atomWithStorage } from "jotai/utils";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
|
|
||||||
const key = "bmp:namespace";
|
|
||||||
// const namespaceAtomPrimitive = atom<string>(localStorage.getItem(key) ??"");
|
|
||||||
|
|
||||||
// export const namespaceAtom = atom<string>(
|
|
||||||
// (get) => get(namespaceAtomPrimitive),
|
|
||||||
// (get, set, newStr) => {
|
|
||||||
// set(namespaceAtomPrimitive, newStr)
|
|
||||||
// localStorage.setItem(key, newStr)
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
|
|
||||||
export const namespaceAtom = atomWithStorage(key, "");
|
|
||||||
|
|
||||||
export const useNamespace = () => {
|
|
||||||
return useAtom(namespaceAtom);
|
|
||||||
};
|
|
@ -1,62 +0,0 @@
|
|||||||
import { useState } from "preact/hooks";
|
|
||||||
import { fetchJson } from "../../util/fetchJson.ts";
|
|
||||||
import { Loader } from "../../components/loader.tsx";
|
|
||||||
import { Modal } from "../../components/modal.tsx";
|
|
||||||
import useSwr from "swr";
|
|
||||||
|
|
||||||
export const NamespaceModal = ({ close }: { close: () => void }) => {
|
|
||||||
const { data: namespaces, isLoading } = useSwr<string[]>(
|
|
||||||
"/api/pack/namespaces",
|
|
||||||
fetchJson,
|
|
||||||
);
|
|
||||||
const [namespace, setNamespace] = useState("");
|
|
||||||
const [invalid, setInvalid] = useState("");
|
|
||||||
|
|
||||||
const createNamespace = async (ns?: string) => {
|
|
||||||
const res = await fetch("/api/pack/namespaces", {
|
|
||||||
method: "POST",
|
|
||||||
body: ns ?? namespace,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
|
||||||
return close();
|
|
||||||
}
|
|
||||||
|
|
||||||
setInvalid(await res.text());
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal>
|
|
||||||
{isLoading
|
|
||||||
? <Loader msg="Checking existing namespaces..." />
|
|
||||||
: (
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<p>Create a new namespace</p>
|
|
||||||
{!namespaces?.includes("minecraft") &&
|
|
||||||
(
|
|
||||||
<button
|
|
||||||
class="w-full"
|
|
||||||
onClick={() => createNamespace("minecraft")}
|
|
||||||
>
|
|
||||||
Create default minecraft namespace
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<form
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
createNamespace();
|
|
||||||
}}
|
|
||||||
class="flex gap-2"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={namespace}
|
|
||||||
onInput={(e) => setNamespace((e.target as any).value)}
|
|
||||||
/>
|
|
||||||
<button type="submit">Create</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
export const Selector = () => {
|
|
||||||
return (
|
|
||||||
<ul class="flex flex-col gap-2">
|
|
||||||
<li>
|
|
||||||
<Link to="/editor/tags">Tag Editor</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,156 +0,0 @@
|
|||||||
import { Link, Route, Routes, useParams } from "react-router-dom";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { fetchJson } from "../../../util/fetchJson.ts";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { namespaceAtom, useNamespace } from "../../../atoms/namespace.ts";
|
|
||||||
import { Loader } from "../../../components/loader.tsx";
|
|
||||||
import { useEffect, useState } from "preact/hooks";
|
|
||||||
import { NewTagModal } from "./newTagModal.tsx";
|
|
||||||
|
|
||||||
export const TagRouter = () => {
|
|
||||||
return (
|
|
||||||
<Routes>
|
|
||||||
<Route index Component={TagList} />
|
|
||||||
<Route path=":typeTag" Component={TagEditor} />
|
|
||||||
</Routes>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function TagList() {
|
|
||||||
const [namespace, _setNamespace] = useAtom(namespaceAtom);
|
|
||||||
const { data, isLoading } = useSWR<string[]>(
|
|
||||||
`/api/pack/${namespace}/tags`,
|
|
||||||
fetchJson,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [showNewTagModal, setShowNewTagModal] = useState(false);
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <Loader msg="Geez, when was the last time you swept?" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ul>
|
|
||||||
</ul>
|
|
||||||
<Resources />
|
|
||||||
{false && (
|
|
||||||
<ul class="flex flex-col gap-2">
|
|
||||||
<li>
|
|
||||||
<button onClick={() => setShowNewTagModal(true)}>New Tag</button>
|
|
||||||
{showNewTagModal && (
|
|
||||||
<NewTagModal
|
|
||||||
close={() => {
|
|
||||||
setShowNewTagModal(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
{data?.map((tag) => (
|
|
||||||
<li class="flex gap-2">
|
|
||||||
<Link to={`/editor/tags/${tag}`}>{tag}</Link>
|
|
||||||
<button>Delete</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Tag {
|
|
||||||
replace: boolean;
|
|
||||||
values: (string | { id: string; required: boolean })[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function TagEditor() {
|
|
||||||
const [namespace, _setNamespace] = useNamespace();
|
|
||||||
const { typeTag } = useParams();
|
|
||||||
const { data, isLoading } = useSWR<Tag>(
|
|
||||||
`/api/pack/${namespace}/tags/${typeTag}`,
|
|
||||||
fetchJson,
|
|
||||||
);
|
|
||||||
const [tag, setTag] = useState<Tag>({
|
|
||||||
replace: false,
|
|
||||||
values: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isLoading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setTag(data || { replace: false, values: [] });
|
|
||||||
}, [data, isLoading]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <Loader msg="Your hard drive is full of... interesting things." />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-bold">
|
|
||||||
{typeTag}:
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="replace"
|
|
||||||
checked={tag.replace}
|
|
||||||
onChange={(e) =>
|
|
||||||
setTag({ ...tag, replace: (e.target as any).checked })}
|
|
||||||
/>
|
|
||||||
{
|
|
||||||
/* {tag.values.map((value, i) => (
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={value}
|
|
||||||
onInput={(e) =>
|
|
||||||
setTag({
|
|
||||||
...tag,
|
|
||||||
values: tag.values.map((v, i) =>
|
|
||||||
i === i ? e.target.value : v
|
|
||||||
),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={() =>
|
|
||||||
setTag({
|
|
||||||
...tag,
|
|
||||||
values: tag.values.filter((_, i) => i !== i),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))} */
|
|
||||||
}
|
|
||||||
{tag.values.length === 0 && (
|
|
||||||
<button onClick={() => setTag({ ...tag, values: [""] })}>
|
|
||||||
Add Value
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Resources() {
|
|
||||||
const { data, isLoading } = useSWR<{ name: string; images: string[] }[]>(
|
|
||||||
`/api/resources/blocks?format=48`,
|
|
||||||
fetchJson,
|
|
||||||
);
|
|
||||||
if (isLoading || !data) {
|
|
||||||
return <Loader msg="Your hard drive is full of... interesting things." />;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ul>
|
|
||||||
{data.map((resource) => (
|
|
||||||
<li>
|
|
||||||
{resource.name}
|
|
||||||
<img class="min-w-12 max-h-16" src={resource.images[0]} />
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
import { useState } from "preact/hooks";
|
|
||||||
import { Modal } from "../../modal.tsx";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { namespaceAtom } from "../../../atoms/namespace.ts";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
|
|
||||||
export const NewTagModal = ({ close }: { close: () => void }) => {
|
|
||||||
const [tagName, setTagName] = useState("");
|
|
||||||
const [tagType, setTagType] = useState("block");
|
|
||||||
const nav = useNavigate();
|
|
||||||
const [namespace, _setNamespace] = useAtom(namespaceAtom);
|
|
||||||
|
|
||||||
const createTag = async () => {
|
|
||||||
const res = await fetch(
|
|
||||||
`/api/pack/${namespace}/tags`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
tag: tagName,
|
|
||||||
type: tagType,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
|
||||||
close();
|
|
||||||
nav(`/editor/tags/${tagType}-${tagName}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Modal>
|
|
||||||
<p class="mb-2">Create a new tag</p>
|
|
||||||
<form
|
|
||||||
class="flex flex-col gap-2"
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
createTag();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<label class="flex gap-2 items-center">
|
|
||||||
Tag Type{" "}
|
|
||||||
<select
|
|
||||||
class="flex-1"
|
|
||||||
name="tag-type"
|
|
||||||
value={tagType}
|
|
||||||
onChange={(e) => setTagType((e.target as any).value)}
|
|
||||||
>
|
|
||||||
<option value="block">Block</option>
|
|
||||||
<option value="entity">Entity</option>
|
|
||||||
<option value="item">Item</option>
|
|
||||||
<option value="function">Function</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={tagName}
|
|
||||||
onInput={(e) => setTagName((e.target as any).value)}
|
|
||||||
placeholder="Tag name"
|
|
||||||
/>
|
|
||||||
<button type="submit">Create</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,15 +0,0 @@
|
|||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import type { FunctionComponent } from "preact";
|
|
||||||
|
|
||||||
export const EditorWrapper: FunctionComponent = ({ children }) => {
|
|
||||||
const nav = useNavigate();
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<button class="hollow flex items-center gap-2" onClick={() => nav(-1)}>
|
|
||||||
<span class="text-2xl">←</span>
|
|
||||||
<span class="italic">back</span>
|
|
||||||
</button>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
import type { FunctionComponent } from "preact";
|
|
||||||
|
|
||||||
export const LabelledHr: FunctionComponent = ({ children }) => {
|
|
||||||
return (
|
|
||||||
<div class="flex gap-4 items-center my-4">
|
|
||||||
<hr class="w-full" />
|
|
||||||
<label>{children}</label>
|
|
||||||
<hr class="w-full" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import { classList } from "../util/classes.ts";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
msg?: string;
|
|
||||||
class?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Loader = ({ msg, class: className }: IProps) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={classList("flex justify-center items-center gap-4", className)}
|
|
||||||
>
|
|
||||||
{!!msg && <p>{msg}</p>}
|
|
||||||
<div class="animate-spin rounded-full max-w-full h-16 w-16 border-t-2 border-b-2 border-primary-600">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,14 +0,0 @@
|
|||||||
import type { FunctionComponent } from "preact";
|
|
||||||
import { Portal } from "./portal.tsx";
|
|
||||||
|
|
||||||
export const Modal: FunctionComponent = ({ children }) => {
|
|
||||||
return (
|
|
||||||
<Portal>
|
|
||||||
<div class="fixed inset-0 z-10 overflow-y-auto bg-black/50 grid">
|
|
||||||
<div class="place-self-center bg-white dark:bg-mixed-400 w-min min-w-64 rounded-lg p-4 overflow-scroll relative">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Portal>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,68 +0,0 @@
|
|||||||
import { useEffect, useState } from "preact/hooks";
|
|
||||||
import type { FunctionComponent } from "preact";
|
|
||||||
import { Loader } from "./loader.tsx";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
packName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PackInfo: FunctionComponent<IProps> = ({ packName }) => {
|
|
||||||
const [showVersions, setShowVersions] = useState(false);
|
|
||||||
const [packVersion, setPackVersion] = useState("");
|
|
||||||
const [packVersionList, setPackVersionList] = useState<string[]>([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [world, setWorld] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
Promise.all([
|
|
||||||
fetch("/api/pack/version").then((res) => res.text()).then((text) => {
|
|
||||||
setPackVersion(text);
|
|
||||||
}),
|
|
||||||
fetch("/api/versions").then((res) => res.json()).then((json) => {
|
|
||||||
setPackVersionList(json);
|
|
||||||
}),
|
|
||||||
fetch("/api/world").then((res) => res.text()).then((text) => {
|
|
||||||
setWorld(text);
|
|
||||||
}),
|
|
||||||
]).finally(() => setLoading(false));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updatePackVersion = async (version: string) => {
|
|
||||||
const res = await fetch("/api/pack/version", {
|
|
||||||
method: "POST",
|
|
||||||
body: version,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
|
||||||
setPackVersion(version);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h2 class="text-xl">{world} :: {packName}</h2>
|
|
||||||
<p class="bg-lime-700 rounded-full shadow-md">
|
|
||||||
{loading ? <Loader class="w-8" /> : (
|
|
||||||
<>
|
|
||||||
Datapack Version:{" "}
|
|
||||||
<span
|
|
||||||
class="relative cursor-pointer rounded-full bg-lime-200 text-black px-2 inline-block"
|
|
||||||
onClick={() => setShowVersions(!showVersions)}
|
|
||||||
>
|
|
||||||
{packVersion}
|
|
||||||
{showVersions && (
|
|
||||||
<ul class="absolute top-full left-0 bg-lime-50 rounded-md shadow-md p-2 min-w 12">
|
|
||||||
{packVersionList.map((version) => (
|
|
||||||
<li onClick={() => updatePackVersion(version)}>
|
|
||||||
{version}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,53 +0,0 @@
|
|||||||
import {
|
|
||||||
type Dispatch,
|
|
||||||
type StateUpdater,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from "preact/hooks";
|
|
||||||
import { Loader } from "./loader.tsx";
|
|
||||||
|
|
||||||
export const PacksList = (
|
|
||||||
{ setPackName }: { setPackName: Dispatch<StateUpdater<string>> },
|
|
||||||
) => {
|
|
||||||
const [packs, setPacks] = useState<{ name: string; path: string }[]>([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch("/api/packs").then((res) => res.json()).then((json) => {
|
|
||||||
setPacks(json);
|
|
||||||
}).finally(() => setLoading(false));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const setPackNameThing = async (name: string) => {
|
|
||||||
setLoading(true);
|
|
||||||
const body = new FormData();
|
|
||||||
body.set("packName", name);
|
|
||||||
const res = await fetch("/api/pack", {
|
|
||||||
method: "POST",
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
|
||||||
document.title = json.packName;
|
|
||||||
setPackName(json.packName);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return loading ? <Loader /> : (
|
|
||||||
<ul>
|
|
||||||
{packs.length
|
|
||||||
? packs.map((pack) => (
|
|
||||||
<li
|
|
||||||
class="cursor-pointer even:bg-black/5"
|
|
||||||
onClick={() => setPackNameThing(pack.name)}
|
|
||||||
>
|
|
||||||
{pack.name}
|
|
||||||
</li>
|
|
||||||
))
|
|
||||||
: <li>No packs found</li>}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,26 +0,0 @@
|
|||||||
import { useEffect, useState } from "preact/hooks";
|
|
||||||
import { createPortal } from "preact/compat";
|
|
||||||
import type { FunctionComponent } from "preact";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
className?: string;
|
|
||||||
el?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Portal: FunctionComponent<IProps> = (
|
|
||||||
{ children, className = "root-portal", el = "div" },
|
|
||||||
) => {
|
|
||||||
const [container, setContainer] = useState<HTMLElement>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const container = document.createElement(el);
|
|
||||||
container.classList.add(className);
|
|
||||||
document.body.appendChild(container);
|
|
||||||
setContainer(container);
|
|
||||||
return () => {
|
|
||||||
document.body.removeChild(container);
|
|
||||||
};
|
|
||||||
}, [className, el]);
|
|
||||||
|
|
||||||
return container ? createPortal(children, container) : <></>;
|
|
||||||
};
|
|
@ -1,42 +0,0 @@
|
|||||||
import { useEffect } from "preact/hooks";
|
|
||||||
|
|
||||||
export const useStream = <T>(
|
|
||||||
stream: ReadableStream<T>,
|
|
||||||
onData: (data: T) => void,
|
|
||||||
onError: (err: Error) => void,
|
|
||||||
) => {
|
|
||||||
const reader = stream.getReader();
|
|
||||||
const read = async () => {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) {
|
|
||||||
reader.releaseLock();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onData(value);
|
|
||||||
read();
|
|
||||||
};
|
|
||||||
read();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useRemoteStream = <T>(
|
|
||||||
url: string,
|
|
||||||
onData: (data: T) => void,
|
|
||||||
onError: (err: Error) => void,
|
|
||||||
) => {
|
|
||||||
useEffect(() => {
|
|
||||||
const stream = new ReadableStream({
|
|
||||||
async start(controller) {
|
|
||||||
const res = await fetch(url);
|
|
||||||
res.body?.pipeThrough(new TextDecoderStream())
|
|
||||||
.pipeTo(
|
|
||||||
new WritableStream({
|
|
||||||
write(chunk) {
|
|
||||||
controller.enqueue(chunk);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
useStream(stream, onData, onError);
|
|
||||||
}, [url]);
|
|
||||||
};
|
|
18
src/index.css
Normal file
18
src/index.css
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
@apply dark:bg-mixed-600 bg-primary-100 text-dark-600 dark:text-white;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body, #app, html {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
@apply bg-primary-600 text-white rounded-md px-4 py-2 font-bold;
|
||||||
|
}
|
||||||
|
}
|
@ -1,50 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
:root {
|
|
||||||
@apply dark:bg-mixed-600 bg-primary-100 text-dark-600 dark:text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body,
|
|
||||||
#app,
|
|
||||||
html {
|
|
||||||
@apply h-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
@apply bg-primary-600 text-white rounded-md px-4 py-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
select {
|
|
||||||
@apply bg-white text-dark-600 rounded-md px-4 py-2;
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
@apply mr-2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer utilities {
|
|
||||||
.animate-spin {
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.hollow {
|
|
||||||
@apply bg-transparent p-0 inline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,5 @@
|
|||||||
import { render } from "preact";
|
import { render } from 'preact'
|
||||||
import { App } from "./app.tsx";
|
import { App } from './app.tsx'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
render(<App />, document.getElementById("app") as HTMLElement);
|
render(<App />, document.getElementById('app') as HTMLElement)
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
export const classList = (...classes: (string | undefined)[]) => {
|
|
||||||
return classes.filter(Boolean).join(" ");
|
|
||||||
};
|
|
@ -1,2 +0,0 @@
|
|||||||
export const fetchJson = (url: string, init?: RequestInit) =>
|
|
||||||
fetch(url, init).then((res) => res.json());
|
|
@ -1,137 +0,0 @@
|
|||||||
import { useEffect, useState } from "preact/hooks";
|
|
||||||
import { Modal } from "../components/modal.tsx";
|
|
||||||
import { LabelledHr } from "../components/labelledHr.tsx";
|
|
||||||
import { PacksList } from "../components/packsList.tsx";
|
|
||||||
import { PackInfo } from "../components/packInfo.tsx";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { namespaceAtom } from "../atoms/namespace.ts";
|
|
||||||
import { Outlet, Route, Routes } from "react-router-dom";
|
|
||||||
import { Selector } from "../components/editor/selector.tsx";
|
|
||||||
import { NamespaceModal } from "../components/editor/namespaceModal.tsx";
|
|
||||||
import { EditorWrapper } from "../components/editor/wrapper.tsx";
|
|
||||||
import { TagRouter } from "../components/editor/tags/editor.tsx";
|
|
||||||
|
|
||||||
export const Editor = () => {
|
|
||||||
const [packName, setPackName] = useState("");
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [namespaces, setNamespaces] = useState<string[]>([]);
|
|
||||||
const [namespace, setNamespace] = useAtom(namespaceAtom);
|
|
||||||
|
|
||||||
const fetchNamespaces = async () => {
|
|
||||||
const res = await fetch("/api/pack/namespaces");
|
|
||||||
const json = await res.json();
|
|
||||||
setNamespaces(json);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.title = "BearMetal Packer";
|
|
||||||
fetch("/api/pack").then((res) => res.json()).then((json) => {
|
|
||||||
setPackName(json.packName);
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
fetchNamespaces();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const setPackNameThing = async (event: SubmitEvent) => {
|
|
||||||
setLoading(true);
|
|
||||||
const res = await fetch("/api/pack", {
|
|
||||||
method: "POST",
|
|
||||||
body: new FormData(event.target as HTMLFormElement),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
|
||||||
document.title = packName;
|
|
||||||
setPackName(packName);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!packName) {
|
|
||||||
return (
|
|
||||||
<Modal>
|
|
||||||
{loading
|
|
||||||
? <p>Just a sec, trying to put the cats back in the bag.</p>
|
|
||||||
: (
|
|
||||||
<>
|
|
||||||
<form
|
|
||||||
method="POST"
|
|
||||||
onSubmit={setPackNameThing}
|
|
||||||
>
|
|
||||||
<label class="w-full">Set pack name</label>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<input type="text" name="packName" />
|
|
||||||
<button type="submit">Set</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<LabelledHr>OR</LabelledHr>
|
|
||||||
<PacksList setPackName={setPackName} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [showNamespaceModal, setShowNamespaceModal] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="flex h-full">
|
|
||||||
<div class="w-1/4 p-4 bg-mixed-400">
|
|
||||||
<div class="text-center">
|
|
||||||
<h1 class="text-2xl">BearMetalPacker</h1>
|
|
||||||
<PackInfo packName={packName} />
|
|
||||||
</div>
|
|
||||||
<LabelledHr>Namespaces</LabelledHr>
|
|
||||||
<div>
|
|
||||||
<ul class="w-full">
|
|
||||||
{namespaces.map((namespace) => (
|
|
||||||
<li
|
|
||||||
class="text-lg cursor-pointer even:bg-black/5"
|
|
||||||
onClick={() => setNamespace(namespace)}
|
|
||||||
>
|
|
||||||
{namespace}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
<li class="mt-4">
|
|
||||||
<button onClick={() => setShowNamespaceModal(true)}>
|
|
||||||
New Namespace
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{showNamespaceModal && (
|
|
||||||
<NamespaceModal
|
|
||||||
close={() => {
|
|
||||||
setShowNamespaceModal(false);
|
|
||||||
fetchNamespaces();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-4 w-max">
|
|
||||||
{!namespace ? <div>No namespace set</div> : (
|
|
||||||
<>
|
|
||||||
<h3 class="text-lg font-bold">
|
|
||||||
Namespace: <span class="italic">{namespace}</span>
|
|
||||||
</h3>
|
|
||||||
<Routes>
|
|
||||||
<Route index element={<Selector />} />
|
|
||||||
|
|
||||||
<Route
|
|
||||||
element={
|
|
||||||
<EditorWrapper>
|
|
||||||
<Outlet />
|
|
||||||
</EditorWrapper>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Route
|
|
||||||
path="tags/*"
|
|
||||||
element={<TagRouter />}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
</Routes>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,99 +1,25 @@
|
|||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
export function Home() {
|
export function Home() {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [mcPath, setMcPath] = useState("");
|
const [mcPath, setMcPath] = useState("");
|
||||||
const [worlds, setWorlds] = useState<{
|
|
||||||
world: string;
|
|
||||||
icon: string;
|
|
||||||
path: string;
|
|
||||||
}[]>([]);
|
|
||||||
|
|
||||||
const fetchWorlds = async () => {
|
|
||||||
const res = await fetch("/api/dir");
|
|
||||||
const json = await res.json();
|
|
||||||
setMcPath(json.mcPath);
|
|
||||||
setWorlds(json.worlds);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "BearMetal Packer";
|
document.title = "BearMetal Packer";
|
||||||
fetchWorlds();
|
|
||||||
|
fetch("/api/dir").then(res => res.json()).then(data => {
|
||||||
|
setMcPath(data);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const submitMcPath = async (event: SubmitEvent) => {
|
|
||||||
setLoading(true);
|
|
||||||
event.preventDefault();
|
|
||||||
const form = new FormData(event.target as HTMLFormElement);
|
|
||||||
const res = await fetch("/api/dir", {
|
|
||||||
method: "POST",
|
|
||||||
body: form,
|
|
||||||
});
|
|
||||||
if (res.status === 200) {
|
|
||||||
fetchWorlds();
|
|
||||||
} else setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const nav = useNavigate();
|
|
||||||
|
|
||||||
const setWorld = async (worldPath: string) => {
|
|
||||||
setLoading(true);
|
|
||||||
const res = await fetch("/api/world", {
|
|
||||||
method: "POST",
|
|
||||||
body: worldPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
|
||||||
nav("/editor");
|
|
||||||
} else setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="grid h-full">
|
<div class="grid h-full">
|
||||||
<div class="place-self-center text-center max-h-96 bg-primary-400 rounded-lg p-4 overflow-scroll">
|
<div class="place-self-center text-center">
|
||||||
<h1 class="text-3xl">Welcome BearMetal Packer</h1>
|
<h1 class="text-3xl">Welcome BearMetal Packer</h1>
|
||||||
<p>An all in one toolkit to build datapacks for Minecraft.</p>
|
<p>An all in one toolkit to build datapacks for Minecraft.</p>
|
||||||
{loading ? <p>Hold tight, we're doing some heavy lifting.</p> : (
|
<p>Hold tight, we're doing some heavy lifting.</p>
|
||||||
<>
|
|
||||||
{mcPath
|
|
||||||
? (
|
|
||||||
<>
|
|
||||||
<p>Minecraft directory set to {mcPath}</p>
|
|
||||||
<p>Worlds:</p>
|
|
||||||
<ul class="w-full even:bg-black/5">
|
|
||||||
{worlds.map((world) => (
|
|
||||||
<li
|
|
||||||
class="flex gap-4 items-center cursor-pointer"
|
|
||||||
onClick={() => setWorld(world.path)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={"/images?location=" + world.icon}
|
|
||||||
class="w-16 h-16"
|
|
||||||
/>
|
|
||||||
{world.world}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<>
|
|
||||||
<p>No Minecraft directory set, please set now:</p>
|
|
||||||
<form method="POST" onSubmit={submitMcPath}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="mcPath"
|
|
||||||
placeholder="Minecraft directory"
|
|
||||||
/>
|
|
||||||
<button type="submit">Set</button>
|
|
||||||
</form>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
@ -6,10 +6,6 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
|
||||||
sans: ["Roboto", "sans-serif"],
|
|
||||||
mono: ["Roboto Mono", "monospace"],
|
|
||||||
},
|
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||||
"gradient-conic":
|
"gradient-conic":
|
||||||
|
7
types.ts
7
types.ts
@ -1,7 +0,0 @@
|
|||||||
declare global {
|
|
||||||
interface Context {
|
|
||||||
url: URL;
|
|
||||||
state: Record<string, unknown>;
|
|
||||||
params: Record<string, string | undefined>;
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,15 +16,12 @@ export default defineConfig({
|
|||||||
"/api": {
|
"/api": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
// rewrite: (path) => path.replace(/^\/api/, ""),
|
||||||
},
|
},
|
||||||
"/puppet": {
|
"/puppet": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
ws: true,
|
// rewrite: (path) => path.replace(/^\/puppet/, ""),
|
||||||
},
|
|
||||||
"/images": {
|
|
||||||
target: "http://localhost:8000",
|
|
||||||
changeOrigin: true,
|
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user