import React, { useState, useEffect, useContext, memo } from "react"; import { toast } from 'react-toastify'; import { IconButton, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction, Tooltip, Button, FormControl, InputLabel, TextField, Divider, Select, MenuItem, Dialog, DialogTitle, DialogContent, DialogActions, Typography, Skeleton, } from "@mui/material"; import { Link as LinkIcon, OpenInNew as OpenInNewIcon, Edit as EditIcon, CloudDownload as CloudDownloadIcon, Delete as DeleteIcon, FileCopy as FileCopyIcon, Cached as CachedIcon, Publish as PublishIcon, Clear as ClearIcon, Add as AddIcon, } from "@mui/icons-material"; import Dropzone from "../components/Dropzone.jsx"; import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; import theme from "../theme.jsx"; import { Context } from "../context/ContextApi.jsx"; const Files = memo((props) => { const { globalUrl, userdata, serverside, selectedOrganization, isCloud,isSelectedFiles } = props; const [files, setFiles] = React.useState([]); const [showLoader, setShowLoader] = useState(true) const [selectedCategory, setSelectedCategory] = React.useState("default"); const [openFileId, setOpenFileId] = React.useState(false); const [fileCategories, setFileCategories] = React.useState([]); const [fileContent, setFileContent] = React.useState(""); const [openEditor, setOpenEditor] = React.useState(false); const [renderTextBox, setRenderTextBox] = React.useState(false); const [loadFileModalOpen, setLoadFileModalOpen] = React.useState(false); const [field1, setField1] = React.useState(""); const [field2, setField2] = React.useState(""); const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/shuffle/standards") const [downloadBranch, setDownloadBranch] = React.useState("main"); const [downloadFolder, setDownloadFolder] = React.useState("translation_standards"); const [contentLoading, setContentLoading] = React.useState(false) //const alert = useAlert(); const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log", "eml", "msg", "md", "xml", "sh", "bat", "ps1", "psm1", "psd1", "ps1xml", "pssc", "psc1", "response"] var upload = ""; const handleKeyDown = (event) => { if (event.key === 'Enter') { fileCategories.push(event.target.value); setSelectedCategory(event.target.value); setRenderTextBox(false); } if (event.key === 'Escape'){ // not working for some reasons console.log('escape pressed') setRenderTextBox(false); } } const runUpdateText = (text) =>{ fetch(`${globalUrl}/api/v1/files/${openFileId}/edit`, { method: "PUT", headers: { "Content-Type": "application/json", Accept: "application/json", }, body:text, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Can't update file"); } return response.json(); }) .then((responseJson) => { if (responseJson.success === true) { toast("Successfully updated file"); } }) .catch((error) => { toast("Error updating file: " + error.toString()); }) } const getFiles = (namespace) => { var parsedurl = `${globalUrl}/api/v1/files` if (namespace === undefined || namespace === null || namespace === "default") { } else if (namespace !== undefined && namespace !== null && namespace !== "") { parsedurl = `${globalUrl}/api/v1/files/namespaces/${namespace}?ids=true` } else if (selectedCategory !== undefined && selectedCategory !== null && selectedCategory !== "default" && selectedCategory !== "") { parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedCategory}?ids=true` } fetch(parsedurl, { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!"); return; } return response.json(); }) .then((responseJson) => { if (responseJson.files !== undefined && responseJson.files !== null) { setFiles(responseJson.files); setShowLoader(false) } else if (responseJson.list !== undefined && responseJson.list !== null) { // Set the "namespace" field in all items if (namespace !== undefined && namespace !== null) { responseJson.list.forEach((item) => { item.namespace = namespace item.filename = item.name item.workflow_id = "global" }) } setFiles(responseJson.list); setShowLoader(false) } else { setFiles([]); setShowLoader(false) } if (namespace === undefined || namespace === null || namespace === "default") { if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileCategories.length === 0 || responseJson.namespaces.length > fileCategories.length)) { setFileCategories(responseJson.namespaces) } } }) .catch((error) => { toast(error.toString()); }); }; useEffect(() => { getFiles("default") setTimeout(() => { var category = selectedCategory if (window.location.search.includes("category=")) { const urlParams = new URLSearchParams(window.location.search) category = urlParams.get("category") } if (category !== undefined && category !== null && category.length > 0 && category !== "default") { setSelectedCategory(category) } getFiles(category) }, 1000) }, []); const importStandardsFromUrl = (url, folder) => { if (url === undefined || url === null || url.length < 5) { toast("Please enter a valid URL"); return; } if (folder === undefined || folder === null || folder.length < 2) { toast("Please enter a valid folder name") return } const parsedData = { url: url, path: folder, field_3: downloadBranch || "master", }; if (field1.length > 0) { parsedData["field_1"] = field1; } if (field2.length > 0) { parsedData["field_2"] = field2; } toast(`Getting files from url ${url}. This may take a while if the repository is large. Please wait...`); fetch(globalUrl + "/api/v1/files/download_remote", { method: "POST", mode: "cors", headers: { Accept: "application/json", }, body: JSON.stringify(parsedData), credentials: "include", }) .then((response) => { if (response.status === 200) { toast("Successfully loaded files from " + downloadUrl); setLoadFileModalOpen(false); } return response.json(); }) .then((responseJson) => { if (!responseJson.success) { if (responseJson.reason !== undefined) { toast("Failed loading: " + responseJson.reason); } else { toast("Failed loading"); } } }) .catch((error) => { toast(error.toString()); }); } const handleGithubValidation = () => { importStandardsFromUrl(downloadUrl, downloadFolder); } const fileDownloadModal = loadFileModalOpen ?
Load Files from Github
Files will be loaded from the repository and branch you specify, with the focus on files in one folder at a time. This is NOT recursive.
Repository URL (supported: github, gitlab, bitbucket) setDownloadUrl(e.target.value)} placeholder="https://github.com/shuffle/standards" fullWidth />
Branch (default value is "main"): setDownloadBranch(e.target.value)} placeholder="master" fullWidth /> Folder (can use / for subfolders): setDownloadFolder(e.target.value)} placeholder="translation_standards" fullWidth />
Authentication (optional - private repos etc):
setField1(e.target.value)} type="username" placeholder="Username / APIkey (optional)" fullWidth /> setField2(e.target.value)} type="password" placeholder="Password (optional)" fullWidth />
: null const deleteFile = (file) => { fetch(globalUrl + "/api/v1/files/" + file.id, { method: "DELETE", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for file delete :O!"); } return response.json(); }) .then((responseJson) => { if (responseJson.success) { toast("Successfully deleted file") } else if ( responseJson.reason !== undefined && responseJson.reason !== null ) { toast("Failed to delete file: " + responseJson.reason); } setTimeout(() => { getFiles(selectedCategory) }, 1500); }) .catch((error) => { toast(error.toString()); }); }; const readFileData = (file) => { setContentLoading(true) fetch(globalUrl + "/api/v1/files/" + file.id + "/content", { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { setContentLoading(false) if (response.status !== 200) { console.log("Status not 200 for file :O!"); return ""; } return response.text(); }) .then((respdata) => { // console.log("respdata ->", respdata); // console.log("respdata type ->", typeof(respdata)); if (respdata.length === 0) { toast("Failed getting file. Is it deleted?"); return; } return respdata }) .then((responseData) => { setFileContent(responseData); //console.log("filecontent state ",fileContent); }) .catch((error) => { setContentLoading(false) toast(error.toString()) }); }; const downloadFile = (file) => { fetch(globalUrl + "/api/v1/files/" + file.id + "/content", { method: "GET", credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!"); return ""; } console.log("Resp: ", response) return response.blob() }) .then((respdata) => { if (respdata.length === 0) { toast("Failed getting file. Is it deleted?"); return; } var blob = new Blob([respdata], { type: "application/octet-stream", }); var url = URL.createObjectURL(blob); var link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", `${file.filename}`); var event = document.createEvent("MouseEvents"); event.initMouseEvent( "click", true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null ); link.dispatchEvent(event); //return response.json() }) .then((responseJson) => { //console.log(responseJson) //setSchedules(responseJson) }) .catch((error) => { toast(error.toString()); }); }; const handleCreateFile = (filename, file) => { var data = { filename: filename, org_id: selectedOrganization.id, workflow_id: "global", }; if ( selectedCategory !== undefined && selectedCategory !== null && selectedCategory.length > 0 && selectedCategory !== "default" ) { data.namespace = selectedCategory; } fetch(globalUrl + "/api/v1/files/create", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", body: JSON.stringify(data), }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!"); return; } return response.json(); }) .then((responseJson) => { //console.log("RESP: ", responseJson) if (responseJson.success === true) { handleFileUpload(responseJson.id, file); } else { toast("Failed to upload file ", filename); } }) .catch((error) => { toast("Failed to upload file ", filename); console.log(error.toString()); }); }; const handleFileUpload = (file_id, file) => { //console.log("FILE: ", file_id, file) fetch(`${globalUrl}/api/v1/files/${file_id}/upload`, { method: "POST", credentials: "include", body: file, }) .then((response) => { if (response.status !== 200 && response.status !== 201) { console.log("Status not 200 for apps :O!"); toast("File was created, but failed to upload."); return; } return response.json(); }) .then((responseJson) => { //console.log("RESPONSE: ", responseJson) //setFiles(responseJson) }) .catch((error) => { toast(error.toString()); }); }; const uploadFiles = (files) => { for (var key in files) { try { const filename = files[key].name; var filedata = new FormData(); filedata.append("shuffle_file", files[key]); if (typeof files[key] === "object") { handleCreateFile(filename, filedata); } /* reader.addEventListener('load', (e) => { var data = e.target.result; setIsDropzone(false) console.log(filename) console.log(data) console.log(files[key]) }) reader.readAsText(files[key]) */ } catch (e) { console.log("Error in dropzone: ", e); } } setTimeout(() => { getFiles() }, 2500); }; const uploadFile = (e) => { const isDropzone = e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0; const files = isDropzone ? e.dataTransfer.files : e.target.files; //const reader = new FileReader(); //toast("Starting fileupload") uploadFiles(files); }; return (
{fileDownloadModal}

Files

Files from Workflows are a way to store as well as edit files.{" "} Learn more
{/* */} (upload = ref)} onChange={(event) => { //const file = event.target.value //const fileObject = URL.createObjectURL(actualFile) //setFile(fileObject) //const files = event.target.files[0] uploadFiles(event.target.files); }} /> {/*
*/} {fileCategories !== undefined && fileCategories !== null && fileCategories.length > 1 ? ( ) : null}
{renderTextBox ? : } {renderTextBox && { handleKeyDown(event); }} InputProps={{ style: { color: "white", }, }} color="primary" placeholder="File category name" required margin="dense" defaultValue={""} autoFocus />}
{isSelectedFiles?null: }
{["Name", "Workflow", "Md5", "Status", "Filesize", "Actions"].map((header, index) => ( ))} {showLoader ? [...Array(6)].map((_, rowIndex) => ( {Array(6) .fill() .map((_, colIndex) => ( ))} )): files.length === 0 ? (
No files found
):( files?.map((file, index) => { if (file.namespace === "") { file.namespace = "default"; } if (file.namespace !== selectedCategory) { return null; } var bgColor = isSelectedFiles ? "#212121":"#27292d"; if (index % 2 === 0) { bgColor = isSelectedFiles ? "#1A1A1A":"#1f2023"; } const filenamesplit = file.filename.split(".") const iseditable = file.filesize < 2000000 && file.status === "active" && allowedFileTypes.includes(filenamesplit[filenamesplit.length-1]) return ( {/* */} : ( ) } style={{ display: 'table-cell', overflow: "hidden", }} /> {file.md5_sum} )} primaryTypographyProps={{ style:{ display: 'table-cell', marginLeft:isSelectedFiles? 15:null, overflow: "hidden", whiteSpace: 'nowrap', textOverflow: 'ellipsis', maxWidth: 200, } }} /> { setOpenEditor(true) setOpenFileId(file.id) readFileData(file) }} > edit icon {/* { // Open the file, without downloading it window.open(`${globalUrl}/api/v1/files/${file.id}/content?type=text&authorization=${file.public_authorization}`, "_blank noreferrer noopener") }} > */} { downloadFile(file); }} > download icon { console.log("file is : ", file) navigator.clipboard.writeText(file.id); document.execCommand("copy"); toast(file.id + " copied to clipboard"); }} > copy icon { deleteFile(file) }} > delete icon style={{ display: 'table-cell', textAlign:'center' // overflow: "hidden", }} /> ); }) ) }
) }) export default memo(Files); const DownloadFileIcon = memo(({ setLoadFileModalOpen, isSelectedFiles, }) => { const { leftSideBarOpenByClick } = useContext(Context) return( setLoadFileModalOpen(true)} > ) })