diff --git a/src/components/files/FilePreview.jsx b/src/components/files/FilePreview.jsx
new file mode 100644
index 0000000..9920a0f
--- /dev/null
+++ b/src/components/files/FilePreview.jsx
@@ -0,0 +1,74 @@
+import { useState, useEffect } from "react";
+import useMediaQuery from "@mui/material/useMediaQuery";
+import { useTheme } from "@mui/material/styles";
+import Button from "@mui/material/Button";
+import DialogTitle from "@mui/material/DialogTitle";
+import DialogContent from "@mui/material/DialogContent";
+import DialogActions from "@mui/material/DialogActions";
+import Dialog from "@mui/material/Dialog";
+import Toolbar from "@mui/material/Toolbar";
+
+const textFileTypes = ["properties", "txt", "yaml", "yml", "json", "env"];
+const imageFileTypes = ["png", "jpeg", "jpg"];
+
+export const supportedFileTypes = [...textFileTypes, ...imageFileTypes];
+
+export function useFilePreview(isOpen = false) {
+ const [open, setOpen] = useState(isOpen);
+ const dialogToggle = () => setOpen(!open);
+ return [open, dialogToggle];
+}
+
+function TextPreview(props) {
+ const { fileText } = props;
+ return
{fileText}
;
+}
+
+export default function FilePreview(props) {
+ const [fileText, setFileText] = useState();
+ const theme = useTheme();
+ const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
+
+ const { previewData, open, dialogToggle } = props;
+ const { fileData, name } = previewData ?? {};
+ const ext = name ? name.split(".").pop() : null;
+ const isTextFile = textFileTypes.includes(ext);
+
+ async function onPreviewChange() {
+ if (isTextFile) setFileText(await fileData.text());
+ }
+
+ useEffect(() => {
+ onPreviewChange();
+ }, [fileData]);
+
+ return (
+
+ );
+}
diff --git a/src/components/files/MineclusterFiles.jsx b/src/components/files/MineclusterFiles.jsx
index ee5b26b..31c1389 100644
--- a/src/components/files/MineclusterFiles.jsx
+++ b/src/components/files/MineclusterFiles.jsx
@@ -17,6 +17,9 @@ import {
deleteServerItem,
getServerItem,
} from "@mcl/queries";
+import { previewServerItem } from "../../util/queries";
+
+import { supportedFileTypes } from "./FilePreview.jsx";
export default function MineclusterFiles(props) {
// Chonky configuration
@@ -31,7 +34,7 @@ export default function MineclusterFiles(props) {
],
[],
);
- const { server: serverId } = props;
+ const { server: serverId, changePreview } = props;
const inputRef = useRef(null);
const [dirStack, setDirStack] = useState(["."]);
const [files, setFiles] = useState([]);
@@ -65,10 +68,13 @@ export default function MineclusterFiles(props) {
const openParentFolder = () => setDirStack(dirStack.slice(0, -1));
- function openFolder(payload) {
+ function openItem(payload) {
const { targetFile: file } = payload;
if (file && file.isDir) return setDirStack(file.id.split("/"));
- if (file && !file.isDir) return downloadFiles([file]);
+ if (!file || file.isDir) return; // Ensure file exists or is dir
+ if (supportedFileTypes.includes(file.name.split(".").pop()))
+ return previewFile(file);
+ return downloadFiles([file]);
}
function createFolder() {
@@ -116,6 +122,13 @@ export default function MineclusterFiles(props) {
.catch((e) => console.error("Error Downloading files!", e));
}
+ function previewFile(file) {
+ const { name } = file;
+ previewServerItem(serverId, [...dirStack, name].join("/")).then(
+ (fileData) => changePreview(name, fileData),
+ );
+ }
+
function fileClick(chonkyEvent) {
const { id: clickEvent, payload } = chonkyEvent;
if (clickEvent === "open_parent_folder") return openParentFolder();
@@ -126,7 +139,7 @@ export default function MineclusterFiles(props) {
if (clickEvent === "delete_files")
return deleteItems(chonkyEvent.state.selectedFilesForAction);
if (clickEvent !== "open_files") return; // console.log(clickEvent);
- openFolder(payload);
+ openItem(payload);
}
return (
diff --git a/src/components/servers/RconDialog.jsx b/src/components/servers/RconDialog.jsx
index 5cb8aad..899c641 100644
--- a/src/components/servers/RconDialog.jsx
+++ b/src/components/servers/RconDialog.jsx
@@ -26,7 +26,7 @@ export default function RconDialog(props) {
sx={
fullScreen
? {}
- : { "& .MuiDialog-paper": { width: "80%", maxHeight: 525 } }
+ : { "& .mcl-MuiDialog-paper": { width: "80%", maxHeight: 525 } }
}
maxWidth="xs"
open={open}
diff --git a/src/pages/Files.jsx b/src/pages/Files.jsx
index 2794530..5226176 100644
--- a/src/pages/Files.jsx
+++ b/src/pages/Files.jsx
@@ -1,20 +1,36 @@
-import { useEffect } from "react";
+import { useState, useEffect } from "react";
import { useSearchParams, useNavigate } from "react-router-dom";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Toolbar from "@mui/material/Toolbar";
+import FilePreview, {
+ useFilePreview,
+} from "@mcl/components/files/FilePreview.jsx";
import MineclusterFiles from "@mcl/components/files/MineclusterFiles.jsx";
export default function Files() {
+ const [open, dialogToggle] = useFilePreview();
+ const [previewData, setPreviewData] = useState();
const [searchParams] = useSearchParams();
const currentServer = searchParams.get("server");
const nav = useNavigate();
useEffect(() => {
if (!currentServer) nav("/");
}, [currentServer]);
+
+ function changePreview(name, fileData) {
+ setPreviewData({ name, fileData });
+ dialogToggle();
+ }
+
return (
-
+
+
);
}
diff --git a/src/util/queries.js b/src/util/queries.js
index 3db8615..0bb758e 100644
--- a/src/util/queries.js
+++ b/src/util/queries.js
@@ -50,6 +50,13 @@ export const createServerFolder = async (serverId, path) =>
export const deleteServerItem = async (serverId, path, isDir) =>
fetchApiCore("/files/item", { id: serverId, path, isDir }, "DELETE");
+export async function previewServerItem(serverId, path) {
+ const resp = await fetchApiCore("/files/item", { id: serverId, path });
+ if (!resp.status === 200) return console.log("AHHHH");
+ const blob = await resp.blob();
+ return blob;
+}
+
export const getServerItem = async (serverId, name, path) =>
fetchApiCore("/files/item", { id: serverId, path })
.then((resp) =>