<commit_before>import './Thread.styles'
import React, { Component } from "react";

import classes from 'classnames';
import uuid from "uuid";

import ThreadPost from './ThreadPost';

import {
    Overlay,
    Spinner,
    TimeAgo
} from '~/components';

import setupThreadEvents from './events'

import {
    bindMembersToClass
} from '~/utils'

import threadConnect from './ThreadHOC'

class Thread extends Component {
    constructor(props) {
        super(props);

        bindMembersToClass(this,
            'openThread',
            'closeThread'
        );

        this.state = {
            headerHeight: window.appSettings.headerHeight
        }
    }

    componentDidMount() {
        console.log("Thread mounted");
        setupThreadEvents(this._thread)
        this._threadWrap && this._threadWrap.nanoScroller({
            sliderMinHeight: 40,
            alwaysVisible: true
        })
    }

    componentDidUpdate({ posts } ) {
        if (!posts.length && this.props.posts.length) {
            this.openThread()
        }
    }

    // shouldComponentUpdate({thread}, nextState) {
    //     return thread.isThreadOpen !== this.props.thread.isThreadOpen ||
    //            this.props.thread.posts.length !== thread.posts.length
    // }

    componentWillUnmount() {
        console.log("Thread will unmount");
    }

    render() {
        const { isDrawerOpen, isCommentPanelOpen, isThreadOpen, didInvalidate, posts } = this.props
        const threadWrapClasses = classes('wrapper', 'nano', {
            "make-visible": isThreadOpen,
            "center-left": isDrawerOpen || isCommentPanelOpen,
            "double-center-left": isDrawerOpen && isCommentPanelOpen
        });

        const threadClasses = classes('content', 'nano-content', {
            "is-active": isThreadOpen
        });

        if (didInvalidate)
            return null

        return (
            <div ref={t => this._threadWrap = $(t)} className={threadWrapClasses}>
                {this.props.children}
                <div id="thread" className={threadClasses} ref={t => this._thread = $(t)} onClick={this.closeThread}>
                    {posts.length && this.createPosts( posts )}
                </div>
            </div>
        )
    }
    // <div className="header-gap"></div>

    createPosts( posts ) {
        return posts.map(
            post => <ThreadPost
                key={post.id}
                post={post}>
                <TimeAgo date={post.time}/>
            </ThreadPost>
        )
    }

    openThread() {
        // Must have separate invocations
        this._threadWrap.nanoScroller({ scroll: "top" })
        this._threadWrap.nanoScroller({ stop: true })

        this._thread.velocity({top: this.state.headerHeight}, {
            duration: 850,
            easing: [0.25, 0.8, 0.25, 1],
            complete: () => this._threadWrap.nanoScroller()
        });
    }

    closeThread() {
        const { closeThread, threadID, scrollHeader } = this.props;

        this._threadWrap.nanoScroller({ stop: true })
        closeThread()
        scrollHeader(true)
    }
}

export default threadConnect(Thread)<commit_msg>refactor(Thread): Move open/close animation inside component + use events<commit_after>import './Thread.styles'
import React, { Component } from "react";
import cx from 'classnames';
import uuid from "uuid";

import ThreadPost from './ThreadPost';

import {
    Overlay,
    Spinner,
    TimeAgo
} from '~/components';

import setupThreadEvents from './events';
import {
    onDrawerToggle,
    onThreadOpen, onThreadClose
} from '~/events/subscribers';
import {bindMembersToClass, isFunction} from '~/utils'

const settings = window.appSettings;

class Thread extends Component {
    constructor(props) {
        super(props);

        bindMembersToClass(this,
            'openThread',
            'closeThread'
        );

        this.state = {
            isDrawerOpen: props.isDrawerOpen,
            isOpen: props.isThreadOpen,
            isOpening: false,
            isClosing: false,
        }
    }

    @onDrawerToggle
    onDrawerToggle(isDrawerOpen) {
        this.setState({isDrawerOpen});
    }

    @onThreadOpen
    onThreadOpen() {
        this.openThread();
    }

    @onThreadClose
    onThreadClose() {
        this.closeThread();

    }

    componentDidMount() {
        console.log("Thread mounted");
        setupThreadEvents(this._thread)

        // Creates the initial scroller
        this.updateScroller({
            sliderMinHeight: 40,
            alwaysVisible: true
        });
    }

    componentDidUpdate({ posts } ) {
        if (!posts.length && this.props.posts.length) {
            this.openThread();
        }
    }

    // shouldComponentUpdate({thread}, nextState) {
    //     return thread.isThreadOpen !== this.props.thread.isThreadOpen ||
    //            this.props.thread.posts.length !== thread.posts.length
    // }

    componentWillUnmount() {
        console.log("Thread will unmount");
    }

    render() {
        const { isFetching, didInvalidate, posts, className } = this.props;
        const { isOpen, isOpening, isClosing, isDrawerOpen } = this.state;

        console.info(`Thread updated: isOpen: ${isOpen}, isOpening: ${isOpening}, isClosing: ${isClosing}`);
        const threadWrapClasses = cx('wrapper', 'nano', {
            "center-left": isDrawerOpen,
            "make-visible": isOpening || isOpen,
            "fadein-boxshadow": isOpen && !isClosing && !isOpening
        });

        if (didInvalidate)
            return null

        console.warn("posts:",posts.length)
        return (
            <div className={cx("Thread", className)}>
                <Overlay onClick={this.closeThread} isVisible={!didInvalidate && (isFetching || isOpening || isOpen)}>
                    <Spinner isSpinning={!posts.length && (isFetching || isOpening)}/>
                </Overlay>

                <div ref={ref => this._threadWrap = ref} className={threadWrapClasses}>
                    <div id="thread" className='content nano-content' ref={ref => this._thread = ref} onClick={this.closeThread}>
                        {posts.length && posts.map(
                            post => <ThreadPost
                                key={post.id}
                                post={post}>
                                <TimeAgo date={post.time}/>
                            </ThreadPost>
                        )}
                    </div>
                </div>
            </div>
        )
    }
    // <div className="header-gap"></div>

    openThread(callback) {
        this.setState({ isOpening: true }, () => {


        // Must have separate invocations
        this.updateScroller({ scroll: "top" });
        this.updateScroller({ stop: true });

        // TODO: Change from global appSettings into redux settingrs
        this.animateThread({top: window.appSettings.headerHeight}, {
            duration: 850,  // this too
            easing: [0.25, 0.8, 0.25, 1],
            complete: () => {
                this.updateScroller();
                this.setState({
                    isOpen: true,
                    isOpening: false
                });
                isFunction(callback) && callback();
            }
        });
        });
    }

    closeThread(callback) {
        this.setState({
            isClosing: true
        })
        // Close scroller otherwise thread slides down while it remains
        this.updateScroller({ stop: true });

        // TODO: Is the header going to be animated in-out?
        // scrollHeader(true)

        this.animateThread({top: window.innerHeight+"px"}, {
            duration: 150,
            complete: () => {
                // TODO: get these from this.props:
                // dispatch(saveThreadToHistory(state))
                // dispatch(destroyThread(threadID))
                isFunction(callback) && callback();
                this.setState({
                    isOpen: false,
                    isClosing: false
                })
            }
        });
    }

    updateScroller(args) {
        this._threadWrap && $(this._threadWrap).nanoScroller(args);
    }

    animateThread(styles, options) {
        this._thread && $(this._thread).velocity(styles, options);
    }
}

export default Thread
{'fraction_non_alphanumeric': 0.12378553436487945, 'fraction_numerical': 0.003718363919875255, 'mean_word_length': 1.820703653585927, 'pattern_counts': {'":': 7, '<': 26, '<?xml version=': 0, '>': 36, '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': '6175541', 'n_tokens_mistral': 2320, 'n_tokens_neox': 2213, 'n_words': 459}