1import React, { Component } from 'react'
2import PropTypes from 'prop-types'
3import { MessageUtils } from '../../flow/utils.js'
4
5export default function withContentLoader(View) {
6
7    return class extends React.Component {
8        static displayName = View.displayName || View.name
9        static matches = View.matches
10
11        static propTypes = {
12            ...View.propTypes,
13            content: PropTypes.string,  // mark as non-required
14            flow: PropTypes.object.isRequired,
15            message: PropTypes.object.isRequired,
16        }
17
18        constructor(props) {
19            super(props)
20            this.state = {
21                content: undefined,
22                request: undefined,
23            }
24        }
25
26        componentWillMount() {
27            this.updateContent(this.props)
28        }
29
30        componentWillReceiveProps(nextProps) {
31            if (
32                nextProps.message.content !== this.props.message.content ||
33                nextProps.message.contentHash !== this.props.message.contentHash ||
34                nextProps.contentView !== this.props.contentView
35            ) {
36                this.updateContent(nextProps)
37            }
38        }
39
40        componentWillUnmount() {
41            if (this.state.request) {
42                this.state.request.abort()
43            }
44        }
45
46        updateContent(props) {
47            if (this.state.request) {
48                this.state.request.abort()
49            }
50            // We have a few special cases where we do not need to make an HTTP request.
51            if (props.message.content !== undefined) {
52                return this.setState({request: undefined, content: props.message.content})
53            }
54            if (props.message.contentLength === 0 || props.message.contentLength === null) {
55                return this.setState({request: undefined, content: ""})
56            }
57
58            let requestUrl = MessageUtils.getContentURL(props.flow, props.message, props.contentView)
59
60            // We use XMLHttpRequest instead of fetch() because fetch() is not (yet) abortable.
61            let request = new XMLHttpRequest();
62            request.addEventListener("load", this.requestComplete.bind(this, request));
63            request.addEventListener("error", this.requestFailed.bind(this, request));
64            request.open("GET", requestUrl);
65            request.send();
66            this.setState({request, content: undefined})
67        }
68
69        requestComplete(request, e) {
70            if (request !== this.state.request) {
71                return // Stale request
72            }
73            this.setState({
74                content: request.responseText,
75                request: undefined
76            })
77        }
78
79        requestFailed(request, e) {
80            if (request !== this.state.request) {
81                return // Stale request
82            }
83            console.error(e)
84            // FIXME: Better error handling
85            this.setState({
86                content: "Error getting content.",
87                request: undefined
88            })
89        }
90
91        render() {
92            return this.state.content !== undefined ? (
93                <View content={this.state.content} {...this.props}/>
94            ) : (
95                <div className="text-center">
96                    <i className="fa fa-spinner fa-spin"></i>
97                </div>
98            )
99        }
100    }
101};
102