Compare commits

..

47 Commits

Author SHA1 Message Date
David Baker
3db86b1f59 changelog 2015-10-28 18:37:53 +00:00
David Baker
5c77395faa v0.1.2 2015-10-28 18:32:13 +00:00
David Baker
74afa710d1 Merge branch 'develop' 2015-10-28 18:27:33 +00:00
David Baker
e48e636c44 Bump js-sdk & react-sdk deps 2015-10-28 18:27:09 +00:00
David Baker
4118c05d15 Unused variables 2015-10-28 16:23:48 +00:00
David Baker
cee37c4152 Port react-sdk 2365fe8c over to vector's fork of the roomlist controller 2015-10-28 15:17:03 +00:00
Kegsay
4175dcd102 Merge pull request #260 from vector-im/inbound-audio
Fix inbound audio
2015-10-28 11:43:52 +00:00
Kegan Dougal
35862e0c66 Explicitly make React use the same audio element.. Use a 'key' and comment why it is required. 2015-10-28 11:43:13 +00:00
Kegsay
424d1b84db Merge pull request #264 from vector-im/246-start-chat-ui-feedback
Show a spinner if creating a room on "Start chat" click
2015-10-28 11:38:52 +00:00
Kegsay
3423a493e7 Merge pull request #265 from vector-im/230-exit-full-screen-voip
Exit full screen programatically when the remote side hangs up
2015-10-28 11:36:18 +00:00
Kegan Dougal
731ad26be4 Exit full screen programatically when the remote side hangs up 2015-10-27 17:45:47 +00:00
Kegan Dougal
9dfd0bc3bb Show a spinner if creating a room on "Start chat" click
Use a gif instead of 'orrible CSS spinners which are CPU hungry. Encapsulate
it in a very basic Spinner atom.
2015-10-27 16:56:37 +00:00
Kegan Dougal
05dba9c2d4 Fix inbound audio
This was caused by an <img> being the first thing in the <div> rather than
the <audio>. This caused a conflict because the "not in call" render was just
<div><audio /></div> and "in call" render was <div><img /> <audio /></div>

React can't tell in this case that the <audio> tags are the "same" so was
clobbering it (which meant that on inbound calls we would call play() on an
audio tag which would then immediately be clobbered by another audio tag).
2015-10-27 12:59:04 +00:00
Kegsay
ed52bc37b2 Merge pull request #259 from vector-im/linkify-userids
Add linkify handlers
2015-10-27 12:25:26 +00:00
Kegan Dougal
99e8a54a27 Add linkify handlers 2015-10-27 10:46:18 +00:00
Kegsay
9455d02d50 Merge pull request #237 from vector-im/kegan/delete-empty-files
Remove empty files
2015-10-27 10:19:05 +00:00
Kegan Dougal
c98f7f926a Remove stuff that was merged from working space 2015-10-27 10:18:43 +00:00
Kegan Dougal
c91b642a8b Merge branch 'develop' into kegan/delete-empty-files
Conflicts:
	src/skins/vector/views/molecules/EventAsTextTile.js
2015-10-27 09:19:08 +00:00
Kegsay
ce33c8cdf6 Merge pull request #258 from vector-im/desktop-notification-spam
Add a tag to notifications so they can clobber.
2015-10-27 09:17:45 +00:00
Kegan Dougal
58bac0fbdc Add a tag to notifications so they can clobber. Fixes #159.
This is the same fix we applied to angular to fix this problem in SYWEB-21.
2015-10-26 16:56:44 +00:00
David Baker
9217ae8fbb Fix event listener leak 2015-10-26 10:30:12 +00:00
Matthew Hodgson
e580cb809d spell out how to set up the deps when developing 2015-10-25 12:33:23 +00:00
Matthew Hodgson
5844fb4020 spell out that developers need to use npm link 2015-10-25 11:56:29 +00:00
Matthew Hodgson
8257f325c4 s/getMembersWithMemership/getMembersWithMembership/ 2015-10-25 11:51:17 +00:00
Matthew Hodgson
379fed813e actually use the config file for default HS and IS URLs... 2015-10-25 02:44:57 +00:00
Matthew Hodgson
1188c4c69f Merge pull request #243 from vector-im/kegan/reg-errors-176
Fix #176 Password complexity error message
2015-10-25 02:12:21 +00:00
Matthew Hodgson
88dd135b5a Merge pull request #241 from vector-im/kegan/redact-messages
Hook up delete button on contextual menu (#56)
2015-10-25 02:12:11 +00:00
David Baker
5392afdec4 Add UI for changing room avatars and update UI when room avatars change 2015-10-23 17:36:02 +01:00
David Baker
e844b7aa21 UI to set Room Avatars 2015-10-23 13:47:32 +01:00
David Baker
d66427ddde Relative-ize paths in webpack config: they're supposed to be relative but sometimes they aren't. 2015-10-23 10:16:00 +01:00
David Baker
f618585bd6 Merge branch 'develop' of github.com:vector-im/vector-web into develop 2015-10-23 09:25:15 +01:00
David Baker
7c6fb36520 Let the tile contents specify whether it requires a sender profile or not. Fixes #250. 2015-10-23 09:24:25 +01:00
Matthew Hodgson
351a94b4a1 oops - wrong branch
Revert "WIP of new CSS"

This reverts commit ad4e3418ff.
2015-10-23 02:38:32 +01:00
Matthew Hodgson
ad4e3418ff WIP of new CSS 2015-10-23 02:32:49 +01:00
Matthew Hodgson
82affac438 oops, typo 2015-10-22 16:33:53 +01:00
David Baker
08270b26ee Do room avatars properly. 2015-10-22 13:10:02 +01:00
David Baker
4b645bcd66 Make context menus point the right way 2015-10-21 19:02:02 +01:00
David Baker
1f3a6e408c Factor out stuff commnon to all timeline events into EventTile: makes timestamp & edit button etc appear on everything, not just messages. 2015-10-21 17:52:34 +01:00
Kegan Dougal
3779ff7691 Handle Length case. Add default case and whine loudly. 2015-10-21 17:45:53 +01:00
Kegan Dougal
3d3680e42f NPE fix when accessing event.sender 2015-10-21 16:11:47 +01:00
Kegan Dougal
af67df4c4a Hook up delete button on contextual menu (#56) 2015-10-21 15:52:35 +01:00
Matthew Hodgson
bf40011815 prettier file upload 2015-10-21 15:30:18 +01:00
Kegan Dougal
a9b093b7f5 Remove empty controllers 2015-10-21 15:30:04 +01:00
Kegan Dougal
5e7bd1e51f Remove empty files 2015-10-21 15:17:03 +01:00
David Baker
044c75270f This has changed in newer react sdk but in master, it should be state.memberDict 2015-10-20 12:41:16 +01:00
David Baker
209889210b Remove unnececessary and wrong null check 2015-10-20 12:36:10 +01:00
David Baker
93d81d27ba fix memberDict reference 2015-10-20 12:32:40 +01:00
39 changed files with 432 additions and 196 deletions

View File

@@ -1,3 +1,14 @@
Changes in vector v0.1.2 (2015-10-28)
======================================
* Support Room Avatars
* Fullscreen video calls
* Mute mic in VoIP calls
* Fix bug with multiple desktop notifications
* Context menu on messages
* Better hover-over on member list
* Support CAS auth
* Many other bug fixes
Changes in vector v0.1.1 (2015-08-10)
======================================

View File

@@ -22,14 +22,27 @@ 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`.

View File

@@ -1,6 +1,6 @@
{
"name": "vector-web",
"version": "0.0.1",
"version": "0.1.2",
"description": "Vector webapp",
"author": "matrix.org",
"repository": {
@@ -27,8 +27,8 @@
"filesize": "^3.1.2",
"flux": "~2.0.3",
"linkifyjs": "^2.0.0-beta.4",
"matrix-js-sdk": "^0.2.2",
"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

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

@@ -90,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({
@@ -66,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);
}
},
@@ -110,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({

View File

@@ -63,6 +63,7 @@ module.exports = {
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);
}
},
@@ -356,23 +357,20 @@ module.exports = {
},
getEventTiles: function() {
var tileTypes = {
'm.room.message': sdk.getComponent('molecules.MessageTile'),
'm.room.member' : sdk.getComponent('molecules.EventAsTextTile'),
'm.call.invite' : sdk.getComponent('molecules.EventAsTextTile'),
'm.call.answer' : sdk.getComponent('molecules.EventAsTextTile'),
'm.call.hangup' : sdk.getComponent('molecules.EventAsTextTile'),
'm.room.topic' : sdk.getComponent('molecules.EventAsTextTile'),
};
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];
var TileType = tileTypes[mxEv.getType()];
if (!EventTile.supportsEventType(mxEv.getType())) {
continue;
}
var continuation = false;
var last = false;
var dateSeparator = null;
@@ -401,13 +399,12 @@ module.exports = {
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 = <DateSeparator key={ts1} ts={ts1}/>;
dateSeparator = <li key={ts1}><DateSeparator ts={ts1}/></li>;
continuation = false;
}
if (!TileType) continue;
ret.unshift(
<li key={mxEv.getId()}><TileType mxEvent={mxEv} continuation={continuation} last={last}/></li>
<li key={mxEv.getId()}><EventTile mxEvent={mxEv} continuation={continuation} last={last}/></li>
);
if (dateSeparator) {
ret.unshift(dateSeparator);

View File

@@ -67,18 +67,29 @@ a:visited {
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

@@ -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 {
.mx_EventTile_highlight {
color: #FF0064;
}
.mx_MessageTile_msgOption {
.mx_EventTile_msgOption {
float: right;
}
@@ -83,29 +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_MessageTile_editButton {
.mx_EventTile_editButton {
float: right;
display: none;
border: 0px;
outline: none;
margin-right: 3px;
}
.mx_MessageTile:hover .mx_MessageTile_editButton {
.mx_EventTile:hover .mx_EventTile_editButton {
display: inline-block;
}
.mx_MessageTile.menu .mx_MessageTile_editButton {
.mx_EventTile.menu .mx_EventTile_editButton {
display: inline-block;
}
.mx_MessageTile.menu .mx_MessageTimestamp {
.mx_EventTile.menu .mx_MessageTimestamp {
display: inline-block;
}

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: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -41,6 +41,7 @@ skin['molecules.ChangeDisplayName'] = require('./views/molecules/ChangeDisplayNa
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');

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,14 +18,11 @@ 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.
@@ -45,7 +42,7 @@ module.exports = React.createClass({
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());
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
}
},

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

@@ -16,5 +16,19 @@ limitations under the License.
'use strict';
module.exports = {
};
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

@@ -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,13 +40,27 @@ 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:

View File

@@ -18,37 +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 = <MessageTimestamp ts={this.props.mxEvent.getTs()} />;
var avatar = this.props.mxEvent.sender ? <MemberAvatar member={this.props.mxEvent.sender} /> : null;
var cssStyles = "mx_MessageTile mx_MessageTile_notice";
if (this.props.last) {
cssStyles += " mx_MessageTile_last";
}
return (
<div className={cssStyles}>
<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

@@ -52,9 +52,27 @@ module.exports = React.createClass({
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 = (
@@ -63,6 +81,13 @@ module.exports = React.createClass({
</div>
);
}
else {
redactButton = (
<div className="mx_ContextualMenu_field" onClick={this.onRedactClick}>
Delete
</div>
);
}
viewSourceButton = (
<div className="mx_ContextualMenu_field" onClick={this.onViewSourceClick}>
View Source
@@ -72,6 +97,7 @@ module.exports = React.createClass({
return (
<div>
{resendButton}
{redactButton}
{viewSourceButton}
</div>
);

View File

@@ -17,39 +17,22 @@ limitations under the License.
'use strict';
var React = require('react');
var classNames = require("classnames");
var sdk = require('matrix-react-sdk')
var MessageTileController = require('matrix-react-sdk/lib/controllers/molecules/MessageTile')
var ContextualMenu = require('../../../../ContextualMenu');
module.exports = React.createClass({
displayName: 'MessageTile',
mixins: [MessageTileController],
onEditClicked: function(e) {
var MessageContextMenu = sdk.getComponent('molecules.MessageContextMenu');
var buttonRect = e.target.getBoundingClientRect()
var x = window.innerWidth - buttonRect.left;
var y = buttonRect.top + (e.target.height / 2);
var self = this;
ContextualMenu.createMenu(MessageContextMenu, {
mxEvent: this.props.mxEvent,
right: x,
top: y,
onFinished: function() {
self.setState({menu: false});
}
});
this.setState({menu: true});
statics: {
needsSenderProfile: function() {
return true;
}
},
render: function() {
var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp');
var SenderProfile = sdk.getComponent('molecules.SenderProfile');
var MemberAvatar = sdk.getComponent('atoms.MemberAvatar');
var UnknownMessageTile = sdk.getComponent('molecules.UnknownMessageTile');
var tileTypes = {
@@ -66,56 +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,
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_MessageTile_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, 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 }
{ sender }
<div>
{ timestamp }
{ editButton }
<TileType mxEvent={this.props.mxEvent} />
</div>
</div>
);
return <TileType mxEvent={this.props.mxEvent} />;
},
});

View File

@@ -39,7 +39,7 @@ module.exports = React.createClass({
},
onFullscreenClick: function() {
dis.dispatch({action: 'video_fullscreen'}, true);
dis.dispatch({action: 'video_fullscreen', fullscreen: true}, true);
},
render: function() {
@@ -61,7 +61,7 @@ module.exports = React.createClass({
var call_buttons;
var zoom_button;
if (this.state && this.state.call_state != 'ended') {
var muteVideoButton;
//var muteVideoButton;
var activeCall = (
CallHandler.getCallForRoom(this.props.room.roomId)
);

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

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

@@ -20,11 +20,9 @@ var React = require('react');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher')
var VideoViewController = require('matrix-react-sdk/lib/controllers/molecules/voip/VideoView')
module.exports = React.createClass({
displayName: 'VideoView',
mixins: [VideoViewController],
componentWillMount: function() {
dis.register(this.onAction);
@@ -53,8 +51,26 @@ module.exports = React.createClass({
return;
}
var element = this.container.getDOMNode();
var requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullscreen;
requestMethod.call(element);
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;
}
},

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

@@ -149,6 +149,11 @@ module.exports = React.createClass({
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">
<div className="mx_RoomView_uploadProgressOuter">
@@ -157,7 +162,7 @@ module.exports = React.createClass({
<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">
{filesize(this.state.upload.uploadedBytes).replace(/ .*/, '')} / {filesize(this.state.upload.totalBytes)}
{ uploadedSize } / { totalSize }
</div>
<div className="mx_RoomView_uploadFilename">Uploading {this.state.upload.fileName}</div>
</div>

View File

@@ -25,7 +25,8 @@ 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',
@@ -46,6 +47,22 @@ module.exports = React.createClass({
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;

View File

@@ -25,10 +25,9 @@ var Loader = require("react-loader");
var RegisterController = require('../../../../controllers/templates/Register')
module.exports = React.createClass({
DEFAULT_HS_URL: 'https://matrix.org',
DEFAULT_IS_URL: 'https://vector.im',
var config = require('../../../../../config.json');
module.exports = React.createClass({
displayName: 'Register',
mixins: [RegisterController],
@@ -39,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() {
@@ -56,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;
}
},
@@ -64,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;
}
},
@@ -173,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

@@ -20,7 +20,8 @@ module.exports = {
// removed which gives a tree with matrix-react-sdk and vector
// trees smashed together, but this fixes everything being under
// various levels of '.' and '..'
return info.resourcePath.replace(/^[\/\.]*/, '');
// Also, sometimes the resource path is absolute.
return path.relative(process.cwd(), info.resourcePath).replace(/^[\/\.]*/, '');
}
},
resolve: {