mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 19:26:04 +03:00
Member list
This commit is contained in:
parent
31ee667102
commit
a8eb93bd6f
7 changed files with 741 additions and 21 deletions
38
src/MatrixTools.js
Normal file
38
src/MatrixTools.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var MatrixClientPeg = require('./MatrixClientPeg');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* Given a room object, return the canonical alias for it
|
||||||
|
* if there is one. Otherwise return null;
|
||||||
|
*/
|
||||||
|
getCanonicalAliasForRoom: function(room) {
|
||||||
|
var aliasEvents = room.currentState.getStateEvents(
|
||||||
|
"m.room.aliases"
|
||||||
|
);
|
||||||
|
// Canonical aliases aren't implemented yet, so just return the first
|
||||||
|
for (var j = 0; j < aliasEvents.length; j++) {
|
||||||
|
var aliases = aliasEvents[j].getContent().aliases;
|
||||||
|
if (aliases && aliases.length) {
|
||||||
|
return aliases[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
107
src/Presence.js
Normal file
107
src/Presence.js
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||||
|
|
||||||
|
// Time in ms after that a user is considered as unavailable/away
|
||||||
|
var UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
|
||||||
|
var PRESENCE_STATES = ["online", "offline", "unavailable"];
|
||||||
|
|
||||||
|
// The current presence state
|
||||||
|
var state, timer;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start listening the user activity to evaluate his presence state.
|
||||||
|
* Any state change will be sent to the Home Server.
|
||||||
|
*/
|
||||||
|
start: function() {
|
||||||
|
var self = this;
|
||||||
|
this.running = true;
|
||||||
|
if (undefined === state) {
|
||||||
|
// The user is online if they move the mouse or press a key
|
||||||
|
document.onmousemove = function() { self._resetTimer(); };
|
||||||
|
document.onkeypress = function() { self._resetTimer(); };
|
||||||
|
this._resetTimer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop tracking user activity
|
||||||
|
*/
|
||||||
|
stop: function() {
|
||||||
|
this.running = false;
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = undefined;
|
||||||
|
}
|
||||||
|
state = undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current presence state.
|
||||||
|
* @returns {string} the presence state (see PRESENCE enum)
|
||||||
|
*/
|
||||||
|
getState: function() {
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the presence state.
|
||||||
|
* If the state has changed, the Home Server will be notified.
|
||||||
|
* @param {string} newState the new presence state (see PRESENCE enum)
|
||||||
|
*/
|
||||||
|
setState: function(newState) {
|
||||||
|
if (newState === state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (PRESENCE_STATES.indexOf(newState) === -1) {
|
||||||
|
throw new Error("Bad presence state: " + newState);
|
||||||
|
}
|
||||||
|
if (!this.running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state = newState;
|
||||||
|
MatrixClientPeg.get().setPresence(state).done(function() {
|
||||||
|
console.log("Presence: %s", newState);
|
||||||
|
}, function(err) {
|
||||||
|
console.error("Failed to set presence: %s", err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback called when the user made no action on the page for UNAVAILABLE_TIME ms.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onUnavailableTimerFire: function() {
|
||||||
|
this.setState("unavailable");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback called when the user made an action on the page
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_resetTimer: function() {
|
||||||
|
var self = this;
|
||||||
|
this.setState("online");
|
||||||
|
// Re-arm the timer
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(function() {
|
||||||
|
self._onUnavailableTimerFire();
|
||||||
|
}, UNAVAILABLE_TIME_MS);
|
||||||
|
}
|
||||||
|
};
|
323
src/controllers/molecules/MemberInfo.js
Normal file
323
src/controllers/molecules/MemberInfo.js
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* State vars:
|
||||||
|
* 'presence' : string (online|offline|unavailable etc)
|
||||||
|
* 'active' : number (ms ago; can be -1)
|
||||||
|
* 'can': {
|
||||||
|
* kick: boolean,
|
||||||
|
* ban: boolean,
|
||||||
|
* mute: boolean,
|
||||||
|
* modifyLevel: boolean
|
||||||
|
* },
|
||||||
|
* 'muted': boolean,
|
||||||
|
* 'isTargetMod': boolean
|
||||||
|
*/
|
||||||
|
|
||||||
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
|
var dis = require("../../dispatcher");
|
||||||
|
var Modal = require("../../Modal");
|
||||||
|
var sdk = require('../../index');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
componentDidMount: function() {
|
||||||
|
var self = this;
|
||||||
|
// listen for presence changes
|
||||||
|
function updateUserState(event, user) {
|
||||||
|
if (!self.props.member) { return; }
|
||||||
|
|
||||||
|
if (user.userId === self.props.member.userId) {
|
||||||
|
self.setState({
|
||||||
|
presence: user.presence,
|
||||||
|
active: user.lastActiveAgo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MatrixClientPeg.get().on("User.presence", updateUserState);
|
||||||
|
this.userPresenceFn = updateUserState;
|
||||||
|
|
||||||
|
// listen for power level changes
|
||||||
|
function updatePowerLevel(event, member) {
|
||||||
|
if (!self.props.member) { return; }
|
||||||
|
|
||||||
|
if (member.roomId !== self.props.member.roomId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// only interested in changes to us or them
|
||||||
|
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||||
|
if ([myUserId, self.props.member.userId].indexOf(member.userId) === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.setState(self._calculateOpsPermissions());
|
||||||
|
}
|
||||||
|
MatrixClientPeg.get().on("RoomMember.powerLevel", updatePowerLevel);
|
||||||
|
this.updatePowerLevelFn = updatePowerLevel;
|
||||||
|
|
||||||
|
// work out the current state
|
||||||
|
if (this.props.member) {
|
||||||
|
var usr = MatrixClientPeg.get().getUser(this.props.member.userId) || {};
|
||||||
|
var memberState = this._calculateOpsPermissions();
|
||||||
|
memberState.presence = usr.presence || "offline";
|
||||||
|
memberState.active = usr.lastActiveAgo || -1;
|
||||||
|
this.setState(memberState);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn);
|
||||||
|
MatrixClientPeg.get().removeListener(
|
||||||
|
"RoomMember.powerLevel", this.updatePowerLevelFn
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
onKick: function() {
|
||||||
|
var roomId = this.props.member.roomId;
|
||||||
|
var target = this.props.member.userId;
|
||||||
|
var self = this;
|
||||||
|
MatrixClientPeg.get().kick(roomId, target).done(function() {
|
||||||
|
// NO-OP; rely on the m.room.member event coming down else we could
|
||||||
|
// get out of sync if we force setState here!
|
||||||
|
console.log("Kick success");
|
||||||
|
}, function(err) {
|
||||||
|
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Kick error",
|
||||||
|
description: err.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onBan: function() {
|
||||||
|
var roomId = this.props.member.roomId;
|
||||||
|
var target = this.props.member.userId;
|
||||||
|
var self = this;
|
||||||
|
MatrixClientPeg.get().ban(roomId, target).done(function() {
|
||||||
|
// NO-OP; rely on the m.room.member event coming down else we could
|
||||||
|
// get out of sync if we force setState here!
|
||||||
|
console.log("Ban success");
|
||||||
|
}, function(err) {
|
||||||
|
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Ban error",
|
||||||
|
description: err.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onMuteToggle: function() {
|
||||||
|
var roomId = this.props.member.roomId;
|
||||||
|
var target = this.props.member.userId;
|
||||||
|
var self = this;
|
||||||
|
var room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
|
if (!room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var powerLevelEvent = room.currentState.getStateEvents(
|
||||||
|
"m.room.power_levels", ""
|
||||||
|
);
|
||||||
|
if (!powerLevelEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var isMuted = this.state.muted;
|
||||||
|
var powerLevels = powerLevelEvent.getContent();
|
||||||
|
var levelToSend = (
|
||||||
|
(powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
|
||||||
|
powerLevels.events_default
|
||||||
|
);
|
||||||
|
var level;
|
||||||
|
if (isMuted) { // unmute
|
||||||
|
level = levelToSend;
|
||||||
|
}
|
||||||
|
else { // mute
|
||||||
|
level = levelToSend - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixClientPeg.get().setPowerLevel(roomId, target, level, powerLevelEvent).done(
|
||||||
|
function() {
|
||||||
|
// NO-OP; rely on the m.room.member event coming down else we could
|
||||||
|
// get out of sync if we force setState here!
|
||||||
|
console.log("Mute toggle success");
|
||||||
|
}, function(err) {
|
||||||
|
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Mute error",
|
||||||
|
description: err.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onModToggle: function() {
|
||||||
|
var roomId = this.props.member.roomId;
|
||||||
|
var target = this.props.member.userId;
|
||||||
|
var room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
|
if (!room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var powerLevelEvent = room.currentState.getStateEvents(
|
||||||
|
"m.room.power_levels", ""
|
||||||
|
);
|
||||||
|
if (!powerLevelEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||||
|
if (!me) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var defaultLevel = powerLevelEvent.getContent().users_default;
|
||||||
|
var modLevel = me.powerLevel - 1;
|
||||||
|
// toggle the level
|
||||||
|
var newLevel = this.state.isTargetMod ? defaultLevel : modLevel;
|
||||||
|
MatrixClientPeg.get().setPowerLevel(roomId, target, newLevel, powerLevelEvent).done(
|
||||||
|
function() {
|
||||||
|
// NO-OP; rely on the m.room.member event coming down else we could
|
||||||
|
// get out of sync if we force setState here!
|
||||||
|
console.log("Mod toggle success");
|
||||||
|
}, function(err) {
|
||||||
|
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Mod error",
|
||||||
|
description: err.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onChatClick: function() {
|
||||||
|
// check if there are any existing rooms with just us and them (1:1)
|
||||||
|
// If so, just view that room. If not, create a private room with them.
|
||||||
|
var rooms = MatrixClientPeg.get().getRooms();
|
||||||
|
var userIds = [
|
||||||
|
this.props.member.userId,
|
||||||
|
MatrixClientPeg.get().credentials.userId
|
||||||
|
];
|
||||||
|
var existingRoomId = null;
|
||||||
|
for (var i = 0; i < rooms.length; i++) {
|
||||||
|
var members = rooms[i].getJoinedMembers();
|
||||||
|
if (members.length === 2) {
|
||||||
|
var hasTargetUsers = true;
|
||||||
|
for (var j = 0; j < members.length; j++) {
|
||||||
|
if (userIds.indexOf(members[j].userId) === -1) {
|
||||||
|
hasTargetUsers = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasTargetUsers) {
|
||||||
|
existingRoomId = rooms[i].roomId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingRoomId) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: existingRoomId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
MatrixClientPeg.get().createRoom({
|
||||||
|
invite: [this.props.member.userId],
|
||||||
|
preset: "private_chat"
|
||||||
|
}).done(function(res) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: res.room_id
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
console.error(
|
||||||
|
"Failed to create room: %s", JSON.stringify(err)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
presence: "offline",
|
||||||
|
active: -1,
|
||||||
|
can: {
|
||||||
|
kick: false,
|
||||||
|
ban: false,
|
||||||
|
mute: false,
|
||||||
|
modifyLevel: false
|
||||||
|
},
|
||||||
|
muted: false,
|
||||||
|
isTargetMod: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_calculateOpsPermissions: function() {
|
||||||
|
var defaultPerms = {
|
||||||
|
can: {},
|
||||||
|
muted: false,
|
||||||
|
modifyLevel: false
|
||||||
|
};
|
||||||
|
var room = MatrixClientPeg.get().getRoom(this.props.member.roomId);
|
||||||
|
if (!room) {
|
||||||
|
return defaultPerms;
|
||||||
|
}
|
||||||
|
var powerLevels = room.currentState.getStateEvents(
|
||||||
|
"m.room.power_levels", ""
|
||||||
|
);
|
||||||
|
if (!powerLevels) {
|
||||||
|
return defaultPerms;
|
||||||
|
}
|
||||||
|
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||||
|
var them = this.props.member;
|
||||||
|
return {
|
||||||
|
can: this._calculateCanPermissions(
|
||||||
|
me, them, powerLevels.getContent()
|
||||||
|
),
|
||||||
|
muted: this._isMuted(them, powerLevels.getContent()),
|
||||||
|
isTargetMod: them.powerLevel > powerLevels.getContent().users_default
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_calculateCanPermissions: function(me, them, powerLevels) {
|
||||||
|
var can = {
|
||||||
|
kick: false,
|
||||||
|
ban: false,
|
||||||
|
mute: false,
|
||||||
|
modifyLevel: false
|
||||||
|
};
|
||||||
|
var canAffectUser = them.powerLevel < me.powerLevel;
|
||||||
|
if (!canAffectUser) {
|
||||||
|
//console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
var editPowerLevel = (
|
||||||
|
(powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
|
||||||
|
powerLevels.state_default
|
||||||
|
);
|
||||||
|
can.kick = me.powerLevel >= powerLevels.kick;
|
||||||
|
can.ban = me.powerLevel >= powerLevels.ban;
|
||||||
|
can.mute = me.powerLevel >= editPowerLevel;
|
||||||
|
can.modifyLevel = me.powerLevel > them.powerLevel;
|
||||||
|
return can;
|
||||||
|
},
|
||||||
|
|
||||||
|
_isMuted: function(member, powerLevelContent) {
|
||||||
|
if (!powerLevelContent || !member) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var levelToSend = (
|
||||||
|
(powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) ||
|
||||||
|
powerLevelContent.events_default
|
||||||
|
);
|
||||||
|
return member.powerLevel < levelToSend;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -24,6 +24,10 @@ var Loader = require("react-loader");
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
getInitialState: function() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
onClick: function() {
|
onClick: function() {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_user',
|
action: 'view_user',
|
||||||
|
|
37
src/controllers/organisms/ErrorDialog.js
Normal file
37
src/controllers/organisms/ErrorDialog.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var React = require("react");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
propTypes: {
|
||||||
|
title: React.PropTypes.string,
|
||||||
|
description: React.PropTypes.string,
|
||||||
|
button: React.PropTypes.string,
|
||||||
|
focus: React.PropTypes.bool,
|
||||||
|
onFinished: React.PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
var self = this;
|
||||||
|
return {
|
||||||
|
title: "Error",
|
||||||
|
description: "An error has occurred.",
|
||||||
|
button: "OK",
|
||||||
|
focus: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var React = require("react");
|
var React = require("react");
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
|
var Modal = require("../../Modal");
|
||||||
|
var sdk = require('../../index');
|
||||||
|
|
||||||
var INITIAL_LOAD_NUM_MEMBERS = 50;
|
var INITIAL_LOAD_NUM_MEMBERS = 50;
|
||||||
|
|
||||||
|
@ -32,39 +32,117 @@ module.exports = {
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
var cli = MatrixClientPeg.get();
|
var cli = MatrixClientPeg.get();
|
||||||
cli.on("RoomState.members", this.onRoomStateMember);
|
cli.on("RoomState.members", this.onRoomStateMember);
|
||||||
|
cli.on("Room", this.onRoom); // invites
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
|
MatrixClientPeg.get().removeListener("Room", this.onRoom);
|
||||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||||
|
MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
var that = this;
|
var self = this;
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (!that.isMounted()) return;
|
if (!self.isMounted()) return;
|
||||||
that.setState({
|
self.setState({
|
||||||
memberDict: that.roomMembers()
|
memberDict: self.roomMembers()
|
||||||
});
|
});
|
||||||
}, 50);
|
}, 50);
|
||||||
},
|
|
||||||
|
|
||||||
|
// Attach a SINGLE listener for global presence changes then locate the
|
||||||
|
// member tile and re-render it. This is more efficient than every tile
|
||||||
|
// evar attaching their own listener.
|
||||||
|
function updateUserState(event, user) {
|
||||||
|
var tile = self.refs[user.userId];
|
||||||
|
if (tile) {
|
||||||
|
tile.forceUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MatrixClientPeg.get().on("User.presence", updateUserState);
|
||||||
|
this.userPresenceFn = updateUserState;
|
||||||
|
},
|
||||||
// Remember to set 'key' on a MemberList to the ID of the room it's for
|
// Remember to set 'key' on a MemberList to the ID of the room it's for
|
||||||
/*componentWillReceiveProps: function(newProps) {
|
/*componentWillReceiveProps: function(newProps) {
|
||||||
},*/
|
},*/
|
||||||
|
|
||||||
|
onRoom: function(room) {
|
||||||
|
if (room.roomId !== this.props.roomId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We listen for room events because when we accept an invite
|
||||||
|
// we need to wait till the room is fully populated with state
|
||||||
|
// before refreshing the member list else we get a stale list.
|
||||||
|
this._updateList();
|
||||||
|
},
|
||||||
|
|
||||||
onRoomStateMember: function(ev, state, member) {
|
onRoomStateMember: function(ev, state, member) {
|
||||||
|
this._updateList();
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateList: function() {
|
||||||
var members = this.roomMembers();
|
var members = this.roomMembers();
|
||||||
this.setState({
|
this.setState({
|
||||||
memberDict: members
|
memberDict: members
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onInvite: function(inputText) {
|
||||||
|
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||||
|
var self = this;
|
||||||
|
// sanity check the input
|
||||||
|
inputText = inputText.trim(); // react requires es5-shim so we know trim() exists
|
||||||
|
if (inputText[0] !== '@' || inputText.indexOf(":") === -1) {
|
||||||
|
console.error("Bad user ID to invite: %s", inputText);
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Invite Error",
|
||||||
|
description: "Malformed user ID. Should look like '@localpart:domain'"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.setState({
|
||||||
|
inviting: true
|
||||||
|
});
|
||||||
|
console.log("Invite %s to %s", inputText, this.props.roomId);
|
||||||
|
MatrixClientPeg.get().invite(this.props.roomId, inputText).done(
|
||||||
|
function(res) {
|
||||||
|
console.log("Invited");
|
||||||
|
self.setState({
|
||||||
|
inviting: false
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
console.error("Failed to invite: %s", JSON.stringify(err));
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Server error whilst inviting",
|
||||||
|
description: err.message
|
||||||
|
});
|
||||||
|
self.setState({
|
||||||
|
inviting: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
roomMembers: function(limit) {
|
roomMembers: function(limit) {
|
||||||
|
if (!this.props.roomId) return {};
|
||||||
var cli = MatrixClientPeg.get();
|
var cli = MatrixClientPeg.get();
|
||||||
var all_members = cli.getRoom(this.props.roomId).currentState.members;
|
var room = cli.getRoom(this.props.roomId);
|
||||||
|
if (!room) return {};
|
||||||
|
var all_members = room.currentState.members;
|
||||||
var all_user_ids = Object.keys(all_members);
|
var all_user_ids = Object.keys(all_members);
|
||||||
|
|
||||||
|
all_user_ids.sort(function(userIdA, userIdB) {
|
||||||
|
var userA = all_members[userIdA].user;
|
||||||
|
var userB = all_members[userIdB].user;
|
||||||
|
|
||||||
|
var latA = userA ? userA.lastActiveAgo || Number.MAX_VALUE : Number.MAX_VALUE;
|
||||||
|
var latB = userB ? userB.lastActiveAgo || Number.MAX_VALUE : Number.MAX_VALUE;
|
||||||
|
|
||||||
|
return latA - latB;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
var to_display = {};
|
var to_display = {};
|
||||||
var count = 0;
|
var count = 0;
|
||||||
for (var i = 0; i < all_user_ids.length && (limit === undefined || count < limit); ++i) {
|
for (var i = 0; i < all_user_ids.length && (limit === undefined || count < limit); ++i) {
|
||||||
|
|
|
@ -16,19 +16,44 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// should be atomised
|
||||||
|
var Loader = require("react-loader");
|
||||||
|
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
var RoomListSorter = require("../../RoomListSorter");
|
var RoomListSorter = require("../../RoomListSorter");
|
||||||
|
var Presence = require("../../Presence");
|
||||||
var dis = require("../../dispatcher");
|
var dis = require("../../dispatcher");
|
||||||
|
var q = require("q");
|
||||||
|
|
||||||
var sdk = require('../../index');
|
var sdk = require('../../index');
|
||||||
|
var MatrixTools = require('../../MatrixTools');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
PageTypes: {
|
||||||
|
RoomView: "room_view",
|
||||||
|
UserSettings: "user_settings",
|
||||||
|
CreateRoom: "create_room",
|
||||||
|
RoomDirectory: "room_directory",
|
||||||
|
},
|
||||||
|
|
||||||
|
AuxPanel: {
|
||||||
|
RoomSettings: "room_settings",
|
||||||
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
var s = {
|
||||||
logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials),
|
logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials),
|
||||||
ready: false
|
ready: false,
|
||||||
|
aux_panel: null,
|
||||||
};
|
};
|
||||||
|
if (s.logged_in) {
|
||||||
|
if (MatrixClientPeg.get().getRooms().length) {
|
||||||
|
s.page_type = this.PageTypes.RoomView;
|
||||||
|
} else {
|
||||||
|
s.page_type = this.PageTypes.RoomDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
@ -49,6 +74,7 @@ module.exports = {
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
document.removeEventListener("keydown", this.onKeyDown);
|
document.removeEventListener("keydown", this.onKeyDown);
|
||||||
|
window.removeEventListener("focus", this.onFocus);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate: function() {
|
componentDidUpdate: function() {
|
||||||
|
@ -72,8 +98,11 @@ module.exports = {
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
}
|
}
|
||||||
Notifier.stop();
|
Notifier.stop();
|
||||||
|
Presence.stop();
|
||||||
|
MatrixClientPeg.get().stopClient();
|
||||||
MatrixClientPeg.get().removeAllListeners();
|
MatrixClientPeg.get().removeAllListeners();
|
||||||
MatrixClientPeg.replace(null);
|
MatrixClientPeg.unset();
|
||||||
|
this.notifyNewScreen('');
|
||||||
break;
|
break;
|
||||||
case 'start_registration':
|
case 'start_registration':
|
||||||
if (this.state.logged_in) return;
|
if (this.state.logged_in) return;
|
||||||
|
@ -106,8 +135,23 @@ module.exports = {
|
||||||
case 'view_room':
|
case 'view_room':
|
||||||
this.focusComposer = true;
|
this.focusComposer = true;
|
||||||
this.setState({
|
this.setState({
|
||||||
currentRoom: payload.room_id
|
currentRoom: payload.room_id,
|
||||||
|
page_type: this.PageTypes.RoomView,
|
||||||
});
|
});
|
||||||
|
if (this.sdkReady) {
|
||||||
|
// if the SDK is not ready yet, remember what room
|
||||||
|
// we're supposed to be on but don't notify about
|
||||||
|
// the new screen yet (we won't be showing it yet)
|
||||||
|
// The normal case where this happens is navigating
|
||||||
|
// to the room in the URL bar on page load.
|
||||||
|
var presentedId = payload.room_id;
|
||||||
|
var room = MatrixClientPeg.get().getRoom(payload.room_id);
|
||||||
|
if (room) {
|
||||||
|
var theAlias = MatrixTools.getCanonicalAliasForRoom(room);
|
||||||
|
if (theAlias) presentedId = theAlias;
|
||||||
|
}
|
||||||
|
this.notifyNewScreen('room/'+presentedId);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'view_prev_room':
|
case 'view_prev_room':
|
||||||
roomIndexDelta = -1;
|
roomIndexDelta = -1;
|
||||||
|
@ -123,9 +167,43 @@ module.exports = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
||||||
|
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
||||||
|
this.focusComposer = true;
|
||||||
this.setState({
|
this.setState({
|
||||||
currentRoom: allRooms[roomIndex].roomId
|
currentRoom: allRooms[roomIndex].roomId
|
||||||
});
|
});
|
||||||
|
this.notifyNewScreen('room/'+allRooms[roomIndex].roomId);
|
||||||
|
break;
|
||||||
|
case 'view_indexed_room':
|
||||||
|
var allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||||
|
MatrixClientPeg.get().getRooms()
|
||||||
|
);
|
||||||
|
var roomIndex = payload.roomIndex;
|
||||||
|
if (allRooms[roomIndex]) {
|
||||||
|
this.focusComposer = true;
|
||||||
|
this.setState({
|
||||||
|
currentRoom: allRooms[roomIndex].roomId
|
||||||
|
});
|
||||||
|
this.notifyNewScreen('room/'+allRooms[roomIndex].roomId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'view_user_settings':
|
||||||
|
this.setState({
|
||||||
|
page_type: this.PageTypes.UserSettings,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'view_create_room':
|
||||||
|
this.setState({
|
||||||
|
page_type: this.PageTypes.CreateRoom,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'view_room_directory':
|
||||||
|
this.setState({
|
||||||
|
page_type: this.PageTypes.RoomDirectory,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'notifier_enabled':
|
||||||
|
this.forceUpdate();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -141,33 +219,67 @@ module.exports = {
|
||||||
|
|
||||||
startMatrixClient: function() {
|
startMatrixClient: function() {
|
||||||
var Notifier = sdk.getComponent('organisms.Notifier');
|
var Notifier = sdk.getComponent('organisms.Notifier');
|
||||||
|
|
||||||
var cli = MatrixClientPeg.get();
|
var cli = MatrixClientPeg.get();
|
||||||
var self = this;
|
var self = this;
|
||||||
cli.on('syncComplete', function() {
|
cli.on('syncComplete', function() {
|
||||||
var firstRoom = null;
|
self.sdkReady = true;
|
||||||
if (cli.getRooms() && cli.getRooms().length) {
|
if (!self.state.currentRoom) {
|
||||||
firstRoom = RoomListSorter.mostRecentActivityFirst(
|
var firstRoom = null;
|
||||||
cli.getRooms()
|
if (cli.getRooms() && cli.getRooms().length) {
|
||||||
)[0].roomId;
|
firstRoom = RoomListSorter.mostRecentActivityFirst(
|
||||||
|
cli.getRooms()
|
||||||
|
)[0].roomId;
|
||||||
|
self.setState({ready: true, currentRoom: firstRoom, page_type: self.PageTypes.RoomView});
|
||||||
|
} else {
|
||||||
|
self.setState({ready: true, page_type: self.PageTypes.RoomDirectory});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.setState({ready: true, currentRoom: self.state.currentRoom});
|
||||||
}
|
}
|
||||||
self.setState({ready: true, currentRoom: firstRoom});
|
|
||||||
|
// we notifyNewScreen now because now the room will actually be displayed,
|
||||||
|
// and (mostly) now we can get the correct alias.
|
||||||
|
var presentedId = self.state.currentRoom;
|
||||||
|
var room = MatrixClientPeg.get().getRoom(self.state.currentRoom);
|
||||||
|
if (room) {
|
||||||
|
var theAlias = MatrixTools.getCanonicalAliasForRoom(room);
|
||||||
|
if (theAlias) presentedId = theAlias;
|
||||||
|
}
|
||||||
|
self.notifyNewScreen('room/'+presentedId);
|
||||||
dis.dispatch({action: 'focus_composer'});
|
dis.dispatch({action: 'focus_composer'});
|
||||||
});
|
});
|
||||||
|
cli.on('Call.incoming', function(call) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'incoming_call',
|
||||||
|
call: call
|
||||||
|
});
|
||||||
|
});
|
||||||
Notifier.start();
|
Notifier.start();
|
||||||
|
Presence.start();
|
||||||
cli.startClient();
|
cli.startClient();
|
||||||
},
|
},
|
||||||
|
|
||||||
onKeyDown: function(ev) {
|
onKeyDown: function(ev) {
|
||||||
if (ev.altKey) {
|
if (ev.altKey) {
|
||||||
|
if (ev.ctrlKey && ev.keyCode > 48 && ev.keyCode < 58) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_indexed_room',
|
||||||
|
roomIndex: ev.keyCode - 49,
|
||||||
|
});
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (ev.keyCode) {
|
switch (ev.keyCode) {
|
||||||
case 38:
|
case 38:
|
||||||
dis.dispatch({action: 'view_prev_room'});
|
dis.dispatch({action: 'view_prev_room'});
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
break;
|
break;
|
||||||
case 40:
|
case 40:
|
||||||
dis.dispatch({action: 'view_next_room'});
|
dis.dispatch({action: 'view_next_room'});
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,6 +300,28 @@ module.exports = {
|
||||||
action: 'start_login',
|
action: 'start_login',
|
||||||
params: params
|
params: params
|
||||||
});
|
});
|
||||||
|
} else if (screen.indexOf('room/') == 0) {
|
||||||
|
var roomString = screen.split('/')[1];
|
||||||
|
var defer = q.defer();
|
||||||
|
if (roomString[0] == '#') {
|
||||||
|
var self = this;
|
||||||
|
MatrixClientPeg.get().getRoomIdForAlias(roomString).done(function(result) {
|
||||||
|
if (self.sdkReady) self.setState({ready: true});
|
||||||
|
defer.resolve(result.room_id);
|
||||||
|
}, function() {
|
||||||
|
if (self.sdkReady) self.setState({ready: true});
|
||||||
|
defer.resolve(null);
|
||||||
|
});
|
||||||
|
this.setState({ready: false});
|
||||||
|
} else {
|
||||||
|
defer.resolve(roomString);
|
||||||
|
}
|
||||||
|
defer.promise.done(function(roomId) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: roomId
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -197,4 +331,3 @@ module.exports = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue