From 9e4ca34a800f77bb8a0d863551ff1aca891991b6 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 18 Jun 2018 10:30:36 -0300 Subject: [PATCH] Settings/Permissions improvements (#325) * Changed the way we read RocketChat settings since setting.type won't be returned from server anymore * Permissions * Unnecessary action sheet render --- app/actions/actionsTypes.js | 4 - app/actions/index.js | 7 - app/actions/messages.js | 27 ---- app/constants/permissions.js | 17 +++ app/constants/settings.js | 91 +++++++++-- app/constants/types.js | 1 - app/containers/MessageActions.js | 193 +++++++++++------------- app/containers/MessageErrorActions.js | 9 +- app/containers/Routes.js | 4 + app/lib/methods/getPermissions.js | 27 ++-- app/lib/methods/getSettings.js | 9 +- app/lib/realm.js | 3 +- app/lib/rocketchat.js | 43 +++--- app/reducers/index.js | 2 - app/reducers/messages.js | 11 -- app/reducers/permissions.js | 24 --- app/sagas/messages.js | 13 -- app/sagas/rooms.js | 3 +- app/sagas/selectServer.js | 2 - app/views/RoomInfoView/index.js | 9 +- app/views/RoomView/index.js | 23 +-- app/views/RoomsListView/Header/index.js | 10 +- e2e/09-roominfo.spec.js | 2 +- package-lock.json | 2 +- 24 files changed, 254 insertions(+), 282 deletions(-) create mode 100644 app/constants/permissions.js delete mode 100644 app/reducers/permissions.js diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index f2d87dd2..2b2429a3 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -62,10 +62,6 @@ export const MESSAGES = createRequestTypes('MESSAGES', [ 'TOGGLE_STAR_REQUEST', 'TOGGLE_STAR_SUCCESS', 'TOGGLE_STAR_FAILURE', - 'PERMALINK_REQUEST', - 'PERMALINK_SUCCESS', - 'PERMALINK_FAILURE', - 'PERMALINK_CLEAR', 'TOGGLE_PIN_REQUEST', 'TOGGLE_PIN_SUCCESS', 'TOGGLE_PIN_FAILURE', diff --git a/app/actions/index.js b/app/actions/index.js index b597699b..8d117c71 100644 --- a/app/actions/index.js +++ b/app/actions/index.js @@ -32,13 +32,6 @@ export function setAllSettings(settings) { }; } -export function setAllPermissions(permissions) { - return { - type: types.SET_ALL_PERMISSIONS, - payload: permissions - }; -} - export function setCustomEmojis(emojis) { return { type: types.SET_CUSTOM_EMOJIS, diff --git a/app/actions/messages.js b/app/actions/messages.js index edaaefb1..b7b3ee69 100644 --- a/app/actions/messages.js +++ b/app/actions/messages.js @@ -117,33 +117,6 @@ export function toggleStarFailure() { }; } -export function permalinkRequest(message) { - return { - type: types.MESSAGES.PERMALINK_REQUEST, - message - }; -} - -export function permalinkSuccess(permalink) { - return { - type: types.MESSAGES.PERMALINK_SUCCESS, - permalink - }; -} - -export function permalinkFailure(err) { - return { - type: types.MESSAGES.PERMALINK_FAILURE, - err - }; -} - -export function permalinkClear() { - return { - type: types.MESSAGES.PERMALINK_CLEAR - }; -} - export function togglePinRequest(message) { return { type: types.MESSAGES.TOGGLE_PIN_REQUEST, diff --git a/app/constants/permissions.js b/app/constants/permissions.js new file mode 100644 index 00000000..16ce1cfc --- /dev/null +++ b/app/constants/permissions.js @@ -0,0 +1,17 @@ +export default [ + 'add-user-to-any-c-room', + 'add-user-to-any-p-room', + 'add-user-to-joined-room', + 'archive-room', + 'delete-c', + 'delete-message', + 'delete-p', + 'edit-message', + 'edit-room', + 'force-delete-message', + 'mute-user', + 'set-react-when-readonly', + 'set-readonly', + 'unarchive-room', + 'view-broadcast-member-list' +]; diff --git a/app/constants/settings.js b/app/constants/settings.js index 8f8a714e..048aa00d 100644 --- a/app/constants/settings.js +++ b/app/constants/settings.js @@ -1,15 +1,80 @@ export default { - boolean: 'valueAsBoolean', - int: 'valueAsNumber', - string: 'valueAsString', - select: 'valueAsString', - code: 'valueAsString', - relativeUrl: 'valueAsString', - language: 'valueAsString', - action: 'valueAsString', - password: 'valueAsString', - // asset: ' Object', - color: 'valueAsString', - font: 'valueAsString', - roomPick: 'valueAsString' + Accounts_CustomFields: { + type: 'valueAsString' + }, + Accounts_EmailOrUsernamePlaceholder: { + type: 'valueAsString' + }, + Accounts_NamePlaceholder: { + type: 'valueAsString' + }, + Accounts_OAuth_Facebook: { + type: 'valueAsBoolean' + }, + Accounts_OAuth_Github: { + type: 'valueAsBoolean' + }, + Accounts_OAuth_Gitlab: { + type: 'valueAsBoolean' + }, + Accounts_OAuth_Google: { + type: 'valueAsBoolean' + }, + Accounts_OAuth_Linkedin: { + type: 'valueAsBoolean' + }, + Accounts_OAuth_Meteor: { + type: 'valueAsBoolean' + }, + Accounts_OAuth_Twitter: { + type: 'valueAsBoolean' + }, + Accounts_PasswordPlaceholder: { + type: 'valueAsString' + }, + Accounts_RepeatPasswordPlaceholder: { + type: 'valueAsString' + }, + CROWD_Enable: { + type: 'valueAsBoolean' + }, + Layout_Privacy_Policy: { + type: 'valueAsString' + }, + Layout_Terms_of_Service: { + type: 'valueAsString' + }, + LDAP_Enable: { + type: 'valueAsBoolean' + }, + Message_AllowDeleting: { + type: 'valueAsBoolean' + }, + Message_AllowDeleting_BlockDeleteInMinutes: { + type: 'valueAsNumber' + }, + Message_AllowEditing: { + type: 'valueAsBoolean' + }, + Message_AllowEditing_BlockEditInMinutes: { + type: 'valueAsNumber' + }, + Message_AllowPinning: { + type: 'valueAsBoolean' + }, + Message_AllowStarring: { + type: 'valueAsBoolean' + }, + Message_GroupingPeriod: { + type: 'valueAsNumber' + }, + Message_TimeFormat: { + type: 'valueAsString' + }, + Site_Url: { + type: 'valueAsString' + }, + Store_Last_Message: { + type: 'valueAsBoolean' + } }; diff --git a/app/constants/types.js b/app/constants/types.js index 3e65838c..8c5a6c8e 100644 --- a/app/constants/types.js +++ b/app/constants/types.js @@ -1,5 +1,4 @@ export const SET_CURRENT_SERVER = 'SET_CURRENT_SERVER'; export const SET_ALL_SETTINGS = 'SET_ALL_SETTINGS'; -export const SET_ALL_PERMISSIONS = 'SET_ALL_PERMISSIONS'; export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS'; export const ADD_SETTINGS = 'ADD_SETTINGS'; diff --git a/app/containers/MessageActions.js b/app/containers/MessageActions.js index ee63c6ad..7dc91f02 100644 --- a/app/containers/MessageActions.js +++ b/app/containers/MessageActions.js @@ -9,8 +9,6 @@ import { deleteRequest, editInit, toggleStarRequest, - permalinkRequest, - permalinkClear, togglePinRequest, setInput, actionsHide, @@ -22,11 +20,8 @@ import I18n from '../i18n'; @connect( state => ({ - showActions: state.messages.showActions, actionMessage: state.messages.actionMessage, user: state.login.user, - permissions: state.permissions, - permalink: state.messages.permalink, Message_AllowDeleting: state.settings.Message_AllowDeleting, Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes, Message_AllowEditing: state.settings.Message_AllowEditing, @@ -39,8 +34,6 @@ import I18n from '../i18n'; deleteRequest: message => dispatch(deleteRequest(message)), editInit: message => dispatch(editInit(message)), toggleStarRequest: message => dispatch(toggleStarRequest(message)), - permalinkRequest: message => dispatch(permalinkRequest(message)), - permalinkClear: () => dispatch(permalinkClear()), togglePinRequest: message => dispatch(togglePinRequest(message)), setInput: message => dispatch(setInput(message)), toggleReactionPicker: message => dispatch(toggleReactionPicker(message)) @@ -49,19 +42,14 @@ import I18n from '../i18n'; export default class MessageActions extends React.Component { static propTypes = { actionsHide: PropTypes.func.isRequired, - showActions: PropTypes.bool.isRequired, room: PropTypes.object, actionMessage: PropTypes.object, user: PropTypes.object, - permissions: PropTypes.object.isRequired, deleteRequest: PropTypes.func.isRequired, editInit: PropTypes.func.isRequired, toggleStarRequest: PropTypes.func.isRequired, - permalinkRequest: PropTypes.func.isRequired, - permalinkClear: PropTypes.func.isRequired, togglePinRequest: PropTypes.func.isRequired, setInput: PropTypes.func.isRequired, - permalink: PropTypes.string, toggleReactionPicker: PropTypes.func.isRequired, Message_AllowDeleting: PropTypes.bool, Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number, @@ -73,99 +61,70 @@ export default class MessageActions extends React.Component { constructor(props) { super(props); - this.state = { - copyPermalink: false, - reply: false, - quote: false - }; this.handleActionPress = this.handleActionPress.bind(this); - this.options = ['']; - this.setPermissions(this.props.permissions); - } + this.setPermissions(); - async componentWillReceiveProps(nextProps) { - if (nextProps.showActions !== this.props.showActions && nextProps.showActions) { - const { actionMessage } = nextProps; - // Cancel - this.options = [I18n.t('Cancel')]; - this.CANCEL_INDEX = 0; - // Reply - if (!this.isRoomReadOnly()) { - this.options.push(I18n.t('Reply')); - this.REPLY_INDEX = this.options.length - 1; - } - // Edit - if (this.allowEdit(nextProps)) { - this.options.push(I18n.t('Edit')); - this.EDIT_INDEX = this.options.length - 1; - } - // Permalink - this.options.push(I18n.t('Copy_Permalink')); - this.PERMALINK_INDEX = this.options.length - 1; - // Copy - this.options.push(I18n.t('Copy_Message')); - this.COPY_INDEX = this.options.length - 1; - // Share - this.options.push(I18n.t('Share_Message')); - this.SHARE_INDEX = this.options.length - 1; - // Quote - if (!this.isRoomReadOnly()) { - this.options.push(I18n.t('Quote')); - this.QUOTE_INDEX = this.options.length - 1; - } - // Star - if (this.props.Message_AllowStarring) { - this.options.push(I18n.t(actionMessage.starred ? 'Unstar' : 'Star')); - this.STAR_INDEX = this.options.length - 1; - } - // Pin - if (this.props.Message_AllowPinning) { - this.options.push(I18n.t(actionMessage.pinned ? 'Unpin' : 'Pin')); - this.PIN_INDEX = this.options.length - 1; - } - // Reaction - if (!this.isRoomReadOnly() || this.canReactWhenReadOnly()) { - this.options.push(I18n.t('Add_Reaction')); - this.REACTION_INDEX = this.options.length - 1; - } - // Delete - if (this.allowDelete(nextProps)) { - this.options.push(I18n.t('Delete')); - this.DELETE_INDEX = this.options.length - 1; - } - setTimeout(() => { - this.ActionSheet.show(); - Vibration.vibrate(50); - }); - } else if (this.props.permalink !== nextProps.permalink && nextProps.permalink) { - // copy permalink - if (this.state.copyPermalink) { - this.setState({ copyPermalink: false }); - await Clipboard.setString(nextProps.permalink); - showToast(I18n.t('Permalink_copied_to_clipboard')); - this.props.permalinkClear(); - // quote - } else if (this.state.quote) { - this.setState({ quote: false }); - const msg = `[ ](${ nextProps.permalink }) `; - this.props.setInput({ msg }); + // Cancel + this.options = [I18n.t('Cancel')]; + this.CANCEL_INDEX = 0; - // reply - } else if (this.state.reply) { - this.setState({ reply: false }); - let msg = `[ ](${ nextProps.permalink }) `; - - // if original message wasn't sent by current user and neither from a direct room - if (this.props.user.username !== this.props.actionMessage.u.username && this.props.room.t !== 'd') { - msg += `@${ this.props.actionMessage.u.username } `; - } - this.props.setInput({ msg }); - } + // Reply + if (!this.isRoomReadOnly()) { + this.options.push(I18n.t('Reply')); + this.REPLY_INDEX = this.options.length - 1; } - } - componentDidUpdate() { - this.setPermissions(this.props.permissions); + // Edit + if (this.allowEdit(props)) { + this.options.push(I18n.t('Edit')); + this.EDIT_INDEX = this.options.length - 1; + } + + // Permalink + this.options.push(I18n.t('Copy_Permalink')); + this.PERMALINK_INDEX = this.options.length - 1; + + // Copy + this.options.push(I18n.t('Copy_Message')); + this.COPY_INDEX = this.options.length - 1; + + // Share + this.options.push(I18n.t('Share_Message')); + this.SHARE_INDEX = this.options.length - 1; + + // Quote + if (!this.isRoomReadOnly()) { + this.options.push(I18n.t('Quote')); + this.QUOTE_INDEX = this.options.length - 1; + } + + // Star + if (this.props.Message_AllowStarring) { + this.options.push(I18n.t(props.actionMessage.starred ? 'Unstar' : 'Star')); + this.STAR_INDEX = this.options.length - 1; + } + + // Pin + if (this.props.Message_AllowPinning) { + this.options.push(I18n.t(props.actionMessage.pinned ? 'Unpin' : 'Pin')); + this.PIN_INDEX = this.options.length - 1; + } + + // Reaction + if (!this.isRoomReadOnly() || this.canReactWhenReadOnly()) { + this.options.push(I18n.t('Add_Reaction')); + this.REACTION_INDEX = this.options.length - 1; + } + + // Delete + if (this.allowDelete(props)) { + this.options.push(I18n.t('Delete')); + this.DELETE_INDEX = this.options.length - 1; + } + setTimeout(() => { + this.ActionSheet.show(); + Vibration.vibrate(50); + }); } setPermissions() { @@ -176,6 +135,14 @@ export default class MessageActions extends React.Component { this.hasForceDeletePermission = result[permissions[2]]; } + getPermalink = async(message) => { + try { + return await RocketChat.getPermalink(message); + } catch (error) { + return null; + } + } + isOwn = props => props.actionMessage.u && props.actionMessage.u._id === props.user.id; isRoomReadOnly = () => this.props.room.ro; @@ -272,23 +239,31 @@ export default class MessageActions extends React.Component { this.props.toggleStarRequest(this.props.actionMessage); } - handlePermalink() { - this.setState({ copyPermalink: true }); - this.props.permalinkRequest(this.props.actionMessage); + async handlePermalink() { + const permalink = await this.getPermalink(this.props.actionMessage); + Clipboard.setString(permalink); + showToast(I18n.t('Permalink_copied_to_clipboard')); } handlePin() { this.props.togglePinRequest(this.props.actionMessage); } - handleReply() { - this.setState({ reply: true }); - this.props.permalinkRequest(this.props.actionMessage); + async handleReply() { + const permalink = await this.getPermalink(this.props.actionMessage); + let msg = `[ ](${ permalink }) `; + + // if original message wasn't sent by current user and neither from a direct room + if (this.props.user.username !== this.props.actionMessage.u.username && this.props.room.t !== 'd') { + msg += `@${ this.props.actionMessage.u.username } `; + } + this.props.setInput({ msg }); } - handleQuote() { - this.setState({ quote: true }); - this.props.permalinkRequest(this.props.actionMessage); + async handleQuote() { + const permalink = await this.getPermalink(this.props.actionMessage); + const msg = `[ ](${ permalink }) `; + this.props.setInput({ msg }); } handleReaction() { diff --git a/app/containers/MessageErrorActions.js b/app/containers/MessageErrorActions.js index 4a8a9eeb..3f89bf86 100644 --- a/app/containers/MessageErrorActions.js +++ b/app/containers/MessageErrorActions.js @@ -11,7 +11,6 @@ import I18n from '../i18n'; @connect( state => ({ - showErrorActions: state.messages.showErrorActions, actionMessage: state.messages.actionMessage }), dispatch => ({ @@ -21,7 +20,6 @@ import I18n from '../i18n'; export default class MessageErrorActions extends React.Component { static propTypes = { errorActionsHide: PropTypes.func.isRequired, - showErrorActions: PropTypes.bool.isRequired, actionMessage: PropTypes.object }; @@ -32,12 +30,7 @@ export default class MessageErrorActions extends React.Component { this.CANCEL_INDEX = 0; this.DELETE_INDEX = 1; this.RESEND_INDEX = 2; - } - - componentWillReceiveProps(nextProps) { - if (nextProps.showErrorActions !== this.props.showErrorActions && nextProps.showErrorActions) { - this.ActionSheet.show(); - } + this.ActionSheet.show(); } handleResend = protectedFunction(() => RocketChat.resendMessage(this.props.actionMessage._id)); diff --git a/app/containers/Routes.js b/app/containers/Routes.js index bb20c90b..b4f0e008 100644 --- a/app/containers/Routes.js +++ b/app/containers/Routes.js @@ -57,6 +57,10 @@ export default class Routes extends React.Component { NavigationService.setNavigator(this.navigator); } + componentWillUnmount() { + Linking.removeAllListeners(); + } + handleOpenURL({ url }) { if (url) { url = url.replace(/rocketchat:\/\/|https:\/\/go.rocket.chat\//, ''); diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.js index cfdc21c3..6772c775 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.js @@ -1,26 +1,29 @@ import { InteractionManager } from 'react-native'; -import reduxStore from '../createStore'; -// import { get } from './helpers/rest'; - import database from '../realm'; -import * as actions from '../../actions'; import log from '../../utils/log'; +import defaultPermissions from '../../constants/permissions'; -const getLastMessage = () => { +const getLastUpdate = () => { const setting = database.objects('permissions').sorted('_updatedAt', true)[0]; return setting && setting._updatedAt; }; - export default async function() { try { - const lastMessage = getLastMessage(); - const result = await (!lastMessage ? this.ddp.call('permissions/get') : this.ddp.call('permissions/get', new Date(lastMessage))); - const permissions = this._preparePermissions(result.update || result); - InteractionManager.runAfterInteractions(() => database.write(() => - permissions.forEach(permission => database.create('permissions', permission, true)))); - reduxStore.dispatch(actions.setAllPermissions(this.parsePermissions(permissions))); + const lastUpdate = getLastUpdate(); + const result = await (!lastUpdate ? this.ddp.call('permissions/get') : this.ddp.call('permissions/get', new Date(lastUpdate))); + const permissions = (result.update || result).filter(permission => defaultPermissions.includes(permission._id)); + permissions + .map((permission) => { + permission._updatedAt = new Date(); + permission.roles = permission.roles.map(role => ({ value: role })); + return permission; + }); + + InteractionManager.runAfterInteractions(() => + database.write(() => + permissions.forEach(permission => database.create('permissions', permission, true)))); } catch (e) { log('getPermissions', e); } diff --git a/app/lib/methods/getSettings.js b/app/lib/methods/getSettings.js index 63ed4bd8..ae4fc63e 100644 --- a/app/lib/methods/getSettings.js +++ b/app/lib/methods/getSettings.js @@ -6,21 +6,22 @@ import database from '../realm'; import * as actions from '../../actions'; import log from '../../utils/log'; -const getLastMessage = () => { +const getLastUpdate = () => { const [setting] = database.objects('settings').sorted('_updatedAt', true); return setting && setting._updatedAt; }; export default async function() { try { - const lastMessage = getLastMessage(); - const result = await (!lastMessage ? this.ddp.call('public-settings/get') : this.ddp.call('public-settings/get', new Date(lastMessage))); + const lastUpdate = getLastUpdate(); + const result = await (!lastUpdate ? this.ddp.call('public-settings/get') : this.ddp.call('public-settings/get', new Date(lastUpdate))); const filteredSettings = this._prepareSettings(this._filterSettings(result.update || result)); InteractionManager.runAfterInteractions(() => database.write(() => - filteredSettings.forEach(setting => database.create('settings', setting, true)))); + filteredSettings.forEach(setting => + database.create('settings', { ...setting, _updatedAt: new Date() }, true)))); reduxStore.dispatch(actions.addSettings(this.parseSettings(filteredSettings))); } catch (e) { log('getSettings', e); diff --git a/app/lib/realm.js b/app/lib/realm.js index ee7b8464..260b160a 100644 --- a/app/lib/realm.js +++ b/app/lib/realm.js @@ -106,8 +106,9 @@ const subscriptionSchema = { const usersSchema = { name: 'users', - primaryKey: 'username', + primaryKey: '_id', properties: { + _id: 'string', username: 'string', name: { type: 'string', optional: true }, avatarVersion: { type: 'int', optional: true } diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 42076875..715f2ff4 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -1,10 +1,10 @@ -import { AsyncStorage, Platform } from 'react-native'; +import { AsyncStorage, Platform, InteractionManager } from 'react-native'; import { hashPassword } from 'react-native-meteor/lib/utils'; import foreach from 'lodash/forEach'; import RNFetchBlob from 'react-native-fetch-blob'; import reduxStore from './createStore'; -import settingsType from '../constants/settings'; +import defaultSettings from '../constants/settings'; import messagesStatus from '../constants/messagesStatus'; import database from './realm'; import log from '../utils/log'; @@ -104,7 +104,7 @@ const RocketChat = { reduxStore.dispatch(setActiveUser(this.activeUsers)); this._setUserTimer = null; return this.activeUsers = {}; - }, 3000); + }, 5000); const activeUser = reduxStore.getState().activeUsers[ddpMessage.id]; if (!ddpMessage.fields) { @@ -190,16 +190,13 @@ const RocketChat = { // we're using it only because our image cache lib doesn't support clear cache if (ddpMessage.fields && ddpMessage.fields.eventName === 'updateAvatar') { const { args } = ddpMessage.fields; - database.write(() => { + InteractionManager.runAfterInteractions(() => args.forEach((arg) => { const user = database.objects('users').filtered('username = $0', arg.username); - if (!user.length) { - database.create('users', { username: arg.username, avatarVersion: 0 }); - } else { + if (user.length > 0) { user[0].avatarVersion += 1; } - }); - }); + })); } }); @@ -687,27 +684,17 @@ const RocketChat = { getPermissions, getCustomEmoji, parseSettings: settings => settings.reduce((ret, item) => { - ret[item._id] = item[settingsType[item.type]] || item.valueAsString || item.valueAsNumber || + ret[item._id] = item[defaultSettings[item._id].type] || item.valueAsString || item.valueAsNumber || item.valueAsBoolean || item.value; return ret; }, {}), _prepareSettings(settings) { return settings.map((setting) => { - setting[settingsType[setting.type]] = setting.value; + setting[defaultSettings[setting._id].type] = setting.value; return setting; }); }, - _filterSettings: settings => settings.filter(setting => settingsType[setting.type] && setting.value), - parsePermissions: permissions => permissions.reduce((ret, item) => { - ret[item._id] = item.roles.reduce((roleRet, role) => [...roleRet, role.value], []); - return ret; - }, {}), - _preparePermissions(permissions) { - permissions.forEach((permission) => { - permission.roles = permission.roles.map(role => ({ value: role })); - }); - return permissions; - }, + _filterSettings: settings => settings.filter(setting => defaultSettings[setting._id] && setting.value), parseEmojis: emojis => emojis.reduce((ret, item) => { ret[item.name] = item.extension; item.aliases.forEach((alias) => { @@ -843,22 +830,26 @@ const RocketChat = { hasPermission(permissions, rid) { // get the room from realm const room = database.objects('subscriptions').filtered('rid = $0', rid)[0]; + // get permissions from realm + const permissionsFiltered = database.objects('permissions') + .filter(permission => permissions.includes(permission._id)); // get room roles const { roles } = room; // transform room roles to array const roomRoles = Array.from(Object.keys(roles), i => roles[i].value); // get user roles on the server from redux const userRoles = reduxStore.getState().login.user.roles || []; - // get all permissions from redux - const allPermissions = reduxStore.getState().permissions; // merge both roles const mergedRoles = [...new Set([...roomRoles, ...userRoles])]; // return permissions in object format // e.g. { 'edit-room': true, 'set-readonly': false } return permissions.reduce((result, permission) => { - result[permission] = returnAnArray(allPermissions[permission]) - .some(item => mergedRoles.indexOf(item) !== -1); + result[permission] = false; + const permissionFound = permissionsFiltered.find(p => p._id === permission); + if (permissionFound) { + result[permission] = returnAnArray(permissionFound.roles).some(r => mergedRoles.includes(r.value)); + } return result; }, {}); }, diff --git a/app/reducers/index.js b/app/reducers/index.js index 6f66738e..27d07f44 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -10,7 +10,6 @@ import navigator from './navigator'; import selectedUsers from './selectedUsers'; import createChannel from './createChannel'; import app from './app'; -import permissions from './permissions'; import customEmojis from './customEmojis'; import activeUsers from './activeUsers'; import roles from './roles'; @@ -32,7 +31,6 @@ export default combineReducers({ app, room, rooms, - permissions, customEmojis, activeUsers, roles, diff --git a/app/reducers/messages.js b/app/reducers/messages.js index ae17f395..caec481d 100644 --- a/app/reducers/messages.js +++ b/app/reducers/messages.js @@ -6,7 +6,6 @@ const initialState = { message: {}, actionMessage: {}, editing: false, - permalink: '', showActions: false, showErrorActions: false, showReactionPicker: false @@ -77,16 +76,6 @@ export default function messages(state = initialState, action) { message: {}, editing: false }; - case types.MESSAGES.PERMALINK_SUCCESS: - return { - ...state, - permalink: action.permalink - }; - case types.MESSAGES.PERMALINK_CLEAR: - return { - ...state, - permalink: '' - }; case types.MESSAGES.SET_INPUT: return { ...state, diff --git a/app/reducers/permissions.js b/app/reducers/permissions.js deleted file mode 100644 index 58d74990..00000000 --- a/app/reducers/permissions.js +++ /dev/null @@ -1,24 +0,0 @@ -import * as types from '../constants/types'; - -const initialState = { - permissions: {} -}; - - -export default function permissions(state = initialState.permissions, action) { - if (action.type === types.SET_ALL_PERMISSIONS) { - return { - ...state, - ...action.payload - }; - } - - if (action.type === types.ADD_PERMISSIONS) { - return { - ...state, - ...action.payload - }; - } - - return state; -} diff --git a/app/sagas/messages.js b/app/sagas/messages.js index 0dff63c6..5792ba32 100644 --- a/app/sagas/messages.js +++ b/app/sagas/messages.js @@ -10,8 +10,6 @@ import { editFailure, toggleStarSuccess, toggleStarFailure, - permalinkSuccess, - permalinkFailure, togglePinSuccess, togglePinFailure, setInput @@ -24,7 +22,6 @@ import log from '../utils/log'; const deleteMessage = message => RocketChat.deleteMessage(message); const editMessage = message => RocketChat.editMessage(message); const toggleStarMessage = message => RocketChat.toggleStarMessage(message); -const getPermalink = message => RocketChat.getPermalink(message); const togglePinMessage = message => RocketChat.togglePinMessage(message); const get = function* get({ room }) { @@ -68,15 +65,6 @@ const handleToggleStarRequest = function* handleToggleStarRequest({ message }) { } }; -const handlePermalinkRequest = function* handlePermalinkRequest({ message }) { - try { - const permalink = yield call(getPermalink, message); - yield put(permalinkSuccess(permalink)); - } catch (error) { - yield put(permalinkFailure(error)); - } -}; - const handleTogglePinRequest = function* handleTogglePinRequest({ message }) { try { yield call(togglePinMessage, message); @@ -110,7 +98,6 @@ const root = function* root() { yield takeLatest(MESSAGES.DELETE_REQUEST, handleDeleteRequest); yield takeLatest(MESSAGES.EDIT_REQUEST, handleEditRequest); yield takeLatest(MESSAGES.TOGGLE_STAR_REQUEST, handleToggleStarRequest); - yield takeLatest(MESSAGES.PERMALINK_REQUEST, handlePermalinkRequest); yield takeLatest(MESSAGES.TOGGLE_PIN_REQUEST, handleTogglePinRequest); yield takeLatest(MESSAGES.REPLY_BROADCAST, handleReplyBroadcast); }; diff --git a/app/sagas/rooms.js b/app/sagas/rooms.js index c3808310..0f9ceaf1 100644 --- a/app/sagas/rooms.js +++ b/app/sagas/rooms.js @@ -6,7 +6,7 @@ import { BACKGROUND } from 'redux-enhancer-react-native-appstate'; import * as types from '../actions/actionsTypes'; // import { roomsSuccess, roomsFailure } from '../actions/rooms'; import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room'; -import { messagesRequest } from '../actions/messages'; +import { messagesRequest, editCancel } from '../actions/messages'; import RocketChat from '../lib/rocketchat'; import database from '../lib/realm'; import * as NavigationService from '../containers/routes/NavigationService'; @@ -92,6 +92,7 @@ const watchRoomOpen = function* watchRoomOpen({ room }) { }); cancel(thread); sub.stop(); + yield put(editCancel()); // subscriptions.forEach((sub) => { // sub.unsubscribe().catch(e => alert(e)); diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index a95a9517..973677f2 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -26,8 +26,6 @@ const selectServer = function* selectServer({ server }) { // yield AsyncStorage.removeItem(RocketChat.TOKEN_KEY); const settings = database.objects('settings'); yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length)))); - const permissions = database.objects('permissions'); - yield put(actions.setAllPermissions(RocketChat.parsePermissions(permissions.slice(0, permissions.length)))); const emojis = database.objects('customEmojis'); yield put(actions.setCustomEmojis(RocketChat.parseEmojis(emojis.slice(0, emojis.length)))); const roles = database.objects('roles'); diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js index 0047354d..b0ed341e 100644 --- a/app/views/RoomInfoView/index.js +++ b/app/views/RoomInfoView/index.js @@ -31,7 +31,6 @@ const getRoomTitle = room => (room.t === 'd' ? @connect(state => ({ baseUrl: state.settings.Site_Url || state.server ? state.server.server : '', user: state.login.user, - permissions: state.permissions, activeUsers: state.activeUsers, Message_TimeFormat: state.settings.Message_TimeFormat, roles: state.roles @@ -123,8 +122,12 @@ export default class RoomInfoView extends LoggedView { } getFullUserData = async(username) => { - const result = await RocketChat.subscribe('fullUserData', username); - this.sub = result; + try { + const result = await RocketChat.subscribe('fullUserData', username); + this.sub = result; + } catch (e) { + log('getFullUserData', e); + } } isDirect = () => this.state.room.t === 'd'; diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index db86edd1..bb56f99d 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -9,7 +9,7 @@ import LoggedView from '../View'; import { List } from './ListView'; // import * as actions from '../../actions'; import { openRoom, setLastOpen } from '../../actions/room'; -import { editCancel, toggleReactionPicker, actionsShow } from '../../actions/messages'; +import { toggleReactionPicker, actionsShow } from '../../actions/messages'; import database from '../../lib/realm'; import RocketChat from '../../lib/rocketchat'; import Message from '../../containers/message'; @@ -22,6 +22,7 @@ import ReactionPicker from './ReactionPicker'; import styles from './styles'; import log from '../../utils/log'; import I18n from '../../i18n'; +import debounce from '../../utils/debounce'; @connect( state => ({ @@ -29,12 +30,14 @@ import I18n from '../../i18n'; // Message_TimeFormat: state.settings.Message_TimeFormat, loading: state.messages.isFetching, user: state.login.user, - actionMessage: state.messages.actionMessage + actionMessage: state.messages.actionMessage, + showActions: state.messages.showActions, + showErrorActions: state.messages.showErrorActions }), dispatch => ({ // actions: bindActionCreators(actions, dispatch), openRoom: room => dispatch(openRoom(room)), - editCancel: () => dispatch(editCancel()), + // editCancel: () => dispatch(editCancel()), setLastOpen: date => dispatch(setLastOpen(date)), toggleReactionPicker: message => dispatch(toggleReactionPicker(message)), actionsShow: actionMessage => dispatch(actionsShow(actionMessage)) @@ -46,7 +49,7 @@ export default class RoomView extends LoggedView { openRoom: PropTypes.func.isRequired, setLastOpen: PropTypes.func.isRequired, user: PropTypes.object.isRequired, - editCancel: PropTypes.func, + // editCancel: PropTypes.func, rid: PropTypes.string, name: PropTypes.string, // Site_Url: PropTypes.string, @@ -85,10 +88,10 @@ export default class RoomView extends LoggedView { } componentWillUnmount() { this.rooms.removeAllListeners(); - this.props.editCancel(); + this.onEndReached.stop(); } - onEndReached = (lastRowData) => { + onEndReached = debounce((lastRowData) => { if (!lastRowData) { this.setState({ end: true }); return; @@ -102,7 +105,7 @@ export default class RoomView extends LoggedView { log('RoomView.onEndReached', e); } }); - } + }) onMessageLongPress = (message) => { this.props.actionsShow(message); @@ -186,8 +189,6 @@ export default class RoomView extends LoggedView { /> ); - // renderSeparator = () => ; - renderFooter = () => { if (!this.state.joined) { return ( @@ -232,8 +233,8 @@ export default class RoomView extends LoggedView { renderRow={this.renderItem} /> {this.renderFooter()} - {this.state.room._id ? : null} - + {this.state.room._id && this.props.showActions ? : null} + {this.props.showErrorActions ? : null} ); diff --git a/app/views/RoomsListView/Header/index.js b/app/views/RoomsListView/Header/index.js index f9272ac2..180f455a 100644 --- a/app/views/RoomsListView/Header/index.js +++ b/app/views/RoomsListView/Header/index.js @@ -6,6 +6,7 @@ import { connect } from 'react-redux'; import Modal from 'react-native-modal'; import FastImage from 'react-native-fast-image'; import { HeaderBackButton } from 'react-navigation'; +import equal from 'deep-equal'; import Avatar from '../../../containers/Avatar'; import Status from '../../../containers/status'; @@ -50,7 +51,7 @@ const title = (offline, connecting, authenticating, logged) => { setSearch: searchText => dispatch(setSearch(searchText)) })) -export default class RoomsListHeaderView extends React.PureComponent { +export default class RoomsListHeaderView extends React.Component { static propTypes = { navigation: PropTypes.object.isRequired, user: PropTypes.object.isRequired, @@ -67,6 +68,13 @@ export default class RoomsListHeaderView extends React.PureComponent { }; } + shouldComponentUpdate(nextProps) { + if (!equal(this.props, nextProps)) { + return true; + } + return false; + } + onPressModalButton(status) { try { RocketChat.setUserPresenceDefaultStatus(status); diff --git a/e2e/09-roominfo.spec.js b/e2e/09-roominfo.spec.js index 06037a7d..e4389194 100644 --- a/e2e/09-roominfo.spec.js +++ b/e2e/09-roominfo.spec.js @@ -318,7 +318,7 @@ describe('Room info screen', () => { await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible(); }); - after(async() => { + afterEach(async() => { takeScreenshot(); }); }); diff --git a/package-lock.json b/package-lock.json index 1458aa30..fa005232 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15005,7 +15005,7 @@ "integrity": "sha512-Bvq4FQPMAFijqjqNX6TxLgKOwdbruM6GvFwF9rb+mowbaFZVoYbHTKLaAbdPlrblgaZKWyOuuxBUoDx41+Xktg==", "requires": { "prop-types": "15.6.1", - "react-native-animatable": "1.2.4" + "react-native-animatable": "1.3.0" } } }