Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have the ability to add comments now #37

Merged
merged 12 commits into from
Aug 10, 2020
61 changes: 36 additions & 25 deletions src/lib/Network.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ function request(command, data, type = 'post') {
console.info('[API] Error', responseData);
})
// eslint-disable-next-line no-unused-vars
.catch(() => isAppOffline = true);
.catch(() => {
isAppOffline = true;

// Throw a new error to prevent any other `then()` in the promise chain from being triggered (until another
// catch() happens
throw new Error('API is offline');
});
}

// Holds a queue of all the write requests that need to happen
Expand All @@ -61,30 +67,35 @@ function delayedWrite(command, data) {
/**
* Process the write queue by looping through the queue and attempting to make the requests
*/
// function processWriteQueue() {
// if (isAppOffline) {
// // Make a simple request to see if we're online again
// request('Get', null, 'get')
// .then(() => isAppOffline = false);
// return;
// }
//
// if (delayedWriteQueue.length === 0) {
// return;
// }
//
// _.each(delayedWriteQueue, (delayedWriteRequest) => {
// request(delayedWriteRequest.command, delayedWriteRequest.data)
// .then(delayedWriteRequest.callback)
// .catch(() => {
// // If the request failed, we need to put the request object back into the queue
// delayedWriteQueue.push(delayedWriteRequest);
// });
// });
// }
function processWriteQueue() {
if (isAppOffline) {
// Make a simple request to see if we're online again
request('Get', null)
.then(() => isAppOffline = false);
return;
}

if (delayedWriteQueue.length === 0) {
return;
}

for (let i = 0; i < delayedWriteQueue.length; i++) {
// Take the request object out of the queue and make the request
const delayedWriteRequest = delayedWriteQueue.shift();

request(delayedWriteRequest.command, delayedWriteRequest.data)
.then(delayedWriteRequest.callback)
.catch(() => {
// If the request failed, we need to put the request object back into the queue
delayedWriteQueue.push(delayedWriteRequest);
});
}
}

// TODO: Figure out setInterval
// Process our write queue very often
// setInterval(processWriteQueue, 1000);
setInterval(processWriteQueue, 1000);

export {request, delayedWrite};
export {
request,
delayedWrite,
};
33 changes: 23 additions & 10 deletions src/lib/Pusher/pusher.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ function init(appKey, params) {
});

// If we want to pass params in our requests to api.php we'll need to add it to socket.config.auth.params
// as per the documentation (https://pusher.com/docs/channels/using_channels/connection#channels-options-parameter).
// as per the documentation
// (https://pusher.com/docs/channels/using_channels/connection#channels-options-parameter).
// Any param mentioned here will show up in $_REQUEST when we call "Push_Authenticate". Params passed here need
// to pass our inputRules to show up in the request.
if (params) {
Expand Down Expand Up @@ -92,8 +93,9 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}, isChun
return;
}

// If we are chunking the requests, we need to construct a rolling list of all packets that have come through Pusher.
// If we've completed one of these full packets, we'll combine the data and act on the event that it's assigned to.
// If we are chunking the requests, we need to construct a rolling list of all packets that have come through
// Pusher. If we've completed one of these full packets, we'll combine the data and act on the event that it's
// assigned to.

// If we haven't seen this eventID yet, initialize it into our rolling list of packets.
if (!chunkedDataEvents[eventData.id]) {
Expand All @@ -109,13 +111,17 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}, isChun
chunkedEvent.receivedFinal = true;
}

// Only call the event callback if we've received the last packet and we don't have any holes in the complete packet.
// Only call the event callback if we've received the last packet and we don't have any holes in the complete
// packet.
if (chunkedEvent.receivedFinal && chunkedEvent.chunks.length === Object.keys(chunkedEvent.chunks).length) {
eventCallback(JSON.parse(chunkedEvent.chunks.join('')));
try {
eventCallback(JSON.parse(chunkedEvent.chunks.join('')));
} catch (err) {
console.error('[Pusher] Unable to parse chunked JSON response from Pusher', 0, {error: err, eventData: chunkedEvent.chunks.join('')});
console.error('[Pusher] Unable to parse chunked JSON response from Pusher', 0, {
error: err,
eventData: chunkedEvent.chunks.join('')
});
}

delete chunkedDataEvents[eventData.id];
Expand All @@ -131,7 +137,8 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}, isChun
* @param {String} channelName
* @param {String} eventName
* @param {Function} [eventCallback]
* @param {Boolean} [isChunked] This parameters tells us whether or not we expect the result to come in individual pieces/chunks (because it exceeds
* @param {Boolean} [isChunked] This parameters tells us whether or not we expect the result to come in individual
* pieces/chunks (because it exceeds
* the 10kB limit that pusher has).
*
* @return {Promise}
Expand All @@ -142,7 +149,8 @@ function subscribe(channelName, eventName, eventCallback = () => {}, isChunked =
return new Promise((resolve, reject) => {
// We cannot call subscribe() before init(). Prevent any attempt to do this on dev.
if (!socket) {
throw new Error('[Pusher] instance not found. Pusher.subscribe() most likely has been called before Pusher.init()');
throw new Error(`[Pusher] instance not found. Pusher.subscribe()
most likely has been called before Pusher.init()`);
}

console.debug('[Pusher] Attempting to subscribe to channel', true, {channelName, eventName});
Expand All @@ -157,7 +165,10 @@ function subscribe(channelName, eventName, eventCallback = () => {}, isChunked =

channel.bind('pusher:subscription_error', (status) => {
if (status === 403) {
console.debug('[Pusher] Issue authenticating with Pusher during subscribe attempt.', 0, {channelName, status});
console.debug('[Pusher] Issue authenticating with Pusher during subscribe attempt.', 0, {
channelName,
status
});
}

reject(status);
Expand Down Expand Up @@ -200,7 +211,8 @@ function unsubscribe(channelName, eventName = '') {
const channel = getChannel(channelName);

if (!channel) {
console.debug('[Pusher] Attempted to unsubscribe or unbind from a channel, but Pusher-JS has no knowledge of it', 0, {channelName, eventName});
console.debug(`[Pusher] Attempted to unsubscribe or unbind from a channel,
but Pusher-JS has no knowledge of it`, 0, {channelName, eventName});
return;
}

Expand All @@ -210,7 +222,8 @@ function unsubscribe(channelName, eventName = '') {
} else {
if (!channel.subscribed) {
// eslint-disable-next-line no-console
console.warn('[Pusher] Attempted to unsubscribe from channel, but we are not subscribed to begin with', 0, {channelName});
console.warn(`[Pusher] Attempted to unsubscribe from channel,
but we are not subscribed to begin with`, 0, {channelName});
return;
}

Expand Down
102 changes: 102 additions & 0 deletions src/page/HomePage/Report/ReportHistoryCompose.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React from 'react';
import PropTypes from 'prop-types';
import {View, TextInput, Button} from 'react-native';
import styles from '../../../style/StyleSheet';

const propTypes = {
// A method to call when the form is submitted
onSubmit: PropTypes.func.isRequired,
};

class ReportHistoryCompose extends React.Component {
constructor(props) {
super(props);

this.updateComment = this.updateComment.bind(this);
this.submitForm = this.submitForm.bind(this);
this.triggerSubmitShortcut = this.triggerSubmitShortcut.bind(this);

this.state = {
comment: '',
};
}

componentDidMount() {
this.textInput.focus();
}

componentDidUpdate() {
this.textInput.focus();
}

/**
* Update the value of the comment input in the state
*
* @param {string} newComment
*/
updateComment(newComment) {
this.setState({
comment: newComment,
});
}

/**
* Listens for the keyboard shortcut and submits
* the form when we have enter
*
* @param {Object} e
*/
triggerSubmitShortcut(e) {
if (e && e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.submitForm();
}
}

/**
* Add a new comment to this chat
*
* @param {SyntheticEvent} [e]
*/
submitForm(e) {
if (e) {
e.preventDefault();
}

// Don't submit empty commentes
// @TODO show an error in the UI
if (!this.state.comment) {
return;
}

this.props.onSubmit(this.state.comment);
this.setState({
comment: '',
});
}

render() {
return (
<View style={[styles.mt2]}>
<TextInput
ref={el => this.textInput = el}
multiline
textAlignVertical
numberOfLines={3}
minHeight={60}
maxHeight={60}
onChangeText={this.updateComment}
onKeyPress={this.triggerSubmitShortcut}
style={[styles.textInput]}
value={this.state.comment}
/>
<View style={[styles.mt1, styles.flexRow]}>
<Button title="Send" onPress={this.submitForm} />
</View>
</View>
);
}
}
ReportHistoryCompose.propTypes = propTypes;

export default ReportHistoryCompose;
4 changes: 2 additions & 2 deletions src/page/HomePage/Report/ReportHistoryItem.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {Text, View} from 'react-native';
import {ActivityIndicator, View} from 'react-native';
import PropTypes from 'prop-types';
import ReportHistoryItemSingle from './ReportHistoryItemSingle';
import ReportHistoryPropsTypes from './ReportHistoryPropsTypes';
Expand All @@ -21,7 +21,7 @@ class ReportHistoryItem extends React.Component {
<View>
{!this.props.displayAsGroup && <ReportHistoryItemSingle historyItem={this.props.historyItem} />}
{this.props.displayAsGroup && <ReportHistoryItemGrouped historyItem={this.props.historyItem} />}
{this.props.historyItem.tempGuid && <Text>pending...</Text>}
{this.props.historyItem.tempGuid && <ActivityIndicator type="small" color="#7d8b8f" />}
</View>
);
}
Expand Down
18 changes: 11 additions & 7 deletions src/page/HomePage/Report/ReportHistoryView.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {Text, FlatList} from 'react-native';
import {Text, VirtualizedList} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import lodashGet from 'lodash.get';
Expand All @@ -18,6 +18,10 @@ const propTypes = {
};

class ReportHistoryView extends React.Component {
constructor(props) {
super(props);
}

componentDidMount() {
this.bindToStore();
}
Expand Down Expand Up @@ -54,10 +58,6 @@ class ReportHistoryView extends React.Component {
loaderParams: [this.props.reportID],
}
}, this);

if (this.reportHistoryList) {
this.reportHistoryList.scrollToEnd();
}
}

/**
Expand Down Expand Up @@ -97,9 +97,13 @@ class ReportHistoryView extends React.Component {
}

return (
<FlatList
<VirtualizedList
ref={el => this.reportHistoryList = el}
data={filteredHistory}
data={filteredHistory.reverse()}
getItemCount={() => filteredHistory.length}
getItem={(data, index) => filteredHistory[index]}
initialNumToRender="10"
inverted
renderItem={({index, item}) => (
<ReportHistoryItem
historyItem={item}
Expand Down
3 changes: 3 additions & 0 deletions src/page/HomePage/Report/ReportView.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import WithStore from '../../../components/WithStore';
import STOREKEYS from '../../../store/STOREKEYS';
import styles from '../../../style/StyleSheet';
import ReportHistoryView from './ReportHistoryView';
import ReportHistoryCompose from './ReportHistoryCompose';
import {addHistoryItem} from '../../../store/actions/ReportActions';

const propTypes = {
// These are from WithStore
Expand Down Expand Up @@ -58,6 +60,7 @@ class ReportView extends React.Component {
<View style={styles.flex1}>
<Route path="/:reportID" exact>
<ReportHistoryView reportID={this.props.match.params.reportID} />
<ReportHistoryCompose onSubmit={text => addHistoryItem(this.props.match.params.reportID, text)} />
</Route>
</View>
);
Expand Down
Loading