mirror of
https://github.com/SchildiChat/element-web.git
synced 2024-10-01 01:26:12 -04:00
Merge remote-tracking branch 'origin/develop' into read_receipts
This commit is contained in:
commit
450036a6ed
@ -31,10 +31,15 @@
|
||||
"modernizr": "^3.1.0",
|
||||
"matrix-js-sdk": "https://github.com/matrix-org/matrix-js-sdk.git#develop",
|
||||
"matrix-react-sdk": "^0.0.2",
|
||||
"modernizr": "^3.1.0",
|
||||
"q": "^1.4.1",
|
||||
"react": "^0.13.3",
|
||||
"react": "^0.14.2",
|
||||
"react-dnd": "^2.0.2",
|
||||
"react-dnd-html5-backend": "^2.0.0",
|
||||
"react-dom": "^0.14.2",
|
||||
"react-gemini-scrollbar": "^2.0.1",
|
||||
"react-loader": "^1.4.0",
|
||||
"sanitize-html": "^1.11.1"
|
||||
"sanitize-html": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel": "^5.8.23",
|
||||
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
|
||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||
@ -74,7 +75,7 @@ module.exports = {
|
||||
</div>
|
||||
);
|
||||
|
||||
React.render(menu, this.getOrCreateContainer());
|
||||
ReactDOM.render(menu, this.getOrCreateContainer());
|
||||
|
||||
return {close: closeMenu};
|
||||
},
|
||||
|
24
src/Resend.js
Normal file
24
src/Resend.js
Normal file
@ -0,0 +1,24 @@
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
module.exports = {
|
||||
resend: function(event) {
|
||||
MatrixClientPeg.get().resendEvent(
|
||||
event, MatrixClientPeg.get().getRoom(event.getRoomId())
|
||||
).done(function() {
|
||||
dis.dispatch({
|
||||
action: 'message_sent',
|
||||
event: event
|
||||
});
|
||||
}, function() {
|
||||
dis.dispatch({
|
||||
action: 'message_send_failed',
|
||||
event: event
|
||||
});
|
||||
});
|
||||
dis.dispatch({
|
||||
action: 'message_resend_started',
|
||||
event: event
|
||||
});
|
||||
},
|
||||
};
|
@ -17,22 +17,31 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
|
||||
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
|
||||
var RoomListSorter = require("matrix-react-sdk/lib/RoomListSorter");
|
||||
var dis = require("matrix-react-sdk/lib/dispatcher");
|
||||
|
||||
var sdk = require('matrix-react-sdk');
|
||||
var VectorConferenceHandler = require("../../modules/VectorConferenceHandler");
|
||||
var CallHandler = require("matrix-react-sdk/lib/CallHandler");
|
||||
|
||||
var HIDE_CONFERENCE_CHANS = true;
|
||||
|
||||
module.exports = {
|
||||
getInitialState: function() {
|
||||
return {
|
||||
activityMap: null,
|
||||
lists: {},
|
||||
}
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
var cli = MatrixClientPeg.get();
|
||||
cli.on("Room", this.onRoom);
|
||||
cli.on("Room.timeline", this.onRoomTimeline);
|
||||
cli.on("Room.name", this.onRoomName);
|
||||
cli.on("Room.tags", this.onRoomTags);
|
||||
cli.on("RoomState.events", this.onRoomStateEvents);
|
||||
cli.on("RoomMember.name", this.onRoomMemberName);
|
||||
|
||||
@ -47,11 +56,6 @@ module.exports = {
|
||||
|
||||
onAction: function(payload) {
|
||||
switch (payload.action) {
|
||||
// listen for call state changes to prod the render method, which
|
||||
// may hide the global CallView if the call it is tracking is dead
|
||||
case 'call_state':
|
||||
this._recheckCallElement(this.props.selectedRoom);
|
||||
break;
|
||||
case 'view_tooltip':
|
||||
this.tooltip = payload.tooltip;
|
||||
this._repositionTooltip();
|
||||
@ -72,7 +76,6 @@ module.exports = {
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
this.state.activityMap[newProps.selectedRoom] = undefined;
|
||||
this._recheckCallElement(newProps.selectedRoom);
|
||||
this.setState({
|
||||
activityMap: this.state.activityMap
|
||||
});
|
||||
@ -109,6 +112,10 @@ module.exports = {
|
||||
this.refreshRoomList();
|
||||
},
|
||||
|
||||
onRoomTags: function(event, room) {
|
||||
this.refreshRoomList();
|
||||
},
|
||||
|
||||
onRoomStateEvents: function(ev, state) {
|
||||
setTimeout(this.refreshRoomList, 0);
|
||||
},
|
||||
@ -117,26 +124,36 @@ module.exports = {
|
||||
setTimeout(this.refreshRoomList, 0);
|
||||
},
|
||||
|
||||
|
||||
refreshRoomList: function() {
|
||||
// TODO: rather than bluntly regenerating and re-sorting everything
|
||||
// every time we see any kind of room change from the JS SDK
|
||||
// we could do incremental updates on our copy of the state
|
||||
// based on the room which has actually changed. This would stop
|
||||
// us re-rendering all the sublists every time anything changes anywhere
|
||||
// in the state of the client.
|
||||
this.setState(this.getRoomLists());
|
||||
},
|
||||
|
||||
getRoomLists: function() {
|
||||
var s = {};
|
||||
var inviteList = [];
|
||||
s.roomList = RoomListSorter.mostRecentActivityFirst(
|
||||
MatrixClientPeg.get().getRooms().filter(function(room) {
|
||||
var s = { lists: {} };
|
||||
|
||||
s.lists["m.invite"] = [];
|
||||
s.lists["m.favourite"] = [];
|
||||
s.lists["m.recent"] = [];
|
||||
s.lists["m.lowpriority"] = [];
|
||||
s.lists["m.archived"] = [];
|
||||
|
||||
MatrixClientPeg.get().getRooms().forEach(function(room) {
|
||||
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
|
||||
if (me && me.membership == "invite") {
|
||||
inviteList.push(room);
|
||||
return false;
|
||||
s.lists["m.invite"].push(room);
|
||||
}
|
||||
|
||||
else {
|
||||
var shouldShowRoom = (
|
||||
me && (me.membership == "join")
|
||||
);
|
||||
|
||||
// hiding conf rooms only ever toggles shouldShowRoom to false
|
||||
if (shouldShowRoom && HIDE_CONFERENCE_CHANS) {
|
||||
// we want to hide the 1:1 conf<->user room and not the group chat
|
||||
@ -151,48 +168,34 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
}
|
||||
return shouldShowRoom;
|
||||
})
|
||||
);
|
||||
s.inviteList = RoomListSorter.mostRecentActivityFirst(inviteList);
|
||||
return s;
|
||||
},
|
||||
|
||||
_recheckCallElement: function(selectedRoomId) {
|
||||
// if we aren't viewing a room with an ongoing call, but there is an
|
||||
// active call, show the call element - we need to do this to make
|
||||
// audio/video not crap out
|
||||
var activeCall = CallHandler.getAnyActiveCall();
|
||||
var callForRoom = CallHandler.getCallForRoom(selectedRoomId);
|
||||
var showCall = (activeCall && !callForRoom);
|
||||
this.setState({
|
||||
show_call_element: showCall
|
||||
if (shouldShowRoom) {
|
||||
var tagNames = Object.keys(room.tags);
|
||||
if (tagNames.length) {
|
||||
for (var i = 0; i < tagNames.length; i++) {
|
||||
var tagName = tagNames[i];
|
||||
s.lists[tagName] = s.lists[tagName] || [];
|
||||
s.lists[tagNames[i]].push(room);
|
||||
}
|
||||
}
|
||||
else {
|
||||
s.lists["m.recent"].push(room);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//console.log("calculated new roomLists; m.recent = " + s.lists["m.recent"]);
|
||||
|
||||
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
|
||||
|
||||
return s;
|
||||
},
|
||||
|
||||
_repositionTooltip: function(e) {
|
||||
if (this.tooltip && this.tooltip.parentElement) {
|
||||
var scroll = this.getDOMNode();
|
||||
this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.scrollTop) + "px";
|
||||
var scroll = ReactDOM.findDOMNode(this);
|
||||
this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.children[2].scrollTop) + "px";
|
||||
}
|
||||
},
|
||||
|
||||
makeRoomTiles: function(list, isInvite) {
|
||||
var self = this;
|
||||
var RoomTile = sdk.getComponent("molecules.RoomTile");
|
||||
return list.map(function(room) {
|
||||
var selected = room.roomId == self.props.selectedRoom;
|
||||
return (
|
||||
<RoomTile
|
||||
room={room}
|
||||
key={room.roomId}
|
||||
collapsed={self.props.collapsed}
|
||||
selected={selected}
|
||||
unread={self.state.activityMap[room.roomId] === 1}
|
||||
highlight={self.state.activityMap[room.roomId] === 2}
|
||||
isInvite={isInvite}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
|
||||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
var q = require("q");
|
||||
var ContentMessages = require("matrix-react-sdk/lib//ContentMessages");
|
||||
var WhoIsTyping = require("matrix-react-sdk/lib/WhoIsTyping");
|
||||
@ -24,6 +25,7 @@ var Modal = require("matrix-react-sdk/lib/Modal");
|
||||
var sdk = require('matrix-react-sdk/lib/index');
|
||||
var CallHandler = require('matrix-react-sdk/lib/CallHandler');
|
||||
var VectorConferenceHandler = require('../../modules/VectorConferenceHandler');
|
||||
var Resend = require("../../Resend");
|
||||
|
||||
var dis = require("matrix-react-sdk/lib/dispatcher");
|
||||
|
||||
@ -32,8 +34,9 @@ var INITIAL_SIZE = 20;
|
||||
|
||||
module.exports = {
|
||||
getInitialState: function() {
|
||||
var room = this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null;
|
||||
return {
|
||||
room: this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null,
|
||||
room: room,
|
||||
messageCap: INITIAL_SIZE,
|
||||
editingRoomSettings: false,
|
||||
uploadingRoomSettings: false,
|
||||
@ -41,6 +44,8 @@ module.exports = {
|
||||
draggingFile: false,
|
||||
searching: false,
|
||||
searchResults: null,
|
||||
syncState: MatrixClientPeg.get().getSyncState(),
|
||||
hasUnsentMessages: this._hasUnsentMessages(room)
|
||||
}
|
||||
},
|
||||
|
||||
@ -51,12 +56,13 @@ module.exports = {
|
||||
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
|
||||
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
|
||||
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
|
||||
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
|
||||
this.atBottom = true;
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
var messageWrapper = ReactDOM.findDOMNode(this.refs.messageWrapper);
|
||||
messageWrapper.removeEventListener('drop', this.onDrop);
|
||||
messageWrapper.removeEventListener('dragover', this.onDragOver);
|
||||
messageWrapper.removeEventListener('dragleave', this.onDragLeaveOrEnd);
|
||||
@ -69,6 +75,7 @@ module.exports = {
|
||||
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
|
||||
MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping);
|
||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||
MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange);
|
||||
}
|
||||
},
|
||||
|
||||
@ -76,6 +83,9 @@ module.exports = {
|
||||
switch (payload.action) {
|
||||
case 'message_send_failed':
|
||||
case 'message_sent':
|
||||
this.setState({
|
||||
hasUnsentMessages: this._hasUnsentMessages(this.state.room)
|
||||
});
|
||||
case 'message_resend_started':
|
||||
this.setState({
|
||||
room: MatrixClientPeg.get().getRoom(this.props.roomId)
|
||||
@ -92,8 +102,8 @@ module.exports = {
|
||||
// scroll to bottom
|
||||
var messageWrapper = this.refs.messageWrapper;
|
||||
if (messageWrapper) {
|
||||
messageWrapper = messageWrapper.getDOMNode();
|
||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||
var messageWrapperScroll = ReactDOM.findDOMNode(messageWrapper).children[2];
|
||||
messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,6 +117,12 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
onSyncStateChange: function(state) {
|
||||
this.setState({
|
||||
syncState: state
|
||||
});
|
||||
},
|
||||
|
||||
// MatrixRoom still showing the messages from the old room?
|
||||
// Set the key to the room_id. Sadly you can no longer get at
|
||||
// the key from inside the component, or we'd check this in code.
|
||||
@ -116,7 +132,7 @@ module.exports = {
|
||||
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
||||
if (!this.isMounted()) return;
|
||||
|
||||
// ignore anything that comes in whilst pagingating: we get one
|
||||
// ignore anything that comes in whilst paginating: we get one
|
||||
// event for each new matrix event so this would cause a huge
|
||||
// number of UI updates. Just update the UI when the paginate
|
||||
// call returns.
|
||||
@ -128,10 +144,10 @@ module.exports = {
|
||||
if (room.roomId != this.props.roomId) return;
|
||||
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
|
||||
this.atBottom = (
|
||||
messageWrapper.scrollHeight - messageWrapper.scrollTop <=
|
||||
(messageWrapper.clientHeight + 150)
|
||||
messageWrapperScroll.scrollHeight - messageWrapperScroll.scrollTop <=
|
||||
(messageWrapperScroll.clientHeight + 150)
|
||||
);
|
||||
}
|
||||
|
||||
@ -184,6 +200,19 @@ module.exports = {
|
||||
this._updateConfCallNotification();
|
||||
},
|
||||
|
||||
_hasUnsentMessages: function(room) {
|
||||
return this._getUnsentMessages(room).length > 0;
|
||||
},
|
||||
|
||||
_getUnsentMessages: function(room) {
|
||||
if (!room) { return []; }
|
||||
// TODO: It would be nice if the JS SDK provided nicer constant-time
|
||||
// constructs rather than O(N) (N=num msgs) on this.
|
||||
return room.timeline.filter(function(ev) {
|
||||
return ev.status === Matrix.EventStatus.NOT_SENT;
|
||||
});
|
||||
},
|
||||
|
||||
_updateConfCallNotification: function() {
|
||||
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
if (!room) return;
|
||||
@ -208,14 +237,16 @@ module.exports = {
|
||||
|
||||
componentDidMount: function() {
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
var messageWrapper = ReactDOM.findDOMNode(this.refs.messageWrapper);
|
||||
|
||||
messageWrapper.addEventListener('drop', this.onDrop);
|
||||
messageWrapper.addEventListener('dragover', this.onDragOver);
|
||||
messageWrapper.addEventListener('dragleave', this.onDragLeaveOrEnd);
|
||||
messageWrapper.addEventListener('dragend', this.onDragLeaveOrEnd);
|
||||
|
||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||
var messageWrapperScroll = messageWrapper.children[2];
|
||||
|
||||
messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
|
||||
|
||||
this.sendReadReceipt();
|
||||
|
||||
@ -228,17 +259,17 @@ module.exports = {
|
||||
componentDidUpdate: function() {
|
||||
if (!this.refs.messageWrapper) return;
|
||||
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
|
||||
|
||||
if (this.state.paginating && !this.waiting_for_paginate) {
|
||||
var heightGained = messageWrapper.scrollHeight - this.oldScrollHeight;
|
||||
messageWrapper.scrollTop += heightGained;
|
||||
var heightGained = messageWrapperScroll.scrollHeight - this.oldScrollHeight;
|
||||
messageWrapperScroll.scrollTop += heightGained;
|
||||
this.oldScrollHeight = undefined;
|
||||
if (!this.fillSpace()) {
|
||||
this.setState({paginating: false});
|
||||
}
|
||||
} else if (this.atBottom) {
|
||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||
messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
|
||||
if (this.state.numUnreadMessages !== 0) {
|
||||
this.setState({numUnreadMessages: 0});
|
||||
}
|
||||
@ -247,11 +278,11 @@ module.exports = {
|
||||
|
||||
fillSpace: function() {
|
||||
if (!this.refs.messageWrapper) return;
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
if (messageWrapper.scrollTop < messageWrapper.clientHeight && this.state.room.oldState.paginationToken) {
|
||||
var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
|
||||
if (messageWrapperScroll.scrollTop < messageWrapperScroll.clientHeight && this.state.room.oldState.paginationToken) {
|
||||
this.setState({paginating: true});
|
||||
|
||||
this.oldScrollHeight = messageWrapper.scrollHeight;
|
||||
this.oldScrollHeight = messageWrapperScroll.scrollHeight;
|
||||
|
||||
if (this.state.messageCap < this.state.room.timeline.length) {
|
||||
this.waiting_for_paginate = false;
|
||||
@ -278,6 +309,13 @@ module.exports = {
|
||||
return false;
|
||||
},
|
||||
|
||||
onResendAllClick: function() {
|
||||
var eventsToResend = this._getUnsentMessages(this.state.room);
|
||||
eventsToResend.forEach(function(event) {
|
||||
Resend.resend(event);
|
||||
});
|
||||
},
|
||||
|
||||
onJoinButtonClicked: function(ev) {
|
||||
var self = this;
|
||||
MatrixClientPeg.get().joinRoom(this.props.roomId).then(function() {
|
||||
@ -298,9 +336,9 @@ module.exports = {
|
||||
|
||||
onMessageListScroll: function(ev) {
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
|
||||
var wasAtBottom = this.atBottom;
|
||||
this.atBottom = messageWrapper.scrollHeight - messageWrapper.scrollTop <= messageWrapper.clientHeight;
|
||||
this.atBottom = messageWrapperScroll.scrollHeight - messageWrapperScroll.scrollTop <= messageWrapperScroll.clientHeight;
|
||||
if (this.atBottom && !wasAtBottom) {
|
||||
this.forceUpdate(); // remove unread msg count
|
||||
}
|
||||
@ -363,8 +401,12 @@ module.exports = {
|
||||
self.setState({
|
||||
upload: undefined
|
||||
});
|
||||
}).done(undefined, function() {
|
||||
// display error message
|
||||
}).done(undefined, function(error) {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to upload file",
|
||||
description: error.toString()
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -390,6 +432,7 @@ module.exports = {
|
||||
room_events: {
|
||||
search_term: term,
|
||||
filter: filter,
|
||||
order_by: "recent",
|
||||
event_context: {
|
||||
before_limit: 1,
|
||||
after_limit: 1,
|
||||
@ -403,7 +446,11 @@ module.exports = {
|
||||
searchResults: data,
|
||||
});
|
||||
}, function(error) {
|
||||
// TODO: show dialog or something
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Search failed",
|
||||
description: error.toString()
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -421,7 +468,7 @@ module.exports = {
|
||||
var eventIds = Object.keys(results);
|
||||
// XXX: todo: merge overlapping results somehow?
|
||||
// XXX: why doesn't searching on name work?
|
||||
var resultList = eventIds.map(function(key) { return results[key]; }).sort(function(a, b) { b.rank - a.rank });
|
||||
var resultList = eventIds.map(function(key) { return results[key]; }); // .sort(function(a, b) { b.rank - a.rank });
|
||||
for (var i = 0; i < resultList.length; i++) {
|
||||
var ts1 = resultList[i].result.origin_server_ts;
|
||||
ret.push(<li key={ts1 + "-search"}><DateSeparator ts={ts1}/></li>); // Rank: {resultList[i].rank}
|
||||
|
@ -19,3 +19,12 @@ limitations under the License.
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.mx_MemberAvatar_initial {
|
||||
position: absolute;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mx_MemberAvatar_wrapper {
|
||||
position: relative;
|
||||
}
|
25
src/skins/vector/css/atoms/RoomAvatar.css
Normal file
25
src/skins/vector/css/atoms/RoomAvatar.css
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.mx_RoomAvatar {
|
||||
}
|
||||
|
||||
.mx_RoomAvatar_initial {
|
||||
position: absolute;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-weight: normal ! important;
|
||||
}
|
25
src/skins/vector/css/atoms/Spinner.css
Normal file
25
src/skins/vector/css/atoms/Spinner.css
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.mx_Spinner {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-justify-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
1
src/skins/vector/css/gemini-scrollbar.css
Symbolic link
1
src/skins/vector/css/gemini-scrollbar.css
Symbolic link
@ -0,0 +1 @@
|
||||
../../../../node_modules/react-gemini-scrollbar/node_modules/gemini-scrollbar/gemini-scrollbar.css
|
@ -1,4 +1,3 @@
|
||||
.mx_RoomDropTarget,
|
||||
.mx_RoomSettings_encrypt,
|
||||
.mx_CreateRoom_encrypt,
|
||||
.mx_RightPanel_filebutton
|
||||
|
@ -18,13 +18,13 @@ limitations under the License.
|
||||
max-width: 100%;
|
||||
clear: both;
|
||||
margin-top: 24px;
|
||||
margin-left: 56px;
|
||||
margin-left: 65px;
|
||||
}
|
||||
|
||||
.mx_EventTile_avatar {
|
||||
padding-left: 18px;
|
||||
padding-right: 12px;
|
||||
margin-left: -64px;
|
||||
margin-left: -73px;
|
||||
margin-top: -4px;
|
||||
float: left;
|
||||
}
|
||||
@ -78,7 +78,7 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.mx_EventTile_notSent {
|
||||
color: #f11;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.mx_EventTile_highlight {
|
||||
|
@ -15,20 +15,40 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_MatrixToolbar {
|
||||
text-align: center;
|
||||
background-color: #ff0064;
|
||||
background-color: #76cfa6;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
padding: 6px;
|
||||
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mx_MatrixToolbar button {
|
||||
margin-left: 12px;
|
||||
.mx_MatrixToolbar_warning {
|
||||
margin-left: 16px;
|
||||
margin-right: 8px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.mx_MatrixToolbar_link
|
||||
{
|
||||
color: #fff ! important;
|
||||
text-decoration: underline ! important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_MatrixToolbar_close {
|
||||
float: right;
|
||||
margin-top: 3px;
|
||||
margin-right: 12px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.mx_MatrixToolbar_close img {
|
||||
display: block;
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
@ -32,7 +32,7 @@ limitations under the License.
|
||||
.mx_MessageComposer .mx_MessageComposer_avatar {
|
||||
display: table-cell;
|
||||
padding-left: 10px;
|
||||
padding-right: 20px;
|
||||
padding-right: 28px;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
|
@ -16,12 +16,46 @@ limitations under the License.
|
||||
|
||||
.mx_RoomDropTarget {
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
background-color: #fbfbfb;
|
||||
border: 1px dashed #d7d7d7;
|
||||
border-radius: 8px;
|
||||
margin-left: 10px;
|
||||
margin-right: 15px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
border: 1px dashed #76cfa6;
|
||||
color: #454545;
|
||||
background-color: rgba(255,255,255,0.5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomDropTarget {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.mx_RoomDropTarget_placeholder {
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.mx_RoomDropTarget_avatar {
|
||||
background-color: #fff;
|
||||
border-radius: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
float: left;
|
||||
margin-left: 7px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.mx_RoomDropTarget_label {
|
||||
position: relative;
|
||||
margin-top: 3px;
|
||||
line-height: 21px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomDropTarget_avatar {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomDropTarget_label {
|
||||
display: none;
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ limitations under the License.
|
||||
.mx_RoomHeader_leftRow {
|
||||
height: 48px;
|
||||
margin-top: 18px;
|
||||
margin-left: -2px;
|
||||
|
||||
-webkit-box-ordinal-group: 1;
|
||||
-moz-box-ordinal-group: 1;
|
||||
@ -103,7 +104,7 @@ limitations under the License.
|
||||
color: #454545;
|
||||
font-weight: 800;
|
||||
font-size: 24px;
|
||||
padding-left: 8px;
|
||||
padding-left: 19px;
|
||||
padding-right: 16px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@ -153,7 +154,7 @@ limitations under the License.
|
||||
max-height: 38px;
|
||||
color: #454545;
|
||||
font-weight: 300;
|
||||
padding-left: 8px;
|
||||
padding-left: 19px;
|
||||
padding-right: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -16,13 +16,13 @@ limitations under the License.
|
||||
|
||||
.mx_RoomTile {
|
||||
cursor: pointer;
|
||||
display: table-row;
|
||||
/* This fixes wrapping of long room names, but breaks drag & drop previews */
|
||||
/* display: table-row; */
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mx_RoomTile_avatar {
|
||||
display: table-cell;
|
||||
background: #eaf5f0;
|
||||
padding-right: 8px;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 2px;
|
||||
@ -39,17 +39,16 @@ limitations under the License.
|
||||
|
||||
.mx_RoomTile_name {
|
||||
display: table-cell;
|
||||
width: 100%;
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 16px;
|
||||
color: #454545;
|
||||
opacity: 0.8;
|
||||
color: rgba(69, 69, 69, 0.8);
|
||||
}
|
||||
|
||||
.mx_RoomTile_invite {
|
||||
opacity: 0.5;
|
||||
font-weight: normal;
|
||||
color: rgba(69, 69, 69, 0.5);
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomTile_name {
|
||||
@ -106,15 +105,16 @@ limitations under the License.
|
||||
|
||||
.mx_RoomTile_unread,
|
||||
.mx_RoomTile_highlight,
|
||||
.mx_RoomTile_invited
|
||||
.mx_RoomTile_selected
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mx_RoomTile_selected {
|
||||
.mx_RoomTile_selected .mx_RoomTile_name {
|
||||
color: #76cfa6 ! important;
|
||||
}
|
||||
|
||||
.mx_RoomTile.mx_RoomTile_selected {
|
||||
.mx_RoomTile.mx_RoomTile_selected .mx_RoomTile_name {
|
||||
background: url('img/selected.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: right center;
|
||||
|
@ -21,7 +21,6 @@ limitations under the License.
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
z-index: 1000;
|
||||
margin-top: 6px;
|
||||
left: 64px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
@ -34,16 +34,21 @@ limitations under the License.
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_RoomList {
|
||||
.mx_LeftPanel_callView {
|
||||
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_RoomList_scrollbar {
|
||||
-webkit-box-ordinal-group: 1;
|
||||
-moz-box-ordinal-group: 1;
|
||||
-ms-flex-order: 1;
|
||||
-webkit-order: 1;
|
||||
order: 1;
|
||||
|
||||
overflow-y: auto;
|
||||
-webkit-flex: 1 1 0;
|
||||
flex: 1 1 0;
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_BottomLeftMenu {
|
||||
@ -53,8 +58,10 @@ limitations under the License.
|
||||
-webkit-order: 3;
|
||||
order: 3;
|
||||
|
||||
-webkit-flex: 0 0 126px;
|
||||
flex: 0 0 126px;
|
||||
-webkit-flex: 0 0 140px;
|
||||
flex: 0 0 140px;
|
||||
|
||||
background-color: rgba(118,207,166,0.19);
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile {
|
||||
@ -62,7 +69,7 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options {
|
||||
margin-top: 12px;
|
||||
margin-top: 17px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -16,13 +16,7 @@ limitations under the License.
|
||||
|
||||
.mx_RoomList {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.mx_RoomList_invites,
|
||||
.mx_RoomList_recents {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.mx_RoomList_expandButton {
|
||||
@ -31,14 +25,3 @@ limitations under the License.
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.mx_RoomList h2 {
|
||||
text-transform: uppercase;
|
||||
color: #3d3b39;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
45
src/skins/vector/css/organisms/RoomSubList.css
Normal file
45
src/skins/vector/css/organisms/RoomSubList.css
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.mx_RoomSubList {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_bottommost {
|
||||
/* XXX: this should really be 100% of the RoomList height, but can't seem to get at it */
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_label {
|
||||
text-transform: uppercase;
|
||||
color: #3d3b39;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_chevron {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomSubList_chevron {
|
||||
padding-left: 13px;
|
||||
}
|
@ -129,7 +129,7 @@ limitations under the License.
|
||||
clear: both;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 8px;
|
||||
margin-left: 54px;
|
||||
margin-left: 63px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
@ -158,18 +158,19 @@ limitations under the License.
|
||||
order: 4;
|
||||
|
||||
width: 100%;
|
||||
-webkit-flex: 0 0 36px;
|
||||
flex: 0 0 36px;
|
||||
-webkit-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.mx_RoomView_statusAreaBox {
|
||||
max-width: 960px;
|
||||
margin: auto;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.mx_RoomView_statusAreaBox_line {
|
||||
border-top: 1px solid #eee;
|
||||
margin-left: 54px;
|
||||
margin-left: 63px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
@ -185,16 +186,44 @@ limitations under the License.
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_RoomView_connectionLostBar {
|
||||
margin-top: 19px;
|
||||
height: 58px;
|
||||
}
|
||||
|
||||
.mx_RoomView_connectionLostBar img {
|
||||
padding-left: 10px;
|
||||
padding-right: 22px;
|
||||
vertical-align: middle;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.mx_RoomView_connectionLostBar_title {
|
||||
color: #ff0064;
|
||||
}
|
||||
|
||||
.mx_RoomView_connectionLostBar_desc {
|
||||
color: #454545;
|
||||
font-size: 14px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.mx_RoomView_resend_link {
|
||||
color: #454545 ! important;
|
||||
text-decoration: underline ! important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_RoomView_typingBar {
|
||||
margin-top: 10px;
|
||||
margin-left: 54px;
|
||||
margin-left: 63px;
|
||||
color: #4a4a4a;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.mx_RoomView_typingImage {
|
||||
display: inline;
|
||||
margin-left: -38px;
|
||||
margin-left: -47px;
|
||||
margin-top: -4px;
|
||||
float: left;
|
||||
}
|
||||
@ -214,7 +243,7 @@ limitations under the License.
|
||||
|
||||
.mx_RoomView_uploadProgressOuter {
|
||||
height: 4px;
|
||||
margin-left: 54px;
|
||||
margin-left: 63px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
@ -225,7 +254,7 @@ limitations under the License.
|
||||
|
||||
.mx_RoomView_uploadFilename {
|
||||
margin-top: 5px;
|
||||
margin-left: 56px;
|
||||
margin-left: 65px;
|
||||
opacity: 0.5;
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ limitations under the License.
|
||||
-webkit-order: 1;
|
||||
order: 1;
|
||||
|
||||
height: 21px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.mx_MatrixChat_toolbarShowing {
|
||||
@ -71,8 +71,8 @@ limitations under the License.
|
||||
|
||||
background-color: #eaf5f0;
|
||||
|
||||
-webkit-flex: 0 0 230px;
|
||||
flex: 0 0 230px;
|
||||
-webkit-flex: 0 0 210px;
|
||||
flex: 0 0 210px;
|
||||
}
|
||||
|
||||
.mx_MatrixChat .mx_LeftPanel.collapsed {
|
||||
@ -87,8 +87,8 @@ limitations under the License.
|
||||
-webkit-order: 2;
|
||||
order: 2;
|
||||
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
padding-left: 25px;
|
||||
padding-right: 22px;
|
||||
background-color: #fff;
|
||||
|
||||
-webkit-flex: 1;
|
||||
@ -116,8 +116,8 @@ limitations under the License.
|
||||
-webkit-order: 3;
|
||||
order: 3;
|
||||
|
||||
-webkit-flex: 0 0 230px;
|
||||
flex: 0 0 230px;
|
||||
-webkit-flex: 0 0 235px;
|
||||
flex: 0 0 235px;
|
||||
}
|
||||
|
||||
.mx_MatrixChat .mx_RightPanel.collapsed {
|
||||
|
BIN
src/skins/vector/img/cancel-black2.png
Normal file
BIN
src/skins/vector/img/cancel-black2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
src/skins/vector/img/list-close.png
Normal file
BIN
src/skins/vector/img/list-close.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
src/skins/vector/img/list-open.png
Normal file
BIN
src/skins/vector/img/list-open.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
src/skins/vector/img/warning.png
Normal file
BIN
src/skins/vector/img/warning.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
src/skins/vector/img/warning2.png
Normal file
BIN
src/skins/vector/img/warning2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -81,6 +81,7 @@ skin['organisms.QuestionDialog'] = require('./views/organisms/QuestionDialog');
|
||||
skin['organisms.RightPanel'] = require('./views/organisms/RightPanel');
|
||||
skin['organisms.RoomDirectory'] = require('./views/organisms/RoomDirectory');
|
||||
skin['organisms.RoomList'] = require('./views/organisms/RoomList');
|
||||
skin['organisms.RoomSubList'] = require('./views/organisms/RoomSubList');
|
||||
skin['organisms.RoomView'] = require('./views/organisms/RoomView');
|
||||
skin['organisms.UserSettings'] = require('./views/organisms/UserSettings');
|
||||
skin['organisms.ViewSource'] = require('./views/organisms/ViewSource');
|
||||
|
@ -40,6 +40,25 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// XXX: recalculates default avatar url constantly
|
||||
if (this.state.imageUrl === this.defaultAvatarUrl(this.props.member)) {
|
||||
var initial;
|
||||
if (this.props.member.name[0])
|
||||
initial = this.props.member.name[0].toUpperCase();
|
||||
if (initial === '@' && this.props.member.name[1])
|
||||
initial = this.props.member.name[1].toUpperCase();
|
||||
|
||||
return (
|
||||
<span className="mx_MemberAvatar_wrapper">
|
||||
<span className="mx_MemberAvatar_initial"
|
||||
style={{ fontSize: (this.props.width * 0.75) + "px",
|
||||
width: this.props.width + "px",
|
||||
lineHeight: this.props.height*1.2 + "px" }}>{ initial }</span>
|
||||
<img className="mx_MemberAvatar" src={this.state.imageUrl}
|
||||
onError={this.onError} width={this.props.width} height={this.props.height} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<img className="mx_MemberAvatar" src={this.state.imageUrl}
|
||||
onError={this.onError}
|
||||
|
@ -43,13 +43,33 @@ module.exports = React.createClass({
|
||||
|
||||
render: function() {
|
||||
var style = {
|
||||
maxWidth: this.props.width,
|
||||
maxHeight: this.props.height,
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
};
|
||||
|
||||
// XXX: recalculates fallback avatar constantly
|
||||
if (this.state.imageUrl === this.getFallbackAvatar()) {
|
||||
var initial;
|
||||
if (this.props.room.name[0])
|
||||
initial = this.props.room.name[0].toUpperCase();
|
||||
if ((initial === '@' || initial === '#') && this.props.room.name[1])
|
||||
initial = this.props.room.name[1].toUpperCase();
|
||||
|
||||
return (
|
||||
<img className="mx_RoomAvatar" src={this.state.imageUrl} onError={this.onError}
|
||||
style={style}
|
||||
/>
|
||||
<span>
|
||||
<span className="mx_RoomAvatar_initial"
|
||||
style={{ fontSize: (this.props.width * 0.75) + "px",
|
||||
width: this.props.width + "px",
|
||||
lineHeight: this.props.height*1.2 + "px" }}>{ initial }</span>
|
||||
<img className="mx_RoomAvatar" src={this.state.imageUrl}
|
||||
onError={this.onError} style={style} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return <img className="mx_RoomAvatar" src={this.state.imageUrl}
|
||||
onError={this.onError} style={style} />
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
@ -26,7 +26,7 @@ module.exports = React.createClass({
|
||||
var h = this.props.h || 32;
|
||||
var imgClass = this.props.imgClassName || "";
|
||||
return (
|
||||
<div>
|
||||
<div className="mx_Spinner">
|
||||
<img src="img/spinner.gif" width={w} height={h} className={imgClass}/>
|
||||
</div>
|
||||
);
|
||||
|
@ -27,9 +27,9 @@ module.exports = React.createClass({
|
||||
mixins: [ChangePasswordController],
|
||||
|
||||
onClickChange: function() {
|
||||
var old_password = this.refs.old_input.getDOMNode().value;
|
||||
var new_password = this.refs.new_input.getDOMNode().value;
|
||||
var confirm_password = this.refs.confirm_input.getDOMNode().value;
|
||||
var old_password = this.refs.old_input.value;
|
||||
var new_password = this.refs.new_input.value;
|
||||
var confirm_password = this.refs.confirm_input.value;
|
||||
if (new_password != confirm_password) {
|
||||
this.setState({
|
||||
state: this.Phases.Error,
|
||||
|
@ -28,12 +28,20 @@ module.exports = React.createClass({
|
||||
Notifier.setToolbarHidden(true);
|
||||
},
|
||||
|
||||
onClick: function() {
|
||||
var Notifier = sdk.getComponent('organisms.Notifier');
|
||||
Notifier.setEnabled(true);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var EnableNotificationsButton = sdk.getComponent("atoms.EnableNotificationsButton");
|
||||
return (
|
||||
<div className="mx_MatrixToolbar">
|
||||
You are not receiving desktop notifications. <EnableNotificationsButton />
|
||||
<div className="mx_MatrixToolbar_close"><img src="img/close-white.png" width="16" height="16" onClick={ this.hideToolbar } /></div>
|
||||
<img className="mx_MatrixToolbar_warning" src="img/warning.png" width="28" height="28" alt="/!\"/>
|
||||
<div>
|
||||
You are not receiving desktop notifications. <a className="mx_MatrixToolbar_link" onClick={ this.onClick }>Enable them now</a>
|
||||
</div>
|
||||
<div className="mx_MatrixToolbar_close"><img src="img/cancel-black2.png" width="23" height="23" onClick={ this.hideToolbar } /></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ module.exports = React.createClass({
|
||||
mixins: [MessageComposerController],
|
||||
|
||||
onUploadClick: function(ev) {
|
||||
this.refs.uploadInput.getDOMNode().click();
|
||||
this.refs.uploadInput.click();
|
||||
},
|
||||
|
||||
onUploadFileSelected: function(ev) {
|
||||
@ -38,7 +38,7 @@ module.exports = React.createClass({
|
||||
if (files && files.length > 0) {
|
||||
this.props.uploadFile(files[0]);
|
||||
}
|
||||
this.refs.uploadInput.getDOMNode().value = null;
|
||||
this.refs.uploadInput.value = null;
|
||||
},
|
||||
|
||||
onCallClick: function(ev) {
|
||||
|
@ -22,25 +22,13 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var Modal = require('matrix-react-sdk/lib/Modal');
|
||||
var Resend = require("../../../../Resend");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MessageContextMenu',
|
||||
|
||||
onResendClick: function() {
|
||||
MatrixClientPeg.get().resendEvent(
|
||||
this.props.mxEvent, MatrixClientPeg.get().getRoom(
|
||||
this.props.mxEvent.getRoomId()
|
||||
)
|
||||
).done(function() {
|
||||
dis.dispatch({
|
||||
action: 'message_sent'
|
||||
});
|
||||
}, function() {
|
||||
dis.dispatch({
|
||||
action: 'message_send_failed'
|
||||
});
|
||||
});
|
||||
dis.dispatch({action: 'message_resend_started'});
|
||||
Resend.resend(this.props.mxEvent);
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
|
@ -18,16 +18,25 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
|
||||
//var RoomDropTargetController = require('matrix-react-sdk/lib/controllers/molecules/RoomDropTargetController')
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomDropTarget',
|
||||
// mixins: [RoomDropTargetController],
|
||||
|
||||
render: function() {
|
||||
if (this.props.placeholder) {
|
||||
return (
|
||||
<div className="mx_RoomDropTarget">
|
||||
{this.props.text}
|
||||
<div className="mx_RoomDropTarget mx_RoomDropTarget_placeholder">
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div className="mx_RoomDropTarget">
|
||||
<div className="mx_RoomDropTarget_avatar"></div>
|
||||
<div className="mx_RoomDropTarget_label">
|
||||
{ this.props.label }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -35,7 +35,7 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
getRoomName: function() {
|
||||
return this.refs.name_edit.getDOMNode().value;
|
||||
return this.refs.name_edit.value;
|
||||
},
|
||||
|
||||
onFullscreenClick: function() {
|
||||
|
@ -27,15 +27,15 @@ module.exports = React.createClass({
|
||||
mixins: [RoomSettingsController],
|
||||
|
||||
getTopic: function() {
|
||||
return this.refs.topic.getDOMNode().value;
|
||||
return this.refs.topic.value;
|
||||
},
|
||||
|
||||
getJoinRules: function() {
|
||||
return this.refs.is_private.getDOMNode().checked ? "invite" : "public";
|
||||
return this.refs.is_private.checked ? "invite" : "public";
|
||||
},
|
||||
|
||||
getHistoryVisibility: function() {
|
||||
return this.refs.share_history.getDOMNode().checked ? "shared" : "invited";
|
||||
return this.refs.share_history.checked ? "shared" : "invited";
|
||||
},
|
||||
|
||||
getPowerLevels: function() {
|
||||
@ -45,13 +45,13 @@ module.exports = React.createClass({
|
||||
power_levels = power_levels.getContent();
|
||||
|
||||
var new_power_levels = {
|
||||
ban: parseInt(this.refs.ban.getDOMNode().value),
|
||||
kick: parseInt(this.refs.kick.getDOMNode().value),
|
||||
redact: parseInt(this.refs.redact.getDOMNode().value),
|
||||
invite: parseInt(this.refs.invite.getDOMNode().value),
|
||||
events_default: parseInt(this.refs.events_default.getDOMNode().value),
|
||||
state_default: parseInt(this.refs.state_default.getDOMNode().value),
|
||||
users_default: parseInt(this.refs.users_default.getDOMNode().value),
|
||||
ban: parseInt(this.refs.ban.value),
|
||||
kick: parseInt(this.refs.kick.value),
|
||||
redact: parseInt(this.refs.redact.value),
|
||||
invite: parseInt(this.refs.invite.value),
|
||||
events_default: parseInt(this.refs.events_default.value),
|
||||
state_default: parseInt(this.refs.state_default.value),
|
||||
users_default: parseInt(this.refs.users_default.value),
|
||||
users: power_levels.users,
|
||||
events: power_levels.events,
|
||||
};
|
||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var DragSource = require('react-dnd').DragSource;
|
||||
var DropTarget = require('react-dnd').DropTarget;
|
||||
var classNames = require('classnames');
|
||||
|
||||
var RoomTileController = require('matrix-react-sdk/lib/controllers/molecules/RoomTile')
|
||||
@ -25,10 +27,178 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
|
||||
module.exports = React.createClass({
|
||||
/**
|
||||
* Specifies the drag source contract.
|
||||
* Only `beginDrag` function is required.
|
||||
*/
|
||||
var roomTileSource = {
|
||||
canDrag: function(props, monitor) {
|
||||
return props.roomSubList.props.editable;
|
||||
},
|
||||
|
||||
beginDrag: function (props) {
|
||||
// Return the data describing the dragged item
|
||||
var item = {
|
||||
room: props.room,
|
||||
originalList: props.roomSubList,
|
||||
originalIndex: props.roomSubList.findRoomTile(props.room).index,
|
||||
targetList: props.roomSubList, // at first target is same as original
|
||||
lastTargetRoom: null,
|
||||
lastYOffset: null,
|
||||
lastYDelta: null,
|
||||
};
|
||||
|
||||
if (props.roomSubList.debug) console.log("roomTile beginDrag for " + item.room.roomId);
|
||||
|
||||
// doing this 'correctly' with state causes react-dnd to break seemingly due to the state transitions
|
||||
props.room._dragging = true;
|
||||
|
||||
return item;
|
||||
},
|
||||
|
||||
endDrag: function (props, monitor, component) {
|
||||
var item = monitor.getItem();
|
||||
|
||||
if (props.roomSubList.debug) console.log("roomTile endDrag for " + item.room.roomId + " with didDrop=" + monitor.didDrop());
|
||||
|
||||
props.room._dragging = false;
|
||||
if (monitor.didDrop()) {
|
||||
if (props.roomSubList.debug) console.log("force updating component " + item.targetList.props.label);
|
||||
item.targetList.forceUpdate(); // as we're not using state
|
||||
}
|
||||
|
||||
if (monitor.didDrop() && item.targetList.props.editable) {
|
||||
// if we moved lists, remove the old tag
|
||||
if (item.targetList !== item.originalList) {
|
||||
// commented out attempts to set a spinner on our target component as component is actually
|
||||
// the original source component being dragged, not our target. To fix we just need to
|
||||
// move all of this to endDrop in the target instead. FIXME later.
|
||||
|
||||
//component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 });
|
||||
MatrixClientPeg.get().deleteRoomTag(item.room.roomId, item.originalList.props.tagName).finally(function() {
|
||||
//component.state.set({ spinner: component.state.spinner-- });
|
||||
}).fail(function(err) {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to remove tag " + item.originalList.props.tagName + " from room",
|
||||
description: err.toString()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var newOrder= {};
|
||||
if (item.targetList.props.order === 'manual') {
|
||||
newOrder['order'] = item.targetList.calcManualOrderTagData(item.room);
|
||||
}
|
||||
|
||||
// if we moved lists or the ordering changed, add the new tag
|
||||
if (item.targetList.props.tagName && (item.targetList !== item.originalList || newOrder)) {
|
||||
//component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 });
|
||||
MatrixClientPeg.get().setRoomTag(item.room.roomId, item.targetList.props.tagName, newOrder).finally(function() {
|
||||
//component.state.set({ spinner: component.state.spinner-- });
|
||||
}).fail(function(err) {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to add tag " + item.targetList.props.tagName + " to room",
|
||||
description: err.toString()
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
// cancel the drop and reset our original position
|
||||
if (props.roomSubList.debug) console.log("cancelling drop & drag");
|
||||
props.roomSubList.moveRoomTile(item.room, item.originalIndex);
|
||||
if (item.targetList && item.targetList !== item.originalList) {
|
||||
item.targetList.removeRoomTile(item.room);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var roomTileTarget = {
|
||||
canDrop: function() {
|
||||
return false;
|
||||
},
|
||||
|
||||
hover: function(props, monitor) {
|
||||
var item = monitor.getItem();
|
||||
var off = monitor.getClientOffset();
|
||||
// console.log("hovering on room " + props.room.roomId + ", isOver=" + monitor.isOver());
|
||||
|
||||
//console.log("item.targetList=" + item.targetList + ", roomSubList=" + props.roomSubList);
|
||||
|
||||
var switchedTarget = false;
|
||||
if (item.targetList !== props.roomSubList) {
|
||||
// we've switched target, so remove the tile from the previous target.
|
||||
// n.b. the previous target might actually be the source list.
|
||||
if (props.roomSubList.debug) console.log("switched target sublist");
|
||||
switchedTarget = true;
|
||||
item.targetList.removeRoomTile(item.room);
|
||||
item.targetList = props.roomSubList;
|
||||
}
|
||||
|
||||
if (!item.targetList.props.editable) return;
|
||||
|
||||
if (item.targetList.props.order === 'manual') {
|
||||
if (item.room.roomId !== props.room.roomId && props.room !== item.lastTargetRoom) {
|
||||
// find the offset of the target tile in the list.
|
||||
var roomTile = props.roomSubList.findRoomTile(props.room);
|
||||
// shuffle the list to add our tile to that position.
|
||||
props.roomSubList.moveRoomTile(item.room, roomTile.index);
|
||||
}
|
||||
|
||||
// stop us from flickering between our droptarget and the previous room.
|
||||
// whenever the cursor changes direction we have to reset the flicker-damping.
|
||||
|
||||
var yDelta = off.y - item.lastYOffset;
|
||||
|
||||
if ((yDelta > 0 && item.lastYDelta < 0) ||
|
||||
(yDelta < 0 && item.lastYDelta > 0))
|
||||
{
|
||||
// the cursor changed direction - forget our previous room
|
||||
item.lastTargetRoom = null;
|
||||
}
|
||||
else {
|
||||
// track the last room we were hovering over so we can stop
|
||||
// bouncing back and forth if the droptarget is narrower than
|
||||
// the other list items. The other way to do this would be
|
||||
// to reduce the size of the hittarget on the list items, but
|
||||
// can't see an easy way to do that.
|
||||
item.lastTargetRoom = props.room;
|
||||
}
|
||||
|
||||
if (yDelta) item.lastYDelta = yDelta;
|
||||
item.lastYOffset = off.y;
|
||||
}
|
||||
else if (switchedTarget) {
|
||||
if (!props.roomSubList.findRoomTile(item.room).room) {
|
||||
// add to the list in the right place
|
||||
props.roomSubList.moveRoomTile(item.room, 0);
|
||||
}
|
||||
// we have to sort the list whatever to recalculate it
|
||||
props.roomSubList.sortList();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var RoomTile = React.createClass({
|
||||
displayName: 'RoomTile',
|
||||
mixins: [RoomTileController],
|
||||
|
||||
propTypes: {
|
||||
connectDragSource: React.PropTypes.func.isRequired,
|
||||
connectDropTarget: React.PropTypes.func.isRequired,
|
||||
isDragging: React.PropTypes.bool.isRequired,
|
||||
room: React.PropTypes.object.isRequired,
|
||||
collapsed: React.PropTypes.bool.isRequired,
|
||||
selected: React.PropTypes.bool.isRequired,
|
||||
unread: React.PropTypes.bool.isRequired,
|
||||
highlight: React.PropTypes.bool.isRequired,
|
||||
isInvite: React.PropTypes.bool.isRequired,
|
||||
roomSubList: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return( { hover : false });
|
||||
},
|
||||
@ -42,21 +212,32 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// if (this.props.clientOffset) {
|
||||
// //console.log("room " + this.props.room.roomId + " has dropTarget clientOffset " + this.props.clientOffset.x + "," + this.props.clientOffset.y);
|
||||
// }
|
||||
|
||||
if (this.props.room._dragging) {
|
||||
var RoomDropTarget = sdk.getComponent("molecules.RoomDropTarget");
|
||||
return <RoomDropTarget placeholder={true}/>;
|
||||
}
|
||||
|
||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
var me = this.props.room.currentState.members[myUserId];
|
||||
var classes = classNames({
|
||||
'mx_RoomTile': true,
|
||||
'mx_RoomTile_selected': this.props.selected,
|
||||
'mx_RoomTile_unread': this.props.unread,
|
||||
'mx_RoomTile_highlight': this.props.highlight,
|
||||
'mx_RoomTile_invited': this.props.room.currentState.members[myUserId].membership == 'invite'
|
||||
'mx_RoomTile_invited': (me && me.membership == 'invite'),
|
||||
});
|
||||
|
||||
var name;
|
||||
if (this.props.isInvite) {
|
||||
name = this.props.room.getMember(MatrixClientPeg.get().credentials.userId).events.member.getSender();
|
||||
name = this.props.room.getMember(myUserId).events.member.getSender();
|
||||
}
|
||||
else {
|
||||
name = this.props.room.name;
|
||||
// XXX: We should never display raw room IDs, but sometimes the room name js sdk gives is undefined
|
||||
name = this.props.room.name || this.props.room.roomId;
|
||||
}
|
||||
|
||||
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
||||
@ -91,7 +272,14 @@ module.exports = React.createClass({
|
||||
}
|
||||
|
||||
var RoomAvatar = sdk.getComponent('atoms.RoomAvatar');
|
||||
return (
|
||||
|
||||
// These props are injected by React DnD,
|
||||
// as defined by your `collect` function above:
|
||||
var isDragging = this.props.isDragging;
|
||||
var connectDragSource = this.props.connectDragSource;
|
||||
var connectDropTarget = this.props.connectDropTarget;
|
||||
|
||||
return connectDragSource(connectDropTarget(
|
||||
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<div className="mx_RoomTile_avatar">
|
||||
<RoomAvatar room={this.props.room} width="24" height="24" />
|
||||
@ -99,6 +287,27 @@ module.exports = React.createClass({
|
||||
</div>
|
||||
{ label }
|
||||
</div>
|
||||
);
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
// Export the wrapped version, inlining the 'collect' functions
|
||||
// to more closely resemble the ES7
|
||||
module.exports =
|
||||
DropTarget('RoomTile', roomTileTarget, function(connect, monitor) {
|
||||
return {
|
||||
// Call this function inside render()
|
||||
// to let React DnD handle the drag events:
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver(),
|
||||
}
|
||||
})(
|
||||
DragSource('RoomTile', roomTileSource, function(connect, monitor) {
|
||||
return {
|
||||
// Call this function inside render()
|
||||
// to let React DnD handle the drag events:
|
||||
connectDragSource: connect.dragSource(),
|
||||
// You can ask the monitor about the current drag state:
|
||||
isDragging: monitor.isDragging()
|
||||
};
|
||||
})(RoomTile));
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
@ -24,21 +25,21 @@ module.exports = React.createClass({
|
||||
displayName: 'RoomTooltip',
|
||||
|
||||
componentDidMount: function() {
|
||||
var tooltip = ReactDOM.findDOMNode(this);
|
||||
if (!this.props.bottom) {
|
||||
// tell the roomlist about us so it can position us
|
||||
dis.dispatch({
|
||||
action: 'view_tooltip',
|
||||
tooltip: this.getDOMNode(),
|
||||
tooltip: tooltip,
|
||||
});
|
||||
}
|
||||
else {
|
||||
var tooltip = this.getDOMNode();
|
||||
tooltip.style.top = tooltip.parentElement.getBoundingClientRect().top + "px";
|
||||
tooltip.style.display = "block";
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUnmount: function() {
|
||||
componentWillUnmount: function() {
|
||||
if (!this.props.bottom) {
|
||||
dis.dispatch({
|
||||
action: 'view_tooltip',
|
||||
|
@ -39,7 +39,7 @@ module.exports = React.createClass({
|
||||
|
||||
onSearchChange: function(e) {
|
||||
if (e.keyCode === 13) { // on enter...
|
||||
this.props.onSearch(this.refs.search_term.getDOMNode().value, this.state.scope);
|
||||
this.props.onSearch(this.refs.search_term.value, this.state.scope);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -25,8 +25,8 @@ module.exports = React.createClass({
|
||||
mixins: [UserSelectorController],
|
||||
|
||||
onAddUserId: function() {
|
||||
this.addUser(this.refs.user_id_input.getDOMNode().value);
|
||||
this.refs.user_id_input.getDOMNode().value = "";
|
||||
this.addUser(this.refs.user_id_input.value);
|
||||
this.refs.user_id_input.value = "";
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -34,7 +34,7 @@ module.exports = React.createClass({
|
||||
render: function(){
|
||||
var VideoView = sdk.getComponent('molecules.voip.VideoView');
|
||||
return (
|
||||
<VideoView ref="video"/>
|
||||
<VideoView ref="video" onClick={ this.props.onClick }/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -27,7 +27,7 @@ module.exports = React.createClass({
|
||||
mixins: [IncomingCallBoxController],
|
||||
|
||||
getRingAudio: function() {
|
||||
return this.refs.ringAudio.getDOMNode();
|
||||
return this.refs.ringAudio;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher')
|
||||
@ -29,15 +30,15 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
getRemoteVideoElement: function() {
|
||||
return this.refs.remote.getDOMNode();
|
||||
return ReactDOM.findDOMNode(this.refs.remote);
|
||||
},
|
||||
|
||||
getRemoteAudioElement: function() {
|
||||
return this.refs.remoteAudio.getDOMNode();
|
||||
return this.refs.remoteAudio;
|
||||
},
|
||||
|
||||
getLocalVideoElement: function() {
|
||||
return this.refs.local.getDOMNode();
|
||||
return ReactDOM.findDOMNode(this.refs.local);
|
||||
},
|
||||
|
||||
setContainer: function(c) {
|
||||
@ -50,7 +51,7 @@ module.exports = React.createClass({
|
||||
if (!this.container) {
|
||||
return;
|
||||
}
|
||||
var element = this.container.getDOMNode();
|
||||
var element = this.container;
|
||||
if (payload.fullscreen) {
|
||||
var requestMethod = (
|
||||
element.requestFullScreen ||
|
||||
@ -78,7 +79,7 @@ module.exports = React.createClass({
|
||||
render: function() {
|
||||
var VideoFeed = sdk.getComponent('atoms.voip.VideoFeed');
|
||||
return (
|
||||
<div className="mx_VideoView" ref={this.setContainer}>
|
||||
<div className="mx_VideoView" ref={this.setContainer} onClick={ this.props.onClick }>
|
||||
<div className="mx_VideoView_remoteVideoFeed">
|
||||
<VideoFeed ref="remote"/>
|
||||
<audio ref="remoteAudio"/>
|
||||
|
@ -17,18 +17,72 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var DragDropContext = require('react-dnd').DragDropContext;
|
||||
var HTML5Backend = require('react-dnd-html5-backend');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
module.exports = React.createClass({
|
||||
var CallHandler = require("matrix-react-sdk/lib/CallHandler");
|
||||
|
||||
var LeftPanel = React.createClass({
|
||||
displayName: 'LeftPanel',
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showCallElement: null,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
this._recheckCallElement(newProps.selectedRoom);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
switch (payload.action) {
|
||||
// listen for call state changes to prod the render method, which
|
||||
// may hide the global CallView if the call it is tracking is dead
|
||||
case 'call_state':
|
||||
this._recheckCallElement(this.props.selectedRoom);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_recheckCallElement: function(selectedRoomId) {
|
||||
// if we aren't viewing a room with an ongoing call, but there is an
|
||||
// active call, show the call element - we need to do this to make
|
||||
// audio/video not crap out
|
||||
var activeCall = CallHandler.getAnyActiveCall();
|
||||
var callForRoom = CallHandler.getCallForRoom(selectedRoomId);
|
||||
var showCall = (activeCall && !callForRoom);
|
||||
this.setState({
|
||||
showCallElement: showCall
|
||||
});
|
||||
},
|
||||
|
||||
onHideClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'hide_left_panel',
|
||||
});
|
||||
},
|
||||
|
||||
onCallViewClick: function() {
|
||||
var call = CallHandler.getAnyActiveCall();
|
||||
if (call) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: call.roomId,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var RoomList = sdk.getComponent('organisms.RoomList');
|
||||
var BottomLeftMenu = sdk.getComponent('molecules.BottomLeftMenu');
|
||||
@ -44,10 +98,17 @@ module.exports = React.createClass({
|
||||
// collapseButton = <img className="mx_LeftPanel_hideButton" onClick={ this.onHideClick } src="img/hide.png" width="12" height="20" alt="<"/>
|
||||
}
|
||||
|
||||
var callPreview;
|
||||
if (this.state.showCallElement) {
|
||||
var CallView = sdk.getComponent('molecules.voip.CallView');
|
||||
callPreview = <CallView className="mx_LeftPanel_callView" onClick={this.onCallViewClick} />
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className={classes}>
|
||||
{ collapseButton }
|
||||
<IncomingCallBox />
|
||||
{ callPreview }
|
||||
<RoomList selectedRoom={this.props.selectedRoom} collapsed={this.props.collapsed}/>
|
||||
<BottomLeftMenu collapsed={this.props.collapsed}/>
|
||||
</aside>
|
||||
@ -55,3 +116,4 @@ module.exports = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = DragDropContext(HTML5Backend)(LeftPanel);
|
||||
|
@ -21,6 +21,7 @@ var classNames = require('classnames');
|
||||
var Loader = require('react-loader');
|
||||
|
||||
var MemberListController = require('matrix-react-sdk/lib/controllers/organisms/MemberList')
|
||||
var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
|
||||
@ -71,7 +72,7 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
onPopulateInvite: function(e) {
|
||||
this.onInvite(this.refs.invite.getDOMNode().value);
|
||||
this.onInvite(this.refs.invite.value);
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
@ -104,7 +105,7 @@ module.exports = React.createClass({
|
||||
}
|
||||
return (
|
||||
<div className="mx_MemberList">
|
||||
<div className="mx_MemberList_border">
|
||||
<GeminiScrollbar autoshow={true} className="mx_MemberList_border">
|
||||
{this.inviteTile()}
|
||||
<div>
|
||||
<div className="mx_MemberList_wrapper">
|
||||
@ -112,7 +113,7 @@ module.exports = React.createClass({
|
||||
</div>
|
||||
</div>
|
||||
{invitedSection}
|
||||
</div>
|
||||
</GeminiScrollbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -110,9 +110,9 @@ module.exports = React.createClass({
|
||||
|
||||
onKeyUp: function(ev) {
|
||||
this.forceUpdate();
|
||||
this.setState({ roomAlias : this.refs.roomAlias.getDOMNode().value })
|
||||
this.setState({ roomAlias : this.refs.roomAlias.value })
|
||||
if (ev.key == "Enter") {
|
||||
this.joinRoom(this.refs.roomAlias.getDOMNode().value);
|
||||
this.joinRoom(this.refs.roomAlias.value);
|
||||
}
|
||||
if (ev.key == "Down") {
|
||||
|
||||
|
@ -20,6 +20,7 @@ var React = require('react');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||
var RoomListController = require('../../../../controllers/organisms/RoomList')
|
||||
|
||||
module.exports = React.createClass({
|
||||
@ -33,48 +34,82 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var CallView = sdk.getComponent('molecules.voip.CallView');
|
||||
var RoomDropTarget = sdk.getComponent('molecules.RoomDropTarget');
|
||||
|
||||
var callElement;
|
||||
if (this.state.show_call_element) {
|
||||
callElement = <CallView className="mx_MatrixChat_callView"/>
|
||||
}
|
||||
|
||||
var expandButton = this.props.collapsed ?
|
||||
<img className="mx_RoomList_expandButton" onClick={ this.onShowClick } src="img/menu.png" width="20" alt=">"/> :
|
||||
null;
|
||||
|
||||
var invitesLabel = this.props.collapsed ? null : "Invites";
|
||||
var recentsLabel = this.props.collapsed ? null : "Recent";
|
||||
|
||||
var invites;
|
||||
if (this.state.inviteList.length) {
|
||||
invites = <div>
|
||||
<h2 className="mx_RoomList_invitesLabel">{ invitesLabel }</h2>
|
||||
<div className="mx_RoomList_invites">
|
||||
{this.makeRoomTiles(this.state.inviteList, true)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
var RoomSubList = sdk.getComponent('organisms.RoomSubList');
|
||||
var self = this;
|
||||
|
||||
return (
|
||||
<div className="mx_RoomList" onScroll={this._repositionTooltip}>
|
||||
<GeminiScrollbar className="mx_RoomList_scrollbar" autoshow={true} onScroll={self._repositionTooltip}>
|
||||
<div className="mx_RoomList">
|
||||
{ expandButton }
|
||||
{ callElement }
|
||||
<h2 className="mx_RoomList_favouritesLabel">Favourites</h2>
|
||||
<RoomDropTarget text="Drop here to favourite"/>
|
||||
|
||||
{ invites }
|
||||
<RoomSubList list={ self.state.lists['m.invite'] }
|
||||
label="Invites"
|
||||
editable={ false }
|
||||
order="recent"
|
||||
activityMap={ self.state.activityMap }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
collapsed={ self.props.collapsed } />
|
||||
|
||||
<h2 className="mx_RoomList_recentsLabel">{ recentsLabel }</h2>
|
||||
<div className="mx_RoomList_recents">
|
||||
{this.makeRoomTiles(this.state.roomList, false)}
|
||||
</div>
|
||||
|
||||
<h2 className="mx_RoomList_archiveLabel">Archive</h2>
|
||||
<RoomDropTarget text="Drop here to archive"/>
|
||||
<RoomSubList list={ self.state.lists['m.favourite'] }
|
||||
label="Favourites"
|
||||
tagName="m.favourite"
|
||||
verb="favourite"
|
||||
editable={ true }
|
||||
order="manual"
|
||||
activityMap={ self.state.activityMap }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
collapsed={ self.props.collapsed } />
|
||||
|
||||
<RoomSubList list={ self.state.lists['m.recent'] }
|
||||
label="Conversations"
|
||||
editable={ true }
|
||||
verb="restore"
|
||||
order="recent"
|
||||
activityMap={ self.state.activityMap }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
collapsed={ self.props.collapsed } />
|
||||
|
||||
{ Object.keys(self.state.lists).map(function(tagName) {
|
||||
if (!tagName.match(/^m\.(invite|favourite|recent|lowpriority|archived)$/)) {
|
||||
return <RoomSubList list={ self.state.lists[tagName] }
|
||||
key={ tagName }
|
||||
label={ tagName }
|
||||
tagName={ tagName }
|
||||
verb={ "tag as " + tagName }
|
||||
editable={ true }
|
||||
order="manual"
|
||||
activityMap={ self.state.activityMap }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
collapsed={ self.props.collapsed } />
|
||||
|
||||
}
|
||||
}) }
|
||||
|
||||
<RoomSubList list={ self.state.lists['m.lowpriority'] }
|
||||
label="Low priority"
|
||||
tagName="m.lowpriority"
|
||||
verb="demote"
|
||||
editable={ true }
|
||||
order="recent"
|
||||
bottommost={ self.state.lists['m.archived'].length === 0 }
|
||||
activityMap={ self.state.activityMap }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
collapsed={ self.props.collapsed } />
|
||||
|
||||
<RoomSubList list={ self.state.lists['m.archived'] }
|
||||
label="Historical"
|
||||
editable={ false }
|
||||
order="recent"
|
||||
bottommost={ true }
|
||||
activityMap={ self.state.activityMap }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
collapsed={ self.props.collapsed } />
|
||||
</div>
|
||||
</GeminiScrollbar>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
290
src/skins/vector/views/organisms/RoomSubList.js
Normal file
290
src/skins/vector/views/organisms/RoomSubList.js
Normal file
@ -0,0 +1,290 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var DropTarget = require('react-dnd').DropTarget;
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
// turn this on for drop & drag console debugging galore
|
||||
var debug = false;
|
||||
|
||||
var roomListTarget = {
|
||||
canDrop: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
drop: function(props, monitor, component) {
|
||||
if (debug) console.log("dropped on sublist")
|
||||
},
|
||||
|
||||
hover: function(props, monitor, component) {
|
||||
var item = monitor.getItem();
|
||||
|
||||
if (component.state.sortedList.length == 0 && props.editable) {
|
||||
if (debug) console.log("hovering on sublist " + props.label + ", isOver=" + monitor.isOver());
|
||||
|
||||
if (item.targetList !== component) {
|
||||
item.targetList.removeRoomTile(item.room);
|
||||
item.targetList = component;
|
||||
}
|
||||
|
||||
component.moveRoomTile(item.room, 0);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var RoomSubList = React.createClass({
|
||||
displayName: 'RoomSubList',
|
||||
|
||||
debug: debug,
|
||||
|
||||
propTypes: {
|
||||
list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
|
||||
label: React.PropTypes.string.isRequired,
|
||||
tagName: React.PropTypes.string,
|
||||
editable: React.PropTypes.bool,
|
||||
order: React.PropTypes.string.isRequired,
|
||||
bottommost: React.PropTypes.bool,
|
||||
selectedRoom: React.PropTypes.string.isRequired,
|
||||
activityMap: React.PropTypes.object.isRequired,
|
||||
collapsed: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
hidden: false,
|
||||
sortedList: [],
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.sortList(this.props.list, this.props.order);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
// order the room list appropriately before we re-render
|
||||
//if (debug) console.log("received new props, list = " + newProps.list);
|
||||
this.sortList(newProps.list, newProps.order);
|
||||
},
|
||||
|
||||
onClick: function(ev) {
|
||||
this.setState({ hidden : !this.state.hidden });
|
||||
},
|
||||
|
||||
tsOfNewestEvent: function(room) {
|
||||
if (room.timeline.length) {
|
||||
return room.timeline[room.timeline.length - 1].getTs();
|
||||
}
|
||||
else {
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: factor the comparators back out into a generic comparator
|
||||
// so that view_prev_room and view_next_room can do the right thing
|
||||
|
||||
recentsComparator: function(roomA, roomB) {
|
||||
return this.tsOfNewestEvent(roomB) - this.tsOfNewestEvent(roomA);
|
||||
},
|
||||
|
||||
manualComparator: function(roomA, roomB) {
|
||||
if (!roomA.tags[this.props.tagName] || !roomB.tags[this.props.tagName]) return 0;
|
||||
var a = roomA.tags[this.props.tagName].order;
|
||||
var b = roomB.tags[this.props.tagName].order;
|
||||
return a == b ? this.recentsComparator(roomA, roomB) : ( a > b ? 1 : -1);
|
||||
},
|
||||
|
||||
sortList: function(list, order) {
|
||||
if (list === undefined) list = this.state.sortedList;
|
||||
if (order === undefined) order = this.props.order;
|
||||
var comparator;
|
||||
list = list || [];
|
||||
if (order === "manual") comparator = this.manualComparator;
|
||||
if (order === "recent") comparator = this.recentsComparator;
|
||||
|
||||
//if (debug) console.log("sorting list for sublist " + this.props.label + " with length " + list.length + ", this.props.list = " + this.props.list);
|
||||
this.setState({ sortedList: list.sort(comparator) });
|
||||
},
|
||||
|
||||
moveRoomTile: function(room, atIndex) {
|
||||
if (debug) console.log("moveRoomTile: id " + room.roomId + ", atIndex " + atIndex);
|
||||
//console.log("moveRoomTile before: " + JSON.stringify(this.state.rooms));
|
||||
var found = this.findRoomTile(room);
|
||||
var rooms = this.state.sortedList;
|
||||
if (found.room) {
|
||||
if (debug) console.log("removing at index " + found.index + " and adding at index " + atIndex);
|
||||
rooms.splice(found.index, 1);
|
||||
rooms.splice(atIndex, 0, found.room);
|
||||
}
|
||||
else {
|
||||
if (debug) console.log("Adding at index " + atIndex);
|
||||
rooms.splice(atIndex, 0, room);
|
||||
}
|
||||
this.setState({ sortedList: rooms });
|
||||
// console.log("moveRoomTile after: " + JSON.stringify(this.state.rooms));
|
||||
},
|
||||
|
||||
// XXX: this isn't invoked via a property method but indirectly via
|
||||
// the roomList property method. Unsure how evil this is.
|
||||
removeRoomTile: function(room) {
|
||||
if (debug) console.log("remove room " + room.roomId);
|
||||
var found = this.findRoomTile(room);
|
||||
var rooms = this.state.sortedList;
|
||||
if (found.room) {
|
||||
rooms.splice(found.index, 1);
|
||||
}
|
||||
else {
|
||||
console.warn("Can't remove room " + room.roomId + " - can't find it");
|
||||
}
|
||||
this.setState({ sortedList: rooms });
|
||||
},
|
||||
|
||||
findRoomTile: function(room) {
|
||||
var index = this.state.sortedList.indexOf(room);
|
||||
if (index >= 0) {
|
||||
// console.log("found: room: " + room.roomId + " with index " + index);
|
||||
}
|
||||
else {
|
||||
if (debug) console.log("didn't find room");
|
||||
room = null;
|
||||
}
|
||||
return ({
|
||||
room: room,
|
||||
index: index,
|
||||
});
|
||||
},
|
||||
|
||||
calcManualOrderTagData: function(room) {
|
||||
var index = this.state.sortedList.indexOf(room);
|
||||
|
||||
// we sort rooms by the lexicographic ordering of the 'order' metadata on their tags.
|
||||
// for convenience, we calculate this for now a floating point number between 0.0 and 1.0.
|
||||
|
||||
var orderA = 0.0; // by default we're next to the beginning of the list
|
||||
if (index > 0) {
|
||||
var prevTag = this.state.sortedList[index - 1].tags[this.props.tagName];
|
||||
if (!prevTag) {
|
||||
console.error("Previous room in sublist is not tagged to be in this list. This should never happen.")
|
||||
}
|
||||
else if (prevTag.order === undefined) {
|
||||
console.error("Previous room in sublist has no ordering metadata. This should never happen.");
|
||||
}
|
||||
else {
|
||||
orderA = prevTag.order;
|
||||
}
|
||||
}
|
||||
|
||||
var orderB = 1.0; // by default we're next to the end of the list too
|
||||
if (index < this.state.sortedList.length - 1) {
|
||||
var nextTag = this.state.sortedList[index + 1].tags[this.props.tagName];
|
||||
if (!nextTag) {
|
||||
console.error("Next room in sublist is not tagged to be in this list. This should never happen.")
|
||||
}
|
||||
else if (nextTag.order === undefined) {
|
||||
console.error("Next room in sublist has no ordering metadata. This should never happen.");
|
||||
}
|
||||
else {
|
||||
orderB = nextTag.order;
|
||||
}
|
||||
}
|
||||
|
||||
var order = (orderA + orderB) / 2.0;
|
||||
if (order === orderA || order === orderB) {
|
||||
console.error("Cannot describe new list position. This should be incredibly unlikely.");
|
||||
// TODO: renumber the list
|
||||
}
|
||||
|
||||
return order;
|
||||
},
|
||||
|
||||
makeRoomTiles: function() {
|
||||
var self = this;
|
||||
var RoomTile = sdk.getComponent("molecules.RoomTile");
|
||||
return this.state.sortedList.map(function(room) {
|
||||
var selected = room.roomId == self.props.selectedRoom;
|
||||
// XXX: is it evil to pass in self as a prop to RoomTile?
|
||||
return (
|
||||
<RoomTile
|
||||
room={ room }
|
||||
roomSubList={ self }
|
||||
key={ room.roomId }
|
||||
collapsed={ self.props.collapsed }
|
||||
selected={ selected }
|
||||
unread={ self.props.activityMap[room.roomId] === 1 }
|
||||
highlight={ self.props.activityMap[room.roomId] === 2 }
|
||||
isInvite={ self.props.label === 'Invites' } />
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var connectDropTarget = this.props.connectDropTarget;
|
||||
var RoomDropTarget = sdk.getComponent('molecules.RoomDropTarget');
|
||||
|
||||
var label = this.props.collapsed ? null : this.props.label;
|
||||
|
||||
//console.log("render: " + JSON.stringify(this.state.sortedList));
|
||||
|
||||
var target;
|
||||
if (this.state.sortedList.length == 0 && this.props.editable) {
|
||||
target = <RoomDropTarget label={ 'Drop here to ' + this.props.verb }/>;
|
||||
}
|
||||
|
||||
if (this.state.sortedList.length > 0 || this.props.editable) {
|
||||
var subList;
|
||||
var classes = "mx_RoomSubList" +
|
||||
(this.props.bottommost ? " mx_RoomSubList_bottommost" : "");
|
||||
|
||||
if (!this.state.hidden) {
|
||||
subList = <div className={ classes }>
|
||||
{ target }
|
||||
{ this.makeRoomTiles() }
|
||||
</div>;
|
||||
}
|
||||
else {
|
||||
subList = <div className={ classes }>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return connectDropTarget(
|
||||
<div>
|
||||
<h2 onClick={ this.onClick } className="mx_RoomSubList_label">{ this.props.collapsed ? '' : this.props.label }
|
||||
<img className="mx_RoomSubList_chevron" src={ this.state.hidden ? "/img/list-open.png" : "/img/list-close.png" } width="10" height="10"/>
|
||||
</h2>
|
||||
{ subList }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div className="mx_RoomSubList">
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Export the wrapped version, inlining the 'collect' functions
|
||||
// to more closely resemble the ES7
|
||||
module.exports =
|
||||
DropTarget('RoomTile', roomListTarget, function(connect) {
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
}
|
||||
})(RoomSubList);
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
@ -25,6 +26,7 @@ var sdk = require('matrix-react-sdk')
|
||||
var classNames = require("classnames");
|
||||
var filesize = require('filesize');
|
||||
|
||||
var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||
var RoomViewController = require('../../../../controllers/organisms/RoomView')
|
||||
|
||||
var Loader = require("react-loader");
|
||||
@ -103,7 +105,7 @@ module.exports = React.createClass({
|
||||
|
||||
scrollToBottom: function() {
|
||||
if (!this.refs.messageWrapper) return;
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
var messageWrapper = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
|
||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||
},
|
||||
|
||||
@ -196,10 +198,48 @@ module.exports = React.createClass({
|
||||
);
|
||||
} else {
|
||||
var typingString = this.getWhoIsTypingString();
|
||||
//typingString = "Testing typing...";
|
||||
var unreadMsgs = this.getUnreadMessagesString();
|
||||
// no conn bar trumps unread count since you can't get unread messages
|
||||
// without a connection! (technically may already have some but meh)
|
||||
// It also trumps the "some not sent" msg since you can't resend without
|
||||
// a connection!
|
||||
if (this.state.syncState === "ERROR") {
|
||||
statusBar = (
|
||||
<div className="mx_RoomView_connectionLostBar">
|
||||
<img src="img/warning2.png" width="30" height="30" alt="/!\"/>
|
||||
<div className="mx_RoomView_connectionLostBar_textArea">
|
||||
<div className="mx_RoomView_connectionLostBar_title">
|
||||
Connectivity to the server has been lost.
|
||||
</div>
|
||||
<div className="mx_RoomView_connectionLostBar_desc">
|
||||
Sent messages will be stored until your connection has returned.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else if (this.state.hasUnsentMessages) {
|
||||
statusBar = (
|
||||
<div className="mx_RoomView_connectionLostBar">
|
||||
<img src="img/warning2.png" width="30" height="30" alt="/!\"/>
|
||||
<div className="mx_RoomView_connectionLostBar_textArea">
|
||||
<div className="mx_RoomView_connectionLostBar_title">
|
||||
Some of your messages have not been sent.
|
||||
</div>
|
||||
<div className="mx_RoomView_connectionLostBar_desc">
|
||||
<a className="mx_RoomView_resend_link"
|
||||
onClick={ this.onResendAllClick }>
|
||||
Resend all now
|
||||
</a> or select individual messages to re-send.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// unread count trumps who is typing since the unread count is only
|
||||
// set when you've scrolled up
|
||||
if (unreadMsgs) {
|
||||
else if (unreadMsgs) {
|
||||
statusBar = (
|
||||
<div className="mx_RoomView_unreadMessagesBar" onClick={ this.scrollToBottom }>
|
||||
<img src="img/newmessages.png" width="24" height="24" alt=""/>
|
||||
@ -230,9 +270,13 @@ module.exports = React.createClass({
|
||||
|
||||
var conferenceCallNotification = null;
|
||||
if (this.state.displayConfCallNotification) {
|
||||
var supportedText;
|
||||
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||
supportedText = " (unsupported)";
|
||||
}
|
||||
conferenceCallNotification = (
|
||||
<div className="mx_RoomView_ongoingConfCallNotification" onClick={this.onConferenceNotificationClick}>
|
||||
Ongoing conference call
|
||||
Ongoing conference call {supportedText}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -256,7 +300,7 @@ module.exports = React.createClass({
|
||||
{ conferenceCallNotification }
|
||||
{ aux }
|
||||
</div>
|
||||
<div ref="messageWrapper" className="mx_RoomView_messagePanel" onScroll={ this.onMessageListScroll }>
|
||||
<GeminiScrollbar autoshow={true} ref="messageWrapper" className="mx_RoomView_messagePanel" onScroll={ this.onMessageListScroll }>
|
||||
<div className="mx_RoomView_messageListWrapper">
|
||||
{ fileDropTarget }
|
||||
<ol className="mx_RoomView_MessageList" aria-live="polite">
|
||||
@ -265,7 +309,7 @@ module.exports = React.createClass({
|
||||
{this.getEventTiles()}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</GeminiScrollbar>
|
||||
<div className="mx_RoomView_statusArea">
|
||||
<div className="mx_RoomView_statusAreaBox">
|
||||
<div className="mx_RoomView_statusAreaBox_line"></div>
|
||||
|
@ -21,9 +21,6 @@ var sdk = require('matrix-react-sdk')
|
||||
|
||||
var MatrixChatController = require('matrix-react-sdk/lib/controllers/pages/MatrixChat')
|
||||
|
||||
// should be atomised
|
||||
var Loader = require("react-loader");
|
||||
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
var ContextualMenu = require("../../../../ContextualMenu");
|
||||
@ -154,8 +151,9 @@ module.exports = React.createClass({
|
||||
);
|
||||
}
|
||||
} else if (this.state.logged_in) {
|
||||
var Spinner = sdk.getComponent('atoms.Spinner');
|
||||
return (
|
||||
<Loader />
|
||||
<Spinner />
|
||||
);
|
||||
} else if (this.state.screen == 'register') {
|
||||
return (
|
||||
|
@ -70,8 +70,8 @@ module.exports = React.createClass({
|
||||
*/
|
||||
getFormVals: function() {
|
||||
return {
|
||||
'username': this.refs.user.getDOMNode().value.trim(),
|
||||
'password': this.refs.pass.getDOMNode().value.trim()
|
||||
'username': this.refs.user.value.trim(),
|
||||
'password': this.refs.pass.value.trim()
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -44,10 +44,10 @@ module.exports = React.createClass({
|
||||
|
||||
getRegFormVals: function() {
|
||||
return {
|
||||
email: this.refs.email.getDOMNode().value.trim(),
|
||||
username: this.refs.username.getDOMNode().value.trim(),
|
||||
password: this.refs.password.getDOMNode().value.trim(),
|
||||
confirmPassword: this.refs.confirmPassword.getDOMNode().value.trim()
|
||||
email: this.refs.email.value.trim(),
|
||||
username: this.refs.username.value.trim(),
|
||||
password: this.refs.password.value.trim(),
|
||||
confirmPassword: this.refs.confirmPassword.value.trim()
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||
|
||||
var RunModernizrTests = require("./modernizr"); // this side-effects a global
|
||||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
var sdk = require("matrix-react-sdk");
|
||||
sdk.loadSkin(require('../skins/vector/skindex'));
|
||||
sdk.loadModule(require('../modules/VectorConferenceHandler'));
|
||||
@ -65,14 +66,21 @@ function parseQsFromFragment(location) {
|
||||
return {};
|
||||
}
|
||||
|
||||
function parseQs(location) {
|
||||
return qs.parse(location.search.substring(1));
|
||||
}
|
||||
|
||||
// Here, we do some crude URL analysis to allow
|
||||
// deep-linking. We only support registration
|
||||
// deep-links in this example.
|
||||
function routeUrl(location) {
|
||||
if (location.hash.indexOf('#/register') == 0) {
|
||||
var params = parseQs(location);
|
||||
var loginToken = params.loginToken;
|
||||
if (loginToken) {
|
||||
window.matrixChat.showScreen('token_login', parseQs(location));
|
||||
}
|
||||
else if (location.hash.indexOf('#/register') == 0) {
|
||||
window.matrixChat.showScreen('register', parseQsFromFragment(location));
|
||||
} else if (location.hash.indexOf('#/login/cas') == 0) {
|
||||
window.matrixChat.showScreen('cas_login', parseQsFromFragment(location));
|
||||
} else {
|
||||
window.matrixChat.showScreen(location.hash.substring(2));
|
||||
}
|
||||
@ -129,7 +137,7 @@ window.onload = function() {
|
||||
function loadApp() {
|
||||
if (validBrowser) {
|
||||
var MatrixChat = sdk.getComponent('pages.MatrixChat');
|
||||
window.matrixChat = React.render(
|
||||
window.matrixChat = ReactDOM.render(
|
||||
<MatrixChat onNewScreen={onNewScreen} registrationUrl={makeRegistrationUrl()} />,
|
||||
document.getElementById('matrixchat')
|
||||
);
|
||||
@ -138,7 +146,7 @@ function loadApp() {
|
||||
console.error("Browser is missing required features.");
|
||||
// take to a different landing page to AWOOOOOGA at the user
|
||||
var CompatibilityPage = require("../skins/vector/views/pages/CompatibilityPage");
|
||||
window.matrixChat = React.render(
|
||||
window.matrixChat = ReactDOM.render(
|
||||
<CompatibilityPage onAccept={function() {
|
||||
validBrowser = true;
|
||||
console.log("User accepts the compatibility risks.");
|
||||
|
@ -4,26 +4,26 @@
|
||||
<meta charset="utf-8">
|
||||
<title>Vector</title>
|
||||
<link href='fonts/MyriadPro.css' rel='stylesheet' type='text/css'>
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/icons/apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/icons/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/icons/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/icons/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/icons/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/icons/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/icons/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/icons/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" href="/icons/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="/icons/android-chrome-192x192.png" sizes="192x192">
|
||||
<link rel="icon" type="image/png" href="/icons/favicon-96x96.png" sizes="96x96">
|
||||
<link rel="icon" type="image/png" href="/icons/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="manifest" href="/icons/manifest.json">
|
||||
<link rel="shortcut icon" href="/icons/favicon.ico">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="icons/apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="icons/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="icons/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="icons/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="icons/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="icons/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="icons/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="icons/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" href="icons/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="icons/android-chrome-192x192.png" sizes="192x192">
|
||||
<link rel="icon" type="image/png" href="icons/favicon-96x96.png" sizes="96x96">
|
||||
<link rel="icon" type="image/png" href="icons/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="manifest" href="icons/manifest.json">
|
||||
<link rel="shortcut icon" href="icons/favicon.ico">
|
||||
<meta name="apple-mobile-web-app-title" content="Vector">
|
||||
<meta name="application-name" content="Vector">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="msapplication-TileImage" content="/icons/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="/icons/browserconfig.xml">
|
||||
<meta name="msapplication-TileImage" content="icons/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="icons/browserconfig.xml">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
</head>
|
||||
<body style="height: 100%;">
|
||||
|
Loading…
Reference in New Issue
Block a user