diff --git a/app/client/package.json b/app/client/package.json
index 48fabfe..ad7ef4c 100644
--- a/app/client/package.json
+++ b/app/client/package.json
@@ -21,7 +21,8 @@
"react-dom": "18.3.1",
"react-router": "6.26.0",
"react-router-dom": "6.26.0",
- "sass": "1.77.8"
+ "sass": "1.77.8",
+ "yaml": "^2.5.0"
},
"packageManager": "yarn@1.22.22"
}
diff --git a/app/client/src/index.html b/app/client/src/index.html
index 51c4b87..ae625fc 100644
--- a/app/client/src/index.html
+++ b/app/client/src/index.html
@@ -3,11 +3,11 @@
+ Open Hotel Asset Editor
diff --git a/app/client/src/main.module.scss b/app/client/src/main.module.scss
index cae9d53..c41f0d1 100644
--- a/app/client/src/main.module.scss
+++ b/app/client/src/main.module.scss
@@ -1,23 +1,40 @@
:root {
--color-primary: #f0ebe3;
+ --color-secondary: #b4b0ad;
--color-background: #121212;
}
* {
font-size: 1.6rem;
- font-family: ui-sans-serif, system-ui, sans-serif;
+ font-family: monospace;
box-sizing: border-box;
button {
border-radius: 0;
- padding: 0;
+ border: 0;
+ padding: 0 .5rem;
+ background: white;
+ cursor: pointer;
+ label {
+ cursor: pointer;
+ }
+ }
+
+ select {
+ border-radius: 0;
border: 0;
}
input {
border: 0 solid;
padding: 0;
- border: 0;
+ width: 100%;
+ }
+
+ form {
+ background-color: gray;
+ display: flex;
+ gap: .25rem;
}
img {
@@ -37,6 +54,9 @@
h4 {
font-size: 2rem;
}
+ hr {
+ color: var(--color-primary);
+ }
}
html {
@@ -55,7 +75,6 @@ body {
background-attachment: fixed;
background-size: cover;
- overflow-x: hidden !important;
}
a {
&:visited,
diff --git a/app/client/src/modules/application/components/content/content.module.scss b/app/client/src/modules/application/components/content/content.module.scss
index 6f6ff94..7009b2b 100644
--- a/app/client/src/modules/application/components/content/content.module.scss
+++ b/app/client/src/modules/application/components/content/content.module.scss
@@ -4,7 +4,6 @@
display: flex;
flex-direction: column;
height: 100%;
- max-width: 200px;
.outlet {
flex: 1;
diff --git a/app/client/src/modules/application/components/footer/footer.component.tsx b/app/client/src/modules/application/components/footer/footer.component.tsx
index c9e2e3a..ab10c65 100644
--- a/app/client/src/modules/application/components/footer/footer.component.tsx
+++ b/app/client/src/modules/application/components/footer/footer.component.tsx
@@ -5,7 +5,18 @@ import { ContainerComponent } from "shared/components";
export const FooterComponent = () => {
return (
);
};
diff --git a/app/client/src/modules/application/components/footer/footer.module.scss b/app/client/src/modules/application/components/footer/footer.module.scss
index 95ca357..2d5c827 100644
--- a/app/client/src/modules/application/components/footer/footer.module.scss
+++ b/app/client/src/modules/application/components/footer/footer.module.scss
@@ -2,4 +2,24 @@
@import "../../../../shared/styles/mixins.module";
.footer {
+ margin: 2rem 0;
+ color: var(--color-secondary);
+
+ .container {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ line-height: 3rem;
+
+ label {
+ flex: 1;
+ }
+ img {
+ height: 3rem;
+ opacity: .5;
+ &:hover {
+ opacity: 1;
+ }
+ }
+ }
}
diff --git a/app/client/src/modules/application/components/header/header.component.tsx b/app/client/src/modules/application/components/header/header.component.tsx
index a7f7709..7c3252c 100644
--- a/app/client/src/modules/application/components/header/header.component.tsx
+++ b/app/client/src/modules/application/components/header/header.component.tsx
@@ -1,11 +1,20 @@
import React from "react";
import styles from "./header.module.scss";
-import { ContainerComponent } from "shared/components";
+import { ContainerComponent, LinkComponent } from "shared/components";
export const HeaderComponent = () => {
return (
);
};
diff --git a/app/client/src/modules/application/components/header/header.module.scss b/app/client/src/modules/application/components/header/header.module.scss
index e6ebfb9..2987b28 100644
--- a/app/client/src/modules/application/components/header/header.module.scss
+++ b/app/client/src/modules/application/components/header/header.module.scss
@@ -2,4 +2,24 @@
@import "../../../../shared/styles/mixins.module";
.header {
+ margin: 2rem 0;
+ color: var(--color-secondary);
+
+ .container {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ .items {
+ display: flex;
+ flex-direction: row;
+ gap: 2rem;
+ a {
+ color: var(--color-secondary);
+
+ &:hover {
+ color: var(--color-primary);
+ }
+ }
+ }
+ }
}
diff --git a/app/client/src/modules/application/components/router/router.component.tsx b/app/client/src/modules/application/components/router/router.component.tsx
index 4284495..f3e5a8b 100644
--- a/app/client/src/modules/application/components/router/router.component.tsx
+++ b/app/client/src/modules/application/components/router/router.component.tsx
@@ -3,13 +3,23 @@ import React from "react";
import { LayoutComponent } from "../layout";
import { NotFoundComponent } from "../not-found";
import { HomeComponent } from "modules/home";
+import { FileManagerComponent } from "modules/file-manager";
import { RedirectComponent } from "shared/components";
+import { SpriteSheetsComponent } from "modules/sprite-sheets";
const router = createBrowserRouter([
{
element: ,
path: "/",
children: [
+ {
+ path: "/sprite-sheets",
+ element: ,
+ },
+ {
+ path: "/file-manager",
+ element: ,
+ },
{
path: "/",
Component: () => ,
diff --git a/app/client/src/modules/file-manager/file-manager.component.tsx b/app/client/src/modules/file-manager/file-manager.component.tsx
new file mode 100644
index 0000000..9572320
--- /dev/null
+++ b/app/client/src/modules/file-manager/file-manager.component.tsx
@@ -0,0 +1,173 @@
+import React, {
+ useEffect,
+ useState,
+ ChangeEvent,
+ useRef,
+ FormEvent,
+ useMemo,
+} from "react";
+import { useData } from "shared/hooks";
+import { File } from "shared/types";
+import { getBase64FromBody } from "shared/utils";
+import { parse } from "yaml";
+import styles from "./file-manager.module.scss";
+
+export const FileManagerComponent: React.FC = () => {
+ const { readDirectory, getFile, addFiles, remove, createDirectory } =
+ useData();
+
+ const uploadRef = useRef();
+
+ const [path, setPath] = useState("");
+ const [files, setFiles] = useState([]);
+
+ const [currentFile, setCurrentFile] = useState();
+ const [currentFileData, setCurrentFileData] = useState<{
+ data: string;
+ format: "image" | "text" | "json";
+ }>();
+
+ useEffect(() => {
+ if (!currentFile) return setCurrentFileData(null);
+
+ getFile(path + "/" + currentFile.name).then(async (data) => {
+ if (currentFile.name.endsWith(".png")) {
+ return setCurrentFileData({
+ data: await getBase64FromBody(data),
+ format: "image",
+ });
+ }
+ if (currentFile.name.endsWith(".yml")) {
+ return setCurrentFileData({
+ data: parse(await data.text()),
+ format: "json",
+ });
+ }
+ if (currentFile.name.endsWith(".json")) {
+ return setCurrentFileData({
+ data: await data.json(),
+ format: "json",
+ });
+ }
+ return setCurrentFileData({
+ data: await data.text(),
+ format: "text",
+ });
+ });
+ }, [currentFile]);
+
+ const loadFiles = () => {
+ setCurrentFile(null);
+ setCurrentFileData(null);
+ readDirectory(path).then(setFiles);
+ };
+
+ useEffect(() => {
+ loadFiles();
+ }, [path]);
+
+ const onClickBack = () => {
+ if (path === "/") return;
+ const pathArr = path.split("/");
+ setPath(pathArr.slice(0, pathArr.length - 1).join("/"));
+ };
+
+ const onClickFile = (file: File) => async () => {
+ if (file.isDirectory) setPath((path) => path + "/" + file.name);
+
+ if (file.isFile) setCurrentFile(file);
+ };
+ const onClickDeleteFile = (file: File) => async () => {
+ await remove(path + `/${file.name}`);
+ loadFiles();
+ };
+
+ const onUploadFiles = async (event: ChangeEvent) => {
+ const { files } = event.target;
+ await addFiles(path, files);
+ loadFiles();
+ //@ts-ignore
+ uploadRef.current.value = "";
+ };
+
+ const onCreateDirectory = async (event: FormEvent) => {
+ event.preventDefault();
+
+ const data = new FormData(event.target as unknown as HTMLFormElement);
+ const name = data.get("name") as string;
+
+ if (!name.length) return;
+
+ const targetPath = path + `/${name}`;
+ //@ts-ignore
+ event.target.reset();
+ await createDirectory(targetPath);
+ loadFiles();
+ setPath(targetPath);
+ };
+
+ const sortedFiles = useMemo(
+ () => files.toSorted((fileA, fileB) => (fileA.isDirectory ? -1 : 1)),
+ [files],
+ );
+
+ return (
+
+
+ {path || "/"}
+ {currentFile?.name ? `/${currentFile?.name}` : ""}
+
+
+
+
+ {path ? "⪻" : "|"}
+
+ {sortedFiles.map((file) => (
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ {currentFileData ? (
+ <>
+ {currentFileData.format === "json" ? (
+
+ ) : null}
+ {currentFileData.format === "text" ? (
+
+ ) : null}
+ {currentFileData.format === "image" ? (
+
+ ) : null}
+ >
+ ) : null}
+
+
+ );
+};
diff --git a/app/client/src/modules/file-manager/file-manager.module.scss b/app/client/src/modules/file-manager/file-manager.module.scss
new file mode 100644
index 0000000..5c82d42
--- /dev/null
+++ b/app/client/src/modules/file-manager/file-manager.module.scss
@@ -0,0 +1,61 @@
+@import "../../shared/styles/consts.module";
+@import "../../shared/styles/mixins.module";
+
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ margin: auto;
+ padding: 0 2rem;
+ @include responsive-width();
+
+ hr {
+ width: 100%;
+ padding: 0;
+ margin: 0;
+ }
+
+ .path {
+ font-weight: bold;
+ }
+ .actions {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ }
+ .files {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ padding: 0 2rem;
+
+ .back {
+ cursor: pointer;
+ }
+
+ .file {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+
+ & > label {
+ flex: 1;
+ }
+
+ .name {
+ cursor: pointer;
+ text-decoration-line: underline;
+ }
+ }
+ }
+
+ .preview {
+ textarea {
+ width: 100%;
+ height: 40rem;
+ resize: none;
+ }
+ img {
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/client/src/modules/file-manager/index.ts b/app/client/src/modules/file-manager/index.ts
new file mode 100644
index 0000000..83fcbfc
--- /dev/null
+++ b/app/client/src/modules/file-manager/index.ts
@@ -0,0 +1,2 @@
+
+export * from './file-manager.component'
\ No newline at end of file
diff --git a/app/client/src/modules/home/home.component.tsx b/app/client/src/modules/home/home.component.tsx
index 3166f42..def396b 100644
--- a/app/client/src/modules/home/home.component.tsx
+++ b/app/client/src/modules/home/home.component.tsx
@@ -1,5 +1,11 @@
import React from "react";
+import { ContainerComponent } from "shared/components";
export const HomeComponent: React.FC = () => {
- return Home!
;
+ return (
+
+ Welcome!
+ We expect you know what are you doing.
+
+ );
};
diff --git a/app/client/src/modules/sprite-sheets/components/animations/animations.component.tsx b/app/client/src/modules/sprite-sheets/components/animations/animations.component.tsx
new file mode 100644
index 0000000..3f25bb8
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/animations/animations.component.tsx
@@ -0,0 +1,131 @@
+import React, { FormEvent, useRef } from "react";
+import { getColorFromSeed } from "shared/utils";
+import styles from "./animations.module.scss";
+
+type Props = {
+ frames: string[];
+ animations: Record;
+ setAnimations: (animations: Record) => void;
+};
+
+export const AnimationsComponent: React.FC = ({
+ frames,
+ animations: animationsObject = {},
+ setAnimations,
+}) => {
+ const nameFrameRef = useRef();
+
+ const onAddAnimation = async (event: FormEvent) => {
+ event.preventDefault();
+
+ const data = new FormData(event.target as unknown as HTMLFormElement);
+ const name = data.get("name") as string;
+
+ if (!name) return;
+
+ setAnimations({
+ ...animationsObject,
+ [name]: [],
+ });
+ //@ts-ignore
+ event.target.reset();
+ //@ts-ignore
+ nameFrameRef.current.focus();
+ };
+
+ const onAddAnimationFrame =
+ (animationKey: string) => async (event: FormEvent) => {
+ event.preventDefault();
+
+ const data = new FormData(event.target as unknown as HTMLFormElement);
+ const frame = data.get("frame") as string;
+
+ if (!frame) return;
+
+ setAnimations({
+ ...animationsObject,
+ [animationKey]: [
+ ...new Set([...animationsObject[animationKey], frame]),
+ ],
+ });
+ //@ts-ignore
+ event.target.reset();
+ };
+
+ const onDeleteAnimation = (animationKey: string) => () => {
+ const clonedAnimationsObject = { ...animationsObject };
+
+ delete clonedAnimationsObject[animationKey];
+ setAnimations(clonedAnimationsObject);
+ };
+
+ const onDeleteAnimationFrame =
+ (animationKey: string, spriteName: string) => () => {
+ const clonedAnimationsObject = { ...animationsObject };
+
+ clonedAnimationsObject[animationKey] = clonedAnimationsObject[
+ animationKey
+ ].filter((frame) => spriteName !== frame);
+ setAnimations(clonedAnimationsObject);
+ };
+
+ const animations = Object.keys(animationsObject).map<
+ [string, string[], string]
+ >((frameKey) => [
+ frameKey,
+ animationsObject[frameKey],
+ getColorFromSeed(frameKey),
+ ]);
+
+ return (
+
+
animations:
+ {animations.map(([animationKey, animations, color]) => (
+
+
+
+ {animations.map((spriteName) => (
+
+ {spriteName}
+
+
+ ))}
+
+
+
+
+ ))}
+
+
+ );
+};
diff --git a/app/client/src/modules/sprite-sheets/components/animations/animations.module.scss b/app/client/src/modules/sprite-sheets/components/animations/animations.module.scss
new file mode 100644
index 0000000..0e16bac
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/animations/animations.module.scss
@@ -0,0 +1,43 @@
+@import "../../../../shared/styles/consts.module";
+@import "../../../../shared/styles/mixins.module";
+
+.animations {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ .frame {
+ display: flex;
+ justify-content: space-between;
+ padding: 0 2rem;
+
+ .sprites {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ padding: .25rem;
+ width: 100%;
+
+ .sprite {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ padding: 0 .5rem;
+ background-color: rgba(0, 0, 0, .25);
+ }
+ }
+
+ label {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ label {
+ min-width: 10rem;
+ }
+ .action {
+ width: 40rem;
+ padding: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/client/src/modules/sprite-sheets/components/animations/index.ts b/app/client/src/modules/sprite-sheets/components/animations/index.ts
new file mode 100644
index 0000000..884c75f
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/animations/index.ts
@@ -0,0 +1 @@
+export * from "./animations.component";
diff --git a/app/client/src/modules/sprite-sheets/components/frames/frames.component.tsx b/app/client/src/modules/sprite-sheets/components/frames/frames.component.tsx
new file mode 100644
index 0000000..f861966
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/frames/frames.component.tsx
@@ -0,0 +1,148 @@
+import React, { FormEvent, useEffect, useRef } from "react";
+import {
+ SpriteSheet,
+ SpriteSheetFrame,
+ SpriteSheetRectangle,
+} from "shared/types";
+import { getColorFromSeed } from "shared/utils";
+import styles from "./frames.module.scss";
+
+type Props = {
+ sheet: SpriteSheet;
+ setSheet: (sheet: SpriteSheet) => void;
+ previewRectangle: SpriteSheetRectangle;
+};
+
+export const FramesComponent: React.FC = ({
+ sheet,
+ setSheet,
+ previewRectangle,
+}) => {
+ const nameFrameRef = useRef();
+
+ useEffect(() => {
+ //@ts-ignore
+ nameFrameRef.current.focus();
+ }, [previewRectangle]);
+
+ const onSubmitAddFrame = async (event: FormEvent) => {
+ event.preventDefault();
+
+ const data = new FormData(event.target as unknown as HTMLFormElement);
+ const name = data.get("name") as string;
+ const x = parseInt(data.get("x") as string);
+ const y = parseInt(data.get("y") as string);
+ const w = parseInt(data.get("w") as string);
+ const h = parseInt(data.get("h") as string);
+
+ if (!name) return;
+
+ const frame: SpriteSheetFrame = {
+ frame: { x, y, w, h },
+ anchor: { x: 0, y: 0 },
+ sourceSize: { w, h },
+ };
+
+ setSheet({
+ ...sheet,
+ frames: {
+ ...sheet.frames,
+ [name]: frame,
+ },
+ });
+ //@ts-ignore
+ event.target.reset();
+ //@ts-ignore
+ nameFrameRef.current.focus();
+ };
+
+ const onDeleteFrame = (frameKey: string) => () => {
+ const clonedFramesObject = { ...sheet.frames };
+
+ delete clonedFramesObject[frameKey];
+
+ setSheet({
+ ...sheet,
+ frames: clonedFramesObject,
+ animations: Object.keys(sheet.animations).reduce(
+ (object, animationKey) => ({
+ ...object,
+ [animationKey]: sheet.animations[animationKey].filter(
+ (frame) => frameKey !== frame,
+ ),
+ }),
+ {},
+ ),
+ });
+ };
+
+ const frames = Object.keys(sheet.frames).map<
+ [string, SpriteSheetFrame, string]
+ >((frameKey) => [
+ frameKey,
+ sheet.frames[frameKey],
+ getColorFromSeed(frameKey),
+ ]);
+
+ return (
+
+
frames:
+
+
+ x
+ y
+ w
+ h
+
+
+ {frames.map(([frameKey, { frame }, color]) => (
+
+
+ {frame.x}
+ {frame.y}
+ {frame.w}
+ {frame.h}
+
+
+ ))}
+
+
+ );
+};
diff --git a/app/client/src/modules/sprite-sheets/components/frames/frames.module.scss b/app/client/src/modules/sprite-sheets/components/frames/frames.module.scss
new file mode 100644
index 0000000..fee06c1
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/frames/frames.module.scss
@@ -0,0 +1,30 @@
+@import "../../../../shared/styles/consts.module";
+@import "../../../../shared/styles/mixins.module";
+
+.frames {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ .frame {
+ display: flex;
+ justify-content: space-between;
+ padding: 0 2rem;
+
+ label {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ span, label {
+ width: 100%;
+ }
+ span {
+ text-align: center;
+ }
+ .action {
+ width: 40rem;
+ padding: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/client/src/modules/sprite-sheets/components/frames/index.ts b/app/client/src/modules/sprite-sheets/components/frames/index.ts
new file mode 100644
index 0000000..f6d158b
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/frames/index.ts
@@ -0,0 +1 @@
+export * from "./frames.component";
diff --git a/app/client/src/modules/sprite-sheets/components/index.ts b/app/client/src/modules/sprite-sheets/components/index.ts
new file mode 100644
index 0000000..d5f7cc2
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/index.ts
@@ -0,0 +1 @@
+export * from "./sprite-sheet";
diff --git a/app/client/src/modules/sprite-sheets/components/preview/preview.component.tsx b/app/client/src/modules/sprite-sheets/components/preview/preview.component.tsx
new file mode 100644
index 0000000..521b5f1
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/preview/preview.component.tsx
@@ -0,0 +1,184 @@
+import React, { useEffect, useState } from "react";
+import styles from "./preview.module.scss";
+import {
+ SpriteSheet,
+ SpriteSheetFrame,
+ SpriteSheetPoint,
+ SpriteSheetRectangle,
+} from "shared/types";
+import { getColorFromSeed } from "shared/utils";
+
+type Props = {
+ sheet: SpriteSheet;
+ sprite: string;
+
+ onChangePreviewRectangle: (rectangle: SpriteSheetRectangle) => void;
+};
+
+const PREVIEW_PADDING = 10;
+
+export const PreviewComponent: React.FC = ({
+ sheet,
+ sprite,
+ onChangePreviewRectangle,
+}) => {
+ const [previewScale, setPreviewScale] = useState(6);
+
+ const [firstPoint, setFirstPoint] = useState({
+ x: 0,
+ y: 0,
+ });
+ const [secondPoint, setSecondPoint] = useState({
+ x: 0,
+ y: 0,
+ });
+ const [previewPoint, setPreviewPoint] = useState({
+ x: 0,
+ y: 0,
+ });
+
+ const previewRectangle: SpriteSheetRectangle = {
+ x: firstPoint.x,
+ y: firstPoint.y,
+ w: secondPoint.x - firstPoint.x,
+ h: secondPoint.y - firstPoint.y,
+ };
+
+ useEffect(() => {
+ onChangePreviewRectangle(previewRectangle);
+ }, [firstPoint, secondPoint]);
+
+ const getPoint = (event) => {
+ const clientRect = event.target.getBoundingClientRect();
+ return {
+ x: Math.round(
+ (event.clientX - clientRect.left - PREVIEW_PADDING) / previewScale,
+ ),
+ y: Math.round(
+ (event.clientY - clientRect.top - PREVIEW_PADDING) / previewScale,
+ ),
+ };
+ };
+
+ const onMouseMoveSprite = (event) => {
+ setPreviewPoint(getPoint(event));
+ };
+ const onClickSprite = (event) => {
+ setFirstPoint(getPoint(event));
+ };
+ const onContextMenu = (event) => {
+ event.preventDefault();
+ setSecondPoint(getPoint(event));
+ };
+
+ const onAddScale = () => {
+ setPreviewScale((scale) => scale + 1);
+ };
+
+ const onSubScale = () => {
+ setPreviewScale((scale) => (scale === 0 ? 0 : scale - 1));
+ };
+
+ const frames = Object.keys(sheet.frames).map<
+ [string, SpriteSheetFrame, string]
+ >((frameKey) => [
+ frameKey,
+ sheet.frames[frameKey],
+ getColorFromSeed(frameKey),
+ ]);
+
+ const imageSize = sheet.meta.size;
+
+ const clickPointPreview = {
+ x: firstPoint.x * previewScale,
+ y: firstPoint.y * previewScale,
+ };
+
+ return (
+
+
+
+
+ {frames.map(([frameKey, { frame }, color]) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ x:{previewPoint.x}
+ y:{previewPoint.y}
+
+
+ x:{previewRectangle.x}
+ y:{previewRectangle.y}
+ -
+ w:{previewRectangle.w}
+ h:{previewRectangle.h}
+
+
+
+ );
+};
diff --git a/app/client/src/modules/sprite-sheets/components/preview/preview.module.scss b/app/client/src/modules/sprite-sheets/components/preview/preview.module.scss
new file mode 100644
index 0000000..e72d950
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/preview/preview.module.scss
@@ -0,0 +1,37 @@
+@import "../../../../shared/styles/consts.module";
+@import "../../../../shared/styles/mixins.module";
+
+.preview {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ hr {
+ width: 100%;
+ }
+ .imageWrap {
+ border: 1px solid;
+ //overflow: hidden;
+ }
+ .image {
+ position: relative;
+ cursor: cell;
+ }
+ .coords {
+ display: flex;
+ flex-direction: column;
+ .scale, .absolute, .relative {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ padding: 0 1rem;
+ }
+ .relative {
+ background-color: #b4b0ad;
+ color: black;
+ font-weight: bold;
+ }
+ }
+ img {
+ }
+ //transform: translate(50%, 50%) scale(2);
+}
\ No newline at end of file
diff --git a/app/client/src/modules/sprite-sheets/components/sprite-sheet/index.ts b/app/client/src/modules/sprite-sheets/components/sprite-sheet/index.ts
new file mode 100644
index 0000000..1e46f7b
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/sprite-sheet/index.ts
@@ -0,0 +1 @@
+export * from "./sprite-sheet.component";
diff --git a/app/client/src/modules/sprite-sheets/components/sprite-sheet/sprite-sheet.component.tsx b/app/client/src/modules/sprite-sheets/components/sprite-sheet/sprite-sheet.component.tsx
new file mode 100644
index 0000000..71302ff
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/sprite-sheet/sprite-sheet.component.tsx
@@ -0,0 +1,133 @@
+import React, { ChangeEvent, useEffect, useRef, useState } from "react";
+import { SpriteSheet, SpriteSheetRectangle } from "shared/types";
+import { useSpriteSheets } from "shared/hooks";
+import styles from "./sprite-sheet.module.scss";
+import { FramesComponent } from "../frames";
+import { AnimationsComponent } from "../animations";
+import { PreviewComponent } from "modules/sprite-sheets/components/preview/preview.component";
+
+type Props = {
+ id: string;
+ onDelete: () => void;
+};
+
+export const SpriteSheetComponent: React.FC = ({ id, onDelete }) => {
+ const { getSprite, getSheet, updateSheet, uploadSheet, uploadSprite } =
+ useSpriteSheets();
+ const uploadSpriteRef = useRef();
+ const uploadSheetRef = useRef();
+
+ const [sprite, setSprite] = useState();
+ const [sheet, setSheet] = useState();
+
+ const [previewRectangle, setPreviewRectangle] =
+ useState({ x: 0, y: 0, w: 0, h: 0 });
+
+ const $reload = () => {
+ getSprite(id).then(setSprite);
+ getSheet(id).then(setSheet);
+ };
+
+ useEffect(() => {
+ if (!id) {
+ setSprite(null);
+ setSheet(null);
+ return;
+ }
+
+ $reload();
+ }, [id]);
+
+ const onUploadSprite = async (event: ChangeEvent) => {
+ const { files } = event.target;
+
+ await uploadSprite(id, files);
+ //@ts-ignore
+ uploadSpriteRef.current.value = "";
+ $reload();
+ };
+
+ const onUploadSheet = async (event: ChangeEvent) => {
+ const { files } = event.target;
+
+ await uploadSheet(id, files);
+ //@ts-ignore
+ uploadSheetRef.current.value = "";
+ $reload();
+ };
+
+ useEffect(() => {
+ if (!sheet) return;
+ updateSheet(id, sheet);
+ }, [sheet]);
+
+ const setAnimations = (animations: Record) => {
+ setSheet({
+ ...sheet,
+ animations,
+ });
+ };
+
+ if (!sheet || !sprite) return ;
+
+ const frames = Object.keys(sheet.frames);
+ const imageSize = sheet.meta.size;
+
+ return (
+
+ );
+};
+
+//
diff --git a/app/client/src/modules/sprite-sheets/components/sprite-sheet/sprite-sheet.module.scss b/app/client/src/modules/sprite-sheets/components/sprite-sheet/sprite-sheet.module.scss
new file mode 100644
index 0000000..c0d1b24
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/components/sprite-sheet/sprite-sheet.module.scss
@@ -0,0 +1,53 @@
+@import "../../../../shared/styles/consts.module";
+@import "../../../../shared/styles/mixins.module";
+
+.container {
+
+ .header {
+ .actions {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ .delete {
+ margin-left: auto;
+ }
+ }
+ .sub {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ }
+ }
+ .frames {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ .frame {
+ display: flex;
+ justify-content: space-between;
+ padding: 0 2rem;
+
+ label {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ span, label {
+ width: 100%;
+ }
+ span {
+ text-align: center;
+ }
+ .action {
+ width: 40rem;
+ padding: 0;
+ }
+ }
+ }
+
+ .footer {
+ display: flex;
+ justify-content: end;
+ }
+}
\ No newline at end of file
diff --git a/app/client/src/modules/sprite-sheets/index.ts b/app/client/src/modules/sprite-sheets/index.ts
new file mode 100644
index 0000000..4252d8a
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/index.ts
@@ -0,0 +1,2 @@
+export * from "./sprite-sheets.component";
+export * from "./components";
diff --git a/app/client/src/modules/sprite-sheets/sprite-sheets.component.tsx b/app/client/src/modules/sprite-sheets/sprite-sheets.component.tsx
new file mode 100644
index 0000000..5aa16b4
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/sprite-sheets.component.tsx
@@ -0,0 +1,69 @@
+import React, { FormEvent, useEffect, useState } from "react";
+import { ContainerComponent } from "shared/components";
+import { useSpriteSheets } from "shared/hooks";
+import { SpriteSheetComponent } from "./components";
+import styles from "./sprite-sheets.module.scss";
+
+export const SpriteSheetsComponent: React.FC = () => {
+ const { getList, create, remove } = useSpriteSheets();
+
+ const [spriteSheets, setSpriteSheets] = useState([]);
+ const [selectedSpriteSheet, setSelectedSpriteSheet] = useState("");
+
+ const reload = () => getList().then(setSpriteSheets);
+
+ useEffect(() => {
+ reload();
+ }, []);
+
+ const onSelectSpriteSheet = (event) => {
+ setSelectedSpriteSheet(event.target.value);
+ };
+
+ const onSubmitAddSpriteSheet = async (event: FormEvent) => {
+ event.preventDefault();
+
+ const data = new FormData(event.target as unknown as HTMLFormElement);
+ const name = data.get("name") as string;
+
+ if (!name.length || spriteSheets.includes(name)) return;
+
+ await create(name);
+ //@ts-ignore
+ event.target.reset();
+ await reload();
+ setSelectedSpriteSheet(name);
+ };
+
+ const onDelete = async () => {
+ await remove(selectedSpriteSheet);
+ await reload();
+ setSelectedSpriteSheet("");
+ };
+
+ return (
+
+
+
+
+
+
+
+ {selectedSpriteSheet ? (
+
+ ) : null}
+
+
+
+ );
+};
diff --git a/app/client/src/modules/sprite-sheets/sprite-sheets.module.scss b/app/client/src/modules/sprite-sheets/sprite-sheets.module.scss
new file mode 100644
index 0000000..aa7fea4
--- /dev/null
+++ b/app/client/src/modules/sprite-sheets/sprite-sheets.module.scss
@@ -0,0 +1,17 @@
+@import "../../shared/styles/consts.module";
+@import "../../shared/styles/mixins.module";
+
+.container {
+ .actions {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+
+ .new {
+
+ }
+ }
+ .content {
+ padding: 1rem 2rem;
+ }
+}
\ No newline at end of file
diff --git a/app/client/src/shared/hooks/index.ts b/app/client/src/shared/hooks/index.ts
index cf1319c..8afd31c 100644
--- a/app/client/src/shared/hooks/index.ts
+++ b/app/client/src/shared/hooks/index.ts
@@ -1 +1,2 @@
-export * from "./useApi";
+export * from "./useData";
+export * from "./useSpriteSheets";
diff --git a/app/client/src/shared/hooks/useApi.ts b/app/client/src/shared/hooks/useApi.ts
deleted file mode 100644
index 92127d7..0000000
--- a/app/client/src/shared/hooks/useApi.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-import Cookies from "js-cookie";
-import { redirectToFallbackRedirectUrl } from "shared/utils/urls.utils";
-
-export const useApi = () => {
- // const getTicketId = () => new URLSearchParams(location.hash).get("#ticketId");
- //
- // const clearSessionCookies = () => {
- // Cookies.remove("sessionId");
- // Cookies.remove("refreshToken");
- // };
- //
- // const setFallbackRedirectUrl = (redirectUrl: string) => {
- // const { href, search } = new URL(redirectUrl);
- // localStorage.setItem("fallbackRedirectUrl", href.replace(search, ""));
- // };
- //
- // const login = (email: string, password: string, captchaId: string) =>
- // new Promise((resolve, reject) => {
- // fetch("/api/v2/account/login", {
- // method: "POST",
- // body: JSON.stringify({
- // ticketId: getTicketId(),
- //
- // email,
- // password,
- // captchaId,
- // }),
- // })
- // .then((data) => data.json())
- // .then(({ status, data }) => {
- // if (status === 410) return redirectToFallbackRedirectUrl();
- // if (status !== 200) return reject({ status });
- // Cookies.set("sessionId", data.sessionId, {
- // expires: 7,
- // sameSite: "strict",
- // });
- // Cookies.set("refreshToken", data.refreshToken, {
- // expires: 7,
- // sameSite: "strict",
- // });
- // setFallbackRedirectUrl(data.redirectUrl);
- // resolve(data);
- // })
- // .catch(() => reject({ status: 600 }));
- // });
- //
- // const refreshSession = () =>
- // new Promise((resolve, reject) => {
- // const sessionId = Cookies.get("sessionId");
- // const refreshToken = Cookies.get("refreshToken");
- //
- // if (!sessionId || !refreshToken) return reject();
- //
- // fetch("/api/v2/account/refresh-session", {
- // method: "POST",
- // body: JSON.stringify({
- // ticketId: getTicketId(),
- //
- // sessionId,
- // refreshToken,
- // }),
- // })
- // .then((data) => data.json())
- // .then(({ status, data }) => {
- // if (status === 410) return redirectToFallbackRedirectUrl();
- // if (status === 200) {
- // Cookies.set("sessionId", sessionId, {
- // expires: 7,
- // sameSite: "strict",
- // });
- // Cookies.set("refreshToken", data.refreshToken, {
- // expires: 7,
- // sameSite: "strict",
- // });
- // setFallbackRedirectUrl(data.redirectUrl);
- // return resolve(data);
- // }
- // clearSessionCookies();
- // reject({ status });
- // })
- // .catch(() => reject({ status: 600 }));
- // });
- //
- // const register = (
- // email: string,
- // username: string,
- // password: string,
- // rePassword: string,
- // captchaId: string,
- // ) =>
- // new Promise((resolve, reject) => {
- // fetch("/api/v2/account/register", {
- // method: "POST",
- // body: JSON.stringify({
- // email,
- // username,
- // password,
- // rePassword,
- // captchaId,
- // }),
- // })
- // .then((data) => data.json())
- // .then(({ status }) => (status === 200 ? resolve() : reject({ status })))
- // .catch(() => reject({ status: 600 }));
- // });
- //
- // const logout = () =>
- // new Promise((resolve) => {
- // const sessionId = Cookies.get("sessionId");
- // const refreshToken = Cookies.get("refreshToken");
- //
- // clearSessionCookies();
- //
- // if (!sessionId || !refreshToken) return resolve({});
- //
- // fetch("/api/v2/account/logout", {
- // method: "POST",
- // body: JSON.stringify({
- // sessionId,
- // refreshToken,
- // }),
- // })
- // .then((data) => data.json())
- // .then(resolve)
- // .catch(resolve);
- // });
- //
- // const verify = (id: string, token: string) =>
- // new Promise((resolve, reject) => {
- // if (!id || !token) return reject({ status: 700 });
- //
- // fetch(`/api/v2/account/verify?id=${id}&token=${token}`)
- // .then((data) => data.json())
- // .then(({ status }) => (status === 200 ? resolve() : reject({ status })))
- // .catch(() => reject({ status: 600 }));
- // });
-
- return {
- // login,
- // refreshSession,
- // register,
- // logout,
- // clearSessionCookies,
- // getTicketId,
- // verify,
- };
-};
diff --git a/app/client/src/shared/hooks/useData.ts b/app/client/src/shared/hooks/useData.ts
new file mode 100644
index 0000000..a20fe2c
--- /dev/null
+++ b/app/client/src/shared/hooks/useData.ts
@@ -0,0 +1,65 @@
+// import Cookies from "js-cookie";
+import { File } from "shared/types";
+
+export const useData = () => {
+ const readDirectory = (path: string): Promise =>
+ new Promise((resolve, reject) => {
+ fetch(`/api/data/read-dir?path=${path}`)
+ .then((data) => data.json())
+ .then(({ status, data }) => {
+ if (status !== 200) return reject({ status });
+ resolve(data.list);
+ })
+ .catch(() => reject({ status: 600 }));
+ });
+
+ const getFile = (path: string): Promise =>
+ new Promise((resolve, reject) => {
+ fetch(`/api/data/read-file?path=${path}`)
+ .then(resolve)
+ .catch(() => reject({ status: 600 }));
+ });
+
+ const addFiles = (path: string, files: FileList): Promise => {
+ const formData = new FormData();
+ for (let i = 0; i < files.length; i++) formData.append("files", files[i]);
+
+ return new Promise((resolve, reject) => {
+ fetch(`/api/data/write-file?path=${path}`, {
+ method: "POST",
+ body: formData,
+ })
+ .then(resolve)
+ .catch(() => reject({ status: 600 }));
+ });
+ };
+
+ const remove = (path: string): Promise => {
+ return new Promise((resolve, reject) => {
+ fetch(`/api/data/rm-file?path=${path}`, {
+ method: "GET",
+ })
+ .then(resolve)
+ .catch(() => reject({ status: 600 }));
+ });
+ };
+
+ const createDirectory = (path: string): Promise => {
+ return new Promise((resolve, reject) => {
+ fetch(`/api/data/mk-dir?path=${path}`, {
+ method: "GET",
+ })
+ .then(resolve)
+ .catch(() => reject({ status: 600 }));
+ });
+ };
+
+ return {
+ readDirectory,
+ createDirectory,
+
+ getFile,
+ addFiles,
+ remove,
+ };
+};
diff --git a/app/client/src/shared/hooks/useSpriteSheets.ts b/app/client/src/shared/hooks/useSpriteSheets.ts
new file mode 100644
index 0000000..b343b9c
--- /dev/null
+++ b/app/client/src/shared/hooks/useSpriteSheets.ts
@@ -0,0 +1,101 @@
+// import Cookies from "js-cookie";
+
+import { getBase64FromBody } from "shared/utils";
+import { SpriteSheet } from "shared/types";
+
+export const useSpriteSheets = () => {
+ const getList = (): Promise =>
+ new Promise((resolve, reject) => {
+ fetch(`/api/sprite-sheets/list`)
+ .then((data) => data.json())
+ .then(({ status, data }) => {
+ if (status !== 200) return reject({ status });
+ resolve(data.list);
+ })
+ .catch(() => reject({ status: 600 }));
+ });
+
+ const getSprite = (id: string): Promise =>
+ new Promise((resolve, reject) => {
+ fetch(`/api/sprite-sheets/sprite?id=${id}`)
+ .then(getBase64FromBody)
+ .then(resolve)
+ .catch(() => reject({ status: 600 }));
+ });
+
+ const getSheet = (id: string): Promise =>
+ new Promise((resolve, reject) => {
+ fetch(`/api/sprite-sheets/sheet?id=${id}`)
+ .then((data) => data.json())
+ .then(resolve)
+ .catch(() => reject({ status: 600 }));
+ });
+
+ const updateSheet = (id: string, sheet: SpriteSheet): Promise =>
+ new Promise((resolve, reject) => {
+ fetch(`/api/sprite-sheets/sheet?id=${id}`, {
+ method: "post",
+ body: JSON.stringify(sheet),
+ })
+ .then(() => resolve())
+ .catch(() => reject({ status: 600 }));
+ });
+
+ const uploadSheet = async (id: string, files: FileList) => {
+ const formData = new FormData();
+ formData.append("files", files[0]);
+
+ return new Promise((resolve, reject) => {
+ fetch(`/api/sprite-sheets/sheet?id=${id}`, {
+ method: "put",
+ body: formData,
+ })
+ .then(resolve)
+ .catch(() => reject({ status: 600 }));
+ });
+ };
+
+ const uploadSprite = async (id: string, files: FileList) => {
+ const formData = new FormData();
+ formData.append("files", files[0]);
+
+ return new Promise((resolve, reject) => {
+ fetch(`/api/sprite-sheets/sprite?id=${id}`, {
+ method: "post",
+ body: formData,
+ })
+ .then(resolve)
+ .catch(() => reject({ status: 600 }));
+ });
+ };
+
+ const remove = async (id: string) =>
+ new Promise((resolve, reject) => {
+ fetch(`/api/sprite-sheets/delete?id=${id}`)
+ .then(resolve)
+ .catch(() => reject({ status: 600 }));
+ });
+
+ const create = async (id: string) =>
+ new Promise((resolve, reject) => {
+ fetch(`/api/sprite-sheets/create?id=${id}`)
+ .then(resolve)
+ .catch(() => reject({ status: 600 }));
+ });
+
+ return {
+ getList,
+
+ getSprite,
+ getSheet,
+
+ updateSheet,
+
+ uploadSheet,
+ uploadSprite,
+
+ remove,
+
+ create,
+ };
+};
diff --git a/app/client/src/shared/types/api.types.ts b/app/client/src/shared/types/api.types.ts
new file mode 100644
index 0000000..5a0b43b
--- /dev/null
+++ b/app/client/src/shared/types/api.types.ts
@@ -0,0 +1,5 @@
+export type File = {
+ name: string;
+ isFile: boolean;
+ isDirectory: boolean;
+};
diff --git a/app/client/src/shared/types/index.ts b/app/client/src/shared/types/index.ts
new file mode 100644
index 0000000..bf7c80a
--- /dev/null
+++ b/app/client/src/shared/types/index.ts
@@ -0,0 +1,2 @@
+export * from "./api.types";
+export * from "./sprite-sheet.types";
diff --git a/app/client/src/shared/types/sprite-sheet.types.ts b/app/client/src/shared/types/sprite-sheet.types.ts
new file mode 100644
index 0000000..291d38b
--- /dev/null
+++ b/app/client/src/shared/types/sprite-sheet.types.ts
@@ -0,0 +1,30 @@
+export type SpriteSheetSize = {
+ w: number;
+ h: number;
+};
+
+export type SpriteSheetPoint = {
+ x: number;
+ y: number;
+};
+
+export type SpriteSheetRectangle = SpriteSheetPoint & SpriteSheetSize;
+
+export type SpriteSheetFrame = {
+ frame: SpriteSheetRectangle;
+ sourceSize: SpriteSheetSize;
+ anchor: SpriteSheetPoint;
+};
+
+export type SpriteSheetMeta = {
+ format: string;
+ image: string;
+ size: SpriteSheetSize;
+ scale: number;
+};
+
+export type SpriteSheet = {
+ frames: Record;
+ animations: Record;
+ meta: SpriteSheetMeta;
+};
diff --git a/app/client/src/shared/utils/color.utils.ts b/app/client/src/shared/utils/color.utils.ts
new file mode 100644
index 0000000..975616e
--- /dev/null
+++ b/app/client/src/shared/utils/color.utils.ts
@@ -0,0 +1,16 @@
+import { getStringHash } from "./hash.utils";
+
+export const getColorFromHash = (hash: number): string => {
+ // Convert the hash into a valid hex color string
+ const color =
+ ((hash >> 24) & 0xff).toString(16) +
+ ((hash >> 16) & 0xff).toString(16) +
+ ((hash >> 8) & 0xff).toString(16) +
+ (hash & 0xff).toString(16);
+
+ // Ensure the color is 6 digits long by padding with zeros
+ return "#" + color.padStart(6, "0").substring(0, 6);
+};
+
+export const getColorFromSeed = (seed: string): string =>
+ getColorFromHash(getStringHash(seed));
diff --git a/app/client/src/shared/utils/hash.utils.ts b/app/client/src/shared/utils/hash.utils.ts
new file mode 100644
index 0000000..afb5f79
--- /dev/null
+++ b/app/client/src/shared/utils/hash.utils.ts
@@ -0,0 +1,7 @@
+export const getStringHash = (text: string): number => {
+ let hash = 0;
+ for (let i = 0; i < text.length; i++) {
+ hash = text.charCodeAt(i) + ((hash << 5) - hash);
+ }
+ return hash;
+};
diff --git a/app/client/src/shared/utils/images.utils.ts b/app/client/src/shared/utils/images.utils.ts
new file mode 100644
index 0000000..0bc6f2c
--- /dev/null
+++ b/app/client/src/shared/utils/images.utils.ts
@@ -0,0 +1,9 @@
+export const getBase64FromBody = async (body: Body): Promise => {
+ const reader = new FileReader();
+ reader.readAsDataURL(await body.blob());
+ return new Promise((resolve) => {
+ reader.onloadend = () => {
+ resolve(reader.result as string);
+ };
+ });
+};
diff --git a/app/client/src/shared/utils/index.ts b/app/client/src/shared/utils/index.ts
index 7a6e948..94d013f 100644
--- a/app/client/src/shared/utils/index.ts
+++ b/app/client/src/shared/utils/index.ts
@@ -1,2 +1,5 @@
export * from "./class-name.utils";
export * from "./environment.utils";
+export * from "./images.utils";
+export * from "./hash.utils";
+export * from "./color.utils";
diff --git a/app/client/yarn.lock b/app/client/yarn.lock
index a64aea5..41d3d21 100644
--- a/app/client/yarn.lock
+++ b/app/client/yarn.lock
@@ -965,3 +965,8 @@ yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
+yaml@^2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.0.tgz#c6165a721cf8000e91c36490a41d7be25176cf5d"
+ integrity sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==
diff --git a/app/server/.gitignore b/app/server/.gitignore
index 23dfcb0..85f187e 100644
--- a/app/server/.gitignore
+++ b/app/server/.gitignore
@@ -1,3 +1,4 @@
/build
config.yml
-index.html
\ No newline at end of file
+index.html
+/data
\ No newline at end of file
diff --git a/app/server/deno.json b/app/server/deno.json
index 6c22973..ec1cbe4 100644
--- a/app/server/deno.json
+++ b/app/server/deno.json
@@ -22,7 +22,7 @@
"loadenv": "https://deno.land/x/loadenv@v1.0.1/mod.ts",
"yaml": "npm:yaml@2.4.2",
-
- "open": "https://deno.land/x/open@v0.0.6/index.ts"
+ "open": "https://deno.land/x/open@v0.0.6/index.ts",
+ "imagescript": "https://deno.land/x/imagescript@1.3.0/mod.ts"
}
}
diff --git a/app/server/deno.lock b/app/server/deno.lock
index edf4381..2dcb452 100644
--- a/app/server/deno.lock
+++ b/app/server/deno.lock
@@ -20,6 +20,8 @@
"https://deno.land/std@0.106.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853",
"https://deno.land/std@0.106.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b",
"https://deno.land/std@0.106.0/path/posix.ts": "b81974c768d298f8dcd2c720229639b3803ca4a241fa9a355c762fa2bc5ef0c1",
+ "https://deno.land/std@0.114.0/bytes/equals.ts": "69f55fdbd45c71f920c1a621e6c0865dc780cd8ae34e0f5e55a9497b70c31c1b",
+ "https://deno.land/std@0.114.0/bytes/mod.ts": "fedb80b8da2e7ad8dd251148e65f92a04c73d6c5a430b7d197dc39588c8dda6f",
"https://deno.land/std@0.173.0/encoding/base64.ts": "7de04c2f8aeeb41453b09b186480be90f2ff357613b988e99fabb91d2eeceba1",
"https://deno.land/std@0.211.0/dotenv/mod.ts": "bcfa9c102d5ce6218ec0c4a69aa0fc1009fc309a85448f98a8debaa75866a3d7",
"https://deno.land/std@0.211.0/dotenv/parse.ts": "ddcb04a8a7198918cfc52efbebb0d4fc51abfea913649912fcc1cf4c52bd81a2",
@@ -146,10 +148,69 @@
"https://deno.land/x/denomailer@1.6.0/config/mail/mod.ts": "a7fafa3386a45a585d7983d816b09ddc28b2f2b84097a614f1e38685b1f62868",
"https://deno.land/x/denomailer@1.6.0/deps.ts": "12bef188bb2a490fedc82ac1889f3d438e8a15887c423b045fee532b31a43102",
"https://deno.land/x/denomailer@1.6.0/mod.ts": "71a197dff098194ab53691abd3c9d22a276ef04e1382eb85f5632dbcb5a83bf3",
+ "https://deno.land/x/imagescript@1.2.15/ImageScript.js": "9ce10c37a0e4fe43699689088c24f386fd401874057932bd57079ac729205038",
+ "https://deno.land/x/imagescript@1.2.15/mod.ts": "18a6cb83c55e690c873505f6fe867364c678afb64934fe7aef593a6b92f79995",
+ "https://deno.land/x/imagescript@1.2.15/png/src/crc.mjs": "5cf50de181d61dd00e66a240d811018ba5070afa8bba302f393604404604de84",
+ "https://deno.land/x/imagescript@1.2.15/png/src/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
+ "https://deno.land/x/imagescript@1.2.15/png/src/png.mjs": "96ef0ceff1b5a6cd9304749e5f187b4ab238509fb5f9a8be8ee934240271ed8d",
+ "https://deno.land/x/imagescript@1.2.15/png/src/zlib.mjs": "9867dc3fab1d31b664f9344b0d7e977f493d9c912a76c760d012ed2b89f7061c",
+ "https://deno.land/x/imagescript@1.2.15/utils/buffer.js": "952cb1beb8827e50a493a5d1f29a4845e8c648789406d389dd51f51205ba02d8",
+ "https://deno.land/x/imagescript@1.2.15/utils/crc32.js": "573d6222b3605890714ebc374e687ec2aa3e9a949223ea199483e47ca4864f7d",
+ "https://deno.land/x/imagescript@1.2.15/utils/png.js": "fbed9117e0a70602645d70df9c103ff6e79c03e987bd5c1685dcb4200729b6de",
+ "https://deno.land/x/imagescript@1.2.15/utils/wasm/font.js": "9e75d842608c057045698d6a7cdf5ffd27241b5cdea0391c89a1917b31294524",
+ "https://deno.land/x/imagescript@1.2.15/utils/wasm/gif.js": "8b86f7b96486bb8ff50fbc7c7487f86cb5cef85e6acd71e1def78a1aa2f12e4f",
+ "https://deno.land/x/imagescript@1.2.15/utils/wasm/jpeg.js": "75295e2fcf96b4f7bb894b3844fdaa8140d63169d28b466b5d5be89d59a7b6e6",
+ "https://deno.land/x/imagescript@1.2.15/utils/wasm/png.js": "0659536a8dd8f892c8346e268b2754b4414fad0ec1e9794dfcde1ba1c804ee02",
+ "https://deno.land/x/imagescript@1.2.15/utils/wasm/svg.js": "f5c8a9d1977b51a7c07549ceb6bbbaca9497321a193f28b3dc229a42d91bcf14",
+ "https://deno.land/x/imagescript@1.2.15/utils/wasm/tiff.js": "c2d7bdaef094df25aae1752e75167f485e89275d76a1379e39d8949580b7af4f",
+ "https://deno.land/x/imagescript@1.2.15/utils/wasm/zlib.js": "749875f83abffe24d3b977475a0cbd5f9b52bee1fbdbef61ec183cbfc17805f6",
+ "https://deno.land/x/imagescript@1.2.15/v2/framebuffer.mjs": "add44ff184636659714b3c6d4b896f628545451abffbc30b5bcc2e8d9a73d012",
+ "https://deno.land/x/imagescript@1.2.15/v2/ops/blur.mjs": "80716f1ffab8a2aeb54a036f583bf51a2b9dd37e005adc000add803df8e8a12f",
+ "https://deno.land/x/imagescript@1.2.15/v2/ops/color.mjs": "5e72cdcbf97dc939a2795223f01e3cb0544c0c56b03ea2aa026050df58348814",
+ "https://deno.land/x/imagescript@1.2.15/v2/ops/crop.mjs": "69431fa6f687fd9f0c31eff0ec27d7ac925275005e53a37f0c3fab4cc4d9a9ea",
+ "https://deno.land/x/imagescript@1.2.15/v2/ops/fill.mjs": "cf1b9488314753fbc9ebf03410ac74c2a34ea5a69fb6892cd6e8366cd1930d93",
+ "https://deno.land/x/imagescript@1.2.15/v2/ops/flip.mjs": "825a34a66567dcf15e76a719f1bf2f66fb106503cd69942292b1b0ae05c5718e",
+ "https://deno.land/x/imagescript@1.2.15/v2/ops/index.mjs": "423ba687119be2bba8cec72890577d3afa3621b6b8108912242fe937a183f2aa",
+ "https://deno.land/x/imagescript@1.2.15/v2/ops/iterator.mjs": "c2adf3d90ce00719a02c48c97634574176a3501ff026676259bd71aa8f5d69b9",
+ "https://deno.land/x/imagescript@1.2.15/v2/ops/overlay.mjs": "7e6e2c2ffd25006d52597ab8babc5f8f503d388a3fdf2fbc0eaea02799a020c9",
+ "https://deno.land/x/imagescript@1.2.15/v2/ops/resize.mjs": "814e78ebce8eaf8f1f918688db7b52a141405e06a36ed4b25d04413d69e7d17b",
+ "https://deno.land/x/imagescript@1.2.15/v2/ops/rotate.mjs": "a1b65616717bd2eed8db406affea3263b4674dada46b56441ef38167a187455d",
+ "https://deno.land/x/imagescript@1.2.15/v2/util/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
+ "https://deno.land/x/imagescript@1.3.0/ImageScript.js": "cf90773c966031edd781ed176c598f7ed495e7694cd9b86c986d2d97f783cca0",
+ "https://deno.land/x/imagescript@1.3.0/mod.ts": "18a6cb83c55e690c873505f6fe867364c678afb64934fe7aef593a6b92f79995",
+ "https://deno.land/x/imagescript@1.3.0/png/src/crc.mjs": "5cf50de181d61dd00e66a240d811018ba5070afa8bba302f393604404604de84",
+ "https://deno.land/x/imagescript@1.3.0/png/src/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
+ "https://deno.land/x/imagescript@1.3.0/png/src/png.mjs": "96ef0ceff1b5a6cd9304749e5f187b4ab238509fb5f9a8be8ee934240271ed8d",
+ "https://deno.land/x/imagescript@1.3.0/png/src/zlib.mjs": "9867dc3fab1d31b664f9344b0d7e977f493d9c912a76c760d012ed2b89f7061c",
+ "https://deno.land/x/imagescript@1.3.0/utils/buffer.js": "952cb1beb8827e50a493a5d1f29a4845e8c648789406d389dd51f51205ba02d8",
+ "https://deno.land/x/imagescript@1.3.0/utils/crc32.js": "573d6222b3605890714ebc374e687ec2aa3e9a949223ea199483e47ca4864f7d",
+ "https://deno.land/x/imagescript@1.3.0/utils/png.js": "fbed9117e0a70602645d70df9c103ff6e79c03e987bd5c1685dcb4200729b6de",
+ "https://deno.land/x/imagescript@1.3.0/utils/wasm/font.js": "9e75d842608c057045698d6a7cdf5ffd27241b5cdea0391c89a1917b31294524",
+ "https://deno.land/x/imagescript@1.3.0/utils/wasm/gif.js": "8b86f7b96486bb8ff50fbc7c7487f86cb5cef85e6acd71e1def78a1aa2f12e4f",
+ "https://deno.land/x/imagescript@1.3.0/utils/wasm/jpeg.js": "75295e2fcf96b4f7bb894b3844fdaa8140d63169d28b466b5d5be89d59a7b6e6",
+ "https://deno.land/x/imagescript@1.3.0/utils/wasm/png.js": "0659536a8dd8f892c8346e268b2754b4414fad0ec1e9794dfcde1ba1c804ee02",
+ "https://deno.land/x/imagescript@1.3.0/utils/wasm/svg.js": "f5c8a9d1977b51a7c07549ceb6bbbaca9497321a193f28b3dc229a42d91bcf14",
+ "https://deno.land/x/imagescript@1.3.0/utils/wasm/tiff.js": "c2d7bdaef094df25aae1752e75167f485e89275d76a1379e39d8949580b7af4f",
+ "https://deno.land/x/imagescript@1.3.0/utils/wasm/zlib.js": "749875f83abffe24d3b977475a0cbd5f9b52bee1fbdbef61ec183cbfc17805f6",
+ "https://deno.land/x/imagescript@1.3.0/v2/framebuffer.mjs": "add44ff184636659714b3c6d4b896f628545451abffbc30b5bcc2e8d9a73d012",
+ "https://deno.land/x/imagescript@1.3.0/v2/ops/blur.mjs": "80716f1ffab8a2aeb54a036f583bf51a2b9dd37e005adc000add803df8e8a12f",
+ "https://deno.land/x/imagescript@1.3.0/v2/ops/color.mjs": "5e72cdcbf97dc939a2795223f01e3cb0544c0c56b03ea2aa026050df58348814",
+ "https://deno.land/x/imagescript@1.3.0/v2/ops/crop.mjs": "69431fa6f687fd9f0c31eff0ec27d7ac925275005e53a37f0c3fab4cc4d9a9ea",
+ "https://deno.land/x/imagescript@1.3.0/v2/ops/fill.mjs": "cf1b9488314753fbc9ebf03410ac74c2a34ea5a69fb6892cd6e8366cd1930d93",
+ "https://deno.land/x/imagescript@1.3.0/v2/ops/flip.mjs": "825a34a66567dcf15e76a719f1bf2f66fb106503cd69942292b1b0ae05c5718e",
+ "https://deno.land/x/imagescript@1.3.0/v2/ops/index.mjs": "423ba687119be2bba8cec72890577d3afa3621b6b8108912242fe937a183f2aa",
+ "https://deno.land/x/imagescript@1.3.0/v2/ops/iterator.mjs": "c2adf3d90ce00719a02c48c97634574176a3501ff026676259bd71aa8f5d69b9",
+ "https://deno.land/x/imagescript@1.3.0/v2/ops/overlay.mjs": "7e6e2c2ffd25006d52597ab8babc5f8f503d388a3fdf2fbc0eaea02799a020c9",
+ "https://deno.land/x/imagescript@1.3.0/v2/ops/resize.mjs": "814e78ebce8eaf8f1f918688db7b52a141405e06a36ed4b25d04413d69e7d17b",
+ "https://deno.land/x/imagescript@1.3.0/v2/ops/rotate.mjs": "a1b65616717bd2eed8db406affea3263b4674dada46b56441ef38167a187455d",
+ "https://deno.land/x/imagescript@1.3.0/v2/util/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
"https://deno.land/x/is_docker@v2.0.0/mod.ts": "4c8753346f4afbb6c251d7984a609aa84055559cf713fba828939a5d39c95cd0",
"https://deno.land/x/is_wsl@v1.1.0/mod.ts": "30996b09376652df7a4d495320e918154906ab94325745c1399e13e658dca5da",
"https://deno.land/x/loadenv@v1.0.1/mod.ts": "287c1cc1b4bf024a1381610428d783854853aa36cca6f927e5f6dba81f350616",
"https://deno.land/x/loadenv@v1.0.1/src/main.ts": "216f20535f9e41be15679b0a48bba5eeb8dcf12b3ad1a1ab1971c5b0ae698e0a",
+ "https://deno.land/x/multiparser@0.114.0/deps.ts": "156a79ce9716a589a49a6486895828316115bc0f9c2107da6022fd5ca2e2ffc3",
+ "https://deno.land/x/multiparser@0.114.0/lib/multiParserV2.ts": "14f8dc877d74b489309764d6a1683f31454faa83c98e6b65cbcf375b727fc1d8",
+ "https://deno.land/x/multiparser@0.114.0/mod.ts": "1c83991a80f9f25b9876a726e96aedc02f09e72da280fcd74ddf1dbc105acfc6",
"https://deno.land/x/open@v0.0.6/index.ts": "c7484a7bf2628236f33bbe354520e651811faf1a7cbc3c3f80958ce81b4c42ef",
"https://deno.land/x/smtp@v0.7.0/code.ts": "f388fae4995b4d35d99fb6b8bfded522f5a3e7e7d63babdf318a059d6db43baf",
"https://deno.land/x/smtp@v0.7.0/deps.ts": "5e2a437e3ae35f0e83719fd2e707858dcb750c1111ff5bebc729522a1380b53d",
diff --git a/app/server/src/modules/api/data/_http/input.txt b/app/server/src/modules/api/data/_http/input.txt
new file mode 100644
index 0000000..f8e29a3
--- /dev/null
+++ b/app/server/src/modules/api/data/_http/input.txt
@@ -0,0 +1 @@
+Input text
\ No newline at end of file
diff --git a/app/server/src/modules/api/data/_http/mk-dir.http b/app/server/src/modules/api/data/_http/mk-dir.http
new file mode 100644
index 0000000..eb1dbdf
--- /dev/null
+++ b/app/server/src/modules/api/data/_http/mk-dir.http
@@ -0,0 +1,2 @@
+#
+GET http://localhost:2030/api/data/mk-dir?path=/furniture/example
diff --git a/app/server/src/modules/api/data/_http/read-dir.http b/app/server/src/modules/api/data/_http/read-dir.http
new file mode 100644
index 0000000..8602deb
--- /dev/null
+++ b/app/server/src/modules/api/data/_http/read-dir.http
@@ -0,0 +1,2 @@
+#
+GET http://localhost:2030/api/data/read-dir?path=/furniture/test
diff --git a/app/server/src/modules/api/data/_http/read-file.http b/app/server/src/modules/api/data/_http/read-file.http
new file mode 100644
index 0000000..756d769
--- /dev/null
+++ b/app/server/src/modules/api/data/_http/read-file.http
@@ -0,0 +1,2 @@
+#
+GET http://localhost:2030/api/data/read-file?path=/furniture/example/data.yml
\ No newline at end of file
diff --git a/app/server/src/modules/api/data/_http/sprite.png b/app/server/src/modules/api/data/_http/sprite.png
new file mode 100644
index 0000000..21c26a8
Binary files /dev/null and b/app/server/src/modules/api/data/_http/sprite.png differ
diff --git a/app/server/src/modules/api/data/_http/write-file.http b/app/server/src/modules/api/data/_http/write-file.http
new file mode 100644
index 0000000..836fda8
--- /dev/null
+++ b/app/server/src/modules/api/data/_http/write-file.http
@@ -0,0 +1,16 @@
+#
+POST http://localhost:2030/api/data/write-file?path=/test
+Content-Type: multipart/form-data; boundary=boundary
+
+--boundary
+Content-Disposition: form-data; name="first"; filename="input.txt"
+Content-Type: text/plain
+
+< ./input.txt
+
+--boundary
+Content-Disposition: form-data; name="second"; filename="sprite.png"
+Content-Type: image/png
+
+< ./sprite.png
+--boundary--
\ No newline at end of file
diff --git a/app/server/src/modules/api/data/main.ts b/app/server/src/modules/api/data/main.ts
new file mode 100644
index 0000000..b21f517
--- /dev/null
+++ b/app/server/src/modules/api/data/main.ts
@@ -0,0 +1,17 @@
+import { getPathRequestList } from "shared/utils/main.ts";
+import { getMkDirRequest } from "./mk-dir.request.ts";
+import { getReadDirRequest } from "./read-dir.request.ts";
+import { postWriteFileRequest } from "./write-file.request.ts";
+import { getReadFileRequest } from "./read-file.request.ts";
+import { getRmFileRequest } from "./rm-file.request.ts";
+
+export const dataRequestList = getPathRequestList({
+ requestList: [
+ getMkDirRequest,
+ getReadDirRequest,
+ postWriteFileRequest,
+ getReadFileRequest,
+ getRmFileRequest,
+ ],
+ pathname: "/data",
+});
diff --git a/app/server/src/modules/api/data/mk-dir.request.ts b/app/server/src/modules/api/data/mk-dir.request.ts
new file mode 100644
index 0000000..97fad0e
--- /dev/null
+++ b/app/server/src/modules/api/data/mk-dir.request.ts
@@ -0,0 +1,18 @@
+import { RequestType } from "shared/types/main.ts";
+import { RequestMethod } from "shared/enums/main.ts";
+import { System } from "system/main.ts";
+import { getPathFromUrl } from "shared/utils/main.ts";
+
+export const getMkDirRequest: RequestType = {
+ method: RequestMethod.GET,
+ pathname: "/mk-dir",
+ func: async (request, url) => {
+ try {
+ await System.data.mkdir(getPathFromUrl(url));
+ return Response.json({ status: 200 }, { status: 200 });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
diff --git a/app/server/src/modules/api/data/read-dir.request.ts b/app/server/src/modules/api/data/read-dir.request.ts
new file mode 100644
index 0000000..eed4696
--- /dev/null
+++ b/app/server/src/modules/api/data/read-dir.request.ts
@@ -0,0 +1,18 @@
+import { RequestType } from "shared/types/main.ts";
+import { RequestMethod } from "shared/enums/main.ts";
+import { getPathFromUrl } from "shared/utils/main.ts";
+import { System } from "system/main.ts";
+
+export const getReadDirRequest: RequestType = {
+ method: RequestMethod.GET,
+ pathname: "/read-dir",
+ func: async (request, url) => {
+ try {
+ const list = await System.data.readDir(getPathFromUrl(url));
+ return Response.json({ status: 200, data: { list } }, { status: 200 });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
diff --git a/app/server/src/modules/api/data/read-file.request.ts b/app/server/src/modules/api/data/read-file.request.ts
new file mode 100644
index 0000000..cfce6dc
--- /dev/null
+++ b/app/server/src/modules/api/data/read-file.request.ts
@@ -0,0 +1,25 @@
+import { RequestType } from "shared/types/main.ts";
+import { RequestMethod } from "shared/enums/main.ts";
+import { getContentType, getPathFromUrl } from "shared/utils/main.ts";
+import { System } from "system/main.ts";
+
+export const getReadFileRequest: RequestType = {
+ method: RequestMethod.GET,
+ pathname: "/read-file",
+ func: async (request, url) => {
+ try {
+ const path = getPathFromUrl(url);
+ const data = await System.data.readFile(path);
+
+ const headers = new Headers();
+ headers.append("Content-Type", getContentType(path));
+ return new Response(data, {
+ status: 200,
+ headers,
+ });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
diff --git a/app/server/src/modules/api/data/rm-file.request.ts b/app/server/src/modules/api/data/rm-file.request.ts
new file mode 100644
index 0000000..33358bd
--- /dev/null
+++ b/app/server/src/modules/api/data/rm-file.request.ts
@@ -0,0 +1,20 @@
+import { RequestType } from "shared/types/main.ts";
+import { RequestMethod } from "shared/enums/main.ts";
+import { getPathFromUrl } from "shared/utils/main.ts";
+import { System } from "system/main.ts";
+
+export const getRmFileRequest: RequestType = {
+ method: RequestMethod.GET,
+ pathname: "/rm-file",
+ func: async (request, url) => {
+ try {
+ const path = getPathFromUrl(url);
+ await System.data.remove(path);
+
+ return Response.json({ status: 200 }, { status: 200 });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
diff --git a/app/server/src/modules/api/data/write-file.request.ts b/app/server/src/modules/api/data/write-file.request.ts
new file mode 100644
index 0000000..58ff114
--- /dev/null
+++ b/app/server/src/modules/api/data/write-file.request.ts
@@ -0,0 +1,25 @@
+import { RequestType } from "shared/types/main.ts";
+import { RequestMethod } from "shared/enums/main.ts";
+import { getPathFromUrl } from "shared/utils/url.utils.ts";
+import { System } from "system/main.ts";
+
+export const postWriteFileRequest: RequestType = {
+ method: RequestMethod.POST,
+ pathname: "/write-file",
+ func: async (request, url) => {
+ try {
+ const formData = await request.formData();
+ const path = getPathFromUrl(url);
+ await System.data.mkdir(path);
+
+ for (const [, value] of formData.entries())
+ if (value instanceof File)
+ await System.data.writeFile(`${path}/${value.name}`, value.stream());
+
+ return Response.json({ status: 200 }, { status: 200 });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
diff --git a/app/server/src/modules/api/main.ts b/app/server/src/modules/api/main.ts
index ee92120..d96abdb 100644
--- a/app/server/src/modules/api/main.ts
+++ b/app/server/src/modules/api/main.ts
@@ -1,9 +1,11 @@
import { RequestType } from "shared/types/request.types.ts";
import { getPathRequestList } from "shared/utils/main.ts";
+import { dataRequestList } from "./data/main.ts";
+import { spriteSheetsRequestList } from "./sprite-sheets/main.ts";
import { versionRequest } from "./version.request.ts";
export const requestList: RequestType[] = getPathRequestList({
- requestList: [versionRequest],
+ requestList: [versionRequest, ...dataRequestList, ...spriteSheetsRequestList],
pathname: "/api",
});
diff --git a/app/server/src/modules/api/sprite-sheets/_http/create.http b/app/server/src/modules/api/sprite-sheets/_http/create.http
new file mode 100644
index 0000000..b4b2006
--- /dev/null
+++ b/app/server/src/modules/api/sprite-sheets/_http/create.http
@@ -0,0 +1,2 @@
+#
+GET http://localhost:2030/api/sprite-sheets/create?id=test
diff --git a/app/server/src/modules/api/sprite-sheets/_http/list.http b/app/server/src/modules/api/sprite-sheets/_http/list.http
new file mode 100644
index 0000000..10acb9f
--- /dev/null
+++ b/app/server/src/modules/api/sprite-sheets/_http/list.http
@@ -0,0 +1,2 @@
+#
+GET http://localhost:2030/api/sprite-sheets/list
diff --git a/app/server/src/modules/api/sprite-sheets/_http/sheet.http b/app/server/src/modules/api/sprite-sheets/_http/sheet.http
new file mode 100644
index 0000000..9f9dfd0
--- /dev/null
+++ b/app/server/src/modules/api/sprite-sheets/_http/sheet.http
@@ -0,0 +1,24 @@
+#
+GET http://localhost:2030/api/sprite-sheets/sheet?id=test
+
+###
+#
+POST http://localhost:2030/api/sprite-sheets/sheet?id=test
+Content-Type: application/json
+
+{
+ "test":"dsdf"
+}
+
+###
+
+#
+PUT http://localhost:2030/api/sprite-sheets/sheet?id=test
+Content-Type: multipart/form-data; boundary=boundary
+
+--boundary
+Content-Disposition: form-data; filename="sheet.json"
+Content-Type: application/json
+
+< ./sheet.json
+--boundary--
\ No newline at end of file
diff --git a/app/server/src/modules/api/sprite-sheets/_http/sheet.json b/app/server/src/modules/api/sprite-sheets/_http/sheet.json
new file mode 100644
index 0000000..d642f94
--- /dev/null
+++ b/app/server/src/modules/api/sprite-sheets/_http/sheet.json
@@ -0,0 +1 @@
+{"frames":{"furniture":{"frame":{"x":0,"y":0,"w":48,"h":50},"sourceSize":{"w":48,"h":50},"anchor":{"x":0,"y":0}},"furniture-icon":{"frame":{"x":48,"y":0,"w":24,"h":24},"sourceSize":{"w":24,"h":24},"anchor":{"x":0,"y":0}}},"animations":{},"meta":{"format":"RGBA8888","image":"sprite.png","size":{"w":72,"h":72},"scale":1}}
\ No newline at end of file
diff --git a/app/server/src/modules/api/sprite-sheets/_http/sprite.http b/app/server/src/modules/api/sprite-sheets/_http/sprite.http
new file mode 100644
index 0000000..440d6b6
--- /dev/null
+++ b/app/server/src/modules/api/sprite-sheets/_http/sprite.http
@@ -0,0 +1,15 @@
+#
+GET http://localhost:2030/api/sprite-sheets/sprite?id=test
+
+###
+
+#
+POST http://localhost:2030/api/sprite-sheets/sprite?id=test
+Content-Type: multipart/form-data; boundary=boundary
+
+--boundary
+Content-Disposition: form-data; filename="sprite.png"
+Content-Type: image/png
+
+< ./sprite.png
+--boundary--
\ No newline at end of file
diff --git a/app/server/src/modules/api/sprite-sheets/_http/sprite.png b/app/server/src/modules/api/sprite-sheets/_http/sprite.png
new file mode 100644
index 0000000..21c26a8
Binary files /dev/null and b/app/server/src/modules/api/sprite-sheets/_http/sprite.png differ
diff --git a/app/server/src/modules/api/sprite-sheets/create.request.ts b/app/server/src/modules/api/sprite-sheets/create.request.ts
new file mode 100644
index 0000000..2de862b
--- /dev/null
+++ b/app/server/src/modules/api/sprite-sheets/create.request.ts
@@ -0,0 +1,20 @@
+import { RequestType } from "shared/types/main.ts";
+import { RequestMethod } from "shared/enums/main.ts";
+import { System } from "system/main.ts";
+import { getSearchParams } from "shared/utils/main.ts";
+
+export const createRequest: RequestType = {
+ method: RequestMethod.GET,
+ pathname: "/create",
+ func: async (request, url) => {
+ try {
+ const id = getSearchParams(url).get("id")!;
+ await System.spriteSheets.create(id);
+
+ return Response.json({ status: 200 }, { status: 200 });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
diff --git a/app/server/src/modules/api/sprite-sheets/delete.request.ts b/app/server/src/modules/api/sprite-sheets/delete.request.ts
new file mode 100644
index 0000000..8d50793
--- /dev/null
+++ b/app/server/src/modules/api/sprite-sheets/delete.request.ts
@@ -0,0 +1,20 @@
+import { RequestType } from "shared/types/main.ts";
+import { RequestMethod } from "shared/enums/main.ts";
+import { System } from "system/main.ts";
+import { getSearchParams } from "shared/utils/main.ts";
+
+export const deleteRequest: RequestType = {
+ method: RequestMethod.GET,
+ pathname: "/delete",
+ func: async (request, url) => {
+ try {
+ const id = getSearchParams(url).get("id")!;
+ await System.spriteSheets.remove(id);
+
+ return Response.json({ status: 200 }, { status: 200 });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
diff --git a/app/server/src/modules/api/sprite-sheets/list.request.ts b/app/server/src/modules/api/sprite-sheets/list.request.ts
new file mode 100644
index 0000000..e4648bb
--- /dev/null
+++ b/app/server/src/modules/api/sprite-sheets/list.request.ts
@@ -0,0 +1,17 @@
+import { RequestType } from "shared/types/main.ts";
+import { RequestMethod } from "shared/enums/main.ts";
+import { System } from "system/main.ts";
+
+export const listRequest: RequestType = {
+ method: RequestMethod.GET,
+ pathname: "/list",
+ func: async (request, url) => {
+ try {
+ const list = await System.spriteSheets.getList();
+ return Response.json({ status: 200, data: { list } }, { status: 200 });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
diff --git a/app/server/src/modules/api/sprite-sheets/main.ts b/app/server/src/modules/api/sprite-sheets/main.ts
new file mode 100644
index 0000000..2e47b97
--- /dev/null
+++ b/app/server/src/modules/api/sprite-sheets/main.ts
@@ -0,0 +1,24 @@
+import { getPathRequestList } from "shared/utils/main.ts";
+import { listRequest } from "./list.request.ts";
+import { getSpriteRequest, postSpriteRequest } from "./sprite.request.ts";
+import {
+ getSheetRequest,
+ postSheetRequest,
+ putSheetRequest,
+} from "./sheet.request.ts";
+import { deleteRequest } from "./delete.request.ts";
+import { createRequest } from "./create.request.ts";
+
+export const spriteSheetsRequestList = getPathRequestList({
+ requestList: [
+ listRequest,
+ getSpriteRequest,
+ postSpriteRequest,
+ getSheetRequest,
+ postSheetRequest,
+ putSheetRequest,
+ deleteRequest,
+ createRequest,
+ ],
+ pathname: "/sprite-sheets",
+});
diff --git a/app/server/src/modules/api/sprite-sheets/sheet.request.ts b/app/server/src/modules/api/sprite-sheets/sheet.request.ts
new file mode 100644
index 0000000..1d4c6d6
--- /dev/null
+++ b/app/server/src/modules/api/sprite-sheets/sheet.request.ts
@@ -0,0 +1,62 @@
+import { RequestType } from "shared/types/main.ts";
+import { RequestMethod } from "shared/enums/main.ts";
+import { System } from "system/main.ts";
+import { getSearchParams } from "shared/utils/main.ts";
+
+export const getSheetRequest: RequestType = {
+ method: RequestMethod.GET,
+ pathname: "/sheet",
+ func: async (request, url) => {
+ try {
+ const id = getSearchParams(url).get("id")!;
+ const data = await System.spriteSheets.getSheet(id);
+
+ const headers = new Headers();
+ headers.append("Content-Type", "application/json");
+ return new Response(data, {
+ status: 200,
+ headers,
+ });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
+
+export const postSheetRequest: RequestType = {
+ method: RequestMethod.POST,
+ pathname: "/sheet",
+ func: async (request, url) => {
+ try {
+ const id = getSearchParams(url).get("id")!;
+ await System.spriteSheets.updateSheet(id, await request.json());
+
+ return Response.json({ status: 200 }, { status: 200 });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
+
+export const putSheetRequest: RequestType = {
+ method: RequestMethod.PUT,
+ pathname: "/sheet",
+ func: async (request, url) => {
+ try {
+ const id = getSearchParams(url).get("id")!;
+
+ const formData = await request.formData();
+ for (const [, value] of formData.entries()) {
+ await System.spriteSheets.updateRawSheet(id, value.stream());
+ break;
+ }
+
+ return Response.json({ status: 200 }, { status: 200 });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
diff --git a/app/server/src/modules/api/sprite-sheets/sprite.request.ts b/app/server/src/modules/api/sprite-sheets/sprite.request.ts
new file mode 100644
index 0000000..b70bc77
--- /dev/null
+++ b/app/server/src/modules/api/sprite-sheets/sprite.request.ts
@@ -0,0 +1,46 @@
+import { RequestType } from "shared/types/main.ts";
+import { RequestMethod } from "shared/enums/main.ts";
+import { System } from "system/main.ts";
+import { getSearchParams } from "shared/utils/main.ts";
+
+export const getSpriteRequest: RequestType = {
+ method: RequestMethod.GET,
+ pathname: "/sprite",
+ func: async (request, url) => {
+ try {
+ const id = getSearchParams(url).get("id")!;
+ const data = await System.spriteSheets.getSprite(id);
+
+ const headers = new Headers();
+ headers.append("Content-Type", "image/png");
+ return new Response(data, {
+ status: 200,
+ headers,
+ });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
+
+export const postSpriteRequest: RequestType = {
+ method: RequestMethod.POST,
+ pathname: "/sprite",
+ func: async (request, url) => {
+ try {
+ const id = getSearchParams(url).get("id")!;
+
+ const formData = await request.formData();
+ for (const [, value] of formData.entries()) {
+ await System.spriteSheets.updateRawSprite(id, value.stream());
+ break;
+ }
+
+ return Response.json({ status: 200 }, { status: 200 });
+ } catch (e) {
+ console.error(e);
+ return Response.json({ status: 500 }, { status: 500 });
+ }
+ },
+};
diff --git a/app/server/src/modules/api/version.http b/app/server/src/modules/api/version.http
index 11a3921..c3cfc5f 100644
--- a/app/server/src/modules/api/version.http
+++ b/app/server/src/modules/api/version.http
@@ -1,2 +1,2 @@
# Version
-GET http://localhost:2024/api/v2/version
+GET http://localhost:2030/api/version
diff --git a/app/server/src/modules/api/version.request.ts b/app/server/src/modules/api/version.request.ts
index b4bed8e..be0ce94 100644
--- a/app/server/src/modules/api/version.request.ts
+++ b/app/server/src/modules/api/version.request.ts
@@ -1,10 +1,19 @@
import { RequestType } from "shared/types/main.ts";
import { RequestMethod } from "shared/enums/main.ts";
+import { System } from "system/main.ts";
export const versionRequest: RequestType = {
method: RequestMethod.GET,
pathname: "/version",
func: (request, url) => {
- return Response.json({}, { status: 200 });
+ return Response.json(
+ {
+ status: 200,
+ data: {
+ version: System.getEnvs().version,
+ },
+ },
+ { status: 200 },
+ );
},
};
diff --git a/app/server/src/shared/consts/data.consts.ts b/app/server/src/shared/consts/data.consts.ts
new file mode 100644
index 0000000..8a88a31
--- /dev/null
+++ b/app/server/src/shared/consts/data.consts.ts
@@ -0,0 +1 @@
+export const DATA_PATH = `./data`;
diff --git a/app/server/src/shared/consts/main.ts b/app/server/src/shared/consts/main.ts
index fd33bfe..2055857 100644
--- a/app/server/src/shared/consts/main.ts
+++ b/app/server/src/shared/consts/main.ts
@@ -1,4 +1,2 @@
export * from "./config.consts.ts";
-export * from "./session.consts.ts";
-export * from "./account.consts.ts";
-export * from "./tickets.consts.ts";
+export * from "./data.consts.ts";
diff --git a/app/server/src/shared/types/files.types.ts b/app/server/src/shared/types/files.types.ts
new file mode 100644
index 0000000..5a0b43b
--- /dev/null
+++ b/app/server/src/shared/types/files.types.ts
@@ -0,0 +1,5 @@
+export type File = {
+ name: string;
+ isFile: boolean;
+ isDirectory: boolean;
+};
diff --git a/app/server/src/shared/types/main.ts b/app/server/src/shared/types/main.ts
index 71a3b9c..30779cb 100644
--- a/app/server/src/shared/types/main.ts
+++ b/app/server/src/shared/types/main.ts
@@ -2,3 +2,5 @@ export * from "./request.types.ts";
export * from "./config.types.ts";
export * from "./yaml.types.ts";
export * from "./envs.types.ts";
+export * from "./files.types.ts";
+export * from "./sprite-sheet.types.ts";
diff --git a/app/server/src/shared/types/sprite-sheet.types.ts b/app/server/src/shared/types/sprite-sheet.types.ts
new file mode 100644
index 0000000..6fa306a
--- /dev/null
+++ b/app/server/src/shared/types/sprite-sheet.types.ts
@@ -0,0 +1,30 @@
+export type SpriteSheetSize = {
+ w: number;
+ h: number;
+};
+
+type SpriteSheetPoint = {
+ x: number;
+ y: number;
+};
+
+export type SpriteSheetRectangle = SpriteSheetPoint & SpriteSheetSize;
+
+export type SpriteSheetFrame = {
+ frame: SpriteSheetRectangle;
+ sourceSize: SpriteSheetSize;
+ anchor: SpriteSheetPoint;
+};
+
+export type SpriteSheetMeta = {
+ format: string;
+ image: string;
+ size: SpriteSheetSize;
+ scale: number;
+};
+
+export type SpriteSheet = {
+ frames: Record;
+ animations: Record;
+ meta: SpriteSheetMeta;
+};
diff --git a/app/server/src/shared/utils/content-type.utils.ts b/app/server/src/shared/utils/content-type.utils.ts
index 0a146ac..56083bf 100644
--- a/app/server/src/shared/utils/content-type.utils.ts
+++ b/app/server/src/shared/utils/content-type.utils.ts
@@ -10,6 +10,8 @@ export const getContentType = (targetFile: string): string => {
contentType = "application/json";
} else if (targetFile.endsWith(".png")) {
contentType = "image/png";
+ } else if (targetFile.endsWith(".yml")) {
+ contentType = "text/yaml";
}
return contentType;
};
diff --git a/app/server/src/shared/utils/image.utils.ts b/app/server/src/shared/utils/image.utils.ts
new file mode 100644
index 0000000..2e3442f
--- /dev/null
+++ b/app/server/src/shared/utils/image.utils.ts
@@ -0,0 +1,16 @@
+import { Image } from "imagescript";
+
+export const getBase64Image = async (image: Image): Promise => {
+ const base64Image = await image.encode(0);
+ const base64String = btoa(String.fromCharCode(...base64Image));
+ return `data:image/png;base64,${base64String}`;
+};
+
+export const getEmptyImage = (
+ width: number,
+ height: number,
+ color: number = 0xff00ffff,
+): Image => {
+ const image = new Image(width, height);
+ return image.drawBox(1, 1, width, height, color);
+};
diff --git a/app/server/src/shared/utils/main.ts b/app/server/src/shared/utils/main.ts
index 4bdc907..d48df1c 100644
--- a/app/server/src/shared/utils/main.ts
+++ b/app/server/src/shared/utils/main.ts
@@ -9,3 +9,7 @@ export * from "./content-type.utils.ts";
export * from "./update.utils.ts";
export * from "./os.utils.ts";
export * from "./environment.utils.ts";
+export * from "./url.utils.ts";
+export * from "./image.utils.ts";
+export * from "./text.utils.ts";
+export * from "./readable-stream.utils.ts";
diff --git a/app/server/src/shared/utils/readable-stream.utils.ts b/app/server/src/shared/utils/readable-stream.utils.ts
new file mode 100644
index 0000000..0670226
--- /dev/null
+++ b/app/server/src/shared/utils/readable-stream.utils.ts
@@ -0,0 +1,31 @@
+export const getUint8ArrayFromReadableStream = async (
+ stream: ReadableStream | Uint8Array,
+): Promise => {
+ if (stream instanceof Uint8Array) return stream;
+
+ const reader = stream.getReader();
+ const chunks = [];
+
+ // Read the stream chunk by chunk
+ let done = false;
+ while (!done) {
+ const { value, done: doneReading } = await reader.read();
+ if (value) {
+ chunks.push(value);
+ }
+ done = doneReading;
+ }
+
+ // Calculate the total size of the resulting Uint8Array
+ const totalSize = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
+
+ // Merge all chunks into a single Uint8Array
+ const result = new Uint8Array(totalSize);
+ let offset = 0;
+ for (const chunk of chunks) {
+ result.set(chunk, offset);
+ offset += chunk.length;
+ }
+
+ return result;
+};
diff --git a/app/server/src/shared/utils/text.utils.ts b/app/server/src/shared/utils/text.utils.ts
new file mode 100644
index 0000000..a593640
--- /dev/null
+++ b/app/server/src/shared/utils/text.utils.ts
@@ -0,0 +1,5 @@
+export const getEncodedText = (text: string): Uint8Array =>
+ new TextEncoder().encode(text);
+
+export const getDecodedText = (data: Uint8Array): string =>
+ new TextDecoder().decode(data);
diff --git a/app/server/src/shared/utils/url.utils.ts b/app/server/src/shared/utils/url.utils.ts
new file mode 100644
index 0000000..7a03644
--- /dev/null
+++ b/app/server/src/shared/utils/url.utils.ts
@@ -0,0 +1,5 @@
+export const getPathFromUrl = (url: URL): string =>
+ getSearchParams(url).get("path")!;
+
+export const getSearchParams = ({ search }: URL): URLSearchParams =>
+ new URLSearchParams(search);
diff --git a/app/server/src/system/api.ts b/app/server/src/system/api.ts
index 118a0bd..9c64775 100644
--- a/app/server/src/system/api.ts
+++ b/app/server/src/system/api.ts
@@ -39,10 +39,6 @@ export const api = () => {
appendCORSHeaders(response.headers);
return response;
}
- if (foundRequests.length)
- return new Response("200", {
- status: 200,
- });
return new Response("404", { status: 404 });
} catch (e) {
console.log(e);
diff --git a/app/server/src/system/data.ts b/app/server/src/system/data.ts
new file mode 100644
index 0000000..2198d00
--- /dev/null
+++ b/app/server/src/system/data.ts
@@ -0,0 +1,53 @@
+import { File } from "shared/types/main.ts";
+import { DATA_PATH } from "shared/consts/main.ts";
+
+export const data = () => {
+ const load = async () => {
+ await mkdir("/");
+ };
+
+ const $getDataPath = (path: string) => `${DATA_PATH}${path}`;
+
+ const mkdir = async (path: string) => {
+ await Deno.mkdir($getDataPath(path), {
+ recursive: true,
+ });
+ };
+
+ const readDir = async (path: string): Promise => {
+ const list = [];
+ for await (const { name, isFile, isDirectory } of Deno.readDir(
+ $getDataPath(path),
+ )) {
+ list.push({
+ name,
+ isFile,
+ isDirectory,
+ });
+ }
+ return list;
+ };
+
+ const readFile = async (path: string): Promise =>
+ await Deno.readFile($getDataPath(path));
+
+ const writeFile = async (
+ path: string,
+ data: ReadableStream | Uint8Array,
+ ) => await Deno.writeFile($getDataPath(path), data);
+
+ const remove = async (path: string) =>
+ await Deno.remove($getDataPath(path), { recursive: true });
+
+ return {
+ load,
+
+ mkdir,
+ readDir,
+
+ readFile,
+ writeFile,
+
+ remove,
+ };
+};
diff --git a/app/server/src/system/main.ts b/app/server/src/system/main.ts
index fb2b119..9c70794 100644
--- a/app/server/src/system/main.ts
+++ b/app/server/src/system/main.ts
@@ -3,9 +3,13 @@ import { ConfigTypes, Envs } from "shared/types/main.ts";
import { getConfig as $getConfig } from "shared/utils/main.ts";
import { load as loadUpdater } from "../modules/updater/main.ts";
import { open } from "open";
+import { data } from "./data.ts";
+import { spriteSheets } from "./sprite-sheets.ts";
export const System = (() => {
const $api = api();
+ const $data = data();
+ const $spriteSheets = spriteSheets();
let $config: ConfigTypes;
let $envs: Envs;
@@ -17,6 +21,8 @@ export const System = (() => {
$config = await $getConfig();
+ await $data.load();
+ await $spriteSheets.load();
$api.load();
if (!$envs.isDevelopment) await open($config.url, { wait: true });
@@ -31,5 +37,7 @@ export const System = (() => {
getEnvs,
api: $api,
+ data: $data,
+ spriteSheets: $spriteSheets,
};
})();
diff --git a/app/server/src/system/sprite-sheets.ts b/app/server/src/system/sprite-sheets.ts
new file mode 100644
index 0000000..93f1e55
--- /dev/null
+++ b/app/server/src/system/sprite-sheets.ts
@@ -0,0 +1,128 @@
+import { System } from "system/main.ts";
+import {
+ getDecodedText,
+ getEmptyImage,
+ getEncodedText,
+ getUint8ArrayFromReadableStream,
+} from "shared/utils/main.ts";
+import { SpriteSheet } from "shared/types/main.ts";
+import { Image } from "imagescript";
+
+const SPRITE_SHEETS_PATH = "/sprite-sheets";
+
+export const spriteSheets = () => {
+ const load = async () => {
+ await System.data.mkdir(SPRITE_SHEETS_PATH);
+ };
+
+ const $getPath = (path: string) => `${SPRITE_SHEETS_PATH}/${path}`;
+
+ const getList = async (): Promise => {
+ const files = await System.data.readDir(SPRITE_SHEETS_PATH);
+ return files.filter((file) => file.isDirectory).map((file) => file.name);
+ };
+
+ const create = async (id: string) => {
+ const path = $getPath(id);
+ await System.data.mkdir(path);
+
+ const sprite = await getEmptyImage(128, 128);
+
+ const sheet: SpriteSheet = {
+ frames: {},
+ animations: {},
+ meta: {
+ format: "RGBA8888",
+ image: "sprite.png",
+ size: {
+ w: 128,
+ h: 128,
+ },
+ scale: 1,
+ },
+ };
+ await updateSheet(id, sheet);
+ await updateSprite(id, sprite);
+ };
+
+ const $updateMetaSheetSize = async (id: string, image: Image) => {
+ const sheet = JSON.parse(getDecodedText(await getSheet(id))) as SpriteSheet;
+
+ await updateSheet(id, {
+ ...sheet,
+ meta: {
+ ...sheet.meta,
+ size: {
+ w: image.width,
+ h: image.height,
+ },
+ },
+ });
+ };
+
+ const updateSprite = async (id: string, image: Image) => {
+ await System.data.writeFile(
+ `${$getPath(id)}/sprite.png`,
+ await image.encode(0),
+ );
+ await $updateMetaSheetSize(id, image);
+ };
+
+ const updateRawSprite = async (
+ id: string,
+ raw: ReadableStream | Uint8Array,
+ ) => {
+ const path = `${$getPath(id)}/sprite.png`;
+ await System.data.writeFile(path, raw);
+
+ const data = await System.data.readFile(path);
+ const image = await Image.decode(data);
+ await $updateMetaSheetSize(id, image);
+ };
+ const updateSheet = async (id: string, sheet: SpriteSheet) =>
+ System.data.writeFile(
+ $getPath(id) + "/sheet.json",
+ getEncodedText(JSON.stringify(sheet)),
+ );
+
+ const updateRawSheet = async (
+ id: string,
+ raw: ReadableStream | Uint8Array,
+ ) => {
+ const sheetData = JSON.parse(
+ getDecodedText(await getUint8ArrayFromReadableStream(raw)),
+ ) as SpriteSheet;
+
+ const sheet = JSON.parse(getDecodedText(await getSheet(id))) as SpriteSheet;
+ sheet.frames = sheetData.frames;
+ sheet.animations = sheetData.animations;
+
+ await updateSheet(id, sheet);
+ };
+
+ const getSprite = async (id: string): Promise =>
+ System.data.readFile($getPath(id) + "/sprite.png");
+
+ const getSheet = (id: string): Promise =>
+ System.data.readFile($getPath(id) + "/sheet.json");
+
+ const remove = (id: string) => System.data.remove($getPath(id));
+
+ return {
+ load,
+
+ getList,
+
+ getSprite,
+ updateSprite,
+ updateRawSprite,
+
+ getSheet,
+ updateSheet,
+ updateRawSheet,
+
+ create,
+
+ remove,
+ };
+};