Merge branch 'develop'
28
README.md
@ -22,18 +22,34 @@ into the `vector` directory and run your own server.
|
||||
|
||||
Development
|
||||
===========
|
||||
You can work on any of the source files within Vector with the setup above,
|
||||
and your changes will cause an instant rebuild. If you also need to make
|
||||
changes to the react sdk, you can:
|
||||
|
||||
1. Link the react sdk package into the example:
|
||||
For simple tweaks, you can work on any of the source files within Vector with the
|
||||
setup above, and your changes will cause an instant rebuild.
|
||||
|
||||
However, all serious development on Vector happens on the `develop` branch. This typically
|
||||
depends on the `develop` snapshot versions of `matrix-react-sdk` and `matrix-js-sdk`
|
||||
too, which isn't expressed in Vector's `package.json`. To do this, check out
|
||||
the `develop` branches of these libraries and then use `npm link` to tell Vector
|
||||
about them:
|
||||
|
||||
1. `git clone git@github.com:matrix-org/matrix-react-sdk.git`
|
||||
2. `cd matrix-react-sdk`
|
||||
3. `git checkout develop`
|
||||
4. `npm install`
|
||||
5. `npm start` (to start the dev rebuilder)
|
||||
6. `cd ../vector-web`
|
||||
7. Link the react sdk package into the example:
|
||||
`npm link path/to/your/react/sdk`
|
||||
2. Start the development rebuilder in your react SDK directory:
|
||||
`npm start`
|
||||
|
||||
Similarly, you may need to `npm link path/to/your/js/sdk` in your `matrix-react-sdk`
|
||||
directory.
|
||||
|
||||
If you add or remove any components from the Vector skin, you will need to rebuild
|
||||
the skin's index by running, `npm run reskindex`.
|
||||
|
||||
You may need to run `npm i source-map-loader` in matrix-js-sdk if you get errors
|
||||
about "Cannot resolve module 'source-map-loader'" due to shortcomings in webpack.
|
||||
|
||||
Deployment
|
||||
==========
|
||||
|
||||
|
@ -27,8 +27,8 @@
|
||||
"filesize": "^3.1.2",
|
||||
"flux": "~2.0.3",
|
||||
"linkifyjs": "^2.0.0-beta.4",
|
||||
"matrix-js-sdk": "^0.2.1",
|
||||
"matrix-react-sdk": "^0.0.1",
|
||||
"matrix-js-sdk": "^0.3.0",
|
||||
"matrix-react-sdk": "^0.0.2",
|
||||
"q": "^1.4.1",
|
||||
"react": "^0.13.3",
|
||||
"react-loader": "^1.4.0"
|
||||
|
@ -20,14 +20,17 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
|
||||
module.exports = {
|
||||
avatarUrlForMember: function(member, width, height, resizeMethod) {
|
||||
var url = MatrixClientPeg.get().getAvatarUrlForMember(
|
||||
member,
|
||||
var url = member.getAvatarUrl(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
width,
|
||||
height,
|
||||
resizeMethod
|
||||
);
|
||||
if (!url) {
|
||||
url = this.defaultAvatarUrlForString(member.userId);
|
||||
// member can be null here currently since on invites, the JS SDK
|
||||
// does not have enough info to build a RoomMember object for
|
||||
// the inviter.
|
||||
url = this.defaultAvatarUrlForString(member ? member.userId : '');
|
||||
}
|
||||
return url;
|
||||
},
|
||||
|
@ -49,15 +49,25 @@ module.exports = {
|
||||
|
||||
var position = {
|
||||
top: props.top - 20,
|
||||
right: props.right + 8,
|
||||
};
|
||||
|
||||
var chevron = null;
|
||||
if (props.left) {
|
||||
chevron = <img className="mx_ContextualMenu_chevron_left" src="img/chevron-left.png" width="9" height="16" />
|
||||
position.left = props.left + 8;
|
||||
} else {
|
||||
chevron = <img className="mx_ContextualMenu_chevron_right" src="img/chevron-right.png" width="9" height="16" />
|
||||
position.right = props.right + 8;
|
||||
}
|
||||
|
||||
var className = 'mx_ContextualMenu_wrapper';
|
||||
|
||||
// FIXME: If a menu uses getDefaultProps it clobbers the onFinished
|
||||
// property set here so you can't close the menu from a button click!
|
||||
var menu = (
|
||||
<div className="mx_ContextualMenu_wrapper">
|
||||
<div className={className}>
|
||||
<div className="mx_ContextualMenu" style={position}>
|
||||
<img className="mx_ContextualMenu_chevron" src="img/chevron-right.png" width="9" height="16" />
|
||||
{chevron}
|
||||
<Element {...props} onFinished={closeMenu}/>
|
||||
</div>
|
||||
<div className="mx_ContextualMenu_background" onClick={closeMenu}></div>
|
||||
|
@ -74,8 +74,10 @@ module.exports = {
|
||||
);
|
||||
if (call) {
|
||||
call.setLocalVideoElement(this.getVideoView().getLocalVideoElement());
|
||||
// N.B. the remote video element is used for playback for audio for voice calls
|
||||
call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement());
|
||||
// give a separate element for audio stream playback - both for voice calls
|
||||
// and for the voice stream of screen captures
|
||||
call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement());
|
||||
}
|
||||
if (call && call.type === "video" && call.state !== 'ended') {
|
||||
// if this call is a conf call, don't display local video as the
|
||||
@ -88,6 +90,7 @@ module.exports = {
|
||||
else {
|
||||
this.getVideoView().getLocalVideoElement().style.display = "none";
|
||||
this.getVideoView().getRemoteVideoElement().style.display = "none";
|
||||
dis.dispatch({action: 'video_fullscreen', fullscreen: false});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -33,6 +33,8 @@ module.exports = {
|
||||
cli.on("Room", this.onRoom);
|
||||
cli.on("Room.timeline", this.onRoomTimeline);
|
||||
cli.on("Room.name", this.onRoomName);
|
||||
cli.on("RoomState.events", this.onRoomStateEvents);
|
||||
cli.on("RoomMember.name", this.onRoomMemberName);
|
||||
|
||||
var rooms = this.getRoomList();
|
||||
this.setState({
|
||||
@ -52,6 +54,11 @@ module.exports = {
|
||||
case 'call_state':
|
||||
this._recheckCallElement(this.props.selectedRoom);
|
||||
break;
|
||||
case 'view_tooltip':
|
||||
this.tooltip = payload.tooltip;
|
||||
this._repositionTooltip();
|
||||
if (this.tooltip) this.tooltip.style.display = 'block';
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
@ -61,6 +68,7 @@ module.exports = {
|
||||
MatrixClientPeg.get().removeListener("Room", this.onRoom);
|
||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
|
||||
}
|
||||
},
|
||||
|
||||
@ -105,6 +113,15 @@ module.exports = {
|
||||
this.refreshRoomList();
|
||||
},
|
||||
|
||||
onRoomStateEvents: function(ev, state) {
|
||||
setTimeout(this.refreshRoomList, 0);
|
||||
},
|
||||
|
||||
onRoomMemberName: function(ev, member) {
|
||||
setTimeout(this.refreshRoomList, 0);
|
||||
},
|
||||
|
||||
|
||||
refreshRoomList: function() {
|
||||
var rooms = this.getRoomList();
|
||||
this.setState({
|
||||
@ -150,6 +167,13 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
_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";
|
||||
}
|
||||
},
|
||||
|
||||
makeRoomTiles: function() {
|
||||
var self = this;
|
||||
var RoomTile = sdk.getComponent("molecules.RoomTile");
|
||||
@ -159,6 +183,7 @@ module.exports = {
|
||||
<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}
|
||||
|
503
src/controllers/organisms/RoomView.js
Normal file
@ -0,0 +1,503 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
|
||||
var React = require("react");
|
||||
var q = require("q");
|
||||
var ContentMessages = require("matrix-react-sdk/lib//ContentMessages");
|
||||
var WhoIsTyping = require("matrix-react-sdk/lib/WhoIsTyping");
|
||||
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 dis = require("matrix-react-sdk/lib/dispatcher");
|
||||
|
||||
var PAGINATE_SIZE = 20;
|
||||
var INITIAL_SIZE = 20;
|
||||
|
||||
module.exports = {
|
||||
getInitialState: function() {
|
||||
return {
|
||||
room: this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null,
|
||||
messageCap: INITIAL_SIZE,
|
||||
editingRoomSettings: false,
|
||||
uploadingRoomSettings: false,
|
||||
numUnreadMessages: 0,
|
||||
draggingFile: false,
|
||||
}
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
||||
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
|
||||
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
|
||||
this.atBottom = true;
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
messageWrapper.removeEventListener('drop', this.onDrop);
|
||||
messageWrapper.removeEventListener('dragover', this.onDragOver);
|
||||
messageWrapper.removeEventListener('dragleave', this.onDragLeaveOrEnd);
|
||||
messageWrapper.removeEventListener('dragend', this.onDragLeaveOrEnd);
|
||||
}
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||
MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping);
|
||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||
}
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
switch (payload.action) {
|
||||
case 'message_send_failed':
|
||||
case 'message_sent':
|
||||
case 'message_resend_started':
|
||||
this.setState({
|
||||
room: MatrixClientPeg.get().getRoom(this.props.roomId)
|
||||
});
|
||||
this.forceUpdate();
|
||||
break;
|
||||
case 'notifier_enabled':
|
||||
this.forceUpdate();
|
||||
break;
|
||||
case 'call_state':
|
||||
if (CallHandler.getCallForRoom(this.props.roomId)) {
|
||||
// Call state has changed so we may be loading video elements
|
||||
// which will obscure the message log.
|
||||
// scroll to bottom
|
||||
var messageWrapper = this.refs.messageWrapper;
|
||||
if (messageWrapper) {
|
||||
messageWrapper = messageWrapper.getDOMNode();
|
||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// possibly remove the conf call notification if we're now in
|
||||
// the conf
|
||||
this._updateConfCallNotification();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// 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.
|
||||
/*componentWillReceiveProps: function(props) {
|
||||
},*/
|
||||
|
||||
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
||||
if (!this.isMounted()) return;
|
||||
|
||||
// ignore anything that comes in whilst pagingating: 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.
|
||||
if (this.state.paginating) return;
|
||||
|
||||
// no point handling anything while we're waiting for the join to finish:
|
||||
// we'll only be showing a spinner.
|
||||
if (this.state.joining) return;
|
||||
if (room.roomId != this.props.roomId) return;
|
||||
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
this.atBottom = (
|
||||
messageWrapper.scrollHeight - messageWrapper.scrollTop <=
|
||||
(messageWrapper.clientHeight + 150)
|
||||
);
|
||||
}
|
||||
|
||||
var currentUnread = this.state.numUnreadMessages;
|
||||
if (!toStartOfTimeline &&
|
||||
(ev.getSender() !== MatrixClientPeg.get().credentials.userId)) {
|
||||
// update unread count when scrolled up
|
||||
if (this.atBottom) {
|
||||
currentUnread = 0;
|
||||
}
|
||||
else {
|
||||
currentUnread += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.setState({
|
||||
room: MatrixClientPeg.get().getRoom(this.props.roomId),
|
||||
numUnreadMessages: currentUnread
|
||||
});
|
||||
|
||||
if (toStartOfTimeline && !this.state.paginating) {
|
||||
this.fillSpace();
|
||||
}
|
||||
},
|
||||
|
||||
onRoomName: function(room) {
|
||||
if (room.roomId == this.props.roomId) {
|
||||
this.setState({
|
||||
room: room
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onRoomMemberTyping: function(ev, member) {
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
onRoomStateMember: function(ev, state, member) {
|
||||
if (member.roomId !== this.props.roomId ||
|
||||
member.userId !== VectorConferenceHandler.getConferenceUserIdForRoom(member.roomId)) {
|
||||
return;
|
||||
}
|
||||
this._updateConfCallNotification();
|
||||
},
|
||||
|
||||
_updateConfCallNotification: function() {
|
||||
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
if (!room) return;
|
||||
var confMember = room.getMember(
|
||||
VectorConferenceHandler.getConferenceUserIdForRoom(this.props.roomId)
|
||||
);
|
||||
|
||||
if (!confMember) {
|
||||
return;
|
||||
}
|
||||
var confCall = VectorConferenceHandler.getConferenceCallForRoom(confMember.roomId);
|
||||
|
||||
// A conf call notification should be displayed if there is an ongoing
|
||||
// conf call but this cilent isn't a part of it.
|
||||
this.setState({
|
||||
displayConfCallNotification: (
|
||||
(!confCall || confCall.call_state === "ended") &&
|
||||
confMember.membership === "join"
|
||||
)
|
||||
});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
|
||||
messageWrapper.addEventListener('drop', this.onDrop);
|
||||
messageWrapper.addEventListener('dragover', this.onDragOver);
|
||||
messageWrapper.addEventListener('dragleave', this.onDragLeaveOrEnd);
|
||||
messageWrapper.addEventListener('dragend', this.onDragLeaveOrEnd);
|
||||
|
||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||
|
||||
this.fillSpace();
|
||||
}
|
||||
|
||||
this._updateConfCallNotification();
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
if (!this.refs.messageWrapper) return;
|
||||
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
|
||||
if (this.state.paginating && !this.waiting_for_paginate) {
|
||||
var heightGained = messageWrapper.scrollHeight - this.oldScrollHeight;
|
||||
messageWrapper.scrollTop += heightGained;
|
||||
this.oldScrollHeight = undefined;
|
||||
if (!this.fillSpace()) {
|
||||
this.setState({paginating: false});
|
||||
}
|
||||
} else if (this.atBottom) {
|
||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||
if (this.state.numUnreadMessages !== 0) {
|
||||
this.setState({numUnreadMessages: 0});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
fillSpace: function() {
|
||||
if (!this.refs.messageWrapper) return;
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
if (messageWrapper.scrollTop < messageWrapper.clientHeight && this.state.room.oldState.paginationToken) {
|
||||
this.setState({paginating: true});
|
||||
|
||||
this.oldScrollHeight = messageWrapper.scrollHeight;
|
||||
|
||||
if (this.state.messageCap < this.state.room.timeline.length) {
|
||||
this.waiting_for_paginate = false;
|
||||
var cap = Math.min(this.state.messageCap + PAGINATE_SIZE, this.state.room.timeline.length);
|
||||
this.setState({messageCap: cap, paginating: true});
|
||||
} else {
|
||||
this.waiting_for_paginate = true;
|
||||
var cap = this.state.messageCap + PAGINATE_SIZE;
|
||||
this.setState({messageCap: cap, paginating: true});
|
||||
var self = this;
|
||||
MatrixClientPeg.get().scrollback(this.state.room, PAGINATE_SIZE).finally(function() {
|
||||
self.waiting_for_paginate = false;
|
||||
if (self.isMounted()) {
|
||||
self.setState({
|
||||
room: MatrixClientPeg.get().getRoom(self.props.roomId)
|
||||
});
|
||||
}
|
||||
// wait and set paginating to false when the component updates
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
onJoinButtonClicked: function(ev) {
|
||||
var self = this;
|
||||
MatrixClientPeg.get().joinRoom(this.props.roomId).then(function() {
|
||||
self.setState({
|
||||
joining: false,
|
||||
room: MatrixClientPeg.get().getRoom(self.props.roomId)
|
||||
});
|
||||
}, function(error) {
|
||||
self.setState({
|
||||
joining: false,
|
||||
joinError: error
|
||||
});
|
||||
});
|
||||
this.setState({
|
||||
joining: true
|
||||
});
|
||||
},
|
||||
|
||||
onMessageListScroll: function(ev) {
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
var wasAtBottom = this.atBottom;
|
||||
this.atBottom = messageWrapper.scrollHeight - messageWrapper.scrollTop <= messageWrapper.clientHeight;
|
||||
if (this.atBottom && !wasAtBottom) {
|
||||
this.forceUpdate(); // remove unread msg count
|
||||
}
|
||||
}
|
||||
if (!this.state.paginating) this.fillSpace();
|
||||
},
|
||||
|
||||
onDragOver: function(ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
ev.dataTransfer.dropEffect = 'none';
|
||||
|
||||
var items = ev.dataTransfer.items;
|
||||
if (items.length == 1) {
|
||||
if (items[0].kind == 'file') {
|
||||
this.setState({ draggingFile : true });
|
||||
ev.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDrop: function(ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.setState({ draggingFile : false });
|
||||
var files = ev.dataTransfer.files;
|
||||
if (files.length == 1) {
|
||||
this.uploadFile(files[0]);
|
||||
}
|
||||
},
|
||||
|
||||
onDragLeaveOrEnd: function(ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.setState({ draggingFile : false });
|
||||
},
|
||||
|
||||
uploadFile: function(file) {
|
||||
this.setState({
|
||||
upload: {
|
||||
fileName: file.name,
|
||||
uploadedBytes: 0,
|
||||
totalBytes: file.size
|
||||
}
|
||||
});
|
||||
var self = this;
|
||||
ContentMessages.sendContentToRoom(
|
||||
file, this.props.roomId, MatrixClientPeg.get()
|
||||
).progress(function(ev) {
|
||||
//console.log("Upload: "+ev.loaded+" / "+ev.total);
|
||||
self.setState({
|
||||
upload: {
|
||||
fileName: file.name,
|
||||
uploadedBytes: ev.loaded,
|
||||
totalBytes: ev.total
|
||||
}
|
||||
});
|
||||
}).finally(function() {
|
||||
self.setState({
|
||||
upload: undefined
|
||||
});
|
||||
}).done(undefined, function() {
|
||||
// display error message
|
||||
});
|
||||
},
|
||||
|
||||
getWhoIsTypingString: function() {
|
||||
return WhoIsTyping.whoIsTypingString(this.state.room);
|
||||
},
|
||||
|
||||
getEventTiles: function() {
|
||||
var DateSeparator = sdk.getComponent('molecules.DateSeparator');
|
||||
|
||||
var ret = [];
|
||||
var count = 0;
|
||||
|
||||
var EventTile = sdk.getComponent('molecules.EventTile');
|
||||
|
||||
for (var i = this.state.room.timeline.length-1; i >= 0 && count < this.state.messageCap; --i) {
|
||||
var mxEv = this.state.room.timeline[i];
|
||||
|
||||
if (!EventTile.supportsEventType(mxEv.getType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var continuation = false;
|
||||
var last = false;
|
||||
var dateSeparator = null;
|
||||
if (i == this.state.room.timeline.length - 1) {
|
||||
last = true;
|
||||
}
|
||||
if (i > 0 && count < this.state.messageCap - 1) {
|
||||
if (this.state.room.timeline[i].sender &&
|
||||
this.state.room.timeline[i - 1].sender &&
|
||||
(this.state.room.timeline[i].sender.userId ===
|
||||
this.state.room.timeline[i - 1].sender.userId) &&
|
||||
(this.state.room.timeline[i].getType() ==
|
||||
this.state.room.timeline[i - 1].getType())
|
||||
)
|
||||
{
|
||||
continuation = true;
|
||||
}
|
||||
|
||||
var ts0 = this.state.room.timeline[i - 1].getTs();
|
||||
var ts1 = this.state.room.timeline[i].getTs();
|
||||
if (new Date(ts0).toDateString() !== new Date(ts1).toDateString()) {
|
||||
dateSeparator = <DateSeparator key={ts1} ts={ts1}/>;
|
||||
continuation = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (i === 1) { // n.b. 1, not 0, as the 0th event is an m.room.create and so doesn't show on the timeline
|
||||
var ts1 = this.state.room.timeline[i].getTs();
|
||||
dateSeparator = <li key={ts1}><DateSeparator ts={ts1}/></li>;
|
||||
continuation = false;
|
||||
}
|
||||
|
||||
ret.unshift(
|
||||
<li key={mxEv.getId()}><EventTile mxEvent={mxEv} continuation={continuation} last={last}/></li>
|
||||
);
|
||||
if (dateSeparator) {
|
||||
ret.unshift(dateSeparator);
|
||||
}
|
||||
++count;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
|
||||
uploadNewState: function(new_name, new_topic, new_join_rule, new_history_visibility, new_power_levels) {
|
||||
var old_name = this.state.room.name;
|
||||
|
||||
var old_topic = this.state.room.currentState.getStateEvents('m.room.topic', '');
|
||||
if (old_topic) {
|
||||
old_topic = old_topic.getContent().topic;
|
||||
} else {
|
||||
old_topic = "";
|
||||
}
|
||||
|
||||
var old_join_rule = this.state.room.currentState.getStateEvents('m.room.join_rules', '');
|
||||
if (old_join_rule) {
|
||||
old_join_rule = old_join_rule.getContent().join_rule;
|
||||
} else {
|
||||
old_join_rule = "invite";
|
||||
}
|
||||
|
||||
var old_history_visibility = this.state.room.currentState.getStateEvents('m.room.history_visibility', '');
|
||||
if (old_history_visibility) {
|
||||
old_history_visibility = old_history_visibility.getContent().history_visibility;
|
||||
} else {
|
||||
old_history_visibility = "shared";
|
||||
}
|
||||
|
||||
var deferreds = [];
|
||||
|
||||
if (old_name != new_name && new_name != undefined && new_name) {
|
||||
deferreds.push(
|
||||
MatrixClientPeg.get().setRoomName(this.state.room.roomId, new_name)
|
||||
);
|
||||
}
|
||||
|
||||
if (old_topic != new_topic && new_topic != undefined) {
|
||||
deferreds.push(
|
||||
MatrixClientPeg.get().setRoomTopic(this.state.room.roomId, new_topic)
|
||||
);
|
||||
}
|
||||
|
||||
if (old_join_rule != new_join_rule && new_join_rule != undefined) {
|
||||
deferreds.push(
|
||||
MatrixClientPeg.get().sendStateEvent(
|
||||
this.state.room.roomId, "m.room.join_rules", {
|
||||
join_rule: new_join_rule,
|
||||
}, ""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (old_history_visibility != new_history_visibility && new_history_visibility != undefined) {
|
||||
deferreds.push(
|
||||
MatrixClientPeg.get().sendStateEvent(
|
||||
this.state.room.roomId, "m.room.history_visibility", {
|
||||
history_visibility: new_history_visibility,
|
||||
}, ""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (new_power_levels) {
|
||||
deferreds.push(
|
||||
MatrixClientPeg.get().sendStateEvent(
|
||||
this.state.room.roomId, "m.room.power_levels", new_power_levels, ""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (deferreds.length) {
|
||||
var self = this;
|
||||
q.all(deferreds).fail(function(err) {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to set state",
|
||||
description: err.toString()
|
||||
});
|
||||
}).finally(function() {
|
||||
self.setState({
|
||||
uploadingRoomSettings: false,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
editingRoomSettings: false,
|
||||
uploadingRoomSettings: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
58
src/controllers/templates/Register.js
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
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 extend = require('matrix-react-sdk/lib/extend');
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var BaseRegisterController = require('matrix-react-sdk/lib/controllers/templates/Register.js');
|
||||
|
||||
var RegisterController = {};
|
||||
extend(RegisterController, BaseRegisterController);
|
||||
|
||||
RegisterController.onRegistered = function(user_id, access_token) {
|
||||
MatrixClientPeg.replaceUsingAccessToken(
|
||||
this.state.hs_url, this.state.is_url, user_id, access_token
|
||||
);
|
||||
|
||||
this.setState({
|
||||
step: 'profile',
|
||||
busy: true
|
||||
});
|
||||
|
||||
var self = this;
|
||||
var cli = MatrixClientPeg.get();
|
||||
cli.getProfileInfo(cli.credentials.userId).done(function(result) {
|
||||
self.setState({
|
||||
avatarUrl: result.avatar_url,
|
||||
busy: false
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
console.err(err);
|
||||
self.setState({
|
||||
busy: false
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
RegisterController.onAccountReady = function() {
|
||||
if (this.props.onLoggedIn) {
|
||||
this.props.onLoggedIn();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = RegisterController;
|
@ -14,6 +14,13 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
html {
|
||||
/* hack to stop overscroll bounce on OSX and iOS.
|
||||
N.B. Breaks things when we have legitimate horizontal overscroll */
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Lato', Helvetica, Arial, Sans-Serif;
|
||||
font-size: 16px;
|
||||
@ -34,6 +41,12 @@ h2 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:link,
|
||||
a:visited {
|
||||
color: #80CEF4;
|
||||
}
|
||||
|
||||
.mx_ContextualMenu_background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@ -54,18 +67,29 @@ h2 {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.mx_ContextualMenu_chevron {
|
||||
.mx_ContextualMenu_chevron_right {
|
||||
padding: 12px;
|
||||
position: absolute;
|
||||
right: -21px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.mx_ContextualMenu_chevron_left {
|
||||
padding: 12px;
|
||||
position: absolute;
|
||||
left: -21px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.mx_ContextualMenu_field {
|
||||
padding: 3px 6px 3px 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_ContextualMenu_spinner {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.mx_Dialog_background {
|
||||
position: fixed;
|
||||
|
@ -1,7 +1,6 @@
|
||||
.mx_RoomDropTarget,
|
||||
.mx_RoomList_favourites_label,
|
||||
.mx_RoomList_archive_label,
|
||||
.mx_LeftPanel_hideButton,
|
||||
.mx_RoomHeader_search,
|
||||
.mx_RoomSettings_encrypt,
|
||||
.mx_CreateRoom_encrypt,
|
||||
|
@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_MessageTile {
|
||||
.mx_EventTile {
|
||||
max-width: 100%;
|
||||
clear: both;
|
||||
margin-top: 32px;
|
||||
margin-left: 56px;
|
||||
}
|
||||
|
||||
.mx_MessageTile_avatar {
|
||||
.mx_EventTile_avatar {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
margin-left: -64px;
|
||||
@ -29,17 +29,17 @@ limitations under the License.
|
||||
float: left;
|
||||
}
|
||||
|
||||
.mx_MessageTile_avatar img {
|
||||
.mx_EventTile_avatar img {
|
||||
background-color: #dbdbdb;
|
||||
border-radius: 20px;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.mx_MessageTile_continuation {
|
||||
.mx_EventTile_continuation {
|
||||
margin-top: 8px ! important;
|
||||
}
|
||||
|
||||
.mx_MessageTile .mx_SenderProfile {
|
||||
.mx_EventTile .mx_SenderProfile {
|
||||
color: #454545;
|
||||
opacity: 0.5;
|
||||
font-size: 14px;
|
||||
@ -47,35 +47,35 @@ limitations under the License.
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_MessageTile .mx_MessageTimestamp {
|
||||
.mx_EventTile .mx_MessageTimestamp {
|
||||
color: #454545;
|
||||
opacity: 0.5;
|
||||
font-size: 14px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.mx_MessageTile_content {
|
||||
.mx_EventTile_content {
|
||||
padding-right: 100px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_MessageTile_notice .mx_MessageTile_content {
|
||||
.mx_EventTile_notice .mx_MessageTile_content {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.mx_MessageTile_sending {
|
||||
.mx_EventTile_sending {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.mx_MessageTile_notSent {
|
||||
.mx_EventTile_notSent {
|
||||
color: #f11;
|
||||
}
|
||||
|
||||
.mx_MessageTile_highlight {
|
||||
color: #00f;
|
||||
.mx_EventTile_highlight {
|
||||
color: #FF0064;
|
||||
}
|
||||
|
||||
.mx_MessageTile_msgOption {
|
||||
.mx_EventTile_msgOption {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@ -83,10 +83,30 @@ limitations under the License.
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_MessageTile_last .mx_MessageTimestamp {
|
||||
.mx_EventTile_last .mx_MessageTimestamp {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_MessageTile:hover .mx_MessageTimestamp {
|
||||
.mx_EventTile:hover .mx_MessageTimestamp {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_EventTile_editButton {
|
||||
float: right;
|
||||
display: none;
|
||||
border: 0px;
|
||||
outline: none;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.mx_EventTile:hover .mx_EventTile_editButton {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_EventTile.menu .mx_EventTile_editButton {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_EventTile.menu .mx_MessageTimestamp {
|
||||
display: inline-block;
|
||||
}
|
@ -15,7 +15,6 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_MatrixToolbar {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
background-color: #ff0064;
|
||||
color: #fff;
|
||||
|
@ -128,3 +128,7 @@ limitations under the License.
|
||||
.mx_MemberTile_zalgo {
|
||||
font-family: Helvetica, Arial, Sans-Serif;
|
||||
}
|
||||
|
||||
.mx_MemberTile:hover .mx_MessageTimestamp {
|
||||
display: block;
|
||||
}
|
||||
|
@ -116,13 +116,16 @@ limitations under the License.
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.mx_RoomHeader_nameInput {
|
||||
.mx_RoomHeader_name input, .mx_RoomHeader_nameInput {
|
||||
border-radius: 3px;
|
||||
width: 260px;
|
||||
border: 1px solid #c7c7c7;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
.mx_RoomHeader_nameInput {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
@ -160,3 +163,11 @@ limitations under the License.
|
||||
.mx_RoomHeader_button img {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_RoomHeader_voipButton {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.mx_RoomHeader_voipButtons {
|
||||
margin-top: 18px;
|
||||
}
|
@ -22,13 +22,13 @@ limitations under the License.
|
||||
|
||||
.mx_RoomTile_avatar {
|
||||
display: table-cell;
|
||||
padding-right: 12px;
|
||||
padding-right: 10px;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
padding-left: 16px;
|
||||
padding-left: 10px;
|
||||
vertical-align: middle;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@ -45,6 +45,10 @@ limitations under the License.
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomTile_name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*
|
||||
.mx_RoomTile_nameBadge {
|
||||
display: table;
|
||||
|
@ -14,7 +14,20 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
.mx_RoomTooltip {
|
||||
display: none;
|
||||
position: fixed;
|
||||
border: 1px solid #a9dbf4;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
z-index: 1000;
|
||||
margin-top: 6px;
|
||||
left: 64px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
};
|
||||
.mx_RoomTooltip_chevron {
|
||||
position: absolute;
|
||||
left: -9px;
|
||||
top: 8px;
|
||||
}
|
@ -29,7 +29,9 @@ limitations under the License.
|
||||
.mx_LeftPanel_hideButton {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
right: 0px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_RoomList {
|
||||
@ -39,7 +41,7 @@ limitations under the License.
|
||||
-webkit-order: 1;
|
||||
order: 1;
|
||||
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
-webkit-flex: 1 1 0;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
@ -61,10 +63,6 @@ limitations under the License.
|
||||
color: #378bb4;
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile_avatar {
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options {
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
|
@ -57,7 +57,7 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_tableWrapper {
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
-webkit-flex: 1 1 0;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
@ -27,4 +27,5 @@ limitations under the License.
|
||||
.mx_RoomList h2 {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
padding-bottom: 10px;
|
||||
}
|
@ -106,10 +106,8 @@ limitations under the License.
|
||||
flex: 1 1 0;
|
||||
|
||||
width: 100%;
|
||||
margin-top: 18px;
|
||||
margin-bottom: 18px;
|
||||
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mx_RoomView_messageListWrapper {
|
||||
@ -123,6 +121,10 @@ limitations under the License.
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.mx_RoomView_MessageList li {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mx_RoomView_MessageList h2 {
|
||||
clear: both;
|
||||
margin-top: 32px;
|
||||
@ -210,13 +212,37 @@ limitations under the License.
|
||||
|
||||
.mx_RoomView_uploadProgressOuter {
|
||||
width: 100%;
|
||||
background-color: black;
|
||||
height: 5px;
|
||||
background-color: rgba(169, 219, 244, 0.5);
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.mx_RoomView_uploadProgressInner {
|
||||
background-color: blue;
|
||||
height: 5px;
|
||||
background-color: #80cef4;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.mx_RoomView_uploadFilename {
|
||||
margin-top: 15px;
|
||||
margin-left: 56px;
|
||||
}
|
||||
|
||||
.mx_RoomView_uploadIcon {
|
||||
float: left;
|
||||
margin-top: 6px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.mx_RoomView_uploadCancel {
|
||||
float: right;
|
||||
margin-top: 6px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.mx_RoomView_uploadBytes {
|
||||
float: right;
|
||||
opacity: 0.5;
|
||||
margin-top: 15px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.mx_RoomView_ongoingConfCallNotification {
|
||||
|
3
src/skins/vector/css/organisms/ViewSource.css
Normal file
@ -0,0 +1,3 @@
|
||||
.mx_ViewSource pre {
|
||||
text-align: left;
|
||||
}
|
@ -73,6 +73,11 @@ limitations under the License.
|
||||
flex: 0 0 230px;
|
||||
}
|
||||
|
||||
.mx_MatrixChat .mx_LeftPanel.collapsed {
|
||||
-webkit-flex: 0 0 60px;
|
||||
flex: 0 0 60px;
|
||||
}
|
||||
|
||||
.mx_MatrixChat .mx_MatrixChat_middlePanel {
|
||||
-webkit-box-ordinal-group: 2;
|
||||
-moz-box-ordinal-group: 2;
|
||||
@ -83,7 +88,9 @@ limitations under the License.
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
background-color: #f3f8fa;
|
||||
width: 100%;
|
||||
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
|
||||
/* XXX: Hack: apparently if you try to nest a flex-box
|
||||
* within a non-flex-box within a flex-box, the height
|
||||
@ -111,3 +118,8 @@ limitations under the License.
|
||||
-webkit-flex: 0 0 230px;
|
||||
flex: 0 0 230px;
|
||||
}
|
||||
|
||||
.mx_MatrixChat .mx_RightPanel.collapsed {
|
||||
-webkit-flex: 0 0 72px;
|
||||
flex: 0 0 72px;
|
||||
}
|
||||
|
BIN
src/skins/vector/img/50e2c2.png
Normal file
After Width: | Height: | Size: 146 B |
BIN
src/skins/vector/img/80cef4.png
Normal file
After Width: | Height: | Size: 146 B |
BIN
src/skins/vector/img/cancel.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 999 B After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 14 KiB |
BIN
src/skins/vector/img/f4c371.png
Normal file
After Width: | Height: | Size: 146 B |
BIN
src/skins/vector/img/fileicon.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 1.1 KiB |
BIN
src/skins/vector/img/menu.png
Normal file → Executable file
Before Width: | Height: | Size: 200 B After Width: | Height: | Size: 122 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 15 KiB |
BIN
src/skins/vector/img/spinner.gif
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
src/skins/vector/img/zoom.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
@ -23,6 +23,9 @@ limitations under the License.
|
||||
|
||||
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.EnableNotificationsButton'] = require('./views/atoms/EnableNotificationsButton');
|
||||
skin['atoms.ImageView'] = require('./views/atoms/ImageView');
|
||||
@ -30,32 +33,34 @@ skin['atoms.LogoutButton'] = require('./views/atoms/LogoutButton');
|
||||
skin['atoms.MemberAvatar'] = require('./views/atoms/MemberAvatar');
|
||||
skin['atoms.MessageTimestamp'] = require('./views/atoms/MessageTimestamp');
|
||||
skin['atoms.RoomAvatar'] = require('./views/atoms/RoomAvatar');
|
||||
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['molecules.BottomLeftMenu'] = require('./views/molecules/BottomLeftMenu');
|
||||
skin['molecules.BottomLeftMenuTile'] = require('./views/molecules/BottomLeftMenuTile');
|
||||
skin['molecules.ChangeAvatar'] = require('./views/molecules/ChangeAvatar');
|
||||
skin['molecules.ChangeDisplayName'] = require('./views/molecules/ChangeDisplayName');
|
||||
skin['molecules.ChangePassword'] = require('./views/molecules/ChangePassword');
|
||||
skin['molecules.DateSeparator'] = require('./views/molecules/DateSeparator');
|
||||
skin['molecules.EventAsTextTile'] = require('./views/molecules/EventAsTextTile');
|
||||
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.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.MImageTile'] = require('./views/molecules/MImageTile');
|
||||
skin['molecules.MNoticeTile'] = require('./views/molecules/MNoticeTile');
|
||||
skin['molecules.MRoomMemberTile'] = require('./views/molecules/MRoomMemberTile');
|
||||
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.MessageTile'] = require('./views/molecules/MessageTile');
|
||||
skin['molecules.ProgressBar'] = require('./views/molecules/ProgressBar');
|
||||
skin['molecules.RoomCreate'] = require('./views/molecules/RoomCreate');
|
||||
skin['molecules.RoomDropTarget'] = require('./views/molecules/RoomDropTarget');
|
||||
skin['molecules.RoomHeader'] = require('./views/molecules/RoomHeader');
|
||||
skin['molecules.RoomSettings'] = require('./views/molecules/RoomSettings');
|
||||
skin['molecules.RoomTile'] = require('./views/molecules/RoomTile');
|
||||
skin['molecules.RoomTooltip'] = require('./views/molecules/RoomTooltip');
|
||||
skin['molecules.SenderProfile'] = require('./views/molecules/SenderProfile');
|
||||
skin['molecules.ServerConfig'] = require('./views/molecules/ServerConfig');
|
||||
skin['molecules.UnknownMessageTile'] = require('./views/molecules/UnknownMessageTile');
|
||||
@ -63,6 +68,7 @@ skin['molecules.UserSelector'] = require('./views/molecules/UserSelector');
|
||||
skin['molecules.voip.CallView'] = require('./views/molecules/voip/CallView');
|
||||
skin['molecules.voip.IncomingCallBox'] = require('./views/molecules/voip/IncomingCallBox');
|
||||
skin['molecules.voip.VideoView'] = require('./views/molecules/voip/VideoView');
|
||||
skin['organisms.CasLogin'] = require('./views/organisms/CasLogin');
|
||||
skin['organisms.CreateRoom'] = require('./views/organisms/CreateRoom');
|
||||
skin['organisms.ErrorDialog'] = require('./views/organisms/ErrorDialog');
|
||||
skin['organisms.LeftPanel'] = require('./views/organisms/LeftPanel');
|
||||
@ -75,6 +81,7 @@ skin['organisms.RoomDirectory'] = require('./views/organisms/RoomDirectory');
|
||||
skin['organisms.RoomList'] = require('./views/organisms/RoomList');
|
||||
skin['organisms.RoomView'] = require('./views/organisms/RoomView');
|
||||
skin['organisms.UserSettings'] = require('./views/organisms/UserSettings');
|
||||
skin['organisms.ViewSource'] = require('./views/organisms/ViewSource');
|
||||
skin['pages.MatrixChat'] = require('./views/pages/MatrixChat');
|
||||
skin['templates.Login'] = require('./views/templates/Login');
|
||||
skin['templates.Register'] = require('./views/templates/Register');
|
||||
|
@ -18,11 +18,8 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var ImageViewController = require('../../../../controllers/atoms/ImageView')
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ImageView',
|
||||
mixins: [ImageViewController],
|
||||
|
||||
// XXX: keyboard shortcuts for managing dialogs should be done by the modal dialog base class omehow, surely...
|
||||
componentDidMount: function() {
|
||||
|
@ -18,17 +18,39 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MessageTimestampController = require('matrix-react-sdk/lib/controllers/atoms/MessageTimestamp')
|
||||
var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MessageTimestamp',
|
||||
mixins: [MessageTimestampController],
|
||||
|
||||
formatDate: function(date) {
|
||||
// date.toLocaleTimeString is completely system dependent.
|
||||
// just go 24h for now
|
||||
function pad(n) {
|
||||
return (n < 10 ? '0' : '') + n;
|
||||
}
|
||||
|
||||
var now = new Date();
|
||||
if (date.toDateString() === now.toDateString()) {
|
||||
return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
|
||||
return days[date.getDay()] + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
else if (now.getFullYear() === date.getFullYear()) {
|
||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
else {
|
||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var date = new Date(this.props.ts);
|
||||
return (
|
||||
<span className="mx_MessageTimestamp">
|
||||
{date.toLocaleTimeString()}
|
||||
{ this.formatDate(date) }
|
||||
</span>
|
||||
);
|
||||
},
|
||||
|
@ -24,10 +24,31 @@ module.exports = React.createClass({
|
||||
displayName: 'RoomAvatar',
|
||||
mixins: [RoomAvatarController],
|
||||
|
||||
getUrlList: function() {
|
||||
return [
|
||||
this.roomAvatarUrl(),
|
||||
this.getOneToOneAvatar(),
|
||||
this.getFallbackAvatar()
|
||||
];
|
||||
},
|
||||
|
||||
getFallbackAvatar: function() {
|
||||
var images = [ '80cef4', '50e2c2', 'f4c371' ];
|
||||
var total = 0;
|
||||
for (var i = 0; i < this.props.room.roomId.length; ++i) {
|
||||
total += this.props.room.roomId.charCodeAt(i);
|
||||
}
|
||||
return 'img/' + images[total % images.length] + '.png';
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var style = {
|
||||
maxWidth: this.props.width,
|
||||
maxHeight: this.props.height,
|
||||
};
|
||||
return (
|
||||
<img className="mx_RoomAvatar" src={this.state.imageUrl} onError={this.onError}
|
||||
width={this.props.width} height={this.props.height}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
34
src/skins/vector/views/atoms/Spinner.js
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
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');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'Spinner',
|
||||
|
||||
render: function() {
|
||||
var w = this.props.w || 32;
|
||||
var h = this.props.h || 32;
|
||||
var imgClass = this.props.imgClassName || "";
|
||||
return (
|
||||
<div>
|
||||
<img src="img/spinner.gif" width={w} height={h} className={imgClass}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -18,11 +18,8 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var VideoFeedController = require('matrix-react-sdk/lib/controllers/atoms/voip/VideoFeed')
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'VideoFeed',
|
||||
mixins: [VideoFeedController],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
module.exports = React.createClass({
|
||||
@ -35,28 +35,24 @@ module.exports = React.createClass({
|
||||
dis.dispatch({action: 'view_create_room'});
|
||||
},
|
||||
|
||||
getLabel: function(name) {
|
||||
if (!this.props.collapsed) {
|
||||
return <div className="mx_RoomTile_name">{name}</div>
|
||||
}
|
||||
else if (this.state.hover) {
|
||||
var RoomTooltip = sdk.getComponent("molecules.RoomTooltip");
|
||||
return <RoomTooltip name={name}/>;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var BottomLeftMenuTile = sdk.getComponent('molecules.BottomLeftMenuTile');
|
||||
return (
|
||||
<div className="mx_BottomLeftMenu">
|
||||
<div className="mx_BottomLeftMenu_options">
|
||||
<div className="mx_RoomTile" onClick={this.onCreateRoomClick}>
|
||||
<div className="mx_RoomTile_avatar">
|
||||
<img src="img/create-big.png" alt="Create new room" title="Create new room" width="42" height="42"/>
|
||||
</div>
|
||||
<div className="mx_RoomTile_name">Create new room</div>
|
||||
</div>
|
||||
<div className="mx_RoomTile" onClick={this.onRoomDirectoryClick}>
|
||||
<div className="mx_RoomTile_avatar">
|
||||
<img src="img/directory-big.png" alt="Directory" title="Directory" width="42" height="42"/>
|
||||
</div>
|
||||
<div className="mx_RoomTile_name">Directory</div>
|
||||
</div>
|
||||
<div className="mx_RoomTile" onClick={this.onSettingsClick}>
|
||||
<div className="mx_RoomTile_avatar">
|
||||
<img src="img/settings-big.png" alt="Settings" title="Settings" width="42" height="42"/>
|
||||
</div>
|
||||
<div className="mx_RoomTile_name">Settings</div>
|
||||
</div>
|
||||
<BottomLeftMenuTile collapsed={ this.props.collapsed } img="img/create-big.png" label="Create new room" onClick={ this.onCreateRoomClick }/>
|
||||
<BottomLeftMenuTile collapsed={ this.props.collapsed } img="img/directory-big.png" label="Directory" onClick={ this.onRoomDirectoryClick }/>
|
||||
<BottomLeftMenuTile collapsed={ this.props.collapsed } img="img/settings-big.png" label="Settings" onClick={ this.onSettingsClick }/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
57
src/skins/vector/views/molecules/BottomLeftMenuTile.js
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 sdk = require('matrix-react-sdk')
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'BottomLeftMenuTile',
|
||||
|
||||
getInitialState: function() {
|
||||
return( { hover : false });
|
||||
},
|
||||
|
||||
onMouseEnter: function() {
|
||||
this.setState( { hover : true });
|
||||
},
|
||||
|
||||
onMouseLeave: function() {
|
||||
this.setState( { hover : false });
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var label;
|
||||
if (!this.props.collapsed) {
|
||||
label = <div className="mx_RoomTile_name">{ this.props.label }</div>;
|
||||
}
|
||||
else if (this.state.hover) {
|
||||
var RoomTooltip = sdk.getComponent("molecules.RoomTooltip");
|
||||
label = <RoomTooltip bottom={ true } label={ this.props.label }/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_RoomTile" onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onClick={this.props.onClick}>
|
||||
<div className="mx_RoomTile_avatar">
|
||||
<img src={ this.props.img } width="36" height="36"/>
|
||||
</div>
|
||||
{ label }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var ChangeAvatarController = require('matrix-react-sdk/lib/controllers/molecules/ChangeAvatar')
|
||||
|
||||
var Loader = require("react-loader");
|
||||
@ -28,6 +29,7 @@ module.exports = React.createClass({
|
||||
mixins: [ChangeAvatarController],
|
||||
|
||||
onFileSelected: function(ev) {
|
||||
this.avatarSet = true;
|
||||
this.setAvatarFromFile(ev.target.files[0]);
|
||||
},
|
||||
|
||||
@ -38,22 +40,33 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var RoomAvatar = sdk.getComponent('atoms.RoomAvatar');
|
||||
var avatarImg;
|
||||
// Having just set an avatar we just display that since it will take a little
|
||||
// time to propagate through to the RoomAvatar.
|
||||
if (this.props.room && !this.avatarSet) {
|
||||
avatarImg = <RoomAvatar room={this.props.room} width='320' height='240' resizeMethod='scale' />;
|
||||
} else {
|
||||
var style = {
|
||||
maxWidth: 320,
|
||||
maxHeight: 240,
|
||||
};
|
||||
avatarImg = <img src={this.state.avatarUrl} style={style} />;
|
||||
}
|
||||
|
||||
switch (this.state.phase) {
|
||||
case this.Phases.Display:
|
||||
case this.Phases.Error:
|
||||
return (
|
||||
<div>
|
||||
<div className="mx_Dialog_content">
|
||||
<img src={this.state.avatarUrl}/>
|
||||
{avatarImg}
|
||||
</div>
|
||||
<div className="mx_Dialog_content">
|
||||
Upload new:
|
||||
<input type="file" onChange={this.onFileSelected}/>
|
||||
{this.state.errorText}
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.props.onFinished}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
case this.Phases.Uploading:
|
||||
|
56
src/skins/vector/views/molecules/ChangeDisplayName.js
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
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 sdk = require('matrix-react-sdk');
|
||||
|
||||
var ChangeDisplayNameController = require("matrix-react-sdk/lib/controllers/molecules/ChangeDisplayName");
|
||||
var Loader = require("react-loader");
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ChangeDisplayName',
|
||||
mixins: [ChangeDisplayNameController],
|
||||
|
||||
edit: function() {
|
||||
this.refs.displayname_edit.edit()
|
||||
},
|
||||
|
||||
onValueChanged: function(new_value, shouldSubmit) {
|
||||
if (shouldSubmit) {
|
||||
this.changeDisplayname(new_value);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.busy) {
|
||||
return (
|
||||
<Loader />
|
||||
);
|
||||
} else if (this.state.errorString) {
|
||||
return (
|
||||
<div className="error">{this.state.errorString}</div>
|
||||
);
|
||||
} else {
|
||||
var EditableText = sdk.getComponent('atoms.EditableText');
|
||||
return (
|
||||
<EditableText ref="displayname_edit" initialValue={this.state.displayName} label="Click to set display name." onValueChanged={this.onValueChanged}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
@ -18,33 +18,24 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var EventAsTextTileController = require('matrix-react-sdk/lib/controllers/molecules/EventAsTextTile')
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var TextForEvent = require('matrix-react-sdk/lib/TextForEvent');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'EventAsTextTile',
|
||||
mixins: [EventAsTextTileController],
|
||||
|
||||
statics: {
|
||||
needsSenderProfile: function() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp');
|
||||
var MemberAvatar = sdk.getComponent('atoms.MemberAvatar');
|
||||
|
||||
var text = TextForEvent.textForEvent(this.props.mxEvent);
|
||||
if (text == null || text.length == 0) return null;
|
||||
|
||||
var timestamp = this.props.last ? <MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null;
|
||||
var avatar = this.props.mxEvent.sender ? <MemberAvatar member={this.props.mxEvent.sender} /> : null;
|
||||
return (
|
||||
<div className="mx_MessageTile mx_MessageTile_notice">
|
||||
<div className="mx_MessageTile_avatar">
|
||||
{ avatar }
|
||||
</div>
|
||||
{ timestamp }
|
||||
<span className="mx_SenderProfile"></span>
|
||||
<span className="mx_MessageTile_content">
|
||||
{TextForEvent.textForEvent(this.props.mxEvent)}
|
||||
</span>
|
||||
<div className="mx_EventAsTextTile">
|
||||
{TextForEvent.textForEvent(this.props.mxEvent)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
131
src/skins/vector/views/molecules/EventTile.js
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
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 classNames = require("classnames");
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
|
||||
var EventTileController = require('matrix-react-sdk/lib/controllers/molecules/EventTile')
|
||||
var ContextualMenu = require('../../../../ContextualMenu');
|
||||
|
||||
var eventTileTypes = {
|
||||
'm.room.message': 'molecules.MessageTile',
|
||||
'm.room.member' : 'molecules.EventAsTextTile',
|
||||
'm.call.invite' : 'molecules.EventAsTextTile',
|
||||
'm.call.answer' : 'molecules.EventAsTextTile',
|
||||
'm.call.hangup' : 'molecules.EventAsTextTile',
|
||||
'm.room.topic' : 'molecules.EventAsTextTile',
|
||||
};
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'EventTile',
|
||||
mixins: [EventTileController],
|
||||
|
||||
statics: {
|
||||
supportsEventType: function(et) {
|
||||
return eventTileTypes[et] !== undefined;
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {menu: false};
|
||||
},
|
||||
|
||||
onEditClicked: function(e) {
|
||||
var MessageContextMenu = sdk.getComponent('molecules.MessageContextMenu');
|
||||
var buttonRect = e.target.getBoundingClientRect()
|
||||
var x = buttonRect.right;
|
||||
var y = buttonRect.top + (e.target.height / 2);
|
||||
var self = this;
|
||||
ContextualMenu.createMenu(MessageContextMenu, {
|
||||
mxEvent: this.props.mxEvent,
|
||||
left: x,
|
||||
top: y,
|
||||
onFinished: function() {
|
||||
self.setState({menu: false});
|
||||
}
|
||||
});
|
||||
this.setState({menu: true});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp');
|
||||
var SenderProfile = sdk.getComponent('molecules.SenderProfile');
|
||||
var MemberAvatar = sdk.getComponent('atoms.MemberAvatar');
|
||||
|
||||
var content = this.props.mxEvent.getContent();
|
||||
var msgtype = content.msgtype;
|
||||
|
||||
var EventTileType = sdk.getComponent(eventTileTypes[this.props.mxEvent.getType()]);
|
||||
// This shouldn't happen: the caller should check we support this type
|
||||
// before trying to instantiate us
|
||||
if (!EventTileType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var classes = classNames({
|
||||
mx_EventTile: true,
|
||||
mx_EventTile_sending: ['sending', 'queued'].indexOf(
|
||||
this.props.mxEvent.status
|
||||
) !== -1,
|
||||
mx_EventTile_notSent: this.props.mxEvent.status == 'not_sent',
|
||||
mx_EventTile_highlight: this.shouldHighlight(),
|
||||
mx_EventTile_continuation: this.props.continuation,
|
||||
mx_EventTile_last: this.props.last,
|
||||
menu: this.state.menu
|
||||
});
|
||||
var timestamp = <MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
||||
var editButton = (
|
||||
<input
|
||||
type="image" src="img/edit.png" alt="Edit"
|
||||
className="mx_EventTile_editButton" onClick={this.onEditClicked}
|
||||
/>
|
||||
);
|
||||
|
||||
var aux = null;
|
||||
if (msgtype === 'm.image') aux = "sent an image";
|
||||
else if (msgtype === 'm.video') aux = "sent a video";
|
||||
else if (msgtype === 'm.file') aux = "uploaded a file";
|
||||
|
||||
var avatar, sender;
|
||||
if (!this.props.continuation) {
|
||||
if (this.props.mxEvent.sender) {
|
||||
avatar = (
|
||||
<div className="mx_EventTile_avatar">
|
||||
<MemberAvatar member={this.props.mxEvent.sender} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (EventTileType.needsSenderProfile()) {
|
||||
sender = <SenderProfile mxEvent={this.props.mxEvent} aux={aux} />;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className={classes}>
|
||||
{ avatar }
|
||||
{ sender }
|
||||
<div>
|
||||
{ timestamp }
|
||||
{ editButton }
|
||||
<EventTileType mxEvent={this.props.mxEvent} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
@ -19,15 +19,12 @@ limitations under the License.
|
||||
var React = require('react');
|
||||
var filesize = require('filesize');
|
||||
|
||||
var MImageTileController = require('matrix-react-sdk/lib/controllers/molecules/MImageTile')
|
||||
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var Modal = require('matrix-react-sdk/lib/Modal');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MImageTile',
|
||||
mixins: [MImageTileController],
|
||||
|
||||
thumbHeight: function(fullWidth, fullHeight, thumbWidth, thumbHeight) {
|
||||
if (!fullWidth || !fullHeight) {
|
||||
|
@ -18,14 +18,11 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MRoomMemberTileController = require('matrix-react-sdk/lib/controllers/molecules/MRoomMemberTile')
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var TextForEvent = require('matrix-react-sdk/lib/TextForEvent');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MRoomMemberTile',
|
||||
mixins: [MRoomMemberTileController],
|
||||
|
||||
getMemberEventText: function() {
|
||||
return TextForEvent.textForEvent(this.props.mxEvent);
|
||||
|
@ -20,11 +20,8 @@ var React = require('react');
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
|
||||
var MatrixToolbarController = require('matrix-react-sdk/lib/controllers/molecules/MatrixToolbar')
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MatrixToolbar',
|
||||
mixins: [MatrixToolbarController],
|
||||
|
||||
hideToolbar: function() {
|
||||
var Notifier = sdk.getComponent('organisms.Notifier');
|
||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var Loader = require("../atoms/Spinner");
|
||||
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var MemberInfoController = require('matrix-react-sdk/lib/controllers/molecules/MemberInfo')
|
||||
@ -26,7 +27,7 @@ module.exports = React.createClass({
|
||||
mixins: [MemberInfoController],
|
||||
|
||||
render: function() {
|
||||
var interactButton, kickButton, banButton, muteButton, giveModButton;
|
||||
var interactButton, kickButton, banButton, muteButton, giveModButton, spinner;
|
||||
if (this.props.member.userId === MatrixClientPeg.get().credentials.userId) {
|
||||
interactButton = <div className="mx_ContextualMenu_field" onClick={this.onLeaveClick}>Leave room</div>;
|
||||
}
|
||||
@ -34,6 +35,10 @@ module.exports = React.createClass({
|
||||
interactButton = <div className="mx_ContextualMenu_field" onClick={this.onChatClick}>Start chat</div>;
|
||||
}
|
||||
|
||||
if (this.state.creatingRoom) {
|
||||
spinner = <Loader imgClassName="mx_ContextualMenu_spinner"/>;
|
||||
}
|
||||
|
||||
if (this.state.can.kick) {
|
||||
kickButton = <div className="mx_ContextualMenu_field" onClick={this.onKick}>
|
||||
Kick
|
||||
@ -64,6 +69,7 @@ module.exports = React.createClass({
|
||||
{kickButton}
|
||||
{banButton}
|
||||
{giveModButton}
|
||||
{spinner}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -31,6 +31,24 @@ module.exports = React.createClass({
|
||||
displayName: 'MemberTile',
|
||||
mixins: [MemberTileController],
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
if (this.state.hover !== nextState.hover) return true;
|
||||
if (
|
||||
this.member_last_modified_time === undefined ||
|
||||
this.member_last_modified_time < nextProps.member.getLastModifiedTime()
|
||||
) {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
nextProps.member.user &&
|
||||
(this.user_last_modified_time === undefined ||
|
||||
this.user_last_modified_time < nextProps.member.user.getLastModifiedTime())
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
mouseEnter: function(e) {
|
||||
this.setState({ 'hover': true });
|
||||
},
|
||||
@ -93,6 +111,11 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.member_last_modified_time = this.props.member.getLastModifiedTime();
|
||||
if (this.props.member.user) {
|
||||
this.user_last_modified_time = this.props.member.user.getLastModifiedTime();
|
||||
}
|
||||
|
||||
var isMyUser = MatrixClientPeg.get().credentials.userId == this.props.member.userId;
|
||||
|
||||
var power;
|
||||
|
105
src/skins/vector/views/molecules/MessageContextMenu.js
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
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 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');
|
||||
|
||||
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'});
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
onViewSourceClick: function() {
|
||||
var ViewSource = sdk.getComponent('organisms.ViewSource');
|
||||
Modal.createDialog(ViewSource, {
|
||||
mxEvent: this.props.mxEvent
|
||||
});
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
onRedactClick: function() {
|
||||
MatrixClientPeg.get().redactEvent(
|
||||
this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()
|
||||
).done(function() {
|
||||
// message should disappear by itself
|
||||
}, function(e) {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
// display error message stating you couldn't delete this.
|
||||
var code = e.errcode || e.statusCode;
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Error",
|
||||
description: "You cannot delete this message. (" + code + ")"
|
||||
});
|
||||
});
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var resendButton;
|
||||
var viewSourceButton;
|
||||
var redactButton;
|
||||
|
||||
if (this.props.mxEvent.status == 'not_sent') {
|
||||
resendButton = (
|
||||
<div className="mx_ContextualMenu_field" onClick={this.onResendClick}>
|
||||
Resend
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
redactButton = (
|
||||
<div className="mx_ContextualMenu_field" onClick={this.onRedactClick}>
|
||||
Delete
|
||||
</div>
|
||||
);
|
||||
}
|
||||
viewSourceButton = (
|
||||
<div className="mx_ContextualMenu_field" onClick={this.onViewSourceClick}>
|
||||
View Source
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{resendButton}
|
||||
{redactButton}
|
||||
{viewSourceButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -18,8 +18,6 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var classNames = require("classnames");
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
|
||||
var MessageTileController = require('matrix-react-sdk/lib/controllers/molecules/MessageTile')
|
||||
@ -28,11 +26,13 @@ module.exports = React.createClass({
|
||||
displayName: 'MessageTile',
|
||||
mixins: [MessageTileController],
|
||||
|
||||
render: function() {
|
||||
var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp');
|
||||
var SenderProfile = sdk.getComponent('molecules.SenderProfile');
|
||||
var MemberAvatar = sdk.getComponent('atoms.MemberAvatar');
|
||||
statics: {
|
||||
needsSenderProfile: function() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var UnknownMessageTile = sdk.getComponent('molecules.UnknownMessageTile');
|
||||
|
||||
var tileTypes = {
|
||||
@ -49,47 +49,7 @@ module.exports = React.createClass({
|
||||
if (msgtype && tileTypes[msgtype]) {
|
||||
TileType = tileTypes[msgtype];
|
||||
}
|
||||
var classes = classNames({
|
||||
mx_MessageTile: true,
|
||||
mx_MessageTile_sending: ['sending', 'queued'].indexOf(
|
||||
this.props.mxEvent.status
|
||||
) !== -1,
|
||||
mx_MessageTile_notSent: this.props.mxEvent.status == 'not_sent',
|
||||
mx_MessageTile_highlight: this.shouldHighlight(),
|
||||
mx_MessageTile_continuation: this.props.continuation,
|
||||
mx_MessageTile_last: this.props.last,
|
||||
});
|
||||
var timestamp = <MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
||||
|
||||
var aux = null;
|
||||
if (msgtype === 'm.image') aux = "sent an image";
|
||||
else if (msgtype === 'm.video') aux = "sent a video";
|
||||
else if (msgtype === 'm.file') aux = "uploaded a file";
|
||||
|
||||
var avatar, sender, resend;
|
||||
if (!this.props.continuation) {
|
||||
if (this.props.mxEvent.sender) {
|
||||
avatar = (
|
||||
<div className="mx_MessageTile_avatar">
|
||||
<MemberAvatar member={this.props.mxEvent.sender} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
sender = <SenderProfile mxEvent={this.props.mxEvent} aux={aux} />;
|
||||
}
|
||||
if (this.props.mxEvent.status === "not_sent" && !this.state.resending) {
|
||||
resend = <button className="mx_MessageTile_msgOption" onClick={this.onResend}>
|
||||
Resend
|
||||
</button>;
|
||||
}
|
||||
return (
|
||||
<div className={classes}>
|
||||
{ avatar }
|
||||
{ timestamp }
|
||||
{ resend }
|
||||
{ sender }
|
||||
<TileType mxEvent={this.props.mxEvent} />
|
||||
</div>
|
||||
);
|
||||
return <TileType mxEvent={this.props.mxEvent} />;
|
||||
},
|
||||
});
|
||||
|
@ -18,7 +18,9 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher')
|
||||
|
||||
var CallHandler = require('matrix-react-sdk/lib/CallHandler');
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var RoomHeaderController = require('matrix-react-sdk/lib/controllers/molecules/RoomHeader')
|
||||
|
||||
@ -36,6 +38,10 @@ module.exports = React.createClass({
|
||||
return this.refs.name_edit.getDOMNode().value;
|
||||
},
|
||||
|
||||
onFullscreenClick: function() {
|
||||
dis.dispatch({action: 'video_fullscreen', fullscreen: true}, true);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var EditableText = sdk.getComponent("atoms.EditableText");
|
||||
var RoomAvatar = sdk.getComponent('atoms.RoomAvatar');
|
||||
@ -52,18 +58,41 @@ module.exports = React.createClass({
|
||||
else {
|
||||
var topic = this.props.room.currentState.getStateEvents('m.room.topic', '');
|
||||
|
||||
var callButtons;
|
||||
if (this.state) {
|
||||
switch (this.state.call_state) {
|
||||
case "ringback":
|
||||
case "connected":
|
||||
callButtons = (
|
||||
<div className="mx_RoomHeader_textButton" onClick={this.onHangupClick}>
|
||||
End call
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
var call_buttons;
|
||||
var zoom_button;
|
||||
if (this.state && this.state.call_state != 'ended') {
|
||||
//var muteVideoButton;
|
||||
var activeCall = (
|
||||
CallHandler.getCallForRoom(this.props.room.roomId)
|
||||
);
|
||||
/*
|
||||
if (activeCall && activeCall.type === "video") {
|
||||
muteVideoButton = (
|
||||
<div className="mx_RoomHeader_textButton mx_RoomHeader_voipButton"
|
||||
onClick={this.onMuteVideoClick}>
|
||||
{
|
||||
(activeCall.isLocalVideoMuted() ?
|
||||
"Unmute" : "Mute") + " video"
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
{muteVideoButton}
|
||||
<div className="mx_RoomHeader_textButton mx_RoomHeader_voipButton"
|
||||
onClick={this.onMuteAudioClick}>
|
||||
{
|
||||
(activeCall && activeCall.isMicrophoneMuted() ?
|
||||
"Unmute" : "Mute") + " audio"
|
||||
}
|
||||
</div>
|
||||
*/
|
||||
|
||||
call_buttons = (
|
||||
<div className="mx_RoomHeader_textButton"
|
||||
onClick={this.onHangupClick}>
|
||||
End call
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var name = null;
|
||||
@ -97,7 +126,15 @@ module.exports = React.createClass({
|
||||
var roomAvatar = null;
|
||||
if (this.props.room) {
|
||||
roomAvatar = (
|
||||
<RoomAvatar room={this.props.room} />
|
||||
<RoomAvatar room={this.props.room} width="48" height="48" />
|
||||
);
|
||||
}
|
||||
|
||||
if (activeCall && activeCall.type == "video") {
|
||||
zoom_button = (
|
||||
<div className="mx_RoomHeader_button" onClick={this.onFullscreenClick}>
|
||||
<img src="img/zoom.png" title="Fullscreen" alt="Fullscreen" width="32" height="32" style={{ 'marginTop': '3px' }}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -112,18 +149,19 @@ module.exports = React.createClass({
|
||||
{ topic_el }
|
||||
</div>
|
||||
</div>
|
||||
{callButtons}
|
||||
{call_buttons}
|
||||
{cancel_button}
|
||||
{save_button}
|
||||
<div className="mx_RoomHeader_rightRow">
|
||||
{ settings_button }
|
||||
{ zoom_button }
|
||||
<div className="mx_RoomHeader_button mx_RoomHeader_search">
|
||||
<img src="img/search.png" title="Search" alt="Search" width="32" height="32"/>
|
||||
</div>
|
||||
<div className="mx_RoomHeader_button mx_RoomHeader_video" onClick={this.onVideoClick}>
|
||||
<div className="mx_RoomHeader_button mx_RoomHeader_video" onClick={activeCall && activeCall.type === "video" ? this.onMuteVideoClick : this.onVideoClick}>
|
||||
<img src="img/video.png" title="Video call" alt="Video call" width="32" height="32"/>
|
||||
</div>
|
||||
<div className="mx_RoomHeader_button mx_RoomHeader_voice" onClick={this.onVoiceClick}>
|
||||
<div className="mx_RoomHeader_button mx_RoomHeader_voice" onClick={activeCall ? this.onMuteAudioClick : this.onVoiceClick}>
|
||||
<img src="img/voip.png" title="VoIP call" alt="VoIP call" width="32" height="32"/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var sdk = require('matrix-react-sdk');
|
||||
|
||||
var RoomSettingsController = require('matrix-react-sdk/lib/controllers/molecules/RoomSettings')
|
||||
|
||||
@ -65,6 +66,8 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var ChangeAvatar = sdk.getComponent('molecules.ChangeAvatar');
|
||||
|
||||
var topic = this.props.room.currentState.getStateEvents('m.room.topic', '');
|
||||
if (topic) topic = topic.getContent().topic;
|
||||
|
||||
@ -76,6 +79,8 @@ module.exports = React.createClass({
|
||||
|
||||
var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', '');
|
||||
|
||||
var events_levels = power_levels.events || {};
|
||||
|
||||
if (power_levels) {
|
||||
power_levels = power_levels.getContent();
|
||||
|
||||
@ -91,8 +96,7 @@ module.exports = React.createClass({
|
||||
if (power_levels.kick == undefined) kick_level = 50;
|
||||
if (power_levels.redact == undefined) redact_level = 50;
|
||||
|
||||
var user_levels = power_levels.users || [];
|
||||
var events_levels = power_levels.events || [];
|
||||
var user_levels = power_levels.users || {};
|
||||
|
||||
var user_id = MatrixClientPeg.get().credentials.userId;
|
||||
|
||||
@ -124,7 +128,21 @@ module.exports = React.createClass({
|
||||
var can_change_levels = false;
|
||||
}
|
||||
|
||||
var banned = this.props.room.getMembersWithMemership("ban");
|
||||
var room_avatar_level = parseInt(power_levels.state_default || 0);
|
||||
if (events_levels['m.room.avatar'] !== undefined) {
|
||||
room_avatar_level = events_levels['m.room.avatar'];
|
||||
}
|
||||
var can_set_room_avatar = current_user_level >= room_avatar_level;
|
||||
|
||||
var change_avatar;
|
||||
if (can_set_room_avatar) {
|
||||
change_avatar = <div>
|
||||
<h3>Room Icon</h3>
|
||||
<ChangeAvatar room={this.props.room} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
var banned = this.props.room.getMembersWithMembership("ban");
|
||||
|
||||
return (
|
||||
<div className="mx_RoomSettings">
|
||||
@ -207,6 +225,7 @@ module.exports = React.createClass({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{change_avatar}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -28,6 +28,19 @@ var sdk = require('matrix-react-sdk')
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomTile',
|
||||
mixins: [RoomTileController],
|
||||
|
||||
getInitialState: function() {
|
||||
return( { hover : false });
|
||||
},
|
||||
|
||||
onMouseEnter: function() {
|
||||
this.setState( { hover : true });
|
||||
},
|
||||
|
||||
onMouseLeave: function() {
|
||||
this.setState( { hover : false });
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
var classes = classNames({
|
||||
@ -57,14 +70,24 @@ module.exports = React.createClass({
|
||||
nameCell = <div className="mx_RoomTile_name">{name}</div>;
|
||||
}
|
||||
*/
|
||||
|
||||
var label;
|
||||
if (!this.props.collapsed) {
|
||||
label = <div className="mx_RoomTile_name">{name}</div>;
|
||||
}
|
||||
else if (this.state.hover) {
|
||||
var RoomTooltip = sdk.getComponent("molecules.RoomTooltip");
|
||||
label = <RoomTooltip room={this.props.room}/>;
|
||||
}
|
||||
|
||||
var RoomAvatar = sdk.getComponent('atoms.RoomAvatar');
|
||||
return (
|
||||
<div className={classes} onClick={this.onClick}>
|
||||
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<div className="mx_RoomTile_avatar">
|
||||
<RoomAvatar room={this.props.room} />
|
||||
{ badge }
|
||||
</div>
|
||||
<div className="mx_RoomTile_name">{name}</div>
|
||||
{ label }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
59
src/skins/vector/views/molecules/RoomTooltip.js
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
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 dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomTooltip',
|
||||
|
||||
componentDidMount: function() {
|
||||
if (!this.props.bottom) {
|
||||
// tell the roomlist about us so it can position us
|
||||
dis.dispatch({
|
||||
action: 'view_tooltip',
|
||||
tooltip: this.getDOMNode(),
|
||||
});
|
||||
}
|
||||
else {
|
||||
var tooltip = this.getDOMNode();
|
||||
tooltip.style.top = tooltip.parentElement.getBoundingClientRect().top + "px";
|
||||
tooltip.style.display = "block";
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUnmount: function() {
|
||||
if (!this.props.bottom) {
|
||||
dis.dispatch({
|
||||
action: 'view_tooltip',
|
||||
tooltip: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var label = this.props.room ? this.props.room.name : this.props.label;
|
||||
return (
|
||||
<div className="mx_RoomTooltip">
|
||||
<img className="mx_RoomTooltip_chevron" src="img/chevron-left.png" width="9" height="16"/>
|
||||
{ label }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -19,15 +19,12 @@ limitations under the License.
|
||||
var React = require('react');
|
||||
var classNames = require("classnames");
|
||||
|
||||
var SenderProfileController = require('matrix-react-sdk/lib/controllers/molecules/SenderProfile')
|
||||
|
||||
// The Lato WOFF doesn't include sensible combining diacritics, so Chrome chokes on rendering them.
|
||||
// Revert to Arial when this happens, which on OSX works at least.
|
||||
var zalgo = /[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/;
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'SenderProfile',
|
||||
mixins: [SenderProfileController],
|
||||
|
||||
render: function() {
|
||||
var mxEvent = this.props.mxEvent;
|
||||
|
@ -18,11 +18,8 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var UnknownMessageTileController = require('matrix-react-sdk/lib/controllers/molecules/UnknownMessageTile')
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'UnknownMessageTile',
|
||||
mixins: [UnknownMessageTileController],
|
||||
|
||||
render: function() {
|
||||
var content = this.props.mxEvent.getContent();
|
||||
|
@ -31,24 +31,28 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
// NB: This block MUST have a "key" so React doesn't clobber the elements
|
||||
// between in-call / not-in-call.
|
||||
var audioBlock = (
|
||||
<audio ref="ringAudio" key="voip_ring_audio" loop>
|
||||
<source src="media/ring.ogg" type="audio/ogg" />
|
||||
<source src="media/ring.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
);
|
||||
|
||||
if (!this.state.incomingCall || !this.state.incomingCall.roomId) {
|
||||
return (
|
||||
<div>
|
||||
<audio ref="ringAudio" loop>
|
||||
<source src="media/ring.ogg" type="audio/ogg" />
|
||||
<source src="media/ring.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
{audioBlock}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
var caller = MatrixClientPeg.get().getRoom(this.state.incomingCall.roomId).name;
|
||||
return (
|
||||
<div className="mx_IncomingCallBox">
|
||||
{audioBlock}
|
||||
<img className="mx_IncomingCallBox_chevron" src="img/chevron-left.png" width="9" height="16" />
|
||||
<audio ref="ringAudio" loop>
|
||||
<source src="media/ring.ogg" type="audio/ogg" />
|
||||
<source src="media/ring.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<div className="mx_IncomingCallBox_title">
|
||||
Incoming { this.state.incomingCall ? this.state.incomingCall.type : '' } call from { caller }
|
||||
</div>
|
||||
|
@ -19,26 +19,69 @@ limitations under the License.
|
||||
var React = require('react');
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var VideoViewController = require('matrix-react-sdk/lib/controllers/molecules/voip/VideoView')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher')
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'VideoView',
|
||||
mixins: [VideoViewController],
|
||||
|
||||
componentWillMount: function() {
|
||||
dis.register(this.onAction);
|
||||
},
|
||||
|
||||
getRemoteVideoElement: function() {
|
||||
return this.refs.remote.getDOMNode();
|
||||
},
|
||||
|
||||
getRemoteAudioElement: function() {
|
||||
return this.refs.remoteAudio.getDOMNode();
|
||||
},
|
||||
|
||||
getLocalVideoElement: function() {
|
||||
return this.refs.local.getDOMNode();
|
||||
},
|
||||
|
||||
setContainer: function(c) {
|
||||
this.container = c;
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
switch (payload.action) {
|
||||
case 'video_fullscreen':
|
||||
if (!this.container) {
|
||||
return;
|
||||
}
|
||||
var element = this.container.getDOMNode();
|
||||
if (payload.fullscreen) {
|
||||
var requestMethod = (
|
||||
element.requestFullScreen ||
|
||||
element.webkitRequestFullScreen ||
|
||||
element.mozRequestFullScreen ||
|
||||
element.msRequestFullscreen
|
||||
);
|
||||
requestMethod.call(element);
|
||||
}
|
||||
else {
|
||||
var exitMethod = (
|
||||
document.exitFullscreen ||
|
||||
document.mozCancelFullScreen ||
|
||||
document.webkitExitFullscreen ||
|
||||
document.msExitFullscreen
|
||||
);
|
||||
if (exitMethod) {
|
||||
exitMethod.call(document);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var VideoFeed = sdk.getComponent('atoms.voip.VideoFeed');
|
||||
return (
|
||||
<div className="mx_VideoView">
|
||||
<div className="mx_VideoView" ref={this.setContainer}>
|
||||
<div className="mx_VideoView_remoteVideoFeed">
|
||||
<VideoFeed ref="remote"/>
|
||||
<audio ref="remoteAudio"/>
|
||||
</div>
|
||||
<div className="mx_VideoView_localVideoFeed">
|
||||
<VideoFeed ref="local"/>
|
||||
|
35
src/skins/vector/views/organisms/CasLogin.js
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
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 CasLoginController = require('matrix-react-sdk/lib/controllers/organisms/CasLogin');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CasLogin',
|
||||
mixins: [CasLoginController],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.onCasClicked}>Sign in with CAS</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
@ -18,21 +18,37 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'LeftPanel',
|
||||
|
||||
onHideClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'hide_left_panel',
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var RoomList = sdk.getComponent('organisms.RoomList');
|
||||
var BottomLeftMenu = sdk.getComponent('molecules.BottomLeftMenu');
|
||||
var IncomingCallBox = sdk.getComponent('molecules.voip.IncomingCallBox');
|
||||
|
||||
var collapseButton;
|
||||
var classes = "mx_LeftPanel";
|
||||
if (this.props.collapsed) {
|
||||
classes += " collapsed";
|
||||
}
|
||||
else {
|
||||
collapseButton = <img className="mx_LeftPanel_hideButton" onClick={ this.onHideClick } src="img/hide.png" width="12" height="20" alt="<"/>
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="mx_LeftPanel">
|
||||
<img className="mx_LeftPanel_hideButton" src="img/hide.png" width="32" height="32" alt="<"/>
|
||||
<aside className={classes}>
|
||||
{ collapseButton }
|
||||
<IncomingCallBox />
|
||||
<RoomList selectedRoom={this.props.selectedRoom} />
|
||||
<BottomLeftMenu />
|
||||
<RoomList selectedRoom={this.props.selectedRoom} collapsed={this.props.collapsed}/>
|
||||
<BottomLeftMenu collapsed={this.props.collapsed}/>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
var Loader = require('react-loader');
|
||||
|
||||
var MemberListController = require('matrix-react-sdk/lib/controllers/organisms/MemberList')
|
||||
|
||||
@ -32,12 +33,38 @@ module.exports = React.createClass({
|
||||
return { editing: false };
|
||||
},
|
||||
|
||||
makeMemberTiles: function() {
|
||||
memberSort: function(userIdA, userIdB) {
|
||||
var userA = this.memberDict[userIdA].user;
|
||||
var userB = this.memberDict[userIdB].user;
|
||||
|
||||
var presenceMap = {
|
||||
online: 3,
|
||||
unavailable: 2,
|
||||
offline: 1
|
||||
};
|
||||
|
||||
var presenceOrdA = userA ? presenceMap[userA.presence] : 0;
|
||||
var presenceOrdB = userB ? presenceMap[userB.presence] : 0;
|
||||
|
||||
if (presenceOrdA != presenceOrdB) {
|
||||
return presenceOrdB - presenceOrdA;
|
||||
}
|
||||
|
||||
var latA = userA ? (userA.lastPresenceTs - (userA.lastActiveAgo || userA.lastPresenceTs)) : 0;
|
||||
var latB = userB ? (userB.lastPresenceTs - (userB.lastActiveAgo || userB.lastPresenceTs)) : 0;
|
||||
|
||||
return latB - latA;
|
||||
},
|
||||
|
||||
makeMemberTiles: function(membership) {
|
||||
var MemberTile = sdk.getComponent("molecules.MemberTile");
|
||||
|
||||
var self = this;
|
||||
return Object.keys(self.state.memberDict).map(function(userId) {
|
||||
var m = self.state.memberDict[userId];
|
||||
return self.state.members.filter(function(userId) {
|
||||
var m = self.memberDict[userId];
|
||||
return m.membership == membership;
|
||||
}).map(function(userId) {
|
||||
var m = self.memberDict[userId];
|
||||
return (
|
||||
<MemberTile key={userId} member={m} ref={userId} />
|
||||
);
|
||||
@ -69,28 +96,49 @@ module.exports = React.createClass({
|
||||
});
|
||||
|
||||
var EditableText = sdk.getComponent("atoms.EditableText");
|
||||
return (
|
||||
<div className={ classes } onClick={ this.onClickInvite } >
|
||||
<div className="mx_MemberTile_avatar"><img src="img/create-big.png" width="40" height="40" alt=""/></div>
|
||||
<div className="mx_MemberTile_name">
|
||||
<EditableText ref="invite" label="Invite" placeHolder="@user:domain.com" initialValue="" onValueChanged={this.onPopulateInvite}/>
|
||||
if (this.state.inviting) {
|
||||
return (
|
||||
<Loader />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={ classes } onClick={ this.onClickInvite } >
|
||||
<div className="mx_MemberTile_avatar"><img src="img/create-big.png" width="40" height="40" alt=""/></div>
|
||||
<div className="mx_MemberTile_name">
|
||||
<EditableText ref="invite" label="Invite" placeHolder="@user:domain.com" initialValue="" onValueChanged={this.onPopulateInvite}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var invitedSection = null;
|
||||
var invitedMemberTiles = this.makeMemberTiles('invite');
|
||||
if (invitedMemberTiles.length > 0) {
|
||||
invitedSection = (
|
||||
<div>
|
||||
<h2>Invited</h2>
|
||||
<div className="mx_MemberList_wrapper">
|
||||
{invitedMemberTiles}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="mx_MemberList">
|
||||
<div className="mx_MemberList_chevron">
|
||||
<img src="img/chevron.png" width="24" height="13"/>
|
||||
</div>
|
||||
<div className="mx_MemberList_border">
|
||||
<h2>Members</h2>
|
||||
<div className="mx_MemberList_wrapper">
|
||||
{this.makeMemberTiles()}
|
||||
{this.inviteTile()}
|
||||
<div>
|
||||
<h2>Members</h2>
|
||||
<div className="mx_MemberList_wrapper">
|
||||
{this.makeMemberTiles('join')}
|
||||
</div>
|
||||
</div>
|
||||
{invitedSection}
|
||||
{this.inviteTile()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -58,15 +58,16 @@ var NotifierView = {
|
||||
if (ev.getContent().body) msg = ev.getContent().body;
|
||||
}
|
||||
|
||||
var avatarUrl = Avatar.avatarUrlForMember(
|
||||
var avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
|
||||
ev.sender, 40, 40, 'crop'
|
||||
);
|
||||
) : null;
|
||||
|
||||
var notification = new global.Notification(
|
||||
title,
|
||||
{
|
||||
"body": msg,
|
||||
"icon": avatarUrl
|
||||
"icon": avatarUrl,
|
||||
"tag": "vector"
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -18,13 +18,12 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RightPanel',
|
||||
|
||||
Phase : {
|
||||
Blank: 'Blank',
|
||||
None: 'None',
|
||||
MemberList: 'MemberList',
|
||||
FileList: 'FileList',
|
||||
},
|
||||
@ -36,11 +35,16 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
onMemberListButtonClick: function() {
|
||||
if (this.state.phase == this.Phase.None) {
|
||||
this.setState({ phase: this.Phase.MemberList });
|
||||
if (this.props.collapsed) {
|
||||
this.setState({ phase: this.Phase.MemberList });
|
||||
dis.dispatch({
|
||||
action: 'show_right_panel',
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.setState({ phase: this.Phase.None });
|
||||
dis.dispatch({
|
||||
action: 'hide_right_panel',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -48,6 +52,7 @@ module.exports = React.createClass({
|
||||
var MemberList = sdk.getComponent('organisms.MemberList');
|
||||
var buttonGroup;
|
||||
var panel;
|
||||
|
||||
if (this.props.roomId) {
|
||||
buttonGroup =
|
||||
<div className="mx_RightPanel_headerButtonGroup">
|
||||
@ -59,13 +64,18 @@ module.exports = React.createClass({
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
if (this.state.phase == this.Phase.MemberList) {
|
||||
if (!this.props.collapsed && this.state.phase == this.Phase.MemberList) {
|
||||
panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />
|
||||
}
|
||||
}
|
||||
|
||||
var classes = "mx_RightPanel";
|
||||
if (this.props.collapsed) {
|
||||
classes += " collapsed";
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="mx_RightPanel">
|
||||
<aside className={classes}>
|
||||
<div className="mx_RightPanel_header">
|
||||
{ buttonGroup }
|
||||
</div>
|
||||
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
var RoomListController = require('../../../../controllers/organisms/RoomList')
|
||||
|
||||
@ -25,6 +26,12 @@ module.exports = React.createClass({
|
||||
displayName: 'RoomList',
|
||||
mixins: [RoomListController],
|
||||
|
||||
onShowClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'show_left_panel',
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var CallView = sdk.getComponent('molecules.voip.CallView');
|
||||
var RoomDropTarget = sdk.getComponent('molecules.RoomDropTarget');
|
||||
@ -34,13 +41,17 @@ module.exports = React.createClass({
|
||||
callElement = <CallView className="mx_MatrixChat_callView"/>
|
||||
}
|
||||
|
||||
var recentsLabel = this.props.collapsed ?
|
||||
<img style={{cursor: 'pointer'}} onClick={ this.onShowClick } src="img/menu.png" width="27" height="20" alt=">"/> :
|
||||
"Recents";
|
||||
|
||||
return (
|
||||
<div className="mx_RoomList">
|
||||
<div className="mx_RoomList" onScroll={this._repositionTooltip}>
|
||||
{callElement}
|
||||
<h2 className="mx_RoomList_favourites_label">Favourites</h2>
|
||||
<RoomDropTarget text="Drop here to favourite"/>
|
||||
|
||||
<h2 className="mx_RoomList_recents_label">Recents</h2>
|
||||
<h2 className="mx_RoomList_recents_label">{ recentsLabel }</h2>
|
||||
<div className="mx_RoomList_recents">
|
||||
{this.makeRoomTiles()}
|
||||
</div>
|
||||
|
@ -19,12 +19,13 @@ limitations under the License.
|
||||
var React = require('react');
|
||||
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var classNames = require("classnames");
|
||||
var filesize = require('filesize');
|
||||
|
||||
var RoomViewController = require('matrix-react-sdk/lib/controllers/organisms/RoomView')
|
||||
var RoomViewController = require('../../../../controllers/organisms/RoomView')
|
||||
|
||||
var Loader = require("react-loader");
|
||||
|
||||
@ -62,6 +63,14 @@ module.exports = React.createClass({
|
||||
this.setState(this.getInitialState());
|
||||
},
|
||||
|
||||
onConferenceNotificationClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: "video",
|
||||
room_id: this.props.roomId
|
||||
});
|
||||
},
|
||||
|
||||
getUnreadMessagesString: function() {
|
||||
if (!this.state.numUnreadMessages) {
|
||||
return "";
|
||||
@ -129,19 +138,33 @@ module.exports = React.createClass({
|
||||
<div />
|
||||
);
|
||||
|
||||
// for testing UI...
|
||||
// this.state.upload = {
|
||||
// uploadedBytes: 123493,
|
||||
// totalBytes: 347534,
|
||||
// fileName: "testing_fooble.jpg",
|
||||
// }
|
||||
|
||||
if (this.state.upload) {
|
||||
var innerProgressStyle = {
|
||||
width: ((this.state.upload.uploadedBytes / this.state.upload.totalBytes) * 100) + '%'
|
||||
};
|
||||
var uploadedSize = filesize(this.state.upload.uploadedBytes);
|
||||
var totalSize = filesize(this.state.upload.totalBytes);
|
||||
if (uploadedSize.replace(/^.* /,'') === totalSize.replace(/^.* /,'')) {
|
||||
uploadedSize = uploadedSize.replace(/ .*/, '');
|
||||
}
|
||||
statusBar = (
|
||||
<div className="mx_RoomView_uploadBar">
|
||||
<span className="mx_RoomView_uploadFilename">Uploading {this.state.upload.fileName}</span>
|
||||
<span className="mx_RoomView_uploadBytes">
|
||||
{filesize(this.state.upload.uploadedBytes)} / {filesize(this.state.upload.totalBytes)}
|
||||
</span>
|
||||
<div className="mx_RoomView_uploadProgressOuter">
|
||||
<div className="mx_RoomView_uploadProgressInner" style={innerProgressStyle}></div>
|
||||
</div>
|
||||
<img className="mx_RoomView_uploadIcon" src="img/fileicon.png" width="40" height="40"/>
|
||||
<img className="mx_RoomView_uploadCancel" src="img/cancel.png" width="40" height="40"/>
|
||||
<div className="mx_RoomView_uploadBytes">
|
||||
{ uploadedSize } / { totalSize }
|
||||
</div>
|
||||
<div className="mx_RoomView_uploadFilename">Uploading {this.state.upload.fileName}</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
@ -30,7 +30,15 @@ module.exports = React.createClass({
|
||||
editAvatar: function() {
|
||||
var url = MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl);
|
||||
var ChangeAvatar = sdk.getComponent('molecules.ChangeAvatar');
|
||||
Modal.createDialog(ChangeAvatar, {initialAvatarUrl: url});
|
||||
var avatarDialog = (
|
||||
<div>
|
||||
<ChangeAvatar initialAvatarUrl={url} />
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.onAvatarDialogCancel}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
this.avatarDialog = Modal.createDialogWithElement(avatarDialog);
|
||||
},
|
||||
|
||||
addEmail: function() {
|
||||
@ -55,12 +63,16 @@ module.exports = React.createClass({
|
||||
this.logoutModal.closeDialog();
|
||||
},
|
||||
|
||||
onAvatarDialogCancel: function() {
|
||||
this.avatarDialog.close();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
switch (this.state.phase) {
|
||||
case this.Phases.Loading:
|
||||
return <Loader />
|
||||
case this.Phases.Display:
|
||||
var EditableText = sdk.getComponent('atoms.EditableText');
|
||||
var ChangeDisplayName = sdk.getComponent('molecules.ChangeDisplayName');
|
||||
var EnableNotificationsButton = sdk.getComponent('atoms.EnableNotificationsButton');
|
||||
return (
|
||||
<div className="mx_UserSettings">
|
||||
@ -74,13 +86,13 @@ module.exports = React.createClass({
|
||||
</div>
|
||||
|
||||
<div className="mx_UserSettings_DisplayName">
|
||||
<EditableText ref="displayname" initialValue={this.state.displayName} label="Click to set display name." onValueChanged={this.changeDisplayname}/>
|
||||
<ChangeDisplayName ref="displayname" />
|
||||
<div className="mx_UserSettings_DisplayName_Edit" onClick={this.editDisplayName}>Edit</div>
|
||||
</div>
|
||||
|
||||
<div className="mx_UserSettings_3pids">
|
||||
{this.state.threepids.map(function(val) {
|
||||
return <div>{val.address}</div>;
|
||||
return <div key={val.address}>{val.address}</div>;
|
||||
})}
|
||||
</div>
|
||||
|
||||
|
34
src/skins/vector/views/organisms/ViewSource.js
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
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');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ViewSource',
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_ViewSource">
|
||||
<pre>
|
||||
{JSON.stringify(this.props.mxEvent.event, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -25,12 +25,66 @@ var MatrixChatController = require('matrix-react-sdk/lib/controllers/pages/Matri
|
||||
var Loader = require("react-loader");
|
||||
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
var ContextualMenu = require("../../../../ContextualMenu");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MatrixChat',
|
||||
mixins: [MatrixChatController],
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
width: 10000,
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
this.handleResize();
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
},
|
||||
|
||||
onAliasClick: function(event, alias) {
|
||||
event.preventDefault();
|
||||
dis.dispatch({action: 'view_room_alias', room_alias: alias});
|
||||
},
|
||||
|
||||
onUserClick: function(event, userId) {
|
||||
event.preventDefault();
|
||||
var MemberInfo = sdk.getComponent('molecules.MemberInfo');
|
||||
var member = new Matrix.RoomMember(null, userId);
|
||||
ContextualMenu.createMenu(MemberInfo, {
|
||||
member: member,
|
||||
right: window.innerWidth - event.pageX,
|
||||
top: event.pageY
|
||||
});
|
||||
},
|
||||
|
||||
handleResize: function(e) {
|
||||
var hideLhsThreshold = 1000;
|
||||
var showLhsThreshold = 1000;
|
||||
var hideRhsThreshold = 820;
|
||||
var showRhsThreshold = 820;
|
||||
|
||||
if (this.state.width > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) {
|
||||
dis.dispatch({ action: 'hide_left_panel' });
|
||||
}
|
||||
if (this.state.width <= showLhsThreshold && window.innerWidth > showLhsThreshold) {
|
||||
dis.dispatch({ action: 'show_left_panel' });
|
||||
}
|
||||
if (this.state.width > hideRhsThreshold && window.innerWidth <= hideRhsThreshold) {
|
||||
dis.dispatch({ action: 'hide_right_panel' });
|
||||
}
|
||||
if (this.state.width <= showRhsThreshold && window.innerWidth > showRhsThreshold) {
|
||||
dis.dispatch({ action: 'show_right_panel' });
|
||||
}
|
||||
|
||||
this.setState({width: window.innerWidth});
|
||||
},
|
||||
|
||||
onRoomCreated: function(room_id) {
|
||||
dis.dispatch({
|
||||
action: "view_room",
|
||||
@ -57,19 +111,19 @@ module.exports = React.createClass({
|
||||
switch (this.state.page_type) {
|
||||
case this.PageTypes.RoomView:
|
||||
page_element = <RoomView roomId={this.state.currentRoom} key={this.state.currentRoom} />
|
||||
right_panel = <RightPanel roomId={this.state.currentRoom} />
|
||||
right_panel = <RightPanel roomId={this.state.currentRoom} collapsed={this.state.collapse_rhs} />
|
||||
break;
|
||||
case this.PageTypes.UserSettings:
|
||||
page_element = <UserSettings />
|
||||
right_panel = <RightPanel/>
|
||||
right_panel = <RightPanel collapsed={this.state.collapse_rhs}/>
|
||||
break;
|
||||
case this.PageTypes.CreateRoom:
|
||||
page_element = <CreateRoom onRoomCreated={this.onRoomCreated}/>
|
||||
right_panel = <RightPanel/>
|
||||
right_panel = <RightPanel collapsed={this.state.collapse_rhs}/>
|
||||
break;
|
||||
case this.PageTypes.RoomDirectory:
|
||||
page_element = <RoomDirectory />
|
||||
right_panel = <RightPanel/>
|
||||
right_panel = <RightPanel collapsed={this.state.collapse_rhs}/>
|
||||
break;
|
||||
}
|
||||
|
||||
@ -79,7 +133,7 @@ module.exports = React.createClass({
|
||||
<div className="mx_MatrixChat_wrapper">
|
||||
<MatrixToolbar />
|
||||
<div className="mx_MatrixChat mx_MatrixChat_toolbarShowing">
|
||||
<LeftPanel selectedRoom={this.state.currentRoom} />
|
||||
<LeftPanel selectedRoom={this.state.currentRoom} collapsed={this.state.collapse_lhs} />
|
||||
<main className="mx_MatrixChat_middlePanel">
|
||||
{page_element}
|
||||
</main>
|
||||
@ -91,7 +145,7 @@ module.exports = React.createClass({
|
||||
else {
|
||||
return (
|
||||
<div className="mx_MatrixChat">
|
||||
<LeftPanel selectedRoom={this.state.currentRoom} />
|
||||
<LeftPanel selectedRoom={this.state.currentRoom} collapsed={this.state.collapse_lhs} />
|
||||
<main className="mx_MatrixChat_middlePanel">
|
||||
{page_element}
|
||||
</main>
|
||||
|
@ -141,6 +141,11 @@ module.exports = React.createClass({
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
case 'stage_m.login.cas':
|
||||
var CasLogin = sdk.getComponent('organisms.CasLogin');
|
||||
return (
|
||||
<CasLogin />
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -19,15 +19,15 @@ limitations under the License.
|
||||
var React = require('react');
|
||||
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg')
|
||||
|
||||
var Loader = require("react-loader");
|
||||
|
||||
var RegisterController = require('matrix-react-sdk/lib/controllers/templates/Register')
|
||||
var RegisterController = require('../../../../controllers/templates/Register')
|
||||
|
||||
var config = require('../../../../../config.json');
|
||||
|
||||
module.exports = React.createClass({
|
||||
DEFAULT_HS_URL: 'https://matrix.org',
|
||||
DEFAULT_IS_URL: 'https://vector.im',
|
||||
|
||||
displayName: 'Register',
|
||||
mixins: [RegisterController],
|
||||
|
||||
@ -38,8 +38,8 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.customHsUrl = this.DEFAULT_HS_URL;
|
||||
this.customIsUrl = this.DEFAULT_IS_URL;
|
||||
this.customHsUrl = config.default_hs_url;
|
||||
this.customIsUrl = config.default_is_url;
|
||||
},
|
||||
|
||||
getRegFormVals: function() {
|
||||
@ -55,7 +55,7 @@ module.exports = React.createClass({
|
||||
if (this.state.serverConfigVisible) {
|
||||
return this.customHsUrl;
|
||||
} else {
|
||||
return this.DEFAULT_HS_URL;
|
||||
return config.default_hs_url;
|
||||
}
|
||||
},
|
||||
|
||||
@ -63,7 +63,7 @@ module.exports = React.createClass({
|
||||
if (this.state.serverConfigVisible) {
|
||||
return this.customIsUrl;
|
||||
} else {
|
||||
return this.DEFAULT_IS_URL;
|
||||
return config.default_is_url;
|
||||
}
|
||||
},
|
||||
|
||||
@ -79,6 +79,10 @@ module.exports = React.createClass({
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
onProfileContinueClicked: function() {
|
||||
this.onAccountReady();
|
||||
},
|
||||
|
||||
componentForStep: function(step) {
|
||||
switch (step) {
|
||||
case 'initial':
|
||||
@ -127,6 +131,18 @@ module.exports = React.createClass({
|
||||
return (
|
||||
<Loader />
|
||||
);
|
||||
} else if (this.state.step == 'profile') {
|
||||
var ChangeDisplayName = sdk.getComponent('molecules.ChangeDisplayName');
|
||||
var ChangeAvatar = sdk.getComponent('molecules.ChangeAvatar');
|
||||
return (
|
||||
<div className="mx_Login_profile">
|
||||
Set a display name:
|
||||
<ChangeDisplayName />
|
||||
Upload an avatar:
|
||||
<ChangeAvatar initialAvatarUrl={MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl)} />
|
||||
<button onClick={this.onProfileContinueClicked}>Continue</button>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
@ -156,6 +172,12 @@ module.exports = React.createClass({
|
||||
case this.FieldErrors.InUse:
|
||||
strings.push(keys[i]+" is already taken");
|
||||
break;
|
||||
case this.FieldErrors.Length:
|
||||
strings.push(keys[i] + " is not long enough.");
|
||||
break;
|
||||
default:
|
||||
console.error("Unhandled FieldError: %s", bad[keys[i]]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var errtxt = strings.join(', ');
|
||||
|
@ -21,24 +21,29 @@ var sdk = require("matrix-react-sdk");
|
||||
sdk.loadSkin(require('../skins/vector/skindex'));
|
||||
sdk.loadModule(require('../modules/VectorConferenceHandler'));
|
||||
|
||||
var qs = require("querystring");
|
||||
|
||||
var lastLocationHashSet = null;
|
||||
|
||||
|
||||
// We want to support some name / value pairs in the fragment
|
||||
// so we're re-using query string like format
|
||||
function parseQsFromFragment(location) {
|
||||
var hashparts = location.hash.split('?');
|
||||
if (hashparts.length > 1) {
|
||||
return qs.parse(hashparts[1]);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// 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 hashparts = location.hash.split('?');
|
||||
var params = {};
|
||||
if (hashparts.length == 2) {
|
||||
var pairs = hashparts[1].split('&');
|
||||
for (var i = 0; i < pairs.length; ++i) {
|
||||
var parts = pairs[i].split('=');
|
||||
if (parts.length != 2) continue;
|
||||
params[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]);
|
||||
}
|
||||
}
|
||||
window.matrixChat.showScreen('register', params);
|
||||
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));
|
||||
}
|
||||
@ -53,14 +58,18 @@ function onHashChange(ev) {
|
||||
}
|
||||
|
||||
var loaded = false;
|
||||
var lastLoadedScreen = null;
|
||||
|
||||
// This will be called whenever the SDK changes screens,
|
||||
// so a web page can update the URL bar appropriately.
|
||||
var onNewScreen = function(screen) {
|
||||
if (!loaded) return;
|
||||
var hash = '#/' + screen;
|
||||
lastLocationHashSet = hash;
|
||||
window.location.hash = hash;
|
||||
if (!loaded) {
|
||||
lastLoadedScreen = screen;
|
||||
} else {
|
||||
var hash = '#/' + screen;
|
||||
lastLocationHashSet = hash;
|
||||
window.location.hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
// We use this to work out what URL the SDK should
|
||||
@ -85,5 +94,9 @@ window.addEventListener('hashchange', onHashChange);
|
||||
window.onload = function() {
|
||||
routeUrl(window.location);
|
||||
loaded = true;
|
||||
if (lastLoadedScreen) {
|
||||
onNewScreen(lastLoadedScreen);
|
||||
lastLoadedScreen = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,19 @@ module.exports = {
|
||||
{ test: /\.js$/, loader: "babel", include: path.resolve('./src') },
|
||||
]
|
||||
},
|
||||
output: {
|
||||
devtoolModuleFilenameTemplate: function(info) {
|
||||
// Reading input source maps gives only relative paths here for
|
||||
// everything. Until I figure out how to fix this, this is a
|
||||
// workaround.
|
||||
// We use the relative resource path with any '../'s on the front
|
||||
// removed which gives a tree with matrix-react-sdk and vector
|
||||
// trees smashed together, but this fixes everything being under
|
||||
// various levels of '.' and '..'
|
||||
// Also, sometimes the resource path is absolute.
|
||||
return path.relative(process.cwd(), info.resourcePath).replace(/^[\/\.]*/, '');
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
// alias any requires to the react module to the one in our path, otherwise
|
||||
@ -19,7 +32,12 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new webpack.IgnorePlugin(/^olm/)
|
||||
new webpack.IgnorePlugin(/^olm/),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
|
||||
}
|
||||
})
|
||||
],
|
||||
devtool: 'source-map'
|
||||
};
|
||||
|