Merge branch 'develop'

pull/446/merge
David Baker 2015-10-28 18:27:33 +00:00
commit 74afa710d1
75 changed files with 1899 additions and 278 deletions

View File

@ -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
==========

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

View 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,
});
}
}
};

View 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;

View File

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

View File

@ -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,

View File

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

View File

@ -15,7 +15,6 @@ limitations under the License.
*/
.mx_MatrixToolbar {
width: 100%;
text-align: center;
background-color: #ff0064;
color: #fff;

View File

@ -128,3 +128,7 @@ limitations under the License.
.mx_MemberTile_zalgo {
font-family: Helvetica, Arial, Sans-Serif;
}
.mx_MemberTile:hover .mx_MessageTimestamp {
display: block;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,4 +27,5 @@ limitations under the License.
.mx_RoomList h2 {
padding-left: 16px;
padding-right: 16px;
padding-bottom: 10px;
}

View File

@ -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 {

View File

@ -0,0 +1,3 @@
.mx_ViewSource pre {
text-align: left;
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 B

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 B

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/skins/vector/img/menu.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 B

After

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

View File

@ -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() {

View File

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

View File

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

View 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>
);
}
});

View File

@ -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 (

View File

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

View 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>
);
}
});

View File

@ -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:

View 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}/>
);
}
}
});

View File

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

View 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>
);
},
});

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View 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>
);
}
});

View File

@ -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} />;
},
});

View File

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

View File

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

View File

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

View 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>
);
}
});

View File

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

View File

@ -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();

View File

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

View File

@ -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"/>

View 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>
);
},
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {

View File

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

View 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>
);
}
});

View File

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

View File

@ -141,6 +141,11 @@ module.exports = React.createClass({
</form>
</div>
);
case 'stage_m.login.cas':
var CasLogin = sdk.getComponent('organisms.CasLogin');
return (
<CasLogin />
);
}
},

View File

@ -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(', ');

View File

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

View File

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