react-virtualized#List TypeScript Examples

The following examples show how to use react-virtualized#List. 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: VirtualizedList.tsx    From symphony-ui-toolkit with Apache License 2.0 6 votes vote down vote up
VirtualizedList: React.FC<ListProps> = ({
  width,
  height,
  rowCount,
  rowHeight,
  rowRenderer,
  ...rest
}: ListProps) => {
  return (
    <List
      rowCount={rowCount}
      rowHeight={rowHeight}
      width={width}
      height={height}
      rowRenderer={rowRenderer}
      {...rest}
    />
  );
}
Example #2
Source File: list.tsx    From gio-design with Apache License 2.0 6 votes vote down vote up
private renderList = () => {
    const { height, disabledOptions, rowHeight, options, value, prefixCls } = this.props;
    const getRowHeight = ({ index }: { index: number }) => {
      if (typeof rowHeight === 'function') {
        return rowHeight(options[index]);
      }
      return rowHeight;
    };
    return (
      <AutoSizer style={{ width: '100%', height: '100%' }}>
        {({ width }) => (
          <List
            value={value}
            width={width}
            height={2000}
            style={{ height: height || '100%', overflow: 'auto', marginBottom: '-4px' }}
            rowCount={options.length}
            rowHeight={typeof rowHeight === 'function' ? getRowHeight : this._cache.rowHeight}
            deferredMeasurementCache={this._cache}
            rowRenderer={this.renderListItem(options)}
            disabledOptions={disabledOptions}
            className={`${prefixCls}-list`}
          />
        )}
      </AutoSizer>
    );
  };
Example #3
Source File: LogViewer.tsx    From kfp-tekton-backend with Apache License 2.0 6 votes vote down vote up
public render(): JSX.Element {
    return (
      <AutoSizer>
        {({ height, width }) => (
          <List
            id='logViewer'
            containerStyle={listContainerStyleOverride}
            width={width}
            height={height}
            rowCount={this.props.logLines.length}
            rowHeight={15}
            className={css.root}
            ref={this._rootRef}
            overscanIndicesGetter={overscanOnBothDirections}
            overscanRowCount={
              400 /* make this large, so selecting maximum 400 lines is supported */
            }
            rowRenderer={this._rowRenderer.bind(this)}
            onScroll={this.handleScroll}
          />
        )}
      </AutoSizer>
    );
  }
Example #4
Source File: LogViewer.tsx    From kfp-tekton-backend with Apache License 2.0 5 votes vote down vote up
private _rootRef = React.createRef<List>();
Example #5
Source File: IconList.tsx    From iconsax-react with MIT License 5 votes vote down vote up
IconList = () => {
  const [filtered, setFiltered] = useState<IIconsArray[]>(icons)
  const [numColumns, setNumColumns] = useState(1)

  const query = searchStore((state) => state.query)

  const onResize = ({ width }: { width: number }) => {
    if (width <= 576) {
      setNumColumns(Math.floor(width / ICON_CONTAINER_SIZE))
    } else {
      setNumColumns(Math.floor(width / ICON_CONTAINER_SIZE))
    }
  }
  useEffect(() => {
    console.log(query)
    const f =
      icons.filter((x) =>
        x.name.toLowerCase().includes(query!.toLowerCase())
      ) || []
    setFiltered(f)
  }, [query])
  return (
    <div className="container flex justify-center m-auto min-h-[400px]">
      <div className="w-full relative mb-10">
        {filtered.length > 0 ? (
          <WindowScroller>
            {({ height, isScrolling, onChildScroll, scrollTop }) => (
              <AutoSizer disableHeight onResize={onResize}>
                {({ width }) => (
                  <List
                    tabIndex={-1}
                    autoHeight
                    width={width}
                    height={height}
                    isScrolling={isScrolling}
                    onScroll={onChildScroll}
                    scrollTop={scrollTop}
                    rowCount={Math.ceil(filtered.length / numColumns)}
                    rowHeight={ICON_CONTAINER_SIZE + 10}
                    rowRenderer={({ key, index: rowIndex, style }) => (
                      <div
                        key={key}
                        className="grid place-items-center"
                        style={{
                          ...style,
                          gridTemplateColumns: `repeat(${numColumns}, 1fr)`,
                        }}
                      >
                        {Array.from(
                          { length: numColumns },
                          (_, columnIndex) => {
                            const icon =
                              filtered[rowIndex * numColumns + columnIndex]
                            if (!icon) {
                              return null
                            }
                            return (
                              <IconItem name={icon.name} Icon={icon.Icon} />
                            )
                          }
                        )}
                      </div>
                    )}
                  />
                )}
              </AutoSizer>
            )}
          </WindowScroller>
        ) : (
          <Empty />
        )}
      </div>
    </div>
  )
}
Example #6
Source File: SearchResults.tsx    From yana with MIT License 5 votes vote down vote up
SearchResults: React.FC<{
  onClickItem?: (item: DataItem) => void;
  hideItemIds: string[];
  hiddenSearch: SearchQuery;
}> = props => {
  const search = useSearchBar();
  const searchResult = useDataSearch({ ...search.searchQuery, ...props.hiddenSearch }, 30);

  const items = searchResult.items.filter(i => !props.hideItemIds.includes(i.id));

  return (
    <AutoSizer>
      {({ width, height }) => {
        return (
          <List
            rowCount={items.length + (searchResult.nextPageAvailable ? 50 : 0)}
            rowHeight={OVERLAY_SEARCH_ITEM_HEIGHT}
            width={width}
            height={height}
            containerStyle={{ paddingRight: '10px' }}
            rowRenderer={cellProps => {
              const itemId = cellProps.index;

              if (itemId >= items.length) {
                if (searchResult.nextPageAvailable) {
                  searchResult.fetchNextPage();
                  return (
                    <ResultItem
                      key={cellProps.key}
                      containerStyle={cellProps.style}
                      icon={<Spinner size={16} />}
                      title="Loading..."
                      meta=""
                    />
                  );
                } else {
                  return null;
                }
              }

              const item = items[itemId];

              return (
                <ResultItem
                  key={cellProps.key}
                  containerStyle={cellProps.style}
                  icon={(item.icon || (item.kind === DataItemKind.Collection ? 'folder-open' : 'document')) as any}
                  title={item.name}
                  meta={ago(new Date(item.lastChange))}
                  onClick={() => props.onClickItem?.(item)}
                  dataItem={item}
                />
              );
            }}
          />
        );
      }}
    </AutoSizer>
  );
}
Example #7
Source File: BatchList.tsx    From lightning-terminal with MIT License 5 votes vote down vote up
BatchList: React.FC = () => {
  const { batchStore } = useStore();

  const { Wrapper, Content, More } = Styled;
  return (
    <Wrapper>
      <BatchRowHeader />
      <Content>
        <AutoSizer>
          {({ width, height }) => (
            <Observer>
              {() => (
                <List
                  rowCount={batchStore.sortedBatches.length}
                  rowHeight={ROW_HEIGHT}
                  rowRenderer={({ index, key, style }) => (
                    <BatchRow
                      key={key}
                      style={style}
                      batch={batchStore.sortedBatches[index]}
                    />
                  )}
                  width={width}
                  height={height}
                />
              )}
            </Observer>
          )}
        </AutoSizer>
      </Content>
      {batchStore.hasMoreBatches && (
        <More>
          {batchStore.loading ? (
            <LoaderLines />
          ) : (
            <Button
              borderless
              ghost
              onClick={batchStore.fetchBatches}
              disabled={batchStore.loading}
            >
              Load Older Batches
            </Button>
          )}
        </More>
      )}
    </Wrapper>
  );
}
Example #8
Source File: ChannelList.tsx    From lightning-terminal with MIT License 5 votes vote down vote up
ChannelList: React.FC = () => {
  const { buildSwapView } = useStore();

  const { Wrapper, ListContainer } = Styled;
  return (
    <Wrapper data-tour="channel-list">
      <ChannelRowHeader />
      <ListContainer>
        <AutoSizer disableHeight>
          {({ width }) => (
            <WindowScroller>
              {({ height, isScrolling, onChildScroll, scrollTop, registerChild }) => (
                <Observer>
                  {() => (
                    <div ref={ref => ref && registerChild(ref)}>
                      <List
                        autoHeight
                        height={height}
                        isScrolling={isScrolling}
                        onScroll={onChildScroll}
                        rowCount={buildSwapView.channels.length}
                        rowHeight={ROW_HEIGHT}
                        rowRenderer={({ index, key, style }) => (
                          <ChannelRow
                            key={key}
                            style={style}
                            channel={buildSwapView.channels[index]}
                          />
                        )}
                        scrollTop={scrollTop}
                        width={width}
                      />
                    </div>
                  )}
                </Observer>
              )}
            </WindowScroller>
          )}
        </AutoSizer>
      </ListContainer>
    </Wrapper>
  );
}
Example #9
Source File: HistoryList.tsx    From lightning-terminal with MIT License 5 votes vote down vote up
HistoryList: React.FC = () => {
  const { swapStore } = useStore();

  const { Wrapper } = Styled;
  return (
    <Wrapper>
      <HistoryRowHeader />
      <ListContainer>
        <AutoSizer disableHeight>
          {({ width }) => (
            <WindowScroller>
              {({ height, isScrolling, onChildScroll, scrollTop, registerChild }) => (
                <Observer>
                  {() => (
                    <div ref={ref => ref && registerChild(ref)}>
                      <List
                        autoHeight
                        height={height}
                        isScrolling={isScrolling}
                        onScroll={onChildScroll}
                        rowCount={swapStore.sortedSwaps.length}
                        rowHeight={ROW_HEIGHT}
                        rowRenderer={({ index, key, style }) => (
                          <HistoryRow
                            key={key}
                            style={style}
                            swap={swapStore.sortedSwaps[index]}
                          />
                        )}
                        scrollTop={scrollTop}
                        width={width}
                      />
                    </div>
                  )}
                </Observer>
              )}
            </WindowScroller>
          )}
        </AutoSizer>
      </ListContainer>
    </Wrapper>
  );
}
Example #10
Source File: SessionList.tsx    From lightning-terminal with MIT License 5 votes vote down vote up
SessionList: React.FC = () => {
  const { sessionStore } = useStore();

  if (sessionStore.sortedSessions.length === 0) return null;

  const { Wrapper } = Styled;
  return (
    <Wrapper>
      <SessionRowHeader />
      <ListContainer>
        <AutoSizer disableHeight>
          {({ width }) => (
            <WindowScroller>
              {({ height, isScrolling, onChildScroll, scrollTop, registerChild }) => (
                <Observer>
                  {() => (
                    <div ref={ref => ref && registerChild(ref)}>
                      <List
                        autoHeight
                        height={height}
                        isScrolling={isScrolling}
                        onScroll={onChildScroll}
                        rowCount={sessionStore.sortedSessions.length}
                        rowHeight={ROW_HEIGHT}
                        rowRenderer={({ index, key, style }) => (
                          <SessionRow
                            key={key}
                            style={style}
                            session={sessionStore.sortedSessions[index]}
                          />
                        )}
                        scrollTop={scrollTop}
                        width={width}
                      />
                    </div>
                  )}
                </Observer>
              )}
            </WindowScroller>
          )}
        </AutoSizer>
      </ListContainer>
    </Wrapper>
  );
}
Example #11
Source File: TransactionInfiniteList.tsx    From End-to-End-Web-Testing-with-Cypress with MIT License 5 votes vote down vote up
TransactionInfiniteList: React.FC<TransactionListProps> = ({
  transactions,
  loadNextPage,
  pagination,
}) => {
  const classes = useStyles();
  const theme = useTheme();
  const isXsBreakpoint = useMediaQuery(theme.breakpoints.down("xs"));
  const isMobile = useMediaQuery(theme.breakpoints.down("sm"));

  const itemCount = pagination.hasNextPages ? transactions.length + 1 : transactions.length;

  const loadMoreItems = () => {
    return new Promise((resolve) => {
      return resolve(pagination.hasNextPages && loadNextPage(pagination.page + 1));
    });
  };

  const isRowLoaded = (params: Index) =>
    !pagination.hasNextPages || params.index < transactions.length;

  // @ts-ignore
  function rowRenderer({ key, index, style }) {
    const transaction = get(index, transactions);

    if (index < transactions.length) {
      return (
        <div key={key} style={style}>
          <TransactionItem transaction={transaction} />
          <Divider variant={isMobile ? "fullWidth" : "inset"} />
        </div>
      );
    }
  }

  return (
    <InfiniteLoader
      isRowLoaded={isRowLoaded}
      loadMoreRows={loadMoreItems}
      rowCount={itemCount}
      threshold={2}
    >
      {({ onRowsRendered, registerChild }) => (
        <div data-test="transaction-list" className={classes.transactionList}>
          <List
            rowCount={itemCount}
            ref={registerChild}
            onRowsRendered={onRowsRendered}
            height={isXsBreakpoint ? theme.spacing(74) : theme.spacing(88)}
            width={isXsBreakpoint ? theme.spacing(38) : theme.spacing(110)}
            rowHeight={isXsBreakpoint ? theme.spacing(28) : theme.spacing(16)}
            rowRenderer={rowRenderer}
          />
        </div>
      )}
    </InfiniteLoader>
  );
}
Example #12
Source File: index.tsx    From metaflow-ui with Apache License 2.0 4 votes vote down vote up
LogList: React.FC<LogProps> = ({ logdata, fixedHeight, onScroll, downloadUrl, setFullscreen }) => {
  const { timezone } = useContext(TimezoneContext);
  const { t } = useTranslation();
  const rows = logdata.logs;
  const [stickBottom, setStickBottom] = useState(true);
  const [cache] = useState(
    new CellMeasurerCache({
      fixedWidth: true,
      minHeight: 25,
    }),
  );
  const _list = useRef<List>(null);
  const search = logdata.localSearch;
  const count = rows.length;

  const okCount = rows.reduce((okAmount, item) => {
    return typeof item === 'object' ? okAmount + 1 : okAmount;
  }, 0);

  const totalHeight = rows.reduce((val, _item, index) => {
    return val + (cache.getHeight(index, 0) || 0);
  }, 0);

  // Clear cached row heights on window resize events
  useEffect(() => {
    const listener = () => {
      cache.clearAll();
    };
    window.addEventListener('resize', listener);
    return () => window.removeEventListener('resize', listener);
  }, []); // eslint-disable-line

  // Force row height calculations after fetch ok

  useEffect(() => {
    if (_list?.current) {
      cache.clearAll();
      _list.current.recomputeRowHeights();
    }
  }, [okCount]); // eslint-disable-line

  useEffect(() => {
    if (stickBottom && _list) {
      _list.current?.scrollToRow(count);
    }
  }, [count, okCount, stickBottom]);

  //
  // Index tracking
  //

  const [scrollIndex, setIndex] = useState(0);
  const [debouncedIndex] = useDebounce(scrollIndex, 300);
  useEffect(() => {
    if (onScroll && !stickBottom) {
      onScroll(debouncedIndex);
    }
  }, [debouncedIndex, onScroll]); // eslint-disable-line

  //
  // Search features
  //

  const searchActive = search.result.active;
  const searchCurrent = search.result.current;
  const searchQuery = search.result.query;

  useEffect(() => {
    if (searchActive && search.result.result[searchCurrent]) {
      _list.current?.scrollToRow(search.result.result[searchCurrent].line);
    }
  }, [searchActive, searchCurrent, searchQuery]); //eslint-disable-line

  return (
    <div style={{ flex: '1 1 0' }} data-testid="loglist-wrapper">
      <LogActionBar
        data={logdata.logs}
        downloadlink={downloadUrl}
        setFullscreen={setFullscreen}
        search={logdata.localSearch}
        spaceAround={!!fixedHeight}
      />

      {rows.length === 0 && ['Ok', 'Error'].includes(logdata.preloadStatus) && logdata.status === 'NotAsked' && (
        <div data-testid="loglist-preload-empty">{t('task.no-preload-logs')}</div>
      )}

      {rows.length === 0 && logdata.status === 'Ok' && <div data-testid="loglist-empty">{t('task.no-logs')}</div>}

      {rows.length > 0 && (
        <LogListContainer data-testid="loglist-container">
          <AutoSizer disableHeight>
            {({ width }) => (
              <List
                ref={_list}
                overscanRowCount={5}
                rowCount={rows.length}
                rowHeight={cache.rowHeight}
                onRowsRendered={(data) => {
                  if (onScroll) {
                    setIndex(data.overscanStartIndex);
                  }
                }}
                deferredMeasurementCache={cache}
                onScroll={(args: { scrollTop: number; clientHeight: number; scrollHeight: number }) => {
                  if (args.scrollTop + args.clientHeight >= args.scrollHeight) {
                    setStickBottom(true);
                  } else if (stickBottom) {
                    setStickBottom(false);
                  }
                }}
                rowRenderer={({ index, style, key, parent }) => (
                  <CellMeasurer cache={cache} columnIndex={0} key={key} rowIndex={index} parent={parent}>
                    {() => {
                      const item = rows[index];

                      return (
                        <LogLine style={style} data-testid="log-line">
                          <LogLineNumber className="logline-number">{index}</LogLineNumber>
                          {getTimestamp(item, timezone)}
                          <LogLineText>
                            {typeof item === 'object' ? getLineText(item as Log, search.result) : 'Loading...'}
                          </LogLineText>
                        </LogLine>
                      );
                    }}
                  </CellMeasurer>
                )}
                height={fixedHeight ? fixedHeight - 16 : totalHeight < LIST_MAX_HEIGHT ? totalHeight : LIST_MAX_HEIGHT}
                width={width}
              />
            )}
          </AutoSizer>

          {!stickBottom && (
            <ScrollToBottomButton onClick={() => setStickBottom(true)} data-testid="loglist-stick-bottom">
              {t('run.scroll-to-bottom')}
            </ScrollToBottomButton>
          )}

          <PollLoader status={logdata.status} preloadStatus={logdata.preloadStatus} />
        </LogListContainer>
      )}
    </div>
  );
}
Example #13
Source File: index.tsx    From yasd with MIT License 4 votes vote down vote up
Page: React.FC = () => {
  const { setModal } = useModal()
  const { t } = useTranslation()
  const profile = useProfile()
  const [isAutoRefresh, setIsAutoRefresh] = useState<boolean>(true)
  const [group, setGroup] = useState<'recent' | 'active'>('recent')
  const { data: recentRequestsResponse, error: requestsError } =
    useSWR<RecentRequests>(() => '/requests/' + group, fetcher, {
      revalidateOnFocus: false,
      revalidateOnReconnect: false,
      dedupingInterval: 1000,
      refreshInterval: isAutoRefresh
        ? profile?.platform === 'macos'
          ? 2000
          : 4000
        : 0,
    })
  const [requestList, setRequestList] = useState<Array<RequestItem>>([])
  const [activeRequestList, setActiveRequestList] = useState<
    Array<RequestItem>
  >([])
  const currentList = useMemo(
    () => (group === 'recent' ? requestList : activeRequestList),
    [group, requestList, activeRequestList],
  )
  const query = useQuery()
  const sourceIp = useMemo<string | null>(() => query.get('source'), [query])

  useEffect(
    () => {
      if (!recentRequestsResponse?.requests) return

      const pendingList = [...recentRequestsResponse.requests]
      const now = new Date()
      let newList = [...currentList]

      while (pendingList.length) {
        const request = pendingList.pop() as RequestItem
        const existingIndex = newList.findIndex(
          (item) => item.id === request.id,
        )

        if (existingIndex >= 0) {
          Object.assign(newList[existingIndex], {
            ...omit(request, ['id']),
            lastUpdated: now,
          })
        } else {
          if (newList.length && request.id > newList[0].id) {
            newList.unshift({
              ...request,
              lastUpdated: now,
            })
          } else {
            newList.push({
              ...request,
              lastUpdated: now,
            })
          }
        }
      }

      if (group === 'recent') {
        newList = newList
          .filter((request) => {
            if (sourceIp) {
              return sourceIp === request.sourceAddress
            }
            return true
          })
          .slice(0, LIST_ITEMS_MAX)
      } else {
        newList = newList
          .filter((request) => {
            if (sourceIp) {
              return (
                request.lastUpdated === now &&
                sourceIp === request.sourceAddress
              )
            }
            return request.lastUpdated === now
          })
          .sort((a, b) => b.id - a.id)
      }

      if (group === 'recent') {
        setRequestList(newList)
        setActiveRequestList([])
      } else {
        setRequestList([])
        setActiveRequestList(newList)
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [recentRequestsResponse, group, sourceIp],
  )

  const openRequestDetail = useCallback(
    (req: RequestItem) => {
      setModal({
        children({ onClose }) {
          return onClose ? (
            <RequestModal req={req} onClose={onClose} />
          ) : (
            <React.Fragment />
          )
        },
        onClose() {
          // noop
        },
      })
    },
    [setModal],
  )

  const rowRenderer: ListRowRenderer = useCallback(
    ({
      key, // Unique key within array of rows
      index, // Index of row within collection
      isScrolling, // The List is currently being scrolled
      isVisible, // This row is visible within the List (eg it is not an overscanned row)
      style, // Style object to be applied to row (to position it)
    }) => {
      const req = currentList[index]

      return (
        <div
          key={key}
          style={style}
          onClick={() => openRequestDetail(req)}
          tw="flex flex-col justify-center py-2 cursor-pointer hover:bg-gray-100"
          css={css`
            padding-left: calc(env(safe-area-inset-left) + 0.75rem);
            padding-right: calc(env(safe-area-inset-right) + 0.75rem);
          `}
        >
          <ListItem req={req} />
        </div>
      )
    },
    [currentList, openRequestDetail],
  )

  return (
    <FixedFullscreenContainer>
      <React.Fragment>
        <PageTitle
          title={t('home.requests')}
          hasAutoRefresh={true}
          defaultAutoRefreshState={true}
          onAuthRefreshStateChange={(newState) => setIsAutoRefresh(newState)}
        />

        <div tw="flex-1">
          {recentRequestsResponse ? (
            currentList.length ? (
              <AutoSizer>
                {({ width, height }) => {
                  return (
                    <List
                      width={width}
                      height={height}
                      rowCount={currentList.length}
                      rowHeight={64}
                      rowRenderer={rowRenderer}
                      style={{
                        outline: 'none',
                      }}
                      css={css`
                        & > div {
                          ${tw`divide-y divide-gray-200`}
                        }
                      `}
                    />
                  )
                }}
              </AutoSizer>
            ) : (
              <div tw="h-full flex items-center justify-center text-base text-gray-500">
                {t('common.no_data')}
              </div>
            )
          ) : (
            <div tw="h-full flex items-center justify-center text-base text-gray-500">
              {t('common.is_loading')}...
            </div>
          )}
        </div>

        <div
          css={[
            tw`flex divide-x divide-gray-200 border-t border-solid border-gray-200 py-2 px-2`,
            css`
              & > div {
                ${tw`mx-2`}
              }
              & > div:first-of-type {
                margin-left: 0;
              }
            `,
          ]}
        >
          <SelectorGroup
            css={[
              tw`flex justify-center items-center`,
              css`
                & label {
                  ${tw`py-2 px-4 ml-2 my-1 text-sm`}
                }
                & label:first-of-type {
                  margin-left: 0;
                }
              `,
            ]}
            label="choose the dns result group"
            name="selector-group"
            onChange={(event: ChangeEvent<HTMLInputElement>) => {
              setGroup(() => {
                const newState = event.target.value as 'recent' | 'active'
                mutate('/requests/' + newState)
                return newState
              })
            }}
            options={[
              {
                children: t('requests.recent'),
                value: 'recent',
              },
              {
                children: t('requests.active'),
                value: 'active',
              },
            ]}
            value={group}
          />
        </div>
      </React.Fragment>
    </FixedFullscreenContainer>
  )
}
Example #14
Source File: index.tsx    From yasd with MIT License 4 votes vote down vote up
Page: React.FC = () => {
  const { t } = useTranslation()
  const [group, setGroup] = useState<'dynamic' | 'static'>('dynamic')
  const { data: dnsResult, error: dnsResultError } = useSWR<DnsResult>(
    '/dns',
    fetcher,
  )
  const list = useMemo(() => {
    if (group === 'dynamic') {
      return dnsResult?.dnsCache ?? []
    }
    return dnsResult?.local ?? []
  }, [dnsResult, group])

  const flushDns: MouseEventHandler = () => {
    fetcher({
      url: '/dns/flush',
      method: 'POST',
    })
      .then(() => {
        toast.success(t('common.success_interaction'))
        return mutate('/dns')
      })
      .catch((err) => {
        toast.error(t('common.failed_interaction'))
        console.error(err)
      })
  }

  const openIpDetail = (ip: string) => {
    window.open(`https://ip.sb/ip/${ip}`, '_blank', 'noopener noreferrer')
  }

  const rowRenderer: ListRowRenderer = useCallback(
    ({
      key, // Unique key within array of rows
      index, // Index of row within collection
      isScrolling, // The List is currently being scrolled
      isVisible, // This row is visible within the List (eg it is not an overscanned row)
      style, // Style object to be applied to row (to position it)
    }) => {
      if (group === 'dynamic') {
        const record = (list as DnsResult['dnsCache'])[index]

        return (
          <div
            key={key}
            style={style}
            onClick={() => openIpDetail(record.data[0])}
            css={[
              tw`flex flex-col justify-center py-2`,
              tw`cursor-pointer hover:bg-gray-100`,
              css`
                padding-left: calc(env(safe-area-inset-left) + 0.75rem);
                padding-right: calc(env(safe-area-inset-right) + 0.75rem);
              `,
            ]}
          >
            <div tw="text-sm truncate">{record.domain}</div>
            <div tw="text-xs text-gray-700 leading-tight">
              DNS: {record.server}
            </div>
            <div tw="text-xs text-gray-700 leading-tight truncate">
              {t('dns.result')}: {record.data.join(', ')}
            </div>
            <div tw="text-xs text-gray-700 leading-tight truncate">
              {t('dns.path')}: {record.path}
            </div>
          </div>
        )
      } else {
        const record = (list as DnsResult['local'])[index]

        return (
          <div
            key={key}
            style={style}
            css={[
              tw`flex flex-col justify-center py-2`,
              css`
                padding-left: calc(env(safe-area-inset-left) + 0.75rem);
                padding-right: calc(env(safe-area-inset-right) + 0.75rem);
              `,
            ]}
          >
            <div tw="text-sm truncate">{record.domain}</div>
            {!!record.server && (
              <div tw="text-xs text-gray-700 leading-tight">
                DNS: {record.server}
              </div>
            )}
            <div tw="text-xs text-gray-700 leading-tight">
              {t('dns.result')}: {record.data ?? 'N/A'}
            </div>
            <div tw="text-xs text-gray-700 leading-tight">
              {t('dns.source')}: {record.source ?? 'N/A'}
            </div>
            {!!record.comment && (
              <div tw="text-xs text-gray-700 leading-tight">
                {t('dns.comment')}: {record.comment}
              </div>
            )}
          </div>
        )
      }
    },
    [group, list],
  )

  return (
    <FixedFullscreenContainer>
      <PageTitle title="DNS" />

      <div tw="flex-1">
        <AutoSizer>
          {({ width, height }) => {
            return (
              <List
                width={width}
                height={height}
                rowCount={list.length}
                rowHeight={85}
                rowRenderer={rowRenderer}
                style={{
                  outline: 'none',
                }}
                css={css`
                  & > div {
                    ${tw`divide-y divide-gray-200`}
                  }
                `}
              />
            )
          }}
        </AutoSizer>
      </div>

      <div
        css={[
          tw`flex divide-x divide-gray-200 border-t border-solid border-gray-200 py-2 px-2`,
          css`
            & > div {
              ${tw`mx-2`}
            }
            & > div:first-of-type {
              margin-left: 0;
            }
          `,
        ]}
      >
        <SelectorGroup
          css={[
            tw`flex justify-center items-center`,
            css`
              & label {
                ${tw`py-2 px-4 ml-2 my-1 text-sm`}
              }
              & label:first-of-type {
                margin-left: 0;
              }
            `,
          ]}
          label="choose the dns result group"
          name="selector-group"
          onChange={(event: ChangeEvent<HTMLInputElement>) => {
            setGroup(event.target.value as 'dynamic' | 'static')
          }}
          options={[
            {
              children: t('dns.dynamic'),
              value: 'dynamic',
            },
            {
              children: t('dns.static'),
              value: 'static',
            },
          ]}
          value={group}
        />

        <div tw="flex items-center">
          <Button
            tw="font-normal"
            variant="tertiary"
            size="kilo"
            onClick={flushDns}
          >
            {t('dns.flush_dns')}
          </Button>
        </div>
      </div>
    </FixedFullscreenContainer>
  )
}
Example #15
Source File: TaskList.tsx    From metaflow-ui with Apache License 2.0 4 votes vote down vote up
TaskList: React.FC<Props> = ({
  rows,
  rowDataDispatch,
  taskStatus,
  activeTaskId,
  results,
  grouped,
  paramsString,
}) => {
  const [viewScrollTop, setScrollTop] = useState(0);
  const ref = useRef<HTMLDivElement>(null);
  const { t } = useTranslation();

  useEffect(() => {
    const listener = () => {
      setScrollTop(window.scrollY);
    };

    window.addEventListener('scroll', listener);
    return () => window.removeEventListener('scroll', listener);
  }, []);

  const listSize = ref?.current
    ? window.innerHeight -
      (viewScrollTop + 25 > ref.current.offsetTop ? HEADER_SIZE_PX + 25 : ref.current.offsetTop - viewScrollTop + 25)
    : 0;

  return (
    <TaskListContainer ref={ref}>
      <FixedList style={{ position: 'sticky', top: HEADER_SIZE_PX + 'px' }}>
        {rows.length > 0 && (
          <List
            overscanRowCount={5}
            rowCount={rows.length}
            rowHeight={toRelativeSize(28)}
            rowRenderer={({ index, style }) => {
              const item = rows[index];

              return (
                <TaskListRow
                  key={index}
                  index={index}
                  style={style}
                  item={item}
                  grouped={grouped}
                  paramsString={paramsString}
                  duration={
                    item.type === 'step'
                      ? getStepDuration(item.data, item.rowObject.status, item.rowObject.duration)
                      : item.data[item.data.length - 1].duration || null
                  }
                  toggle={
                    item.type === 'step'
                      ? () => (item.data ? rowDataDispatch({ type: 'toggle', id: item.data.step_name }) : null)
                      : undefined
                  }
                  active={item.type === 'task' && getTaskId(item.data[0]) === activeTaskId}
                  isOpen={item.type === 'step' && item.rowObject.isOpen}
                />
              );
            }}
            height={listSize}
            width={toRelativeSize(245)}
          />
        )}

        {/* Search ok, no results */}
        {rows.length === 0 && results.status === 'Ok' && (
          <div style={{ padding: '1rem 0' }}>{t('search.no-results')}</div>
        )}
        {/* Not searched, no more loading, no results -> Not tasks message */}
        {rows.length === 0 && results.status === 'NotAsked' && taskStatus !== 'Loading' && (
          <div style={{ padding: '1rem 0' }}>{t('search.no-tasks')}</div>
        )}
        {/* No rows, still loading more */}
        {rows.length === 0 && taskStatus === 'Loading' && (
          <div style={{ padding: '1rem 0', display: 'flex', justifyContent: 'center' }}>
            <Spinner sm />
          </div>
        )}
      </FixedList>
    </TaskListContainer>
  );
}
Example #16
Source File: VersionsNavigator.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
VersionsNavigator = ({
  classes,
  internalPaths,
  bucketName,
}: IVersionsNavigatorProps) => {
  const dispatch = useDispatch();

  const searchVersions = useSelector(
    (state: AppState) => state.objectBrowser.searchVersions
  );
  const loadingVersions = useSelector(
    (state: AppState) => state.objectBrowser.loadingVersions
  );
  const selectedVersion = useSelector(
    (state: AppState) => state.objectBrowser.selectedVersion
  );

  const distributedSetup = useSelector(selDistSet);
  const [shareFileModalOpen, setShareFileModalOpen] = useState<boolean>(false);
  const [actualInfo, setActualInfo] = useState<IFileInfo | null>(null);
  const [objectToShare, setObjectToShare] = useState<IFileInfo | null>(null);
  const [versions, setVersions] = useState<IFileInfo[]>([]);
  const [restoreVersionOpen, setRestoreVersionOpen] = useState<boolean>(false);
  const [restoreVersion, setRestoreVersion] = useState<string>("");
  const [sortValue, setSortValue] = useState<string>("date");
  const [previewOpen, setPreviewOpen] = useState<boolean>(false);
  const [deleteNonCurrentOpen, setDeleteNonCurrentOpen] =
    useState<boolean>(false);
  const [selectEnabled, setSelectEnabled] = useState<boolean>(false);
  const [selectedItems, setSelectedItems] = useState<string[]>([]);
  const [delSelectedVOpen, setDelSelectedVOpen] = useState<boolean>(false);

  // calculate object name to display
  let objectNameArray: string[] = [];
  if (actualInfo) {
    objectNameArray = actualInfo.name.split("/");
  }

  useEffect(() => {
    if (!loadingVersions && !actualInfo) {
      dispatch(setLoadingVersions(true));
    }
  }, [loadingVersions, actualInfo, dispatch]);

  useEffect(() => {
    if (loadingVersions && internalPaths !== "") {
      api
        .invoke(
          "GET",
          `/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}${
            distributedSetup ? "&with_versions=true" : ""
          }`
        )
        .then((res: IFileInfo[]) => {
          const result = get(res, "objects", []);
          if (distributedSetup) {
            setActualInfo(
              result.find((el: IFileInfo) => el.is_latest) || emptyFile
            );
            setVersions(result);
          } else {
            setActualInfo(result[0]);
            setVersions([]);
          }

          dispatch(setLoadingVersions(false));
        })
        .catch((err: ErrorResponseHandler) => {
          dispatch(setErrorSnackMessage(err));
          dispatch(setLoadingVersions(false));
        });
    }
  }, [loadingVersions, bucketName, internalPaths, dispatch, distributedSetup]);

  const shareObject = () => {
    setShareFileModalOpen(true);
  };

  const closeShareModal = () => {
    setObjectToShare(null);
    setShareFileModalOpen(false);
    setPreviewOpen(false);
  };

  const downloadObject = (object: IFileInfo) => {
    const identityDownload = encodeURLString(
      `${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}`
    );

    const downloadCall = download(
      bucketName,
      internalPaths,
      object.version_id,
      parseInt(object.size || "0"),
      (progress) => {
        dispatch(
          updateProgress({
            instanceID: identityDownload,
            progress: progress,
          })
        );
      },
      () => {
        dispatch(completeObject(identityDownload));
      },
      () => {
        dispatch(failObject(identityDownload));
      },
      () => {
        dispatch(cancelObjectInList(identityDownload));
      }
    );
    const ID = makeid(8);
    storeCallForObjectWithID(ID, downloadCall);
    dispatch(
      setNewObject({
        ID,
        bucketName,
        done: false,
        instanceID: identityDownload,
        percentage: 0,
        prefix: object.name,
        type: "download",
        waitingForFile: true,
        failed: false,
        cancelled: false,
      })
    );

    downloadCall.send();
  };

  const onShareItem = (item: IFileInfo) => {
    setObjectToShare(item);
    shareObject();
  };

  const onPreviewItem = (item: IFileInfo) => {
    setObjectToShare(item);
    setPreviewOpen(true);
  };

  const onRestoreItem = (item: IFileInfo) => {
    setRestoreVersion(item.version_id || "");
    setRestoreVersionOpen(true);
  };

  const onDownloadItem = (item: IFileInfo) => {
    downloadObject(item);
  };

  const onGlobalClick = (item: IFileInfo) => {
    dispatch(setSelectedVersion(item.version_id || ""));
  };

  const filteredRecords = versions.filter((version) => {
    if (version.version_id) {
      return version.version_id.includes(searchVersions);
    }
    return false;
  });

  const closeRestoreModal = (reloadObjectData: boolean) => {
    setRestoreVersionOpen(false);
    setRestoreVersion("");

    if (reloadObjectData) {
      dispatch(setLoadingVersions(true));
      dispatch(setLoadingObjectInfo(true));
    }
  };

  const closeDeleteNonCurrent = (reloadAfterDelete: boolean) => {
    setDeleteNonCurrentOpen(false);

    if (reloadAfterDelete) {
      dispatch(setLoadingVersions(true));
      dispatch(setSelectedVersion(""));
      dispatch(setLoadingObjectInfo(true));
    }
  };

  const closeSelectedVersions = (reloadOnComplete: boolean) => {
    setDelSelectedVOpen(false);

    if (reloadOnComplete) {
      dispatch(setLoadingVersions(true));
      dispatch(setSelectedVersion(""));
      dispatch(setLoadingObjectInfo(true));
      setSelectedItems([]);
    }
  };

  const totalSpace = versions.reduce((acc: number, currValue: IFileInfo) => {
    if (currValue.size) {
      return acc + parseInt(currValue.size);
    }
    return acc;
  }, 0);

  filteredRecords.sort((a, b) => {
    switch (sortValue) {
      case "size":
        if (a.size && b.size) {
          if (a.size < b.size) {
            return -1;
          }
          if (a.size > b.size) {
            return 1;
          }
          return 0;
        }
        return 0;
      default:
        const dateA = new Date(a.last_modified).getTime();
        const dateB = new Date(b.last_modified).getTime();

        if (dateA < dateB) {
          return 1;
        }
        if (dateA > dateB) {
          return -1;
        }
        return 0;
    }
  });

  const onCheckVersion = (selectedVersion: string) => {
    if (selectedItems.includes(selectedVersion)) {
      const filteredItems = selectedItems.filter(
        (element) => element !== selectedVersion
      );

      setSelectedItems(filteredItems);

      return;
    }

    const cloneState = [...selectedItems];
    cloneState.push(selectedVersion);

    setSelectedItems(cloneState);
  };

  const rowRenderer = ({
    key, // Unique key within array of rows
    index, // Index of row within collection
    isScrolling, // The List is currently being scrolled
    isVisible, // This row is visible within the List (eg it is not an overscanned row)
    style, // Style object to be applied to row (to position it)
  }: ListRowProps) => {
    const versOrd = versions.length - index;
    return (
      <FileVersionItem
        style={style}
        key={key}
        fileName={actualInfo?.name || ""}
        versionInfo={filteredRecords[index]}
        index={versOrd}
        onDownload={onDownloadItem}
        onRestore={onRestoreItem}
        onShare={onShareItem}
        onPreview={onPreviewItem}
        globalClick={onGlobalClick}
        isSelected={selectedVersion === filteredRecords[index].version_id}
        checkable={selectEnabled}
        onCheck={onCheckVersion}
        isChecked={selectedItems.includes(
          filteredRecords[index].version_id || ""
        )}
      />
    );
  };

  return (
    <Fragment>
      {shareFileModalOpen && actualInfo && (
        <ShareFile
          open={shareFileModalOpen}
          closeModalAndRefresh={closeShareModal}
          bucketName={bucketName}
          dataObject={objectToShare || actualInfo}
        />
      )}
      {restoreVersionOpen && actualInfo && (
        <RestoreFileVersion
          restoreOpen={restoreVersionOpen}
          bucketName={bucketName}
          versionID={restoreVersion}
          objectPath={actualInfo.name}
          onCloseAndUpdate={closeRestoreModal}
        />
      )}
      {previewOpen && actualInfo && (
        <PreviewFileModal
          open={previewOpen}
          bucketName={bucketName}
          object={{
            name: actualInfo.name,
            version_id:
              objectToShare && objectToShare.version_id
                ? objectToShare.version_id
                : "null",
            size: parseInt(
              objectToShare && objectToShare.size ? objectToShare.size : "0"
            ),
            content_type: "",
            last_modified: new Date(actualInfo.last_modified),
          }}
          onClosePreview={() => {
            setPreviewOpen(false);
          }}
        />
      )}
      {deleteNonCurrentOpen && (
        <DeleteNonCurrent
          deleteOpen={deleteNonCurrentOpen}
          closeDeleteModalAndRefresh={closeDeleteNonCurrent}
          selectedBucket={bucketName}
          selectedObject={internalPaths}
        />
      )}
      {delSelectedVOpen && (
        <DeleteSelectedVersions
          selectedBucket={bucketName}
          selectedObject={decodeURLString(internalPaths)}
          deleteOpen={delSelectedVOpen}
          selectedVersions={selectedItems}
          closeDeleteModalAndRefresh={closeSelectedVersions}
        />
      )}
      <Grid container className={classes.versionsContainer}>
        {!actualInfo && (
          <Grid item xs={12}>
            <LinearProgress />
          </Grid>
        )}

        {actualInfo && (
          <Fragment>
            <Grid item xs={12}>
              <BrowserBreadcrumbs
                bucketName={bucketName}
                internalPaths={decodeURLString(internalPaths)}
                existingFiles={[]}
                hidePathButton={true}
              />
            </Grid>
            <Grid item xs={12} className={classes.screenTitleContainer}>
              <ScreenTitle
                icon={
                  <span className={classes.listIcon}>
                    <VersionsIcon />
                  </span>
                }
                title={
                  <span className={classes.titleSpacer}>
                    {objectNameArray.length > 0
                      ? objectNameArray[objectNameArray.length - 1]
                      : actualInfo.name}{" "}
                    Versions
                  </span>
                }
                subTitle={
                  <Fragment>
                    <Grid item xs={12} className={classes.bucketDetails}>
                      <span className={classes.detailsSpacer}>
                        <strong>
                          {versions.length} Version
                          {versions.length === 1 ? "" : "s"}&nbsp;&nbsp;&nbsp;
                        </strong>
                      </span>
                      <span className={classes.detailsSpacer}>
                        <strong>{niceBytesInt(totalSpace)}</strong>
                      </span>
                    </Grid>
                  </Fragment>
                }
                actions={
                  <Fragment>
                    <RBIconButton
                      id={"select-multiple-versions"}
                      tooltip={"Select Multiple Versions"}
                      onClick={() => {
                        setSelectEnabled(!selectEnabled);
                      }}
                      text={""}
                      icon={<SelectMultipleIcon />}
                      color="primary"
                      variant={selectEnabled ? "contained" : "outlined"}
                      style={{ marginRight: 8 }}
                    />
                    {selectEnabled && (
                      <RBIconButton
                        id={"delete-multiple-versions"}
                        tooltip={"Delete Selected Versions"}
                        onClick={() => {
                          setDelSelectedVOpen(true);
                        }}
                        text={""}
                        icon={<DeleteIcon />}
                        color="secondary"
                        style={{ marginRight: 8 }}
                        disabled={selectedItems.length === 0}
                      />
                    )}
                    <RBIconButton
                      id={"delete-non-current"}
                      tooltip={"Delete Non Current Versions"}
                      onClick={() => {
                        setDeleteNonCurrentOpen(true);
                      }}
                      text={""}
                      icon={<DeleteNonCurrentIcon />}
                      color="secondary"
                      style={{ marginRight: 15 }}
                      disabled={versions.length <= 1}
                    />
                    <span className={classes.sortByLabel}>Sort by</span>
                    <SelectWrapper
                      id={"sort-by"}
                      label={""}
                      value={sortValue}
                      onChange={(e: SelectChangeEvent<string>) => {
                        setSortValue(e.target.value as string);
                      }}
                      name={"sort-by"}
                      options={[
                        { label: "Date", value: "date" },
                        {
                          label: "Size",
                          value: "size",
                        },
                      ]}
                    />
                  </Fragment>
                }
                className={classes.noBottomBorder}
              />
            </Grid>
            <Grid item xs={12} className={classes.versionsVirtualPanel}>
              {actualInfo.version_id && actualInfo.version_id !== "null" && (
                // @ts-ignore
                <List
                  style={{
                    width: "100%",
                  }}
                  containerStyle={{
                    width: "100%",
                    maxWidth: "100%",
                  }}
                  width={1}
                  height={800}
                  rowCount={filteredRecords.length}
                  rowHeight={108}
                  rowRenderer={rowRenderer}
                />
              )}
            </Grid>
          </Fragment>
        )}
      </Grid>
    </Fragment>
  );
}
Example #17
Source File: PodLogs.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
PodLogs = ({
  classes,
  tenant,
  namespace,
  podName,
  propLoading,
}: IPodLogsProps) => {
  const dispatch = useDispatch();
  const loadingTenant = useSelector(
    (state: AppState) => state.tenants.loadingTenant
  );
  const [highlight, setHighlight] = useState<string>("");
  const [logLines, setLogLines] = useState<string[]>([]);
  const [loading, setLoading] = useState<boolean>(true);

  const cache = new CellMeasurerCache({
    minWidth: 5,
    fixedHeight: false,
  });

  useEffect(() => {
    if (propLoading) {
      setLoading(true);
    }
  }, [propLoading]);

  useEffect(() => {
    if (loadingTenant) {
      setLoading(true);
    }
  }, [loadingTenant]);

  const renderLog = (logMessage: string, index: number) => {
    if (!logMessage) {
      return null;
    }
    // remove any non ascii characters, exclude any control codes
    logMessage = logMessage.replace(/([^\x20-\x7F])/g, "");

    // regex for terminal colors like e.g. `[31;4m `
    const tColorRegex = /((\[[0-9;]+m))/g;

    // get substring if there was a match for to split what
    // is going to be colored and what not, here we add color
    // only to the first match.
    let substr = logMessage.replace(tColorRegex, "");

    // in case highlight is set, we select the line that contains the requested string
    let highlightedLine =
      highlight !== ""
        ? logMessage.toLowerCase().includes(highlight.toLowerCase())
        : false;

    // if starts with multiple spaces add padding
    if (substr.startsWith("   ")) {
      return (
        <div
          key={index}
          className={`${highlightedLine ? classes.highlight : ""}`}
        >
          <span className={classes.tab}>{substr}</span>
        </div>
      );
    } else {
      // for all remaining set default class
      return (
        <div
          key={index}
          className={`${highlightedLine ? classes.highlight : ""}`}
        >
          <span className={classes.ansidefault}>{substr}</span>
        </div>
      );
    }
  };

  useEffect(() => {
    if (loading) {
      api
        .invoke(
          "GET",
          `/api/v1/namespaces/${namespace}/tenants/${tenant}/pods/${podName}`
        )
        .then((res: string) => {
          setLogLines(res.split("\n"));
          setLoading(false);
        })
        .catch((err: ErrorResponseHandler) => {
          dispatch(setErrorSnackMessage(err));
          setLoading(false);
        });
    }
  }, [loading, podName, namespace, tenant, dispatch]);

  function cellRenderer({ columnIndex, key, parent, index, style }: any) {
    return (
      // @ts-ignore
      <CellMeasurer
        cache={cache}
        columnIndex={columnIndex}
        key={key}
        parent={parent}
        rowIndex={index}
      >
        <div
          style={{
            ...style,
          }}
        >
          {renderLog(logLines[index], index)}
        </div>
      </CellMeasurer>
    );
  }

  return (
    <React.Fragment>
      <Grid item xs={12} className={classes.actionsTray}>
        <TextField
          placeholder="Highlight Line"
          className={classes.searchField}
          id="search-resource"
          label=""
          onChange={(val) => {
            setHighlight(val.target.value);
          }}
          InputProps={{
            disableUnderline: true,
            startAdornment: (
              <InputAdornment position="start">
                <SearchIcon />
              </InputAdornment>
            ),
          }}
          variant="standard"
        />
      </Grid>
      <Grid item xs={12}>
        <br />
      </Grid>
      <Grid item xs={12}>
        <Paper>
          <div className={classes.logList}>
            {logLines.length >= 1 && (
              // @ts-ignore
              <AutoSizer>
                {({ width, height }) => (
                  // @ts-ignore
                  <List
                    rowHeight={(item) => cache.rowHeight(item)}
                    overscanRowCount={15}
                    rowCount={logLines.length}
                    rowRenderer={cellRenderer}
                    width={width}
                    height={height}
                  />
                )}
              </AutoSizer>
            )}
          </div>
        </Paper>
      </Grid>
    </React.Fragment>
  );
}
Example #18
Source File: InfinityScroll.tsx    From querybook with Apache License 2.0 4 votes vote down vote up
function InfinityScrollComponent<T>({
    labelField = 'name',
    itemClass = '',
    itemHeight = 24,

    elements,
    className,
    onClick,
    itemRenderer,

    onLoadMore,
    hasMore,
    defaultListHeight,
    autoSizerStyles,
}: React.PropsWithChildren<IInfinityScrollProps<T>>) {
    const listRef = useRef<List>();

    const rowRenderer = useCallback(
        ({
            index, // Index of row
            key, // Unique key within array of rendered rows
            style, // Style object to be applied to row (to position it);
        }: // This must be passed through to the rendered row element.
        {
            index: number;
            key: string;
            style: CSSProperties;
        }) => {
            if (index >= elements.length) {
                return (
                    <div
                        key={key}
                        style={style}
                        className="InfiniteScroll-loader flex-center"
                    >
                        <LoadingRow />
                    </div>
                );
            }

            const element = elements[index];

            const content = itemRenderer ? (
                itemRenderer(element)
            ) : (
                <span
                    className={itemClass}
                    onClick={onClick.bind(null, element)}
                >
                    {element[labelField]}
                </span>
            );

            return (
                <div key={key} style={style}>
                    {content}
                </div>
            );
        },
        [itemClass, itemRenderer, elements, labelField, onClick]
    );

    const isRowLoaded = useCallback(
        ({ index }: { index: number }) => index < elements.length,
        [elements.length]
    );
    const handleLoadMoreRows = useCallback(
        () =>
            new Promise<void>(async (resolve) => {
                try {
                    if (onLoadMore) {
                        await onLoadMore();
                    }
                } finally {
                    resolve();
                }
            }),
        [onLoadMore]
    );

    useEffect(() => {
        if (listRef.current) {
            listRef.current.forceUpdateGrid();
        }
    }, [elements]);

    const rowCount = hasMore ? elements.length + 1 : elements.length;

    return (
        <InfiniteLoader
            isRowLoaded={isRowLoaded}
            loadMoreRows={handleLoadMoreRows}
            rowCount={rowCount}
        >
            {({ onRowsRendered, registerChild }) => (
                <AutoSizer style={autoSizerStyles}>
                    {({ height, width }) => (
                        <List
                            className={className}
                            onRowsRendered={onRowsRendered}
                            ref={(ref) => {
                                registerChild(ref);
                                listRef.current = ref;
                            }}
                            height={defaultListHeight ?? height}
                            width={width}
                            rowCount={rowCount}
                            rowHeight={itemHeight}
                            rowRenderer={rowRenderer}
                        />
                    )}
                </AutoSizer>
            )}
        </InfiniteLoader>
    );
}
Example #19
Source File: Timeline.tsx    From metaflow-ui with Apache License 2.0 4 votes vote down vote up
Timeline: React.FC<TimelineProps> = ({
  rows,
  timeline,
  searchStatus,
  footerType = 'minimap',
  paramsString = '',
  customMinimumHeight = 31.25,
  onHandleMove = () => null,
  onMove = () => null,
  onStepRowClick = () => null,
}) => {
  const { t } = useTranslation();

  // Position of each step in timeline. Used to track if we should use sticky header (move to rowDataState?)
  const [stepPositions, setStepPositions] = useState<StepIndex[]>([]);
  // Name of sticky header (if should be visible)
  const [stickyHeader, setStickyHeader] = useState<null | string>(null);
  const [dragging, setDragging] = useState(false);

  // Update step position indexes (for sticky headers). We might wanna do this else where
  useEffect(() => {
    const stepPos: StepIndex[] = [];
    let index = 0;

    for (const current of rows) {
      index++;
      if (current.type === 'step') {
        stepPos.push({ name: current.data.step_name, index });
      }
    }

    setStepPositions(stepPos);
  }, [rows]);

  //
  // Event handling
  //

  const onRowsRendered = (params: RenderedRows) => {
    const stepNeedsSticky = timelineNeedStickyHeader(stepPositions, params.startIndex);

    if (stepNeedsSticky) {
      setStickyHeader(stepNeedsSticky.name);
    } else {
      if (stickyHeader) {
        setStickyHeader(null);
      }
    }
  };

  return (
    <ListContainer customMinHeight={customMinimumHeight}>
      <AutoSizer>
        {({ width, height }) => (
          <>
            <List
              overscanRowCount={10}
              rowCount={rows.length}
              onRowsRendered={onRowsRendered}
              rowHeight={ROW_HEIGHT}
              rowRenderer={createRowRenderer({
                rows,
                timeline,
                searchStatus,
                onStepRowClick,
                paramsString,
                t: t,
                dragging: dragging,
              })}
              height={
                height - SPACE_UNDER_TIMELINE(footerType) > rows.length * ROW_HEIGHT
                  ? rows.length * ROW_HEIGHT
                  : height - SPACE_UNDER_TIMELINE(footerType)
              }
              width={width}
              style={{ transition: 'height 0.25s' }}
            />
            {stickyHeader && timeline.groupingEnabled && (
              <StickyHeader
                stickyStep={stickyHeader}
                items={rows}
                timeline={timeline}
                onToggle={() => onStepRowClick(stickyHeader)}
                t={t}
                dragging={dragging}
              />
            )}

            <div style={{ width: width + 'px' }}>
              <TimelineFooter
                {...(footerType === 'minimap'
                  ? {
                      type: 'minimap',
                      props: {
                        timeline,
                        rows,
                        onMove: onMove,
                        onHandleMove: onHandleMove,
                        onDraggingStateChange: setDragging,
                      },
                    }
                  : {
                      type: 'minimal',
                      props: {
                        startTime: timeline.startTime,
                        visibleStartTime: timeline.visibleStartTime,
                        visibleEndtime: timeline.visibleEndTime,
                      },
                    })}
              />
            </div>
          </>
        )}
      </AutoSizer>
    </ListContainer>
  );
}