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
This commit is contained in:
Diego Mello 2018-06-18 10:30:36 -03:00 committed by Guilherme Gazzo
parent da173275ce
commit 9e4ca34a80
24 changed files with 254 additions and 282 deletions

View File

@ -62,10 +62,6 @@ export const MESSAGES = createRequestTypes('MESSAGES', [
'TOGGLE_STAR_REQUEST', 'TOGGLE_STAR_REQUEST',
'TOGGLE_STAR_SUCCESS', 'TOGGLE_STAR_SUCCESS',
'TOGGLE_STAR_FAILURE', 'TOGGLE_STAR_FAILURE',
'PERMALINK_REQUEST',
'PERMALINK_SUCCESS',
'PERMALINK_FAILURE',
'PERMALINK_CLEAR',
'TOGGLE_PIN_REQUEST', 'TOGGLE_PIN_REQUEST',
'TOGGLE_PIN_SUCCESS', 'TOGGLE_PIN_SUCCESS',
'TOGGLE_PIN_FAILURE', 'TOGGLE_PIN_FAILURE',

View File

@ -32,13 +32,6 @@ export function setAllSettings(settings) {
}; };
} }
export function setAllPermissions(permissions) {
return {
type: types.SET_ALL_PERMISSIONS,
payload: permissions
};
}
export function setCustomEmojis(emojis) { export function setCustomEmojis(emojis) {
return { return {
type: types.SET_CUSTOM_EMOJIS, type: types.SET_CUSTOM_EMOJIS,

View File

@ -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) { export function togglePinRequest(message) {
return { return {
type: types.MESSAGES.TOGGLE_PIN_REQUEST, type: types.MESSAGES.TOGGLE_PIN_REQUEST,

View File

@ -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'
];

View File

@ -1,15 +1,80 @@
export default { export default {
boolean: 'valueAsBoolean', Accounts_CustomFields: {
int: 'valueAsNumber', type: 'valueAsString'
string: 'valueAsString', },
select: 'valueAsString', Accounts_EmailOrUsernamePlaceholder: {
code: 'valueAsString', type: 'valueAsString'
relativeUrl: 'valueAsString', },
language: 'valueAsString', Accounts_NamePlaceholder: {
action: 'valueAsString', type: 'valueAsString'
password: 'valueAsString', },
// asset: ' Object', Accounts_OAuth_Facebook: {
color: 'valueAsString', type: 'valueAsBoolean'
font: 'valueAsString', },
roomPick: 'valueAsString' 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'
}
}; };

View File

@ -1,5 +1,4 @@
export const SET_CURRENT_SERVER = 'SET_CURRENT_SERVER'; export const SET_CURRENT_SERVER = 'SET_CURRENT_SERVER';
export const SET_ALL_SETTINGS = 'SET_ALL_SETTINGS'; 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 SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
export const ADD_SETTINGS = 'ADD_SETTINGS'; export const ADD_SETTINGS = 'ADD_SETTINGS';

View File

@ -9,8 +9,6 @@ import {
deleteRequest, deleteRequest,
editInit, editInit,
toggleStarRequest, toggleStarRequest,
permalinkRequest,
permalinkClear,
togglePinRequest, togglePinRequest,
setInput, setInput,
actionsHide, actionsHide,
@ -22,11 +20,8 @@ import I18n from '../i18n';
@connect( @connect(
state => ({ state => ({
showActions: state.messages.showActions,
actionMessage: state.messages.actionMessage, actionMessage: state.messages.actionMessage,
user: state.login.user, user: state.login.user,
permissions: state.permissions,
permalink: state.messages.permalink,
Message_AllowDeleting: state.settings.Message_AllowDeleting, Message_AllowDeleting: state.settings.Message_AllowDeleting,
Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes, Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes,
Message_AllowEditing: state.settings.Message_AllowEditing, Message_AllowEditing: state.settings.Message_AllowEditing,
@ -39,8 +34,6 @@ import I18n from '../i18n';
deleteRequest: message => dispatch(deleteRequest(message)), deleteRequest: message => dispatch(deleteRequest(message)),
editInit: message => dispatch(editInit(message)), editInit: message => dispatch(editInit(message)),
toggleStarRequest: message => dispatch(toggleStarRequest(message)), toggleStarRequest: message => dispatch(toggleStarRequest(message)),
permalinkRequest: message => dispatch(permalinkRequest(message)),
permalinkClear: () => dispatch(permalinkClear()),
togglePinRequest: message => dispatch(togglePinRequest(message)), togglePinRequest: message => dispatch(togglePinRequest(message)),
setInput: message => dispatch(setInput(message)), setInput: message => dispatch(setInput(message)),
toggleReactionPicker: message => dispatch(toggleReactionPicker(message)) toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
@ -49,19 +42,14 @@ import I18n from '../i18n';
export default class MessageActions extends React.Component { export default class MessageActions extends React.Component {
static propTypes = { static propTypes = {
actionsHide: PropTypes.func.isRequired, actionsHide: PropTypes.func.isRequired,
showActions: PropTypes.bool.isRequired,
room: PropTypes.object, room: PropTypes.object,
actionMessage: PropTypes.object, actionMessage: PropTypes.object,
user: PropTypes.object, user: PropTypes.object,
permissions: PropTypes.object.isRequired,
deleteRequest: PropTypes.func.isRequired, deleteRequest: PropTypes.func.isRequired,
editInit: PropTypes.func.isRequired, editInit: PropTypes.func.isRequired,
toggleStarRequest: PropTypes.func.isRequired, toggleStarRequest: PropTypes.func.isRequired,
permalinkRequest: PropTypes.func.isRequired,
permalinkClear: PropTypes.func.isRequired,
togglePinRequest: PropTypes.func.isRequired, togglePinRequest: PropTypes.func.isRequired,
setInput: PropTypes.func.isRequired, setInput: PropTypes.func.isRequired,
permalink: PropTypes.string,
toggleReactionPicker: PropTypes.func.isRequired, toggleReactionPicker: PropTypes.func.isRequired,
Message_AllowDeleting: PropTypes.bool, Message_AllowDeleting: PropTypes.bool,
Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number, Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number,
@ -73,99 +61,70 @@ export default class MessageActions extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {
copyPermalink: false,
reply: false,
quote: false
};
this.handleActionPress = this.handleActionPress.bind(this); this.handleActionPress = this.handleActionPress.bind(this);
this.options = ['']; this.setPermissions();
this.setPermissions(this.props.permissions);
}
async componentWillReceiveProps(nextProps) { // Cancel
if (nextProps.showActions !== this.props.showActions && nextProps.showActions) { this.options = [I18n.t('Cancel')];
const { actionMessage } = nextProps; this.CANCEL_INDEX = 0;
// 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 });
// reply // Reply
} else if (this.state.reply) { if (!this.isRoomReadOnly()) {
this.setState({ reply: false }); this.options.push(I18n.t('Reply'));
let msg = `[ ](${ nextProps.permalink }) `; this.REPLY_INDEX = this.options.length - 1;
// 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 });
}
} }
}
componentDidUpdate() { // Edit
this.setPermissions(this.props.permissions); 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() { setPermissions() {
@ -176,6 +135,14 @@ export default class MessageActions extends React.Component {
this.hasForceDeletePermission = result[permissions[2]]; 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; isOwn = props => props.actionMessage.u && props.actionMessage.u._id === props.user.id;
isRoomReadOnly = () => this.props.room.ro; isRoomReadOnly = () => this.props.room.ro;
@ -272,23 +239,31 @@ export default class MessageActions extends React.Component {
this.props.toggleStarRequest(this.props.actionMessage); this.props.toggleStarRequest(this.props.actionMessage);
} }
handlePermalink() { async handlePermalink() {
this.setState({ copyPermalink: true }); const permalink = await this.getPermalink(this.props.actionMessage);
this.props.permalinkRequest(this.props.actionMessage); Clipboard.setString(permalink);
showToast(I18n.t('Permalink_copied_to_clipboard'));
} }
handlePin() { handlePin() {
this.props.togglePinRequest(this.props.actionMessage); this.props.togglePinRequest(this.props.actionMessage);
} }
handleReply() { async handleReply() {
this.setState({ reply: true }); const permalink = await this.getPermalink(this.props.actionMessage);
this.props.permalinkRequest(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() { async handleQuote() {
this.setState({ quote: true }); const permalink = await this.getPermalink(this.props.actionMessage);
this.props.permalinkRequest(this.props.actionMessage); const msg = `[ ](${ permalink }) `;
this.props.setInput({ msg });
} }
handleReaction() { handleReaction() {

View File

@ -11,7 +11,6 @@ import I18n from '../i18n';
@connect( @connect(
state => ({ state => ({
showErrorActions: state.messages.showErrorActions,
actionMessage: state.messages.actionMessage actionMessage: state.messages.actionMessage
}), }),
dispatch => ({ dispatch => ({
@ -21,7 +20,6 @@ import I18n from '../i18n';
export default class MessageErrorActions extends React.Component { export default class MessageErrorActions extends React.Component {
static propTypes = { static propTypes = {
errorActionsHide: PropTypes.func.isRequired, errorActionsHide: PropTypes.func.isRequired,
showErrorActions: PropTypes.bool.isRequired,
actionMessage: PropTypes.object actionMessage: PropTypes.object
}; };
@ -32,12 +30,7 @@ export default class MessageErrorActions extends React.Component {
this.CANCEL_INDEX = 0; this.CANCEL_INDEX = 0;
this.DELETE_INDEX = 1; this.DELETE_INDEX = 1;
this.RESEND_INDEX = 2; this.RESEND_INDEX = 2;
} this.ActionSheet.show();
componentWillReceiveProps(nextProps) {
if (nextProps.showErrorActions !== this.props.showErrorActions && nextProps.showErrorActions) {
this.ActionSheet.show();
}
} }
handleResend = protectedFunction(() => RocketChat.resendMessage(this.props.actionMessage._id)); handleResend = protectedFunction(() => RocketChat.resendMessage(this.props.actionMessage._id));

View File

@ -57,6 +57,10 @@ export default class Routes extends React.Component {
NavigationService.setNavigator(this.navigator); NavigationService.setNavigator(this.navigator);
} }
componentWillUnmount() {
Linking.removeAllListeners();
}
handleOpenURL({ url }) { handleOpenURL({ url }) {
if (url) { if (url) {
url = url.replace(/rocketchat:\/\/|https:\/\/go.rocket.chat\//, ''); url = url.replace(/rocketchat:\/\/|https:\/\/go.rocket.chat\//, '');

View File

@ -1,26 +1,29 @@
import { InteractionManager } from 'react-native'; import { InteractionManager } from 'react-native';
import reduxStore from '../createStore';
// import { get } from './helpers/rest';
import database from '../realm'; import database from '../realm';
import * as actions from '../../actions';
import log from '../../utils/log'; import log from '../../utils/log';
import defaultPermissions from '../../constants/permissions';
const getLastMessage = () => { const getLastUpdate = () => {
const setting = database.objects('permissions').sorted('_updatedAt', true)[0]; const setting = database.objects('permissions').sorted('_updatedAt', true)[0];
return setting && setting._updatedAt; return setting && setting._updatedAt;
}; };
export default async function() { export default async function() {
try { try {
const lastMessage = getLastMessage(); const lastUpdate = getLastUpdate();
const result = await (!lastMessage ? this.ddp.call('permissions/get') : this.ddp.call('permissions/get', new Date(lastMessage))); const result = await (!lastUpdate ? this.ddp.call('permissions/get') : this.ddp.call('permissions/get', new Date(lastUpdate)));
const permissions = this._preparePermissions(result.update || result); const permissions = (result.update || result).filter(permission => defaultPermissions.includes(permission._id));
InteractionManager.runAfterInteractions(() => database.write(() => permissions
permissions.forEach(permission => database.create('permissions', permission, true)))); .map((permission) => {
reduxStore.dispatch(actions.setAllPermissions(this.parsePermissions(permissions))); 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) { } catch (e) {
log('getPermissions', e); log('getPermissions', e);
} }

View File

@ -6,21 +6,22 @@ import database from '../realm';
import * as actions from '../../actions'; import * as actions from '../../actions';
import log from '../../utils/log'; import log from '../../utils/log';
const getLastMessage = () => { const getLastUpdate = () => {
const [setting] = database.objects('settings').sorted('_updatedAt', true); const [setting] = database.objects('settings').sorted('_updatedAt', true);
return setting && setting._updatedAt; return setting && setting._updatedAt;
}; };
export default async function() { export default async function() {
try { try {
const lastMessage = getLastMessage(); const lastUpdate = getLastUpdate();
const result = await (!lastMessage ? this.ddp.call('public-settings/get') : this.ddp.call('public-settings/get', new Date(lastMessage))); 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)); const filteredSettings = this._prepareSettings(this._filterSettings(result.update || result));
InteractionManager.runAfterInteractions(() => InteractionManager.runAfterInteractions(() =>
database.write(() => 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))); reduxStore.dispatch(actions.addSettings(this.parseSettings(filteredSettings)));
} catch (e) { } catch (e) {
log('getSettings', e); log('getSettings', e);

View File

@ -106,8 +106,9 @@ const subscriptionSchema = {
const usersSchema = { const usersSchema = {
name: 'users', name: 'users',
primaryKey: 'username', primaryKey: '_id',
properties: { properties: {
_id: 'string',
username: 'string', username: 'string',
name: { type: 'string', optional: true }, name: { type: 'string', optional: true },
avatarVersion: { type: 'int', optional: true } avatarVersion: { type: 'int', optional: true }

View File

@ -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 { hashPassword } from 'react-native-meteor/lib/utils';
import foreach from 'lodash/forEach'; import foreach from 'lodash/forEach';
import RNFetchBlob from 'react-native-fetch-blob'; import RNFetchBlob from 'react-native-fetch-blob';
import reduxStore from './createStore'; import reduxStore from './createStore';
import settingsType from '../constants/settings'; import defaultSettings from '../constants/settings';
import messagesStatus from '../constants/messagesStatus'; import messagesStatus from '../constants/messagesStatus';
import database from './realm'; import database from './realm';
import log from '../utils/log'; import log from '../utils/log';
@ -104,7 +104,7 @@ const RocketChat = {
reduxStore.dispatch(setActiveUser(this.activeUsers)); reduxStore.dispatch(setActiveUser(this.activeUsers));
this._setUserTimer = null; this._setUserTimer = null;
return this.activeUsers = {}; return this.activeUsers = {};
}, 3000); }, 5000);
const activeUser = reduxStore.getState().activeUsers[ddpMessage.id]; const activeUser = reduxStore.getState().activeUsers[ddpMessage.id];
if (!ddpMessage.fields) { if (!ddpMessage.fields) {
@ -190,16 +190,13 @@ const RocketChat = {
// we're using it only because our image cache lib doesn't support clear cache // we're using it only because our image cache lib doesn't support clear cache
if (ddpMessage.fields && ddpMessage.fields.eventName === 'updateAvatar') { if (ddpMessage.fields && ddpMessage.fields.eventName === 'updateAvatar') {
const { args } = ddpMessage.fields; const { args } = ddpMessage.fields;
database.write(() => { InteractionManager.runAfterInteractions(() =>
args.forEach((arg) => { args.forEach((arg) => {
const user = database.objects('users').filtered('username = $0', arg.username); const user = database.objects('users').filtered('username = $0', arg.username);
if (!user.length) { if (user.length > 0) {
database.create('users', { username: arg.username, avatarVersion: 0 });
} else {
user[0].avatarVersion += 1; user[0].avatarVersion += 1;
} }
}); }));
});
} }
}); });
@ -687,27 +684,17 @@ const RocketChat = {
getPermissions, getPermissions,
getCustomEmoji, getCustomEmoji,
parseSettings: settings => settings.reduce((ret, item) => { 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; item.valueAsBoolean || item.value;
return ret; return ret;
}, {}), }, {}),
_prepareSettings(settings) { _prepareSettings(settings) {
return settings.map((setting) => { return settings.map((setting) => {
setting[settingsType[setting.type]] = setting.value; setting[defaultSettings[setting._id].type] = setting.value;
return setting; return setting;
}); });
}, },
_filterSettings: settings => settings.filter(setting => settingsType[setting.type] && setting.value), _filterSettings: settings => settings.filter(setting => defaultSettings[setting._id] && 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;
},
parseEmojis: emojis => emojis.reduce((ret, item) => { parseEmojis: emojis => emojis.reduce((ret, item) => {
ret[item.name] = item.extension; ret[item.name] = item.extension;
item.aliases.forEach((alias) => { item.aliases.forEach((alias) => {
@ -843,22 +830,26 @@ const RocketChat = {
hasPermission(permissions, rid) { hasPermission(permissions, rid) {
// get the room from realm // get the room from realm
const room = database.objects('subscriptions').filtered('rid = $0', rid)[0]; 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 // get room roles
const { roles } = room; const { roles } = room;
// transform room roles to array // transform room roles to array
const roomRoles = Array.from(Object.keys(roles), i => roles[i].value); const roomRoles = Array.from(Object.keys(roles), i => roles[i].value);
// get user roles on the server from redux // get user roles on the server from redux
const userRoles = reduxStore.getState().login.user.roles || []; const userRoles = reduxStore.getState().login.user.roles || [];
// get all permissions from redux
const allPermissions = reduxStore.getState().permissions;
// merge both roles // merge both roles
const mergedRoles = [...new Set([...roomRoles, ...userRoles])]; const mergedRoles = [...new Set([...roomRoles, ...userRoles])];
// return permissions in object format // return permissions in object format
// e.g. { 'edit-room': true, 'set-readonly': false } // e.g. { 'edit-room': true, 'set-readonly': false }
return permissions.reduce((result, permission) => { return permissions.reduce((result, permission) => {
result[permission] = returnAnArray(allPermissions[permission]) result[permission] = false;
.some(item => mergedRoles.indexOf(item) !== -1); const permissionFound = permissionsFiltered.find(p => p._id === permission);
if (permissionFound) {
result[permission] = returnAnArray(permissionFound.roles).some(r => mergedRoles.includes(r.value));
}
return result; return result;
}, {}); }, {});
}, },

View File

@ -10,7 +10,6 @@ import navigator from './navigator';
import selectedUsers from './selectedUsers'; import selectedUsers from './selectedUsers';
import createChannel from './createChannel'; import createChannel from './createChannel';
import app from './app'; import app from './app';
import permissions from './permissions';
import customEmojis from './customEmojis'; import customEmojis from './customEmojis';
import activeUsers from './activeUsers'; import activeUsers from './activeUsers';
import roles from './roles'; import roles from './roles';
@ -32,7 +31,6 @@ export default combineReducers({
app, app,
room, room,
rooms, rooms,
permissions,
customEmojis, customEmojis,
activeUsers, activeUsers,
roles, roles,

View File

@ -6,7 +6,6 @@ const initialState = {
message: {}, message: {},
actionMessage: {}, actionMessage: {},
editing: false, editing: false,
permalink: '',
showActions: false, showActions: false,
showErrorActions: false, showErrorActions: false,
showReactionPicker: false showReactionPicker: false
@ -77,16 +76,6 @@ export default function messages(state = initialState, action) {
message: {}, message: {},
editing: false 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: case types.MESSAGES.SET_INPUT:
return { return {
...state, ...state,

View File

@ -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;
}

View File

@ -10,8 +10,6 @@ import {
editFailure, editFailure,
toggleStarSuccess, toggleStarSuccess,
toggleStarFailure, toggleStarFailure,
permalinkSuccess,
permalinkFailure,
togglePinSuccess, togglePinSuccess,
togglePinFailure, togglePinFailure,
setInput setInput
@ -24,7 +22,6 @@ import log from '../utils/log';
const deleteMessage = message => RocketChat.deleteMessage(message); const deleteMessage = message => RocketChat.deleteMessage(message);
const editMessage = message => RocketChat.editMessage(message); const editMessage = message => RocketChat.editMessage(message);
const toggleStarMessage = message => RocketChat.toggleStarMessage(message); const toggleStarMessage = message => RocketChat.toggleStarMessage(message);
const getPermalink = message => RocketChat.getPermalink(message);
const togglePinMessage = message => RocketChat.togglePinMessage(message); const togglePinMessage = message => RocketChat.togglePinMessage(message);
const get = function* get({ room }) { 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 }) { const handleTogglePinRequest = function* handleTogglePinRequest({ message }) {
try { try {
yield call(togglePinMessage, message); yield call(togglePinMessage, message);
@ -110,7 +98,6 @@ const root = function* root() {
yield takeLatest(MESSAGES.DELETE_REQUEST, handleDeleteRequest); yield takeLatest(MESSAGES.DELETE_REQUEST, handleDeleteRequest);
yield takeLatest(MESSAGES.EDIT_REQUEST, handleEditRequest); yield takeLatest(MESSAGES.EDIT_REQUEST, handleEditRequest);
yield takeLatest(MESSAGES.TOGGLE_STAR_REQUEST, handleToggleStarRequest); yield takeLatest(MESSAGES.TOGGLE_STAR_REQUEST, handleToggleStarRequest);
yield takeLatest(MESSAGES.PERMALINK_REQUEST, handlePermalinkRequest);
yield takeLatest(MESSAGES.TOGGLE_PIN_REQUEST, handleTogglePinRequest); yield takeLatest(MESSAGES.TOGGLE_PIN_REQUEST, handleTogglePinRequest);
yield takeLatest(MESSAGES.REPLY_BROADCAST, handleReplyBroadcast); yield takeLatest(MESSAGES.REPLY_BROADCAST, handleReplyBroadcast);
}; };

View File

@ -6,7 +6,7 @@ import { BACKGROUND } from 'redux-enhancer-react-native-appstate';
import * as types from '../actions/actionsTypes'; import * as types from '../actions/actionsTypes';
// import { roomsSuccess, roomsFailure } from '../actions/rooms'; // import { roomsSuccess, roomsFailure } from '../actions/rooms';
import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room'; import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room';
import { messagesRequest } from '../actions/messages'; import { messagesRequest, editCancel } from '../actions/messages';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import database from '../lib/realm'; import database from '../lib/realm';
import * as NavigationService from '../containers/routes/NavigationService'; import * as NavigationService from '../containers/routes/NavigationService';
@ -92,6 +92,7 @@ const watchRoomOpen = function* watchRoomOpen({ room }) {
}); });
cancel(thread); cancel(thread);
sub.stop(); sub.stop();
yield put(editCancel());
// subscriptions.forEach((sub) => { // subscriptions.forEach((sub) => {
// sub.unsubscribe().catch(e => alert(e)); // sub.unsubscribe().catch(e => alert(e));

View File

@ -26,8 +26,6 @@ const selectServer = function* selectServer({ server }) {
// yield AsyncStorage.removeItem(RocketChat.TOKEN_KEY); // yield AsyncStorage.removeItem(RocketChat.TOKEN_KEY);
const settings = database.objects('settings'); const settings = database.objects('settings');
yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length)))); 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'); const emojis = database.objects('customEmojis');
yield put(actions.setCustomEmojis(RocketChat.parseEmojis(emojis.slice(0, emojis.length)))); yield put(actions.setCustomEmojis(RocketChat.parseEmojis(emojis.slice(0, emojis.length))));
const roles = database.objects('roles'); const roles = database.objects('roles');

View File

@ -31,7 +31,6 @@ const getRoomTitle = room => (room.t === 'd' ?
@connect(state => ({ @connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '', baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
user: state.login.user, user: state.login.user,
permissions: state.permissions,
activeUsers: state.activeUsers, activeUsers: state.activeUsers,
Message_TimeFormat: state.settings.Message_TimeFormat, Message_TimeFormat: state.settings.Message_TimeFormat,
roles: state.roles roles: state.roles
@ -123,8 +122,12 @@ export default class RoomInfoView extends LoggedView {
} }
getFullUserData = async(username) => { getFullUserData = async(username) => {
const result = await RocketChat.subscribe('fullUserData', username); try {
this.sub = result; const result = await RocketChat.subscribe('fullUserData', username);
this.sub = result;
} catch (e) {
log('getFullUserData', e);
}
} }
isDirect = () => this.state.room.t === 'd'; isDirect = () => this.state.room.t === 'd';

View File

@ -9,7 +9,7 @@ import LoggedView from '../View';
import { List } from './ListView'; import { List } from './ListView';
// import * as actions from '../../actions'; // import * as actions from '../../actions';
import { openRoom, setLastOpen } from '../../actions/room'; 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 database from '../../lib/realm';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import Message from '../../containers/message'; import Message from '../../containers/message';
@ -22,6 +22,7 @@ import ReactionPicker from './ReactionPicker';
import styles from './styles'; import styles from './styles';
import log from '../../utils/log'; import log from '../../utils/log';
import I18n from '../../i18n'; import I18n from '../../i18n';
import debounce from '../../utils/debounce';
@connect( @connect(
state => ({ state => ({
@ -29,12 +30,14 @@ import I18n from '../../i18n';
// Message_TimeFormat: state.settings.Message_TimeFormat, // Message_TimeFormat: state.settings.Message_TimeFormat,
loading: state.messages.isFetching, loading: state.messages.isFetching,
user: state.login.user, user: state.login.user,
actionMessage: state.messages.actionMessage actionMessage: state.messages.actionMessage,
showActions: state.messages.showActions,
showErrorActions: state.messages.showErrorActions
}), }),
dispatch => ({ dispatch => ({
// actions: bindActionCreators(actions, dispatch), // actions: bindActionCreators(actions, dispatch),
openRoom: room => dispatch(openRoom(room)), openRoom: room => dispatch(openRoom(room)),
editCancel: () => dispatch(editCancel()), // editCancel: () => dispatch(editCancel()),
setLastOpen: date => dispatch(setLastOpen(date)), setLastOpen: date => dispatch(setLastOpen(date)),
toggleReactionPicker: message => dispatch(toggleReactionPicker(message)), toggleReactionPicker: message => dispatch(toggleReactionPicker(message)),
actionsShow: actionMessage => dispatch(actionsShow(actionMessage)) actionsShow: actionMessage => dispatch(actionsShow(actionMessage))
@ -46,7 +49,7 @@ export default class RoomView extends LoggedView {
openRoom: PropTypes.func.isRequired, openRoom: PropTypes.func.isRequired,
setLastOpen: PropTypes.func.isRequired, setLastOpen: PropTypes.func.isRequired,
user: PropTypes.object.isRequired, user: PropTypes.object.isRequired,
editCancel: PropTypes.func, // editCancel: PropTypes.func,
rid: PropTypes.string, rid: PropTypes.string,
name: PropTypes.string, name: PropTypes.string,
// Site_Url: PropTypes.string, // Site_Url: PropTypes.string,
@ -85,10 +88,10 @@ export default class RoomView extends LoggedView {
} }
componentWillUnmount() { componentWillUnmount() {
this.rooms.removeAllListeners(); this.rooms.removeAllListeners();
this.props.editCancel(); this.onEndReached.stop();
} }
onEndReached = (lastRowData) => { onEndReached = debounce((lastRowData) => {
if (!lastRowData) { if (!lastRowData) {
this.setState({ end: true }); this.setState({ end: true });
return; return;
@ -102,7 +105,7 @@ export default class RoomView extends LoggedView {
log('RoomView.onEndReached', e); log('RoomView.onEndReached', e);
} }
}); });
} })
onMessageLongPress = (message) => { onMessageLongPress = (message) => {
this.props.actionsShow(message); this.props.actionsShow(message);
@ -186,8 +189,6 @@ export default class RoomView extends LoggedView {
/> />
); );
// renderSeparator = () => <View style={styles.separator} />;
renderFooter = () => { renderFooter = () => {
if (!this.state.joined) { if (!this.state.joined) {
return ( return (
@ -232,8 +233,8 @@ export default class RoomView extends LoggedView {
renderRow={this.renderItem} renderRow={this.renderItem}
/> />
{this.renderFooter()} {this.renderFooter()}
{this.state.room._id ? <MessageActions room={this.state.room} /> : null} {this.state.room._id && this.props.showActions ? <MessageActions room={this.state.room} /> : null}
<MessageErrorActions /> {this.props.showErrorActions ? <MessageErrorActions /> : null}
<ReactionPicker onEmojiSelected={this.onReactionPress} /> <ReactionPicker onEmojiSelected={this.onReactionPress} />
</View> </View>
); );

View File

@ -6,6 +6,7 @@ import { connect } from 'react-redux';
import Modal from 'react-native-modal'; import Modal from 'react-native-modal';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
import { HeaderBackButton } from 'react-navigation'; import { HeaderBackButton } from 'react-navigation';
import equal from 'deep-equal';
import Avatar from '../../../containers/Avatar'; import Avatar from '../../../containers/Avatar';
import Status from '../../../containers/status'; import Status from '../../../containers/status';
@ -50,7 +51,7 @@ const title = (offline, connecting, authenticating, logged) => {
setSearch: searchText => dispatch(setSearch(searchText)) setSearch: searchText => dispatch(setSearch(searchText))
})) }))
export default class RoomsListHeaderView extends React.PureComponent { export default class RoomsListHeaderView extends React.Component {
static propTypes = { static propTypes = {
navigation: PropTypes.object.isRequired, navigation: PropTypes.object.isRequired,
user: 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) { onPressModalButton(status) {
try { try {
RocketChat.setUserPresenceDefaultStatus(status); RocketChat.setUserPresenceDefaultStatus(status);

View File

@ -318,7 +318,7 @@ describe('Room info screen', () => {
await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible(); await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible();
}); });
after(async() => { afterEach(async() => {
takeScreenshot(); takeScreenshot();
}); });
}); });

2
package-lock.json generated
View File

@ -15005,7 +15005,7 @@
"integrity": "sha512-Bvq4FQPMAFijqjqNX6TxLgKOwdbruM6GvFwF9rb+mowbaFZVoYbHTKLaAbdPlrblgaZKWyOuuxBUoDx41+Xktg==", "integrity": "sha512-Bvq4FQPMAFijqjqNX6TxLgKOwdbruM6GvFwF9rb+mowbaFZVoYbHTKLaAbdPlrblgaZKWyOuuxBUoDx41+Xktg==",
"requires": { "requires": {
"prop-types": "15.6.1", "prop-types": "15.6.1",
"react-native-animatable": "1.2.4" "react-native-animatable": "1.3.0"
} }
} }
} }