Skip to content

Commit

Permalink
feat: FileManager + SpriteSheets
Browse files Browse the repository at this point in the history
  • Loading branch information
pagoru committed Aug 23, 2024
1 parent a5fb8f5 commit b8ecfb0
Show file tree
Hide file tree
Showing 85 changed files with 2,188 additions and 173 deletions.
3 changes: 2 additions & 1 deletion app/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "[email protected]"
}
4 changes: 2 additions & 2 deletions app/client/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
<head>
<style>
body {
background-color: #1e1e1e;
background-color: #121212;
margin: 0;
overflow: hidden;
}
</style>
<title>Open Hotel Asset Editor</title>
<link lang="scss" rel="stylesheet" href="/main.module.scss" />
</head>
<body>
Expand Down
27 changes: 23 additions & 4 deletions app/client/src/main.module.scss
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -37,6 +54,9 @@
h4 {
font-size: 2rem;
}
hr {
color: var(--color-primary);
}
}

html {
Expand All @@ -55,7 +75,6 @@ body {

background-attachment: fixed;
background-size: cover;
overflow-x: hidden !important;
}
a {
&:visited,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
display: flex;
flex-direction: column;
height: 100%;
max-width: 200px;

.outlet {
flex: 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,18 @@ import { ContainerComponent } from "shared/components";
export const FooterComponent = () => {
return (
<footer className={styles.footer}>
<ContainerComponent>footer</ContainerComponent>
<ContainerComponent className={styles.container}>
<label>Open Hotel</label>
<a
href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en"
target="_blank"
>
<img
alt="by-nc-sa"
src="https://mirrors.creativecommons.org/presskit/buttons/88x31/svg/by-nc-sa.eu.svg"
/>
</a>
</ContainerComponent>
</footer>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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 (
<header className={styles.header}>
<ContainerComponent>header</ContainerComponent>
<ContainerComponent className={styles.container}>
<span>Open Hotel Asset Editor</span>
<div className={styles.items}>
<LinkComponent to="/">Home</LinkComponent>
<LinkComponent to="/file-manager">File Manager</LinkComponent>
<LinkComponent to="/sprite-sheets">Sprite Sheets</LinkComponent>
<LinkComponent to="/furniture">Furniture</LinkComponent>
<LinkComponent to="/human-clothes">Human and Clothes</LinkComponent>
</div>
</ContainerComponent>
</header>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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: <LayoutComponent />,
path: "/",
children: [
{
path: "/sprite-sheets",
element: <SpriteSheetsComponent />,
},
{
path: "/file-manager",
element: <FileManagerComponent />,
},
{
path: "/",
Component: () => <HomeComponent />,
Expand Down
173 changes: 173 additions & 0 deletions app/client/src/modules/file-manager/file-manager.component.tsx
Original file line number Diff line number Diff line change
@@ -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<string>("");
const [files, setFiles] = useState<File[]>([]);

const [currentFile, setCurrentFile] = useState<File>();
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<HTMLInputElement>) => {
const { files } = event.target;
await addFiles(path, files);
loadFiles();
//@ts-ignore
uploadRef.current.value = "";
};

const onCreateDirectory = async (event: FormEvent<HTMLFormElement>) => {
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 (
<div className={styles.container}>
<div className={styles.path}>
{path || "/"}
{currentFile?.name ? `/${currentFile?.name}` : ""}
</div>
<hr />
<div className={styles.files}>
<div onClick={onClickBack} className={styles.back}>
{path ? "⪻" : "|"}
</div>
{sortedFiles.map((file) => (
<div key={file.name} className={styles.file}>
<label onClick={onClickFile(file)} className={styles.name}>
{file.isDirectory ? "/" : ""}
{file.name}
</label>
<button onClick={onClickDeleteFile(file)}>delete</button>
</div>
))}
</div>
<hr />
<div className={styles.actions}>
<button onClick={loadFiles}>Reload</button>
<form onSubmit={onCreateDirectory}>
<input name="name" placeholder="Folder Name" />
<button>+</button>
</form>
<div>
<button>
<label htmlFor="uploadFile">Upload Files</label>
</button>
<input
id="uploadFile"
hidden
ref={uploadRef}
type="file"
multiple
onChange={onUploadFiles}
/>
</div>
</div>
<div className={styles.preview}>
{currentFileData ? (
<>
{currentFileData.format === "json" ? (
<textarea readOnly value={JSON.stringify(currentFileData.data)} />
) : null}
{currentFileData.format === "text" ? (
<textarea readOnly value={currentFileData.data} />
) : null}
{currentFileData.format === "image" ? (
<img alt="" src={currentFileData.data} />
) : null}
</>
) : null}
</div>
</div>
);
};
Loading

0 comments on commit b8ecfb0

Please sign in to comment.