Source

Header/Elements/FilesDetails.js

import React from "react";
import PropTypes from "prop-types";
import CustomPopper from "../../Utils/Surfaces/CustomPopper";
import CustomTooltip from "../../Utils/DataDisplay/CustomTooltip";
import { StyledIconButton } from "../../Utils/Buttons";
import Popper from "@material-ui/core/Popper";
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
import Grow from "@material-ui/core/Grow";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import FileQuestion from "mdi-material-ui/FileQuestion";
import {fetchProjectDetails} from "../../Utils/utilFunctions/fetchFunctions";

function Wrapper(props) {
    const { component, ...other } = props;

    return React.createElement(component, {...other});
}

Wrapper.propTypes = {
    component: PropTypes.elementType.isRequired
};

// To get unblurred tooltip text in Google Chrome
const disableGPUOptions = {
    modifiers: {
        computeStyle: {
            enabled: true,
            gpuAcceleration: false
        }
    }
};

/**
 * <h3>Overview</h3>
 * Presents files that were used to create current project.
 * Idea for a composition was taken from this
 * <a href="https://material-ui.com/components/button-group/#split-button" target="_blank">tutorial</a>.
 *
 * @constructor
 * @category Header
 * @subcategory Elements
 * @param {Object} props
 * @param {boolean} props.disableGpu - If <code>true</code> tooltip text will be unblurred in Google Chrome.
 * @param {function} props.onSnackbarOpen - Callback fired when the component requests to display an error.
 * @param {string} props.projectId - The identifier of a selected project.
 * @param {string} props.serverBase - The host and port in the URL of an API call.
 * @param {React.ElementType} props.WrapperComponent - The HTML element that will be used as a Wrapper.
 * @param {Object} props.WrapperProps - The props applied to the Wrapper element.
 * @returns {React.PureComponent}
 */
class FilesDetails extends React.PureComponent {
    constructor(props) {
        super(props);

        this.state = {
            open: false,
            files: []
        };

        this.anchorRef = React.createRef();
    }

    componentDidMount() {
        this._isMounted = true;
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        if (!prevState.open && this.state.open) {
            this.getProjectDetails();
        }
    }

    componentWillUnmount() {
        this._isMounted = false;
    }

    getProjectDetails = () => {
        this.setState({
            files: [{label: "Loading details"}]
        }, () => {
            const { projectId, serverBase } = this.props;
            const pathParams = { projectId };

            fetchProjectDetails(
                pathParams, serverBase
            ).then(result => {
                if (this._isMounted && result != null) {
                    let files = []

                    if (result.hasOwnProperty("metadataFileName"))
                        files.push({ label: "Metadata", value: result.metadataFileName });

                    if (result.hasOwnProperty("dataFileName"))
                        files.push({ label: "Data", value: result.dataFileName });

                    if (result.hasOwnProperty("rulesFileName"))
                        files.push({ label: "Rules", value: result.rulesFileName });

                    if (result.hasOwnProperty("externalClassifiedDataFileName"))
                        files.push({ label: "Classified external data", value: result.externalClassifiedDataFileName });

                    this.setState({ files: files });
                }
            }).catch(exception => {
                this.props.onSnackbarOpen(exception);
            }).finally(() => {
                if (this._isMounted) {
                    this.setState(({files}) => {
                        if (files.length === 1 && files[0].hasOwnProperty("label")
                            && files[0].label === "Loading details") {

                            return { files: [{label: "Error when fetching details"}]};
                        }

                        return { files };
                    });
                }
            });
        });
    };

    onToggleButtonClick = () => {
        this.setState(({open}) => ({
            open: !open
        }));
    };

    onPopperClose = (event) => {
        if (this.anchorRef.current && this.anchorRef.current.contains(event.target)) {
            return;
        }

        this.setState({
            open: false
        });
    };

    render() {
        const { open, files } = this.state;
        const { disableGPU, WrapperComponent, WrapperProps } = this.props;

        return (
            <Wrapper component={WrapperComponent} {...WrapperProps}>
                <CustomTooltip title={"Show project's files details"}>
                    <StyledIconButton
                        aria-controls={open ? 'files-details' : undefined}
                        aria-expanded={open ? true : undefined}
                        aria-label={"files-details-button"}
                        aria-haspopup={"menu"}
                        ButtonRef={this.anchorRef}
                        onClick={this.onToggleButtonClick}
                    >
                        <FileQuestion />
                    </StyledIconButton>
                </CustomTooltip>
                <Popper
                    anchorEl={this.anchorRef.current}
                    aria-label={"files-details"}
                    open={open}
                    popperOptions={disableGPU ? disableGPUOptions : undefined}
                    role={undefined}
                    style={{zIndex: 2000}}
                    transition={true}
                >
                    {({ TransitionProps }) => (
                        <Grow {...TransitionProps}>
                            <CustomPopper style={{outline: "none"}}>
                                <ClickAwayListener onClickAway={this.onPopperClose}>
                                    <List>
                                        {files.map((file, index) => (
                                            <ListItem
                                                key={index}
                                                divider={index < files.length - 1}
                                            >
                                                <ListItemText
                                                    primary={file.label}
                                                    secondary={file.value}
                                                />
                                            </ListItem>
                                        ))}
                                    </List>
                                </ClickAwayListener>
                            </CustomPopper>
                        </Grow>
                    )}
                </Popper>
            </Wrapper>
        );
    };
}

FilesDetails.propTypes = {
    disableGPU: PropTypes.bool,
    onSnackbarOpen: PropTypes.func,
    projectId: PropTypes.string,
    serverBase: PropTypes.string,
    WrapperComponent: PropTypes.elementType,
    WrapperProps: PropTypes.object
};

FilesDetails.defaultProps = {
    disableGPU: true,
    WrapperComponent: "div"
};

export default FilesDetails;