[ICU-629] Add support for DM with deactivated channel (teammate) (#1385)
* Add support for DM with deactivated channel (teammate) * add archive_icon * add extra protection to prevent error when teammate is evetually deactivated
This commit is contained in:
parent
cd80cdfdf4
commit
bda1ba153e
9 changed files with 153 additions and 32 deletions
|
|
@ -31,6 +31,7 @@ export default class ChannelItem extends PureComponent {
|
|||
navigator: PropTypes.object,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
status: PropTypes.string,
|
||||
teammateDeletedAt: PropTypes.number,
|
||||
type: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
|
@ -79,6 +80,7 @@ export default class ChannelItem extends PureComponent {
|
|||
isUnread,
|
||||
mentions,
|
||||
status,
|
||||
teammateDeletedAt,
|
||||
theme,
|
||||
type
|
||||
} = this.props;
|
||||
|
|
@ -133,6 +135,7 @@ export default class ChannelItem extends PureComponent {
|
|||
membersCount={displayName.split(',').length}
|
||||
size={16}
|
||||
status={status}
|
||||
teammateDeletedAt={teammateDeletedAt}
|
||||
theme={theme}
|
||||
type={type}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {connect} from 'react-redux';
|
|||
import {General} from 'mattermost-redux/constants';
|
||||
import {getCurrentChannelId, makeGetChannel, getMyChannelMember} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import ChannelItem from './channel_item';
|
||||
|
||||
|
|
@ -22,8 +22,13 @@ function makeMapStateToProps() {
|
|||
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
let isMyUser = false;
|
||||
let teammateDeletedAt = 0;
|
||||
if (channel.type === General.DM_CHANNEL && channel.teammate_id) {
|
||||
isMyUser = channel.teammate_id === currentUserId;
|
||||
const teammate = getUser(state, channel.teammate_id);
|
||||
if (teammate && teammate.delete_at) {
|
||||
teammateDeletedAt = teammate.delete_at;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -33,6 +38,7 @@ function makeMapStateToProps() {
|
|||
isMyUser,
|
||||
mentions: member ? member.mention_count : 0,
|
||||
status: channel.status,
|
||||
teammateDeletedAt,
|
||||
theme: getTheme(state),
|
||||
type: channel.type
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,13 @@ import {
|
|||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import {AwayAvatar, DndAvatar, OfflineAvatar, OnlineAvatar} from 'app/components/status_icons';
|
||||
import {
|
||||
ArchiveIcon,
|
||||
AwayAvatar,
|
||||
DndAvatar,
|
||||
OfflineAvatar,
|
||||
OnlineAvatar
|
||||
} from 'app/components/status_icons';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
|
|
@ -23,6 +29,7 @@ export default class ChannelIcon extends React.PureComponent {
|
|||
membersCount: PropTypes.number,
|
||||
size: PropTypes.number,
|
||||
status: PropTypes.string,
|
||||
teammateDeletedAt: PropTypes.number,
|
||||
theme: PropTypes.object.isRequired,
|
||||
type: PropTypes.string.isRequired
|
||||
};
|
||||
|
|
@ -35,7 +42,17 @@ export default class ChannelIcon extends React.PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {isActive, isUnread, isInfo, membersCount, size, status, theme, type} = this.props;
|
||||
const {
|
||||
isActive,
|
||||
isUnread,
|
||||
isInfo,
|
||||
membersCount,
|
||||
size,
|
||||
status,
|
||||
teammateDeletedAt,
|
||||
theme,
|
||||
type
|
||||
} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let activeIcon;
|
||||
|
|
@ -89,6 +106,14 @@ export default class ChannelIcon extends React.PureComponent {
|
|||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (type === General.DM_CHANNEL && teammateDeletedAt) {
|
||||
icon = (
|
||||
<ArchiveIcon
|
||||
width={size}
|
||||
height={size}
|
||||
color={offlineColor}
|
||||
/>
|
||||
);
|
||||
} else if (type === General.DM_CHANNEL) {
|
||||
switch (status) {
|
||||
case General.AWAY:
|
||||
|
|
|
|||
|
|
@ -10,24 +10,14 @@ import {getCurrentUserId, getUser, makeGetProfilesInChannel} from 'mattermost-re
|
|||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {getChannelMembersForDm} from 'app/selectors/channel';
|
||||
|
||||
import ChannelIntro from './channel_intro';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const getChannel = makeGetChannel();
|
||||
const getProfilesInChannel = makeGetProfilesInChannel();
|
||||
|
||||
const getOtherUserIdForDm = createSelector(
|
||||
(state, channel) => channel,
|
||||
getCurrentUserId,
|
||||
(channel, currentUserId) => {
|
||||
if (!channel) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return channel.name.split('__').find((m) => m !== currentUserId) || currentUserId;
|
||||
}
|
||||
);
|
||||
|
||||
const getChannelMembers = createSelector(
|
||||
getCurrentUserId,
|
||||
(state, channel) => getProfilesInChannel(state, channel.id),
|
||||
|
|
@ -37,17 +27,6 @@ function makeMapStateToProps() {
|
|||
}
|
||||
);
|
||||
|
||||
const getChannelMembersForDm = createSelector(
|
||||
(state, channel) => getUser(state, getOtherUserIdForDm(state, channel)),
|
||||
(otherUser) => {
|
||||
if (!otherUser) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [otherUser];
|
||||
}
|
||||
);
|
||||
|
||||
return function mapStateToProps(state, ownProps) {
|
||||
const currentChannel = getChannel(state, {id: ownProps.channelId}) || {};
|
||||
const {status: getPostsRequestStatus} = state.requests.posts.getPosts;
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
import {createPost} from 'mattermost-redux/actions/posts';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {canUploadFilesOnMobile} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
|
@ -17,17 +19,28 @@ import {handleClearFiles, handleClearFailedFiles, handleRemoveLastFile, handleUp
|
|||
import {handleCommentDraftChanged, handleCommentDraftSelectionChanged} from 'app/actions/views/thread';
|
||||
import {userTyping} from 'app/actions/views/typing';
|
||||
import {getCurrentChannelDraft, getThreadDraft} from 'app/selectors/views';
|
||||
import {getChannelMembersForDm} from 'app/selectors/channel';
|
||||
|
||||
import PostTextbox from './post_textbox';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const currentDraft = ownProps.rootId ? getThreadDraft(state, ownProps.rootId) : getCurrentChannelDraft(state);
|
||||
|
||||
const currentChannel = getCurrentChannel(state);
|
||||
let deactivatedChannel = false;
|
||||
if (currentChannel.type === General.DM_CHANNEL) {
|
||||
const teammate = getChannelMembersForDm(state, currentChannel);
|
||||
if (teammate.length && teammate[0].delete_at) {
|
||||
deactivatedChannel = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
channelId: ownProps.channelId || getCurrentChannelId(state),
|
||||
channelId: ownProps.channelId || currentChannel.id,
|
||||
canUploadFiles: canUploadFilesOnMobile(state),
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
currentUserId: getCurrentUserId(state),
|
||||
deactivatedChannel,
|
||||
files: currentDraft.files,
|
||||
theme: getTheme(state),
|
||||
uploadFileRequestStatus: state.requests.files.uploadFiles.status,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Alert, BackHandler, Keyboard, Platform, TextInput, TouchableOpacity, View} from 'react-native';
|
||||
import {Alert, BackHandler, Keyboard, Platform, Text, TextInput, TouchableOpacity, View} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
|
|
@ -41,6 +41,7 @@ export default class PostTextbox extends PureComponent {
|
|||
channelId: PropTypes.string.isRequired,
|
||||
channelIsLoading: PropTypes.bool.isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
deactivatedChannel: PropTypes.bool.isRequired,
|
||||
files: PropTypes.array,
|
||||
navigator: PropTypes.object,
|
||||
rootId: PropTypes.string,
|
||||
|
|
@ -95,7 +96,9 @@ export default class PostTextbox extends PureComponent {
|
|||
};
|
||||
|
||||
blur = () => {
|
||||
this.refs.input.blur();
|
||||
if (this.refs.input) {
|
||||
this.refs.input.blur();
|
||||
}
|
||||
};
|
||||
|
||||
canSend = () => {
|
||||
|
|
@ -409,16 +412,28 @@ export default class PostTextbox extends PureComponent {
|
|||
canUploadFiles,
|
||||
channelId,
|
||||
channelIsLoading,
|
||||
deactivatedChannel,
|
||||
files,
|
||||
navigator,
|
||||
rootId,
|
||||
theme
|
||||
} = this.props;
|
||||
const {showFileMaxWarning} = this.state;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
const textInputHeight = Math.min(this.state.contentHeight, MAX_CONTENT_HEIGHT);
|
||||
if (deactivatedChannel) {
|
||||
return (
|
||||
<Text style={style.deactivatedMessage}>
|
||||
{intl.formatMessage({
|
||||
id: 'create_post.deactivated',
|
||||
defaultMessage: 'You are viewing an archived channel with a deactivated user.'
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
const {showFileMaxWarning} = this.state;
|
||||
|
||||
const textInputHeight = Math.min(this.state.contentHeight, MAX_CONTENT_HEIGHT);
|
||||
const textValue = channelIsLoading ? '' : this.state.value;
|
||||
|
||||
let placeholder;
|
||||
|
|
@ -533,6 +548,19 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
borderTopWidth: 1,
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.20)
|
||||
},
|
||||
deactivatedMessage: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.8),
|
||||
fontSize: 15,
|
||||
lineHeight: 22,
|
||||
alignItems: 'flex-end',
|
||||
flexDirection: 'row',
|
||||
paddingVertical: 4,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.20),
|
||||
marginLeft: 10,
|
||||
marginRight: 10
|
||||
},
|
||||
sendButtonContainer: {
|
||||
justifyContent: 'flex-end',
|
||||
paddingHorizontal: 5,
|
||||
|
|
|
|||
36
app/components/status_icons/archive_icon.js
Normal file
36
app/components/status_icons/archive_icon.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {View} from 'react-native';
|
||||
import Svg, {
|
||||
Path
|
||||
} from 'react-native-svg';
|
||||
|
||||
export default class ArchiveIcon extends PureComponent {
|
||||
static propTypes = {
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
color: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const {color, height, width} = this.props;
|
||||
|
||||
return (
|
||||
<View style={{height, width, alignItems: 'flex-start'}}>
|
||||
<Svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox='0 0 14 14'
|
||||
>
|
||||
<Path
|
||||
d='M8.5 6.5q0-0.203-0.148-0.352t-0.352-0.148h-2q-0.203 0-0.352 0.148t-0.148 0.352 0.148 0.352 0.352 0.148h2q0.203 0 0.352-0.148t0.148-0.352zM13 5v7.5q0 0.203-0.148 0.352t-0.352 0.148h-11q-0.203 0-0.352-0.148t-0.148-0.352v-7.5q0-0.203 0.148-0.352t0.352-0.148h11q0.203 0 0.352 0.148t0.148 0.352zM13.5 1.5v2q0 0.203-0.148 0.352t-0.352 0.148h-12q-0.203 0-0.352-0.148t-0.148-0.352v-2q0-0.203 0.148-0.352t0.352-0.148h12q0.203 0 0.352 0.148t0.148 0.352z'
|
||||
fill={color}
|
||||
/>
|
||||
</Svg>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import ArchiveIcon from './archive_icon';
|
||||
import AwayAvatar from './away_avatar';
|
||||
import AwayIcon from './away_icon';
|
||||
import DndAvatar from './dnd_avatar';
|
||||
|
|
@ -11,6 +12,7 @@ import OnlineAvatar from './online_avatar';
|
|||
import OnlineIcon from './online_icon';
|
||||
|
||||
export {
|
||||
ArchiveIcon,
|
||||
AwayAvatar,
|
||||
AwayIcon,
|
||||
DndAvatar,
|
||||
|
|
|
|||
29
app/selectors/channel.js
Normal file
29
app/selectors/channel.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
const getOtherUserIdForDm = createSelector(
|
||||
(state, channel) => channel,
|
||||
getCurrentUserId,
|
||||
(channel, currentUserId) => {
|
||||
if (!channel) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return channel.name.split('__').find((m) => m !== currentUserId) || currentUserId;
|
||||
}
|
||||
);
|
||||
|
||||
export const getChannelMembersForDm = createSelector(
|
||||
(state, channel) => getUser(state, getOtherUserIdForDm(state, channel)),
|
||||
(otherUser) => {
|
||||
if (!otherUser) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [otherUser];
|
||||
}
|
||||
);
|
||||
Loading…
Reference in a new issue