diff options
Diffstat (limited to 'syweb')
-rwxr-xr-x | syweb/webclient/app.css | 150 | ||||
-rw-r--r-- | syweb/webclient/img/attach.png | bin | 0 -> 473 bytes | |||
-rw-r--r-- | syweb/webclient/img/settings.png | bin | 0 -> 864 bytes | |||
-rw-r--r-- | syweb/webclient/img/video.png | bin | 0 -> 604 bytes | |||
-rw-r--r-- | syweb/webclient/img/voice.png | bin | 0 -> 659 bytes | |||
-rw-r--r-- | syweb/webclient/index.html | 2 | ||||
-rw-r--r-- | syweb/webclient/js/angular-peity.js | 69 | ||||
-rw-r--r-- | syweb/webclient/js/jquery.peity.min.js | 13 | ||||
-rw-r--r-- | syweb/webclient/mobile.css | 18 | ||||
-rw-r--r-- | syweb/webclient/room/room-controller.js | 19 | ||||
-rw-r--r-- | syweb/webclient/room/room.html | 131 |
11 files changed, 300 insertions, 102 deletions
diff --git a/syweb/webclient/app.css b/syweb/webclient/app.css index be2a73872d..e2f99f3975 100755 --- a/syweb/webclient/app.css +++ b/syweb/webclient/app.css @@ -318,7 +318,7 @@ textarea, input { position: absolute; bottom: 0px; width: 100%; - height: 100px; + height: 70px; background-color: #f8f8f8; border-top: #aaa 1px solid; } @@ -326,7 +326,9 @@ textarea, input { #controls { max-width: 1280px; padding: 12px; + padding-right: 42px; margin: auto; + position: relative; } #buttonsCell { @@ -343,7 +345,17 @@ textarea, input { #mainInput { width: 100%; - resize: none; + padding: 5px; +} + +#attachButton { + position: absolute; + margin-top: 3px; + right: 0px; + background: url('img/attach.png'); + width: 25px; + height: 25px; + border: 0px; } .blink { @@ -415,7 +427,8 @@ textarea, input { .roomHeaderInfo { text-align: right; float: right; - margin-top: 15px; + margin-top: 0px; + margin-right: 30px; } /*** Room Info Dialog ***/ @@ -449,15 +462,32 @@ textarea, input { resize: vertical; } +/*** Control Buttons ***/ +#controlButtons { + float: right; + margin-right: -4px; + padding-bottom: 6px; +} + +.controlButton { + border: 0px; + width: 30px; + height: 30px; + padding-left: 3px; + padding-right: 3px; +} + /*** Participant list ***/ #usersTableWrapper { float: right; - width: 120px; + clear: right; + width: 100px; height: 100%; overflow-y: auto; } +/* #usersTable { width: 100%; border-collapse: collapse; @@ -473,36 +503,66 @@ textarea, input { position: relative; background-color: #000; } +*/ -.userAvatar .userAvatarImage { - position: absolute; - top: 0px; +.userAvatar { +} + +.userAvatarFrame { + border-radius: 46px; + width: 80px; + margin: auto; + position: relative; + border: 3px solid #aaa; + background-color: #aaa; +} + +.userAvatarImage { + border-radius: 40px; + text-align: center; object-fit: cover; - width: 100%; + display: block; } +/* .userAvatar .userAvatarGradient { position: absolute; bottom: 20px; width: 100%; } +*/ -.userAvatar .userName { - position: absolute; - color: #fff; - margin: 2px; - bottom: 0px; +.userName { + margin-top: 3px; + margin-bottom: 6px; + text-align: center; font-size: 12px; word-break: break-all; } -.userAvatar .userPowerLevel { +.userPowerLevel { position: absolute; + bottom: -1px; + height: 1px; + background-color: #f00; +} + +.userPowerLevelBar { + display: inline; + position: absolute; + width: 2px; + height: 10px; +/* border: 1px solid #000; +*/ background-color: #aaa; +} + +.userPowerLevelMeter { + position: relative; bottom: 0px; - height: 2px; background-color: #f00; } +/* .userPresence { text-align: center; font-size: 12px; @@ -510,12 +570,15 @@ textarea, input { background-color: #aaa; border-bottom: 1px #ddd solid; } +*/ .online { + border-color: #38AF00; background-color: #38AF00; } .unavailable { + border-color: #FFCC00; background-color: #FFCC00; } @@ -538,18 +601,21 @@ textarea, input { #messageTable td { padding: 0px; +/* border: 1px solid #888; */ } .leftBlock { - width: 14em; + width: 6em; word-wrap: break-word; vertical-align: top; background-color: #fff; - color: #888; + color: #aaa; font-weight: medium; font-size: 12px; text-align: right; +/* border-top: 1px #ddd solid; +*/ } .rightBlock { @@ -560,13 +626,24 @@ textarea, input { } .sender, .timestamp { - padding-right: 1em; - padding-left: 1em; - padding-top: 3px; +/* padding-top: 3px; +*/} + +.timestamp { + font-size: 10px; + color: #ccc; + height: 13px; + margin-top: 4px; +*/ transition-property: opacity; + transition-duration: 0.3s; } .sender { - margin-bottom: -3px; + font-size: 12px; +/* + margin-top: 5px; + margin-bottom: -9px; +*/ } .avatar { @@ -577,7 +654,11 @@ textarea, input { } .avatarImage { + position: relative; + top: 5px; object-fit: cover; + border-radius: 32px; + margin-top: 4px; } .emote { @@ -591,6 +672,7 @@ textarea, input { } .image { + border: 1px solid #888; display: block; max-width:320px; max-height:320px; @@ -603,19 +685,23 @@ textarea, input { } .bubble { +/* background-color: #eee; border: 1px solid #d8d8d8; - display: inline-block; margin-bottom: -1px; - max-width: 90%; - font-size: 14px; - word-wrap: break-word; padding-top: 7px; padding-bottom: 5px; + -webkit-text-size-adjust:100% + vertical-align: middle; +*/ + display: inline-block; + max-width: 90%; padding-left: 1em; padding-right: 1em; - vertical-align: middle; - -webkit-text-size-adjust:100% + padding-top: 2px; + padding-bottom: 2px; + font-size: 14px; + word-wrap: break-word; } .bubble img { @@ -623,9 +709,11 @@ textarea, input { max-height: auto; } +/* .differentUser td { padding-bottom: 5px ! important; } +*/ .mine { text-align: right; @@ -635,13 +723,15 @@ textarea, input { .text.membership .bubble, .mine .text.emote .bubble, .mine .text.membership .bubble - { +{ background-color: transparent ! important; border: 0px ! important; } .mine .text .bubble { +/* background-color: #f8f8ff ! important; +*/ text-align: left ! important; } @@ -701,6 +791,8 @@ textarea, input { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + padding-left: 0.5em; + padding-right: 0.5em; } .recentsRoom { @@ -751,7 +843,7 @@ textarea, input { padding-right: 10px; margin-right: 10px; height: 100%; - border-right: 1px solid #ddd; +/* border-right: 1px solid #ddd; */ overflow-y: auto; } diff --git a/syweb/webclient/img/attach.png b/syweb/webclient/img/attach.png new file mode 100644 index 0000000000..d95eabaf00 --- /dev/null +++ b/syweb/webclient/img/attach.png Binary files differdiff --git a/syweb/webclient/img/settings.png b/syweb/webclient/img/settings.png new file mode 100644 index 0000000000..ac99fe402b --- /dev/null +++ b/syweb/webclient/img/settings.png Binary files differdiff --git a/syweb/webclient/img/video.png b/syweb/webclient/img/video.png new file mode 100644 index 0000000000..e90afea0c1 --- /dev/null +++ b/syweb/webclient/img/video.png Binary files differdiff --git a/syweb/webclient/img/voice.png b/syweb/webclient/img/voice.png new file mode 100644 index 0000000000..fe464999c0 --- /dev/null +++ b/syweb/webclient/img/voice.png Binary files differdiff --git a/syweb/webclient/index.html b/syweb/webclient/index.html index 45be6c274b..f6487f381d 100644 --- a/syweb/webclient/index.html +++ b/syweb/webclient/index.html @@ -17,6 +17,8 @@ <script src="js/angular-route.min.js"></script> <script src="js/angular-sanitize.min.js"></script> <script src="js/angular-animate.min.js"></script> + <script src="js/jquery.peity.min.js"></script> + <script src="js/angular-peity.js"></script> <script type='text/javascript' src="js/ui-bootstrap-tpls-0.11.2.js"></script> <script type='text/javascript' src='js/ng-infinite-scroll-matrix.js'></script> <script type='text/javascript' src='js/autofill-event.js'></script> diff --git a/syweb/webclient/js/angular-peity.js b/syweb/webclient/js/angular-peity.js new file mode 100644 index 0000000000..2acb647d91 --- /dev/null +++ b/syweb/webclient/js/angular-peity.js @@ -0,0 +1,69 @@ +var angularPeity = angular.module( 'angular-peity', [] ); + +$.fn.peity.defaults.pie = { + fill: ["#ff0000", "#aaaaaa"], + radius: 4, +} + +var buildChartDirective = function ( chartType ) { + return { + restrict: 'E', + scope: { + data: "=", + options: "=" + }, + link: function ( scope, element, attrs ) { + + var options = {}; + if ( scope.options ) { + options = scope.options; + } + + // N.B. live-binding to data by Matthew + scope.$watch('data', function () { + var span = document.createElement( 'span' ); + span.textContent = scope.data.join(); + + if ( !attrs.class ) { + span.className = ""; + } else { + span.className = attrs.class; + } + + if (element[0].nodeType === 8) { + element.replaceWith( span ); + } + else if (element[0].firstChild) { + element.empty(); + element[0].appendChild( span ); + } + else { + element[0].appendChild( span ); + } + + jQuery( span ).peity( chartType, options ); + }); + } + }; +}; + + +angularPeity.directive( 'pieChart', function () { + + return buildChartDirective( "pie" ); + +} ); + + +angularPeity.directive( 'barChart', function () { + + return buildChartDirective( "bar" ); + +} ); + + +angularPeity.directive( 'lineChart', function () { + + return buildChartDirective( "line" ); + +} ); diff --git a/syweb/webclient/js/jquery.peity.min.js b/syweb/webclient/js/jquery.peity.min.js new file mode 100644 index 0000000000..054b83c5d8 --- /dev/null +++ b/syweb/webclient/js/jquery.peity.min.js @@ -0,0 +1,13 @@ +// Peity jQuery plugin version 3.0.2 +// (c) 2014 Ben Pickles +// +// http://benpickles.github.io/peity +// +// Released under MIT license. +(function(h,w,i,v){var p=function(a,b){var d=w.createElementNS("http://www.w3.org/2000/svg",a);h(d).attr(b);return d},y="createElementNS"in w&&p("svg",{}).createSVGRect,e=h.fn.peity=function(a,b){y&&this.each(function(){var d=h(this),c=d.data("peity");c?(a&&(c.type=a),h.extend(c.opts,b)):(c=new x(d,a,h.extend({},e.defaults[a],b)),d.change(function(){c.draw()}).data("peity",c));c.draw()});return this},x=function(a,b,d){this.$el=a;this.type=b;this.opts=d},r=x.prototype;r.draw=function(){e.graphers[this.type].call(this, +this.opts)};r.fill=function(){var a=this.opts.fill;return h.isFunction(a)?a:function(b,d){return a[d%a.length]}};r.prepare=function(a,b){this.svg||this.$el.hide().after(this.svg=p("svg",{"class":"peity"}));return h(this.svg).empty().data("peity",this).attr({height:b,width:a})};r.values=function(){return h.map(this.$el.text().split(this.opts.delimiter),function(a){return parseFloat(a)})};e.defaults={};e.graphers={};e.register=function(a,b,d){this.defaults[a]=b;this.graphers[a]=d};e.register("pie", +{fill:["#ff9900","#fff4dd","#ffc66e"],radius:8},function(a){if(!a.delimiter){var b=this.$el.text().match(/[^0-9\.]/);a.delimiter=b?b[0]:","}b=this.values();if("/"==a.delimiter)var d=b[0],b=[d,i.max(0,b[1]-d)];for(var c=0,d=b.length,n=0;c<d;c++)n+=b[c];for(var c=2*a.radius,f=this.prepare(a.width||c,a.height||c),c=f.width(),f=f.height(),s=c/2,k=f/2,f=i.min(s,k),a=a.innerRadius,e=i.PI,q=this.fill(),g=this.scale=function(a,b){var c=2*a/n*e-e/2;return[b*i.cos(c)+s,b*i.sin(c)+k]},l=0,c=0;c<d;c++){var t= +b[c],j=t/n;if(0!=j){if(1==j)if(a)var j=s-0.01,o=k-f,m=k-a,j=p("path",{d:["M",s,o,"A",f,f,0,1,1,j,o,"L",j,m,"A",a,a,0,1,0,s,m].join(" ")});else j=p("circle",{cx:s,cy:k,r:f});else o=l+t,m=["M"].concat(g(l,f),"A",f,f,0,0.5<j?1:0,1,g(o,f),"L"),a?m=m.concat(g(o,a),"A",a,a,0,0.5<j?1:0,0,g(l,a)):m.push(s,k),l+=t,j=p("path",{d:m.join(" ")});h(j).attr("fill",q.call(this,t,c,b));this.svg.appendChild(j)}}});e.register("donut",h.extend(!0,{},e.defaults.pie),function(a){a.innerRadius||(a.innerRadius=0.5*a.radius); +e.graphers.pie.call(this,a)});e.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},function(a){var b=this.values();1==b.length&&b.push(b[0]);for(var d=i.max.apply(i,a.max==v?b:b.concat(a.max)),c=i.min.apply(i,a.min==v?b:b.concat(a.min)),n=this.prepare(a.width,a.height),f=a.strokeWidth,e=n.width(),k=n.height()-f,h=d-c,d=this.x=function(a){return a*(e/(b.length-1))},n=this.y=function(a){var b=k;h&&(b-=(a-c)/h*k);return b+f/2},q=n(i.max(c,0)),g=[0, +q],l=0;l<b.length;l++)g.push(d(l),n(b[l]));g.push(e,q);this.svg.appendChild(p("polygon",{fill:a.fill,points:g.join(" ")}));f&&this.svg.appendChild(p("polyline",{fill:"transparent",points:g.slice(2,g.length-2).join(" "),stroke:a.stroke,"stroke-width":f,"stroke-linecap":"square"}))});e.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:0.1,width:32},function(a){for(var b=this.values(),d=i.max.apply(i,a.max==v?b:b.concat(a.max)),c=i.min.apply(i,a.min==v?b:b.concat(a.min)),e=this.prepare(a.width, +a.height),f=e.width(),h=e.height(),k=d-c,a=a.padding,e=this.fill(),r=this.x=function(a){return a*f/b.length},q=this.y=function(a){return h-(k?(a-c)/k*h:1)},g=0;g<b.length;g++){var l=r(g+a),t=r(g+1-a)-l,j=b[g],o=q(j),m=o,u;k?0>j?m=q(i.min(d,0)):o=q(i.max(c,0)):u=1;u=o-m;0==u&&(u=1,0<d&&k&&m--);this.svg.appendChild(p("rect",{fill:e.call(this,j,g,b),x:l,y:m,width:t,height:u}))}})})(jQuery,document,Math); diff --git a/syweb/webclient/mobile.css b/syweb/webclient/mobile.css index 6fa9221ccf..32b01c503d 100644 --- a/syweb/webclient/mobile.css +++ b/syweb/webclient/mobile.css @@ -1,4 +1,13 @@ /*** Mobile voodoo ***/ + +/** iPads **/ +@media all and (max-device-width: 768px) { + #roomRecentsTableWrapper { + display: none; + } +} + +/** iPhones **/ @media all and (max-device-width: 640px) { #messageTableWrapper { @@ -37,11 +46,16 @@ max-width: 640px ! important; } + #controls { + padding: 0px; + } + #headerUserId, #roomHeader img, #userIdCell, #roomRecentsTableWrapper, #usersTableWrapper, + #controlButtons, .extraControls { display: none; } @@ -64,6 +78,10 @@ padding-top: 10px; } + .roomHeaderInfo { + margin-right: 0px; + } + #roomName { font-size: 12px ! important; margin-top: 0px ! important; diff --git a/syweb/webclient/room/room-controller.js b/syweb/webclient/room/room-controller.js index b878a1f718..be433d6e80 100644 --- a/syweb/webclient/room/room-controller.js +++ b/syweb/webclient/room/room-controller.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) +angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput', 'angular-peity']) .controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', 'notificationService', 'modelService', function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, notificationService, modelService) { 'use strict'; @@ -905,7 +905,20 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) paginate(MESSAGES_PER_PAGINATION); }; - $scope.startVoiceCall = function() { + $scope.checkWebRTC = function() { + if (!$rootScope.isWebRTCSupported()) { + alert("Your browser does not support WebRTC"); + return false; + } + if ($scope.memberCount() != 2) { + alert("WebRTC calls are currently only supported on rooms with two members"); + return false; + } + return true; + }; + + $scope.startVoiceCall = function() { + if (!$scope.checkWebRTC()) return; var call = new MatrixCall($scope.room_id); call.onError = $rootScope.onCallError; call.onHangup = $rootScope.onCallHangup; @@ -916,6 +929,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) }; $scope.startVideoCall = function() { + if (!$scope.checkWebRTC()) return; + var call = new MatrixCall($scope.room_id); call.onError = $rootScope.onCallError; call.onHangup = $rootScope.onCallHangup; diff --git a/syweb/webclient/room/room.html b/syweb/webclient/room/room.html index e59cc30edc..018d66f4fb 100644 --- a/syweb/webclient/room/room.html +++ b/syweb/webclient/room/room.html @@ -15,6 +15,15 @@ <script type="text/ng-template" id="roomInfoTemplate.html"> <div class="modal-body"> + <span> + Invite a user: + <input ng-model="userIDToInvite" size="32" type="text" ng-enter="inviteUser()" ng-disabled="state.permission_denied" placeholder="User ID (ex:@user:homeserver)"/> + <button ng-click="inviteUser()" ng-disabled="state.permission_denied">Invite</button> + </span> + <br/> + <br/> + <button ng-click="leaveRoom()" ng-disabled="state.permission_denied">Leave Room</button> + </br/> <table class="room-info"> <tr ng-repeat="(key, event) in roomInfo.stateEvents" class="room-info-event"> <td class="room-info-event-meta" width="30%"> @@ -57,6 +66,26 @@ <div id="roomHeader"> <a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a> + + <div id="controlButtons"> + <button ng-click="startVoiceCall()" class="controlButton" + style="background: url('img/voice.png')" + ng-show="(currentCall == undefined || currentCall.state == 'ended')" + ng-disabled="state.permission_denied" + > + </button> + <button ng-click="startVideoCall()" class="controlButton" + style="background: url('img/video.png')" + ng-show="(currentCall == undefined || currentCall.state == 'ended')" + ng-disabled="state.permission_denied" + > + </button> + <button ng-click="openRoomInfo()" class="controlButton" + style="background: url('img/settings.png')" + > + </button> + </div> + <div class="roomHeaderInfo"> <div class="roomNameSection"> @@ -91,32 +120,30 @@ <div id="roomRecentsTableWrapper"> <div ng-include="'recents/recents.html'"></div> </div> - + <div id="usersTableWrapper" ng-hide="state.permission_denied"> - <table id="usersTable"> - <tr ng-repeat="member in members | orderMembersList"> - <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.png'}}" - alt="{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}" - title="{{ member.id }} - power: {{ member.powerLevel }}" - width="80" height="80"/> - <img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/> - <div class="userPowerLevel" ng-style="{'width': member.powerLevelNorm +'%'}"></div> - <div class="userName"> - <div ng-show="member.displayname"> - {{ member.id | mUserDisplayName: room_id }} - </div> - <div ng-hide="member.displayname"> - {{ member.id.substr(0, member.id.indexOf(':')) }}<br/> - {{ member.id.substr(member.id.indexOf(':')) }} - </div> - </div> - </td> - <td class="userPresence" ng-class="(member.presence === 'online' ? 'online' : (member.presence === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.membership == 'invite' ? 'invited' : '')"> - <span ng-show="member.last_active_ago">{{ member.last_active_ago + (now - member.last_updated) | duration }}<br/>ago</span> - </td> - </table> + <div ng-repeat="member in members | orderMembersList" class="userAvatar"> + <div class="userAvatarFrame" ng-class="(member.presence === 'online' ? 'online' : (member.presence === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.membership == 'invite' ? 'invited' : '')"> + <img class="userAvatarImage mouse-pointer" + ng-click="$parent.goToUserPage(member.id)" + ng-src="{{member.avatar_url || 'img/default-profile.png'}}" + alt="{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}" + title="{{ member.id }} - power: {{ member.powerLevel }}" + width="80" height="80"/> + <!-- <div class="userPowerLevel" ng-style="{'width': member.powerLevelNorm +'%'}"></div> --> + </div> + <div class="userName"> + <pie-chart ng-show="member.powerLevelNorm" data="[ (member.powerLevelNorm + 0), (100 - member.powerLevelNorm) ]"></pie-chart> + <span ng-show="member.displayname"> + {{ member.id | mUserDisplayName: room_id }} + </span> + <span ng-hide="member.displayname"> + {{ member.id.substr(0, member.id.indexOf(':')) }}<br/> + {{ member.id.substr(member.id.indexOf(':')) }} + </span> + <span ng-show="member.last_active_ago" style="color: #aaa">({{ member.last_active_ago + (now - member.last_updated) | duration }})</span> + </div> + </div> </div> <div id="messageTableWrapper" @@ -126,19 +153,20 @@ <table id="messageTable" infinite-scroll="paginateMore()"> <tr ng-repeat="msg in room.events" ng-class="(room.events[$index + 1].user_id !== msg.user_id ? 'differentUser' : '') + (msg.user_id === state.user_id ? ' mine' : '')" scroll-item> - <td class="leftBlock"> - <div class="sender" ng-hide="room.events[$index - 1].user_id === msg.user_id"> {{ msg.__room_member.cnt.displayname || msg.user_id | mUserDisplayName: room_id }}</div> + <td class="leftBlock" ng-mouseover="state.showTs = 1" ng-mouseout="state.showTs = 0"> <div class="timestamp" + ng-style="{ 'opacity': state.showTs ? 1.0 : 0.0 }" ng-class="msg.echo_msg_state"> {{ (msg.origin_server_ts) | date:'MMM d HH:mm' }} </div> + <div class="sender" ng-hide="room.events[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"> {{ msg.__room_member.cnt.displayname || msg.user_id | mUserDisplayName: room_id }}</div> </td> <td class="avatar"> <!-- msg.__room_member.avatar_url is just backwards compat, and can be removed in the future. --> <img class="avatarImage" ng-src="{{ msg.__room_member.cnt.avatar_url || msg.__room_member.avatar_url || 'img/default-profile.png' }}" width="32" height="32" title="{{msg.user_id}}" ng-hide="room.events[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/> </td> - <td ng-class="(!msg.content.membership && ('m.room.topic' !== msg.type && 'm.room.name' !== msg.type))? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'"> + <td style="vertical-align: bottom" ng-class="(!msg.content.membership && ('m.room.topic' !== msg.type && 'm.room.name' !== msg.type))? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'"> <div class="bubble" ng-dblclick="openJson(msg)"> <span ng-if="'join' === msg.content.membership && msg.changedKey === 'membership'"> {{ msg.content.displayname || members[msg.state_key].displayname || msg.state_key }} joined @@ -222,49 +250,10 @@ <div id="controlPanel"> <div id="controls"> - <table id="inputBarTable"> - <tr> - <td id="userIdCell" width="1px"> - {{ state.user_id }} - </td> - <td width="*"> - <textarea id="mainInput" rows="1" ng-enter="send()" - ng-disabled="state.permission_denied" - ng-focus="true" autocomplete="off" tab-complete command-history/> - </td> - <td id="buttonsCell"> - <button ng-click="send()" ng-disabled="state.permission_denied">Send</button> - <button m-file-input="imageFileToSend" class="extraControls" ng-disabled="state.permission_denied">Image</button> - </td> - </tr> - </table> - - <div class="extraControls"> - <span> - Invite a user: - <input ng-model="userIDToInvite" size="32" type="text" ng-enter="inviteUser()" ng-disabled="state.permission_denied" placeholder="User ID (ex:@user:homeserver)"/> - <button ng-click="inviteUser()" ng-disabled="state.permission_denied">Invite</button> - </span> - <button ng-click="leaveRoom()" ng-disabled="state.permission_denied">Leave</button> - <button ng-click="startVoiceCall()" - ng-show="(currentCall == undefined || currentCall.state == 'ended')" - ng-disabled="state.permission_denied || !isWebRTCSupported() || memberCount() != 2" - title ="{{ !isWebRTCSupported() ? 'VoIP requires webRTC but your browser does not support it' : (memberCount() == 2 ? '' : 'VoIP calls can only be made in rooms with two participants') }}" - > - Voice Call - </button> - <button ng-click="startVideoCall()" - ng-show="(currentCall == undefined || currentCall.state == 'ended')" - ng-disabled="state.permission_denied || !isWebRTCSupported() || memberCount() != 2" - title ="{{ !isWebRTCSupported() ? 'VoIP requires webRTC but your browser does not support it' : (memberCount() == 2 ? '' : 'VoIP calls can only be made in rooms with two participants') }}" - > - Video Call - </button> - <button ng-click="openRoomInfo()"> - Room Info - </button> - </div> - + <button id="attachButton" m-file-input="imageFileToSend" class="extraControls" ng-disabled="state.permission_denied"></button> + <textarea id="mainInput" rows="1" ng-enter="send()" + ng-disabled="state.permission_denied" + ng-focus="true" autocomplete="off" tab-complete command-history/> {{ feedback }} <div ng-show="state.stream_failure"> {{ state.stream_failure.data.error || "Connection failure" }} |