@material-ui/icons#ArrowDropDown TypeScript Examples

The following examples show how to use @material-ui/icons#ArrowDropDown. 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: ItemGroup.tsx    From projectboard with MIT License 6 votes vote down vote up
ItemGroup = ({ title, children }: Props) => {
  const [showItems, setshowItems] = useState(true);

  let Icon = showItems ? ArrowDropDown : ArrowRight;
  return (
    <div className='flex flex-col w-full text-sm'>
      <div
        className='px-2 relative w-full mt-0.5 h-7 flex items-center rounded hover:bg-gray-100 cursor-pointer'
        onClick={() => setshowItems(!showItems)}
      >
        <Icon className='w-3 h-3 mr-2 -ml-1' />
        {title}
      </div>
      {showItems && children}
    </div>
  );

}
Example #2
Source File: Selector.tsx    From anchor-web-app with Apache License 2.0 5 votes vote down vote up
function SelectorBase<T>({
  className,
  items,
  selectedItem,
  onChange,
  labelFunction,
  keyFunction,
}: SelectorProps<T>) {
  const {
    isOpen,
    getToggleButtonProps,
    getMenuProps,
    highlightedIndex,
    getItemProps,
  } = useSelect({
    items,
    selectedItem,
    onSelectedItemChange: ({ selectedItem }) => onChange(selectedItem ?? null),
  });
  return (
    <div className={className} aria-expanded={isOpen}>
      <button type="button" {...getToggleButtonProps()}>
        <span aria-selected={!!selectedItem}>
          {labelFunction(selectedItem)}
        </span>
        <i>{isOpen ? <ArrowDropUp /> : <ArrowDropDown />}</i>
      </button>
      {isOpen && <HorizontalRuler />}
      <ul {...getMenuProps()}>
        {isOpen &&
          items.map((item, index) => (
            <li
              data-selected={highlightedIndex === index}
              key={`${keyFunction(item)}${index}`}
              {...getItemProps({ item, index })}
            >
              {labelFunction(item)}
            </li>
          ))}
      </ul>
    </div>
  );
}
Example #3
Source File: SwapTokenDetails.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
SwapTokenDetails: React.FC<{
  token: Token;
}> = ({ token }) => {
  const classes = useStyles();
  const currency = unwrappedToken(token);
  const tokenAddress = token.address;
  const { palette } = useTheme();
  const latestBlock = useBlockNumber();
  const { tokenDetails, updateTokenDetails } = useTokenDetails();
  const [tokenData, setTokenData] = useState<any>(null);
  const [priceData, setPriceData] = useState<any>(null);
  const priceUp = Number(tokenData?.priceChangeUSD) > 0;
  const priceUpPercent = Number(tokenData?.priceChangeUSD).toFixed(2);
  const [isCopied, setCopied] = useCopyClipboard();
  const prices = priceData ? priceData.map((price: any) => price.close) : [];

  useEffect(() => {
    async function fetchTokenData() {
      const tokenDetail = tokenDetails.find(
        (item) => item.address === tokenAddress,
      );
      setTokenData(tokenDetail?.tokenData);
      setPriceData(tokenDetail?.priceData);
      const currentTime = dayjs.utc();
      const startTime = currentTime
        .subtract(1, 'day')
        .startOf('hour')
        .unix();
      const tokenPriceData = await getIntervalTokenData(
        tokenAddress,
        startTime,
        3600,
        latestBlock,
      );
      setPriceData(tokenPriceData);

      const [newPrice, oneDayPrice] = await getEthPrice();
      const tokenInfo = await getTokenInfo(newPrice, oneDayPrice, tokenAddress);
      if (tokenInfo) {
        const token0 = tokenInfo[0];
        setTokenData(token0);
        const tokenDetailToUpdate = {
          address: tokenAddress,
          tokenData: token0,
          priceData: tokenPriceData,
        };
        updateTokenDetails(tokenDetailToUpdate);
      }
    }
    fetchTokenData();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tokenAddress]);

  return (
    <Box>
      <Box
        display='flex'
        alignItems='center'
        justifyContent='space-between'
        px={2}
        py={1.5}
      >
        <Box display='flex' alignItems='center'>
          <CurrencyLogo currency={currency} size='28px' />
          <Box ml={1}>
            <Typography variant='body2'>{currency.symbol}</Typography>
            {tokenData ? (
              <Box display='flex' alignItems='center'>
                <Typography variant='body2'>
                  ${formatNumber(tokenData.priceUSD)}
                </Typography>
                <Box
                  ml={0.5}
                  display='flex'
                  alignItems='center'
                  className={priceUp ? classes.success : classes.danger}
                >
                  {priceUp ? <ArrowDropUp /> : <ArrowDropDown />}
                  <Typography variant='body2'>{priceUpPercent}%</Typography>
                </Box>
              </Box>
            ) : (
              <Skeleton variant='rect' width={100} height={20} />
            )}
          </Box>
        </Box>
        {tokenData && priceData ? (
          <Box width={88} height={47} position='relative'>
            <Box position='absolute' top={-30} width={1}>
              {prices.length > 0 && (
                <LineChart
                  data={prices}
                  width='100%'
                  height={120}
                  color={priceUp ? palette.success.main : palette.error.main}
                />
              )}
            </Box>
          </Box>
        ) : (
          <Skeleton variant='rect' width={88} height={47} />
        )}
      </Box>
      <Box
        borderTop={`1px solid ${palette.secondary.light}`}
        borderBottom={`1px solid ${palette.secondary.light}`}
        px={2}
      >
        <Grid container>
          <Grid item xs={6}>
            <Box borderRight={`1px solid ${palette.secondary.light}`} py={1}>
              {tokenData ? (
                <Typography
                  variant='body2'
                  style={{ color: palette.text.secondary }}
                >
                  TVL: {formatCompact(tokenData?.totalLiquidityUSD)}
                </Typography>
              ) : (
                <Skeleton variant='rect' width={100} height={16} />
              )}
            </Box>
          </Grid>
          <Grid item xs={6}>
            <Box py={1} pl={2}>
              {tokenData ? (
                <Typography
                  variant='body2'
                  style={{ color: palette.text.secondary }}
                >
                  24h VOL: {formatCompact(tokenData?.oneDayVolumeUSD)}
                </Typography>
              ) : (
                <Skeleton variant='rect' width={100} height={16} />
              )}
            </Box>
          </Grid>
        </Grid>
      </Box>
      <Box
        display='flex'
        justifyContent='space-between'
        alignItems='center'
        py={1}
        px={2}
      >
        <a
          href={`https://polygonscan.com/token/${tokenAddress}`}
          target='_blank'
          rel='noopener noreferrer'
          style={{ textDecoration: 'none' }}
        >
          <Typography variant='body2' style={{ color: palette.primary.main }}>
            {shortenAddress(tokenAddress)}
          </Typography>
        </a>
        <Box
          display='flex'
          style={{ cursor: 'pointer', opacity: isCopied ? 0.5 : 1 }}
          onClick={() => {
            setCopied(tokenAddress);
          }}
        >
          <CopyIcon />
        </Box>
      </Box>
    </Box>
  );
}
Example #4
Source File: TopMovers.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
TopMovers: React.FC<TopMoversProps> = ({
  background,
  hideArrow = false,
}) => {
  const classes = useStyles();
  const { palette, breakpoints } = useTheme();
  const [topTokens, updateTopTokens] = useState<any[] | null>(null);
  const smallWindowSize = useMediaQuery(breakpoints.down('xs'));

  const topMoverTokens = useMemo(
    () => (topTokens && topTokens.length >= 5 ? topTokens.slice(0, 5) : null),
    [topTokens],
  );

  useEffect(() => {
    async function fetchTopTokens() {
      const [newPrice, oneDayPrice] = await getEthPrice();
      const topTokensData = await getTopTokens(newPrice, oneDayPrice, 5);
      if (topTokensData) {
        updateTopTokens(topTokensData);
      }
    }
    fetchTopTokens();
  }, [updateTopTokens]);

  return (
    <Box
      width='100%'
      display='flex'
      flexWrap='wrap'
      flexDirection='column'
      justifyContent='center'
      alignItems={smallWindowSize ? 'center' : 'flex-start'}
      bgcolor={background}
      border={`1px solid ${
        background === 'transparent' ? palette.background.paper : 'transparent'
      }`}
      borderRadius={10}
      px={2.5}
      pt={2.5}
      pb={0.5}
    >
      <Typography variant='h6' style={{ color: palette.text.secondary }}>
        24h TOP MOVERS
      </Typography>
      <Box width={1} pb={2} style={{ overflowX: 'auto' }}>
        {topMoverTokens ? (
          <Box className={classes.content}>
            {topMoverTokens.map((token: any, index: number) => {
              const currency = new Token(
                ChainId.MATIC,
                getAddress(token.id),
                token.decimals,
              );
              const priceColor = getPriceColor(
                Number(token.priceChangeUSD),
                palette,
              );
              const priceUp = Number(token.priceChangeUSD) > 0;
              const priceDown = Number(token.priceChangeUSD) < 0;
              const priceUpPercent = Number(token.priceChangeUSD).toFixed(2);
              return (
                <Box
                  mr={index < topMoverTokens.length ? 2 : 0}
                  width={smallWindowSize ? 180 : 'unset'}
                  mt={2}
                  key={token.id}
                  display='flex'
                  flexDirection='row'
                  justifyContent={smallWindowSize ? 'flex-start' : 'center'}
                  alignItems='center'
                >
                  <CurrencyLogo currency={currency} size='28px' />
                  <Box ml={1}>
                    <Typography variant='body2' style={{ fontWeight: 'bold' }}>
                      {token.symbol}
                    </Typography>
                    <Box
                      display='flex'
                      flexDirection='row'
                      justifyContent='center'
                      alignItems='center'
                    >
                      <Typography variant='body2'>
                        ${formatNumber(token.priceUSD)}
                      </Typography>
                      <Box
                        ml={hideArrow ? 1 : 0}
                        display='flex'
                        flexDirection='row'
                        justifyContent='center'
                        alignItems='center'
                        px={0.75}
                        py={0.25}
                        borderRadius={12}
                        bgcolor={
                          !hideArrow ? 'transparent' : priceColor.bgColor
                        }
                        color={priceColor.textColor}
                      >
                        {!hideArrow && priceUp && <ArrowDropUp />}
                        {!hideArrow && priceDown && <ArrowDropDown />}
                        <Typography variant='caption'>
                          {hideArrow && priceUp ? '+' : ''}
                          {priceUpPercent}%
                        </Typography>
                      </Box>
                    </Box>
                  </Box>
                </Box>
              );
            })}
          </Box>
        ) : (
          <Skeleton variant='rect' width='100%' height={100} />
        )}
      </Box>
    </Box>
  );
}
Example #5
Source File: index.tsx    From prism-frontend with MIT License 4 votes vote down vote up
function AlertForm({ classes, isOpen, setOpen }: AlertFormProps) {
  const boundaryLayerData = useSelector(layerDataSelector(boundaryLayer.id)) as
    | LayerData<BoundaryLayerProps>
    | undefined;
  const regionsList = useSelector(getSelectedBoundaries);
  const dispatch = useDispatch();
  // form elements
  const [hazardLayerId, setHazardLayerId] = useState<LayerKey>();
  const [emailValid, setEmailValid] = useState<boolean>(false);
  const [email, setEmail] = useState('');
  const [belowThreshold, setBelowThreshold] = useState('');
  const [aboveThreshold, setAboveThreshold] = useState('');
  const [thresholdError, setThresholdError] = useState<string | null>(null);
  const [alertName, setAlertName] = useState('');
  const [alertWaiting, setAlertWaiting] = useState(false);

  const regionCodesToFeatureData: { [k: string]: object } = useMemo(() => {
    if (!boundaryLayerData) {
      // Not loaded yet. Will proceed when it is.
      return {};
    }

    return Object.fromEntries(
      boundaryLayerData.data.features
        .filter(feature => feature.properties !== null)
        .map(feature => {
          return [feature.properties?.[boundaryLayer.adminCode], feature];
        }),
    );
  }, [boundaryLayerData]);

  const generateGeoJsonForRegionNames = () => {
    // TODO - Handle these errors properly.
    if (!boundaryLayerData) {
      throw new Error('Boundary layer data is not loaded yet.');
    }

    if (regionsList.length === 0) {
      throw new Error('Please select at least one region boundary.');
    }

    const features = regionsList.map(region => {
      return regionCodesToFeatureData[region];
    });

    // Generate a copy of admin layer data (to preserve top-level properties)
    // and replace the 'features' property with just the selected regions.
    const mutableFeatureCollection = JSON.parse(
      JSON.stringify(boundaryLayerData.data),
    );
    // eslint-disable-next-line fp/no-mutation
    mutableFeatureCollection.features = features;

    return mutableFeatureCollection;
  };

  const onChangeEmail = (event: React.ChangeEvent<HTMLInputElement>) => {
    const newEmail = event.target.value;
    setEmailValid(!!newEmail.match(EMAIL_REGEX));
    setEmail(newEmail);
  };

  const onOptionChange = <T extends string>(
    setterFunc: Dispatch<SetStateAction<T>>,
  ) => (event: React.ChangeEvent<HTMLInputElement>) => {
    const value = event.target.value as T;
    setterFunc(value);
    return value;
  };

  // specially for threshold values, also does error checking
  const onThresholdOptionChange = (thresholdType: 'above' | 'below') => (
    event: React.ChangeEvent<HTMLInputElement>,
  ) => {
    const setterFunc =
      thresholdType === 'above' ? setAboveThreshold : setBelowThreshold;
    const changedOption = onOptionChange(setterFunc)(event);
    // setting a value doesn't update the existing value until next render,
    // therefore we must decide whether to access the old one or the newly change one here.
    const aboveThresholdValue = parseFloat(
      thresholdType === 'above' ? changedOption : aboveThreshold,
    );
    const belowThresholdValue = parseFloat(
      thresholdType === 'below' ? changedOption : belowThreshold,
    );
    if (belowThresholdValue > aboveThresholdValue) {
      setThresholdError('Below threshold is larger than above threshold!');
    } else {
      setThresholdError(null);
    }
  };

  const runAlertForm = async () => {
    if (!hazardLayerId) {
      throw new Error('Layer should be selected to create alert.');
    }

    const request: AlertRequest = {
      alert_name: alertName,
      alert_config: LayerDefinitions[hazardLayerId] as WMSLayerProps,
      max: parseFloat(aboveThreshold) || undefined,
      min: parseFloat(belowThreshold) || undefined,
      zones: generateGeoJsonForRegionNames(),
      email,
      prism_url: getPrismUrl(),
    };

    setAlertWaiting(true);
    const response = await fetchApiData(ALERT_API_URL, request);
    setAlertWaiting(false);

    if ('message' in response) {
      // TODO response isn't typed correctly because fetchApiData is too strict.
      dispatch(
        addNotification({
          message: (response as { message: string }).message,
          type: 'success',
        }),
      );
    }
  };

  if (!ALERT_FORM_ENABLED) {
    return null;
  }

  return (
    <div className={classes.alertForm}>
      <Button
        variant="contained"
        color="primary"
        onClick={() => {
          setOpen(!isOpen);
        }}
      >
        <Notifications fontSize="small" />
        <Typography variant="body2" className={classes.alertLabel}>
          Create Alert
        </Typography>
        <ArrowDropDown fontSize="small" />
      </Button>

      <Box
        className={classes.alertFormMenu}
        width={isOpen ? 'min-content' : 0}
        padding={isOpen ? '10px' : 0}
      >
        {isOpen ? (
          <div>
            <div className={classes.newAlertFormContainer}>
              <div className={classes.alertFormOptions}>
                <Typography variant="body2">Hazard Layer</Typography>
                <LayerDropdown
                  type="wms"
                  value={hazardLayerId}
                  setValue={setHazardLayerId}
                  className={classes.selector}
                  placeholder="Choose hazard layer"
                />
              </div>
              <div className={classes.alertFormOptions}>
                <Typography variant="body2">Threshold</Typography>
                <TextField
                  id="filled-number"
                  error={!!thresholdError}
                  helperText={thresholdError}
                  className={classes.numberField}
                  label="Below"
                  type="number"
                  value={belowThreshold}
                  onChange={onThresholdOptionChange('below')}
                  variant="filled"
                />
                <TextField
                  id="filled-number"
                  label="Above"
                  className={classes.numberField}
                  style={{ paddingLeft: '10px' }}
                  value={aboveThreshold}
                  onChange={onThresholdOptionChange('above')}
                  type="number"
                  variant="filled"
                />
              </div>
              <div className={classes.alertFormOptions}>
                <Typography variant="body2">Regions</Typography>
                <BoundaryDropdown className={classes.regionSelector} />
              </div>
              <div className={classes.alertFormOptions}>
                <TextField
                  id="alert-name"
                  label="Alert Name"
                  type="text"
                  variant="filled"
                  value={alertName}
                  onChange={e => setAlertName(e.target.value)}
                  fullWidth
                />
              </div>
              <div className={classes.alertFormOptions}>
                <TextField
                  id="email-address"
                  label="Email Address"
                  type="text"
                  variant="filled"
                  onChange={onChangeEmail}
                  fullWidth
                />
              </div>
            </div>
            <Button
              className={classes.innerCreateAlertButton}
              onClick={runAlertForm}
              disabled={
                !hazardLayerId ||
                !!thresholdError ||
                !emailValid ||
                alertWaiting ||
                regionsList.length === 0
              }
            >
              <Typography variant="body2">Create Alert</Typography>
            </Button>
          </div>
        ) : null}
      </Box>
    </div>
  );
}
Example #6
Source File: index.tsx    From prism-frontend with MIT License 4 votes vote down vote up
function Analyser({ extent, classes }: AnalyserProps) {
  const dispatch = useDispatch();
  const map = useSelector(mapSelector);
  const selectedLayers = useSelector(layersSelector);
  const {
    updateHistory,
    removeKeyFromUrl,
    resetAnalysisParams,
    updateAnalysisParams,
    getAnalysisParams,
  } = useUrlHistory();

  const availableDates = useSelector(availableDatesSelector);
  const analysisResult = useSelector(analysisResultSelector);

  const isAnalysisLoading = useSelector(isAnalysisLoadingSelector);
  const isMapLayerActive = useSelector(isAnalysisLayerActiveSelector);

  const {
    analysisHazardLayerId: hazardLayerIdFromUrl,
    analysisBaselineLayerId: baselineLayerIdFromUrl,
    analysisDate: selectedDateFromUrl,
    analysisStatistic: selectedStatisticFromUrl,
    analysisThresholdAbove: aboveThresholdFromUrl,
    analysisThresholdBelow: belowThresholdFromUrl,
    analysisAdminLevel: adminLevelFromUrl,
    analysisStartDate: selectedStartDateFromUrl,
    analysisEndDate: selectedEndDateFromUrl,
  } = getAnalysisParams();

  // form elements
  const [hazardLayerId, setHazardLayerId] = useState<LayerKey | undefined>(
    hazardLayerIdFromUrl,
  );
  const [statistic, setStatistic] = useState(
    (selectedStatisticFromUrl as AggregationOperations) ||
      AggregationOperations.Mean,
  );
  const [baselineLayerId, setBaselineLayerId] = useState<LayerKey | undefined>(
    baselineLayerIdFromUrl,
  );
  const [selectedDate, setSelectedDate] = useState<number | null>(null);
  const [belowThreshold, setBelowThreshold] = useState(
    belowThresholdFromUrl || '',
  );
  const [aboveThreshold, setAboveThreshold] = useState(
    aboveThresholdFromUrl || '',
  );
  const [thresholdError, setThresholdError] = useState<string | null>(null);

  const [isAnalyserFormOpen, setIsAnalyserFormOpen] = useState<boolean>(
    hazardLayerIdFromUrl !== undefined,
  );
  const [isTableViewOpen, setIsTableViewOpen] = useState(true);

  // for polygon intersection analysis
  const [adminLevel, setAdminLevel] = useState<AdminLevelType>(
    Number(adminLevelFromUrl || '1') as AdminLevelType,
  );
  const [startDate, setStartDate] = useState<number | null>(null);
  const [endDate, setEndDate] = useState<number | null>(null);

  // find layer for the given adminLevel
  const adminLevelLayer = getAdminLevelLayer(adminLevel);
  const adminLevelLayerData = useSelector(
    // if we couldn't find an admin layer, just return undefined
    adminLevelLayer ? layerDataSelector(adminLevelLayer.id) : () => undefined,
  ) as LayerData<BoundaryLayerProps> | undefined;

  // get variables derived from state
  const selectedHazardLayer = hazardLayerId
    ? (LayerDefinitions[hazardLayerId] as WMSLayerProps)
    : null;
  const hazardDataType: HazardDataType | null = selectedHazardLayer
    ? selectedHazardLayer.geometry || RasterType.Raster
    : null;
  const availableHazardDates = selectedHazardLayer
    ? getPossibleDatesForLayer(selectedHazardLayer, availableDates)?.map(
        d => new Date(d),
      ) || []
    : undefined;

  const BASELINE_URL_LAYER_KEY = 'baselineLayerId';
  const preSelectedBaselineLayer = selectedLayers.find(
    l => l.type === 'admin_level_data',
  );
  const [previousBaselineId, setPreviousBaselineId] = useState<
    LayerKey | undefined
  >(preSelectedBaselineLayer?.id);

  const { t } = useSafeTranslation();

  // check if there is any available date from the url, otherwise use last available date for the selected hazard layer
  const lastAvailableHazardDate = availableHazardDates
    ? getDateFromList(
        selectedDateFromUrl ? new Date(selectedDateFromUrl) : null,
        availableHazardDates,
      )?.getTime() || null
    : null;
  const lastAvailableHazardStartDate = availableHazardDates
    ? getDateFromList(
        selectedStartDateFromUrl ? new Date(selectedStartDateFromUrl) : null,
        availableHazardDates,
      )?.getTime() || null
    : null;
  const lastAvailableHazardEndDate = availableHazardDates
    ? getDateFromList(
        selectedEndDateFromUrl ? new Date(selectedEndDateFromUrl) : null,
        availableHazardDates,
      )?.getTime() || null
    : null;
  const { translatedColumns } = useAnalysisTableColumns(analysisResult);

  // set default date after dates finish loading and when hazard layer changes
  useEffect(() => {
    if (isNil(lastAvailableHazardDate)) {
      setSelectedDate(null);
    } else {
      setSelectedDate(lastAvailableHazardDate);
    }
    if (isNil(lastAvailableHazardStartDate)) {
      setStartDate(null);
    } else {
      setStartDate(lastAvailableHazardStartDate);
    }
    if (isNil(lastAvailableHazardEndDate)) {
      setEndDate(null);
    } else {
      setEndDate(lastAvailableHazardEndDate);
    }
  }, [
    availableDates,
    hazardLayerId,
    lastAvailableHazardDate,
    lastAvailableHazardStartDate,
    lastAvailableHazardEndDate,
  ]);

  const onOptionChange = <T extends string>(
    setterFunc: Dispatch<SetStateAction<T>>,
  ) => (event: React.ChangeEvent<HTMLInputElement>) => {
    const value = event.target.value as T;
    setterFunc(value);
    return value;
  };
  // specially for threshold values, also does error checking
  const onThresholdOptionChange = (thresholdType: 'above' | 'below') => (
    event: React.ChangeEvent<HTMLInputElement>,
  ) => {
    const setterFunc =
      thresholdType === 'above' ? setAboveThreshold : setBelowThreshold;
    const changedOption = onOptionChange(setterFunc)(event);
    // setting a value doesn't update the existing value until next render, therefore we must decide whether to access the old one or the newly change one here.
    const aboveThresholdValue = parseFloat(
      thresholdType === 'above' ? changedOption : aboveThreshold,
    );
    const belowThresholdValue = parseFloat(
      thresholdType === 'below' ? changedOption : belowThreshold,
    );
    if (belowThresholdValue > aboveThresholdValue) {
      setThresholdError('Below threshold is larger than above threshold!');
    } else {
      setThresholdError(null);
    }
  };

  const statisticOptions = Object.entries(AggregationOperations)
    .filter(([, value]) => value !== AggregationOperations.Sum) // sum is used only for exposure analysis.
    .map(([key, value]) => (
      <FormControlLabel
        key={key}
        value={value}
        control={
          <Radio
            className={classes.radioOptions}
            color="default"
            size="small"
          />
        }
        label={t(key)}
      />
    ));
  const activateUniqueBoundary = (forceAdminLevel?: BoundaryLayerProps) => {
    if (forceAdminLevel) {
      // remove displayed boundaries
      getDisplayBoundaryLayers().forEach(l => {
        if (l.id !== forceAdminLevel.id) {
          safeDispatchRemoveLayer(map, l, dispatch);
        }
      });

      safeDispatchAddLayer(
        map,
        { ...forceAdminLevel, isPrimary: true },
        dispatch,
      );
      return;
    }

    if (!baselineLayerId) {
      throw new Error('Layer should be selected to run analysis');
    }

    const baselineLayer = LayerDefinitions[
      baselineLayerId
    ] as AdminLevelDataLayerProps;

    if (baselineLayer.boundary) {
      const boundaryLayer = LayerDefinitions[
        baselineLayer.boundary
      ] as BoundaryLayerProps;
      // remove displayed boundaries
      getDisplayBoundaryLayers().forEach(l => {
        if (l.id !== boundaryLayer.id) {
          safeDispatchRemoveLayer(map, l, dispatch);
        }
      });

      safeDispatchAddLayer(
        map,
        { ...boundaryLayer, isPrimary: true },
        dispatch,
      );
    } else {
      getDisplayBoundaryLayers().forEach(l => {
        safeDispatchAddLayer(map, l, dispatch);
      });
    }
  };

  const deactivateUniqueBoundary = () => {
    if (!baselineLayerId) {
      throw new Error('Layer should be selected to run analysis');
    }
    const baselineLayer = LayerDefinitions[
      baselineLayerId
    ] as AdminLevelDataLayerProps;

    if (baselineLayer.boundary) {
      const boundaryLayer = LayerDefinitions[
        baselineLayer.boundary
      ] as BoundaryLayerProps;
      if (!getDisplayBoundaryLayers().includes(boundaryLayer)) {
        safeDispatchRemoveLayer(map, boundaryLayer, dispatch);
      }
    }

    getDisplayBoundaryLayers().forEach(l => {
      safeDispatchAddLayer(map, l, dispatch);
    });
  };

  const clearAnalysis = () => {
    dispatch(clearAnalysisResult());

    resetAnalysisParams();

    if (previousBaselineId) {
      const previousBaseline = LayerDefinitions[
        previousBaselineId
      ] as AdminLevelDataLayerProps;
      updateHistory(BASELINE_URL_LAYER_KEY, previousBaselineId);
      safeDispatchAddLayer(map, previousBaseline, dispatch);
      // check isMapLayerActive on analysis clear
      // to avoid miss behaviour on boundary layers
      dispatch(setIsMapLayerActive(true));
    }
  };

  const shareAnalysis = () => {
    copyTextToClipboard(window.location.href).then(() => {
      dispatch(
        addNotification({
          message: 'Link to this analysis copied to clipboard!',
          type: 'success',
        }),
      );
    });
  };

  const onMapSwitchChange = (e: ChangeEvent<HTMLInputElement>) => {
    dispatch(setIsMapLayerActive(e.target.checked));

    // hazard layer doesn't needs a display boundary
    // because it is already a vector
    if (hazardDataType === GeometryType.Polygon) {
      return;
    }

    if (isMapLayerActive) {
      deactivateUniqueBoundary();
      // check for previous baseline and bring it back
      if (previousBaselineId) {
        const previousBaseline = LayerDefinitions[
          previousBaselineId
        ] as AdminLevelDataLayerProps;
        updateHistory(BASELINE_URL_LAYER_KEY, previousBaselineId);
        safeDispatchAddLayer(map, previousBaseline, dispatch);
      }
    } else {
      // check for previous baseline and remove it before...
      if (previousBaselineId) {
        const previousBaseline = LayerDefinitions[
          previousBaselineId
        ] as AdminLevelDataLayerProps;
        removeKeyFromUrl(BASELINE_URL_LAYER_KEY);
        safeDispatchRemoveLayer(map, previousBaseline, dispatch);
      }

      // activating the unique boundary layer
      activateUniqueBoundary();
    }
  };

  const runAnalyser = async () => {
    if (preSelectedBaselineLayer) {
      setPreviousBaselineId(preSelectedBaselineLayer.id);
      removeKeyFromUrl(BASELINE_URL_LAYER_KEY);
      // no need to safely dispatch remove we are sure
      dispatch(removeLayer(preSelectedBaselineLayer));
    }

    if (analysisResult) {
      clearAnalysis();
    }

    if (!extent) {
      return;
    } // hasn't been calculated yet

    if (!selectedHazardLayer) {
      throw new Error('Hazard layer should be selected to run analysis');
    }

    if (hazardDataType === GeometryType.Polygon) {
      if (!startDate) {
        throw new Error('Date Range must be given to run analysis');
      }
      if (!endDate) {
        throw new Error('Date Range must be given to run analysis');
      }
      if (!adminLevelLayer || !adminLevelLayerData) {
        // technically we can't get here because the run analaysis button
        // is disabled while the admin level data loads
        // but we have to put this in so the typescript compiler
        // doesn't throw an error when we try to access the data
        // property of adminLevelLayerData
        throw new Error('Admin level data is still loading');
      }

      const params: PolygonAnalysisDispatchParams = {
        hazardLayer: selectedHazardLayer,
        adminLevel,
        adminLevelLayer,
        adminLevelData: adminLevelLayerData.data,
        startDate,
        endDate,
        extent,
      };
      activateUniqueBoundary(adminLevelLayer);
      // update history
      updateAnalysisParams({
        analysisHazardLayerId: hazardLayerId,
        analysisAdminLevel: adminLevel.toString(),
        analysisStartDate: moment(startDate).format(DEFAULT_DATE_FORMAT),
        analysisEndDate: moment(endDate).format(DEFAULT_DATE_FORMAT),
        analysisStatistic: statistic,
      });
      dispatch(requestAndStorePolygonAnalysis(params));
    } else {
      if (!selectedDate) {
        throw new Error('Date must be given to run analysis');
      }

      if (!baselineLayerId) {
        throw new Error('Baseline layer should be selected to run analysis');
      }

      const selectedBaselineLayer = LayerDefinitions[
        baselineLayerId
      ] as AdminLevelDataLayerProps;

      activateUniqueBoundary();

      const params: AnalysisDispatchParams = {
        hazardLayer: selectedHazardLayer,
        baselineLayer: selectedBaselineLayer,
        date: selectedDate,
        statistic,
        extent,
        threshold: {
          above: parseFloat(aboveThreshold) || undefined,
          below: parseFloat(belowThreshold) || undefined,
        },
      };

      // update history
      updateAnalysisParams({
        analysisHazardLayerId: hazardLayerId,
        analysisBaselineLayerId: baselineLayerId,
        analysisDate: moment(selectedDate).format(DEFAULT_DATE_FORMAT),
        analysisStatistic: statistic,
        analysisThresholdAbove: aboveThreshold || undefined,
        analysisThresholdBelow: belowThreshold || undefined,
      });

      dispatch(requestAndStoreAnalysis(params));
    }
  };

  return (
    <div className={classes.analyser}>
      <Button
        variant="contained"
        color="primary"
        onClick={() => {
          setIsAnalyserFormOpen(!isAnalyserFormOpen);
        }}
      >
        <BarChart fontSize="small" />
        <Typography variant="body2" className={classes.analyserLabel}>
          {t('Run Analysis')}
        </Typography>
        <ArrowDropDown fontSize="small" />
      </Button>

      <Box
        className={classes.analyserMenu}
        width={isAnalyserFormOpen ? 'min-content' : 0}
        padding={isAnalyserFormOpen ? '10px' : 0}
      >
        {isAnalyserFormOpen ? (
          <div>
            <div className={classes.newAnalyserContainer}>
              <div className={classes.analyserOptions}>
                <Typography variant="body2">{t('Hazard Layer')}</Typography>
                <LayerDropdown
                  type="wms"
                  value={hazardLayerId}
                  setValue={setHazardLayerId}
                  className={classes.selector}
                  placeholder="Choose hazard layer"
                />
              </div>

              {hazardDataType === GeometryType.Polygon && (
                <>
                  <div className={classes.analyserOptions}>
                    <Typography variant="body2">Admin Level</Typography>
                    <SimpleDropdown
                      value={adminLevel}
                      options={range(getAdminLevelCount()).map(i => [
                        (i + 1) as AdminLevelType,
                        `Admin ${i + 1}`,
                      ])}
                      onChange={setAdminLevel}
                    />
                  </div>

                  <div className={classes.analyserOptions}>
                    <Typography variant="body2">{t('Date Range')}</Typography>
                    <div className={classes.dateRangePicker}>
                      <Typography variant="body2">{t('Start')}</Typography>
                      <DatePicker
                        selected={startDate ? new Date(startDate) : null}
                        onChange={date =>
                          setStartDate(date?.getTime() || startDate)
                        }
                        maxDate={new Date()}
                        todayButton="Today"
                        peekNextMonth
                        showMonthDropdown
                        showYearDropdown
                        dropdownMode="select"
                        customInput={<Input />}
                        popperClassName={classes.calendarPopper}
                        includeDates={availableHazardDates}
                      />
                    </div>
                    <div className={classes.dateRangePicker}>
                      <Typography variant="body2">{t('End')}</Typography>
                      <DatePicker
                        selected={endDate ? new Date(endDate) : null}
                        onChange={date =>
                          setEndDate(date?.getTime() || endDate)
                        }
                        maxDate={new Date()}
                        todayButton="Today"
                        peekNextMonth
                        showMonthDropdown
                        showYearDropdown
                        dropdownMode="select"
                        customInput={<Input />}
                        popperClassName={classes.calendarPopper}
                        includeDates={availableHazardDates}
                      />
                    </div>
                  </div>
                </>
              )}

              {hazardDataType === RasterType.Raster && (
                <>
                  <div className={classes.analyserOptions}>
                    <Typography variant="body2">{t('Statistic')}</Typography>
                    <FormControl component="div">
                      <RadioGroup
                        name="statistics"
                        value={statistic}
                        onChange={onOptionChange(setStatistic)}
                        row
                      >
                        {statisticOptions}
                      </RadioGroup>
                    </FormControl>
                  </div>
                  <div className={classes.analyserOptions}>
                    <Typography variant="body2">
                      {t('Baseline Layer')}
                    </Typography>
                    <LayerDropdown
                      type="admin_level_data"
                      value={baselineLayerId || undefined}
                      setValue={setBaselineLayerId}
                      className={classes.selector}
                      placeholder="Choose baseline layer"
                    />
                  </div>
                  <div className={classes.analyserOptions}>
                    <Typography variant="body2">{t('Threshold')}</Typography>
                    <TextField
                      id="filled-number"
                      error={!!thresholdError}
                      helperText={thresholdError}
                      className={classes.numberField}
                      label={t('Below')}
                      type="number"
                      value={belowThreshold}
                      onChange={onThresholdOptionChange('below')}
                      variant="filled"
                    />
                    <TextField
                      id="filled-number"
                      label={t('Above')}
                      className={classes.numberField}
                      value={aboveThreshold}
                      onChange={onThresholdOptionChange('above')}
                      type="number"
                      variant="filled"
                    />
                  </div>
                  <div className={classes.analyserOptions}>
                    <Typography variant="body2">{t('Date')}</Typography>
                    <DatePicker
                      locale={t('date_locale')}
                      dateFormat="PP"
                      selected={selectedDate ? new Date(selectedDate) : null}
                      onChange={date =>
                        setSelectedDate(date?.getTime() || selectedDate)
                      }
                      maxDate={new Date()}
                      todayButton={t('Today')}
                      peekNextMonth
                      showMonthDropdown
                      showYearDropdown
                      dropdownMode="select"
                      customInput={<Input />}
                      popperClassName={classes.calendarPopper}
                      includeDates={availableHazardDates}
                    />
                  </div>
                </>
              )}
            </div>

            {!isAnalysisLoading &&
              analysisResult &&
              (analysisResult instanceof BaselineLayerResult ||
                analysisResult instanceof PolygonAnalysisResult) && (
                <>
                  <FormGroup>
                    <FormControlLabel
                      control={
                        <Switch
                          color="default"
                          checked={isMapLayerActive}
                          onChange={onMapSwitchChange}
                        />
                      }
                      label={t('Map View')}
                    />
                    <FormControlLabel
                      control={
                        <Switch
                          color="default"
                          checked={isTableViewOpen}
                          onChange={e => setIsTableViewOpen(e.target.checked)}
                        />
                      }
                      label={t('Table View')}
                    />
                  </FormGroup>
                  {isTableViewOpen && (
                    <AnalysisTable
                      tableData={analysisResult.tableData}
                      columns={translatedColumns}
                    />
                  )}
                  <Button
                    className={classes.innerAnalysisButton}
                    onClick={() =>
                      downloadCSVFromTableData(
                        analysisResult,
                        translatedColumns,
                        selectedDate,
                      )
                    }
                  >
                    <Typography variant="body2">{t('Download')}</Typography>
                  </Button>
                  <Button
                    className={classes.innerAnalysisButton}
                    onClick={clearAnalysis}
                  >
                    <Typography variant="body2">
                      {t('Clear Analysis')}
                    </Typography>
                  </Button>
                  <Button
                    className={classes.innerAnalysisButton}
                    onClick={shareAnalysis}
                  >
                    <Typography variant="body2">
                      {t('Share Analysis')}
                    </Typography>
                  </Button>
                </>
              )}
            {(!analysisResult ||
              analysisResult instanceof ExposedPopulationResult) && (
              <Button
                className={classes.innerAnalysisButton}
                onClick={runAnalyser}
                disabled={
                  !!thresholdError || // if there is a threshold error
                  isAnalysisLoading || // or analysis is currently loading
                  !hazardLayerId || // or hazard layer hasn't been selected
                  (hazardDataType === GeometryType.Polygon
                    ? !startDate || !endDate || !adminLevelLayerData
                    : !selectedDate || !baselineLayerId) // or date hasn't been selected // or baseline layer hasn't been selected
                }
              >
                <Typography variant="body2">{t('Run Analysis')}</Typography>
              </Button>
            )}
            {isAnalysisLoading ? <LinearProgress /> : null}
          </div>
        ) : null}
      </Box>
    </div>
  );
}
Example #7
Source File: index.tsx    From prism-frontend with MIT License 4 votes vote down vote up
function Download({ classes }: DownloadProps) {
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
  const [open, setOpen] = useState(false);
  const selectedMap = useSelector(mapSelector);
  const previewRef = useRef<HTMLCanvasElement>(null);

  const { t } = useSafeTranslation();

  const handleClick = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorEl(event.currentTarget);
  };

  const handleClose = () => {
    setAnchorEl(null);
  };

  const openModal = () => {
    if (selectedMap) {
      const activeLayers = selectedMap.getCanvas();
      const canvas = previewRef.current;
      if (canvas) {
        canvas.setAttribute('width', activeLayers.width.toString());
        canvas.setAttribute('height', activeLayers.height.toString());
        const context = canvas.getContext('2d');
        if (context) {
          context.drawImage(activeLayers, 0, 0);
        }
      }
      setOpen(true);
    }
    handleClose();
  };

  const download = (format: string) => {
    const ext = format === 'pdf' ? 'png' : format;
    const canvas = previewRef.current;
    if (canvas) {
      const file = canvas.toDataURL(`image/${ext}`);
      if (format === 'pdf') {
        // eslint-disable-next-line new-cap
        const pdf = new jsPDF({
          orientation: 'landscape',
        });
        const imgProps = pdf.getImageProperties(file);
        const pdfWidth = pdf.internal.pageSize.getWidth();
        const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
        pdf.addImage(file, 'PNG', 0, 0, pdfWidth, pdfHeight);
        pdf.save('map.pdf');
      } else {
        const link = document.createElement('a');
        link.setAttribute('href', file);
        link.setAttribute('download', `map.${ext}`);
        link.click();
      }
      setOpen(false);
      handleClose();
    }
  };

  return (
    <Grid item>
      <Button variant="contained" color="primary" onClick={handleClick}>
        <CloudDownload fontSize="small" />
        <Hidden smDown>
          <Typography className={classes.label} variant="body2">
            {t('Export')}
          </Typography>
        </Hidden>
        <ArrowDropDown fontSize="small" />
      </Button>
      <ExportMenu
        id="export-menu"
        anchorEl={anchorEl}
        keepMounted
        open={Boolean(anchorEl)}
        onClose={handleClose}
      >
        <ExportMenuItem onClick={openModal}>
          <ListItemIcon>
            <Image fontSize="small" style={{ color: 'white' }} />
          </ListItemIcon>
          <ListItemText primary={t('IMAGE')} />
        </ExportMenuItem>
      </ExportMenu>
      <Dialog
        maxWidth="xl"
        open={open}
        keepMounted
        onClose={() => setOpen(false)}
        aria-labelledby="dialog-preview"
      >
        <DialogTitle className={classes.title} id="dialog-preview">
          {t('Map Preview')}
        </DialogTitle>
        <DialogContent>
          <canvas ref={previewRef} />
        </DialogContent>
        <DialogActions>
          <Button onClick={() => setOpen(false)} color="primary">
            {t('Cancel')}
          </Button>
          <Button
            variant="contained"
            onClick={() => download('png')}
            color="primary"
          >
            {t('Download PNG')}
          </Button>
          <Button
            variant="contained"
            onClick={() => download('jpeg')}
            color="primary"
          >
            {t('Download JPEG')}
          </Button>
          <Button
            variant="contained"
            onClick={() => download('pdf')}
            color="primary"
          >
            {t('Download PDF')}
          </Button>
        </DialogActions>
      </Dialog>
    </Grid>
  );
}
Example #8
Source File: Header.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
HeaderNoCtx: React.FC<ContextType> = (props) => {
  const { searchTerm, setSearchTerm } = props;
  const classes = useStyles();
  const history = useHistory();
  const location = useLocation();
  const {
    currentOrganization,
    setOrganization,
    showAllOrganizations,
    setShowAllOrganizations,
    user,
    logout,
    apiGet
  } = useAuthContext();
  const [navOpen, setNavOpen] = useState(false);
  const [organizations, setOrganizations] = useState<
    (Organization | OrganizationTag)[]
  >([]);
  const theme = useTheme();
  const isSmall = useMediaQuery(theme.breakpoints.down('md'));

  let userLevel = 0;
  if (user && user.isRegistered) {
    if (user.userType === 'standard') {
      userLevel = STANDARD_USER;
    } else {
      userLevel = GLOBAL_ADMIN;
    }
  }

  const fetchOrganizations = useCallback(async () => {
    try {
      const rows = await apiGet<Organization[]>('/organizations/');
      let tags: (OrganizationTag | Organization)[] = [];
      if (userLevel === GLOBAL_ADMIN) {
        tags = await apiGet<OrganizationTag[]>('/organizations/tags');
      }
      setOrganizations(tags.concat(rows));
    } catch (e) {
      console.error(e);
    }
  }, [apiGet, setOrganizations, userLevel]);

  React.useEffect(() => {
    if (userLevel > 0) {
      fetchOrganizations();
    }
  }, [fetchOrganizations, userLevel]);

  const navItems: NavItemType[] = [
    {
      title: 'Overview',
      path: '/',
      users: ALL_USERS,
      exact: true
    },
    {
      title: 'Inventory',
      path: '/inventory',
      users: ALL_USERS,
      exact: false
    },
    { title: 'Feeds', path: '/feeds', users: ALL_USERS, exact: false },
    {
      title: 'Scans',
      path: '/scans',
      users: GLOBAL_ADMIN,
      exact: true
    }
  ].filter(({ users }) => (users & userLevel) > 0);

  const userMenu: NavItemType = {
    title: (
      <div className={classes.userLink}>
        <UserIcon /> My Account <ArrowDropDown />
      </div>
    ),
    path: '#',
    exact: false,
    nested: [
      {
        title: 'Manage Organizations',
        path: '/organizations',
        users: GLOBAL_ADMIN,
        exact: true
      },
      {
        title: 'My Organizations',
        path: '/organizations',
        users: STANDARD_USER,
        exact: true
      },
      {
        title: 'Manage Users',
        path: '/users',
        users: GLOBAL_ADMIN,
        exact: true
      },
      {
        title: 'My Settings',
        path: '/settings',
        users: ALL_USERS,
        exact: true
      },
      {
        title: 'Logout',
        path: '/settings',
        users: ALL_USERS,
        onClick: logout,
        exact: true
      }
    ].filter(({ users }) => (users & userLevel) > 0)
  };

  const userItemsSmall: NavItemType[] = [
    {
      title: 'My Account',
      path: '#',
      users: ALL_USERS,
      exact: true
    },
    {
      title: 'Manage Organizations',
      path: '/organizations',
      users: GLOBAL_ADMIN,
      exact: true
    },
    {
      title: 'My Organizations',
      path: '/organizations',
      users: STANDARD_USER,
      exact: true
    },
    {
      title: 'Manage Users',
      path: '/users',
      users: GLOBAL_ADMIN,
      exact: true
    },
    {
      title: 'My Settings',
      path: '/settings',
      users: ALL_USERS,
      exact: true
    },
    {
      title: 'Logout',
      path: '/',
      users: ALL_USERS,
      onClick: logout,
      exact: true
    }
  ].filter(({ users }) => (users & userLevel) > 0);

  const desktopNavItems: JSX.Element[] = navItems.map((item) => (
    <NavItem key={item.title.toString()} {...item} />
  ));

  const navItemsToUse = () => {
    if (isSmall) {
      return userItemsSmall;
    } else {
      return navItems;
    }
  };

  return (
    <div>
      <AppBar position="static" elevation={0}>
        <div className={classes.inner}>
          <Toolbar>
            <Link to="/">
              <img
                src={logo}
                className={classes.logo}
                alt="Crossfeed Icon Navigate Home"
              />
            </Link>
            <div className={classes.lgNav}>{desktopNavItems.slice()}</div>

            <div className={classes.spacing} />

            {userLevel > 0 && (
              <>
                <SearchBar
                  initialValue={searchTerm}
                  value={searchTerm}
                  onChange={(value) => {
                    if (location.pathname !== '/inventory')
                      history.push('/inventory?q=' + value);
                    setSearchTerm(value, {
                      shouldClearFilters: false,
                      autocompleteResults: false
                    });
                  }}
                />
                {organizations.length > 1 && (
                  <>
                    <div className={classes.spacing} />
                    <Autocomplete
                      options={[{ name: 'All Organizations' }].concat(
                        organizations
                      )}
                      autoComplete={false}
                      className={classes.selectOrg}
                      classes={{
                        option: classes.option
                      }}
                      value={
                        showAllOrganizations
                          ? { name: 'All Organizations' }
                          : currentOrganization ?? undefined
                      }
                      filterOptions={(options, state) => {
                        // If already selected, show all
                        if (
                          options.find(
                            (option) =>
                              option.name.toLowerCase() ===
                              state.inputValue.toLowerCase()
                          )
                        ) {
                          return options;
                        }
                        return options.filter((option) =>
                          option.name
                            .toLowerCase()
                            .includes(state.inputValue.toLowerCase())
                        );
                      }}
                      disableClearable
                      blurOnSelect
                      selectOnFocus
                      getOptionLabel={(option) => option.name}
                      renderOption={(option) => (
                        <React.Fragment>{option.name}</React.Fragment>
                      )}
                      onChange={(
                        event: any,
                        value:
                          | Organization
                          | {
                              name: string;
                            }
                          | undefined
                      ) => {
                        if (value && 'id' in value) {
                          setOrganization(value);
                          setShowAllOrganizations(false);
                        } else {
                          setShowAllOrganizations(true);
                        }
                      }}
                      renderInput={(params) => (
                        <TextField
                          {...params}
                          variant="outlined"
                          inputProps={{
                            ...params.inputProps,
                            id: 'autocomplete-input',
                            autoComplete: 'new-password' // disable autocomplete and autofill
                          }}
                        />
                      )}
                    />
                  </>
                )}
                {isSmall ? null : <NavItem {...userMenu} />}
              </>
            )}
            <IconButton
              edge="start"
              className={classes.menuButton}
              aria-label="toggle mobile menu"
              color="inherit"
              onClick={() => setNavOpen((open) => !open)}
            >
              <MenuIcon />
            </IconButton>
          </Toolbar>
        </div>
      </AppBar>

      <Drawer
        anchor="right"
        open={navOpen}
        onClose={() => setNavOpen(false)}
        data-testid="mobilenav"
      >
        <List className={classes.mobileNav}>
          {navItemsToUse().map(({ title, path, nested, onClick }) => (
            <React.Fragment key={title.toString()}>
              {path && (
                <ListItem
                  button
                  exact
                  component={NavLink}
                  to={path}
                  activeClassName={classes.activeMobileLink}
                  onClick={onClick ? onClick : undefined}
                >
                  {title}
                </ListItem>
              )}
              {nested?.map((nested) => (
                <ListItem
                  button
                  exact
                  key={nested.title.toString()}
                  component={NavLink}
                  to={nested.onClick ? '#' : nested.path}
                  activeClassName={classes.activeMobileLink}
                  onClick={nested.onClick ? nested.onClick : undefined}
                >
                  {nested.title}
                </ListItem>
              ))}
            </React.Fragment>
          ))}
        </List>
      </Drawer>
    </div>
  );
}
Example #9
Source File: Vulnerability.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
Vulnerability: React.FC = () => {
  const { vulnerabilityId } = useParams();
  const { apiGet, apiPut } = useAuthContext();
  const [vulnerability, setVulnerability] = useState<VulnerabilityType>();
  const [comment, setComment] = useState<string>('');
  const [showCommentForm, setShowCommentForm] = useState<boolean>(false);
  const [menuAnchor, setMenuAnchor] = React.useState<null | HTMLElement>(null);
  const classes = useStyles();
  const history = useHistory();

  const formatDate = (date: string) => {
    return format(parseISO(date), 'MM-dd-yyyy');
  };

  const fetchVulnerability = useCallback(async () => {
    try {
      const result = await apiGet<VulnerabilityType>(
        `/vulnerabilities/${vulnerabilityId}`
      );
      setVulnerability(result);
    } catch (e) {
      console.error(e);
    }
  }, [vulnerabilityId, apiGet]);

  const updateVulnerability = async (body: { [key: string]: string }) => {
    try {
      if (!vulnerability) return;
      const res = await apiPut<VulnerabilityType>(
        '/vulnerabilities/' + vulnerability.id,
        {
          body: body
        }
      );
      setVulnerability({
        ...vulnerability,
        state: res.state,
        substate: res.substate,
        actions: res.actions
      });
    } catch (e) {
      console.error(e);
    }
  };

  useEffect(() => {
    fetchVulnerability();
  }, [fetchVulnerability]);

  if (!vulnerability) return <></>;

  const references = vulnerability.references.map((ref) => ref);
  if (vulnerability.cve)
    references.unshift({
      name: 'NIST National Vulnerability Database',
      url: `https://nvd.nist.gov/vuln/detail/${vulnerability.cve}`,
      source: '',
      tags: []
    });

  const states = [
    'unconfirmed',
    'exploitable',
    'false-positive',
    'accepted-risk',
    'remediated'
  ];
  interface dnstwist {
    'domain-name': string;
    fuzzer: string;
    'dns-a'?: string;
    'dns-aaas'?: string;
    'dns-mx'?: string;
    'dns-ns'?: string;
    'date-first-observed'?: string;
  }

  return (
    <>
      {/* <Alert severity="info">
        This vulnerability is found on 17 domains you have access to.
      </Alert> */}
      <div className={classes.root}>
        <p>
          <Link
            to="# "
            onClick={() => history.goBack()}
            className={classes.backLink}
          >
            <ChevronLeft
              style={{
                height: '100%',
                verticalAlign: 'middle',
                marginTop: '-2px'
              }}
            ></ChevronLeft>
            Go back
          </Link>
        </p>

        <div className={classes.contentWrapper}>
          <div className={classes.content}>
            <div
              className={classes.panel}
              style={{
                flex: '0 0 45%'
              }}
            >
              <Paper classes={{ root: classes.cardRoot }}>
                <div className={classes.title}>
                  <h4>{vulnerability.title}</h4>
                  <Button
                    aria-haspopup="true"
                    onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
                      setMenuAnchor(event.currentTarget)
                    }
                  >
                    <Flag
                      style={{
                        fontSize: '14px',
                        color: '#A9AEB1',
                        marginRight: '5px'
                      }}
                    ></Flag>
                    Mark Item <ArrowDropDown />
                  </Button>
                  <Menu
                    anchorEl={menuAnchor}
                    keepMounted
                    open={Boolean(menuAnchor)}
                    getContentAnchorEl={null}
                    onClose={() => setMenuAnchor(null)}
                    anchorOrigin={{
                      vertical: 'bottom',
                      horizontal: 'center'
                    }}
                    transformOrigin={{
                      vertical: 'top',
                      horizontal: 'center'
                    }}
                  >
                    {states.map((state) => (
                      <MenuItem
                        key={state}
                        onClick={() => {
                          updateVulnerability({
                            substate: state
                          });
                          setMenuAnchor(null);
                        }}
                        style={{ outline: 'none' }}
                      >
                        {stateMap[state]}
                      </MenuItem>
                    ))}
                  </Menu>
                </div>
                <Chip
                  style={{
                    marginLeft: '1.5rem'
                  }}
                  // icon={<Check></Check>}
                  label={`${vulnerability.state[0].toUpperCase()}${vulnerability.state.slice(
                    1
                  )} (${stateMap[vulnerability.substate]})`}
                  color={
                    vulnerability.state === 'open' ? 'secondary' : 'default'
                  }
                />
                <div className={classes.inner}>
                  <div className={classes.section}>
                    <h4 className={classes.subtitle}>Description</h4>
                    {vulnerability.description}
                  </div>
                  <div className={classes.section}>
                    <h4 className={classes.subtitle}>References</h4>
                    {references &&
                      references.map((ref, index) => (
                        <p key={index}>
                          <a
                            href={ref.url}
                            target="_blank"
                            rel="noopener noreferrer"
                          >
                            {ref.name ? ref.name : ref.url}
                          </a>
                          {ref.tags.length > 0
                            ? ' - ' + ref.tags.join(',')
                            : ''}
                        </p>
                      ))}
                  </div>
                  {vulnerability.source === 'hibp' && (
                    <div className={classes.section}>
                      <h4 className={classes.subtitle}>Data</h4>
                      <Table aria-label="simple table">
                        <TableHead>
                          <TableRow>
                            <TableCell>Exposed Emails</TableCell>
                            <TableCell align="right">Breaches</TableCell>
                          </TableRow>
                        </TableHead>
                        <TableBody>
                          {Object.keys(
                            vulnerability.structuredData['emails']
                          ).map((keyName, keyIndex) => (
                            <TableRow key={keyName}>
                              <TableCell component="th" scope="row">
                                {keyName}
                              </TableCell>
                              <TableCell align="right">
                                {vulnerability.structuredData['emails'][
                                  keyName
                                ].join(',  ')}
                              </TableCell>
                            </TableRow>
                          ))}
                        </TableBody>
                      </Table>
                    </div>
                  )}
                  {vulnerability.source === 'lookingGlass' && (
                    <div className={classes.section}>
                      <h4 className={classes.subtitle}>Data</h4>
                      <Table aria-label="simple table">
                        <TableHead>
                          <TableRow>
                            <TableCell>First Seen</TableCell>
                            <TableCell align="right">Last Seen</TableCell>
                            <TableCell align="right">Vuln Name</TableCell>
                            <TableCell align="right">Type</TableCell>
                          </TableRow>
                        </TableHead>
                        <TableBody>
                          {vulnerability.structuredData['lookingGlassData'].map(
                            (col: any) => (
                              <TableRow key={col.right_name}>
                                <TableCell component="th" scope="row">
                                  {formatDistanceToNow(
                                    parseISO(col.firstSeen)
                                  ) + ' ago'}
                                </TableCell>
                                <TableCell align="right">
                                  {formatDistanceToNow(parseISO(col.lastSeen)) +
                                    ' ago'}
                                </TableCell>
                                <TableCell align="right">
                                  {col.right_name}
                                </TableCell>
                                <TableCell align="right">
                                  {col.vulnOrMal}
                                </TableCell>
                              </TableRow>
                            )
                          )}
                        </TableBody>
                      </Table>
                    </div>
                  )}
                  {vulnerability.source === 'dnstwist' && (
                    <div className={classes.section}>
                      <h4 className={classes.subtitle}>Data</h4>
                      <TableContainer>
                        <Table aria-label="simple table">
                          <TableHead>
                            <TableRow>
                              <TableCell>Domain Name</TableCell>
                              <TableCell>IP Address / A Record</TableCell>
                              <TableCell>MX Record</TableCell>
                              <TableCell>NS Record</TableCell>
                              <TableCell>Date Observed</TableCell>
                              <TableCell>Fuzzer</TableCell>
                            </TableRow>
                          </TableHead>
                          <TableBody>
                            {vulnerability.structuredData['domains'].map(
                              (dom: dnstwist) => (
                                <TableRow key={dom['domain-name']}>
                                  <TableCell component="th" scope="row">
                                    {dom['domain-name']}
                                  </TableCell>
                                  <TableCell>{dom['dns-a']}</TableCell>
                                  <TableCell>{dom['dns-mx']}</TableCell>
                                  <TableCell>{dom['dns-ns']}</TableCell>
                                  <TableCell>
                                    {dom['date-first-observed']}
                                  </TableCell>
                                  <TableCell>{dom['fuzzer']}</TableCell>
                                </TableRow>
                              )
                            )}
                          </TableBody>
                        </Table>
                      </TableContainer>
                    </div>
                  )}
                </div>
              </Paper>
            </div>
            <div
              className={classes.panel}
              style={{
                flex: '0 0 30%'
              }}
            >
              <Paper className={classes.cardRoot}>
                <div className={classes.inner}>
                  <div className={classes.section}>
                    <h2 className={classes.subtitle}>Team notes</h2>
                    <button
                      onClick={() => {
                        setShowCommentForm(!showCommentForm);
                      }}
                      className={classes.linkSmall}
                    >
                      Add new note
                    </button>
                  </div>
                  {showCommentForm && (
                    <div>
                      <TextareaAutosize
                        style={{
                          width: '100%',
                          padding: 10,
                          marginBottom: '20px'
                        }}
                        rowsMin={4}
                        placeholder="Leave a Note"
                        onChange={(e) => setComment(e.target.value)}
                      />
                      <Button
                        onClick={() => {
                          updateVulnerability({
                            comment
                          });
                          setComment('');
                          setShowCommentForm(false);
                        }}
                        style={{
                          width: 150,
                          marginBottom: '20px'
                        }}
                        variant="contained"
                        color="secondary"
                      >
                        Save
                      </Button>
                    </div>
                  )}
                  {vulnerability.actions &&
                    vulnerability.actions
                      .filter((action) => action.type === 'comment')
                      .map((action, index) => (
                        <div className={classes.section} key={index}>
                          <h4
                            className={classes.subtitle}
                            style={{ fontSize: '16px', display: 'inline' }}
                          >
                            {action.userName}
                          </h4>
                          <span style={{ float: 'right', display: 'inline' }}>
                            {formatDistanceToNow(parseISO(action.date))} ago
                          </span>
                          <ReactMarkdown linkTarget="_blank">
                            {action.value || ''}
                          </ReactMarkdown>
                        </div>
                      ))}
                </div>
              </Paper>
              <Paper className={classes.cardRoot}>
                <div className={classes.inner}>
                  <div className={classes.section}>
                    <h2 className={classes.subtitle}>Vulnerability History</h2>
                  </div>
                  <Timeline
                    style={{
                      position: 'relative',
                      marginLeft: '-90%'
                    }}
                    align="left"
                  >
                    {vulnerability.actions &&
                      vulnerability.actions
                        .filter(
                          (action) =>
                            action.type === 'state-change' && action.substate
                        )
                        .map((action, index) => (
                          <TimelineItem key={index}>
                            <TimelineSeparator>
                              <TimelineDot />
                              <TimelineConnector />
                            </TimelineSeparator>{' '}
                            <TimelineContent>
                              State {action.automatic ? 'automatically ' : ''}
                              changed to {action.state} (
                              {stateMap[action.substate!].toLowerCase()})
                              {action.userName ? ' by ' + action.userName : ''}{' '}
                              <br></br>
                              <span
                                style={{
                                  color: '#A9AEB1'
                                }}
                              >
                                {formatDate(action.date)}
                              </span>
                            </TimelineContent>
                          </TimelineItem>
                        ))}

                    <TimelineItem>
                      <TimelineSeparator>
                        <TimelineDot />
                      </TimelineSeparator>
                      <TimelineContent>
                        Vulnerability opened<br></br>
                        <span
                          style={{
                            color: '#A9AEB1'
                          }}
                        >
                          {formatDate(vulnerability.createdAt)}
                        </span>
                      </TimelineContent>
                    </TimelineItem>
                  </Timeline>
                </div>
              </Paper>
              <Paper className={classes.cardRoot}>
                <div className={classes.inner}>
                  <div className={classes.section}>
                    <h2 className={classes.subtitle}>Provenance</h2>
                    <p>
                      <strong>Root Domain: </strong>
                      {vulnerability.domain.fromRootDomain}
                    </p>
                    <p>
                      <strong>Subdomain: </strong>
                      {vulnerability.domain.name} (
                      {vulnerability.domain.subdomainSource})
                    </p>
                    {vulnerability.service && (
                      <p>
                        <strong>Service/Port: </strong>
                        {vulnerability.service.service
                          ? vulnerability.service.service
                          : vulnerability.service.port}{' '}
                        ({vulnerability.service.serviceSource})
                      </p>
                    )}
                    {vulnerability.cpe && (
                      <>
                        <p>
                          <strong>Product: </strong>
                          {vulnerability.cpe}
                        </p>
                      </>
                    )}
                    <p>
                      <strong>Vulnerability: </strong>
                      {vulnerability.title} ({vulnerability.source})
                    </p>
                  </div>
                </div>
              </Paper>
              {vulnerability.source === 'hibp' && (
                <Paper className={classes.cardRoot}>
                  <div className={classes.inner}>
                    <div className={classes.section}>
                      <h2 className={classes.subtitle}>Breaches</h2>
                      <Table aria-label="simple table">
                        <TableHead>
                          <TableRow>
                            <TableCell>Breach Name</TableCell>
                            <TableCell align="right">Date Added</TableCell>
                          </TableRow>
                        </TableHead>
                        <TableBody>
                          {Object.keys(vulnerability.structuredData['breaches'])
                            .sort(
                              (a, b) =>
                                parseISO(
                                  vulnerability.structuredData['breaches'][b][
                                    'AddedDate'
                                  ]
                                ).getTime() -
                                parseISO(
                                  vulnerability.structuredData['breaches'][a][
                                    'AddedDate'
                                  ]
                                ).getTime()
                            )
                            .map((keyName, keyIndex) => (
                              <TableRow key={keyName}>
                                <TableCell component="th" scope="row">
                                  {keyName}
                                </TableCell>
                                <TableCell align="right">
                                  {formatDistanceToNow(
                                    parseISO(
                                      vulnerability.structuredData['breaches'][
                                        keyName
                                      ]['AddedDate']
                                    )
                                  ) + ' ago'}
                                </TableCell>
                              </TableRow>
                            ))}
                        </TableBody>
                      </Table>
                    </div>
                  </div>
                </Paper>
              )}
            </div>
          </div>
        </div>
      </div>
    </>
  );
}