<commit_before>// @flow

import * as React from 'react';

import type { ChatMessageItem } from 'lib/selectors/chat-selectors';
import type { ThreadInfo } from 'lib/types/thread-types';

import { ChatContext, type RegisteredMeasurer } from './chat-context';
import ChatItemHeightMeasurer from './chat-item-height-measurer.react';
import type { ChatMessageItemWithHeight } from './message-list-container.react';

type Props = {
  +children: React.Node,
};

export type MeasurementTask = {
  +messages: $ReadOnlyArray<ChatMessageItem>,
  +threadInfo: ThreadInfo,
  +onMessagesMeasured: (
    messagesWithHeight: $ReadOnlyArray<ChatMessageItemWithHeight>,
    measuredHeights: $ReadOnlyMap<string, number>,
  ) => mixed,
  +measurerID: number,
  +initialMeasuredHeights: ?$ReadOnlyMap<string, number>,
};
type State = {
  +measurements: $ReadOnlyArray<MeasurementTask>,
};
class ChatContextProvider extends React.PureComponent<Props, State> {
  state: State = {
    measurements: [],
  };
  nextMeasurerID: number = 0;
  measuredHeights: Map<number, $ReadOnlyMap<string, number>> = new Map<
    number,
    $ReadOnlyMap<string, number>,
  >();

  registerMeasurer: () => RegisteredMeasurer = () => {
    const measurerID = this.nextMeasurerID++;
    return {
      measure: (
        messages: $ReadOnlyArray<ChatMessageItem>,
        threadInfo: ThreadInfo,
        onMessagesMeasured: (
          $ReadOnlyArray<ChatMessageItemWithHeight>,
        ) => mixed,
      ) =>
        this.measureMessages(
          messages,
          threadInfo,
          onMessagesMeasured,
          measurerID,
        ),
      unregister: () => {
        this.setState(state => ({
          measurements: state.measurements.filter(
            measurement => measurement.measurerID !== measurerID,
          ),
        }));
        this.measuredHeights.delete(measurerID);
      },
    };
  };

  measureMessages: (
    messages: $ReadOnlyArray<ChatMessageItem>,
    threadInfo: ThreadInfo,
    onMessagesMeasured: ($ReadOnlyArray<ChatMessageItemWithHeight>) => mixed,
    measurerID: number,
  ) => void = (messages, threadInfo, onMessagesMeasured, measurerID) => {
    const measureCallback = (
      messagesWithHeight: $ReadOnlyArray<ChatMessageItemWithHeight>,
      measuredHeights: $ReadOnlyMap<string, number>,
    ) => {
      this.measuredHeights.set(measurerID, measuredHeights);
      onMessagesMeasured(messagesWithHeight);
    };

    let initialMeasuredHeights = null;
    const isMeasurementPresent = this.measuredHeights.has(measurerID);
    if (!isMeasurementPresent) {
      const sourceMeasurerID = this.state.measurements.find(
        measurement => measurement.threadInfo.id === threadInfo.id,
      )?.measurerID;
      initialMeasuredHeights = sourceMeasurerID
        ? this.measuredHeights.get(sourceMeasurerID)
        : null;
    }

    const newMeasurement = {
      messages,
      threadInfo,
      onMessagesMeasured: measureCallback,
      measurerID,
      initialMeasuredHeights,
    };
    this.setState(state => {
      const withoutCurrentMeasurement = state.measurements.filter(
        measurement => measurement.measurerID !== measurerID,
      );
      return {
        measurements: [...withoutCurrentMeasurement, newMeasurement],
      };
    });
  };

  contextValue: { +registerMeasurer: () => RegisteredMeasurer } = {
    registerMeasurer: this.registerMeasurer,
  };

  render(): React.Node {
    const heightMeasurers = this.state.measurements.map(measurement => (
      <ChatItemHeightMeasurer
        key={measurement.measurerID}
        measurement={measurement}
      />
    ));
    return (
      <ChatContext.Provider value={this.contextValue}>
        {heightMeasurers}
        {this.props.children}
      </ChatContext.Provider>
    );
  }
}

export default ChatContextProvider;<commit_msg>[native] Make ChatContextProvider a functional component<commit_after>// @flow

import * as React from 'react';

import type { ChatMessageItem } from 'lib/selectors/chat-selectors';
import type { ThreadInfo } from 'lib/types/thread-types';

import { ChatContext } from './chat-context';
import ChatItemHeightMeasurer from './chat-item-height-measurer.react';
import type { ChatMessageItemWithHeight } from './message-list-container.react';

type Props = {
  +children: React.Node,
};

export type MeasurementTask = {
  +messages: $ReadOnlyArray<ChatMessageItem>,
  +threadInfo: ThreadInfo,
  +onMessagesMeasured: (
    messagesWithHeight: $ReadOnlyArray<ChatMessageItemWithHeight>,
    measuredHeights: $ReadOnlyMap<string, number>,
  ) => mixed,
  +measurerID: number,
  +initialMeasuredHeights: ?$ReadOnlyMap<string, number>,
};

function ChatContextProvider(props: Props): React.Node {
  const [measurements, setMeasurements] = React.useState<
    $ReadOnlyArray<MeasurementTask>,
  >([]);
  const nextMeasurerID = React.useRef(0);
  const measuredHeights = React.useRef<
    Map<number, $ReadOnlyMap<string, number>>,
  >(new Map());

  const measureMessages = React.useCallback(
    (
      messages: $ReadOnlyArray<ChatMessageItem>,
      threadInfo: ThreadInfo,
      onMessagesMeasured: ($ReadOnlyArray<ChatMessageItemWithHeight>) => mixed,
      measurerID: number,
    ) => {
      const measureCallback = (
        messagesWithHeight: $ReadOnlyArray<ChatMessageItemWithHeight>,
        newMeasuredHeights: $ReadOnlyMap<string, number>,
      ) => {
        measuredHeights.current.set(measurerID, newMeasuredHeights);
        onMessagesMeasured(messagesWithHeight);
      };

      let initialMeasuredHeights = null;
      const isMeasurementPresent = measuredHeights.current.has(measurerID);
      if (!isMeasurementPresent) {
        const sourceMeasurerID = measurements.find(
          measurement => measurement.threadInfo.id === threadInfo.id,
        )?.measurerID;
        initialMeasuredHeights = sourceMeasurerID
          ? measuredHeights.current.get(sourceMeasurerID)
          : null;
      }

      const newMeasurement = {
        messages,
        threadInfo,
        onMessagesMeasured: measureCallback,
        measurerID,
        initialMeasuredHeights,
      };
      setMeasurements(prevMeasurements => {
        const withoutCurrentMeasurement = prevMeasurements.filter(
          measurement => measurement.measurerID !== measurerID,
        );
        return [...withoutCurrentMeasurement, newMeasurement];
      });
    },
    [measurements],
  );

  const registerMeasurer = React.useCallback(() => {
    const measurerID = nextMeasurerID.current++;
    return {
      measure: (
        messages: $ReadOnlyArray<ChatMessageItem>,
        threadInfo: ThreadInfo,
        onMessagesMeasured: (
          $ReadOnlyArray<ChatMessageItemWithHeight>,
        ) => mixed,
      ) =>
        measureMessages(messages, threadInfo, onMessagesMeasured, measurerID),
      unregister: () => {
        setMeasurements(prevMeasurements =>
          prevMeasurements.filter(
            measurement => measurement.measurerID !== measurerID,
          ),
        );
        measuredHeights.current.delete(measurerID);
      },
    };
  }, [measureMessages]);

  const contextValue = React.useMemo(
    () => ({
      registerMeasurer,
    }),
    [registerMeasurer],
  );

  const heightMeasurers = React.useMemo(
    () =>
      measurements.map(measurement => (
        <ChatItemHeightMeasurer
          key={measurement.measurerID}
          measurement={measurement}
        />
      )),
    [measurements],
  );
  return (
    <ChatContext.Provider value={contextValue}>
      {heightMeasurers}
      {props.children}
    </ChatContext.Provider>
  );
}

export default ChatContextProvider;
{'fraction_non_alphanumeric': 0.1145397489539749, 'fraction_numerical': 0.0002615062761506276, 'mean_word_length': 3.205057724024189, 'pattern_counts': {'":': 0, '<': 40, '<?xml version=': 0, '>': 73, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, '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': '358067', 'n_tokens_mistral': 2212, 'n_tokens_neox': 2154, 'n_words': 377}