<commit_before>// Copyright (c) ppy Pty Ltd <<EMAIL>>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

import { ChatChannelLoadEarlierMessages } from 'actions/chat-actions';
import { dispatch } from 'app-dispatcher';
import { route } from 'laroute';
import * as _ from 'lodash';
import { computed, observe } from 'mobx';
import { inject, observer } from 'mobx-react';
import Message from 'models/chat/message';
import * as moment from 'moment';
import * as React from 'react';
import { ShowMoreLink } from 'show-more-link';
import { Spinner } from 'spinner';
import RootDataStore from 'stores/root-data-store';
import { StringWithComponent } from 'string-with-component';
import { UserAvatar } from 'user-avatar';
import { MessageDivider } from './message-divider';
import MessageGroup from './message-group';

interface Props {
  dataStore?: RootDataStore;
}

interface Snapshot {
  chatHeight: number;
  chatTop: number;
}

const blankSnapshot = (): Snapshot => ({ chatHeight: 0, chatTop: 0 });

@inject('dataStore')
@observer
export default class ConversationView extends React.Component<Props> {
  private assumeHasBacklog: boolean = false;
  private chatViewRef = React.createRef<HTMLDivElement>();
  private readonly dataStore: RootDataStore;
  private didSwitchChannel: boolean = true;
  private firstMessage?: Message;
  private unreadMarkerRef = React.createRef<HTMLDivElement>();

  @computed
  get currentChannel() {
    return this.dataStore.chatState.selectedChannel;
  }

  constructor(props: Props) {
    super(props);

    this.dataStore = props.dataStore!;

    observe(this.dataStore.chatState.selectedBoxed, (change) => {
      if (change.newValue !== change.oldValue) {
        this.didSwitchChannel = true;
      }
    });
  }

  componentDidMount() {
    this.componentDidUpdate();
    $(window).on('scroll', _.throttle(this.onScroll, 1000));
  }

  componentDidUpdate = (prevProps?: Props, prevState?: {}, snapshot?: Snapshot) => {
    const chatView = this.chatViewRef.current;
    if (!chatView) {
      return;
    }

    const dataStore = this.dataStore;
    const channel = dataStore.channelStore.channels.get(dataStore.chatState.selected);
    if (!channel?.loaded) {
      return;
    }

    if (this.didSwitchChannel) {
      if (this.unreadMarkerRef.current) {
        this.scrollToUnread();
      } else {
        this.scrollToBottom();
      }
      this.didSwitchChannel = false;
    } else {
      snapshot = snapshot ?? blankSnapshot();
      const prepending = this.firstMessage !== this.currentChannel?.messages[0];

      if (prepending && this.chatViewRef.current != null) {
        const chatEl = this.chatViewRef.current;
        const newHeight = chatEl.scrollHeight;
        chatEl.scrollTo(chatEl.scrollLeft, snapshot.chatTop + (newHeight - snapshot.chatHeight));
      } else {
        if (this.dataStore.chatState.autoScroll) {
          this.scrollToBottom();
        }
      }
    }

    this.firstMessage = channel.messages[0];
  }

  getSnapshotBeforeUpdate() {
    const snapshot = blankSnapshot();

    if (this.chatViewRef.current != null) {
      snapshot.chatHeight = this.chatViewRef.current.scrollHeight;
      snapshot.chatTop = this.chatViewRef.current.scrollTop;
    }

    return snapshot;
  }

  noCanSendMessage(): React.ReactNode {
    const channel = this.currentChannel;

    if (channel == null) {
      // this shouldn't happen...
      return;
    }

    if (channel.type === 'PM' || channel.transient) {
      return (
        <div>
          <div className='chat-conversation__cannot-message'>{osu.trans('chat.cannot_send.user')}</div>
          <ul className='chat-conversation__cannot-message-reasons'>
            <li>{osu.trans('chat.cannot_send.reasons.friends_only')}</li>
            <li>{osu.trans('chat.cannot_send.reasons.target_restricted')}</li>
            <li>{osu.trans('chat.cannot_send.reasons.restricted')}</li>
            <li>{osu.trans('chat.cannot_send.reasons.blocked')}</li>
          </ul>
        </div>
      );
    } else if (channel.type === 'GROUP') {
      return (
        <div>
          <div className='chat-conversation__cannot-message'>{osu.trans('chat.cannot_send.channel')}</div>
          <ul className='chat-conversation__cannot-message-reasons'>
            <li>{osu.trans('chat.cannot_send.reasons.channel_moderated')}</li>
            <li>{osu.trans('chat.cannot_send.reasons.restricted')}</li>
          </ul>
        </div>
      );
    }
  }

  onScroll = () => {
    const chatView = this.chatViewRef.current;
    if (chatView) {
      this.dataStore.chatState.autoScroll = chatView.scrollTop + chatView.clientHeight >= chatView.scrollHeight;
    }
  }<commit_msg>use computed property<commit_after>// Copyright (c) ppy Pty Ltd <<EMAIL>>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

import { ChatChannelLoadEarlierMessages } from 'actions/chat-actions';
import { dispatch } from 'app-dispatcher';
import { route } from 'laroute';
import * as _ from 'lodash';
import { computed, observe } from 'mobx';
import { inject, observer } from 'mobx-react';
import Message from 'models/chat/message';
import * as moment from 'moment';
import * as React from 'react';
import { ShowMoreLink } from 'show-more-link';
import { Spinner } from 'spinner';
import RootDataStore from 'stores/root-data-store';
import { StringWithComponent } from 'string-with-component';
import { UserAvatar } from 'user-avatar';
import { MessageDivider } from './message-divider';
import MessageGroup from './message-group';

interface Props {
  dataStore?: RootDataStore;
}

interface Snapshot {
  chatHeight: number;
  chatTop: number;
}

const blankSnapshot = (): Snapshot => ({ chatHeight: 0, chatTop: 0 });

@inject('dataStore')
@observer
export default class ConversationView extends React.Component<Props> {
  private assumeHasBacklog: boolean = false;
  private chatViewRef = React.createRef<HTMLDivElement>();
  private readonly dataStore: RootDataStore;
  private didSwitchChannel: boolean = true;
  private firstMessage?: Message;
  private unreadMarkerRef = React.createRef<HTMLDivElement>();

  @computed
  get currentChannel() {
    return this.dataStore.chatState.selectedChannel;
  }

  constructor(props: Props) {
    super(props);

    this.dataStore = props.dataStore!;

    observe(this.dataStore.chatState.selectedBoxed, (change) => {
      if (change.newValue !== change.oldValue) {
        this.didSwitchChannel = true;
      }
    });
  }

  componentDidMount() {
    this.componentDidUpdate();
    $(window).on('scroll', _.throttle(this.onScroll, 1000));
  }

  componentDidUpdate(prevProps?: Props, prevState?: {}, snapshot?: Snapshot) {
    const chatView = this.chatViewRef.current;
    if (!chatView) {
      return;
    }

    if (!this.currentChannel?.loaded) {
      return;
    }

    if (this.didSwitchChannel) {
      if (this.unreadMarkerRef.current) {
        this.scrollToUnread();
      } else {
        this.scrollToBottom();
      }
      this.didSwitchChannel = false;
    } else {
      snapshot = snapshot ?? blankSnapshot();
      const prepending = this.firstMessage !== this.currentChannel.messages[0];

      if (prepending && this.chatViewRef.current != null) {
        const chatEl = this.chatViewRef.current;
        const newHeight = chatEl.scrollHeight;
        chatEl.scrollTo(chatEl.scrollLeft, snapshot.chatTop + (newHeight - snapshot.chatHeight));
      } else {
        if (this.dataStore.chatState.autoScroll) {
          this.scrollToBottom();
        }
      }
    }

    this.firstMessage = this.currentChannel.messages[0];
  }

  getSnapshotBeforeUpdate() {
    const snapshot = blankSnapshot();

    if (this.chatViewRef.current != null) {
      snapshot.chatHeight = this.chatViewRef.current.scrollHeight;
      snapshot.chatTop = this.chatViewRef.current.scrollTop;
    }

    return snapshot;
  }

  noCanSendMessage(): React.ReactNode {
    if (this.currentChannel == null) {
      // this shouldn't happen...
      return;
    }

    if (this.currentChannel.type === 'PM' || this.currentChannel.transient) {
      return (
        <div>
          <div className='chat-conversation__cannot-message'>{osu.trans('chat.cannot_send.user')}</div>
          <ul className='chat-conversation__cannot-message-reasons'>
            <li>{osu.trans('chat.cannot_send.reasons.friends_only')}</li>
            <li>{osu.trans('chat.cannot_send.reasons.target_restricted')}</li>
            <li>{osu.trans('chat.cannot_send.reasons.restricted')}</li>
            <li>{osu.trans('chat.cannot_send.reasons.blocked')}</li>
          </ul>
        </div>
      );
    } else if (this.currentChannel.type === 'GROUP') {
      return (
        <div>
          <div className='chat-conversation__cannot-message'>{osu.trans('chat.cannot_send.channel')}</div>
          <ul className='chat-conversation__cannot-message-reasons'>
            <li>{osu.trans('chat.cannot_send.reasons.channel_moderated')}</li>
            <li>{osu.trans('chat.cannot_send.reasons.restricted')}</li>
          </ul>
        </div>
      );
    }
  }

  onScroll = () => {
    const chatView = this.chatViewRef.current;
    if (chatView) {
      this.dataStore.chatState.autoScroll = chatView.scrollTop + chatView.clientHeight >= chatView.scrollHeight;
    }
  }
{'fraction_non_alphanumeric': 0.135092180546726, 'fraction_numerical': 0.0021190930281839372, 'mean_word_length': 3.1951111111111112, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 70, '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': '4400', 'n_tokens_mistral': 2902, 'n_tokens_neox': 2831, 'n_words': 566}