mirror of
https://github.com/SchildiChat/element-web.git
synced 2024-10-01 01:26:12 -04:00
Merge branch 'develop' into kegan/reg-refactor
This commit is contained in:
commit
5f57cd9559
@ -28,8 +28,8 @@
|
|||||||
"filesize": "^3.1.2",
|
"filesize": "^3.1.2",
|
||||||
"flux": "~2.0.3",
|
"flux": "~2.0.3",
|
||||||
"linkifyjs": "^2.0.0-beta.4",
|
"linkifyjs": "^2.0.0-beta.4",
|
||||||
"matrix-js-sdk": "^0.3.0",
|
"matrix-js-sdk": "https://github.com/matrix-org/matrix-js-sdk.git#develop",
|
||||||
"matrix-react-sdk": "^0.0.2",
|
"matrix-react-sdk": "https://github.com/matrix-org/matrix-react-sdk.git#develop",
|
||||||
"modernizr": "^3.1.0",
|
"modernizr": "^3.1.0",
|
||||||
"q": "^1.4.1",
|
"q": "^1.4.1",
|
||||||
"react": "^0.14.2",
|
"react": "^0.14.2",
|
||||||
@ -37,6 +37,7 @@
|
|||||||
"react-dnd-html5-backend": "^2.0.0",
|
"react-dnd-html5-backend": "^2.0.0",
|
||||||
"react-dom": "^0.14.2",
|
"react-dom": "^0.14.2",
|
||||||
"react-gemini-scrollbar": "^2.0.1",
|
"react-gemini-scrollbar": "^2.0.1",
|
||||||
|
"velocity-animate": "^1.2.3",
|
||||||
"sanitize-html": "^1.0.0"
|
"sanitize-html": "^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -43,7 +43,7 @@ module.exports = {
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var closeMenu = function() {
|
var closeMenu = function() {
|
||||||
React.unmountComponentAtNode(self.getOrCreateContainer());
|
ReactDOM.unmountComponentAtNode(self.getOrCreateContainer());
|
||||||
|
|
||||||
if (props && props.onFinished) props.onFinished.apply(null, arguments);
|
if (props && props.onFinished) props.onFinished.apply(null, arguments);
|
||||||
};
|
};
|
||||||
|
113
src/Velociraptor.js
Normal file
113
src/Velociraptor.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var ReactDom = require('react-dom');
|
||||||
|
var Velocity = require('velocity-animate');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Velociraptor contains components and animates transitions with velocity.
|
||||||
|
* It will only pick up direct changes to properties ('left', currently), and so
|
||||||
|
* will not work for animating positional changes where the position is implicit
|
||||||
|
* from DOM order. This makes it a lot simpler and lighter: if you need fully
|
||||||
|
* automatic positional animation, look at react-shuffle or similar libraries.
|
||||||
|
*/
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'Velociraptor',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
children: React.PropTypes.array,
|
||||||
|
transition: React.PropTypes.object,
|
||||||
|
container: React.PropTypes.string
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this.children = {};
|
||||||
|
this.nodes = {};
|
||||||
|
var self = this;
|
||||||
|
React.Children.map(this.props.children, function(c) {
|
||||||
|
self.children[c.props.key] = c;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
var self = this;
|
||||||
|
var oldChildren = this.children;
|
||||||
|
this.children = {};
|
||||||
|
React.Children.map(nextProps.children, function(c) {
|
||||||
|
if (oldChildren[c.key]) {
|
||||||
|
var old = oldChildren[c.key];
|
||||||
|
var oldNode = ReactDom.findDOMNode(self.nodes[old.key]);
|
||||||
|
|
||||||
|
if (oldNode.style.left != c.props.style.left) {
|
||||||
|
Velocity(oldNode, { left: c.props.style.left }, self.props.transition).then(function() {
|
||||||
|
// special case visibility because it's nonsensical to animate an invisible element
|
||||||
|
// so we always hidden->visible pre-transition and visible->hidden after
|
||||||
|
if (oldNode.style.visibility == 'visible' && c.props.style.visibility == 'hidden') {
|
||||||
|
oldNode.style.visibility = c.props.style.visibility;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (oldNode.style.visibility == 'hidden' && c.props.style.visibility == 'visible') {
|
||||||
|
oldNode.style.visibility = c.props.style.visibility;
|
||||||
|
}
|
||||||
|
//console.log("translation: "+oldNode.style.left+" -> "+c.props.style.left);
|
||||||
|
}
|
||||||
|
self.children[c.key] = old;
|
||||||
|
} else {
|
||||||
|
// new element. If it has a startStyle, use that as the style and go through
|
||||||
|
// the enter animations
|
||||||
|
var newProps = {
|
||||||
|
ref: self.collectNode.bind(self, c.key)
|
||||||
|
};
|
||||||
|
if (c.props.startStyle && Object.keys(c.props.startStyle).length) {
|
||||||
|
var startStyle = c.props.startStyle;
|
||||||
|
if (Array.isArray(startStyle)) {
|
||||||
|
startStyle = startStyle[0];
|
||||||
|
}
|
||||||
|
newProps._restingStyle = c.props.style;
|
||||||
|
newProps.style = startStyle;
|
||||||
|
//console.log("mounted@startstyle0: "+JSON.stringify(startStyle));
|
||||||
|
// apply the enter animations once it's mounted
|
||||||
|
}
|
||||||
|
self.children[c.key] = React.cloneElement(c, newProps);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
collectNode: function(k, node) {
|
||||||
|
if (
|
||||||
|
this.nodes[k] === undefined &&
|
||||||
|
node.props.startStyle &&
|
||||||
|
Object.keys(node.props.startStyle).length
|
||||||
|
) {
|
||||||
|
var domNode = ReactDom.findDOMNode(node);
|
||||||
|
var startStyles = node.props.startStyle;
|
||||||
|
var transitionOpts = node.props.enterTransitionOpts;
|
||||||
|
if (!Array.isArray(startStyles)) {
|
||||||
|
startStyles = [ startStyles ];
|
||||||
|
transitionOpts = [ transitionOpts ];
|
||||||
|
}
|
||||||
|
// start from startStyle 1: 0 is the one we gave it
|
||||||
|
// to start with, so now we animate 1 etc.
|
||||||
|
for (var i = 1; i < startStyles.length; ++i) {
|
||||||
|
Velocity(domNode, startStyles[i], transitionOpts[i-1]);
|
||||||
|
//console.log("start: "+JSON.stringify(startStyles[i]));
|
||||||
|
}
|
||||||
|
// and then we animate to the resting state
|
||||||
|
Velocity(domNode, node.props._restingStyle, transitionOpts[i-1]);
|
||||||
|
//console.log("enter: "+JSON.stringify(node.props._restingStyle));
|
||||||
|
}
|
||||||
|
this.nodes[k] = node;
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var self = this;
|
||||||
|
var childList = Object.keys(this.children).map(function(k) {
|
||||||
|
return React.cloneElement(self.children[k], {
|
||||||
|
ref: self.collectNode.bind(self, self.children[k].key)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{childList}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
15
src/VelocityBounce.js
Normal file
15
src/VelocityBounce.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
var Velocity = require('velocity-animate');
|
||||||
|
|
||||||
|
// courtesy of https://github.com/julianshapiro/velocity/issues/283
|
||||||
|
// We only use easeOutBounce (easeInBounce is just sort of nonsensical)
|
||||||
|
function bounce( p ) {
|
||||||
|
var pow2,
|
||||||
|
bounce = 4;
|
||||||
|
|
||||||
|
while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
|
||||||
|
return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
Velocity.Easings.easeOutBounce = function(p) {
|
||||||
|
return 1 - bounce(1 - p);
|
||||||
|
}
|
@ -53,6 +53,7 @@ module.exports = {
|
|||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||||
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
||||||
|
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
|
||||||
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
|
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
|
||||||
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
|
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
|
||||||
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
|
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
|
||||||
@ -71,6 +72,7 @@ module.exports = {
|
|||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||||
|
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
|
||||||
MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping);
|
MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping);
|
||||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||||
MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange);
|
MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange);
|
||||||
@ -108,6 +110,9 @@ module.exports = {
|
|||||||
// the conf
|
// the conf
|
||||||
this._updateConfCallNotification();
|
this._updateConfCallNotification();
|
||||||
break;
|
break;
|
||||||
|
case 'user_activity':
|
||||||
|
this.sendReadReceipt();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -187,6 +192,12 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onRoomReceipt: function(receiptEvent, room) {
|
||||||
|
if (room.roomId == this.props.roomId) {
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onRoomMemberTyping: function(ev, member) {
|
onRoomMemberTyping: function(ev, member) {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
},
|
},
|
||||||
@ -247,6 +258,8 @@ module.exports = {
|
|||||||
|
|
||||||
messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
|
messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
|
||||||
|
|
||||||
|
this.sendReadReceipt();
|
||||||
|
|
||||||
this.fillSpace();
|
this.fillSpace();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,7 +542,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret.unshift(
|
ret.unshift(
|
||||||
<li key={mxEv.getId()}><EventTile mxEvent={mxEv} continuation={continuation} last={last}/></li>
|
<li key={mxEv.getId()} ref={this._collectEventNode.bind(this, mxEv.getId())}><EventTile mxEvent={mxEv} continuation={continuation} last={last}/></li>
|
||||||
);
|
);
|
||||||
if (dateSeparator) {
|
if (dateSeparator) {
|
||||||
ret.unshift(dateSeparator);
|
ret.unshift(dateSeparator);
|
||||||
@ -624,5 +637,58 @@ module.exports = {
|
|||||||
uploadingRoomSettings: false,
|
uploadingRoomSettings: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_collectEventNode: function(eventId, node) {
|
||||||
|
if (this.eventNodes == undefined) this.eventNodes = {};
|
||||||
|
this.eventNodes[eventId] = node;
|
||||||
|
},
|
||||||
|
|
||||||
|
_indexForEventId(evId) {
|
||||||
|
for (var i = 0; i < this.state.room.timeline.length; ++i) {
|
||||||
|
if (evId == this.state.room.timeline[i].getId()) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
sendReadReceipt: function() {
|
||||||
|
if (!this.state.room) return;
|
||||||
|
var currentReadUpToEventId = this.state.room.getEventReadUpTo(MatrixClientPeg.get().credentials.userId);
|
||||||
|
var currentReadUpToEventIndex = this._indexForEventId(currentReadUpToEventId);
|
||||||
|
|
||||||
|
var lastReadEventIndex = this._getLastDisplayedEventIndexIgnoringOwn();
|
||||||
|
if (lastReadEventIndex === null) return;
|
||||||
|
|
||||||
|
if (lastReadEventIndex > currentReadUpToEventIndex) {
|
||||||
|
MatrixClientPeg.get().sendReadReceipt(this.state.room.timeline[lastReadEventIndex]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_getLastDisplayedEventIndexIgnoringOwn: function() {
|
||||||
|
if (this.eventNodes === undefined) return null;
|
||||||
|
|
||||||
|
var messageWrapper = this.refs.messagePanel;
|
||||||
|
if (messageWrapper === undefined) return null;
|
||||||
|
var wrapperRect = messageWrapper.getDOMNode().getBoundingClientRect();
|
||||||
|
|
||||||
|
for (var i = this.state.room.timeline.length-1; i >= 0; --i) {
|
||||||
|
var ev = this.state.room.timeline[i];
|
||||||
|
|
||||||
|
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = this.eventNodes[ev.getId()];
|
||||||
|
if (!node) continue;
|
||||||
|
|
||||||
|
var boundingRect = node.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (boundingRect.bottom < wrapperRect.bottom) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -15,7 +15,8 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_MemberAvatar {
|
.mx_MemberAvatar {
|
||||||
position: relative;
|
/* commenting this out as it breaks on FF seemingly */
|
||||||
|
/* position: relative; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberAvatar_initial {
|
.mx_MemberAvatar_initial {
|
||||||
@ -23,6 +24,7 @@ limitations under the License.
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
speak: none;
|
speak: none;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberAvatar_image {
|
.mx_MemberAvatar_image {
|
||||||
|
@ -23,4 +23,5 @@ limitations under the License.
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: normal ! important;
|
font-weight: normal ! important;
|
||||||
speak: none;
|
speak: none;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
@ -49,7 +49,6 @@ limitations under the License.
|
|||||||
.mx_EventTile .mx_MessageTimestamp {
|
.mx_EventTile .mx_MessageTimestamp {
|
||||||
color: #acacac;
|
color: #acacac;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
float: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_line {
|
.mx_EventTile_line {
|
||||||
@ -91,10 +90,18 @@ limitations under the License.
|
|||||||
|
|
||||||
.mx_EventTile_msgOption {
|
.mx_EventTile_msgOption {
|
||||||
float: right;
|
float: right;
|
||||||
|
text-align: right;
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
width: 90px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageTimestamp {
|
.mx_MessageTimestamp {
|
||||||
|
display: block;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_last .mx_MessageTimestamp {
|
.mx_EventTile_last .mx_MessageTimestamp {
|
||||||
@ -107,8 +114,7 @@ limitations under the License.
|
|||||||
|
|
||||||
.mx_EventTile_editButton {
|
.mx_EventTile_editButton {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 1px;
|
display: inline-block;
|
||||||
top: 15px;
|
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,3 +129,21 @@ limitations under the License.
|
|||||||
.mx_EventTile.menu .mx_MessageTimestamp {
|
.mx_EventTile.menu .mx_MessageTimestamp {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_readAvatars {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_readAvatars .mx_MemberAvatar {
|
||||||
|
position: absolute;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_readAvatarRemainder {
|
||||||
|
color: #acacac;
|
||||||
|
font-size: 12px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
@ -72,7 +72,8 @@ limitations under the License.
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageComposer_upload,
|
.mx_MessageComposer_upload,
|
||||||
.mx_MessageComposer_call {
|
.mx_MessageComposer_voicecall,
|
||||||
|
.mx_MessageComposer_videocall {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
@ -80,7 +81,12 @@ limitations under the License.
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageComposer_call {
|
.mx_MessageComposer_videocall {
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MessageComposer_voicecall {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,18 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.mx_MatrixChat_splash {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MatrixChat_splashButtons {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MatrixChat_wrapper {
|
.mx_MatrixChat_wrapper {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -moz-box;
|
display: -moz-box;
|
||||||
|
BIN
src/skins/vector/img/voice.png
Normal file
BIN
src/skins/vector/img/voice.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 856 B |
@ -23,6 +23,9 @@ limitations under the License.
|
|||||||
|
|
||||||
var skin = {};
|
var skin = {};
|
||||||
|
|
||||||
|
skin['atoms.create_room.CreateRoomButton'] = require('./views/atoms/create_room/CreateRoomButton');
|
||||||
|
skin['atoms.create_room.Presets'] = require('./views/atoms/create_room/Presets');
|
||||||
|
skin['atoms.create_room.RoomAlias'] = require('./views/atoms/create_room/RoomAlias');
|
||||||
skin['atoms.EditableText'] = require('./views/atoms/EditableText');
|
skin['atoms.EditableText'] = require('./views/atoms/EditableText');
|
||||||
skin['atoms.EnableNotificationsButton'] = require('./views/atoms/EnableNotificationsButton');
|
skin['atoms.EnableNotificationsButton'] = require('./views/atoms/EnableNotificationsButton');
|
||||||
skin['atoms.ImageView'] = require('./views/atoms/ImageView');
|
skin['atoms.ImageView'] = require('./views/atoms/ImageView');
|
||||||
@ -31,9 +34,6 @@ skin['atoms.MemberAvatar'] = require('./views/atoms/MemberAvatar');
|
|||||||
skin['atoms.MessageTimestamp'] = require('./views/atoms/MessageTimestamp');
|
skin['atoms.MessageTimestamp'] = require('./views/atoms/MessageTimestamp');
|
||||||
skin['atoms.RoomAvatar'] = require('./views/atoms/RoomAvatar');
|
skin['atoms.RoomAvatar'] = require('./views/atoms/RoomAvatar');
|
||||||
skin['atoms.Spinner'] = require('./views/atoms/Spinner');
|
skin['atoms.Spinner'] = require('./views/atoms/Spinner');
|
||||||
skin['atoms.create_room.CreateRoomButton'] = require('./views/atoms/create_room/CreateRoomButton');
|
|
||||||
skin['atoms.create_room.Presets'] = require('./views/atoms/create_room/Presets');
|
|
||||||
skin['atoms.create_room.RoomAlias'] = require('./views/atoms/create_room/RoomAlias');
|
|
||||||
skin['atoms.voip.VideoFeed'] = require('./views/atoms/voip/VideoFeed');
|
skin['atoms.voip.VideoFeed'] = require('./views/atoms/voip/VideoFeed');
|
||||||
skin['molecules.BottomLeftMenu'] = require('./views/molecules/BottomLeftMenu');
|
skin['molecules.BottomLeftMenu'] = require('./views/molecules/BottomLeftMenu');
|
||||||
skin['molecules.BottomLeftMenuTile'] = require('./views/molecules/BottomLeftMenuTile');
|
skin['molecules.BottomLeftMenuTile'] = require('./views/molecules/BottomLeftMenuTile');
|
||||||
@ -43,18 +43,18 @@ skin['molecules.ChangePassword'] = require('./views/molecules/ChangePassword');
|
|||||||
skin['molecules.DateSeparator'] = require('./views/molecules/DateSeparator');
|
skin['molecules.DateSeparator'] = require('./views/molecules/DateSeparator');
|
||||||
skin['molecules.EventAsTextTile'] = require('./views/molecules/EventAsTextTile');
|
skin['molecules.EventAsTextTile'] = require('./views/molecules/EventAsTextTile');
|
||||||
skin['molecules.EventTile'] = require('./views/molecules/EventTile');
|
skin['molecules.EventTile'] = require('./views/molecules/EventTile');
|
||||||
|
skin['molecules.MatrixToolbar'] = require('./views/molecules/MatrixToolbar');
|
||||||
|
skin['molecules.MemberInfo'] = require('./views/molecules/MemberInfo');
|
||||||
|
skin['molecules.MemberTile'] = require('./views/molecules/MemberTile');
|
||||||
skin['molecules.MEmoteTile'] = require('./views/molecules/MEmoteTile');
|
skin['molecules.MEmoteTile'] = require('./views/molecules/MEmoteTile');
|
||||||
|
skin['molecules.MessageComposer'] = require('./views/molecules/MessageComposer');
|
||||||
|
skin['molecules.MessageContextMenu'] = require('./views/molecules/MessageContextMenu');
|
||||||
|
skin['molecules.MessageTile'] = require('./views/molecules/MessageTile');
|
||||||
skin['molecules.MFileTile'] = require('./views/molecules/MFileTile');
|
skin['molecules.MFileTile'] = require('./views/molecules/MFileTile');
|
||||||
skin['molecules.MImageTile'] = require('./views/molecules/MImageTile');
|
skin['molecules.MImageTile'] = require('./views/molecules/MImageTile');
|
||||||
skin['molecules.MNoticeTile'] = require('./views/molecules/MNoticeTile');
|
skin['molecules.MNoticeTile'] = require('./views/molecules/MNoticeTile');
|
||||||
skin['molecules.MRoomMemberTile'] = require('./views/molecules/MRoomMemberTile');
|
skin['molecules.MRoomMemberTile'] = require('./views/molecules/MRoomMemberTile');
|
||||||
skin['molecules.MTextTile'] = require('./views/molecules/MTextTile');
|
skin['molecules.MTextTile'] = require('./views/molecules/MTextTile');
|
||||||
skin['molecules.MatrixToolbar'] = require('./views/molecules/MatrixToolbar');
|
|
||||||
skin['molecules.MemberInfo'] = require('./views/molecules/MemberInfo');
|
|
||||||
skin['molecules.MemberTile'] = require('./views/molecules/MemberTile');
|
|
||||||
skin['molecules.MessageComposer'] = require('./views/molecules/MessageComposer');
|
|
||||||
skin['molecules.MessageContextMenu'] = require('./views/molecules/MessageContextMenu');
|
|
||||||
skin['molecules.MessageTile'] = require('./views/molecules/MessageTile');
|
|
||||||
skin['molecules.ProgressBar'] = require('./views/molecules/ProgressBar');
|
skin['molecules.ProgressBar'] = require('./views/molecules/ProgressBar');
|
||||||
skin['molecules.RoomCreate'] = require('./views/molecules/RoomCreate');
|
skin['molecules.RoomCreate'] = require('./views/molecules/RoomCreate');
|
||||||
skin['molecules.RoomDropTarget'] = require('./views/molecules/RoomDropTarget');
|
skin['molecules.RoomDropTarget'] = require('./views/molecules/RoomDropTarget');
|
||||||
|
@ -49,12 +49,12 @@ module.exports = React.createClass({
|
|||||||
initial = this.props.member.name[1].toUpperCase();
|
initial = this.props.member.name[1].toUpperCase();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="mx_MemberAvatar">
|
<span className="mx_MemberAvatar" {...this.props}>
|
||||||
<span className="mx_MemberAvatar_initial" aria-hidden="true"
|
<span className="mx_MemberAvatar_initial" aria-hidden="true"
|
||||||
style={{ fontSize: (this.props.width * 0.75) + "px",
|
style={{ fontSize: (this.props.width * 0.75) + "px",
|
||||||
width: this.props.width + "px",
|
width: this.props.width + "px",
|
||||||
lineHeight: this.props.height*1.2 + "px" }}>{ initial }</span>
|
lineHeight: this.props.height*1.2 + "px" }}>{ initial }</span>
|
||||||
<img className="mx_MemberAvatar_image" src={this.state.imageUrl}
|
<img className="mx_MemberAvatar_image" src={this.state.imageUrl} title={this.props.member.name}
|
||||||
onError={this.onError} width={this.props.width} height={this.props.height} />
|
onError={this.onError} width={this.props.width} height={this.props.height} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@ -62,7 +62,10 @@ module.exports = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<img className="mx_MemberAvatar mx_MemberAvatar_image" src={this.state.imageUrl}
|
<img className="mx_MemberAvatar mx_MemberAvatar_image" src={this.state.imageUrl}
|
||||||
onError={this.onError}
|
onError={this.onError}
|
||||||
width={this.props.width} height={this.props.height} />
|
width={this.props.width} height={this.props.height}
|
||||||
|
title={this.props.member.name}
|
||||||
|
{...this.props}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -17,15 +17,28 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
var ReactDom = require('react-dom');
|
||||||
var classNames = require("classnames");
|
var classNames = require("classnames");
|
||||||
|
|
||||||
var sdk = require('matrix-react-sdk')
|
var sdk = require('matrix-react-sdk')
|
||||||
|
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg')
|
||||||
|
|
||||||
var EventTileController = require('matrix-react-sdk/lib/controllers/molecules/EventTile')
|
var EventTileController = require('matrix-react-sdk/lib/controllers/molecules/EventTile')
|
||||||
var ContextualMenu = require('../../../../ContextualMenu');
|
var ContextualMenu = require('../../../../ContextualMenu');
|
||||||
|
|
||||||
var TextForEvent = require('matrix-react-sdk/lib/TextForEvent');
|
var TextForEvent = require('matrix-react-sdk/lib/TextForEvent');
|
||||||
|
|
||||||
|
var Velociraptor = require('../../../../Velociraptor');
|
||||||
|
require('../../../../VelocityBounce');
|
||||||
|
|
||||||
|
var bounce = false;
|
||||||
|
try {
|
||||||
|
if (global.localStorage) {
|
||||||
|
bounce = global.localStorage.getItem('avatar_bounce') == 'true';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
|
||||||
var eventTileTypes = {
|
var eventTileTypes = {
|
||||||
'm.room.message': 'molecules.MessageTile',
|
'm.room.message': 'molecules.MessageTile',
|
||||||
'm.room.member' : 'molecules.EventAsTextTile',
|
'm.room.member' : 'molecules.EventAsTextTile',
|
||||||
@ -36,6 +49,8 @@ var eventTileTypes = {
|
|||||||
'm.room.topic' : 'molecules.EventAsTextTile',
|
'm.room.topic' : 'molecules.EventAsTextTile',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var MAX_READ_AVATARS = 5;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'EventTile',
|
displayName: 'EventTile',
|
||||||
mixins: [EventTileController],
|
mixins: [EventTileController],
|
||||||
@ -52,7 +67,11 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {menu: false};
|
return {menu: false, allReadAvatars: false};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidUpdate: function() {
|
||||||
|
this.readAvatarRect = ReactDom.findDOMNode(this.readAvatarNode).getBoundingClientRect();
|
||||||
},
|
},
|
||||||
|
|
||||||
onEditClicked: function(e) {
|
onEditClicked: function(e) {
|
||||||
@ -72,6 +91,123 @@ module.exports = React.createClass({
|
|||||||
this.setState({menu: true});
|
this.setState({menu: true});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleAllReadAvatars: function() {
|
||||||
|
this.setState({
|
||||||
|
allReadAvatars: !this.state.allReadAvatars
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getReadAvatars: function() {
|
||||||
|
var avatars = [];
|
||||||
|
|
||||||
|
var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
|
||||||
|
if (!room) return [];
|
||||||
|
|
||||||
|
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||||
|
|
||||||
|
// get list of read receipts, sorted most recent first
|
||||||
|
var receipts = room.getReceiptsForEvent(this.props.mxEvent).filter(function(r) {
|
||||||
|
return r.type === "m.read" && r.userId != myUserId;
|
||||||
|
}).sort(function(r1, r2) {
|
||||||
|
return r2.data.ts - r1.data.ts;
|
||||||
|
});
|
||||||
|
|
||||||
|
var MemberAvatar = sdk.getComponent('atoms.MemberAvatar');
|
||||||
|
|
||||||
|
var left = 0;
|
||||||
|
|
||||||
|
var reorderTransitionOpts = {
|
||||||
|
duration: 100,
|
||||||
|
easing: 'easeOut'
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < receipts.length; ++i) {
|
||||||
|
var member = room.getMember(receipts[i].userId);
|
||||||
|
|
||||||
|
// Using react refs here would mean both getting Velociraptor to expose
|
||||||
|
// them and making them scoped to the whole RoomView. Not impossible, but
|
||||||
|
// getElementById seems simpler at least for a first cut.
|
||||||
|
var oldAvatarDomNode = document.getElementById('mx_readAvatar'+member.userId);
|
||||||
|
var startStyles = [];
|
||||||
|
var enterTransitionOpts = [];
|
||||||
|
if (oldAvatarDomNode && this.readAvatarRect) {
|
||||||
|
var oldRect = oldAvatarDomNode.getBoundingClientRect();
|
||||||
|
var topOffset = oldRect.top - this.readAvatarRect.top;
|
||||||
|
|
||||||
|
if (oldAvatarDomNode.style.left !== '0px') {
|
||||||
|
var leftOffset = oldAvatarDomNode.style.left;
|
||||||
|
// start at the old height and in the old h pos
|
||||||
|
startStyles.push({ top: topOffset, left: leftOffset });
|
||||||
|
enterTransitionOpts.push(reorderTransitionOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// then shift to the rightmost column,
|
||||||
|
// and then it will drop down to its resting position
|
||||||
|
startStyles.push({ top: topOffset, left: '0px' });
|
||||||
|
enterTransitionOpts.push({
|
||||||
|
duration: bounce ? Math.min(Math.log(Math.abs(topOffset)) * 200, 3000) : 300,
|
||||||
|
easing: bounce ? 'easeOutBounce' : 'easeOutCubic',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var style = {
|
||||||
|
left: left+'px',
|
||||||
|
top: '0px',
|
||||||
|
visibility: ((i < MAX_READ_AVATARS) || this.state.allReadAvatars) ? 'visible' : 'hidden'
|
||||||
|
};
|
||||||
|
|
||||||
|
//console.log("i = " + i + ", MAX_READ_AVATARS = " + MAX_READ_AVATARS + ", allReadAvatars = " + this.state.allReadAvatars + " visibility = " + style.visibility);
|
||||||
|
|
||||||
|
// add to the start so the most recent is on the end (ie. ends up rightmost)
|
||||||
|
avatars.unshift(
|
||||||
|
<MemberAvatar key={member.userId} member={member}
|
||||||
|
width={14} height={14} resizeMethod="crop"
|
||||||
|
style={style}
|
||||||
|
startStyle={startStyles}
|
||||||
|
enterTransitionOpts={enterTransitionOpts}
|
||||||
|
id={'mx_readAvatar'+member.userId}
|
||||||
|
onClick={this.toggleAllReadAvatars}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
// TODO: we keep the extra read avatars in the dom to make animation simpler
|
||||||
|
// we could optimise this to reduce the dom size.
|
||||||
|
if (i < MAX_READ_AVATARS - 1 || this.state.allReadAvatars) { // XXX: where does this -1 come from? is it to make the max'th avatar animate properly?
|
||||||
|
left -= 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var editButton;
|
||||||
|
if (!this.state.allReadAvatars) {
|
||||||
|
var remainder = receipts.length - MAX_READ_AVATARS;
|
||||||
|
var remText;
|
||||||
|
if (i >= MAX_READ_AVATARS - 1) left -= 15;
|
||||||
|
if (remainder > 0) {
|
||||||
|
remText = <span className="mx_EventTile_readAvatarRemainder"
|
||||||
|
onClick={this.toggleAllReadAvatars}
|
||||||
|
style={{ left: left }}>{ remainder }+
|
||||||
|
</span>;
|
||||||
|
left -= 15;
|
||||||
|
}
|
||||||
|
editButton = (
|
||||||
|
<input style={{ left: left }}
|
||||||
|
type="image" src="img/edit.png" alt="Edit" width="14" height="14"
|
||||||
|
className="mx_EventTile_editButton" onClick={this.onEditClicked} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span className="mx_EventTile_readAvatars" ref={this.collectReadAvatarNode}>
|
||||||
|
{ editButton }
|
||||||
|
{ remText }
|
||||||
|
<Velociraptor transition={ reorderTransitionOpts }>
|
||||||
|
{ avatars }
|
||||||
|
</Velociraptor>
|
||||||
|
</span>;
|
||||||
|
},
|
||||||
|
|
||||||
|
collectReadAvatarNode: function(node) {
|
||||||
|
this.readAvatarNode = node;
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp');
|
var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp');
|
||||||
var SenderProfile = sdk.getComponent('molecules.SenderProfile');
|
var SenderProfile = sdk.getComponent('molecules.SenderProfile');
|
||||||
@ -100,18 +236,14 @@ module.exports = React.createClass({
|
|||||||
menu: this.state.menu,
|
menu: this.state.menu,
|
||||||
});
|
});
|
||||||
var timestamp = <MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
var timestamp = <MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
||||||
var editButton = (
|
|
||||||
<input
|
|
||||||
type="image" src="img/edit.png" alt="Edit" width="14" height="14"
|
|
||||||
className="mx_EventTile_editButton" onClick={this.onEditClicked}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
var aux = null;
|
var aux = null;
|
||||||
if (msgtype === 'm.image') aux = "sent an image";
|
if (msgtype === 'm.image') aux = "sent an image";
|
||||||
else if (msgtype === 'm.video') aux = "sent a video";
|
else if (msgtype === 'm.video') aux = "sent a video";
|
||||||
else if (msgtype === 'm.file') aux = "uploaded a file";
|
else if (msgtype === 'm.file') aux = "uploaded a file";
|
||||||
|
|
||||||
|
var readAvatars = this.getReadAvatars();
|
||||||
|
|
||||||
var avatar, sender;
|
var avatar, sender;
|
||||||
if (!this.props.continuation) {
|
if (!this.props.continuation) {
|
||||||
if (this.props.mxEvent.sender) {
|
if (this.props.mxEvent.sender) {
|
||||||
@ -127,11 +259,13 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
|
<div className="mx_EventTile_msgOption">
|
||||||
|
{ timestamp }
|
||||||
|
{ readAvatars }
|
||||||
|
</div>
|
||||||
{ avatar }
|
{ avatar }
|
||||||
{ sender }
|
{ sender }
|
||||||
<div className="mx_EventTile_line">
|
<div className="mx_EventTile_line">
|
||||||
{ timestamp }
|
|
||||||
{ editButton }
|
|
||||||
<EventTileType mxEvent={this.props.mxEvent} searchTerm={this.props.searchTerm} />
|
<EventTileType mxEvent={this.props.mxEvent} searchTerm={this.props.searchTerm} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,6 +28,10 @@ module.exports = React.createClass({
|
|||||||
displayName: 'MessageComposer',
|
displayName: 'MessageComposer',
|
||||||
mixins: [MessageComposerController],
|
mixins: [MessageComposerController],
|
||||||
|
|
||||||
|
onInputClick: function(ev) {
|
||||||
|
this.refs.textarea.focus();
|
||||||
|
},
|
||||||
|
|
||||||
onUploadClick: function(ev) {
|
onUploadClick: function(ev) {
|
||||||
this.refs.uploadInput.click();
|
this.refs.uploadInput.click();
|
||||||
},
|
},
|
||||||
@ -49,6 +53,14 @@ module.exports = React.createClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onVoiceCallClick: function(ev) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'place_call',
|
||||||
|
type: 'voice',
|
||||||
|
room_id: this.props.room.roomId
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
|
var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||||
var uploadInputStyle = {display: 'none'};
|
var uploadInputStyle = {display: 'none'};
|
||||||
@ -60,14 +72,17 @@ module.exports = React.createClass({
|
|||||||
<div className="mx_MessageComposer_avatar">
|
<div className="mx_MessageComposer_avatar">
|
||||||
<MemberAvatar member={me} width={24} height={24} />
|
<MemberAvatar member={me} width={24} height={24} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_MessageComposer_input">
|
<div className="mx_MessageComposer_input" onClick={ this.onInputClick }>
|
||||||
<textarea ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder="Type a message..." />
|
<textarea ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder="Type a message..." />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_MessageComposer_upload" onClick={this.onUploadClick}>
|
<div className="mx_MessageComposer_upload" onClick={this.onUploadClick}>
|
||||||
<img src="img/upload.png" width="17" height="22"/>
|
<img src="img/upload.png" width="17" height="22"/>
|
||||||
<input type="file" style={uploadInputStyle} ref="uploadInput" onChange={this.onUploadFileSelected} />
|
<input type="file" style={uploadInputStyle} ref="uploadInput" onChange={this.onUploadFileSelected} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_MessageComposer_call" onClick={this.onCallClick}>
|
<div className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick}>
|
||||||
|
<img src="img/voice.png" width="16" height="26"/>
|
||||||
|
</div>
|
||||||
|
<div className="mx_MessageComposer_videocall" onClick={this.onCallClick}>
|
||||||
<img src="img/call.png" width="28" height="20"/>
|
<img src="img/call.png" width="28" height="20"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,6 +65,14 @@ module.exports = React.createClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onLogoutClick: function(event) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'logout'
|
||||||
|
});
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
handleResize: function(e) {
|
handleResize: function(e) {
|
||||||
var hideLhsThreshold = 1000;
|
var hideLhsThreshold = 1000;
|
||||||
var showLhsThreshold = 1000;
|
var showLhsThreshold = 1000;
|
||||||
@ -164,7 +172,10 @@ module.exports = React.createClass({
|
|||||||
} else if (this.state.logged_in) {
|
} else if (this.state.logged_in) {
|
||||||
var Spinner = sdk.getComponent('atoms.Spinner');
|
var Spinner = sdk.getComponent('atoms.Spinner');
|
||||||
return (
|
return (
|
||||||
|
<div className="mx_MatrixChat_splash">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>Logout</a>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
} else if (this.state.screen == 'register') {
|
} else if (this.state.screen == 'register') {
|
||||||
/*
|
/*
|
||||||
|
Loading…
Reference in New Issue
Block a user