Newer
Older
import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';
import { connect } from 'react-redux';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import { withTheme } from '../theme';
import { themedHeader } from '../utils/navigation';
import EventEmitter from '../utils/events';
import { themes } from '../constants/colors';
import { CustomHeaderButtons, Item } from '../containers/HeaderButton';
import { modalBlockWithContext } from '../containers/UIKit/MessageBlock';
import RocketChat from '../lib/rocketchat';
import ActivityIndicator from '../containers/ActivityIndicator';
import { MODAL_ACTIONS, CONTAINER_TYPES } from '../lib/methods/actions';
import sharedStyles from './Styles';
import { textParser } from '../containers/UIKit/utils';
import Navigation from '../lib/Navigation';
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 16
},
content: {
paddingVertical: 16
},
submit: {
...sharedStyles.textSemibold,
fontSize: 16
}
});
Object.fromEntries = Object.fromEntries || (arr => arr.reduce((acc, [k, v]) => ((acc[k] = v, acc)), {}));
const groupStateByBlockIdMap = (obj, [key, { blockId, value }]) => {
obj[blockId] = obj[blockId] || {};
obj[blockId][key] = value;
return obj;
};
const groupStateByBlockId = obj => Object.entries(obj).reduce(groupStateByBlockIdMap, {});
const filterInputFields = ({ element, elements = [] }) => {
if (element && element.initialValue) {
return true;
}
if (elements.length && elements.map(e => ({ element: e })).filter(filterInputFields).length) {
return true;
}
};
const mapElementToState = ({ element, blockId, elements = [] }) => {
if (elements.length) {
return elements.map(e => ({ element: e, blockId })).filter(filterInputFields).map(mapElementToState);
}
return [element.actionId, { value: element.initialValue, blockId }];
};
const reduceState = (obj, el) => (Array.isArray(el[0]) ? { ...obj, ...Object.fromEntries(el) } : { ...obj, [el[0]]: el[1] });
class ModalBlockView extends React.Component {
static navigationOptions = ({ navigation, screenProps }) => {
const { theme, closeModal } = screenProps;
const data = navigation.getParam('data');
const cancel = navigation.getParam('cancel', () => {});
const submitting = navigation.getParam('submitting', false);
const { view } = data;
const { title, submit, close } = view;
return {
title: textParser([title]),
...themedHeader(theme),
headerLeft: (
<CustomHeaderButtons>
<Item
title={textParser([close.text])}
style={styles.submit}
onPress={!submitting && (() => cancel({ closeModal }))}
testID='close-modal-uikit'
/>
</CustomHeaderButtons>
),
headerRight: (
<CustomHeaderButtons>
<Item
title={textParser([submit.text])}
style={styles.submit}
onPress={!submitting && (navigation.getParam('submit', () => {}))}
testID='submit-modal-uikit'
/>
</CustomHeaderButtons>
)
};
}
static propTypes = {
navigation: PropTypes.object,
theme: PropTypes.string,
language: PropTypes.string,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string
})
}
constructor(props) {
super(props);
this.submitting = false;
const { navigation } = props;
const data = navigation.getParam('data');
this.values = data.view.blocks.filter(filterInputFields).map(mapElementToState).reduce(reduceState, {});
this.state = {
data,
loading: false
};
}
componentDidMount() {
const { data } = this.state;
const { navigation } = this.props;
const { viewId } = data;
navigation.setParams({ submit: this.submit, cancel: this.cancel });
EventEmitter.addEventListener(viewId, this.handleUpdate);
}
shouldComponentUpdate(nextProps, nextState) {
if (!isEqual(nextProps, this.props)) {
return true;
}
if (!isEqual(nextState, this.state)) {
return true;
}
return false;
}
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
componentDidUpdate(prevProps) {
const { navigation } = this.props;
const oldData = prevProps.navigation.getParam('data', {});
const newData = navigation.getParam('data', {});
if (!isEqual(oldData, newData)) {
navigation.push('ModalBlockView', { data: newData });
}
}
componentWillUnmount() {
const { data } = this.state;
const { viewId } = data;
EventEmitter.removeListener(viewId, this.handleUpdate);
}
handleUpdate = ({ type, ...data }) => {
if ([MODAL_ACTIONS.ERRORS].includes(type)) {
const { errors } = data;
this.setState({ errors });
} else {
this.setState({ data });
}
};
cancel = async({ closeModal }) => {
const { data } = this.state;
const { appId, viewId, view } = data;
// handle tablet case
if (closeModal) {
closeModal();
} else {
Navigation.back();
}
try {
await RocketChat.triggerCancel({
appId,
viewId,
view: {
...view,
id: viewId,
state: groupStateByBlockId(this.values)
},
isCleared: true
});
} catch (e) {
// do nothing
}
}
submit = async() => {
const { data } = this.state;
const { navigation } = this.props;
navigation.setParams({ submitting: true });
const { appId, viewId } = data;
this.setState({ loading: true });
try {
await RocketChat.triggerSubmitView({
viewId,
appId,
payload: {
view: {
id: viewId,
state: groupStateByBlockId(this.values)
}
}
});
} catch (e) {
// do nothing
}
navigation.setParams({ submitting: false });
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
this.setState({ loading: false });
};
action = async({ actionId, value, blockId }) => {
const { data } = this.state;
const { mid, appId, viewId } = data;
await RocketChat.triggerBlockAction({
container: {
type: CONTAINER_TYPES.VIEW,
id: viewId
},
actionId,
appId,
value,
blockId,
mid
});
this.changeState({ actionId, value, blockId });
}
changeState = ({ actionId, value, blockId = 'default' }) => {
this.values[actionId] = {
blockId,
value
};
};
render() {
const { data, loading, errors } = this.state;
const { theme, language } = this.props;
const { values } = this;
const { view } = data;
const { blocks } = view;
return (
style={[
styles.container,
{ backgroundColor: themes[theme].auxiliaryBackground }
]}
keyboardShouldPersistTaps='always'
>
<View style={styles.content}>
{
React.createElement(
modalBlockWithContext({
action: this.action,
state: this.changeState,
...data
}),
{
blocks,
errors,
language,
values
}
)
}
</View>
{loading ? <ActivityIndicator absolute size='large' theme={theme} /> : null}