<commit_before>/*
 * Copyright (C) 2012-2017  Online-Go.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import * as React from "react";
import {Link} from "react-router";
import {_, interpolate} from "translate";
import {post, get} from "requests";
import {Player} from "Player";
import {profanity_filter} from "profanity_filter";
import {comm_socket} from "sockets";
import data from "data";
import preferences from "preferences";
import {emitNotification} from "Notifications";
import {Flag} from "Flag";
import {Card} from "components";
import {TabCompleteInput} from "TabCompleteInput";
import player_cache from "player_cache";
import {string_splitter, n2s, dup} from "misc";
import {SeekGraph} from "SeekGraph";
import {PersistentElement} from "PersistentElement";

declare let swal;


data.setDefault("chat.joined", {});

interface ChatProperties {
    channel?: string;
    autofocus?: boolean;
    showChannels?: boolean;
    showUserList?: boolean;
    updateTitle: boolean;
}

let name_match_regex = /^loading...$/;
data.watch("config.user", (user) => {
    name_match_regex = new RegExp("\b" + user.username.replace(/[\\^$*+.()|[\]{}]/g, "\$&") + "\b", "i");
});

let global_channels: Array<any> = [ /* {{{ */
    {"id": "global-english" , "name": "English", "country": "us"},
    {"id": "global-help" , "name": "Help", "country": "un"},
    {"id": "global-offtopic" , "name": "Off Topic", "country": "un"},
    {"id": "global-japanese", "name": "日本語 ", "country": "jp"},
    {"id": "global-chinese" , "name": "中文"   , "country": "cn"},
    {"id": "global-korean"  , "name": "한국어" , "country": "kr"},
    {"id": "global-russian"  , "name": "Русский" , "country": "r"},
    {"id": "global-polish"  , "name": "Polski" , "country": "pl"},
    {"id": "global-arabic", "name": "العَرَبِيَّةُ", "country": "_Arab_League", "rtl": true},
    {"id": "global-bulgarian"  , "name": "Български" , "country": "bg"},
    {"id": "global-catalan"  , "name": "Català" , "country": "es"},
    {"id": "global-czech"  , "name": "Čeština" , "country": "cz"},
    {"id": "global-esperanto"  , "name": "Esperanto" , "country": "_Esperanto"},
    {"id": "global-german"  , "name": "Deutsch" , "country": "de"},
    {"id": "global-spanish"  , "name": "Español" , "country": "es"},
    {"id": "global-french"  , "name": "Français" , "country": "fr"},
    {"id": "global-filipino"  , "name": "Filipino" , "country": "ph"},
    {"id": "global-indonesian", "name": "Indonesian", "country": "id"},
    {"id": "global-hebrew", "name": "עִבְרִית", "country": "il", "rtl": true},
    {"id": "global-hindi"  , "name": "हिन्दी" , "country": "in"},
    {"id": "global-nepali" , "name": "नेपाली", "country": "np"},
    {"id": "global-bangla"  , "name": "বাংলা" , "country": "bd"},
    {"id": "global-lithuanian", "name": "Lietuvių", "country": "lt"},
    {"id": "global-hungarian"  , "name": "Magyar" , "country": "h"},
    {"id": "global-dutch"  , "name": "Nederlands" , "country": "nl"},
    {"id": "global-norwegian"  , "name": "Norsk" , "country": "no"},
    {"id": "global-italian"  , "name": "Italiano" , "country": "it"},
    {"id": "global-portuguese"  , "name": "Português" , "country": "pt"},
    {"id": "global-romanian"  , "name": "Română" , "country": "ro"},
    {"id": "global-swedish"  , "name": "Svenska" , "country": "se"},
    {"id": "global-finnish"  , "name": "Suomi" , "country": "fi"},
    {"id": "global-turkish"  , "name": "Türkçe" , "country": "tr"},
    {"id": "global-ukrainian"  , "name": "Українська" , "country": "ua"},
    {"id": "global-vietnamese"  , "name": "Ti<NAME>" , "country": "vn"},
    {"id": "global-thai"  , "name": "ภาษาไทย" , "country": "th"},
]; /* }}} */

data.watch("config.ogs", (settings) => {
    if (settings && settings.channels) {
        global_channels = settings.channels;
    }
});


let rtl_mode = {};
for (let chan of global_channels) {
    rtl_mode[chan.id] = !!chan.rtl;
}

let channels = { };

function getChannel(channel) { /* {{{ */
    if (!(channel in channels)) {
        channels[channel] = {
            chat_log: [],
            chat_ids: {},
            unread_ct: 0,
            mentioned: false,
            user_list: {},
            user_count: 0,
            rtl_mode: rtl_mode[channel],
        };
    }
    return channels[channel];
}; /* }}} */

export class EmbeddedChat extends React.PureComponent<ChatProperties, any> {
    render() {
        return <Card className="Card EmbeddedChat"><Chat {...this.props} /></Card>;
    }
}

export class Chat extends React.Component<ChatProperties, any> {
    refs: {
        input;
        chat_log;
    };

    received_messages = {};
    online_count_interval = null;
    joining_channel = {};
    channel_names = {};
    defered_state_update = null;
    send_tokens = 5;
    scrolled_to_bottom: boolean = true;
    flood_protection = null;
    unread_ct: number = 0;

    seekgraph: SeekGraph;
    seekgraph_canvas: any;



    constructor(props) {
        super(props);
        this.state = {
            online_count: 0,
            chat_log: [],
            user_list: [],
            joined_channels: this.props.channel ? [this.props.channel] : data.get("chat.joined"),
            active_channel: this.props.channel ? this.props.channel : data.get("chat.active_channel", "global-english"),
            group_channels: [],
            tournament_channels: [],
            show_all_channels: preferences.get("chat.show-all-channels"),
            user_sort_order: preferences.get("chat.user-sort-order"),
            force_show_channels: false,
            force_show_users: false,
            show_say_hi_placeholder: true,
        };

        this.resolve();
        this.seekgraph_canvas = $("<canvas class='SeekGraph'>")[0];
    }

    resolve() {{{
        if (!data.get("config.user").anonymous) {
            get("me/groups", {page_size: 30})
            .then((groups) => this.setState({group_channels: groups.results.sort((a, b) => a.name.localeCompare(b.name))}))
            .catch((err) => 0);

            get("me/tournaments", {ended__isnull:true, page_size: 30})
            .then((tournaments) => this.setState({tournament_channels: tournaments.results.sort((a, b) => a.name.localeCompare(b.name))}))
            .catch((err) => 0);
        }
    }}}

    componentDidMount() {{{
        comm_socket.on("connect", this.connect);
        comm_socket.on("disconnect", this.disconnect);
        if (comm_socket.connected) {
            this.connect();
        }

        if (this.props.autofocus) {
            $(this.refs.input.refs.input).focus();
        }
        this.autoscroll();
        $(window).on("focus", this.onDocumentFocus);


        this.seekgraph = new SeekGraph({
            canvas: this.seekgraph_canvas,
            show_live_games: true,
        });
        this.seekgraph.resize($(this.seekgraph_canvas).width(), $(this.seekgraph_canvas).height());
    }}}
    componentDidUpdate() {{{
        this.autoscroll();
    }}}
    componentWillUnmount() {{{
        this.disconnect();
        comm_socket.off("connect", this.connect);
        comm_socket.off("disconnect", this.disconnect);
        $(window).off("focus", this.onDocumentFocus);
        if (this.props.updateTitle) {
            window.document.title = "OGS";
        }
        this.seekgraph.destroy();
    }}}

    connect = () => {{{
        channels = {};
        comm_socket.on("chat-message", this.onChatMessage);
        comm_socket.on("chat-join", this.onChatJoin);
        comm_socket.on("chat-part", this.onChatPart);
        this.online_count_interval = setInterval(() => {
            comm_socket.send("getOnlineCount", {interval: 1800}, (ct) => this.setState({online_count: ct}));
        }, 30000);
        comm_socket.send("getOnlineCount", {interval: 1800}, (ct) => this.setState({online_count: ct}));

        let joined = data.get("chat.joined");<commit_msg>Handle delays in seek graph canvas creation on the Chat page<commit_after>/*
 * Copyright (C) 2012-2017  Online-Go.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import * as React from "react";
import {Link} from "react-router";
import {_, interpolate} from "translate";
import {post, get} from "requests";
import {Player} from "Player";
import {profanity_filter} from "profanity_filter";
import {comm_socket} from "sockets";
import data from "data";
import preferences from "preferences";
import {emitNotification} from "Notifications";
import {Flag} from "Flag";
import {Card} from "components";
import {TabCompleteInput} from "TabCompleteInput";
import player_cache from "player_cache";
import {string_splitter, n2s, dup} from "misc";
import {SeekGraph} from "SeekGraph";
import {PersistentElement} from "PersistentElement";

declare let swal;


data.setDefault("chat.joined", {});

interface ChatProperties {
    channel?: string;
    autofocus?: boolean;
    showChannels?: boolean;
    showUserList?: boolean;
    updateTitle: boolean;
}

let name_match_regex = /^loading...$/;
data.watch("config.user", (user) => {
    name_match_regex = new RegExp("\b" + user.username.replace(/[\\^$*+.()|[\]{}]/g, "\$&") + "\b", "i");
});

let global_channels: Array<any> = [ /* {{{ */
    {"id": "global-english" , "name": "English", "country": "us"},
    {"id": "global-help" , "name": "Help", "country": "un"},
    {"id": "global-offtopic" , "name": "Off Topic", "country": "un"},
    {"id": "global-japanese", "name": "日本語 ", "country": "jp"},
    {"id": "global-chinese" , "name": "中文"   , "country": "cn"},
    {"id": "global-korean"  , "name": "한국어" , "country": "kr"},
    {"id": "global-russian"  , "name": "Русский" , "country": "r"},
    {"id": "global-polish"  , "name": "Polski" , "country": "pl"},
    {"id": "global-arabic", "name": "العَرَبِيَّةُ", "country": "_Arab_League", "rtl": true},
    {"id": "global-bulgarian"  , "name": "Български" , "country": "bg"},
    {"id": "global-catalan"  , "name": "Català" , "country": "es"},
    {"id": "global-czech"  , "name": "Čeština" , "country": "cz"},
    {"id": "global-esperanto"  , "name": "Esperanto" , "country": "_Esperanto"},
    {"id": "global-german"  , "name": "Deutsch" , "country": "de"},
    {"id": "global-spanish"  , "name": "Español" , "country": "es"},
    {"id": "global-french"  , "name": "Français" , "country": "fr"},
    {"id": "global-filipino"  , "name": "Filipino" , "country": "ph"},
    {"id": "global-indonesian", "name": "Indonesian", "country": "id"},
    {"id": "global-hebrew", "name": "עִבְרִית", "country": "il", "rtl": true},
    {"id": "global-hindi"  , "name": "हिन्दी" , "country": "in"},
    {"id": "global-nepali" , "name": "नेपाली", "country": "np"},
    {"id": "global-bangla"  , "name": "বাংলা" , "country": "bd"},
    {"id": "global-lithuanian", "name": "Lietuvių", "country": "lt"},
    {"id": "global-hungarian"  , "name": "Magyar" , "country": "h"},
    {"id": "global-dutch"  , "name": "Nederlands" , "country": "nl"},
    {"id": "global-norwegian"  , "name": "Norsk" , "country": "no"},
    {"id": "global-italian"  , "name": "Italiano" , "country": "it"},
    {"id": "global-portuguese"  , "name": "Português" , "country": "pt"},
    {"id": "global-romanian"  , "name": "Română" , "country": "ro"},
    {"id": "global-swedish"  , "name": "Svenska" , "country": "se"},
    {"id": "global-finnish"  , "name": "Suomi" , "country": "fi"},
    {"id": "global-turkish"  , "name": "Türkçe" , "country": "tr"},
    {"id": "global-ukrainian"  , "name": "Українська" , "country": "ua"},
    {"id": "global-vietnamese"  , "name": "<NAME>" , "country": "vn"},
    {"id": "global-thai"  , "name": "ภาษาไทย" , "country": "th"},
]; /* }}} */

data.watch("config.ogs", (settings) => {
    if (settings && settings.channels) {
        global_channels = settings.channels;
    }
});


let rtl_mode = {};
for (let chan of global_channels) {
    rtl_mode[chan.id] = !!chan.rtl;
}

let channels = { };

function getChannel(channel) { /* {{{ */
    if (!(channel in channels)) {
        channels[channel] = {
            chat_log: [],
            chat_ids: {},
            unread_ct: 0,
            mentioned: false,
            user_list: {},
            user_count: 0,
            rtl_mode: rtl_mode[channel],
        };
    }
    return channels[channel];
}; /* }}} */

export class EmbeddedChat extends React.PureComponent<ChatProperties, any> {
    render() {
        return <Card className="Card EmbeddedChat"><Chat {...this.props} /></Card>;
    }
}

export class Chat extends React.Component<ChatProperties, any> {
    refs: {
        input;
        chat_log;
    };

    received_messages = {};
    online_count_interval = null;
    joining_channel = {};
    channel_names = {};
    defered_state_update = null;
    send_tokens = 5;
    scrolled_to_bottom: boolean = true;
    flood_protection = null;
    unread_ct: number = 0;

    seekgraph: SeekGraph;
    seekgraph_canvas: any;



    constructor(props) {
        super(props);
        this.state = {
            online_count: 0,
            chat_log: [],
            user_list: [],
            joined_channels: this.props.channel ? [this.props.channel] : data.get("chat.joined"),
            active_channel: this.props.channel ? this.props.channel : data.get("chat.active_channel", "global-english"),
            group_channels: [],
            tournament_channels: [],
            show_all_channels: preferences.get("chat.show-all-channels"),
            user_sort_order: preferences.get("chat.user-sort-order"),
            force_show_channels: false,
            force_show_users: false,
            show_say_hi_placeholder: true,
        };

        this.resolve();
        this.seekgraph_canvas = $("<canvas class='SeekGraph'>")[0];
    }

    resolve() {{{
        if (!data.get("config.user").anonymous) {
            get("me/groups", {page_size: 30})
            .then((groups) => this.setState({group_channels: groups.results.sort((a, b) => a.name.localeCompare(b.name))}))
            .catch((err) => 0);

            get("me/tournaments", {ended__isnull:true, page_size: 30})
            .then((tournaments) => this.setState({tournament_channels: tournaments.results.sort((a, b) => a.name.localeCompare(b.name))}))
            .catch((err) => 0);
        }
    }}}

    componentDidMount() {{{
        comm_socket.on("connect", this.connect);
        comm_socket.on("disconnect", this.disconnect);
        if (comm_socket.connected) {
            this.connect();
        }

        if (this.props.autofocus) {
            $(this.refs.input.refs.input).focus();
        }
        this.autoscroll();
        $(window).on("focus", this.onDocumentFocus);


        this.seekgraph = new SeekGraph({
            canvas: this.seekgraph_canvas,
            show_live_games: true,
        });

        let I;
        let resizeSeekgraph = () => {
            if ($(this.seekgraph_canvas).width() > 0) {
                this.seekgraph.resize($(this.seekgraph_canvas).width(), $(this.seekgraph_canvas).height());
                clearInterval(I);
            }
        };
        I = setInterval(resizeSeekgraph);
        resizeSeekgraph();
    }}}
    componentDidUpdate() {{{
        this.autoscroll();
    }}}
    componentWillUnmount() {{{
        this.disconnect();
        comm_socket.off("connect", this.connect);
        comm_socket.off("disconnect", this.disconnect);
        $(window).off("focus", this.onDocumentFocus);
        if (this.props.updateTitle) {
            window.document.title = "OGS";
        }
        this.seekgraph.destroy();
    }}}

    connect = () => {{{
        channels = {};
        comm_socket.on("chat-message", this.onChatMessage);
        comm_socket.on("chat-join", this.onChatJoin);
        comm_socket.on("chat-part", this.onChatPart);
        this.online_count_interval = setInterval(() => {
            comm_socket.send("getOnlineCount", {interval: 1800}, (ct) => this.setState({online_count: ct}));
        }, 30000);
        comm_socket.send("getOnlineCount", {interval: 1800}, (ct) => this.setState({online_count: ct}));

        let joined = data.get("chat.joined");
{'fraction_non_alphanumeric': 0.19426622738570024, 'fraction_numerical': 0.004103809028379863, 'mean_word_length': 3.0576923076923075, 'pattern_counts': {'":': 214, '<': 21, '<?xml version=': 0, '>': 47, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '3685346', 'n_tokens_mistral': 5837, 'n_tokens_neox': 5713, 'n_words': 1270}