Source

Utils/Dialogs/DetailsDialog/UnionsDialog.js

import React from "react";
import PropTypes from "prop-types";
import { fetchObject, fetchUnion } from "../../utilFunctions/fetchFunctions";
import { getItemName } from "../../utilFunctions/parseItems";
import { MultiColumns } from "../../Containers";
import ColouredTitle from "../../DataDisplay/ColouredTitle";
import { FullscreenDialog, FullscreenHeader,  } from "../FullscreenDialog";
import ObjectTable from "../../DataDisplay/ObjectTable";
import TableItemsList from "../../DataDisplay/TableItemsList";
import TablesList from "../../DataDisplay/TablesList";
import TraitsTable from "../../DataDisplay/TraitsTable";
import StyledCircularProgress from "../../Feedback/StyledCircularProgress";
import { AttributesMenu } from "../../Menus/AttributesMenu";

/**
 * <h3>Overview</h3>
 * The fullscreen dialog with details of a selected union.
 *
 * @constructor
 * @category Dialogs
 * @subcategory Details Dialogs
 * @param {Object} props - Any other props will be forwarded to the {@link FullscreenDialog} element.
 * @param {Object} props.item - The selected union with it's characteristics.
 * @param {number} props.item.id - The id of a selected union.
 * @param {Object} props.item.name - The name of a selected union.
 * @param {number|string} props.item.name.primary - The part of a name coloured with a primary colour.
 * @param {number|string} props.item.name.secondary - The part of a name coloured with a secondary colour.
 * @param {function} props.item.name.toString - Returns name as a single string.
 * @param {Object} props.item.traits - The characteristics of a selected union in a key-value form.
 * @param {number} props.item.traits.Accuracy_of_approximation
 * @param {number} props.item.traits.Quality_of_approximation
 * @param {function} props.item.toFilter - Returns item in an easy to filter form.
 * @param {string} props.objectGlobalName - The global visible object name used by all tabs as reference.
 * @param {function} props.onClose - Callback fired when the component requests to be closed.
 * @param {function} props.onSnackbarOpen - Callback fired when the component requests to display an error.
 * @param {boolean} props.open - If <code>true</code> the Dialog is open.
 * @param {string} props.projectId - The identifier of a selected project.
 * @param {string} props.serverBase - The host in the URL of an API call.
 * @returns {React.PureComponent}
 */
class UnionsDialog extends React.PureComponent {
    constructor(props) {
        super(props);

        this.state = {
            loading: {
                unionProperties: false,
                unionPropertyContent: false,
                object: false
            },
            requestIndex: {
                unionProperties: 0,
                unionPropertyContent: 0,
                object: 0
            },
            unionProperties: {},
            unionPropertyIndex: -1,
            unionPropertyContent: [],
            objectNames: [],
            object: null,
            attributes: [],
            objectIndex: -1,
            attributesMenuEl: null
        };

        this._unionPropertyKeys = [
            "objects",
            "lowerApproximation",
            "upperApproximation",
            "boundary",
            "positiveRegion",
            "negativeRegion",
            "boundaryRegion"
        ];
    }

    componentDidMount() {
        this._isMounted = true;

        this.getUnionProperties();
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        if (prevProps.projectId !== this.props.projectId) {
            this.setState({
                unionProperties: {},
                unionPropertyIndex: -1,
                unionPropertyContent: [],
                objectNames: [],
                object: null,
                attributes: [],
                objectIndex: -1
            });

            return;
        }

        if (prevProps.item.id !== this.props.item.id) {
            this.setState({
                unionProperties: {},
                unionPropertyIndex: undefined,
                unionPropertyContent: [],
                objectIndex: undefined
            }, () => this.getUnionProperties());
        }
    }

    componentWillUnmount() {
        this._isMounted = false;
    }

    getUnionProperties = () => {
        let localRequestIndex = 0;

        this.setState(({loading, requestIndex}) => {
            localRequestIndex = requestIndex.unionProperties;

            return {
                loading: { ...loading, unionProperties: true },
                requestIndex: { ...requestIndex, unionProperties: localRequestIndex + 1 }
            };
        }, () => {
            const { item: { id: unionIndex }, projectId, serverBase } = this.props;
            const pathParams = { projectId, unionIndex, arrayPropertyType: undefined };

            fetchUnion(
                pathParams, serverBase
            ).then(result => {
                if (this._isMounted && Object.keys(result).length === this._unionPropertyKeys.length) {
                    this.setState(({requestIndex}) => {
                        if (requestIndex.unionProperties !== localRequestIndex + 1) {
                            return { };
                        }

                        return {
                            requestIndex: { ...requestIndex, unionProperties: 0 },
                            unionProperties: result
                        };
                    });
                }
            }).catch(exception => {
                this.props.onSnackbarOpen(exception);
            }).finally(() => {
                if (this._isMounted) {
                    this.setState(({loading}) => ({
                        loading: { ...loading, unionProperties: false }
                    }));
                }
            });
        });
    }

    getUnionPropertyContent = () => {
        let localRequestIndex = 0;

        this.setState(({loading, requestIndex}) => {
            localRequestIndex = requestIndex.unionPropertyContent;

            return {
                loading: { ...loading, unionPropertyContent: true },
                requestIndex: { ...requestIndex, unionPropertyContent: localRequestIndex + 1 }
            };
        }, () => {
            const { item: { id: unionIndex }, projectId, serverBase } = this.props;
            const { unionPropertyIndex } = this.state;
            const pathParams = { projectId, unionIndex, arrayPropertyType: this._unionPropertyKeys[unionPropertyIndex] };

            fetchUnion(
                pathParams, serverBase
            ).then(result => {
                if (this._isMounted && result != null
                    && result.hasOwnProperty("objectNames") && result.hasOwnProperty("value")) {
                    this.setState(({requestIndex}) => {
                        if (requestIndex.unionPropertyContent !== localRequestIndex + 1) {
                            return { };
                        }

                        return {
                            requestIndex: { ...requestIndex, unionPropertyContent: 0 },
                            unionPropertyContent: result.value,
                            objectNames: result.objectNames
                        };
                    });
                }
            }).catch(exception => {
                this.props.onSnackbarOpen(exception);
            }).finally(() => {
                if (this._isMounted) {
                    this.setState(({loading}) => ({
                       loading: { ...loading, unionPropertyContent: false }
                    }));
                }
            });
        });
    }

    getObject = (objectIndex, finallyCallback) => {
        let localRequestIndex = 0;

        this.setState(({loading, requestIndex}) => {
            localRequestIndex = requestIndex.object;

            return {
                loading: { ...loading, object: true },
                requestIndex: { ...requestIndex, object: localRequestIndex + 1 }
            }
        }, () => {
            const { projectId, serverBase } = this.props;
            const { attributes } = this.state;

            const resource = "unions";
            const pathParams = { projectId };
            const queryParams = { objectIndex, isAttributes: attributes.length === 0 }

            fetchObject(
                resource, pathParams, queryParams, serverBase
            ).then(result => {
                if (this._isMounted && result != null) {
                    this.setState(({requestIndex, attributes}) => {
                        if (requestIndex.object !== localRequestIndex + 1) {
                            return { };
                        }

                        return {
                            requestIndex: { ...requestIndex, object: 0 },
                            object: result.value,
                            attributes: result.hasOwnProperty("attributes") ? result.attributes : attributes
                        };
                    });
                }
            }).catch(exception => {
                this.props.onSnackbarOpen(exception);
            }).finally(() => {
                if (this._isMounted) {
                    this.setState(({loading}) => ({
                        loading: { ...loading, object: false }
                    }), () => {
                        if (typeof finallyCallback === "function") finallyCallback();
                    });
                }
            });
        });
    }

    onExited = () => {
        this.setState({
            unionPropertyIndex: -1,
            objectIndex: -1,
        })
    };

    onUnionPropertySelected = (index) => {
        this.setState({
            unionPropertyIndex: index,
            objectIndex: -1
        }, () => this.getUnionPropertyContent());
    };

    onObjectSelected = (index) => {
        const finallyCallback = () => this.setState({ objectIndex: index });
        this.getObject(index, finallyCallback);
    };

    onAttributesMenuOpen = (event) => {
        const currentTarget = event.currentTarget;

        this.setState({
            attributesMenuEl: currentTarget
        });
    }

    onAttributesMenuClose = () => {
        this.setState({
            attributesMenuEl: null
        })
    }

    onObjectNamesChange = (names) => {
        this.setState({
            objectNames: names
        });
    }

    getUnionsTitle = () => {
        const { item } = this.props;

        return (
            <ColouredTitle
                text={[
                    { primary: "Selected union:" },
                    { ...item.name, brackets: false }
                ]}
            />
        )
    };

    getName = (index) => {
        const { unionPropertyContent, objectNames } = this.state;
        return getItemName(unionPropertyContent.indexOf(index), objectNames).toString();
    }

    render() {
        const {
            loading,
            unionProperties,
            unionPropertyIndex,
            unionPropertyContent,
            object,
            attributes,
            objectIndex,
            attributesMenuEl
        } = this.state;

        const { item, objectGlobalName, open, projectId, serverBase } = this.props;

        return (
            <FullscreenDialog keepMounted={true} open={open} onClose={this.props.onClose}>
                <FullscreenHeader
                    id={"unions-details-header"}
                    onClose={this.props.onClose}
                    title={this.getUnionsTitle()}
                />
                <MultiColumns>
                    <div id={"unions-tables"} style={{display: "flex", flexDirection: "column"}}>
                        {loading.unionProperties ?
                            <StyledCircularProgress />
                            :
                            <TablesList
                                headerText={"Union's characteristics"}
                                tableIndex={unionPropertyIndex}
                                onTableSelected={this.onUnionPropertySelected}
                                tables={unionProperties}
                            />
                        }
                    </div>
                    <div id={"unions-table-content"} style={{display: "flex", flexDirection: "column"}}>
                        {loading.unionPropertyContent &&
                            <StyledCircularProgress />
                        }
                        {!loading.unionPropertyContent && unionPropertyIndex > -1 &&
                            <TableItemsList
                                getName={this.getName}
                                headerText={Object.keys(unionProperties)[unionPropertyIndex]}
                                itemIndex={objectIndex}
                                onItemInTableSelected={this.onObjectSelected}
                                onSettingsClick={this.onAttributesMenuOpen}
                                table={unionPropertyContent}
                            />
                        }
                    </div>
                    <div id={"unions-table-item"} style={{display: "flex", flexDirection: "column"}}>
                        <div style={{minHeight: "30%"}}>
                            <TraitsTable
                                columnsLabels={{key: "Name", value: "Value"}}
                                traits={item.traits}
                            />
                        </div>
                        <div style={{display: "flex", flexDirection: "column", flexGrow: 1}}>
                            {loading.object &&
                                <StyledCircularProgress />
                            }
                            {!loading.object && objectIndex > -1 &&
                                <ObjectTable
                                    attributes={attributes}
                                    object={object}
                                    objectHeader={this.getName(objectIndex)}
                                />
                            }
                        </div>
                    </div>
                </MultiColumns>
                <AttributesMenu
                    ListProps={{
                        id: 'unions-details-desc-attributes-menu'
                    }}
                    MuiMenuProps={{
                        anchorEl: attributesMenuEl,
                        onClose: this.onAttributesMenuClose
                    }}
                    objectGlobalName={objectGlobalName}
                    onObjectNamesChange={this.onObjectNamesChange}
                    onSnackbarOpen={this.props.onSnackbarOpen}
                    projectId={projectId}
                    resource={"unions"}
                    serverBase={serverBase}
                    queryParams={{subject: item.id, set: this._unionPropertyKeys[unionPropertyIndex]}}
                />
            </FullscreenDialog>
        );
    }
}

UnionsDialog.propTypes = {
    item: PropTypes.exact({
        id: PropTypes.number,
        name: PropTypes.shape({
            primary: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
            secondary: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
            toString: PropTypes.func
        }),
        traits: PropTypes.exact({
            'Accuracy of approximation': PropTypes.number,
            'Quality of approximation': PropTypes.number,
        }),
        toFilter: PropTypes.func
    }),
    objectGlobalName: PropTypes.string,
    onClose: PropTypes.func,
    onSnackbarOpen: PropTypes.func,
    open: PropTypes.bool,
    projectId: PropTypes.string.isRequired,
    serverBase: PropTypes.string
};

export default UnionsDialog;