diff --git a/webclient/app-controller.js b/webclient/app-controller.js
index 80474bb8df..172770f82f 100644
--- a/webclient/app-controller.js
+++ b/webclient/app-controller.js
@@ -37,6 +37,8 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
mPresence.start();
}
+ $scope.user_id = matrixService.config().user_id;
+
/**
* Open a given page.
* @param {String} url url of the page
@@ -45,6 +47,16 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
$location.url(url);
};
+ // Open the given user profile page
+ $scope.goToUserPage = function(user_id) {
+ if (user_id === $scope.user_id) {
+ $location.url("/settings");
+ }
+ else {
+ $location.url("/user/" + user_id);
+ }
+ };
+
// Logs the user out
$scope.logout = function() {
@@ -69,11 +81,8 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even
$scope.logout();
});
- $scope.requestNotifications = function() {
- if (window.Notification) {
- console.log("Notification.permission: " + window.Notification.permission);
- window.Notification.requestPermission(function(){});
- }
+ $scope.updateHeader = function() {
+ $scope.user_id = matrixService.config().user_id;
};
}]);
diff --git a/webclient/app-directive.js b/webclient/app-directive.js
index 01f60fdadf..eee0d3842f 100644
--- a/webclient/app-directive.js
+++ b/webclient/app-directive.js
@@ -32,7 +32,12 @@ angular.module('matrixWebClient')
.directive('ngFocus', ['$timeout', function($timeout) {
return {
link: function(scope, element, attr) {
- $timeout(function() { element[0].focus(); }, 0);
+ // XXX: slightly evil hack to disable autofocus on iOS, as in general
+ // it causes more problems than it fixes, by bouncing the page
+ // around
+ if (!/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) {
+ $timeout(function() { element[0].focus(); }, 0);
+ }
}
};
}]);
\ No newline at end of file
diff --git a/webclient/app.css b/webclient/app.css
index 16f9dd72b7..cd1820e155 100644..100755
--- a/webclient/app.css
+++ b/webclient/app.css
@@ -1,121 +1,194 @@
-/*** Mobile voodoo ***/
-@media all and (max-device-width: 640px) {
-
- #messageTableWrapper {
- margin-right: 0px ! important;
- }
-
- .leftBlock {
- width: 8em ! important;
- font-size: 8px ! important;
- }
-
- .rightBlock {
- width: 0px ! important;
- display: none ! important;
- }
-
- .avatar {
- width: 36px ! important;
- }
-
- #header,
- #messageTable,
- #wrapper,
- #roomName,
- #controls {
- max-width: 640px ! important;
- }
-
- #userIdCell,
- #usersTableWrapper,
- #extraControls {
- display: none;
- }
-
- #buttonsCell {
- width: 60px ! important;
- padding-left: 20px ! important;
- }
-
- #roomLogo {
- display: none;
- }
-
- #roomName {
- text-align: left ! important;
- top: -35px ! important;
- }
-
- .bubble {
- font-size: 12px ! important;
- min-height: 20px ! important;
- }
-
- #page {
- top: 35px ! important;
- bottom: 70px ! important;
- }
-
- #header,
- #page {
- margin: 5px ! important;
- }
-
- #header {
- padding: 5px ! important;
- }
-
- /* stop zoom on select */
- select:focus,
- textarea,
- input
- {
- font-size: 16px ! important;
- }
-
+/** Common layout **/
+
+html {
+ height: 100%;
}
body {
+ height: 100%;
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
font-size: 12pt;
margin: 0px;
}
h1 {
- font-family: Helvetica, Arial, sans-serif;
+ font-size: 20pt;
}
-/*** Overall page layout ***/
+a:link { color: #666; }
+a:visited { color: #666; }
+a:hover { color: #000; }
+a:active { color: #000; }
#page {
- position: absolute;
- top: 80px;
- bottom: 100px;
- left: 0px;
- right: 0px;
- margin: 20px;
+ min-height: 100%;
+ margin-bottom: -32px; /* to make room for the footer */
}
#wrapper {
margin: auto;
max-width: 1280px;
- height: 100%;
+ padding-top: 40px;
+ padding-bottom: 40px;
+ padding-left: 20px;
+ padding-right: 20px;
}
-#roomName {
+#header
+{
+ position: absolute;
+ top: 0px;
+ width: 100%;
+ background-color: #333;
+ height: 32px;
+}
+
+#headerContent {
+ color: #ccc;
max-width: 1280px;
+ margin: auto;
+ text-align: right;
+ height: 32px;
+ line-height: 32px;
+}
+
+#headerContent a:link,
+#headerContent a:visited,
+#headerContent a:hover,
+#headerContent a:active {
+ color: #fff;
+}
+
+#footer
+{
width: 100%;
+ border-top: #666 1px solid;
+ background-color: #aaa;
+ height: 32px;
+}
+
+#footerContent
+{
+ font-size: 8pt;
+ color: #fff;
+ max-width: 1280px;
+ margin: auto;
+ text-align: center;
+ height: 32px;
+ line-height: 32px;
+}
+
+#genericHeading
+{
+ margin-top: 13px;
+}
+
+#feedback {
+ color: #800;
+}
+
+.mouse-pointer {
+ cursor: pointer;
+}
+
+.invited {
+ opacity: 0.2;
+}
+
+/*** Login Pages ***/
+
+.loginWrapper {
+ text-align: center;
+}
+
+#loginForm {
+ text-align: left;
+ padding: 1em;
+ margin-bottom: 40px;
+ display: inline-block;
+
+ -webkit-border-radius: 10px;
+ -moz-border-radius: 10px;
+ border-radius: 10px;
+
+ -webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
+ -moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
+ box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
+
+ background-color: #f8f8f8;
+ border: 1px #ccc solid;
+}
+
+#loginForm input[type='radio'] {
+ margin-right: 1em;
+}
+
+#serverConfig {
+ text-align: center;
+}
+
+#serverConfig,
+#serverConfig input,
+#serverConfig button
+{
+ font-size: 10pt ! important;
+}
+
+.smallPrint {
+ color: #888;
+ font-size: 9pt ! important;
+ font-style: italic ! important;
+}
+
+#serverConfig label {
+ display: inline-block;
text-align: right;
- top: -40px;
+ margin-right: 0.5em;
+ width: 7em;
+}
+
+#loginForm,
+#loginForm input,
+#loginForm button,
+#loginForm select {
+ font-size: 18px;
+}
+
+/*** Room page ***/
+
+#roomPage {
position: absolute;
+ top: 120px;
+ bottom: 120px;
+ left: 20px;
+ right: 20px;
+}
+
+#roomWrapper {
+ margin: auto;
+ max-width: 1280px;
+ height: 100%;
+}
+
+#roomName {
+ float: right;
font-size: 16px;
+ margin-top: 15px;
+}
+
+#roomHeader {
+ margin: auto;
+ padding-left: 20px;
+ padding-right: 20px;
+ padding-top: 53px;
+ max-width: 1280px;
}
#controlPanel {
position: absolute;
bottom: 0px;
width: 100%;
+ height: 100px;
background-color: #f8f8f8;
border-top: #aaa 1px solid;
}
@@ -146,10 +219,6 @@ h1 {
background-color: #faa;
}
-.mouse-pointer {
- cursor: pointer;
-}
-
/*** Participant list ***/
#usersTableWrapper {
@@ -300,7 +369,7 @@ h1 {
display: inline-block;
margin-bottom: -1px;
max-width: 90%;
- font-size: 16px;
+ font-size: 14px;
word-wrap: break-word;
padding-top: 7px;
padding-bottom: 5px;
@@ -310,6 +379,11 @@ h1 {
-webkit-text-size-adjust:100%
}
+.bubble img {
+ max-width: 100%;
+ max-height: auto;
+}
+
.differentUser td {
padding-bottom: 5px ! important;
}
@@ -341,8 +415,8 @@ h1 {
}
#room-fullscreen-image img {
- max-width: 100%;
- max-height: 100%;
+ max-width: 90%;
+ max-height: 90%;
bottom: 0;
left: 0;
margin: auto;
@@ -350,9 +424,14 @@ h1 {
position: fixed;
right: 0;
top: 0;
+
+ -webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.75);
+ -moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.75);
+ box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.75);
}
/*** Recents ***/
+
.recentsTable {
max-width: 480px;
width: 100%;
@@ -402,11 +481,14 @@ h1 {
}
/*** Recents in the room page ***/
+
#roomRecentsTableWrapper {
float: left;
max-width: 320px;
- margin-right: 20px;
+ padding-right: 10px;
+ margin-right: 10px;
height: 100%;
+ border-right: 1px solid #ddd;
overflow-y: auto;
}
@@ -421,55 +503,14 @@ h1 {
}
.profile-avatar img {
- max-width: 100%;
- max-height: 100%;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
}
/*** User profile page ***/
-#user-ids {
- padding-left: 1em;
-}
#user-displayname {
font-size: 24px;
}
-/******************************/
-#header
-{
- padding: 20px;
- max-width: 1280px;
- margin: auto;
-}
-
-#logo,
-#roomLogo {
- max-width: 1280px;
- margin: auto;
-}
-
-#header-buttons {
- float: right;
-}
-
-.text_entry_section {
- position: fixed;
- bottom: 0;
- z-index: 100;
- left: 0;
- right: 10em;
- width: 100%;
- background: #e0e0e0;
-}
-
-.member_invited {
- color: blue;
-}
-
-.member_joined {
-
-}
-
-.member_left {
- color: gray;
-}
diff --git a/webclient/app.js b/webclient/app.js
index 02695c3ae6..9663ddf967 100644
--- a/webclient/app.js
+++ b/webclient/app.js
@@ -18,6 +18,7 @@ var matrixWebClient = angular.module('matrixWebClient', [
'ngRoute',
'MatrixWebClientController',
'LoginController',
+ 'RegisterController',
'RoomController',
'HomeController',
'RecentsController',
@@ -38,6 +39,10 @@ matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
templateUrl: 'login/login.html',
controller: 'LoginController'
}).
+ when('/register', {
+ templateUrl: 'login/register.html',
+ controller: 'RegisterController'
+ }).
when('/room/:room_id_or_alias', {
templateUrl: 'room/room.html',
controller: 'RoomController'
@@ -84,7 +89,10 @@ matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
matrixWebClient.run(['$location', 'matrixService', function($location, matrixService) {
// If user auth details are not in cache, go to the login page
- if (!matrixService.isUserLoggedIn()) {
+ if (!matrixService.isUserLoggedIn() &&
+ $location.path() !== "/login" &&
+ $location.path() !== "/register")
+ {
$location.path("login");
}
diff --git a/webclient/components/fileUpload/file-upload-service.js b/webclient/components/fileUpload/file-upload-service.js
index 5f01478fd1..699a3cbffc 100644
--- a/webclient/components/fileUpload/file-upload-service.js
+++ b/webclient/components/fileUpload/file-upload-service.js
@@ -30,7 +30,7 @@ angular.module('mFileUpload', ['matrixService', 'mUtilities'])
*/
this.uploadFile = function(file) {
var deferred = $q.defer();
- console.log("Uploading " + file.name + "... to /matrix/content");
+ console.log("Uploading " + file.name + "... to /_matrix/content");
matrixService.uploadContent(file).then(
function(response) {
var content_url = response.data.content_token;
diff --git a/webclient/components/matrix/matrix-call.js b/webclient/components/matrix/matrix-call.js
index 45d00ee792..47b63d7f2f 100644
--- a/webclient/components/matrix/matrix-call.js
+++ b/webclient/components/matrix/matrix-call.js
@@ -36,7 +36,7 @@ var forAllTracksOnStream = function(s, f) {
}
angular.module('MatrixCall', [])
-.factory('MatrixCall', ['matrixService', 'matrixPhoneService', function MatrixCallFactory(matrixService, matrixPhoneService) {
+.factory('MatrixCall', ['matrixService', 'matrixPhoneService', '$rootScope', function MatrixCallFactory(matrixService, matrixPhoneService, $rootScope) {
var MatrixCall = function(room_id) {
this.room_id = room_id;
this.call_id = "c" + new Date().getTime();
@@ -73,9 +73,7 @@ angular.module('MatrixCall', [])
this.state = 'wait_local_media';
};
- MatrixCall.prototype.hangup = function() {
- console.trace("Ending call "+this.call_id);
-
+ MatrixCall.prototype.stopAllMedia = function() {
if (this.localAVStream) {
forAllTracksOnStream(this.localAVStream, function(t) {
t.stop();
@@ -86,6 +84,12 @@ angular.module('MatrixCall', [])
t.stop();
});
}
+ };
+
+ MatrixCall.prototype.hangup = function() {
+ console.trace("Ending call "+this.call_id);
+
+ this.stopAllMedia();
var content = {
version: 0,
@@ -204,6 +208,7 @@ angular.module('MatrixCall', [])
// ideally we'd consider the call to be connected when we get media but chrome doesn't implement nay of the 'onstarted' events yet
if (this.peerConn.iceConnectionState == 'completed' || this.peerConn.iceConnectionState == 'connected') {
this.state = 'connected';
+ $rootScope.$apply();
}
};
@@ -232,8 +237,9 @@ angular.module('MatrixCall', [])
t.onstarted = self.onRemoteStreamTrackStarted;
});
+ event.stream.onended = function(e) { self.onRemoteStreamEnded(e); };
// not currently implemented in chrome
- event.stream.onstarted = this.onRemoteStreamStarted;
+ event.stream.onstarted = function(e) { self.onRemoteStreamStarted(e); };
var player = new Audio();
player.src = URL.createObjectURL(s);
player.play();
@@ -243,24 +249,19 @@ angular.module('MatrixCall', [])
this.state = 'connected';
};
+ MatrixCall.prototype.onRemoteStreamEnded = function(event) {
+ this.state = 'ended';
+ this.stopAllMedia();
+ this.onHangup();
+ };
+
MatrixCall.prototype.onRemoteStreamTrackStarted = function(event) {
this.state = 'connected';
};
MatrixCall.prototype.onHangupReceived = function() {
this.state = 'ended';
-
- if (this.localAVStream) {
- forAllTracksOnStream(this.localAVStream, function(t) {
- t.stop();
- });
- }
- if (this.remoteAVStream) {
- forAllTracksOnStream(this.remoteAVStream, function(t) {
- t.stop();
- });
- }
-
+ this.stopAllMedia();
this.onHangup();
};
diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js
index 8543491dca..d509c20b0f 100644
--- a/webclient/components/matrix/matrix-service.js
+++ b/webclient/components/matrix/matrix-service.js
@@ -38,7 +38,7 @@ angular.module('matrixService', [])
// Current version of permanent storage
var configVersion = 0;
- var prefixPath = "/matrix/client/api/v1";
+ var prefixPath = "/_matrix/client/api/v1";
var MAPPING_PREFIX = "alias_for_";
var doRequest = function(method, path, params, data, $httpParams) {
@@ -95,14 +95,18 @@ angular.module('matrixService', [])
},
// Create a room
- create: function(room_id, visibility) {
+ create: function(room_alias, visibility) {
// The REST path spec
var path = "/createRoom";
- return doRequest("POST", path, undefined, {
- visibility: visibility,
- room_alias_name: room_id
- });
+ var req = {
+ "visibility": visibility
+ };
+ if (room_alias) {
+ req.room_alias_name = room_alias;
+ }
+
+ return doRequest("POST", path, undefined, req);
},
// List all rooms joined or been invited to
@@ -164,7 +168,7 @@ angular.module('matrixService', [])
// Retrieves the room ID corresponding to a room alias
resolveRoomAlias:function(room_alias) {
- var path = "/matrix/client/api/v1/directory/room/$room_alias";
+ var path = "/_matrix/client/api/v1/directory/room/$room_alias";
room_alias = encodeURIComponent(room_alias);
path = path.replace("$room_alias", room_alias);
@@ -304,7 +308,7 @@ angular.module('matrixService', [])
// hit the Identity Server for a 3PID request.
linkEmail: function(email, clientSecret, sendAttempt) {
- var path = "/matrix/identity/api/v1/validate/email/requestToken"
+ var path = "/_matrix/identity/api/v1/validate/email/requestToken"
var data = "clientSecret="+clientSecret+"&email=" + encodeURIComponent(email)+"&sendAttempt="+sendAttempt;
var headers = {};
headers["Content-Type"] = "application/x-www-form-urlencoded";
@@ -312,7 +316,7 @@ angular.module('matrixService', [])
},
authEmail: function(clientSecret, tokenId, code) {
- var path = "/matrix/identity/api/v1/validate/email/submitToken";
+ var path = "/_matrix/identity/api/v1/validate/email/submitToken";
var data = "token="+code+"&sid="+tokenId+"&clientSecret="+clientSecret;
var headers = {};
headers["Content-Type"] = "application/x-www-form-urlencoded";
@@ -320,7 +324,7 @@ angular.module('matrixService', [])
},
bindEmail: function(userId, tokenId, clientSecret) {
- var path = "/matrix/identity/api/v1/3pid/bind";
+ var path = "/_matrix/identity/api/v1/3pid/bind";
var data = "mxid="+encodeURIComponent(userId)+"&sid="+tokenId+"&clientSecret="+clientSecret;
var headers = {};
headers["Content-Type"] = "application/x-www-form-urlencoded";
@@ -328,7 +332,7 @@ angular.module('matrixService', [])
},
uploadContent: function(file) {
- var path = "/matrix/content";
+ var path = "/_matrix/content";
var headers = {
"Content-Type": undefined // undefined means angular will figure it out
};
diff --git a/webclient/components/matrix/presence-service.js b/webclient/components/matrix/presence-service.js
index 6a1edcaf43..555118133b 100644
--- a/webclient/components/matrix/presence-service.js
+++ b/webclient/components/matrix/presence-service.js
@@ -23,9 +23,9 @@
angular.module('mPresence', [])
.service('mPresence', ['$timeout', 'matrixService', function ($timeout, matrixService) {
- // Time in ms after that a user is considered as offline/away
- var OFFLINE_TIME = 5 * 60000; // 5 mins
-
+ // Time in ms after that a user is considered as unavailable/away
+ var UNAVAILABLE_TIME = 5 * 60000; // 5 mins
+
// The current presence state
var state = undefined;
@@ -88,11 +88,11 @@ angular.module('mPresence', [])
};
/**
- * Callback called when the user made no action on the page for OFFLINE_TIME ms.
+ * Callback called when the user made no action on the page for UNAVAILABLE_TIME ms.
* @private
*/
- function onOfflineTimerFire() {
- self.setState(matrixService.presence.offline);
+ function onUnvailableTimerFire() {
+ self.setState(matrixService.presence.unavailable);
}
/**
@@ -105,7 +105,7 @@ angular.module('mPresence', [])
// Re-arm the timer
$timeout.cancel(timer);
- timer = $timeout(onOfflineTimerFire, OFFLINE_TIME);
+ timer = $timeout(onUnvailableTimerFire, UNAVAILABLE_TIME);
}
}]);
diff --git a/webclient/home/home-controller.js b/webclient/home/home-controller.js
index 547a5c5603..847918d5dc 100644
--- a/webclient/home/home-controller.js
+++ b/webclient/home/home-controller.js
@@ -37,6 +37,11 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen
$scope.joinAlias = {
room_alias: ""
};
+
+ $scope.profile = {
+ displayName: "",
+ avatarUrl: ""
+ };
var refresh = function() {
@@ -53,14 +58,14 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen
);
};
- $scope.createNewRoom = function(room_id, isPrivate) {
+ $scope.createNewRoom = function(room_alias, isPrivate) {
var visibility = "public";
if (isPrivate) {
visibility = "private";
}
- matrixService.create(room_id, visibility).then(
+ matrixService.create(room_alias, visibility).then(
function(response) {
// This room has been created. Refresh the rooms list
console.log("Created room " + response.data.room_alias + " with id: "+
@@ -108,6 +113,26 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen
};
$scope.onInit = function() {
+ // Load profile data
+ // Display name
+ matrixService.getDisplayName($scope.config.user_id).then(
+ function(response) {
+ $scope.profile.displayName = response.data.displayname;
+ },
+ function(error) {
+ $scope.feedback = "Can't load display name";
+ }
+ );
+ // Avatar
+ matrixService.getProfilePictureUrl($scope.config.user_id).then(
+ function(response) {
+ $scope.profile.avatarUrl = response.data.avatar_url;
+ },
+ function(error) {
+ $scope.feedback = "Can't load avatar URL";
+ }
+ );
+
refresh();
};
}]);
diff --git a/webclient/home/home.html b/webclient/home/home.html
index d38b843d83..c1f9643839 100644
--- a/webclient/home/home.html
+++ b/webclient/home/home.html
@@ -1,29 +1,24 @@
<div ng-controller="HomeController" data-ng-init="onInit()">
- <div id="page">
<div id="wrapper">
-
+
+ <div id="genericHeading">
+ <a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a>
+ </div>
+
+ <h1>Welcome to homeserver {{ config.homeserver }}</h1>
+
<div>
- <form>
- <table>
- <tr>
- <td>
- <div class="profile-avatar">
- <img ng-src="{{ config.avatarUrl || 'img/default-profile.jpg' }}"/>
- </div>
- </td>
- <td>
- <div id="user-ids">
- <div id="user-displayname">{{ config.displayName }}</div>
- <div>{{ config.user_id }}</div>
- </div>
- </td>
- </tr>
- </table>
- </form>
+ <div class="profile-avatar">
+ <img ng-src="{{ (null !== profile.avatarUrl) ? profile.avatarUrl : 'img/default-profile.png' }}"/>
+ </div>
+ <div id="user-ids">
+ <div id="user-displayname">{{ profile.displayName }}</div>
+ <div>{{ config.user_id }}</div>
+ </div>
</div>
- <h3>Recents</h3>
+ <h3>Recent conversations</h3>
<div ng-include="'recents/recents.html'"></div>
<br/>
@@ -38,9 +33,9 @@
<div>
<form>
- <input size="40" ng-model="newRoom.room_id" ng-enter="createNewRoom(newRoom.room_id, newRoom.private)" placeholder="(e.g. foo_channel)"/>
+ <input size="40" ng-model="newRoom.room_alias" ng-enter="createNewRoom(newRoom.room_alias, newRoom.private)" placeholder="(e.g. foo_channel)"/>
<input type="checkbox" ng-model="newRoom.private">private
- <button ng-disabled="!newRoom.room_id" ng-click="createNewRoom(newRoom.room_id, newRoom.private)">Create room</button>
+ <button ng-disabled="!newRoom.room_alias" ng-click="createNewRoom(newRoom.room_alias, newRoom.private)">Create room</button>
</form>
</div>
<div>
@@ -54,5 +49,4 @@
{{ feedback }}
</div>
- </div>
</div>
diff --git a/webclient/img/default-profile.jpg b/webclient/img/default-profile.jpg
deleted file mode 100644
index 20f2a2b085..0000000000
--- a/webclient/img/default-profile.jpg
+++ /dev/null
Binary files differdiff --git a/webclient/img/default-profile.png b/webclient/img/default-profile.png
new file mode 100644
index 0000000000..6f81a3c417
--- /dev/null
+++ b/webclient/img/default-profile.png
Binary files differdiff --git a/webclient/img/logo-small.png b/webclient/img/logo-small.png
new file mode 100644
index 0000000000..411206dcdc
--- /dev/null
+++ b/webclient/img/logo-small.png
Binary files differdiff --git a/webclient/img/logo.png b/webclient/img/logo.png
new file mode 100644
index 0000000000..c4b53a8487
--- /dev/null
+++ b/webclient/img/logo.png
Binary files differdiff --git a/webclient/index.html b/webclient/index.html
index 5faf165626..3c31a8a051 100644
--- a/webclient/index.html
+++ b/webclient/index.html
@@ -4,6 +4,8 @@
<title>[matrix]</title>
<link rel="stylesheet" href="app.css">
+ <link rel="stylesheet" href="mobile.css">
+
<link rel="icon" href="favicon.ico">
<meta name="viewport" content="width=device-width">
@@ -19,6 +21,7 @@
<script src="app-filter.js"></script>
<script src="home/home-controller.js"></script>
<script src="login/login-controller.js"></script>
+ <script src="login/register-controller.js"></script>
<script src="recents/recents-controller.js"></script>
<script src="recents/recents-filter.js"></script>
<script src="room/room-controller.js"></script>
@@ -38,15 +41,23 @@
<body>
- <header id="header">
+ <div id="header">
<!-- Do not show buttons on the login page -->
- <div id="header-buttons" ng-hide="'/login' == location ">
+ <div id="headerContent" ng-hide="'/login' == location || '/register' == location">
+ <a href id="headerUserId" ng-click='goToUserPage(user_id)'>{{ user_id }}</a>
+
+ <button ng-click='goToPage("/")'>Home</button>
<button ng-click='goToPage("settings")'>Settings</button>
<button ng-click="logout()">Log out</button>
</div>
- </header>
+ </div>
- <div ng-view></div>
+ <div id="page" ng-view></div>
+ <div id="footer" ng-hide="location.indexOf('/room') == 0">
+ <div id="footerContent">
+ © 2014 Matrix.org
+ </div>
+ </div>
</body>
</html>
diff --git a/webclient/login/login-controller.js b/webclient/login/login-controller.js
index 51f9a3bdf4..7369a28ef0 100644
--- a/webclient/login/login-controller.js
+++ b/webclient/login/login-controller.js
@@ -1,3 +1,19 @@
+/*
+ Copyright 2014 matrix.org
+
+ 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.
+ */
+
angular.module('LoginController', ['matrixService'])
.controller('LoginController', ['$scope', '$location', 'matrixService', 'eventStreamService',
function($scope, $location, matrixService, eventStreamService) {
@@ -7,7 +23,10 @@ angular.module('LoginController', ['matrixService'])
// Assume that this is hosted on the home server, in which case the URL
// contains the home server.
var hs_url = $location.protocol() + "://" + $location.host();
- if ($location.port()) {
+ if ($location.port() &&
+ !($location.protocol() === "http" && $location.port() === 80) &&
+ !($location.protocol() === "https" && $location.port() === 443))
+ {
hs_url += ":" + $location.port();
}
@@ -16,57 +35,18 @@ angular.module('LoginController', ['matrixService'])
desired_user_name: "",
user_id: "",
password: "",
- identityServer: "",
+ identityServer: "http://matrix.org:8090",
pwd1: "",
- pwd2: ""
+ pwd2: "",
};
-
- $scope.register = function() {
-
- // Set the urls
- matrixService.setConfig({
- homeserver: $scope.account.homeserver,
- identityServer: $scope.account.identityServer
- });
-
- if ($scope.account.pwd1 !== $scope.account.pwd2) {
- $scope.feedback = "Passwords don't match.";
- return;
- }
- else if ($scope.account.pwd1.length < 6) {
- $scope.feedback = "Password must be at least 6 characters.";
- return;
- }
-
- matrixService.register($scope.account.desired_user_name, $scope.account.pwd1).then(
- function(response) {
- $scope.feedback = "Success";
- // Update the current config
- var config = matrixService.config();
- angular.extend(config, {
- access_token: response.data.access_token,
- user_id: response.data.user_id
- });
- matrixService.setConfig(config);
-
- // And permanently save it
- matrixService.saveConfig();
- eventStreamService.resume();
- // Go to the user's rooms list page
- $location.url("home");
- },
- function(error) {
- if (error.data) {
- if (error.data.errcode === "M_USER_IN_USE") {
- $scope.feedback = "Username already taken.";
- }
- }
- else if (error.status === 0) {
- $scope.feedback = "Unable to talk to the server.";
- }
- });
+
+ $scope.login_types = [ "email", "mxid" ];
+ $scope.login_type_label = {
+ "email": "Email address",
+ "mxid": "Matrix ID (e.g. @bob:matrix.org or bob)",
};
-
+ $scope.login_type = 'mxid'; // TODO: remember the user's preferred login_type
+
$scope.login = function() {
matrixService.setConfig({
homeserver: $scope.account.homeserver,
diff --git a/webclient/login/login.html b/webclient/login/login.html
index 4b2ea60928..18e7a02815 100644
--- a/webclient/login/login.html
+++ b/webclient/login/login.html
@@ -1,55 +1,49 @@
<div ng-controller="LoginController" class="login">
- <h1 id="logo">[matrix]</h1>
-
- <div id="page">
- <div id="wrapper">
-
- {{ feedback }}
+ <div id="wrapper" class="loginWrapper">
- <h3>Register for an account:</h3>
- <form novalidate>
- <input id="desired_user_name" size="70" type="text" auto-focus ng-model="account.desired_user_name" placeholder="User name (ex:bob)"/>
- <br/>
- <input id="pwd1" size="70" type="password" auto-focus ng-model="account.pwd1" placeholder="Type a password"/>
- <br/>
- <input id="pwd2" size="70" type="password" auto-focus ng-model="account.pwd2" placeholder="Re-type your password"/>
+ <a href ng-click="goToPage('/')">
+ <img src="img/logo.png" width="240" height="102" alt="[matrix]" style="padding: 50px"/>
+ </a>
+
<br/>
- <!-- New user registration -->
- <div>
- <br/>
- <button ng-click="register()" ng-disabled="!account.desired_user_name || !account.homeserver || !account.pwd1 || !account.pwd2 || account.pwd1 !== account.pwd2">Register</button>
- </div>
- </form>
- <h3>Got an account?</h3>
- <form novalidate>
- <!-- Login with an registered user -->
- <div>{{ login_error_msg }} </div>
- <div>
- <input id="user_id" size="70" type="text" auto-focus ng-model="account.user_id" placeholder="User ID (ex:@bob:localhost or bob)"/>
- <br />
- <input id="password" size="70" type="password" ng-model="account.password" placeholder="Password"/><br />
- <br/>
- <button ng-click="login()" ng-disabled="!account.user_id || !account.password || !account.homeserver">Login</button>
- </div>
-
- </form>
+ <form id="loginForm" novalidate>
+ <!-- Login with an registered user -->
+ <div>
+ Log in using:<br/>
+
+ <div ng-repeat="type in login_types">
+ <input type="radio" ng-model="$parent.login_type" value="{{ type }}" id="radio_{{ type }}"/>
+ <label for="radio_{{ type }}">{{ login_type_label[type] }}</label>
+ </div>
+
+ <div style="text-align: center">
+ <br/>
+ <input id="user_id" size="32" type="text" ng-focus="true" ng-model="account.user_id" placeholder="{{ login_type_label[login_type] }}"/>
+ <br/>
+ <input id="password" size="32" type="password" ng-model="account.password" placeholder="Password"/>
+ <br/><br/>
+ <button ng-click="login()" ng-disabled="!account.user_id || !account.password || !account.homeserver">Login</button>
+ <br/><br/>
+ </div>
- <h3>Servers</h3>
- <form novalidate>
- <div>
- Home Server:
- <input id="homeserver" size="57" type="text" ng-model="account.homeserver" placeholder="Home server URL (ex: http://localhost:8080)"/>
- </div>
- <br />
- <div>
- Identity Server:
- <input id="identityServer" size="56" type="text" ng-model="account.identityServer" placeholder="Identity server URL (ex: http://localhost:8090)"/>
- </div>
- <br />
- </form>
- <br/>
-
+ <div class="feedback">{{ feedback }} {{ login_error_msg }}</div>
+
+ <div id="serverConfig">
+ <label for="homeserver">Home Server:</label>
+ <input id="homeserver" size="32" type="text" ng-model="account.homeserver" placeholder="URL (e.g. http://matrix.org:8080)"/>
+ <div class="smallPrint">Your home server stores all your conversation and account data.</div>
+ <label for="identityServer">Identity Server:</label>
+ <input id="identityServer" size="32" type="text" ng-model="account.identityServer" placeholder="URL (e.g. http://matrix.org:8090)"/>
+ <div class="smallPrint">Matrix provides identity servers to track which emails etc. belong to which Matrix IDs.<br/>
+ Only http://matrix.org:8090 currently exists.</div>
+ <br/>
+ <br/>
+ <a href="#/register" style="padding-right: 3em">Create account</a>
+ <a href="#/reset_password">Forgotten password?</a>
+ </div>
+ </div>
+ </form>
</div>
</div>
diff --git a/webclient/login/register-controller.js b/webclient/login/register-controller.js
new file mode 100644
index 0000000000..0ece57502b
--- /dev/null
+++ b/webclient/login/register-controller.js
@@ -0,0 +1,102 @@
+/*
+ Copyright 2014 matrix.org
+
+ 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.
+ */
+
+angular.module('RegisterController', ['matrixService'])
+.controller('RegisterController', ['$scope', '$location', 'matrixService', 'eventStreamService',
+ function($scope, $location, matrixService, eventStreamService) {
+ 'use strict';
+
+ // FIXME: factor out duplication with login-controller.js
+
+ // Assume that this is hosted on the home server, in which case the URL
+ // contains the home server.
+ var hs_url = $location.protocol() + "://" + $location.host();
+ if ($location.port() &&
+ !($location.protocol() === "http" && $location.port() === 80) &&
+ !($location.protocol() === "https" && $location.port() === 443))
+ {
+ hs_url += ":" + $location.port();
+ }
+
+ $scope.account = {
+ homeserver: hs_url,
+ desired_user_id: "",
+ desired_user_name: "",
+ password: "",
+ identityServer: "http://matrix.org:8090",
+ pwd1: "",
+ pwd2: "",
+ displayName : ""
+ };
+
+ $scope.register = function() {
+
+ // Set the urls
+ matrixService.setConfig({
+ homeserver: $scope.account.homeserver,
+ identityServer: $scope.account.identityServer
+ });
+
+ if ($scope.account.pwd1 !== $scope.account.pwd2) {
+ $scope.feedback = "Passwords don't match.";
+ return;
+ }
+ else if ($scope.account.pwd1.length < 6) {
+ $scope.feedback = "Password must be at least 6 characters.";
+ return;
+ }
+
+ matrixService.register($scope.account.desired_user_id, $scope.account.pwd1).then(
+ function(response) {
+ $scope.feedback = "Success";
+ // Update the current config
+ var config = matrixService.config();
+ angular.extend(config, {
+ access_token: response.data.access_token,
+ user_id: response.data.user_id
+ });
+ matrixService.setConfig(config);
+
+ // And permanently save it
+ matrixService.saveConfig();
+
+ // Update the global scoped used_id var (used in the app header)
+ $scope.updateHeader();
+
+ eventStreamService.resume();
+
+ if ($scope.account.displayName) {
+ // FIXME: handle errors setting displayName
+ matrixService.setDisplayName($scope.account.displayName);
+ }
+
+ // Go to the user's rooms list page
+ $location.url("home");
+ },
+ function(error) {
+ if (error.data) {
+ if (error.data.errcode === "M_USER_IN_USE") {
+ $scope.feedback = "Username already taken.";
+ }
+ }
+ else if (error.status === 0) {
+ $scope.feedback = "Unable to talk to the server.";
+ }
+ });
+ };
+
+}]);
+
diff --git a/webclient/login/register.html b/webclient/login/register.html
new file mode 100644
index 0000000000..1b470e4554
--- /dev/null
+++ b/webclient/login/register.html
@@ -0,0 +1,48 @@
+<div ng-controller="RegisterController" class="register">
+ <div id="wrapper" class="loginWrapper">
+
+ <a href ng-click="goToPage('/')">
+ <img src="img/logo.png" width="240" height="102" alt="[matrix]" style="padding: 50px"/>
+ </a>
+ <br/>
+
+ <form id="loginForm" novalidate>
+ <div>
+ Create account:<br/>
+
+ <div style="text-align: center">
+ <br/>
+ <input id="email" size="32" type="text" ng-focus="true" ng-model="account.email" placeholder="Email address (optional)"/>
+ <div class="smallPrint">Specifying an email address lets other users find you on Matrix more easily,<br/>
+ and gives you a way to reset your password</div>
+ <input id="desired_user_id" size="32" type="text" ng-model="account.desired_user_id" placeholder="Matrix ID (e.g. bob)"/>
+ <br/>
+ <input id="pwd1" size="32" type="password" ng-model="account.pwd1" placeholder="Type a password"/>
+ <br/>
+ <input id="pwd2" size="32" type="password" ng-model="account.pwd2" placeholder="Confirm your password"/>
+ <br/>
+ <input id="displayName" size="32" type="text" ng-model="account.displayName" placeholder="Display name (e.g. Bob Obson)"/>
+ <br/>
+ <br/>
+
+ <button ng-click="register()" ng-disabled="!account.desired_user_id || !account.homeserver || !account.pwd1 || !account.pwd2 || account.pwd1 !== account.pwd2">Sign up</button>
+ <br/><br/>
+ </div>
+
+ <div class="feedback">{{ feedback }} {{ login_error_msg }}</div>
+
+ <div id="serverConfig">
+ <label for="homeserver">Home Server:</label>
+ <input id="homeserver" size="32" type="text" ng-model="account.homeserver" placeholder="URL (e.g. http://matrix.org:8080)"/>
+ <div class="smallPrint">Your home server stores all your conversation and account data.</div>
+ <label for="identityServer">Identity Server:</label>
+ <input id="identityServer" size="32" type="text" ng-model="account.identityServer" placeholder="URL (e.g. http://matrix.org:8090)"/>
+ <div class="smallPrint">Matrix provides identity servers to track which emails etc. belong to which Matrix IDs.<br/>
+ Only http://matrix.org:8090 currently exists.</div>
+ </div>
+ </div>
+ </form>
+
+ </div>
+ </div>
+</div>
diff --git a/webclient/mobile.css b/webclient/mobile.css
new file mode 100644
index 0000000000..7c62a072d5
--- /dev/null
+++ b/webclient/mobile.css
@@ -0,0 +1,92 @@
+/*** Mobile voodoo ***/
+@media all and (max-device-width: 640px) {
+
+ #messageTableWrapper {
+ margin-right: 0px ! important;
+ }
+
+ .leftBlock {
+ width: 8em ! important;
+ font-size: 8px ! important;
+ }
+
+ .rightBlock {
+ width: 0px ! important;
+ display: none ! important;
+ }
+
+ .avatar {
+ width: 36px ! important;
+ }
+
+ #header {
+ background-color: transparent;
+ }
+
+ #headerContent {
+ padding-right: 5px;
+ }
+
+ #headerContent button {
+ font-size: 8px;
+ }
+
+ #messageTable,
+ #wrapper,
+ #controls {
+ max-width: 640px ! important;
+ }
+
+ #headerUserId,
+ #roomHeader img,
+ #userIdCell,
+ #roomRecentsTableWrapper,
+ #usersTableWrapper,
+ .extraControls {
+ display: none;
+ }
+
+ #buttonsCell {
+ width: 60px ! important;
+ padding-left: 20px ! important;
+ }
+
+ #roomLogo {
+ display: none;
+ }
+
+ .bubble {
+ font-size: 12px ! important;
+ min-height: 20px ! important;
+ }
+
+ #roomHeader {
+ padding-top: 10px;
+ }
+
+ #roomName {
+ float: left;
+ font-size: 14px ! important;
+ margin-top: 0px ! important;
+ }
+
+ #roomPage {
+ top: 35px ! important;
+ left: 5px ! important;
+ right: 5px ! important;
+ bottom: 70px ! important;
+ }
+
+ #controlPanel {
+ height: 70px;
+ }
+
+ /* stop zoom on select */
+ select:focus,
+ textarea,
+ input
+ {
+ font-size: 16px ! important;
+ }
+
+}
diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js
index 09dac85d26..c6028f874e 100644
--- a/webclient/room/room-controller.js
+++ b/webclient/room/room-controller.js
@@ -88,7 +88,7 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
call.onHangup = $scope.onCallHangup;
$scope.currentCall = call;
});
-
+
$scope.memberCount = function() {
return Object.keys($scope.members).length;
};
@@ -175,6 +175,8 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
// set target_user_id to keep things clear
var target_user_id = chunk.state_key;
+
+ var now = new Date().getTime();
var isNewMember = !(target_user_id in $scope.members);
if (isNewMember) {
@@ -185,44 +187,14 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
if ("mtime_age" in chunk.content) {
chunk.mtime_age = chunk.content.mtime_age;
}
- // Once the HS reliably returns the displaynames & avatar_urls for both
- // local and remote users, we should use this rather than the evalAsync block
- // below
if ("displayname" in chunk.content) {
chunk.displayname = chunk.content.displayname;
}
if ("avatar_url" in chunk.content) {
chunk.avatar_url = chunk.content.avatar_url;
}
- $scope.members[target_user_id] = chunk;
-
-/*
- // Stale code for explicitly hammering the homeserver for every displayname & avatar_url
-
- // get their display name and profile picture and set it to their
- // member entry in $scope.members. We HAVE to use $timeout with 0 delay
- // to make this function run AFTER the current digest cycle, else the
- // response may update a STALE VERSION of the member list (manifesting
- // as no member names appearing, or appearing sporadically).
- $scope.$evalAsync(function() {
- matrixService.getDisplayName(chunk.target_user_id).then(
- function(response) {
- var member = $scope.members[chunk.target_user_id];
- if (member !== undefined) {
- member.displayname = response.data.displayname;
- }
- }
- );
- matrixService.getProfilePictureUrl(chunk.target_user_id).then(
- function(response) {
- var member = $scope.members[chunk.target_user_id];
- if (member !== undefined) {
- member.avatar_url = response.data.avatar_url;
- }
- }
- );
- });
-*/
+ chunk.last_updated = now;
+ $scope.members[target_user_id] = chunk;
if (target_user_id in $rootScope.presence) {
updatePresence($rootScope.presence[target_user_id]);
@@ -234,6 +206,12 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
member.content.membership = chunk.content.membership;
}
};
+
+ var updateMemberListPresenceAge = function() {
+ $scope.now = new Date().getTime();
+ // TODO: don't bother polling every 5s if we know none of our counters are younger than 1 minute
+ $timeout(updateMemberListPresenceAge, 5 * 1000);
+ };
var updatePresence = function(chunk) {
if (!(chunk.content.user_id in $scope.members)) {
@@ -275,6 +253,10 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
if ($scope.textInput.indexOf("/me") === 0) {
promise = matrixService.sendEmoteMessage($scope.room_id, $scope.textInput.substr(4));
}
+ else if ($scope.textInput.indexOf("/nick ") === 0) {
+ // Change user display name
+ promise = matrixService.setDisplayName($scope.textInput.substr(6));
+ }
else {
promise = matrixService.sendTextMessage($scope.room_id, $scope.textInput);
}
@@ -395,8 +377,10 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
// Make recents highlight the current room
$scope.recentsSelectedRoomID = $scope.room_id;
-
+
paginate(MESSAGES_PER_PAGINATION);
+
+ updateMemberListPresenceAge();
};
$scope.inviteUser = function(user_id) {
@@ -404,18 +388,13 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
matrixService.invite($scope.room_id, user_id).then(
function() {
console.log("Invited.");
- $scope.feedback = "Request for invitation succeeds";
+ $scope.feedback = "Invite sent successfully";
},
function(reason) {
$scope.feedback = "Failure: " + reason;
});
};
- // Open the user profile page
- $scope.goToUserPage = function(user_id) {
- $location.url("/user/" + user_id);
- };
-
$scope.leaveRoom = function() {
matrixService.leave($scope.room_id).then(
@@ -487,7 +466,5 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput'])
}
$scope.onCallHangup = function() {
- $scope.feedback = "Call ended";
- $scope.currentCall = undefined;
}
}]);
diff --git a/webclient/room/room.html b/webclient/room/room.html
index a3514c3a91..e5e454864b 100644
--- a/webclient/room/room.html
+++ b/webclient/room/room.html
@@ -1,13 +1,15 @@
-<div ng-controller="RoomController" data-ng-init="onInit()" class="room">
- <h1 id="roomLogo">[matrix]</h1>
+<div ng-controller="RoomController" data-ng-init="onInit()" class="room" style="height: 100%;">
- <div id="page">
- <div id="wrapper">
-
- <div id="roomName">
- {{ room_alias || room_id }}
+ <div id="roomHeader">
+ <a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a>
+ <div id="roomName">
+ {{ room_alias || room_id }}
+ </div>
</div>
+ <div id="roomPage">
+ <div id="roomWrapper">
+
<div id="roomRecentsTableWrapper">
<div ng-include="'recents/recents.html'"></div>
</div>
@@ -15,17 +17,17 @@
<div id="usersTableWrapper">
<table id="usersTable">
<tr ng-repeat="member in members | orderMembersList">
- <td class="userAvatar mouse-pointer" ng-click="goToUserPage(member.id)">
+ <td class="userAvatar mouse-pointer" ng-click="$parent.goToUserPage(member.id)" ng-class="member.membership == 'invite' ? 'invited' : ''">
<img class="userAvatarImage"
- ng-src="{{member.avatar_url || 'img/default-profile.jpg'}}"
+ ng-src="{{member.avatar_url || 'img/default-profile.png'}}"
alt="{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}"
title="{{ member.id }}"
width="80" height="80"/>
<img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/>
<div class="userName">{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}<br/>{{ member.displayname ? "" : member.id.substr(member.id.indexOf(':')) }}</div>
</td>
- <td class="userPresence" ng-class="member.presenceState === 'online' ? 'online' : (member.presenceState === 'unavailable' ? 'unavailable' : '')">
- {{ member.mtime_age | duration }}<br/>{{ member.mtime_age ? "ago" : "" }}
+ <td class="userPresence" ng-class="(member.presenceState === 'online' ? 'online' : (member.presenceState === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.membership == 'invite' ? 'invited' : '')">
+ <span ng-show="member.mtime_age">{{ member.mtime_age + (now - member.last_updated) | duration }}<br/>ago</span>
</td>
</table>
</div>
@@ -40,7 +42,7 @@
<div class="timestamp">{{ (msg.content.hsob_ts || msg.ts) | date:'MMM d HH:mm' }}</div>
</td>
<td class="avatar">
- <img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.jpg' }}" width="32" height="32"
+ <img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32"
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/>
</td>
<td ng-class="!msg.content.membership ? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'">
@@ -64,7 +66,7 @@
</div>
</td>
<td class="rightBlock">
- <img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.jpg' }}" width="32" height="32"
+ <img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32"
ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/>
</td>
</tr>
@@ -86,12 +88,12 @@
</td>
<td id="buttonsCell">
<button ng-click="send()">Send</button>
- <button m-file-input="imageFileToSend">Image</button>
+ <button m-file-input="imageFileToSend" class="extraControls">Image</button>
</td>
</tr>
</table>
- <div id="extraControls">
+ <div class="extraControls">
<span>
Invite a user:
<input ng-model="userIDToInvite" size="32" type="text" placeholder="User ID (ex:@user:homeserver)"/>
@@ -105,6 +107,10 @@
<button ng-click="hangupCall()">Reject</button>
</div>
<button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing'">Hang up</button>
+ <span ng-show="currentCall.state == 'invite_sent'">Calling...</span>
+ <span ng-show="currentCall.state == 'connecting'">Call Connecting...</span>
+ <span ng-show="currentCall.state == 'connected'">Call Connected</span>
+ <span ng-show="currentCall.state == 'ended'">Call Ended</span>
<span style="display: none; ">{{ currentCall.state }}</span>
</div>
diff --git a/webclient/settings/settings-controller.js b/webclient/settings/settings-controller.js
index f7d5e8eb75..dc680ef075 100644
--- a/webclient/settings/settings-controller.js
+++ b/webclient/settings/settings-controller.js
@@ -22,8 +22,38 @@ angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInpu
$scope.config = matrixService.config();
$scope.profile = {
- displayName: $scope.config.displayName,
- avatarUrl: $scope.config.avatarUrl
+ displayName: "",
+ avatarUrl: ""
+ };
+
+ // The profile as stored on the server
+ $scope.profileOnServer = {
+ displayName: "",
+ avatarUrl: ""
+ };
+
+ $scope.onInit = function() {
+ // Load profile data
+ // Display name
+ matrixService.getDisplayName($scope.config.user_id).then(
+ function(response) {
+ $scope.profile.displayName = response.data.displayname;
+ $scope.profileOnServer.displayName = response.data.displayname;
+ },
+ function(error) {
+ $scope.feedback = "Can't load display name";
+ }
+ );
+ // Avatar
+ matrixService.getProfilePictureUrl($scope.config.user_id).then(
+ function(response) {
+ $scope.profile.avatarUrl = response.data.avatar_url;
+ $scope.profileOnServer.avatarUrl = response.data.avatar_url;
+ },
+ function(error) {
+ $scope.feedback = "Can't load avatar URL";
+ }
+ );
};
$scope.$watch("profile.avatarFile", function(newValue, oldValue) {
@@ -41,10 +71,10 @@ angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInpu
});
$scope.saveProfile = function() {
- if ($scope.profile.displayName !== $scope.config.displayName) {
+ if ($scope.profile.displayName !== $scope.profileOnServer.displayName) {
setDisplayName($scope.profile.displayName);
}
- if ($scope.profile.avatarUrl !== $scope.config.avatarUrl) {
+ if ($scope.profile.avatarUrl !== $scope.profileOnServer.avatarUrl) {
setAvatar($scope.profile.avatarUrl);
}
};
@@ -53,11 +83,6 @@ angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInpu
matrixService.setDisplayName(displayName).then(
function(response) {
$scope.feedback = "Updated display name.";
-
- var config = matrixService.config();
- config.displayName = displayName;
- matrixService.setConfig(config);
- matrixService.saveConfig();
},
function(error) {
$scope.feedback = "Can't update display name: " + error.data;
@@ -71,11 +96,6 @@ angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInpu
function(response) {
console.log("Updated avatar");
$scope.feedback = "Updated avatar.";
-
- var config = matrixService.config();
- config.avatarUrl = avatarURL;
- matrixService.setConfig(config);
- matrixService.saveConfig();
},
function(error) {
$scope.feedback = "Can't update avatar: " + error.data;
@@ -143,4 +163,23 @@ angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInpu
}
);
};
+
+
+ /*** Desktop notifications section ***/
+ $scope.settings = {
+ notifications: undefined
+ };
+
+ // If the browser supports it, check the desktop notification state
+ if ("Notification" in window) {
+ $scope.settings.notifications = window.Notification.permission;
+ }
+
+ $scope.requestNotifications = function() {
+ console.log("requestNotifications");
+ window.Notification.requestPermission(function (permission) {
+ console.log(" -> User decision: " + permission);
+ $scope.settings.notifications = permission;
+ });
+ };
}]);
\ No newline at end of file
diff --git a/webclient/settings/settings.html b/webclient/settings/settings.html
index 453a4fc35f..a69a8de300 100644
--- a/webclient/settings/settings.html
+++ b/webclient/settings/settings.html
@@ -1,35 +1,29 @@
-<div ng-controller="SettingsController" class="user">
+<div ng-controller="SettingsController" class="user" data-ng-init="onInit()">
- <div id="page">
<div id="wrapper">
-
- <h3>Me</h3>
- <div>
+
+ <div id="genericHeading">
+ <a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a>
+ </div>
+
+ <h1>Settings</h1>
+ <div class="section">
<form>
- <table>
- <tr>
- <td>
- <div class="profile-avatar">
- <img ng-src="{{ profile.avatarUrl || 'img/default-profile.jpg' }}" m-file-input="profile.avatarFile"/>
- </div>
- </td>
- <td>
- <div id="user-ids">
- <input size="40" ng-model="profile.displayName" placeholder="Your name"/>
- </div>
- </td>
- <td>
- <button ng-disabled="(profile.displayName == config.displayName) && (profile.avatarUrl == config.avatarUrl)"
- ng-click="saveProfile()">Save</button>
- </td>
- </tr>
- </table>
+ <div class="profile-avatar">
+ <img ng-src="{{ (null !== profile.avatarUrl) ? profile.avatarUrl : 'img/default-profile.png' }}" m-file-input="profile.avatarFile"/>
+ </div>
+ <div id="user-ids">
+ <input size="40" ng-model="profile.displayName" placeholder="Your display name"/>
+ <br/>
+ <button ng-disabled="(profile.displayName == profileOnServer.displayName) && (profile.avatarUrl == profileOnServer.avatarUrl)"
+ ng-click="saveProfile()">Save</button>
+ </div>
</form>
</div>
<br/>
<h3>Linked emails</h3>
- <div>
+ <div class="section">
<form>
<input size="40" ng-model="linkedEmails.linkNewEmail" ng-enter="linkEmail(linkedEmails.linkNewEmail)" />
<button ng-disabled="!linkedEmails.linkNewEmail" ng-click="linkEmail(linkedEmails.linkNewEmail)">
@@ -52,22 +46,35 @@
</table>
</div>
<br/>
-
+
+ <h3>Desktop notifications</h3>
+ <div class="section" ng-switch="settings.notifications">
+ <div ng-switch-when="granted">
+ Notifications are enabled.
+ </div>
+ <div ng-switch-when="denied">
+ You have denied permission for notifications.<br/>
+ To enable it, reset the notification setting for this web site into your browser settings.
+ </div>
+ <div ng-switch-when="default">
+ <button ng-click="requestNotifications()" style="font-size: 14pt">Enable desktop notifications</button>
+ </div>
+ <div ng-switch-default="">
+ Sorry, your browser does not support notifications.
+ </div>
+ </div>
+ <br/>
+
<h3>Configuration</h3>
- <div>
+ <div class="section">
<div>Home server: {{ config.homeserver }} </div>
+ <div>Identity server: {{ config.identityServer }} </div>
<div>User ID: {{ config.user_id }} </div>
<div>Access token: {{ config.access_token }} </div>
</div>
<br/>
-
- <div>
- <div><button ng-click="requestNotifications()">Request notifications</button></div>
- </div>
- <br/>
{{ feedback }}
</div>
- </div>
</div>
diff --git a/webclient/user/user-controller.js b/webclient/user/user-controller.js
index 620230561c..b5b2d439a2 100644
--- a/webclient/user/user-controller.js
+++ b/webclient/user/user-controller.js
@@ -25,14 +25,42 @@ angular.module('UserController', ['matrixService'])
avatar_url: undefined
};
+ $scope.user_id = matrixService.config().user_id;
+
matrixService.getDisplayName($scope.user.id).then(
function(response) {
$scope.user.displayname = response.data.displayname;
}
);
+
matrixService.getProfilePictureUrl($scope.user.id).then(
function(response) {
$scope.user.avatar_url = response.data.avatar_url;
}
);
+
+ $scope.messageUser = function() {
+
+ // FIXME: create a new room every time, for now
+
+ matrixService.create(null, 'private').then(
+ function(response) {
+ // This room has been created. Refresh the rooms list
+ var room_id = response.data.room_id;
+ console.log("Created room with id: "+ room_id);
+
+ matrixService.invite(room_id, $scope.user.id).then(
+ function() {
+ $scope.feedback = "Invite sent successfully";
+ $scope.$parent.goToPage("/room/" + room_id);
+ },
+ function(reason) {
+ $scope.feedback = "Failure: " + JSON.stringify(reason);
+ });
+ },
+ function(error) {
+ $scope.feedback = "Failure: " + JSON.stringify(error.data);
+ });
+ };
+
}]);
\ No newline at end of file
diff --git a/webclient/user/user.html b/webclient/user/user.html
index 4c91c8a48a..2aa981437b 100644
--- a/webclient/user/user.html
+++ b/webclient/user/user.html
@@ -1,31 +1,25 @@
<div ng-controller="UserController" class="user">
- <h1 id="logo">[matrix]</h1>
- <div id="page">
<div id="wrapper">
-
+
+ <div id="genericHeading">
+ <a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a>
+ </div>
+
+ <h1>{{ user.displayname || user.id }}</h1>
+
<div>
- <form>
- <table>
- <tr>
- <td>
- <div class="profile-avatar">
- <img ng-src="{{ user.avatar_url || 'img/default-profile.jpg' }}"/>
- </div>
- </td>
- <td>
- <div id="user-ids">
- <div id="user-displayname">{{ user.displayname }}</div>
- <div>{{ user.id }}</div>
- </div>
- </td>
- </tr>
- </table>
- </form>
+ <div class="profile-avatar">
+ <img ng-src="{{ user.avatar_url || 'img/default-profile.png' }}"/>
+ </div>
+ <div id="user-ids">
+ <div>{{ user.id }}</div>
+ </div>
</div>
+ <button ng-hide="user.id == user_id" ng-click="messageUser()" style="font-size: 14pt; margin-top: 40px; margin-bottom: 40px">Start chat</button>
+ <br/>
{{ feedback }}
- </div>
</div>
</div>
|