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 ( + + + {name} + + + + + + + + ); +} 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) =>