RN-52: RN: Make create post and upload file actions optimistic (#504)
* RN-52: RN: Make create post and upload file actions optimistic * Review feedback
This commit is contained in:
parent
43bdf33c03
commit
9d13c985c2
16 changed files with 249 additions and 104 deletions
|
|
@ -22,7 +22,10 @@ export function handleUploadFiles(files, rootId) {
|
|||
const mimeType = lookupMimeType(name);
|
||||
const clientId = generateId();
|
||||
|
||||
clientIds.push(clientId);
|
||||
clientIds.push({
|
||||
clientId,
|
||||
localPath: file.path
|
||||
});
|
||||
|
||||
const fileData = {
|
||||
uri: file.path,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ export default class FileAttachment extends PureComponent {
|
|||
const {file, theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
if (!file.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text
|
||||
|
|
@ -66,7 +70,7 @@ export default class FileAttachment extends PureComponent {
|
|||
const style = getStyleSheet(theme);
|
||||
|
||||
let fileAttachmentComponent;
|
||||
if (file.has_preview_image) {
|
||||
if (file.has_preview_image || file.loading) {
|
||||
fileAttachmentComponent = (
|
||||
<FileAttachmentImage
|
||||
addFileToFetchCache={this.props.addFileToFetchCache}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ export default class FileAttachmentImage extends PureComponent {
|
|||
handleGetImageURL = () => {
|
||||
const {file, imageSize} = this.props;
|
||||
|
||||
if (file.localPath && this.state.retry === 0) {
|
||||
return file.localPath;
|
||||
}
|
||||
|
||||
switch (imageSize) {
|
||||
case IMAGE_SIZE.Fullsize:
|
||||
return Client.getFileUrl(file.id, this.state.timestamp);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
View,
|
||||
TouchableOpacity
|
||||
} from 'react-native';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import FileAttachment from './file_attachment';
|
||||
|
|
@ -24,7 +25,8 @@ export default class FileAttachmentList extends Component {
|
|||
onPress: PropTypes.func,
|
||||
post: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
toggleSelected: PropTypes.func.isRequired
|
||||
toggleSelected: PropTypes.func.isRequired,
|
||||
filesForPostRequest: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -32,6 +34,15 @@ export default class FileAttachmentList extends Component {
|
|||
this.props.actions.loadFilesForPostIfNecessary(post);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const {files, filesForPostRequest, post} = this.props;
|
||||
|
||||
// Fixes an issue where files weren't loading with optimistic post
|
||||
if (!files.length && post.file_ids.length > 0 && filesForPostRequest.status !== RequestStatus.STARTED) {
|
||||
this.props.actions.loadFilesForPostIfNecessary(post);
|
||||
}
|
||||
}
|
||||
|
||||
handleInfoPress = () => {
|
||||
this.props.hideOptionsContext();
|
||||
this.props.onPress();
|
||||
|
|
@ -43,26 +54,41 @@ export default class FileAttachmentList extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const fileAttachments = this.props.files.map((file) => (
|
||||
<TouchableOpacity
|
||||
key={file.id}
|
||||
onLongPress={this.props.onLongPress}
|
||||
onPressIn={() => this.props.toggleSelected(true)}
|
||||
onPressOut={() => this.props.toggleSelected(false)}
|
||||
>
|
||||
const {files, post} = this.props;
|
||||
|
||||
let fileAttachments;
|
||||
if (!files.length && post.file_ids.length > 0) {
|
||||
fileAttachments = post.file_ids.map((id) => (
|
||||
<FileAttachment
|
||||
key={id}
|
||||
addFileToFetchCache={this.props.actions.addFileToFetchCache}
|
||||
fetchCache={this.props.fetchCache}
|
||||
file={file}
|
||||
onInfoPress={this.handleInfoPress}
|
||||
onPreviewPress={this.handlePreviewPress}
|
||||
file={{loading: true}}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
));
|
||||
));
|
||||
} else {
|
||||
fileAttachments = files.map((file) => (
|
||||
<TouchableOpacity
|
||||
key={file.id}
|
||||
onLongPress={this.props.onLongPress}
|
||||
onPressIn={() => this.props.toggleSelected(true)}
|
||||
onPressOut={() => this.props.toggleSelected(false)}
|
||||
>
|
||||
<FileAttachment
|
||||
addFileToFetchCache={this.props.actions.addFileToFetchCache}
|
||||
fetchCache={this.props.fetchCache}
|
||||
file={file}
|
||||
onInfoPress={this.handleInfoPress}
|
||||
onPreviewPress={this.handlePreviewPress}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<View style={[{flex: 1}, (post.failed && {opacity: 0.5})]}>
|
||||
{fileAttachments}
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ function makeMapStateToProps() {
|
|||
...ownProps,
|
||||
fetchCache: state.views.fetchCache,
|
||||
files: getFilesForPost(state, ownProps.post),
|
||||
theme: getTheme(state)
|
||||
theme: getTheme(state),
|
||||
filesForPostRequest: state.requests.files.getFilesForPost
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ export default class FileUploadPreview extends PureComponent {
|
|||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
addFileToFetchCache: PropTypes.func.isRequired,
|
||||
handleClearFiles: PropTypes.func.isRequired,
|
||||
handleRemoveFile: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
channelId: PropTypes.string.isRequired,
|
||||
|
|
@ -35,12 +34,6 @@ export default class FileUploadPreview extends PureComponent {
|
|||
uploadFileRequestStatus: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.createPostRequestStatus === RequestStatus.STARTED && nextProps.createPostRequestStatus === RequestStatus.SUCCESS) {
|
||||
this.props.actions.handleClearFiles(this.props.channelId, this.props.rootId);
|
||||
}
|
||||
}
|
||||
|
||||
buildFilePreviews = () => {
|
||||
return this.props.files.map((file) => {
|
||||
return (
|
||||
|
|
@ -48,11 +41,13 @@ export default class FileUploadPreview extends PureComponent {
|
|||
key={file.clientId}
|
||||
style={style.preview}
|
||||
>
|
||||
<FileAttachmentImage
|
||||
addFileToFetchCache={this.props.actions.addFileToFetchCache}
|
||||
fetchCache={this.props.fetchCache}
|
||||
file={file}
|
||||
/>
|
||||
<View style={style.previewShadow}>
|
||||
<FileAttachmentImage
|
||||
addFileToFetchCache={this.props.actions.addFileToFetchCache}
|
||||
fetchCache={this.props.fetchCache}
|
||||
file={file}
|
||||
/>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={style.removeButtonWrapper}
|
||||
onPress={() => this.props.actions.handleRemoveFile(file.clientId, this.props.channelId, this.props.rootId)}
|
||||
|
|
@ -75,14 +70,16 @@ export default class FileUploadPreview extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<KeyboardLayout style={style.container}>
|
||||
<ScrollView
|
||||
horizontal={true}
|
||||
style={style.scrollView}
|
||||
contentContainerStyle={[style.scrollViewContent, {marginBottom: this.props.inputHeight}]}
|
||||
>
|
||||
{this.buildFilePreviews()}
|
||||
</ScrollView>
|
||||
<KeyboardLayout>
|
||||
<View style={[style.container]}>
|
||||
<ScrollView
|
||||
horizontal={true}
|
||||
style={style.scrollView}
|
||||
contentContainerStyle={style.scrollViewContent}
|
||||
>
|
||||
{this.buildFilePreviews()}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</KeyboardLayout>
|
||||
);
|
||||
}
|
||||
|
|
@ -100,15 +97,23 @@ const style = StyleSheet.create({
|
|||
preview: {
|
||||
justifyContent: 'flex-end',
|
||||
height: 115,
|
||||
width: 115,
|
||||
width: 115
|
||||
},
|
||||
previewShadow: {
|
||||
width: 100,
|
||||
elevation: 10,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.5,
|
||||
shadowRadius: 4,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
...Platform.select({
|
||||
ios: {
|
||||
backgroundColor: '#fff',
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.5,
|
||||
shadowRadius: 4,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
removeButtonIcon: Platform.select({
|
||||
ios: {
|
||||
|
|
@ -123,6 +128,7 @@ const style = StyleSheet.create({
|
|||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
elevation: 11,
|
||||
top: 7,
|
||||
right: 7,
|
||||
width: 24,
|
||||
|
|
@ -134,7 +140,7 @@ const style = StyleSheet.create({
|
|||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
marginBottom: 24
|
||||
marginBottom: 12
|
||||
},
|
||||
scrollViewContent: {
|
||||
alignItems: 'flex-end',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {handleClearFiles, handleRemoveFile} from 'app/actions/views/file_upload';
|
||||
import {handleRemoveFile} from 'app/actions/views/file_upload';
|
||||
import {addFileToFetchCache} from 'app/actions/views/file_preview';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
|
|
@ -25,7 +25,6 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
addFileToFetchCache,
|
||||
handleClearFiles,
|
||||
handleRemoveFile
|
||||
}, dispatch)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {deletePost, flagPost, removePost, unflagPost} from 'mattermost-redux/actions/posts';
|
||||
import {createPost, deletePost, flagPost, removePost, unflagPost} from 'mattermost-redux/actions/posts';
|
||||
import {getMyPreferences} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {makeGetCommentCountForPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getCurrentUserId, getCurrentUserRoles, getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
import {isPostFlagged} from 'mattermost-redux/utils/post_utils';
|
||||
import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import {goToUserProfile, openEditPostModal} from 'app/actions/navigation';
|
||||
import {goToUserProfile, openEditPostModal, requestCloseModal, showOptionsModal} from 'app/actions/navigation';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
|
||||
import Post from './post';
|
||||
|
|
@ -43,11 +43,14 @@ function makeMapStateToProps() {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
createPost,
|
||||
deletePost,
|
||||
flagPost,
|
||||
goToUserProfile,
|
||||
openEditPostModal,
|
||||
removePost,
|
||||
requestCloseModal,
|
||||
showOptionsModal,
|
||||
unflagPost
|
||||
}, dispatch)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
import FileAttachmentList from 'app/components/file_attachment_list';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
|
@ -55,12 +56,15 @@ class Post extends PureComponent {
|
|||
theme: PropTypes.object.isRequired,
|
||||
onPress: PropTypes.func,
|
||||
actions: PropTypes.shape({
|
||||
createPost: PropTypes.func.isRequired,
|
||||
deletePost: PropTypes.func.isRequired,
|
||||
flagPost: PropTypes.func.isRequired,
|
||||
goToUserProfile: PropTypes.func.isRequired,
|
||||
openEditPostModal: PropTypes.func.isRequired,
|
||||
removePost: PropTypes.func.isRequired,
|
||||
unflagPost: PropTypes.func.isRequired
|
||||
unflagPost: PropTypes.func.isRequired,
|
||||
requestCloseModal: PropTypes.func.isRequired,
|
||||
showOptionsModal: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
|
|
@ -117,9 +121,44 @@ class Post extends PureComponent {
|
|||
actions.openEditPostModal(post);
|
||||
};
|
||||
|
||||
handleFailedPostPress = () => {
|
||||
const options = {
|
||||
title: {
|
||||
id: 'mobile.post.failed_title',
|
||||
defaultMessage: 'Unable to send your message:'
|
||||
},
|
||||
items: [{
|
||||
action: () => {
|
||||
const {failed, id, ...post} = this.props.post; // eslint-disable-line
|
||||
|
||||
this.props.actions.requestCloseModal();
|
||||
this.props.actions.createPost(post);
|
||||
},
|
||||
text: {
|
||||
id: 'mobile.post.failed_retry',
|
||||
defaultMessage: 'Try Again'
|
||||
}
|
||||
}, {
|
||||
action: () => {
|
||||
this.props.actions.requestCloseModal();
|
||||
this.onRemovePost(this.props.post);
|
||||
},
|
||||
text: {
|
||||
id: 'mobile.post.failed_delete',
|
||||
defaultMessage: 'Delete Message'
|
||||
},
|
||||
textStyle: {
|
||||
color: '#CC3239'
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
this.props.actions.showOptionsModal(options);
|
||||
};
|
||||
|
||||
handlePress = () => {
|
||||
const {post, onPress} = this.props;
|
||||
if (onPress && post.state !== Posts.POST_DELETED && !isSystemMessage(post)) {
|
||||
if (onPress && post.state !== Posts.POST_DELETED && !isSystemMessage(post) && !post.failed) {
|
||||
preventDoubleTap(onPress, null, post);
|
||||
} else if (post.state === Posts.POST_DELETED) {
|
||||
preventDoubleTap(this.onRemovePost, this, post);
|
||||
|
|
@ -228,24 +267,26 @@ class Post extends PureComponent {
|
|||
const actions = [];
|
||||
|
||||
// we should check for the user roles and permissions
|
||||
if (isFlagged) {
|
||||
actions.push({
|
||||
text: formatMessage({id: 'post_info.mobile.unflag', defaultMessage: 'Unflag'}),
|
||||
onPress: () => unflagPost(post.id)
|
||||
});
|
||||
} else {
|
||||
actions.push({
|
||||
text: formatMessage({id: 'post_info.mobile.flag', defaultMessage: 'Flag'}),
|
||||
onPress: () => flagPost(post.id)
|
||||
});
|
||||
}
|
||||
if (!post.failed) {
|
||||
if (isFlagged) {
|
||||
actions.push({
|
||||
text: formatMessage({id: 'post_info.mobile.unflag', defaultMessage: 'Unflag'}),
|
||||
onPress: () => unflagPost(post.id)
|
||||
});
|
||||
} else {
|
||||
actions.push({
|
||||
text: formatMessage({id: 'post_info.mobile.flag', defaultMessage: 'Flag'}),
|
||||
onPress: () => flagPost(post.id)
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.canEdit) {
|
||||
actions.push({text: formatMessage({id: 'post_info.edit', defaultMessage: 'Edit'}), onPress: () => this.handlePostEdit()});
|
||||
}
|
||||
if (this.state.canEdit) {
|
||||
actions.push({text: formatMessage({id: 'post_info.edit', defaultMessage: 'Edit'}), onPress: () => this.handlePostEdit()});
|
||||
}
|
||||
|
||||
if (this.state.canDelete && post.state !== Posts.POST_DELETED) {
|
||||
actions.push({text: formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}), onPress: () => this.handlePostDelete()});
|
||||
if (this.state.canDelete && post.state !== Posts.POST_DELETED) {
|
||||
actions.push({text: formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}), onPress: () => this.handlePostDelete()});
|
||||
}
|
||||
}
|
||||
|
||||
let messageContainer;
|
||||
|
|
@ -260,12 +301,16 @@ class Post extends PureComponent {
|
|||
);
|
||||
} else if (this.props.post.message.length) {
|
||||
message = (
|
||||
<Markdown
|
||||
baseTextStyle={messageStyle}
|
||||
textStyles={getMarkdownTextStyles(theme)}
|
||||
blockStyles={getMarkdownBlockStyles(theme)}
|
||||
value={post.message}
|
||||
/>
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<View style={[{flex: 1}, (post.failed && style.failedPost)]}>
|
||||
<Markdown
|
||||
baseTextStyle={messageStyle}
|
||||
textStyles={getMarkdownTextStyles(theme)}
|
||||
blockStyles={getMarkdownBlockStyles(theme)}
|
||||
value={post.message}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -273,16 +318,30 @@ class Post extends PureComponent {
|
|||
messageContainer = (
|
||||
<View style={style.messageContainerWithReplyBar}>
|
||||
{replyBar && this.renderReplyBar(style)}
|
||||
<View style={{flex: 1}}>
|
||||
<OptionsContext
|
||||
actions={actions}
|
||||
ref='tooltip'
|
||||
onPress={this.handlePress}
|
||||
toggleSelected={this.toggleSelected}
|
||||
>
|
||||
{message}
|
||||
{this.renderFileAttachments()}
|
||||
</OptionsContext>
|
||||
<View style={{flex: 1, flexDirection: 'row'}}>
|
||||
<View style={{flex: 1}}>
|
||||
<OptionsContext
|
||||
actions={actions}
|
||||
ref='tooltip'
|
||||
onPress={this.handlePress}
|
||||
toggleSelected={this.toggleSelected}
|
||||
>
|
||||
{message}
|
||||
{this.renderFileAttachments()}
|
||||
</OptionsContext>
|
||||
</View>
|
||||
{post.failed &&
|
||||
<TouchableOpacity
|
||||
onPress={this.handleFailedPostPress}
|
||||
style={{justifyContent: 'center', marginLeft: 12}}
|
||||
>
|
||||
<Icon
|
||||
name='ios-information-circle-outline'
|
||||
size={26}
|
||||
color={theme.errorTextColor}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
|
@ -296,16 +355,30 @@ class Post extends PureComponent {
|
|||
onPress={this.handlePress}
|
||||
onShowUnderlay={() => this.toggleSelected(true)}
|
||||
underlayColor='transparent'
|
||||
style={{flex: 1}}
|
||||
style={{flex: 1, flexDirection: 'row'}}
|
||||
>
|
||||
<View style={{flex: 1}}>
|
||||
{message}
|
||||
<OptionsContext
|
||||
ref='bottomSheet'
|
||||
actions={actions}
|
||||
cancelText={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
/>
|
||||
{this.renderFileAttachments()}
|
||||
<View style={{flexDirection: 'row', flex: 1}}>
|
||||
<View style={{flex: 1}}>
|
||||
{message}
|
||||
<OptionsContext
|
||||
ref='bottomSheet'
|
||||
actions={actions}
|
||||
cancelText={formatMessage({id: 'channel_modal.cancel', defaultMessage: 'Cancel'})}
|
||||
/>
|
||||
{this.renderFileAttachments()}
|
||||
</View>
|
||||
{post.failed &&
|
||||
<TouchableOpacity
|
||||
onPress={this.handleFailedPostPress}
|
||||
style={{justifyContent: 'center', marginLeft: 12}}
|
||||
>
|
||||
<Icon
|
||||
name='ios-information-circle-outline'
|
||||
size={26}
|
||||
color={theme.errorTextColor}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
|
|
@ -432,11 +505,11 @@ class Post extends PureComponent {
|
|||
if (this.props.commentedOnPost) {
|
||||
contents = (
|
||||
<View style={[style.container, this.props.style, selected]}>
|
||||
<View style={style.profilePictureContainer}>
|
||||
<View style={[style.profilePictureContainer, (post.failed && style.failedPost)]}>
|
||||
{profilePicture}
|
||||
</View>
|
||||
<View style={style.rightColumn}>
|
||||
<View style={style.postInfoContainer}>
|
||||
<View style={[style.postInfoContainer, (post.failed && style.failedPost)]}>
|
||||
{displayName}
|
||||
<Text style={style.time}>
|
||||
<FormattedTime value={this.props.post.create_at}/>
|
||||
|
|
@ -452,13 +525,13 @@ class Post extends PureComponent {
|
|||
} else {
|
||||
contents = (
|
||||
<View style={[style.container, this.props.style, selected]}>
|
||||
<View style={style.profilePictureContainer}>
|
||||
<View style={[style.profilePictureContainer, (post.failed && style.failedPost)]}>
|
||||
{profilePicture}
|
||||
</View>
|
||||
<View style={style.messageContainerWithReplyBar}>
|
||||
{this.renderReplyBar(style)}
|
||||
<View style={[style.rightColumn, (this.props.isLastReply && style.rightColumnPadding)]}>
|
||||
<View style={style.postInfoContainer}>
|
||||
<View style={[style.postInfoContainer, (post.failed && style.failedPost)]}>
|
||||
<View style={{flexDirection: 'row', flex: 1}}>
|
||||
{displayName}
|
||||
<Text style={style.time}>
|
||||
|
|
@ -497,6 +570,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
backgroundColor: theme.centerChannelBg,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
failedPost: {
|
||||
opacity: 0.5
|
||||
},
|
||||
rightColumn: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ export default class PostList extends Component {
|
|||
lastViewedAt: PropTypes.number
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
channel: {}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
|
@ -76,7 +80,7 @@ export default class PostList extends Component {
|
|||
renderChannelIntro = () => {
|
||||
const {channel, channelIsLoading, posts} = this.props;
|
||||
|
||||
if (channel) {
|
||||
if (channel.hasOwnProperty('id')) {
|
||||
const firstPostHasRendered = channel.total_msg_count ? posts.length > 0 : true;
|
||||
const messageCount = channel.total_msg_count - posts.length;
|
||||
if (channelIsLoading || !firstPostHasRendered || messageCount > Posts.POST_CHUNK_SIZE) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {createPost} from 'mattermost-redux/actions/posts';
|
|||
import {userTyping} from 'mattermost-redux/actions/websocket';
|
||||
|
||||
import {showOptionsModal, requestCloseModal} from 'app/actions/navigation';
|
||||
import {handleRemoveLastFile, handleUploadFiles} from 'app/actions/views/file_upload';
|
||||
import {handleClearFiles, handleRemoveLastFile, handleUploadFiles} from 'app/actions/views/file_upload';
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getUsersTyping} from 'mattermost-redux/selectors/entities/typing';
|
||||
|
|
@ -30,6 +30,7 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
createPost,
|
||||
closeModal: requestCloseModal,
|
||||
handleClearFiles,
|
||||
handleRemoveLastFile,
|
||||
handleUploadFiles,
|
||||
showOptionsModal,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class PostTextbox extends PureComponent {
|
|||
actions: PropTypes.shape({
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
createPost: PropTypes.func.isRequired,
|
||||
handleClearFiles: PropTypes.func.isRequired,
|
||||
handleRemoveLastFile: PropTypes.func.isRequired,
|
||||
handleUploadFiles: PropTypes.func.isRequired,
|
||||
showOptionsModal: PropTypes.func.isRequired,
|
||||
|
|
@ -148,6 +149,9 @@ class PostTextbox extends PureComponent {
|
|||
|
||||
this.props.actions.createPost(post, this.props.files);
|
||||
this.handleTextChange('');
|
||||
if (this.props.files.length) {
|
||||
this.props.actions.handleClearFiles(this.props.channelId, this.props.rootId);
|
||||
}
|
||||
};
|
||||
|
||||
handleTextChange = (text) => {
|
||||
|
|
@ -298,7 +302,7 @@ class PostTextbox extends PureComponent {
|
|||
onChangeText={this.props.onChangeText}
|
||||
rootId={this.props.rootId}
|
||||
/>
|
||||
<View style={{backgroundColor: theme.centerChannelBg}}>
|
||||
<View style={{backgroundColor: theme.centerChannelBg, elevation: 5}}>
|
||||
<View
|
||||
style={style.inputWrapper}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ function drafts(state = {}, action) {
|
|||
return state;
|
||||
}
|
||||
|
||||
const tempFiles = action.clientIds.map((id) => ({clientId: id, loading: true}));
|
||||
const tempFiles = action.clientIds.map((temp) => ({...temp, loading: true}));
|
||||
const files = [
|
||||
...state[action.channelId].files,
|
||||
...tempFiles
|
||||
|
|
@ -62,7 +62,10 @@ function drafts(state = {}, action) {
|
|||
const files = state[action.channelId].files.map((tempFile) => {
|
||||
const file = action.data.find((f) => f.clientId === tempFile.clientId);
|
||||
if (file) {
|
||||
return file;
|
||||
return {
|
||||
...file,
|
||||
localPath: tempFile.localPath
|
||||
};
|
||||
}
|
||||
|
||||
return tempFile;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,11 @@ export default class OptionsModalList extends PureComponent {
|
|||
onPress={() => preventDoubleTap(onCancelPress, this)}
|
||||
style={style.option}
|
||||
>
|
||||
<Text style={style.optionText}>{'Cancel'}</Text>
|
||||
<FormattedText
|
||||
id='channel_modal.cancel'
|
||||
defaultMessage='Cancel'
|
||||
style={style.optionText}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,11 @@ export default class OptionsModalList extends PureComponent {
|
|||
onPress={() => preventDoubleTap(onCancelPress, this)}
|
||||
style={style.option}
|
||||
>
|
||||
<Text style={style.optionCancelText}>{'Cancel'}</Text>
|
||||
<FormattedText
|
||||
id='channel_modal.cancel'
|
||||
defaultMessage='Cancel'
|
||||
style={style.optionCancelText}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -1707,6 +1707,9 @@
|
|||
"mobile.post.delete_question": "Are you sure you want to delete this post?",
|
||||
"mobile.post.delete_title": "Delete Post",
|
||||
"mobile.post.retry": "Refresh",
|
||||
"mobile.post.failed_delete": "Delete Message",
|
||||
"mobile.post.failed_retry": "Try Again",
|
||||
"mobile.post.failed_title": "Unable to send your message",
|
||||
"mobile.request.invalid_response": "Received invalid response from the server.",
|
||||
"mobile.routes.channelInfo": "Info",
|
||||
"mobile.routes.channelInfo.createdBy": "Created by {creator} on ",
|
||||
|
|
|
|||
Loading…
Reference in a new issue