[FEATURE] Initial File Preview

This commit is contained in:
Dunemask 2023-12-26 13:40:22 -07:00
parent cb118e07c0
commit e96c326c1d
5 changed files with 117 additions and 7 deletions

View file

@ -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 <div style={{ whiteSpace: "break-spaces" }}>{fileText}</div>;
}
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 (
<Dialog
sx={
fullScreen
? {}
: {
"& .mcl-MuiDialog-paper": {
width: "100%",
maxHeight: 525,
maxWidth: "80%",
},
}
}
maxWidth="xs"
open={open}
fullScreen={fullScreen}
>
<Toolbar sx={{ display: { sm: "none" } }} />
<DialogTitle>{name}</DialogTitle>
<DialogContent>
<TextPreview fileText={fileText} />
</DialogContent>
<DialogActions>
<Button autoFocus onClick={dialogToggle}>
Close
</Button>
</DialogActions>
</Dialog>
);
}

View file

@ -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 (
<Box className="minecluster-files" sx={{ height: "calc(100vh - 6rem)" }}>

View file

@ -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}

View file

@ -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 (
<Box className="edit" sx={{ height: "100%" }}>
<MineclusterFiles server={currentServer} />
<FilePreview
open={open}
dialogToggle={dialogToggle}
previewData={previewData}
/>
<MineclusterFiles server={currentServer} changePreview={changePreview} />
</Box>
);
}

View file

@ -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) =>