react-icons/fa#FaTrash TypeScript Examples

The following examples show how to use react-icons/fa#FaTrash. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ManagedTitle.tsx    From personal-archive with MIT License 5 votes vote down vote up
Options: FC<{
  onEdit: () => void,
  onDelete: () => void,
}> = ({onEdit, onDelete}) => {
  const [isOpened, setOpened] = useState(false)

  return (
    <ButtonWrapper>
      <Popover
        content={
          <ButtonGroup>
            <Button
              iconLeft={<Edit/>}
              type="secondary"
              onClick={() => {
                setOpened(false)
                onEdit()
              }}
              size="small"
            >
              Edit
            </Button>
            <Button
              iconLeft={<FaTrash/>}
              type="secondary"
              onClick={() => {
                setOpened(false)
                onDelete()
              }}
              size="small"
            >
              Delete
            </Button>
          </ButtonGroup>
        }
        opened={isOpened}
        onClose={() => setOpened(false)}
      >
        <Button
          iconLeft={<ChevronDown/>}
          type="white"
          size="small"
          onClick={() => setOpened(!isOpened)}
        />
      </Popover>
    </ButtonWrapper>
  )
}
Example #2
Source File: NoteParagraphButtons.tsx    From personal-archive with MIT License 5 votes vote down vote up
NoteParagraphButtons: FC<Props> = ({paragraph, onMoveUp, onMoveDown, onReload}) => {
  const [opened, setOpened] = useState(false)
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const [_, deleteParagraph] = useRequestDeleteParagraph()
  const history = useHistory()

  const onDelete = () => {
    setOpened(false)
    confirm({
      message: 'delete paragraph?',
      onOK: () =>
        deleteParagraph(paragraph.id, paragraph.noteID)
          .then(() => onReload())
    })
  }

  return (
    <Wrapper>
      <Popover
        preferredPosition="bottom"
        opened={opened}
        onClose={() => setOpened(false)}
        content={
          <ButtonGroup>
            <Button
              iconLeft={<ChevronUp/>}
              onClick={() => onMoveUp(paragraph.seq)}
              type="secondary"
              size="small"
            >
              Up
            </Button>
            <Button
              iconLeft={<ChevronDown/>}
              onClick={() => onMoveDown(paragraph.seq)}
              type="secondary"
              size="small"
            >
              Down
            </Button>
            <Button
              iconLeft={<Edit/>}
              onClick={() => history.push(`/notes/${paragraph.noteID}/paragraphs/${paragraph.id}/edit`)}
              type="secondary"
              size="small"
            >
              Edit
            </Button>
            <Button
              iconLeft={<FaTrash/>}
              onClick={onDelete}
              type="secondary"
              size="small"
            >
              Delete
            </Button>
          </ButtonGroup>
        }
      >
        <Button
          iconLeft={<ChevronDown/>}
          type="white"
          onClick={() => setOpened(true)}
        />
      </Popover>
    </Wrapper>
  )
}
Example #3
Source File: RoomLink.tsx    From convoychat with GNU General Public License v3.0 5 votes vote down vote up
RoomLink: React.FC<IRoomLink> = ({
  name,
  id,
  isSelected,
  onInviteMemberClick,
}) => {
  const { dispatch } = useModalContext();
  const [deleteRoom, { loading: isDeleting }] = useDeleteRoomMutation({
    onError(err) {
      console.log(err.message);
    },
    refetchQueries: [{ query: ListCurrentUserRoomsDocument }],
  });

  const handleDelete = () => {
    deleteRoom({
      variables: {
        roomId: id,
      },
    }).catch(console.log);
  };

  const handleAddMembers = () => {
    dispatch({ type: "OPEN", modal: "InviteMembers" });
    onInviteMemberClick(id);
  };

  return (
    <StyledRoomLink isSelected={isSelected}>
      <Flex align="center" justify="space-between" nowrap>
        <Link to={`/room/${id}`}>
          <Flex gap="medium" align="center" nowrap>
            <FaUsers />
            <span>{name}</span>
          </Flex>
        </Link>

        <Dropdown>
          <Dropdown.Toggle>
            <IconButton icon={<FiMoreVertical />} />
          </Dropdown.Toggle>
          <Dropdown.Content style={{ right: "initial" }}>
            <Dropdown.Item>
              <Button
                variant="secondary"
                onClick={handleAddMembers}
                icon={FaUserPlus}
              >
                Add Members
              </Button>
            </Dropdown.Item>
            <Dropdown.Item>
              <Button
                variant="danger"
                isLoading={isDeleting}
                onClick={handleDelete}
                icon={FaTrash}
              >
                Delete
              </Button>
            </Dropdown.Item>
          </Dropdown.Content>
        </Dropdown>
      </Flex>
    </StyledRoomLink>
  );
}
Example #4
Source File: IPMenu.tsx    From iplocate with MIT License 5 votes vote down vote up
IPMenu: React.FC<Props> = (props) => {
  const { ips, onSetCurrentIp, onToggleIpVisible, onRemoveIp } = props;

  if (ips.length === 0) return null;

  return (
    <Wrapper>
      {ips.map((ip) => (
        <Row key={ip.traits.ipAddress}>
          <RowText onClick={() => onSetCurrentIp(ip)}>
            {ip.traits.ipAddress}
          </RowText>
          <div>
            <RowAction
              onClick={() => onRemoveIp(ip)}
              aria-label="remove ip address"
            >
              <FaTrash />
            </RowAction>
            {ip.hidden ? (
              <RowAction
                onClick={() => onToggleIpVisible(ip)}
                aria-label="toggle ip visibility on"
              >
                <FaEyeSlash />
              </RowAction>
            ) : (
              <RowAction
                onClick={() => onToggleIpVisible(ip)}
                aria-label="toggle ip visibility off"
              >
                <FaEye />
              </RowAction>
            )}
          </div>
        </Row>
      ))}
    </Wrapper>
  );
}
Example #5
Source File: MessageContainer.tsx    From convoychat with GNU General Public License v3.0 4 votes vote down vote up
MessageContainer: React.FC<IMessage> = ({
  id,
  content,
  date,
  author,
  isAuthor,
}) => {
  const client = useApolloClient();
  const roomData = useRef<GetRoomQuery>();
  const { roomId } = useParams();
  const [isEditing, setIsEditing] = useState<boolean>(false);
  const {
    value,
    setValue,
    textareaRef,
    handleChange,
    handleEmojiClick,
  } = useMessageInput({
    defaultValue: content,
  });

  const [
    deleteMessage,
    { loading: isDeleting, error },
  ] = useDeleteMessageMutation({
    update: deleteMessageMutationUpdater,
  });

  const [
    editMessage,
    { loading: editLoading, error: editError },
  ] = useEditMessageMutation({
    onCompleted() {
      setIsEditing(false);
    },
  });

  const handleDelete = () => {
    deleteMessage({ variables: { messageId: id } });
  };

  const handleEdit = (event: React.FormEvent<HTMLFormElement>) => {
    editMessage({
      variables: {
        messageId: id,
        content: (event.target as any).message.value,
      },
    });
  };

  const handleCancel = () => {
    setIsEditing(false);
  };

  useEffect(() => {
    try {
      roomData.current = client.readQuery<GetRoomQuery>({
        query: GetRoomDocument,
        variables: { roomId: roomId, limit: MAX_MESSAGES },
      });
    } catch (err) {
      console.log(err);
    }
  }, [roomId]);

  return (
    <Message>
      <Message.MetaInfo author={author} date={date}>
        <Message.Actions isAuthor={isAuthor}>
          <Message.Action loading={editLoading}>
            <FaPen onClick={() => setIsEditing(true)} />
          </Message.Action>
          <Message.Action loading={isDeleting}>
            <FaTrash onClick={handleDelete} />
          </Message.Action>
        </Message.Actions>
      </Message.MetaInfo>
      <Message.Content
        isEditing={isEditing}
        onEditing={
          <MessageInput
            value={value}
            setValue={setValue}
            innerRef={textareaRef}
            onCancel={handleCancel}
            handleSubmit={handleEdit}
            handleChange={handleChange}
            onEmojiClick={handleEmojiClick}
            mentionSuggestions={roomData.current?.room?.members}
          />
        }
      >
        <MarkdownView
          markdown={content}
          sanitizeHtml={html => DOMPurify.sanitize(html)}
          options={markdownSettings}
        />
      </Message.Content>
    </Message>
  );
}
Example #6
Source File: Content.tsx    From hub with Apache License 2.0 4 votes vote down vote up
Content = (props: Props) => {
  const history = useHistory();
  const wrapper = useRef<HTMLDivElement>(null);
  const versionsRef = useRef<Array<HTMLDivElement | null>>([]);
  const [currentOffsets, setCurrentOffsets] = useState<number[]>([]);

  const openPackagePage = (newVersion: string) => {
    props.onCloseModal(false);
    history.push({
      pathname: buildPackageURL(props.normalizedName, props.repository, newVersion, true),
      state: { searchUrlReferer: props.searchUrlReferer, fromStarredPage: props.fromStarredPage },
    });
  };

  useLayoutEffect(() => {
    const offsets: number[] = versionsRef.current.map((el) => (el ? el.offsetTop : 0));
    setCurrentOffsets(offsets);
  }, []);

  useEffect(() => {
    const handleScroll = (e: Event) => {
      const scrollYPosition = e.target ? (e.target as HTMLDivElement).scrollTop : 0;
      const index = findClosestNumberIndex(currentOffsets, scrollYPosition);
      if (index >= 0 && props.activeVersionIndex !== index) {
        props.setActiveVersionIndex(index);
      }
    };

    if (wrapper && wrapper.current && currentOffsets.length > 0) {
      const element = wrapper.current;
      element.addEventListener('scroll', handleScroll, { passive: true });
      return () => element.removeEventListener('scroll', handleScroll);
    }
  }, [currentOffsets, props]);

  return (
    <>
      <div className="flex-grow-1 overflow-auto h-100" ref={wrapper}>
        {props.changelog.map((item: ChangeLog, index: number) => {
          if (isNull(item.changes) || isUndefined(item.changes)) return null;

          const hasBadge = item.changes.some(
            (change: Change) =>
              change.hasOwnProperty('kind') && !isUndefined(change.kind) && change.kind.toString() !== ''
          );

          return (
            <div
              key={`v_${item.version}`}
              data-testid="changelogBlock"
              className={classnames({
                [styles.lastVersion]: index === props.changelog.length - 1,
              })}
            >
              <div
                className="d-inline-block d-md-flex flex-row align-items-baseline border-bottom w-100 mb-3 pb-2"
                id={`changelog-${index}`}
                ref={(el) => (versionsRef.current[index] = el)}
              >
                <div className={`d-flex flex-row align-items-baseline ${styles.versionWrapper}`}>
                  <button
                    className={`btn btn-link btn-sm text-dark text-truncate mb-0 p-0 fs-5 ${styles.btnTitle}`}
                    onClick={() => openPackagePage(item.version)}
                    aria-label={`Open version ${item.version}`}
                  >
                    {item.version}
                  </button>
                  <button
                    className={`btn btn-link btn-sm text-dark py-0 position-relative ${styles.btnLink}`}
                    onClick={() => props.updateVersionInQueryString(item.version, index)}
                    aria-label={`Update active version in querystring to ${item.version}`}
                  >
                    <FaLink />
                  </button>
                </div>

                {(item.containsSecurityUpdates || item.prerelease) && (
                  <div className={styles.badgesWrapper}>
                    {item.prerelease && (
                      <span className={`badge badge-sm rounded-pill me-2 position-relative border ${styles.badge}`}>
                        Pre-release
                      </span>
                    )}
                    {item.containsSecurityUpdates && (
                      <span className={`badge badge-sm rounded-pill me-2 position-relative border ${styles.badge}`}>
                        Contains security updates
                      </span>
                    )}
                  </div>
                )}

                {!isFuture(item.ts) && (
                  <div className="ms-auto ps-0 ps-md-2 text-nowrap">
                    <small className="text-muted">Released {moment.unix(item.ts).fromNow()}</small>
                  </div>
                )}
              </div>

              <div className={`d-flex flex-column mb-4 ${styles.list}`}>
                {item.changes.map((change: Change, idx: number) => (
                  <div key={`change_${item.version}_${idx}`} className="mb-1 w-100 d-flex flex-row">
                    <div className="d-flex align-items-start flex-row w-100">
                      {change.kind ? (
                        <div className={`position-relative ${styles.changeBadgeWrapper}`}>
                          <div
                            className={classnames(
                              'd-flex flex-row align-items-center justify-content-center text-uppercase badge rounded-pill me-2 fw-normal px-1 py-0',
                              styles.changeBadge,
                              styles[`${change.kind.toString()}ChangeBadge`]
                            )}
                          >
                            <span className={`position-relative ${styles.badgeIcon}`}>
                              {(() => {
                                switch (change.kind) {
                                  case ChangeKind.added:
                                    return <TiPlus />;
                                  case ChangeKind.changed:
                                    return <TiArrowSync />;
                                  case ChangeKind.removed:
                                    return <FaTrash />;
                                  case ChangeKind.fixed:
                                    return <FaWrench />;
                                  case ChangeKind.security:
                                    return <MdSecurity />;
                                  case ChangeKind.deprecated:
                                    return <RiTimerFill />;
                                  default:
                                    return <>-</>;
                                }
                              })()}
                            </span>
                            <span className="d-none d-md-block ms-1">{change.kind.toString()}</span>
                          </div>
                        </div>
                      ) : (
                        <>
                          {hasBadge ? (
                            <div className={`position-relative ${styles.changeBadgeWrapper}`}>
                              <div
                                className={classnames(
                                  'd-flex flex-row align-items-center justify-content-center text-uppercase badge rounded-pill me-2',
                                  styles.changeBadge
                                )}
                              >
                                -
                              </div>
                            </div>
                          ) : (
                            <div className="me-1 me-md-2">
                              <BsDot />
                            </div>
                          )}
                        </>
                      )}
                      <div className="flex-grow-1">
                        <div>{change.description}</div>
                        {!isUndefined(change.links) && (
                          <div className={`d-flex flex-row ${styles.linksWrapper}`}>
                            {change.links.map((link: PackageLink, idx: number) => {
                              return (
                                <div key={`change_${index}_link${idx}`}>
                                  <ExternalLink
                                    className={`text-muted text-decoration-underline ${styles.link}`}
                                    href={link.url}
                                    label={`Open link ${link.name}`}
                                  >
                                    {link.name}
                                  </ExternalLink>
                                  {idx !== change.links!.length - 1 && <BsDot className="text-muted" />}
                                </div>
                              );
                            })}
                          </div>
                        )}
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          );
        })}
      </div>
    </>
  );
}
Example #7
Source File: SlideViewer.tsx    From slim with Apache License 2.0 4 votes vote down vote up
render (): React.ReactNode {
    const rois: dmv.roi.ROI[] = []
    const segments: dmv.segment.Segment[] = []
    const mappings: dmv.mapping.ParameterMapping[] = []
    const annotationGroups: dmv.annotation.AnnotationGroup[] = []
    rois.push(...this.volumeViewer.getAllROIs())
    segments.push(...this.volumeViewer.getAllSegments())
    mappings.push(...this.volumeViewer.getAllParameterMappings())
    annotationGroups.push(...this.volumeViewer.getAllAnnotationGroups())

    const openSubMenuItems = ['specimens', 'opticalpaths', 'annotations']

    let report: React.ReactNode
    const dataset = this.state.generatedReport
    if (dataset !== undefined) {
      report = <Report dataset={dataset} />
    }

    let annotationMenuItems: React.ReactNode
    if (rois.length > 0) {
      annotationMenuItems = (
        <AnnotationList
          rois={rois}
          selectedRoiUIDs={this.state.selectedRoiUIDs}
          visibleRoiUIDs={this.state.visibleRoiUIDs}
          onSelection={this.handleAnnotationSelection}
          onVisibilityChange={this.handleAnnotationVisibilityChange}
        />
      )
    }

    const findingOptions = this.findingOptions.map(finding => {
      return (
        <Select.Option
          key={finding.CodeValue}
          value={finding.CodeValue}
        >
          {finding.CodeMeaning}
        </Select.Option>
      )
    })

    const geometryTypeOptionsMapping: { [key: string]: React.ReactNode } = {
      point: <Select.Option key='point' value='point'>Point</Select.Option>,
      circle: <Select.Option key='circle' value='circle'>Circle</Select.Option>,
      box: <Select.Option key='box' value='box'>Box</Select.Option>,
      polygon: <Select.Option key='polygon' value='polygon'>Polygon</Select.Option>,
      line: <Select.Option key='line' value='line'>Line</Select.Option>,
      freehandpolygon: (
        <Select.Option key='freehandpolygon' value='freehandpolygon'>
          Polygon (freehand)
        </Select.Option>
      ),
      freehandline: (
        <Select.Option key='freehandline' value='freehandline'>
          Line (freehand)
        </Select.Option>
      )
    }

    const selections: React.ReactNode[] = [
      (
        <Select
          style={{ minWidth: 130 }}
          onSelect={this.handleAnnotationFindingSelection}
          key='annotation-finding'
          defaultActiveFirstOption
        >
          {findingOptions}
        </Select>
      )
    ]

    const selectedFinding = this.state.selectedFinding
    if (selectedFinding !== undefined) {
      const key = _buildKey(selectedFinding)
      this.evaluationOptions[key].forEach(evaluation => {
        const evaluationOptions = evaluation.values.map(code => {
          return (
            <Select.Option
              key={code.CodeValue}
              value={code.CodeValue}
              label={evaluation.name}
            >
              {code.CodeMeaning}
            </Select.Option>
          )
        })
        selections.push(
          <>
            {evaluation.name.CodeMeaning}
            <Select
              style={{ minWidth: 130 }}
              onSelect={this.handleAnnotationEvaluationSelection}
              allowClear
              onClear={this.handleAnnotationEvaluationClearance}
              defaultActiveFirstOption={false}
            >
              {evaluationOptions}
            </Select>
          </>
        )
      })
      const geometryTypeOptions = this.geometryTypeOptions[key].map(name => {
        return geometryTypeOptionsMapping[name]
      })
      selections.push(
        <Select
          style={{ minWidth: 130 }}
          onSelect={this.handleAnnotationGeometryTypeSelection}
          key='annotation-geometry-type'
        >
          {geometryTypeOptions}
        </Select>
      )
      selections.push(
        <Checkbox
          onChange={this.handleAnnotationMeasurementActivation}
          key='annotation-measurement'
        >
          measure
        </Checkbox>
      )
    }

    const specimenMenu = (
      <Menu.SubMenu key='specimens' title='Specimens'>
        <SpecimenList
          metadata={this.props.slide.volumeImages[0]}
          showstain={false}
        />
      </Menu.SubMenu>
    )

    const equipmentMenu = (
      <Menu.SubMenu key='equipment' title='Equipment'>
        <Equipment metadata={this.props.slide.volumeImages[0]} />
      </Menu.SubMenu>
    )

    const defaultOpticalPathStyles: {
      [identifier: string]: {
        opacity: number
        color?: number[]
        limitValues?: number[]
      }
    } = {}
    const opticalPathMetadata: {
      [identifier: string]: dmv.metadata.VLWholeSlideMicroscopyImage[]
    } = {}
    const opticalPaths = this.volumeViewer.getAllOpticalPaths()
    opticalPaths.sort((a, b) => {
      if (a.identifier < b.identifier) {
        return -1
      } else if (a.identifier > b.identifier) {
        return 1
      }
      return 0
    })
    opticalPaths.forEach(opticalPath => {
      const identifier = opticalPath.identifier
      const metadata = this.volumeViewer.getOpticalPathMetadata(identifier)
      opticalPathMetadata[identifier] = metadata
      const style = this.volumeViewer.getOpticalPathStyle(identifier)
      defaultOpticalPathStyles[identifier] = style
    })
    const opticalPathMenu = (
      <Menu.SubMenu key='opticalpaths' title='Optical Paths'>
        <OpticalPathList
          metadata={opticalPathMetadata}
          opticalPaths={opticalPaths}
          defaultOpticalPathStyles={defaultOpticalPathStyles}
          visibleOpticalPathIdentifiers={this.state.visibleOpticalPathIdentifiers}
          activeOpticalPathIdentifiers={this.state.activeOpticalPathIdentifiers}
          onOpticalPathVisibilityChange={this.handleOpticalPathVisibilityChange}
          onOpticalPathStyleChange={this.handleOpticalPathStyleChange}
          onOpticalPathActivityChange={this.handleOpticalPathActivityChange}
          selectedPresentationStateUID={this.state.selectedPresentationStateUID}
        />
      </Menu.SubMenu>
    )

    let presentationStateMenu
    console.log('DEBUG: ', this.state.presentationStates)
    if (this.state.presentationStates.length > 0) {
      const presentationStateOptions = this.state.presentationStates.map(
        presentationState => {
          return (
            <Select.Option
              key={presentationState.SOPInstanceUID}
              value={presentationState.SOPInstanceUID}
              dropdownMatchSelectWidth={false}
              size='small'
            >
              {presentationState.ContentDescription}
            </Select.Option>
          )
        }
      )
      presentationStateMenu = (
        <Menu.SubMenu key='presentationStates' title='Presentation States'>
          <Space align='center' size={20} style={{ padding: '14px' }}>
            <Select
              style={{ minWidth: 200, maxWidth: 200 }}
              onSelect={this.handlePresentationStateSelection}
              key='presentation-states'
              defaultValue={this.props.selectedPresentationStateUID}
              value={this.state.selectedPresentationStateUID}
            >
              {presentationStateOptions}
            </Select>
            <Tooltip title='Reset'>
              <Btn
                icon={<UndoOutlined />}
                type='primary'
                onClick={this.handlePresentationStateReset}
              />
            </Tooltip>
          </Space>
        </Menu.SubMenu>
      )
    }

    let segmentationMenu
    if (segments.length > 0) {
      const defaultSegmentStyles: {
        [segmentUID: string]: {
          opacity: number
        }
      } = {}
      const segmentMetadata: {
        [segmentUID: string]: dmv.metadata.Segmentation[]
      } = {}
      const segments = this.volumeViewer.getAllSegments()
      segments.forEach(segment => {
        defaultSegmentStyles[segment.uid] = this.volumeViewer.getSegmentStyle(
          segment.uid
        )
        segmentMetadata[segment.uid] = this.volumeViewer.getSegmentMetadata(
          segment.uid
        )
      })
      segmentationMenu = (
        <Menu.SubMenu key='segmentations' title='Segmentations'>
          <SegmentList
            segments={segments}
            metadata={segmentMetadata}
            defaultSegmentStyles={defaultSegmentStyles}
            visibleSegmentUIDs={this.state.visibleSegmentUIDs}
            onSegmentVisibilityChange={this.handleSegmentVisibilityChange}
            onSegmentStyleChange={this.handleSegmentStyleChange}
          />
        </Menu.SubMenu>
      )
      openSubMenuItems.push('segmentations')
    }

    let parametricMapMenu
    if (mappings.length > 0) {
      const defaultMappingStyles: {
        [mappingUID: string]: {
          opacity: number
        }
      } = {}
      const mappingMetadata: {
        [mappingUID: string]: dmv.metadata.ParametricMap[]
      } = {}
      mappings.forEach(mapping => {
        defaultMappingStyles[mapping.uid] = this.volumeViewer.getParameterMappingStyle(
          mapping.uid
        )
        mappingMetadata[mapping.uid] = this.volumeViewer.getParameterMappingMetadata(
          mapping.uid
        )
      })
      parametricMapMenu = (
        <Menu.SubMenu key='parmetricmaps' title='Parametric Maps'>
          <MappingList
            mappings={mappings}
            metadata={mappingMetadata}
            defaultMappingStyles={defaultMappingStyles}
            visibleMappingUIDs={this.state.visibleMappingUIDs}
            onMappingVisibilityChange={this.handleMappingVisibilityChange}
            onMappingStyleChange={this.handleMappingStyleChange}
          />
        </Menu.SubMenu>
      )
      openSubMenuItems.push('parametricmaps')
    }

    let annotationGroupMenu
    if (annotationGroups.length > 0) {
      const defaultAnnotationGroupStyles: {
        [annotationGroupUID: string]: {
          opacity: number
        }
      } = {}
      const annotationGroupMetadata: {
        [annotationGroupUID: string]: dmv.metadata.MicroscopyBulkSimpleAnnotations
      } = {}
      const annotationGroups = this.volumeViewer.getAllAnnotationGroups()
      annotationGroups.forEach(annotationGroup => {
        defaultAnnotationGroupStyles[annotationGroup.uid] = this.volumeViewer.getAnnotationGroupStyle(
          annotationGroup.uid
        )
        annotationGroupMetadata[annotationGroup.uid] = this.volumeViewer.getAnnotationGroupMetadata(
          annotationGroup.uid
        )
      })
      annotationGroupMenu = (
        <Menu.SubMenu key='annotationGroups' title='Annotation Groups'>
          <AnnotationGroupList
            annotationGroups={annotationGroups}
            metadata={annotationGroupMetadata}
            defaultAnnotationGroupStyles={defaultAnnotationGroupStyles}
            visibleAnnotationGroupUIDs={this.state.visibleAnnotationGroupUIDs}
            onAnnotationGroupVisibilityChange={this.handleAnnotationGroupVisibilityChange}
            onAnnotationGroupStyleChange={this.handleAnnotationGroupStyleChange}
          />
        </Menu.SubMenu>
      )
      openSubMenuItems.push('annotationGroups')
    }

    let toolbar
    let toolbarHeight = '0px'
    if (this.props.enableAnnotationTools) {
      toolbar = (
        <Row>
          <Button
            tooltip='Draw ROI [d]'
            icon={FaDrawPolygon}
            onClick={this.handleRoiDrawing}
            isSelected={this.state.isRoiDrawingActive}
          />
          <Button
            tooltip='Modify ROIs [m]'
            icon={FaHandPointer}
            onClick={this.handleRoiModification}
            isSelected={this.state.isRoiModificationActive}
          />
          <Button
            tooltip='Translate ROIs [t]'
            icon={FaHandPaper}
            onClick={this.handleRoiTranslation}
            isSelected={this.state.isRoiTranslationActive}
          />
          <Button
            tooltip='Remove selected ROI [r]'
            onClick={this.handleRoiRemoval}
            icon={FaTrash}
          />
          <Button
            tooltip='Show/Hide ROIs [v]'
            icon={this.state.areRoisHidden ? FaEye : FaEyeSlash}
            onClick={this.handleRoiVisibilityChange}
            isSelected={this.state.areRoisHidden}
          />
          <Button
            tooltip='Save ROIs [s]'
            icon={FaSave}
            onClick={this.handleReportGeneration}
          />
        </Row>
      )
      toolbarHeight = '50px'
    }

    /* It would be nicer to use the ant Spin component, but that causes issues
     * with the positioning of the viewport.
     */
    let loadingDisplay = 'none'
    if (this.state.isLoading) {
      loadingDisplay = 'block'
    }

    return (
      <Layout style={{ height: '100%' }} hasSider>
        <Layout.Content style={{ height: '100%' }}>
          {toolbar}

          <div className='dimmer' style={{ display: loadingDisplay }} />
          <div className='spinner' style={{ display: loadingDisplay }} />
          <div
            style={{
              height: `calc(100% - ${toolbarHeight})`,
              overflow: 'hidden'
            }}
            ref={this.volumeViewportRef}
          />

          <Modal
            visible={this.state.isAnnotationModalVisible}
            title='Configure annotations'
            onOk={this.handleAnnotationConfigurationCompletion}
            onCancel={this.handleAnnotationConfigurationCancellation}
            okText='Select'
          >
            <Space align='start' direction='vertical'>
              {selections}
            </Space>
          </Modal>

          <Modal
            visible={this.state.isReportModalVisible}
            title='Verify and save report'
            onOk={this.handleReportVerification}
            onCancel={this.handleReportCancellation}
            okText='Save'
          >
            {report}
          </Modal>
        </Layout.Content>

        <Layout.Sider
          width={300}
          reverseArrow
          style={{
            borderLeft: 'solid',
            borderLeftWidth: 0.25,
            overflow: 'hidden',
            background: 'none'
          }}
        >
          <Menu
            mode='inline'
            defaultOpenKeys={openSubMenuItems}
            style={{ height: '100%' }}
            inlineIndent={14}
            forceSubMenuRender
          >
            <Menu.SubMenu key='label' title='Slide label'>
              <Menu.Item style={{ height: '100%' }}>
                <div
                  style={{ height: '220px' }}
                  ref={this.labelViewportRef}
                />
              </Menu.Item>
            </Menu.SubMenu>
            {specimenMenu}
            {equipmentMenu}
            {opticalPathMenu}
            {presentationStateMenu}
            <Menu.SubMenu key='annotations' title='Annotations'>
              {annotationMenuItems}
            </Menu.SubMenu>
            {annotationGroupMenu}
            {segmentationMenu}
            {parametricMapMenu}
          </Menu>
        </Layout.Sider>
      </Layout>
    )
  }