Create an image detail modal

This recipe covers a modal which can show an image and additional data. It's mostly meant to serve as an example for displaying an image in a modal while maintaining its aspect ratio.

This is by no means an example for a be-all and end-all image detail modal. Assumptions have been made, and functionality has been omitted, in favour of brevity. It should be carefully evaluated and refined when building a production worthy image detail modal.

Recommended reading

  • The 'Create a modal' guide for instructions on creating and setting up a modal.
ImageDetailsModal.jsx.js
import React, { useCallback, useEffect, useState } from 'react';

// More information about each of the following imports can be found in the FontoXML Editor API
// section of the documentation (http://documentation.fontoxml.com/api/).
import {
    Button,
    ContainedImage,
    Flex,
    HorizontalSeparationLine,
    KeyValueList,
    Modal,
    ModalBody,
    ModalContent,
    ModalFooter,
    ModalHeader,
    SpinnerIcon,
    StateMessage
} from 'fds/components';

import documentsManager from 'fontoxml-documents/src/documentsManager.js';
import FxImageLoader from 'fontoxml-fx/src/FxImageLoader.jsx';
import referencesManager from 'fontoxml-references/src/referencesManager.js';

function ImageDetailsModal({ cancelModal, data }) {
    const [target, setTarget] = useState(null);
    const [metadata, setMetadata] = useState(null);

    const handleKeyDown = useCallback(
        event => {
            switch (event.key) {
                case 'Escape':
                    cancelModal();
                    break;
            }
        },
        [cancelModal]
    );

    // Upon loading the modal we use the documentsManager to get the image node. This is then used
    // to get the value of the attribute containing the permanentid. This passed to the retrieveSingle
    // function of the referencesManager to get the actual target and any metadata associated with
    // the image.
    useEffect(() => {
        const contextNode = documentsManager.getNodeById(data.contextNodeId);
        const imagePermanentId = contextNode.getAttribute('href');

        referencesManager.retrieveSingle(imagePermanentId).then(result => {
            setTarget(result.target);
            setMetadata(result.metadata);
        });
    }, [data.contextNodeId]);

    // The FxImageLoader component is used to load the actual image. This component returns an Object
    // which we can use to display the image in our modal. The modal contains three StateMessage
    // components for various states and uses Flex components to display the image and any metadata
    // associated with the image. The image is either shown full size, or if the size would exceed
    // that of the modal will use the maximum width while maintaining its aspect ratio.
    return (
        <Modal size="m" isFullHeight onKeyDown={handleKeyDown}>
            <ModalHeader icon="image" title="Image" />

            <ModalBody>
                <ModalContent alignItems="center" flex="1" flexDirection="column">
                    {target && (
                        <FxImageLoader remoteId={target} type="web">
                            {({ isErrored, isLoading, imageData }) => {
                                if (isErrored) {
                                    return (
                                        <StateMessage
                                            connotation="warning"
                                            paddingSize="m"
                                            visual="exclamation-triangle"
                                            message="Could not load image."
                                        />
                                    );
                                }

                                if (isLoading) {
                                    return (
                                        <StateMessage
                                            paddingSize="m"
                                            visual={<SpinnerIcon />}
                                            message="Loading image..."
                                        />
                                    );
                                }

                                return (
                                    <Flex flex="1" flexDirection="column">
                                        <Flex
                                            flex="1"
                                            flexDirection="column"
                                            justifyContent="center"
                                            paddingSize="l"
                                            spaceSize="m"
                                        >
                                            <ContainedImage src={imageData.dataUrl} />
                                        </Flex>

                                        {metadata && metadata.properties && (
                                            <Flex flex="none" flexDirection="column">
                                                <Flex paddingSize={{ horizontal: 'l' }}>
                                                    <HorizontalSeparationLine />
                                                </Flex>

                                                <KeyValueList
                                                    valueByKey={metadata.properties}
                                                    scrollLimit={5}
                                                    paddingSize="l"
                                                />
                                            </Flex>
                                        )}
                                    </Flex>
                                );
                            }}
                        </FxImageLoader>
                    )}
                    {!target && (
                        <StateMessage
                            paddingSize="m"
                            visual={<SpinnerIcon />}
                            message="Loading image..."
                        />
                    )}
                </ModalContent>
            </ModalBody>

            <ModalFooter>
                <Button label="Close" onClick={cancelModal} />
            </ModalFooter>
        </Modal>
    );
}

export default ImageDetailsModal;
Was this page helpful?