diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/Json.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/Json.tsx new file mode 100644 index 0000000000..4dce33e2ed --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/Json.tsx @@ -0,0 +1,111 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { AxiosError } from "axios"; +import { useSnackbar } from "notistack"; +import SaveIcon from "@mui/icons-material/Save"; +import { Box, Button, Typography } from "@mui/material"; +import { useUpdateEffect } from "react-use"; +import { + editStudy, + getStudyData, +} from "../../../../../../../services/api/study"; +import { Header, Root } from "./style"; +import SimpleLoader from "../../../../../../common/loaders/SimpleLoader"; +import JSONEditor from "../../../../../../common/JSONEditor"; +import usePromiseWithSnackbarError from "../../../../../../../hooks/usePromiseWithSnackbarError"; +import UsePromiseCond from "../../../../../../common/utils/UsePromiseCond"; +import SimpleContent from "../../../../../../common/page/SimpleContent"; +import useEnqueueErrorSnackbar from "../../../../../../../hooks/useEnqueueErrorSnackbar"; + +interface Props { + path: string; + studyId: string; +} + +function Json({ path, studyId }: Props) { + const [t] = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); + const enqueueErrorSnackbar = useEnqueueErrorSnackbar(); + const [jsonData, setJsonData] = useState(null); + const [isSaveAllowed, setSaveAllowed] = useState(false); + + const res = usePromiseWithSnackbarError( + () => getStudyData(studyId, path, -1), + { + errorMessage: t("studies.error.retrieveData"), + deps: [studyId, path], + }, + ); + + /* Reset save button when path changes */ + useUpdateEffect(() => { + setSaveAllowed(false); + }, [studyId, path]); + + //////////////////////////////////////////////////////////////// + // Event Handlers + //////////////////////////////////////////////////////////////// + + const handleSaveJson = async () => { + if (isSaveAllowed && jsonData) { + try { + await editStudy(jsonData, studyId, path); + enqueueSnackbar(t("studies.success.saveData"), { + variant: "success", + }); + setSaveAllowed(false); + } catch (e) { + enqueueErrorSnackbar(t("studies.error.saveData"), e as AxiosError); + } + } + }; + + const handleJsonChange = (newJson: string) => { + setJsonData(newJson); + setSaveAllowed(true); + }; + + //////////////////////////////////////////////////////////////// + // JSX + //////////////////////////////////////////////////////////////// + + return ( + +
+ +
+ } + ifResolved={(json) => ( + + + + )} + ifRejected={(error) => } + /> +
+ ); +} + +export default Json; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/Matrix.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/Matrix.tsx new file mode 100644 index 0000000000..524c6b5117 --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/Matrix.tsx @@ -0,0 +1,26 @@ +import { useOutletContext } from "react-router"; +import { MatrixStats, StudyMetadata } from "../../../../../../../common/types"; +import { Root, Content } from "./style"; +import MatrixInput from "../../../../../../common/MatrixInput"; + +interface Props { + path: string; +} + +function Matrix({ path }: Props) { + const { study } = useOutletContext<{ study: StudyMetadata }>(); + + //////////////////////////////////////////////////////////////// + // JSX + //////////////////////////////////////////////////////////////// + + return ( + + + + + + ); +} + +export default Matrix; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/Text.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/Text.tsx new file mode 100644 index 0000000000..d4f00ac1fc --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/Text.tsx @@ -0,0 +1,91 @@ +import { useState } from "react"; +import { AxiosError } from "axios"; +import { useSnackbar } from "notistack"; +import { useTranslation } from "react-i18next"; +import { Button } from "@mui/material"; +import UploadOutlinedIcon from "@mui/icons-material/UploadOutlined"; +import { + getStudyData, + importFile, +} from "../../../../../../../services/api/study"; +import { Content, Header, Root } from "./style"; +import useEnqueueErrorSnackbar from "../../../../../../../hooks/useEnqueueErrorSnackbar"; +import SimpleLoader from "../../../../../../common/loaders/SimpleLoader"; +import ImportDialog from "../../../../../../common/dialogs/ImportDialog"; +import usePromiseWithSnackbarError from "../../../../../../../hooks/usePromiseWithSnackbarError"; +import SimpleContent from "../../../../../../common/page/SimpleContent"; +import UsePromiseCond from "../../../../../../common/utils/UsePromiseCond"; +import { useDebugContext } from "../DebugContext"; + +interface Props { + studyId: string; + path: string; +} + +function Text({ studyId, path }: Props) { + const [t] = useTranslation(); + const { reloadTreeData } = useDebugContext(); + const { enqueueSnackbar } = useSnackbar(); + const enqueueErrorSnackbar = useEnqueueErrorSnackbar(); + const [openImportDialog, setOpenImportDialog] = useState(false); + + const res = usePromiseWithSnackbarError(() => getStudyData(studyId, path), { + errorMessage: t("studies.error.retrieveData"), + deps: [studyId, path], + }); + + //////////////////////////////////////////////////////////////// + // Event Handlers + //////////////////////////////////////////////////////////////// + + const handleImport = async (file: File) => { + try { + await importFile(file, studyId, path); + reloadTreeData(); + enqueueSnackbar(t("studies.success.saveData"), { + variant: "success", + }); + } catch (e) { + enqueueErrorSnackbar(t("studies.error.saveData"), e as AxiosError); + } + }; + + //////////////////////////////////////////////////////////////// + // JSX + //////////////////////////////////////////////////////////////// + + return ( + +
+ +
+ } + ifResolved={(data) => ( + + {data} + + )} + ifRejected={(error) => } + /> + {openImportDialog && ( + setOpenImportDialog(false)} + onImport={handleImport} + /> + )} +
+ ); +} + +export default Text; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/index.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/index.tsx new file mode 100644 index 0000000000..0cb75a3eab --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/index.tsx @@ -0,0 +1,24 @@ +import Text from "./Text"; +import Json from "./Json"; +import Matrix from "./Matrix"; +import { FileType } from "../utils"; + +interface Props { + studyId: string; + fileType: FileType; + filePath: string; +} + +const componentByFileType = { + matrix: Matrix, + json: Json, + file: Text, +} as const; + +function Data({ studyId, fileType, filePath }: Props) { + const DataViewer = componentByFileType[fileType]; + + return ; +} + +export default Data; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/style.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/style.ts similarity index 97% rename from webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/style.ts rename to webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/style.ts index 5d9106f03d..b0143fdec8 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/style.ts +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Data/style.ts @@ -32,5 +32,3 @@ export const Content = styled(Paper)(({ theme }) => ({ overflow: "auto", position: "relative", })); - -export default {}; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/DebugContext.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/DebugContext.ts new file mode 100644 index 0000000000..52e4c07d7f --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/DebugContext.ts @@ -0,0 +1,21 @@ +import { createContext, useContext } from "react"; +import { FileType, TreeData } from "./utils"; + +interface DebugContextProps { + treeData: TreeData; + onFileSelect: (fileType: FileType, filePath: string) => void; + reloadTreeData: () => void; +} + +const initialDebugContextValue: DebugContextProps = { + treeData: {}, + onFileSelect: () => {}, + reloadTreeData: () => {}, +}; + +const DebugContext = createContext(initialDebugContextValue); + +export const useDebugContext = (): DebugContextProps => + useContext(DebugContext); + +export default DebugContext; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Tree/FileTreeItem.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Tree/FileTreeItem.tsx new file mode 100644 index 0000000000..5ae11cff9d --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Tree/FileTreeItem.tsx @@ -0,0 +1,64 @@ +import { Box } from "@mui/material"; +import { TreeItem } from "@mui/x-tree-view"; +import { TreeData, determineFileType, getFileIcon } from "../utils"; +import { useDebugContext } from "../DebugContext"; + +interface Props { + name: string; + content: TreeData; + path: string; +} + +function FileTreeItem({ name, content, path }: Props) { + const { onFileSelect } = useDebugContext(); + const fullPath = `${path}/${name}`; + const fileType = determineFileType(content); + const FileIcon = getFileIcon(fileType); + const isFolderEmpty = !Object.keys(content).length; + + //////////////////////////////////////////////////////////////// + // Event handlers + //////////////////////////////////////////////////////////////// + + const handleClick = () => { + if (fileType !== "folder") { + onFileSelect(fileType, fullPath); + } + }; + + //////////////////////////////////////////////////////////////// + // JSX + //////////////////////////////////////////////////////////////// + + return ( + + + {name} + + } + > + {typeof content === "object" && + Object.keys(content).map((childName) => ( + + ))} + + ); +} + +export default FileTreeItem; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Tree/index.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Tree/index.tsx new file mode 100644 index 0000000000..faa75ab045 --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/Tree/index.tsx @@ -0,0 +1,28 @@ +import { TreeView } from "@mui/x-tree-view"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import ChevronRightIcon from "@mui/icons-material/ChevronRight"; +import FileTreeItem from "./FileTreeItem"; +import { useDebugContext } from "../DebugContext"; + +function Tree() { + const { treeData } = useDebugContext(); + + //////////////////////////////////////////////////////////////// + // JSX + //////////////////////////////////////////////////////////////// + + return ( + } + defaultExpandIcon={} + > + {typeof treeData === "object" && + Object.keys(treeData).map((key) => ( + + ))} + + ); +} + +export default Tree; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/index.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/index.tsx new file mode 100644 index 0000000000..414c004093 --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/index.tsx @@ -0,0 +1,94 @@ +import { useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useOutletContext } from "react-router-dom"; +import { Box } from "@mui/material"; +import Tree from "./Tree"; +import Data from "./Data"; +import { StudyMetadata } from "../../../../../../common/types"; +import SimpleLoader from "../../../../../common/loaders/SimpleLoader"; +import UsePromiseCond from "../../../../../common/utils/UsePromiseCond"; +import SimpleContent from "../../../../../common/page/SimpleContent"; +import usePromiseWithSnackbarError from "../../../../../../hooks/usePromiseWithSnackbarError"; +import { getStudyData } from "../../../../../../services/api/study"; +import DebugContext from "./DebugContext"; +import { TreeData, filterTreeData, File } from "./utils"; + +function Debug() { + const [t] = useTranslation(); + const { study } = useOutletContext<{ study: StudyMetadata }>(); + const [selectedFile, setSelectedFile] = useState(); + + const studyTree = usePromiseWithSnackbarError( + async () => { + const treeData = await getStudyData(study.id, "", -1); + return filterTreeData(treeData); + }, + { + errorMessage: t("studies.error.retrieveData"), + deps: [study.id], + }, + ); + + const handleFileSelection = useCallback( + (fileType: File["fileType"], filePath: string) => { + setSelectedFile({ fileType, filePath }); + }, + [], + ); + + //////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////// + + const contextValue = useMemo( + () => ({ + treeData: studyTree.data ?? {}, + onFileSelect: handleFileSelection, + reloadTreeData: studyTree.reload, + }), + [studyTree.data, studyTree.reload, handleFileSelection], + ); + + //////////////////////////////////////////////////////////////// + // JSX + //////////////////////////////////////////////////////////////// + + return ( + + + } + ifResolved={() => ( + <> + + + + + {selectedFile && ( + + )} + + + )} + ifRejected={(error) => } + /> + + + ); +} + +export default Debug; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/utils.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/utils.ts new file mode 100644 index 0000000000..7fc82f087e --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Debug/utils.ts @@ -0,0 +1,72 @@ +import DataObjectIcon from "@mui/icons-material/DataObject"; +import TextSnippetIcon from "@mui/icons-material/TextSnippet"; +import FolderIcon from "@mui/icons-material/Folder"; +import DatasetIcon from "@mui/icons-material/Dataset"; +import { SvgIconComponent } from "@mui/icons-material"; + +//////////////////////////////////////////////////////////////// +// Types +//////////////////////////////////////////////////////////////// + +export type FileType = "json" | "file" | "matrix"; + +export interface File { + fileType: FileType; + filePath: string; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type TreeData = Record | string; + +//////////////////////////////////////////////////////////////// +// Utils +//////////////////////////////////////////////////////////////// + +/** + * Maps file types and folder to their corresponding icon components. + */ +const iconByFileType: Record = { + matrix: DatasetIcon, + json: DataObjectIcon, + folder: FolderIcon, + file: TextSnippetIcon, +} as const; + +/** + * Gets the icon component for a given file type or folder. + * @param {FileType | "folder"} type - The type of the file or "folder". + * @returns {SvgIconComponent} The corresponding icon component. + */ +export const getFileIcon = (type: FileType | "folder"): SvgIconComponent => { + return iconByFileType[type] || TextSnippetIcon; +}; + +/** + * Determines the file type based on the tree data. + * @param {TreeData} treeData - The data of the tree item. + * @returns {FileType | "folder"} The determined file type or "folder". + */ +export const determineFileType = (treeData: TreeData): FileType | "folder" => { + if (typeof treeData === "string") { + if (treeData.startsWith("matrix://")) { + return "matrix"; + } + if (treeData.startsWith("json://")) { + return "json"; + } + } + return typeof treeData === "object" ? "folder" : "file"; +}; + +/** + * Filters out specific keys from the tree data. + * @param {TreeData} data - The original tree data. + * @returns {TreeData} The filtered tree data. + */ +export const filterTreeData = (data: TreeData): TreeData => { + const excludedKeys = new Set(["Desktop", "study", "output", "logs"]); + + return Object.fromEntries( + Object.entries(data).filter(([key]) => !excludedKeys.has(key)), + ); +}; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyFileView.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyFileView.tsx deleted file mode 100644 index 1a35c18add..0000000000 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyFileView.tsx +++ /dev/null @@ -1,122 +0,0 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { useEffect, useState } from "react"; -import { AxiosError } from "axios"; -import debug from "debug"; -import { useSnackbar } from "notistack"; -import { useTranslation } from "react-i18next"; -import { Box, Button } from "@mui/material"; -import UploadOutlinedIcon from "@mui/icons-material/UploadOutlined"; -import { - getStudyData, - importFile, -} from "../../../../../../../services/api/study"; -import { Header, Root, Content } from "./style"; -import useEnqueueErrorSnackbar from "../../../../../../../hooks/useEnqueueErrorSnackbar"; -import SimpleLoader from "../../../../../../common/loaders/SimpleLoader"; -import ImportDialog from "../../../../../../common/dialogs/ImportDialog"; - -const logErr = debug("antares:createimportform:error"); - -interface PropTypes { - study: string; - url: string; - refreshView: () => void; - filterOut: Array; -} - -function StudyFileView(props: PropTypes) { - const { study, url, filterOut, refreshView } = props; - const { enqueueSnackbar } = useSnackbar(); - const enqueueErrorSnackbar = useEnqueueErrorSnackbar(); - const [t] = useTranslation(); - const [data, setData] = useState(); - const [loaded, setLoaded] = useState(false); - const [isEditable, setEditable] = useState(true); - const [formattedPath, setFormattedPath] = useState(""); - const [openImportDialog, setOpenImportDialog] = useState(false); - - const loadFileData = async () => { - setData(undefined); - setLoaded(false); - try { - const res = await getStudyData(study, url); - if (Array.isArray(res)) { - setData(res.join("\n")); - } else { - setData(res); - } - } catch (e) { - enqueueErrorSnackbar(t("studies.error.retrieveData"), e as AxiosError); - } finally { - setLoaded(true); - } - }; - - const onImport = async (file: File) => { - try { - await importFile(file, study, formattedPath); - } catch (e) { - logErr("Failed to import file", file, e); - enqueueErrorSnackbar(t("studies.error.saveData"), e as AxiosError); - } - refreshView(); - enqueueSnackbar(t("studies.success.saveData"), { - variant: "success", - }); - }; - - useEffect(() => { - const urlParts = url.split("/"); - const tmpUrl = urlParts.filter((item) => item); - setFormattedPath(tmpUrl.join("/")); - if (tmpUrl.length > 0) { - setEditable(!filterOut.includes(tmpUrl[0])); - } - if (urlParts.length < 2) { - enqueueSnackbar(t("studies.error.retrieveData"), { - variant: "error", - }); - return; - } - loadFileData(); - }, [url, filterOut]); - - return ( - <> - {data && ( - - {isEditable && ( -
- -
- )} - - {data} - -
- )} - {!loaded && ( - - - - )} - {openImportDialog && ( - setOpenImportDialog(false)} - onImport={onImport} - /> - )} - - ); -} - -export default StudyFileView; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyJsonView.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyJsonView.tsx deleted file mode 100644 index bb84bc90c2..0000000000 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyJsonView.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { useState, useEffect } from "react"; -import { AxiosError } from "axios"; -import { useSnackbar } from "notistack"; -import { useTranslation } from "react-i18next"; -import SaveIcon from "@mui/icons-material/Save"; -import { Box, Button, Typography } from "@mui/material"; -import { - editStudy, - getStudyData, -} from "../../../../../../../services/api/study"; -import useEnqueueErrorSnackbar from "../../../../../../../hooks/useEnqueueErrorSnackbar"; -import { Header, Root } from "./style"; -import SimpleLoader from "../../../../../../common/loaders/SimpleLoader"; -import JSONEditor from "../../../../../../common/JSONEditor"; - -interface PropTypes { - data: string; - study: string; - filterOut: Array; -} - -function StudyJsonView(props: PropTypes) { - const { data, study, filterOut } = props; - const { enqueueSnackbar } = useSnackbar(); - const enqueueErrorSnackbar = useEnqueueErrorSnackbar(); - const [t] = useTranslation(); - const [jsonData, setJsonData] = useState(); - const [loaded, setLoaded] = useState(false); - const [saveAllowed, setSaveAllowed] = useState(false); - const [isEditable, setEditable] = useState(true); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const handleJsonChange = (newJsonData: any) => { - setJsonData(newJsonData); - setSaveAllowed(true); - }; - - const saveData = async () => { - const tmpDataPath = data.split("/").filter((item) => item); - const tmpPath = tmpDataPath.join("/"); - if (loaded && jsonData) { - try { - await editStudy(jsonData, study, tmpPath); - enqueueSnackbar(t("studies.success.saveData"), { - variant: "success", - }); - setSaveAllowed(false); - } catch (e) { - enqueueErrorSnackbar(t("studies.error.saveData"), e as AxiosError); - } - } else { - enqueueSnackbar(t("studies.error.saveData"), { variant: "error" }); - } - }; - - useEffect(() => { - (async () => { - setJsonData(undefined); - setLoaded(false); - const tmpDataPath = data.split("/").filter((item) => item); - if (tmpDataPath.length > 0) { - setEditable(!filterOut.includes(tmpDataPath[0])); - } - try { - const res = await getStudyData(study, data, -1); - setJsonData(res); - setSaveAllowed(false); - } catch (e) { - enqueueErrorSnackbar(t("studies.error.retrieveData"), e as AxiosError); - } finally { - setLoaded(true); - } - })(); - }, [data, filterOut]); - - return ( - - {isEditable && ( -
- -
- )} - {jsonData && ( - - console.log(json)} - modes={["tree", "code"]} - enableSort={false} - enableTransform={false} - /> - - )} - {!loaded && ( - - - - )} -
- ); -} - -export default StudyJsonView; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyMatrixView/StudyMatrixView.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyMatrixView/StudyMatrixView.tsx deleted file mode 100644 index 3c6f113dca..0000000000 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyMatrixView/StudyMatrixView.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import { useEffect, useState } from "react"; -import { AxiosError } from "axios"; -import debug from "debug"; -import { useSnackbar } from "notistack"; -import { useTranslation } from "react-i18next"; -import { Box, Typography, Divider, ButtonGroup, Button } from "@mui/material"; -import TableViewIcon from "@mui/icons-material/TableView"; -import BarChartIcon from "@mui/icons-material/BarChart"; -import GetAppOutlinedIcon from "@mui/icons-material/GetAppOutlined"; -import { - getStudyData, - importFile, -} from "../../../../../../../../services/api/study"; -import { - MatrixType, - MatrixEditDTO, -} from "../../../../../../../../common/types"; -import { Header, Root, Content } from "../style"; -import usePromiseWithSnackbarError from "../../../../../../../../hooks/usePromiseWithSnackbarError"; -import { StyledButton } from "./style"; -import useEnqueueErrorSnackbar from "../../../../../../../../hooks/useEnqueueErrorSnackbar"; -import SimpleContent from "../../../../../../../common/page/SimpleContent"; -import ImportDialog from "../../../../../../../common/dialogs/ImportDialog"; -import SimpleLoader from "../../../../../../../common/loaders/SimpleLoader"; -import EditableMatrix from "../../../../../../../common/EditableMatrix"; -import { - editMatrix, - getStudyMatrixIndex, -} from "../../../../../../../../services/api/matrix"; - -const logErr = debug("antares:createimportform:error"); - -interface PropTypes { - study: string; - url: string; - filterOut: Array; -} - -function StudyMatrixView(props: PropTypes) { - const { study, url, filterOut } = props; - const { enqueueSnackbar } = useSnackbar(); - const enqueueErrorSnackbar = useEnqueueErrorSnackbar(); - const [t] = useTranslation(); - const [data, setData] = useState(); - const [loaded, setLoaded] = useState(false); - const [toggleView, setToggleView] = useState(true); - const [openImportDialog, setOpenImportDialog] = useState(false); - const [isEditable, setEditable] = useState(true); - const [formattedPath, setFormattedPath] = useState(""); - - const { data: matrixIndex } = usePromiseWithSnackbarError( - () => getStudyMatrixIndex(study, formattedPath), - { - errorMessage: t("matrix.error.failedToRetrieveIndex"), - deps: [study, formattedPath], - }, - ); - - //////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////// - - const loadFileData = async () => { - setData(undefined); - setLoaded(false); - try { - const res = await getStudyData(study, url); - if (typeof res === "string") { - const fixed = res - .replace(/NaN/g, '"NaN"') - .replace(/Infinity/g, '"Infinity"'); - setData(JSON.parse(fixed)); - } else { - setData(res); - } - } catch (e) { - enqueueErrorSnackbar(t("data.error.matrix"), e as AxiosError); - } finally { - setLoaded(true); - } - }; - - //////////////////////////////////////////////////////////////// - // Event Handlers - //////////////////////////////////////////////////////////////// - - const handleUpdate = async (change: MatrixEditDTO[], source: string) => { - if (source !== "loadData" && source !== "updateData") { - try { - if (change.length > 0) { - await editMatrix(study, formattedPath, change); - enqueueSnackbar(t("matrix.success.matrixUpdate"), { - variant: "success", - }); - } - } catch (e) { - enqueueErrorSnackbar(t("matrix.error.matrixUpdate"), e as AxiosError); - } - } - }; - - const handleImport = async (file: File) => { - try { - await importFile(file, study, formattedPath); - } catch (e) { - logErr("Failed to import file", file, e); - enqueueErrorSnackbar(t("variants.error.import"), e as AxiosError); - } finally { - enqueueSnackbar(t("variants.success.import"), { - variant: "success", - }); - loadFileData(); - } - }; - - useEffect(() => { - const urlParts = url.split("/"); - const tmpUrl = urlParts.filter((item) => item); - setFormattedPath(tmpUrl.join("/")); - if (tmpUrl.length > 0) { - setEditable(!filterOut.includes(tmpUrl[0])); - } - if (urlParts.length < 2) { - enqueueSnackbar(t("studies.error.retrieveData"), { - variant: "error", - }); - return; - } - loadFileData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [url, filterOut, enqueueSnackbar, t]); - - //////////////////////////////////////////////////////////////// - // JSX - //////////////////////////////////////////////////////////////// - - return ( - - -
- - {t("xpansion.timeSeries")} - - - {loaded && data && data.columns?.length > 1 && ( - - setToggleView((prev) => !prev)}> - {toggleView ? ( - - ) : ( - - )} - - - )} - {isEditable && ( - - )} - -
- - {!loaded && } - {loaded && data && data.columns?.length >= 1 ? ( - - ) : ( - loaded && ( - } - onClick={() => setOpenImportDialog(true)} - > - {t("global.import")} - - } - /> - ) - )} -
- {openImportDialog && ( - setOpenImportDialog(false)} - onImport={handleImport} - /> - )} -
- ); -} - -export default StudyMatrixView; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyMatrixView/style.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyMatrixView/style.ts deleted file mode 100644 index 0e6eb5513b..0000000000 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/StudyMatrixView/style.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { styled, Button } from "@mui/material"; - -export const StyledButton = styled(Button)(({ theme }) => ({ - backgroundColor: "rgba(180, 180, 180, 0.09)", - color: "white", - borderRight: "none !important", - "&:hover": { - color: "white", - backgroundColor: theme.palette.secondary.main, - }, - "&:disabled": { - backgroundColor: theme.palette.secondary.dark, - color: "white !important", - }, -})); - -export default {}; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/index.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/index.tsx deleted file mode 100644 index 8cc91350af..0000000000 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyDataView/index.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { CSSProperties, ReactNode } from "react"; -import { Box } from "@mui/material"; -import StudyFileView from "./StudyFileView"; -import StudyJsonView from "./StudyJsonView"; -import StudyMatrixView from "./StudyMatrixView/StudyMatrixView"; -import { StudyDataType } from "../../../../../../../common/types"; - -interface PropTypes { - study: string; - type: StudyDataType; - data: string; - studyData: any; - setStudyData: (elm: any) => void; -} - -interface RenderData { - css: CSSProperties; - data: ReactNode; -} - -function StudyDataView(props: PropTypes) { - const { study, type, data, studyData, setStudyData } = props; - const filterOut = ["output", "logs", "Desktop"]; - - const refreshView = () => { - setStudyData({ ...studyData }); - }; - - const renderData = (): RenderData => { - if (type === "file") { - return { - css: { overflow: "auto" }, - data: ( - - ), - }; - } - if (type === "matrix" || type === "matrixfile") { - return { - css: { overflow: "auto" }, - data: ( - - ), - }; - } - return { - css: { overflow: "hidden", paddingTop: "0px" }, - data: , - }; - }; - - const rd = renderData(); - return ( - - {rd.data} - - ); -} - -export default StudyDataView; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyTreeView/index.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyTreeView/index.tsx deleted file mode 100644 index fc7e2157e5..0000000000 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyTreeView/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* eslint-disable jsx-a11y/interactive-supports-focus */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -import { Box } from "@mui/material"; -import { TreeItem, TreeView } from "@mui/lab"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import ChevronRightIcon from "@mui/icons-material/ChevronRight"; -import { StudyDataType } from "../../../../../../../common/types"; -import { getStudyParams } from "./utils"; - -interface ItemPropTypes { - itemkey: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: any; - path?: string; - viewer: (type: StudyDataType, data: string) => void; -} - -function StudyTreeItem(props: ItemPropTypes) { - const { itemkey, data, path = "/", viewer } = props; - - // if not an object then it's a RawFileNode or MatrixNode - // here we have to decide which viewer to use - const params = getStudyParams(data, path, itemkey); - if (params) { - const FileIcon = params.icon; - return ( - viewer(params.type, params.data)} - > - - {itemkey} - - } - /> - ); - } - - // else this is a folder containing.. stuff (recursion) - return ( - - {Object.keys(data).map((childkey) => ( - - ))} - - ); -} - -interface PropTypes { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: any; - view: (type: StudyDataType, data: string) => void; -} - -function StudyTreeView(props: PropTypes) { - const { data, view } = props; - return ( - } - defaultExpandIcon={} - > - {Object.keys(data).map((key) => ( - - ))} - - ); -} - -StudyTreeItem.defaultProps = { - path: "/", -}; - -export default StudyTreeView; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyTreeView/utils.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyTreeView/utils.ts deleted file mode 100644 index b5395ccaba..0000000000 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/StudyTreeView/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -import IntegrationInstructionsIcon from "@mui/icons-material/IntegrationInstructions"; -import TextSnippetIcon from "@mui/icons-material/TextSnippet"; -import { SvgIconComponent } from "@mui/icons-material"; -import { StudyDataType } from "../../../../../../../common/types"; - -export interface StudyParams { - type: StudyDataType; - icon: SvgIconComponent; - data: string; -} - -export const getStudyParams = ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: any, - path: string, - itemkey: string, -): StudyParams | undefined => { - if (typeof data === "string") { - const tmp = data.split("://"); - if (tmp && tmp.length > 0) { - if (tmp[0] === "json" || tmp[1].endsWith(".json")) { - return { - type: "json", - data: `${path}/${itemkey}`, - icon: IntegrationInstructionsIcon, - }; - } - return { - type: tmp[0] as StudyDataType, - icon: TextSnippetIcon, - data: `${path}/${itemkey}`, - }; - } - return { type: "file", icon: TextSnippetIcon, data: `${path}/${itemkey}` }; - } - return undefined; -}; - -export default {}; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/index.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/index.tsx deleted file mode 100644 index 3ba45612c8..0000000000 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/DebugView/index.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { useEffect, useState, useCallback } from "react"; -import { AxiosError } from "axios"; -import debug from "debug"; -import { useTranslation } from "react-i18next"; -import { useOutletContext } from "react-router-dom"; -import { Box } from "@mui/material"; -import { getStudyData } from "../../../../../../services/api/study"; -import StudyTreeView from "./StudyTreeView"; -import StudyDataView from "./StudyDataView"; -import { StudyDataType, StudyMetadata } from "../../../../../../common/types"; -import useEnqueueErrorSnackbar from "../../../../../../hooks/useEnqueueErrorSnackbar"; -import SimpleLoader from "../../../../../common/loaders/SimpleLoader"; - -const logError = debug("antares:studyview:error"); - -interface ElementView { - type: StudyDataType; - data: string; -} - -function DebugView() { - const { study } = useOutletContext<{ study: StudyMetadata }>(); - const [t] = useTranslation(); - const enqueueErrorSnackbar = useEnqueueErrorSnackbar(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const [studyData, setStudyData] = useState(); - const [loaded, setLoaded] = useState(false); - const [elementView, setElementView] = useState(); - - const initStudyData = useCallback( - async (sid: string) => { - setLoaded(false); - try { - const data = await getStudyData(sid, "", -1); - setStudyData(data); - } catch (e) { - enqueueErrorSnackbar(t("studies.error.retrieveData"), e as AxiosError); - logError("Failed to fetch study data", sid, e); - } finally { - setLoaded(true); - } - }, - [enqueueErrorSnackbar, t], - ); - - useEffect(() => { - if (study) { - initStudyData(study.id); - } - }, [study, initStudyData]); - - return ( - - {study && studyData && ( - <> - - - {studyData && ( - setElementView({ type, data })} - /> - )} - - - - - {elementView && ( - - )} - - - - )} - {!loaded && studyData === undefined && } - - ); -} - -export default DebugView; diff --git a/webapp/src/components/common/JSONEditor/dark-theme.css b/webapp/src/components/common/JSONEditor/dark-theme.css index fb44f64326..18029e871d 100644 --- a/webapp/src/components/common/JSONEditor/dark-theme.css +++ b/webapp/src/components/common/JSONEditor/dark-theme.css @@ -1,7 +1,3 @@ -//////////////////////////////////////////////////////////////// -// JSONEditor -//////////////////////////////////////////////////////////////// - div.jsoneditor { border: 2px solid #212c38; /* Dark gray border for consistency */ color: #ffffff; @@ -102,6 +98,7 @@ div.jsoneditor-contextmenu .jsoneditor-menu button { background-color: #222333; color: #ffffff; /* White color for context menu items */ } + .jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected, .jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:focus, .jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:hover { @@ -120,11 +117,6 @@ div.jsoneditor-contextmenu .jsoneditor-menu button { color: #ffffff; /* White color for search input */ } -//////////////////////////////////////////////////////////////// -// Ace editor -//////////////////////////////////////////////////////////////// - - div.ace_jsoneditor .ace_editor { background-color: #222333; color: #ffffff;