<commit_before>/*
 *  This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
 *  License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
 */

/* WARNING/TODO:

   scroll_to_index does basically NOTHING right now.
   This is due to an API change in react-window,
   which I just found by installing @types/react-window.
*/

// Windowed List, based on react-window:
//
// - automatically handles rows changing sizes, which I guess solves this problem?  https://github.com/bvaughn/react-window/issues/6
//
// - handles maintaining sroll position between unmount/mount
//
// - We use react-window instead of react-virtualized, since react-window is
//   enough for our needs, is faster, is smaller, and seems to work better.
//   I did implement everything first using react-virtualized, but react-window
//   is definitely faster, and the overscan seems to work much better.

import { delay } from "awaiting";

import ResizeObserver from "resize-observer-polyfill";

const SHRINK_THRESH: number = 10;
const BIG: number = 9999999;

import { VariableSizeList as List, ListOnScrollProps } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";

export interface ScrollInfo extends ListOnScrollProps {
  maxScrollOffset?: number;
}

import { React, Component, Rendered } from "../app-framework";

interface Props {
  overscan_row_count: number; // how many not visible cells to render on each side of window
  estimated_row_size: number; // estimate to use for the row size before measuring
  row_count: number; // number of rows
  row_renderer: (obj: {
    key: string;
    index: number;
    isScrolling?: boolean;
    isVisible?: boolean;
  }) => Rendered; // renders row with given key (or index).
  row_key: (index: number) => string | undefined; // map from row number to string key; must have unique stable keys!
  scroll_to_index?: number; // moves to this row during next render (but doesn't get stuck there!)
  scroll_top?: number;
  cache_id?: string; // if set, the measured cell sizes and scroll position are preserved between unmount/mounts
  on_scroll?: (info: ScrollInfo) => void;
  use_is_scrolling?: boolean;
  hide_resize?: boolean;
  render_info?: boolean; // if true, record RenderInfo; also makes isVisible available for row_renderer.
  scroll_margin?: number;
}

interface State {
  scroll_to_index?: number;
  scroll_top?: number;
}

interface RenderInfo {
  overscanStartIndex: number;
  overscanStopIndex: number;
  visibleStartIndex: number;
  visibleStopIndex: number;
}

// TODO: this should be an LRU cache, to avoid a longterm memory leak.
const scroll_cache: {
  [cache_id: string]: {
    info: ListOnScrollProps;
    row_heights_cache: { [key: string]: number };
  };
} = {};

export class WindowedList extends Component<Props, State> {
  private cell_refs: { [key: string]: any } = {};
  private list_ref;
  private row_heights_cache: { [key: string]: number } = {};
  private row_heights_stale: { [key: string]: boolean } = {};
  private row_heights_removed: { [key: string]: boolean } = {};
  public resize_observer: any; // ResizeObserver, but can't because that's only for the polyfill...
  private is_mounted: boolean = true;
  private _disable_refresh: boolean = false;
  private RowComponent: any;
  private height: number = 0;
  private width: number = 0;
  private scroll_info: ScrollInfo = {
    scrollDirection: "forward",
    scrollOffset: 0,
    scrollUpdateWasRequested: false,
  };

  private min_changed_index: number = 0;

  public render_info: RenderInfo = {
    overscanStartIndex: 0,
    overscanStopIndex: 0,
    visibleStartIndex: 0,
    visibleStopIndex: 0,
  };
  private ensure_visible?: { row: number; align: string };

  constructor(props) {
    super(props);
    this.list_ref = React.createRef();
    this.resize_observer = new ResizeObserver(this.rows_resized.bind(this));
    let scroll_top: number | undefined = props.scroll_top;
    if (scroll_top == null && this.props.cache_id != null) {
      const x = scroll_cache[this.props.cache_id];<commit_msg>fix #4254 -- part 2; fix some glitchy behavior caused by overloading resizeobserver when scrolling, which is only visible (to me) on Safari<commit_after>/*
 *  This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
 *  License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
 */

/* WARNING/TODO:

   scroll_to_index does basically NOTHING right now.
   This is due to an API change in react-window,
   which I just found by installing @types/react-window.
*/

// Windowed List, based on react-window:
//
// - automatically handles rows changing sizes, which I guess solves this problem?  https://github.com/bvaughn/react-window/issues/6
//
// - handles maintaining sroll position between unmount/mount
//
// - We use react-window instead of react-virtualized, since react-window is
//   enough for our needs, is faster, is smaller, and seems to work better.
//   I did implement everything first using react-virtualized, but react-window
//   is definitely faster, and the overscan seems to work much better.

import { delay } from "awaiting";

import ResizeObserver from "resize-observer-polyfill";

const SHRINK_THRESH: number = 10;
const BIG: number = 9999999;

import { VariableSizeList as List, ListOnScrollProps } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";

export interface ScrollInfo extends ListOnScrollProps {
  maxScrollOffset?: number;
}

import { React, Component, Rendered } from "../app-framework";

interface Props {
  overscan_row_count: number; // how many not visible cells to render on each side of window
  estimated_row_size: number; // estimate to use for the row size before measuring
  row_count: number; // number of rows
  row_renderer: (obj: {
    key: string;
    index: number;
    isScrolling?: boolean;
    isVisible?: boolean;
  }) => Rendered; // renders row with given key (or index).
  row_key: (index: number) => string | undefined; // map from row number to string key; must have unique stable keys!
  scroll_to_index?: number; // moves to this row during next render (but doesn't get stuck there!)
  scroll_top?: number;
  cache_id?: string; // if set, the measured cell sizes and scroll position are preserved between unmount/mounts
  on_scroll?: (info: ScrollInfo) => void;
  use_is_scrolling?: boolean;
  hide_resize?: boolean;
  render_info?: boolean; // if true, record RenderInfo; also makes isVisible available for row_renderer.
  scroll_margin?: number;
}

interface State {
  scroll_to_index?: number;
  scroll_top?: number;
}

interface RenderInfo {
  overscanStartIndex: number;
  overscanStopIndex: number;
  visibleStartIndex: number;
  visibleStopIndex: number;
}

// TODO: this should be an LRU cache, to avoid a longterm memory leak.
const scroll_cache: {
  [cache_id: string]: {
    info: ListOnScrollProps;
    row_heights_cache: { [key: string]: number };
  };
} = {};

export class WindowedList extends Component<Props, State> {
  private cell_refs: { [key: string]: any } = {};
  private list_ref;
  private row_heights_cache: { [key: string]: number } = {};
  private row_heights_stale: { [key: string]: boolean } = {};
  private row_heights_removed: { [key: string]: boolean } = {};
  public resize_observer: any; // ResizeObserver, but can't because that's only for the polyfill...
  private is_mounted: boolean = true;
  private _disable_refresh: boolean = false;
  private RowComponent: any;
  private height: number = 0;
  private width: number = 0;
  private scroll_info: ScrollInfo = {
    scrollDirection: "forward",
    scrollOffset: 0,
    scrollUpdateWasRequested: false,
  };

  private min_changed_index: number = 0;

  public render_info: RenderInfo = {
    overscanStartIndex: 0,
    overscanStopIndex: 0,
    visibleStartIndex: 0,
    visibleStopIndex: 0,
  };
  private ensure_visible?: { row: number; align: string };

  constructor(props) {
    super(props);
    this.list_ref = React.createRef();
    this.resize_observer = new ResizeObserver((entries) =>
      // We wrap it in requestAnimationFrame to avoid this error - ResizeObserver loop limit exceeded
      // See https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
      // This overflow happens frequently on Safari and results in glitchy behavior.
      window.requestAnimationFrame(() => this.rows_resized(entries))
    );
    let scroll_top: number | undefined = props.scroll_top;
    if (scroll_top == null && this.props.cache_id != null) {
      const x = scroll_cache[this.props.cache_id];
{'fraction_non_alphanumeric': 0.1028965195047886, 'fraction_numerical': 0.006890913338005139, 'mean_word_length': 3.941142527409117, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 13, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1675603', 'n_tokens_mistral': 2603, 'n_tokens_neox': 2511, 'n_words': 884}