Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView

This commit is contained in:
Gerzon Z 2021-04-30 13:09:10 -04:00
parent 37421d395a
commit bb0632b689
16 changed files with 6913 additions and 5828 deletions

File diff suppressed because it is too large Load Diff

View File

@ -719,5 +719,8 @@
"team-name-already-exists": "A team with that name already exists",
"Add_Channel_to_Team": "Add Channel to Team",
"Create_New": "Create New",
"Add_Existing": "Add Existing"
"Add_Existing": "Add Existing",
"Add_Existing_Channel": "Add Existing Channel",
"Remove_from_Team": "Remove from Team",
"Auto-join": "Auto-join"
}

View File

@ -13,6 +13,7 @@ const PERMISSIONS = [
'add-user-to-any-c-room',
'add-user-to-any-p-room',
'add-user-to-joined-room',
'add-team-channel',
'archive-room',
'auto-translate',
'create-invite-links',
@ -21,11 +22,13 @@ const PERMISSIONS = [
'delete-p',
'edit-message',
'edit-room',
'edit-team-channel',
'force-delete-message',
'mute-user',
'pin-message',
'post-readonly',
'remove-user',
'remove-team-channel',
'set-leader',
'set-moderator',
'set-owner',
@ -38,7 +41,9 @@ const PERMISSIONS = [
'view-privileged-setting',
'view-room-administration',
'view-statistics',
'view-user-administration'
'view-user-administration',
'view-all-teams',
'view-all-team-channels'
];
export async function setPermissions() {

View File

@ -734,7 +734,7 @@ const RocketChat = {
const params = {
name,
users,
type: type ? 1 : 0,
type,
room: {
readOnly,
extraData: {
@ -746,6 +746,22 @@ const RocketChat = {
// RC 3.13.0
return this.post('teams.create', params);
},
addTeamRooms({ rooms, teamId }) {
const params = {
rooms: rooms.length ? [...rooms] : [rooms],
teamId
};
// RC 3.13.0
return this.post('teams.addRooms', params);
},
deleteTeamRoom({ rid, teamId }) {
const params = {
roomId: rid,
teamId
};
// RC 3.13.0
return this.post('teams.removeRoom', params);
},
joinRoom(roomId, joinCode, type) {
// TODO: join code
// RC 0.48.0

View File

@ -42,6 +42,7 @@ const RoomItem = ({
testID,
swipeEnabled,
onPress,
onLongPress,
toggleFav,
toggleRead,
hideChannel,
@ -49,6 +50,7 @@ const RoomItem = ({
}) => (
<Touchable
onPress={onPress}
onLongPress={onLongPress}
width={width}
favorite={favorite}
toggleFav={toggleFav}
@ -181,6 +183,7 @@ RoomItem.propTypes = {
toggleFav: PropTypes.func,
toggleRead: PropTypes.func,
onPress: PropTypes.func,
onLongPress: PropTypes.func,
hideChannel: PropTypes.func
};

View File

@ -1,9 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Animated } from 'react-native';
import { Animated, Pressable } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Touch from '../../utils/touch';
import {
ACTION_WIDTH,
SMALL_SWIPE,
@ -17,6 +16,7 @@ class Touchable extends React.Component {
static propTypes = {
type: PropTypes.string.isRequired,
onPress: PropTypes.func,
onLongPress: PropTypes.func,
testID: PropTypes.string,
width: PropTypes.number,
favorite: PropTypes.bool,
@ -203,6 +203,18 @@ class Touchable extends React.Component {
}
};
onLongPress = () => {
const { rowState } = this.state;
if (rowState !== 0) {
this.close();
return;
}
const { onLongPress } = this.props;
if (onLongPress) {
onLongPress();
}
};
render() {
const {
testID, isRead, width, favorite, children, theme, isFocused, swipeEnabled
@ -237,8 +249,9 @@ class Touchable extends React.Component {
transform: [{ translateX: this.transX }]
}}
>
<Touch
<Pressable
onPress={this.onPress}
onLongPress={this.onLongPress}
theme={theme}
testID={testID}
style={{
@ -246,7 +259,7 @@ class Touchable extends React.Component {
}}
>
{children}
</Touch>
</Pressable>
</Animated.View>
</Animated.View>

View File

@ -25,6 +25,7 @@ class RoomItemContainer extends React.Component {
showLastMessage: PropTypes.bool,
id: PropTypes.string,
onPress: PropTypes.func,
onLongPress: PropTypes.func,
username: PropTypes.string,
avatarSize: PropTypes.number,
width: PropTypes.number,
@ -112,6 +113,11 @@ class RoomItemContainer extends React.Component {
return onPress(item);
}
onLongPress = () => {
const { item, onLongPress } = this.props;
return onLongPress(item);
}
render() {
const {
item,
@ -160,6 +166,7 @@ class RoomItemContainer extends React.Component {
isGroupChat={this.isGroupChat}
isRead={isRead}
onPress={this.onPress}
onLongPress={this.onLongPress}
date={date}
accessibilityLabel={accessibilityLabel}
width={width}

View File

@ -25,6 +25,10 @@ const createTeam = function createTeam(data) {
return RocketChat.createTeam(data);
};
const addTeamRoom = function addRoomToTeam(params) {
return RocketChat.addTeamRoom(params);
};
const handleRequest = function* handleRequest({ data }) {
try {
const auth = yield select(state => state.login.isAuthenticated);
@ -40,6 +44,7 @@ const handleRequest = function* handleRequest({ data }) {
broadcast,
encrypted
} = data;
// TODO: Create event CT_CREATE
logEvent(events.CR_CREATE, {
type,
readOnly,
@ -67,14 +72,22 @@ const handleRequest = function* handleRequest({ data }) {
encrypted
});
sub = yield call(createChannel, data);
}
if (data.teamId) {
// TODO: Log when adding room to team
const channels = yield call(addTeamRoom, { rooms: sub.rid, teamId: data.teamId });
if (channels.success) {
sub.teamId = channels.teamId;
sub.isTeamChannel = true;
}
}
}
try {
const db = database.active;
const subCollection = db.get('subscriptions');
yield db.action(async() => {
await subCollection.create((s) => {
s._raw = sanitizedRaw({ id: sub.team ? sub.team.roomId : sub.rid }, subCollection.schema);
s._raw = sanitizedRaw({ id: sub.team ? sub.team.roomId : sub.rid, team_id: sub.teamId }, subCollection.schema);
Object.assign(s, sub);
});
});

View File

@ -72,6 +72,7 @@ import CreateDiscussionView from '../views/CreateDiscussionView';
import QueueListView from '../ee/omnichannel/views/QueueListView';
import AddChannelTeamView from '../views/AddChannelTeamView';
import AddExistingChannelView from '../views/AddExistingChannelView';
// ChatsStackNavigator
const ChatsStack = createStackNavigator();
@ -180,6 +181,11 @@ const ChatsStackNavigator = () => {
component={AddChannelTeamView}
options={AddChannelTeamView.navigationOptions}
/>
<ChatsStack.Screen
name='AddExistingChannelView'
component={AddExistingChannelView}
options={AddExistingChannelView.navigationOptions}
/>
<ChatsStack.Screen
name='MarkdownTableView'
component={MarkdownTableView}

View File

@ -8,17 +8,34 @@ const navigate = ({ item, isMasterDetail, ...props }) => {
navigationMethod = Navigation.replace;
}
navigationMethod('RoomView', {
rid: item.roomId || item.rid,
name: RocketChat.getRoomTitle(item),
t: item.type ? 'p' : item.t,
prid: item.prid,
room: item,
search: item.search,
visitor: item.visitor,
roomUserId: RocketChat.getUidDirectMessage(item),
...props
});
if (item.isTeamChannel) {
// TODO: Refactor
Navigation.navigate('TeamChannelsView');
Navigation.push('RoomView', {
rid: item.roomId || item.rid,
name: RocketChat.getRoomTitle(item),
t: item.type ? 'p' : item.t,
prid: item.prid,
room: item,
search: item.search,
visitor: item.visitor,
roomUserId: RocketChat.getUidDirectMessage(item),
teamId: item.teamId,
...props
});
} else {
navigationMethod('RoomView', {
rid: item.roomId || item.rid,
name: RocketChat.getRoomTitle(item),
t: item.type ? 'p' : item.t,
prid: item.prid,
room: item,
search: item.search,
visitor: item.visitor,
roomUserId: RocketChat.getUidDirectMessage(item),
...props
});
}
};
export const goRoom = async({ item = {}, isMasterDetail = false, ...props }) => {

View File

@ -15,13 +15,14 @@ class Touch extends React.Component {
render() {
const {
children, onPress, theme, underlayColor, ...props
children, onPress, onLongPress, theme, underlayColor, ...props
} = this.props;
return (
<RectButton
ref={this.getRef}
onPress={onPress}
onLongPress={onLongPress}
activeOpacity={1}
underlayColor={underlayColor || themes[theme].bannerBackground}
rippleColor={themes[theme].bannerBackground}
@ -36,6 +37,7 @@ class Touch extends React.Component {
Touch.propTypes = {
children: PropTypes.node,
onPress: PropTypes.func,
onLongPress: PropTypes.func,
theme: PropTypes.string,
underlayColor: PropTypes.string
};

View File

@ -7,7 +7,6 @@ import sharedStyles from './Styles';
import { CustomIcon } from '../lib/Icons';
import Touch from '../utils/touch';
import StatusBar from '../containers/StatusBar';
import RoomHeader from '../containers/RoomHeader';
import { withTheme } from '../theme';
import * as HeaderButton from '../containers/HeaderButton';
import SafeAreaView from '../containers/SafeAreaView';
@ -47,9 +46,7 @@ class AddChannelTeamView extends React.Component {
const options = {
headerShown: true,
headerTitleAlign: 'center',
headerTitle: () => (
<RoomHeader title={I18n.t('Add_Channel_to_Team')} />
)
headerTitle: I18n.t('Add_Channel_to_Team')
};
if (isMasterDetail) {
@ -74,7 +71,7 @@ class AddChannelTeamView extends React.Component {
return (
<Touch
onPress={() => onPress()}
onPress={onPress}
style={{ backgroundColor: themes[theme].backgroundColor }}
testID={testID}
theme={theme}
@ -88,21 +85,22 @@ class AddChannelTeamView extends React.Component {
}
render() {
const { navigation } = this.props;
const { navigation, route } = this.props;
const { teamChannels } = route?.params;
return (
<SafeAreaView testID='add-channel-team-view'>
<StatusBar />
<View style={styles.buttonContainer}>
{this.renderButton({
onPress: navigation.navigate('NewMessageStackNavigator', { screen: 'CreateChannelView', isTeam: false }),
onPress: () => navigation.navigate('NewMessageStackNavigator', { screen: 'SelectedUsersViewCreateChannel', params: { nextAction: () => navigation.navigate('CreateChannelView', { teamId: this.teamId }) } }),
title: I18n.t('Create_New'),
icon: 'channel-public',
testID: 'add-channel-team-view-create-channel',
first: true
})}
{this.renderButton({
// onPress: navigation.navigate('AddExistingChannelView'),
onPress: () => navigation.navigate('AddExistingChannelView', { teamId: this.teamId, teamChannels }),
title: I18n.t('Add_Existing'),
icon: 'team',
testID: 'add-channel-team-view-create-channel'

View File

@ -0,0 +1,256 @@
/* eslint-disable no-mixed-spaces-and-tabs */
import React from 'react';
import PropTypes from 'prop-types';
import {
View, StyleSheet, FlatList, Text
} from 'react-native';
import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb';
import { HeaderBackButton } from '@react-navigation/stack';
import * as List from '../containers/List';
import Touch from '../utils/touch';
import database from '../lib/database';
import RocketChat from '../lib/rocketchat';
import sharedStyles from './Styles';
import I18n from '../i18n';
import log from '../utils/log';
import SearchBox from '../containers/SearchBox';
import { CustomIcon } from '../lib/Icons';
import * as HeaderButton from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
import { themes } from '../constants/colors';
import { withTheme } from '../theme';
import SafeAreaView from '../containers/SafeAreaView';
import { animateNextTransition } from '../utils/layoutAnimation';
import { goRoom } from '../utils/goRoom';
import Loading from '../containers/Loading';
const QUERY_SIZE = 15;
const styles = StyleSheet.create({
button: {
height: 46,
flexDirection: 'row',
alignItems: 'center'
},
buttonIcon: {
marginLeft: 18,
marginRight: 16
},
buttonText: {
fontSize: 17,
...sharedStyles.textRegular
},
textContainer: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
marginRight: 15
},
icon: {
marginHorizontal: 15,
alignSelf: 'center'
}
});
class AddExistingChannelView extends React.Component {
static propTypes = {
navigation: PropTypes.object,
route: PropTypes.object,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string
}),
theme: PropTypes.string,
isMasterDetail: PropTypes.bool
};
constructor(props) {
super(props);
this.init();
this.teamId = props.route?.params?.teamId;
this.state = {
search: [],
channels: [],
selected: [],
loading: false
};
this.setHeader();
}
setHeader = () => {
const { navigation, isMasterDetail, theme } = this.props;
const { selected } = this.state;
const options = {
headerShown: true,
headerTitleAlign: 'center',
headerTitle: I18n.t('Add_Existing_Channel')
};
if (isMasterDetail) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
} else {
options.headerLeft = () => <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />;
}
options.headerRight = () => selected.length > 0 && (
<HeaderButton.Container>
<HeaderButton.Item title={I18n.t('Create')} onPress={this.submit} testID='add-existing-channel-view-submit' />
</HeaderButton.Container>
);
navigation.setOptions(options);
}
// eslint-disable-next-line react/sort-comp
init = async() => {
try {
const db = database.active;
const channels = await db.collections
.get('subscriptions')
.query(
Q.where('t', 'p'),
Q.where('team_id', ''),
Q.experimentalTake(QUERY_SIZE),
Q.experimentalSortBy('room_updated_at', Q.desc)
)
.fetch();
this.setState({ channels });
} catch (e) {
log(e);
}
}
onSearchChangeText(text) {
this.search(text);
}
dismiss = () => {
const { navigation } = this.props;
return navigation.pop();
}
search = async(text) => {
const result = await RocketChat.search({ text, filterUsers: false });
this.setState({
search: result
});
}
submit = async() => {
const { selected } = this.state;
const { isMasterDetail } = this.props;
this.setState({ loading: true });
try {
// TODO: Log request
const result = await RocketChat.addTeamRooms({ rooms: selected, teamId: this.teamId });
if (result.success) {
this.setState({ loading: false });
goRoom(result, isMasterDetail);
}
} catch (e) {
// TODO: Log error
this.setState({ loading: false });
}
}
renderChannel = ({
onPress, testID, title, icon, checked
}) => {
const { theme } = this.props;
return (
<Touch
onPress={onPress}
style={{ backgroundColor: themes[theme].backgroundColor }}
testID={testID}
theme={theme}
>
<View style={[styles.button, { borderColor: themes[theme].separatorColor, marginVertical: 4 }]}>
<CustomIcon style={[styles.buttonIcon, { color: themes[theme].controlText }]} size={24} name={icon} />
<View style={styles.textContainer}>
<Text style={[styles.buttonText, { color: themes[theme].bodyText }]}>{title}</Text>
</View>
{checked ? <CustomIcon name={checked} size={22} style={[styles.icon, { color: themes[theme].actionTintColor }]} /> : null}
</View>
</Touch>
);
}
renderHeader = () => {
const { theme } = this.props;
return (
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='add-existing-channel-view-search' />
</View>
);
}
isChecked = (rid) => {
const { selected } = this.state;
return selected.includes(rid);
}
toggleChannel = (rid) => {
const { selected } = this.state;
animateNextTransition();
if (!this.isChecked(rid)) {
// logEvent(events.SELECTED_USERS_ADD_USER);
this.setState({ selected: [...selected, rid] }, () => this.setHeader());
} else {
// logEvent(events.SELECTED_USERS_REMOVE_USER);
const filterSelected = selected.filter(el => el !== rid);
this.setState({ selected: filterSelected }, () => this.setHeader());
}
}
renderItem = ({ item }) => (
<>
{this.renderChannel({
onPress: () => this.toggleChannel(item.rid),
title: item.name,
icon: item.t === 'p' && !item.teamId ? 'channel-private' : 'channel-public',
checked: this.isChecked(item.rid) ? 'check' : null,
testID: 'add-existing-channel-view-item'
})}
</>
)
renderList = () => {
const { search, channels } = this.state;
const { theme } = this.props;
return (
<FlatList
data={search.length > 0 ? search : channels}
extraData={this.state}
keyExtractor={item => item._id}
ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={List.Separator}
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
keyboardShouldPersistTaps='always'
/>
);
}
render() {
const { loading } = this.state;
return (
<SafeAreaView testID='new-message-view'>
<StatusBar />
{this.renderList()}
<Loading visible={loading} />
</SafeAreaView>
);
}
}
const mapStateToProps = state => ({
isMasterDetail: state.app.isMasterDetail
});
export default connect(mapStateToProps, null)(withTheme(AddExistingChannelView));

View File

@ -87,13 +87,15 @@ class CreateChannelView extends React.Component {
id: PropTypes.string,
token: PropTypes.string
}),
theme: PropTypes.string
theme: PropTypes.string,
teamId: PropTypes.string
};
constructor(props) {
super(props);
const { route } = this.props;
this.isTeam = route?.params?.isTeam || false;
this.teamId = route?.params?.teamId;
this.state = {
channelName: '',
type: true,
@ -173,7 +175,7 @@ class CreateChannelView extends React.Component {
// create channel or team
create({
name: channelName, users, type, readOnly, broadcast, encrypted, isTeam: this.isTeam
name: channelName, users, type, readOnly, broadcast, encrypted, isTeam: this.isTeam, teamId: this.teamId
});
Review.pushPositiveEvent();

View File

@ -114,7 +114,7 @@ class RightButtonsContainer extends Component {
goTeamChannels = () => {
logEvent(events.ROOM_GO_TEAM_CHANNELS);
const {
navigation, isMasterDetail, teamId
navigation, isMasterDetail, teamId, rid
} = this.props;
if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', {
@ -122,7 +122,7 @@ class RightButtonsContainer extends Component {
params: { teamId }
});
} else {
navigation.navigate('TeamChannelsView', { teamId });
navigation.navigate('TeamChannelsView', { teamId, rid });
}
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Keyboard } from 'react-native';
import { Keyboard, Alert } from 'react-native';
import PropTypes from 'prop-types';
import { Q } from '@nozbe/watermelondb';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
@ -28,6 +28,8 @@ import debounce from '../utils/debounce';
import { showErrorAlert } from '../utils/info';
import { goRoom } from '../utils/goRoom';
import I18n from '../i18n';
import { withActionSheet } from '../containers/ActionSheet';
import { deleteRoom as deleteRoomAction } from '../actions/room';
const API_FETCH_COUNT = 25;
@ -47,12 +49,16 @@ class TeamChannelsView extends React.Component {
theme: PropTypes.string,
useRealName: PropTypes.bool,
width: PropTypes.number,
StoreLastMessage: PropTypes.bool
StoreLastMessage: PropTypes.bool,
addTeamChannelPermission: PropTypes.array,
showActionSheet: PropTypes.func,
deleteRoom: PropTypes.func
}
constructor(props) {
super(props);
this.teamId = props.route.params?.teamId;
this.rid = props.route.params?.rid;
this.state = {
loading: true,
loadingMore: false,
@ -60,9 +66,11 @@ class TeamChannelsView extends React.Component {
isSearching: false,
searchText: '',
search: [],
end: false
end: false,
showCreate: false
};
this.loadTeam();
this.setHeader();
}
componentDidMount() {
@ -70,6 +78,7 @@ class TeamChannelsView extends React.Component {
}
loadTeam = async() => {
const { addTeamChannelPermission } = this.props;
const db = database.active;
try {
const subCollection = db.get('subscriptions');
@ -82,6 +91,11 @@ class TeamChannelsView extends React.Component {
if (!this.team) {
throw new Error();
}
const permissions = await RocketChat.hasPermission([addTeamChannelPermission], this.team.rid);
if (permissions[0]) {
this.setState({ showCreate: true }, () => this.setHeader());
}
} catch {
const { navigation } = this.props;
navigation.pop();
@ -135,8 +149,8 @@ class TeamChannelsView extends React.Component {
}
}, 300)
getHeader = () => {
const { isSearching } = this.state;
setHeader = () => {
const { isSearching, showCreate, data } = this.state;
const {
navigation, isMasterDetail, insets, theme
} = this.props;
@ -201,15 +215,11 @@ class TeamChannelsView extends React.Component {
options.headerRight = () => (
<HeaderButton.Container>
<HeaderButton.Item iconName='search' onPress={this.onSearchPress} />
<HeaderButton.Item iconName='create' onPress={() => navigation.navigate('AddChannelTeamView')} />
{ showCreate
? <HeaderButton.Item iconName='create' onPress={() => navigation.navigate('AddChannelTeamView', { teamId: this.teamId, teamChannels: data })} />
: null}
</HeaderButton.Container>
);
return options;
}
setHeader = () => {
const { navigation } = this.props;
const options = this.getHeader();
navigation.setOptions(options);
}
@ -288,6 +298,54 @@ class TeamChannelsView extends React.Component {
}
}, 1000, true);
options = item => ([
{
title: I18n.t('Auto-join'),
icon: item.t === 'p' ? 'channel-private' : 'channel-public'
// onPress: this.autoJoin
},
{
title: I18n.t('Remove_from_Team'),
icon: 'close',
danger: true,
onPress: this.removeFromTeam(item.id, this.teamId)
},
{
title: I18n.t('Delete'),
icon: 'delete',
danger: true,
onPress: this.delete
}
])
delete = () => {
const { room } = this.state;
const { deleteRoom } = this.props;
Alert.alert(
I18n.t('Are_you_sure_question_mark'),
I18n.t('Delete_Room_Warning'),
[
{
text: I18n.t('Cancel'),
style: 'cancel'
},
{
text: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
style: 'destructive',
onPress: () => deleteRoom(room.rid, room.t)
}
],
{ cancelable: false }
);
}
showChannelActions = (item) => {
logEvent(events.ROOM_SHOW_BOX_ACTIONS);
const { showActionSheet } = this.props;
showActionSheet({ options: this.options(item) });
}
renderItem = ({ item }) => {
const {
StoreLastMessage,
@ -303,6 +361,7 @@ class TeamChannelsView extends React.Component {
showLastMessage={StoreLastMessage}
onPress={this.onPressItem}
width={width}
onLongPress={this.showChannelActions}
useRealName={useRealName}
getRoomTitle={this.getRoomTitle}
getRoomAvatar={this.getRoomAvatar}
@ -366,7 +425,12 @@ const mapStateToProps = state => ({
user: getUserSelector(state),
useRealName: state.settings.UI_Use_Real_Name,
isMasterDetail: state.app.isMasterDetail,
StoreLastMessage: state.settings.Store_Last_Message
StoreLastMessage: state.settings.Store_Last_Message,
addTeamChannelPermission: state.permissions['add-team-channel']
});
export default connect(mapStateToProps)(withDimensions(withSafeAreaInsets(withTheme(TeamChannelsView))));
const mapDispatchToProps = dispatch => ({
deleteRoom: (rid, t) => dispatch(deleteRoomAction(rid, t))
});
export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withSafeAreaInsets(withTheme(withActionSheet(TeamChannelsView)))));