react-icons/fa#FaEyeSlash TypeScript Examples

The following examples show how to use react-icons/fa#FaEyeSlash. 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: ScannerDisabledRepositoryBadge.tsx    From hub with Apache License 2.0 6 votes vote down vote up
ScannerDisabledRepositoryBadge = (props: Props) => {
  if (!props.scannerDisabled && !props.allContainersImagesWhitelisted) return null;
  return (
    <ElementWithTooltip
      active
      className={props.className}
      element={<Label text="Security scanner disabled" labelStyle="warning" icon={<FaEyeSlash />} />}
      tooltipMessage="Security scanning of this package has been disabled by the publisher."
      visibleTooltip={!isUndefined(props.withTooltip) && props.withTooltip}
    />
  );
}
Example #2
Source File: AnnotationList.tsx    From slim with Apache License 2.0 6 votes vote down vote up
render (): React.ReactNode {
    const items = this.props.rois.map((roi, index) => (
      <AnnotationItem
        key={roi.uid}
        roi={roi}
        index={index}
        isVisible={this.props.visibleRoiUIDs.includes(roi.uid)}
        onVisibilityChange={this.props.onVisibilityChange}
      />
    ))

    return (
      <>
        <div style={{ paddingLeft: '14px', paddingTop: '7px', paddingBottom: '7px' }}>
          <Switch
            size='small'
            onChange={this.handleVisibilityChange}
            checked={this.props.visibleRoiUIDs.length > 0}
            checkedChildren={<FaEye />}
            unCheckedChildren={<FaEyeSlash />}
          />
        </div>
        <Menu
          selectedKeys={this.props.selectedRoiUIDs}
          onSelect={this.handleMenuItemSelection}
          onClick={this.handleMenuItemSelection}
        >
          {items}
        </Menu>
      </>
    )
  }
Example #3
Source File: DisabledRepositoryBadge.tsx    From hub with Apache License 2.0 5 votes vote down vote up
DisabledRepositoryBadge = (props: Props) => {
  if (!props.disabled) return null;
  return <Label text="Disabled" labelStyle="warning" className={props.className} icon={<FaEyeSlash />} />;
}
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: InputField.tsx    From hub with Apache License 2.0 4 votes vote down vote up
InputField = forwardRef((props: Props, ref: Ref<RefInputField>) => {
  const input = useRef<HTMLInputElement>(null);
  const [isValid, setIsValid] = useState<boolean | null>(null);
  const [inputValue, setInputValue] = useState(props.value || '');
  const [invalidText, setInvalidText] = useState(!isUndefined(props.invalidText) ? props.invalidText.default : '');
  const [isCheckingAvailability, setIsCheckingAvailability] = useState(false);
  const [isCheckingPwdStrength, setIsCheckingPwdStrength] = useState(false);
  const [pwdStrengthError, setPwdStrengthError] = useState<string | null>(null);
  const [activeType, setActiveType] = useState<string>(props.type);
  const [validateTimeout, setValidateTimeout] = useState<NodeJS.Timeout | null>(null);

  useImperativeHandle(ref, () => ({
    checkIsValid(): Promise<boolean> {
      return isValidField();
    },
    reset: () => {
      setInputValue('');
    },
    getValue(): string {
      return inputValue;
    },
    checkValidity(): boolean {
      return input.current ? input.current!.checkValidity() : true;
    },
    updateValue(newValue: string): void {
      setInputValue(newValue);
    },
  }));

  const checkValidity = (): boolean => {
    let isInputValid = true;
    if (input.current) {
      isInputValid = input.current!.checkValidity();
      if (!isInputValid && !isUndefined(props.invalidText)) {
        let errorTxt = props.invalidText.default;
        const validityState: ValidityState | undefined = input.current.validity;
        if (!isUndefined(validityState)) {
          if (validityState.typeMismatch && !isUndefined(props.invalidText.typeMismatch)) {
            errorTxt = props.invalidText.typeMismatch;
          } else if (validityState.tooShort && !isUndefined(props.invalidText.tooShort)) {
            errorTxt = props.invalidText.tooShort;
          } else if (validityState.patternMismatch && !isUndefined(props.invalidText.patternMismatch)) {
            errorTxt = props.invalidText.patternMismatch;
          } else if (validityState.typeMismatch && !isUndefined(props.invalidText.typeMismatch)) {
            errorTxt = props.invalidText.typeMismatch;
          } else if (validityState.rangeUnderflow && !isUndefined(props.invalidText.rangeUnderflow)) {
            errorTxt = props.invalidText.rangeUnderflow;
          } else if (validityState.rangeOverflow && !isUndefined(props.invalidText.rangeOverflow)) {
            errorTxt = props.invalidText.rangeOverflow;
          } else if (validityState.customError && !isUndefined(props.invalidText.customError)) {
            if (!isUndefined(props.excludedValues) && props.excludedValues.includes(input.current.value)) {
              errorTxt = props.invalidText.excluded;
            } else {
              errorTxt = props.invalidText.customError;
            }
          }
        }
        setInvalidText(errorTxt);
      }
      setIsValid(isInputValid);
      if (!isUndefined(props.setValidationStatus)) {
        props.setValidationStatus(false);
      }
    }
    return isInputValid;
  };

  const isValidField = async (): Promise<boolean> => {
    if (input.current) {
      const value = input.current!.value;
      if (value !== '') {
        if (!isUndefined(props.excludedValues) && props.excludedValues.includes(value)) {
          input.current!.setCustomValidity('Value is excluded');
        } else if (!isUndefined(props.checkAvailability) && !props.checkAvailability.excluded.includes(value)) {
          setIsCheckingAvailability(true);
          try {
            const isAvailable = await API.checkAvailability({
              resourceKind: props.checkAvailability.resourceKind,
              value: value,
            });
            if (!isNull(input.current)) {
              if (isAvailable) {
                input.current!.setCustomValidity(props.checkAvailability!.isAvailable ? 'Already taken' : '');
              } else {
                input.current!.setCustomValidity(props.checkAvailability!.isAvailable ? '' : 'Resource is not valid');
              }
            }
          } catch {
            if (!isNull(input.current)) {
              input.current!.setCustomValidity(props.checkAvailability!.isAvailable ? 'Already taken' : '');
            }
          }
          setIsCheckingAvailability(false);
        } else if (props.checkPasswordStrength) {
          setIsCheckingPwdStrength(true);
          try {
            await API.checkPasswordStrength(value);
            if (!isNull(input.current)) {
              input.current!.setCustomValidity('');
              setPwdStrengthError(null);
            }
          } catch (e: any) {
            if (!isNull(input.current) && e.message) {
              setPwdStrengthError(e.message);
              input.current!.setCustomValidity(e.message);
            }
          }
          setIsCheckingPwdStrength(false);
        } else {
          if (!isNull(input.current)) {
            input.current!.setCustomValidity('');
          }
        }
      }
    }
    return checkValidity();
  };

  const handleOnBlur = (): void => {
    if (!isUndefined(props.validateOnBlur) && props.validateOnBlur && input.current) {
      cleanTimeout(); // On blur we clean timeout if it's necessary
      isValidField();
    }
  };

  const handleOnChange = (e: ChangeEvent<HTMLInputElement>): void => {
    setInputValue(e.target.value);
    if (!isUndefined(props.onChange)) {
      props.onChange(e);
    }
  };

  const cleanTimeout = () => {
    if (!isNull(validateTimeout)) {
      clearTimeout(validateTimeout);
      setValidateTimeout(null);
    }
  };

  useEffect(() => {
    const isInputFocused = input.current === document.activeElement;
    if (isInputFocused && !isUndefined(props.validateOnChange) && props.validateOnChange) {
      cleanTimeout();
      setValidateTimeout(
        setTimeout(() => {
          isValidField();
        }, VALIDATION_DELAY)
      );
    }

    return () => {
      if (validateTimeout) {
        clearTimeout(validateTimeout);
      }
    };
  }, [inputValue]); /* eslint-disable-line react-hooks/exhaustive-deps */

  return (
    <div className={`${props.smallBottomMargin ? 'mb-3' : 'mb-4'} position-relative ${props.className}`}>
      {!isUndefined(props.label) && (
        <label htmlFor={props.name} className={`form-label fw-bold ${styles.label}`}>
          <span className="fw-bold">{props.label}</span>
          {!isUndefined(props.labelLegend) && <>{props.labelLegend}</>}
        </label>
      )}

      <input
        data-testid={`${props.name}Input`}
        ref={input}
        type={activeType}
        id={props.name}
        name={props.name}
        value={inputValue}
        className={classnames('form-control', props.inputClassName, { 'is-invalid': !isNull(isValid) && !isValid })}
        placeholder={props.placeholder}
        required={props.required}
        minLength={props.minLength}
        maxLength={props.maxLength}
        min={props.min}
        max={props.max}
        pattern={props.pattern}
        autoComplete={props.autoComplete}
        readOnly={props.readOnly || false}
        onChange={handleOnChange}
        onBlur={handleOnBlur}
        onKeyDown={props.onKeyDown}
        autoFocus={props.autoFocus}
        disabled={props.disabled}
        spellCheck="false"
      />

      {props.type === 'password' && props.visiblePassword && (
        <button
          type="button"
          className={classnames('btn btn-link position-absolute bottom-0', styles.revealBtn, {
            'text-muted': activeType === 'password',
            'text-secondary': activeType !== 'password',
          })}
          onClick={() => setActiveType(activeType === 'password' ? 'text' : 'password')}
          aria-label={`${activeType === 'password' ? 'Hide' : 'Show'} password`}
        >
          {activeType === 'password' ? <FaEyeSlash /> : <FaEye />}
        </button>
      )}

      {(isCheckingAvailability || isCheckingPwdStrength) && (
        <div className={`position-absolute ${styles.spinner}`}>
          <span className="spinner-border spinner-border-sm text-primary" />
        </div>
      )}

      {!isUndefined(props.validText) && (
        <div className={`valid-feedback mt-0 ${styles.inputFeedback}`}>{props.validText}</div>
      )}

      {!isUndefined(invalidText) && isNull(pwdStrengthError) && (
        <div className={`invalid-feedback mt-0 ${styles.inputFeedback}`}>{invalidText}</div>
      )}

      {!isNull(pwdStrengthError) && (
        <div className={`invalid-feedback mt-0 ${styles.inputPwdStrengthError}`}>
          {capitalizeFirstLetter(pwdStrengthError)}
        </div>
      )}

      {!isUndefined(props.additionalInfo) && <div className="alert p-0 mt-4">{props.additionalInfo}</div>}
    </div>
  );
})
Example #6
Source File: AnnotationGroupItem.tsx    From slim with Apache License 2.0 4 votes vote down vote up
render (): React.ReactNode {
    const identifier = `Annotation Group ${this.props.annotationGroup.number}`
    const attributes: Array<{ name: string, value: string }> = [
      {
        name: 'Label',
        value: this.props.annotationGroup.label
      },
      {
        name: 'Algorithm Name',
        value: this.props.annotationGroup.algorithmName
      },
      {
        name: 'Property category',
        value: this.props.annotationGroup.propertyCategory.CodeMeaning
      },
      {
        name: 'Property type',
        value: this.props.annotationGroup.propertyType.CodeMeaning
      }
    ]

    const index = this.props.metadata.AnnotationGroupSequence.findIndex(
      item => (item.AnnotationGroupUID === this.props.annotationGroup.uid)
    )
    const item = this.props.metadata.AnnotationGroupSequence[index]
    const measurementsSequence = item.MeasurementsSequence ?? []

    const measurementOptions = measurementsSequence.map(measurementItem => {
      const name = measurementItem.ConceptNameCodeSequence[0]
      const key = `${name.CodingSchemeDesignator}-${name.CodeValue}`
      return (
        <Select.Option
          key={key}
          value={key}
          dropdownMatchSelectWidth={false}
          size='small'
          disabled={!this.props.isVisible}
        >
          {name.CodeMeaning}
        </Select.Option>
      )
    })

    const settings = (
      <div>
        <Row justify='start' align='middle' gutter={[8, 8]}>
          <Col span={6}>
            Opacity
          </Col>
          <Col span={12}>
            <Slider
              range={false}
              min={0}
              max={1}
              step={0.01}
              value={this.state.currentStyle.opacity}
              onChange={this.handleOpacityChange}
            />
          </Col>
          <Col span={6}>
            <InputNumber
              min={0}
              max={1}
              size='small'
              step={0.1}
              style={{ width: '65px' }}
              value={this.state.currentStyle.opacity}
              onChange={this.handleOpacityChange}
            />
          </Col>
        </Row>
        <Divider plain>
          Exploration
        </Divider>
        <Row justify='start' align='middle' gutter={[8, 8]}>
          <Col span={8}>
            Measurement
          </Col>
          <Col span={16}>
            <Select
              style={{ minWidth: '65px', width: '90%' }}
              onSelect={this.handleMeasurementSelection}
              key='annotation-group-measurements'
              defaultValue={undefined}
            >
              {measurementOptions}
            </Select>
          </Col>
        </Row>
      </div>
    )

    const {
      annotationGroup,
      defaultStyle,
      isVisible,
      metadata,
      onVisibilityChange,
      onStyleChange,
      ...otherProps
    } = this.props
    return (
      <Menu.Item
        style={{ height: '100%', paddingLeft: '3px' }}
        key={this.props.annotationGroup.uid}
        {...otherProps}
      >
        <Space align='start'>
          <div style={{ paddingLeft: '14px' }}>
            <Space direction='vertical' align='end'>
              <Switch
                size='small'
                onChange={this.handleVisibilityChange}
                checked={this.props.isVisible}
                checkedChildren={<FaEye />}
                unCheckedChildren={<FaEyeSlash />}
              />
              <Popover
                placement='left'
                content={settings}
                overlayStyle={{ width: '350px' }}
                title='Display Settings'
              >
                <Button
                  type='primary'
                  shape='circle'
                  icon={<SettingOutlined />}
                />
              </Popover>
            </Space>
          </div>
          <Description
            header={identifier}
            attributes={attributes}
            selectable
            hasLongValues
          />
        </Space>
      </Menu.Item>
    )
  }
Example #7
Source File: AnnotationItem.tsx    From slim with Apache License 2.0 4 votes vote down vote up
render (): React.ReactNode {
    const identifier = `ROI ${this.props.index + 1}`
    const attributes: Array<{ name: string, value: string }> = []
    /**
     * This hack is required for Menu.Item to work properly:
     * https://github.com/react-component/menu/issues/142
     */
    const { isVisible, onVisibilityChange, ...otherProps } = this.props
    this.props.roi.evaluations.forEach((
      item: (
        dcmjs.sr.valueTypes.TextContentItem |
        dcmjs.sr.valueTypes.CodeContentItem
      )
    ) => {
      const nameValue = item.ConceptNameCodeSequence[0].CodeValue
      const nameMeaning = item.ConceptNameCodeSequence[0].CodeMeaning
      const name = `${nameMeaning}`
      if (item.ValueType === dcmjs.sr.valueTypes.ValueTypes.CODE) {
        const codeContentItem = item as dcmjs.sr.valueTypes.CodeContentItem
        const valueMeaning = codeContentItem.ConceptCodeSequence[0].CodeMeaning
        // For consistency with Segment and Annotation Group
        if (nameValue === '276214006') {
          attributes.push({
            name: 'Property category',
            value: `${valueMeaning}`
          })
        } else if (nameValue === '121071') {
          attributes.push({
            name: 'Property type',
            value: `${valueMeaning}`
          })
        } else if (nameValue === '111001') {
          attributes.push({
            name: 'Algorithm Name',
            value: `${valueMeaning}`
          })
        } else {
          attributes.push({
            name: name,
            value: `${valueMeaning}`
          })
        }
      } else if (item.ValueType === dcmjs.sr.valueTypes.ValueTypes.TEXT) {
        const textContentItem = item as dcmjs.sr.valueTypes.TextContentItem
        attributes.push({
          name: name,
          value: textContentItem.TextValue
        })
      }
    })
    this.props.roi.measurements.forEach(item => {
      const nameMeaning = item.ConceptNameCodeSequence[0].CodeMeaning
      const name = `${nameMeaning}`
      const seq = item.MeasuredValueSequence[0]
      const value = seq.NumericValue.toPrecision(6)
      const unit = seq.MeasurementUnitsCodeSequence[0].CodeValue
      attributes.push({
        name: name,
        value: `${value} ${unit}`
      })
    })
    return (
      <Space align='start'>
        <div style={{ paddingLeft: '14px' }}>
          <Switch
            size='small'
            onChange={this.handleVisibilityChange}
            checked={this.props.isVisible}
            checkedChildren={<FaEye />}
            unCheckedChildren={<FaEyeSlash />}
          />
        </div>
        <Menu.Item
          style={{ height: '100%', paddingLeft: '3px' }}
          key={this.props.roi.uid}
          {...otherProps}
        >
          <Description
            header={identifier}
            attributes={attributes}
            selectable
            hasLongValues
          />
        </Menu.Item>
      </Space>
    )
  }
Example #8
Source File: MappingItem.tsx    From slim with Apache License 2.0 4 votes vote down vote up
render (): React.ReactNode {
    const identifier = `Mapping ${this.props.mapping.number}`
    const attributes: Array<{ name: string, value: string }> = [
      {
        name: 'Label',
        value: this.props.mapping.label
      }
    ]

    const settings = (
      <div>
        <Row justify='center' align='middle'>
          <Col span={6}>
            Opacity
          </Col>
          <Col span={12}>
            <Slider
              range={false}
              min={0}
              max={1}
              step={0.01}
              value={this.state.currentStyle.opacity}
              onChange={this.handleOpacityChange}
            />
          </Col>
          <Col span={6}>
            <InputNumber
              min={0}
              max={1}
              size='small'
              step={0.1}
              style={{ width: '65px' }}
              value={this.state.currentStyle.opacity}
              onChange={this.handleOpacityChange}
            />
          </Col>
        </Row>
      </div>
    )

    /**
     * This hack is required for Menu.Item to work properly:
     * https://github.com/react-component/menu/issues/142
     */
    const {
      defaultStyle,
      isVisible,
      mapping,
      metadata,
      onVisibilityChange,
      onStyleChange,
      ...otherProps
    } = this.props
    return (
      <Menu.Item
        style={{ height: '100%', paddingLeft: '3px' }}
        key={this.props.mapping.uid}
        {...otherProps}
      >
        <Space align='start'>
          <div style={{ paddingLeft: '14px' }}>
            <Space direction='vertical' align='end' size={100}>
              <Space direction='vertical' align='end'>
                <Switch
                  size='small'
                  onChange={this.handleVisibilityChange}
                  checked={this.props.isVisible}
                  checkedChildren={<FaEye />}
                  unCheckedChildren={<FaEyeSlash />}
                />
                <Popover
                  placement='left'
                  content={settings}
                  overlayStyle={{ width: '350px' }}
                  title='Display Settings'
                >
                  <Button
                    type='primary'
                    shape='circle'
                    icon={<SettingOutlined />}
                  />
                </Popover>
              </Space>
            </Space>
          </div>
          <Description
            header={identifier}
            attributes={attributes}
            selectable
            hasLongValues
          />
        </Space>
      </Menu.Item>
    )
  }
Example #9
Source File: SegmentItem.tsx    From slim with Apache License 2.0 4 votes vote down vote up
render (): React.ReactNode {
    const attributes: Array<{ name: string, value: string }> = [
      {
        name: 'Property Category',
        value: this.props.segment.propertyCategory.CodeMeaning
      },
      {
        name: 'Property Type',
        value: this.props.segment.propertyType.CodeMeaning
      },
      {
        name: 'Algorithm Name',
        value: this.props.segment.algorithmName
      }
    ]

    const settings = (
      <div>
        <Row justify='center' align='middle'>
          <Col span={6}>
            Opacity
          </Col>
          <Col span={12}>
            <Slider
              range={false}
              min={0}
              max={1}
              step={0.01}
              value={this.state.currentStyle.opacity}
              onChange={this.handleOpacityChange}
            />
          </Col>
          <Col span={6}>
            <InputNumber
              min={0}
              max={1}
              size='small'
              step={0.1}
              style={{ width: '65px' }}
              value={this.state.currentStyle.opacity}
              onChange={this.handleOpacityChange}
            />
          </Col>
        </Row>
      </div>
    )

    /**
     * This hack is required for Menu.Item to work properly:
     * https://github.com/react-component/menu/issues/142
     */
    const {
      defaultStyle,
      isVisible,
      segment,
      metadata,
      onVisibilityChange,
      onStyleChange,
      ...otherProps
    } = this.props
    return (
      <Menu.Item
        style={{ height: '100%', paddingLeft: '3px' }}
        key={this.props.segment.uid}
        {...otherProps}
      >
        <Space align='start'>
          <div style={{ paddingLeft: '14px' }}>
            <Space direction='vertical' align='end'>
              <Switch
                size='small'
                onChange={this.handleVisibilityChange}
                checked={this.props.isVisible}
                checkedChildren={<FaEye />}
                unCheckedChildren={<FaEyeSlash />}
              />
              <Popover
                placement='left'
                content={settings}
                overlayStyle={{ width: '350px' }}
                title='Display Settings'
              >
                <Button
                  type='primary'
                  shape='circle'
                  icon={<SettingOutlined />}
                />
              </Popover>
            </Space>
          </div>
          <Description
            header={this.props.segment.label}
            attributes={attributes}
            selectable
            hasLongValues
          />
        </Space>
      </Menu.Item>
    )
  }
Example #10
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>
    )
  }
Example #11
Source File: Builder.tsx    From crosshare with GNU Affero General Public License v3.0 4 votes vote down vote up
GridMode = ({
  getMostConstrainedEntry,
  reRunAutofill,
  state,
  dispatch,
  setClueMode,
  ...props
}: GridModeProps) => {
  const [muted, setMuted] = usePersistedBoolean('muted', false);
  const [toggleKeyboard, setToggleKeyboard] = usePersistedBoolean(
    'keyboard',
    false
  );
  const { showSnackbar } = useSnackbar();

  const gridRef = useRef<HTMLDivElement | null>(null);

  const focusGrid = useCallback(() => {
    if (gridRef.current) {
      gridRef.current.focus();
    }
  }, []);

  const physicalKeyboardHandler = useCallback(
    (e: KeyboardEvent) => {
      const mkey = fromKeyboardEvent(e);
      if (isSome(mkey)) {
        e.preventDefault();
        if (mkey.value.k === KeyK.Enter && !state.isEnteringRebus) {
          reRunAutofill();
          return;
        }
        if (mkey.value.k === KeyK.Exclamation) {
          const entry = getMostConstrainedEntry();
          if (entry !== null) {
            const ca: ClickedEntryAction = {
              type: 'CLICKEDENTRY',
              entryIndex: entry,
            };
            dispatch(ca);
          }
          return;
        }
        if (mkey.value.k === KeyK.Octothorp) {
          const a: ToggleHiddenAction = { type: 'TOGGLEHIDDEN' };
          dispatch(a);
        }

        const kpa: KeypressAction = { type: 'KEYPRESS', key: mkey.value };
        dispatch(kpa);
      }
    },
    [dispatch, reRunAutofill, state.isEnteringRebus, getMostConstrainedEntry]
  );
  useEventListener(
    'keydown',
    physicalKeyboardHandler,
    gridRef.current || undefined
  );

  const pasteHandler = useCallback(
    (e: ClipboardEvent) => {
      const tagName = (e.target as HTMLElement)?.tagName?.toLowerCase();
      if (tagName === 'textarea' || tagName === 'input') {
        return;
      }
      const pa: PasteAction = {
        type: 'PASTE',
        content: e.clipboardData?.getData('Text') || '',
      };
      dispatch(pa);
      e.preventDefault();
    },
    [dispatch]
  );
  useEventListener('paste', pasteHandler);

  const fillLists = useMemo(() => {
    let left = <></>;
    let right = <></>;
    const [entry, cross] = entryAndCrossAtPosition(state.grid, state.active);
    let crossMatches = cross && potentialFill(cross, state.grid);
    let entryMatches = entry && potentialFill(entry, state.grid);

    if (
      crossMatches !== null &&
      entryMatches !== null &&
      entry !== null &&
      cross !== null
    ) {
      /* If we have both entry + cross we now filter for only matches that'd work for both. */
      const entryActiveIndex = activeIndex(state.grid, state.active, entry);
      const crossActiveIndex = activeIndex(state.grid, state.active, cross);
      const entryValidLetters = lettersAtIndex(entryMatches, entryActiveIndex);
      const crossValidLetters = lettersAtIndex(crossMatches, crossActiveIndex);
      const validLetters = (
        entryValidLetters.match(
          new RegExp('[' + crossValidLetters + ']', 'g')
        ) || []
      ).join('');
      entryMatches = entryMatches.filter(([word]) => {
        const l = word[entryActiveIndex];
        return l && validLetters.indexOf(l) !== -1;
      });
      crossMatches = crossMatches.filter(([word]) => {
        const l = word[crossActiveIndex];
        return l && validLetters.indexOf(l) !== -1;
      });
    }

    if (cross && crossMatches !== null) {
      if (cross.direction === Direction.Across) {
        left = (
          <PotentialFillList
            selected={false}
            gridRef={gridRef}
            header="Across"
            values={crossMatches}
            entryIndex={cross.index}
            dispatch={dispatch}
          />
        );
      } else {
        right = (
          <PotentialFillList
            selected={false}
            gridRef={gridRef}
            header="Down"
            values={crossMatches}
            entryIndex={cross.index}
            dispatch={dispatch}
          />
        );
      }
    }
    if (entry && entryMatches !== null) {
      if (entry.direction === Direction.Across) {
        left = (
          <PotentialFillList
            selected={true}
            gridRef={gridRef}
            header="Across"
            values={entryMatches}
            entryIndex={entry.index}
            dispatch={dispatch}
          />
        );
      } else {
        right = (
          <PotentialFillList
            selected={true}
            gridRef={gridRef}
            header="Down"
            values={entryMatches}
            entryIndex={entry.index}
            dispatch={dispatch}
          />
        );
      }
    }
    return { left, right };
  }, [state.grid, state.active, dispatch]);

  const { autofillEnabled, setAutofillEnabled } = props;
  const toggleAutofillEnabled = useCallback(() => {
    if (autofillEnabled) {
      showSnackbar('Autofill Disabled');
    }
    setAutofillEnabled(!autofillEnabled);
  }, [autofillEnabled, setAutofillEnabled, showSnackbar]);

  const stats = useMemo(() => {
    let totalLength = 0;
    const lengthHistogram: Array<number> = new Array(
      Math.max(state.grid.width, state.grid.height) - 1
    ).fill(0);
    const lengthHistogramNames = lengthHistogram.map((_, i) =>
      (i + 2).toString()
    );

    state.grid.entries.forEach((e) => {
      totalLength += e.cells.length;
      lengthHistogram[e.cells.length - 2] += 1;
    });
    const numEntries = state.grid.entries.length;
    const averageLength = totalLength / numEntries;
    const lettersHistogram: Array<number> = new Array(26).fill(0);
    const lettersHistogramNames = lettersHistogram.map((_, i) =>
      String.fromCharCode(i + 65)
    );
    let numBlocks = 0;
    const numTotal = state.grid.width * state.grid.height;
    state.grid.cells.forEach((s) => {
      if (s === '.') {
        numBlocks += 1;
      } else {
        const index = lettersHistogramNames.indexOf(s);
        if (index !== -1) {
          lettersHistogram[index] += 1;
        }
      }
    });
    return {
      numBlocks,
      numTotal,
      lengthHistogram,
      lengthHistogramNames,
      numEntries,
      averageLength,
      lettersHistogram,
      lettersHistogramNames,
    };
  }, [
    state.grid.entries,
    state.grid.height,
    state.grid.width,
    state.grid.cells,
  ]);

  const keyboardHandler = useCallback(
    (key: string) => {
      const mkey = fromKeyString(key);
      if (isSome(mkey)) {
        const kpa: KeypressAction = { type: 'KEYPRESS', key: mkey.value };
        dispatch(kpa);
      }
    },
    [dispatch]
  );

  const topBarChildren = useMemo(() => {
    let autofillIcon = <SpinnerDisabled />;
    let autofillReverseIcon = <SpinnerWorking />;
    let autofillReverseText = 'Enable Autofill';
    let autofillText = 'Autofill disabled';
    if (props.autofillEnabled) {
      autofillReverseIcon = <SpinnerDisabled />;
      autofillReverseText = 'Disable Autofill';
      if (props.autofillInProgress) {
        autofillIcon = <SpinnerWorking />;
        autofillText = 'Autofill in progress';
      } else if (props.autofilledGrid.length) {
        autofillIcon = <SpinnerFinished />;
        autofillText = 'Autofill complete';
      } else {
        autofillIcon = <SpinnerFailed />;
        autofillText = "Couldn't autofill this grid";
      }
    }
    return (
      <>
        <TopBarDropDown
          onClose={focusGrid}
          icon={autofillIcon}
          text="Autofill"
          hoverText={autofillText}
        >
          {() => (
            <>
              <TopBarDropDownLink
                icon={autofillReverseIcon}
                text={autofillReverseText}
                onClick={toggleAutofillEnabled}
              />
              <TopBarDropDownLink
                icon={<FaSignInAlt />}
                text="Jump to Most Constrained"
                shortcutHint={<ExclamationKey />}
                onClick={() => {
                  const entry = getMostConstrainedEntry();
                  if (entry !== null) {
                    const ca: ClickedEntryAction = {
                      type: 'CLICKEDENTRY',
                      entryIndex: entry,
                    };
                    dispatch(ca);
                  }
                }}
              />
              <TopBarDropDownLink
                icon={<MdRefresh />}
                text="Rerun Autofiller"
                shortcutHint={<EnterKey />}
                onClick={() => {
                  reRunAutofill();
                }}
              />
            </>
          )}
        </TopBarDropDown>
        <TopBarLink
          icon={<FaListOl />}
          text="Clues"
          onClick={() => setClueMode(true)}
        />
        <TopBarLink
          icon={<FaRegNewspaper />}
          text="Publish"
          onClick={() => {
            const a: PublishAction = {
              type: 'PUBLISH',
              publishTimestamp: TimestampClass.now(),
            };
            dispatch(a);
          }}
        />
        <TopBarDropDown onClose={focusGrid} icon={<FaEllipsisH />} text="More">
          {(closeDropdown) => (
            <>
              <NestedDropDown
                onClose={focusGrid}
                closeParent={closeDropdown}
                icon={<FaRegPlusSquare />}
                text="New Puzzle"
              >
                {() => <NewPuzzleForm dispatch={dispatch} />}
              </NestedDropDown>
              <NestedDropDown
                onClose={focusGrid}
                closeParent={closeDropdown}
                icon={<FaFileImport />}
                text="Import .puz File"
              >
                {() => <ImportPuzForm dispatch={dispatch} />}
              </NestedDropDown>
              <TopBarDropDownLink
                icon={<FaRegFile />}
                text="Export .puz File"
                onClick={() => {
                  const a: SetShowDownloadLink = {
                    type: 'SETSHOWDOWNLOAD',
                    value: true,
                  };
                  dispatch(a);
                }}
              />
              <NestedDropDown
                onClose={focusGrid}
                closeParent={closeDropdown}
                icon={<IoMdStats />}
                text="Stats"
              >
                {() => (
                  <>
                    <h2>Grid</h2>
                    <div>
                      {state.gridIsComplete ? (
                        <FaRegCheckCircle />
                      ) : (
                        <FaRegCircle />
                      )}{' '}
                      All cells should be filled
                    </div>
                    <div>
                      {state.hasNoShortWords ? (
                        <FaRegCheckCircle />
                      ) : (
                        <FaRegCircle />
                      )}{' '}
                      All words should be at least three letters
                    </div>
                    <div>
                      {state.repeats.size > 0 ? (
                        <>
                          <FaRegCircle /> (
                          {Array.from(state.repeats).sort().join(', ')})
                        </>
                      ) : (
                        <FaRegCheckCircle />
                      )}{' '}
                      No words should be repeated
                    </div>
                    <h2 css={{ marginTop: '1.5em' }}>Fill</h2>
                    <div>Number of words: {stats.numEntries}</div>
                    <div>
                      Mean word length: {stats.averageLength.toPrecision(3)}
                    </div>
                    <div>
                      Number of blocks: {stats.numBlocks} (
                      {((100 * stats.numBlocks) / stats.numTotal).toFixed(1)}%)
                    </div>
                    <div
                      css={{
                        marginTop: '1em',
                        textDecoration: 'underline',
                        textAlign: 'center',
                      }}
                    >
                      Word Lengths
                    </div>
                    <Histogram
                      data={stats.lengthHistogram}
                      names={stats.lengthHistogramNames}
                    />
                    <div
                      css={{
                        marginTop: '1em',
                        textDecoration: 'underline',
                        textAlign: 'center',
                      }}
                    >
                      Letter Counts
                    </div>
                    <Histogram
                      data={stats.lettersHistogram}
                      names={stats.lettersHistogramNames}
                    />
                  </>
                )}
              </NestedDropDown>
              <NestedDropDown
                onClose={focusGrid}
                closeParent={closeDropdown}
                icon={<SymmetryIcon type={state.symmetry} />}
                text="Change Symmetry"
              >
                {() => (
                  <>
                    <TopBarDropDownLink
                      icon={<SymmetryRotational />}
                      text="Use Rotational Symmetry"
                      onClick={() => {
                        const a: SymmetryAction = {
                          type: 'CHANGESYMMETRY',
                          symmetry: Symmetry.Rotational,
                        };
                        dispatch(a);
                      }}
                    />
                    <TopBarDropDownLink
                      icon={<SymmetryHorizontal />}
                      text="Use Horizontal Symmetry"
                      onClick={() => {
                        const a: SymmetryAction = {
                          type: 'CHANGESYMMETRY',
                          symmetry: Symmetry.Horizontal,
                        };
                        dispatch(a);
                      }}
                    />
                    <TopBarDropDownLink
                      icon={<SymmetryVertical />}
                      text="Use Vertical Symmetry"
                      onClick={() => {
                        const a: SymmetryAction = {
                          type: 'CHANGESYMMETRY',
                          symmetry: Symmetry.Vertical,
                        };
                        dispatch(a);
                      }}
                    />
                    <TopBarDropDownLink
                      icon={<SymmetryNone />}
                      text="Use No Symmetry"
                      onClick={() => {
                        const a: SymmetryAction = {
                          type: 'CHANGESYMMETRY',
                          symmetry: Symmetry.None,
                        };
                        dispatch(a);
                      }}
                    />
                    {state.grid.width === state.grid.height ? (
                      <>
                        <TopBarDropDownLink
                          icon={<SymmetryIcon type={Symmetry.DiagonalNESW} />}
                          text="Use NE/SW Diagonal Symmetry"
                          onClick={() => {
                            const a: SymmetryAction = {
                              type: 'CHANGESYMMETRY',
                              symmetry: Symmetry.DiagonalNESW,
                            };
                            dispatch(a);
                          }}
                        />
                        <TopBarDropDownLink
                          icon={<SymmetryIcon type={Symmetry.DiagonalNWSE} />}
                          text="Use NW/SE Diagonal Symmetry"
                          onClick={() => {
                            const a: SymmetryAction = {
                              type: 'CHANGESYMMETRY',
                              symmetry: Symmetry.DiagonalNWSE,
                            };
                            dispatch(a);
                          }}
                        />
                      </>
                    ) : (
                      ''
                    )}
                  </>
                )}
              </NestedDropDown>
              <TopBarDropDownLink
                icon={<FaSquare />}
                text="Toggle Block"
                shortcutHint={<PeriodKey />}
                onClick={() => {
                  const a: KeypressAction = {
                    type: 'KEYPRESS',
                    key: { k: KeyK.Dot },
                  };
                  dispatch(a);
                }}
              />
              <TopBarDropDownLink
                icon={<CgSidebarRight />}
                text="Toggle Bar"
                shortcutHint={<CommaKey />}
                onClick={() => {
                  const a: KeypressAction = {
                    type: 'KEYPRESS',
                    key: { k: KeyK.Comma },
                  };
                  dispatch(a);
                }}
              />
              <TopBarDropDownLink
                icon={<FaEyeSlash />}
                text="Toggle Cell Visibility"
                shortcutHint={<KeyIcon text="#" />}
                onClick={() => {
                  const a: ToggleHiddenAction = {
                    type: 'TOGGLEHIDDEN',
                  };
                  dispatch(a);
                }}
              />
              <TopBarDropDownLink
                icon={<Rebus />}
                text="Enter Rebus"
                shortcutHint={<EscapeKey />}
                onClick={() => {
                  const a: KeypressAction = {
                    type: 'KEYPRESS',
                    key: { k: KeyK.Escape },
                  };
                  dispatch(a);
                }}
              />
              <TopBarDropDownLink
                icon={
                  state.grid.highlight === 'circle' ? (
                    <FaRegCircle />
                  ) : (
                    <FaFillDrip />
                  )
                }
                text="Toggle Square Highlight"
                shortcutHint={<BacktickKey />}
                onClick={() => {
                  const a: KeypressAction = {
                    type: 'KEYPRESS',
                    key: { k: KeyK.Backtick },
                  };
                  dispatch(a);
                }}
              />
              <TopBarDropDownLink
                icon={
                  state.grid.highlight === 'circle' ? (
                    <FaFillDrip />
                  ) : (
                    <FaRegCircle />
                  )
                }
                text={
                  state.grid.highlight === 'circle'
                    ? 'Use Shade for Highlights'
                    : 'Use Circle for Highlights'
                }
                onClick={() => {
                  const a: SetHighlightAction = {
                    type: 'SETHIGHLIGHT',
                    highlight:
                      state.grid.highlight === 'circle' ? 'shade' : 'circle',
                  };
                  dispatch(a);
                }}
              />
              {muted ? (
                <TopBarDropDownLink
                  icon={<FaVolumeUp />}
                  text="Unmute"
                  onClick={() => setMuted(false)}
                />
              ) : (
                <TopBarDropDownLink
                  icon={<FaVolumeMute />}
                  text="Mute"
                  onClick={() => setMuted(true)}
                />
              )}
              <TopBarDropDownLink
                icon={<FaKeyboard />}
                text="Toggle Keyboard"
                onClick={() => setToggleKeyboard(!toggleKeyboard)}
              />
              {props.isAdmin ? (
                <>
                  <TopBarDropDownLinkA
                    href="/admin"
                    icon={<FaUserLock />}
                    text="Admin"
                  />
                </>
              ) : (
                ''
              )}
              <TopBarDropDownLinkA
                href="/dashboard"
                icon={<FaHammer />}
                text="Constructor Dashboard"
              />
              <TopBarDropDownLinkA
                href="/account"
                icon={<FaUser />}
                text="Account"
              />
            </>
          )}
        </TopBarDropDown>
      </>
    );
  }, [
    focusGrid,
    getMostConstrainedEntry,
    props.autofillEnabled,
    props.autofillInProgress,
    props.autofilledGrid.length,
    stats,
    props.isAdmin,
    setClueMode,
    setMuted,
    state.grid.highlight,
    state.grid.width,
    state.grid.height,
    state.gridIsComplete,
    state.hasNoShortWords,
    state.repeats,
    state.symmetry,
    toggleAutofillEnabled,
    reRunAutofill,
    dispatch,
    muted,
    toggleKeyboard,
    setToggleKeyboard,
  ]);

  return (
    <>
      <Global styles={FULLSCREEN_CSS} />
      <div
        css={{
          display: 'flex',
          flexDirection: 'column',
          height: '100%',
        }}
      >
        <div css={{ flex: 'none' }}>
          <TopBar>{topBarChildren}</TopBar>
        </div>
        {state.showDownloadLink ? (
          <PuzDownloadOverlay
            state={state}
            cancel={() => {
              const a: SetShowDownloadLink = {
                type: 'SETSHOWDOWNLOAD',
                value: false,
              };
              dispatch(a);
            }}
          />
        ) : (
          ''
        )}
        {state.toPublish ? (
          <PublishOverlay
            id={state.id}
            toPublish={state.toPublish}
            warnings={state.publishWarnings}
            user={props.user}
            cancelPublish={() => dispatch({ type: 'CANCELPUBLISH' })}
          />
        ) : (
          ''
        )}
        {state.publishErrors.length ? (
          <Overlay
            closeCallback={() => dispatch({ type: 'CLEARPUBLISHERRORS' })}
          >
            <>
              <div>
                Please fix the following errors and try publishing again:
              </div>
              <ul>
                {state.publishErrors.map((s, i) => (
                  <li key={i}>{s}</li>
                ))}
              </ul>
              {state.publishWarnings.length ? (
                <>
                  <div>Warnings:</div>
                  <ul>
                    {state.publishWarnings.map((s, i) => (
                      <li key={i}>{s}</li>
                    ))}
                  </ul>
                </>
              ) : (
                ''
              )}
            </>
          </Overlay>
        ) : (
          ''
        )}
        <div
          css={{ flex: '1 1 auto', overflow: 'scroll', position: 'relative' }}
        >
          <SquareAndCols
            leftIsActive={state.active.dir === Direction.Across}
            ref={gridRef}
            aspectRatio={state.grid.width / state.grid.height}
            square={(width: number, _height: number) => {
              return (
                <GridView
                  isEnteringRebus={state.isEnteringRebus}
                  rebusValue={state.rebusValue}
                  squareWidth={width}
                  grid={state.grid}
                  active={state.active}
                  dispatch={dispatch}
                  allowBlockEditing={true}
                  autofill={props.autofillEnabled ? props.autofilledGrid : []}
                />
              );
            }}
            left={fillLists.left}
            right={fillLists.right}
            dispatch={dispatch}
          />
        </div>
        <div css={{ flex: 'none', width: '100%' }}>
          <Keyboard
            toggleKeyboard={toggleKeyboard}
            keyboardHandler={keyboardHandler}
            muted={muted}
            showExtraKeyLayout={state.showExtraKeyLayout}
            includeBlockKey={true}
          />
        </div>
      </div>
    </>
  );
}