Forked mumble-django project from https://bitbucket.org/Svedrin/mumble-django
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

280 lines
11 KiB

  1. // kate: space-indent on; indent-width 4; replace-tabs on;
  2. /**
  3. * Copyright © 2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
  4. *
  5. * Mumble-Django is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This package is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * Documentation for this channel viewer can be found here:
  16. * http://mumble-django.org/docs/api/channelviewer.html
  17. */
  18. Ext.namespace('Ext.ux');
  19. if( typeof gettext == "undefined" ){
  20. // Cope with Django's jsi18n not being available by adding dummy gettext
  21. gettext = function( text ){
  22. return text;
  23. };
  24. gettext_noop = gettext;
  25. }
  26. Ext.ux.MumbleChannelNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
  27. renderElements : function(n, a, targetNode, bulkRender){
  28. Ext.ux.MumbleUserNodeUI.superclass.renderElements.call( this, n, a, targetNode, bulkRender );
  29. Ext.DomHelper.applyStyles( this.elNode, 'position: relative' );
  30. var tpl = new Ext.DomHelper.createTemplate(
  31. '<img style="position: absolute; top: 0px; right: {pos}px;" src="{imageurl}/{icon}.png"/>'
  32. );
  33. var icons = []
  34. if( a.chandata.description != "" ) icons.push( "comment_seen" );
  35. var pos = 8;
  36. for( var i = 0; i < icons.length; i++ ){
  37. tpl.append( this.elNode, {'imageurl': a.imageurl, 'icon': icons[i], 'pos': pos} );
  38. pos += 18
  39. }
  40. }
  41. });
  42. Ext.ux.MumbleUserNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
  43. renderElements : function(n, a, targetNode, bulkRender){
  44. Ext.ux.MumbleUserNodeUI.superclass.renderElements.call( this, n, a, targetNode, bulkRender );
  45. Ext.DomHelper.applyStyles( this.elNode, 'position: relative' );
  46. var tpl = new Ext.DomHelper.createTemplate(
  47. '<img style="position: absolute; top: 0px; right: {pos}px;" src="{imageurl}/{icon}.png"/>'
  48. );
  49. var icons = []
  50. if( a.userdata.userid != -1 ) icons.push( "authenticated" );
  51. if( a.userdata.selfDeaf ) icons.push( "deafened_self" );
  52. if( a.userdata.deaf ) icons.push( "deafened_server" );
  53. if( a.userdata.selfMute ) icons.push( "muted_self" );
  54. if( a.userdata.suppress ) icons.push( "muted_suppressed" );
  55. if( a.userdata.mute ) icons.push( "muted_server" );
  56. if( a.userdata.comment != "" ) icons.push( "comment_seen" );
  57. if( a.userdata.prioritySpeaker ) icons.push( "priority_speaker" );
  58. if( a.userdata.recording ) icons.push( "recording" );
  59. var pos = 8;
  60. for( var i = 0; i < icons.length; i++ ){
  61. tpl.append( this.elNode, {'imageurl': a.imageurl, 'icon': icons[i], 'pos': pos} );
  62. pos += 18
  63. }
  64. }
  65. });
  66. function cmp_channels( left, rite ){
  67. // Compare two channels, first by position, and if that equals, by name.
  68. if( typeof left.position != "undefined" && typeof rite.position != "undefined" ){
  69. byorder = left.position - rite.position;
  70. if( byorder != 0 )
  71. return byorder;
  72. }
  73. return left.name.localeCompare(rite.name);
  74. }
  75. function cmp_names( left, rite ){
  76. return left.name.localeCompare(rite.name);
  77. }
  78. Ext.ux.MumbleChannelViewer = function( config ){
  79. Ext.apply( this, config );
  80. Ext.applyIf( this, {
  81. title: gettext("Channel Viewer"),
  82. refreshInterval: 30000,
  83. idleInterval: 2,
  84. usersAboveChannels: false,
  85. autoScroll: true,
  86. enableDD: false, // Users need to enable this explicitly
  87. root: {
  88. text: gettext("Loading..."),
  89. leaf: true
  90. },
  91. listeners: {}
  92. });
  93. Ext.applyIf( this.listeners, {
  94. dragdrop: function( tree, node, targetdd, ev ){
  95. if( typeof node.attributes.userdata != "undefined" )
  96. tree.fireEvent("moveUser", tree, node.attributes.userdata, targetdd.dragOverData.target.attributes.chandata);
  97. else if( typeof node.attributes.chandata != "undefined" )
  98. tree.fireEvent("moveChannel", tree, node.attributes.chandata, targetdd.dragOverData.target.attributes.chandata);
  99. }
  100. });
  101. Ext.applyIf( this, {
  102. // This stuff needs the above applied already
  103. bbar: [ gettext("Auto-Refresh")+':', {
  104. xtype: "checkbox",
  105. ref: "../cbAutoRefresh",
  106. scope: this,
  107. handler: this.setAutoRefresh,
  108. checked: (this.refreshInterval > 0),
  109. }, {
  110. xtype: "numberfield",
  111. width: 30,
  112. value: this.refreshInterval / 1000,
  113. ref: "../nfAutoRefreshInterval",
  114. scope: this,
  115. selectOnFocus: true,
  116. listeners: {
  117. render: function(c) {
  118. Ext.QuickTips.register({
  119. target: c.getEl(),
  120. text: gettext('Enter the interval in seconds in which the channel viewer should refresh and hit Enter.')
  121. });
  122. },
  123. specialkey: function( field, ev ){
  124. if( ev.getKey() == ev.ENTER ){
  125. this.scope.setAutoRefresh(); // lawl
  126. this.blur();
  127. }
  128. }
  129. },
  130. }, gettext("Seconds"), '->', {
  131. xtype: "button",
  132. text: gettext("Refresh"),
  133. handler: this.refresh,
  134. scope: this
  135. }]
  136. } );
  137. Ext.ux.MumbleChannelViewer.superclass.constructor.call( this );
  138. this.addEvents({
  139. 'moveUser': true,
  140. 'moveChannel': true
  141. });
  142. this.autoRefreshId = 0;
  143. this.on("afterrender", function(){
  144. this.setAutoRefresh();
  145. if( this.refreshInterval == 0 )
  146. this.refresh();
  147. }, this);
  148. }
  149. Ext.extend( Ext.ux.MumbleChannelViewer, Ext.tree.TreePanel, {
  150. setAutoRefresh: function(){
  151. if( this.autoRefreshId != 0 ){
  152. clearTimeout( this.autoRefreshId );
  153. }
  154. if( this.cbAutoRefresh.getValue() ){
  155. this.refreshInterval = this.nfAutoRefreshInterval.getValue() * 1000;
  156. this.autoRefresh();
  157. }
  158. else{
  159. this.refreshInterval = 0;
  160. }
  161. },
  162. autoRefresh: function(){
  163. this.refresh();
  164. if( this.refreshInterval > 0 ){
  165. this.autoRefreshId = this.autoRefresh.defer( this.refreshInterval, this );
  166. }
  167. },
  168. refresh: function(){
  169. var conn = new Ext.data.Connection();
  170. conn.request({
  171. url: this.source_url,
  172. scope: this,
  173. success: function( resp, opt ){
  174. var respdata = Ext.decode( resp.responseText );
  175. var root = {
  176. text: respdata.name,
  177. nodeType: 'async',
  178. id: "mumbroot",
  179. leaf: false,
  180. icon: this.imageurl+'/mumble.16x16.png',
  181. children: [],
  182. chandata: respdata.root,
  183. uiProvider: Ext.ux.MumbleChannelNodeUI,
  184. imageurl: this.imageurl
  185. };
  186. tree = this;
  187. function populateNode( node, json ){
  188. var subchan_users = 0;
  189. var popChannels = function(){
  190. json.channels.sort(cmp_channels);
  191. for( var i = 0; i < json.channels.length; i++ ){
  192. var child = {
  193. text: json.channels[i].name,
  194. id: ("channel_" + json.channels[i].id),
  195. nodeType: 'async',
  196. allowDrag: true,
  197. allowDrop: true,
  198. draggable: true,
  199. icon: tree.imageurl+'/channel.png',
  200. children: [],
  201. uiProvider: Ext.ux.MumbleChannelNodeUI,
  202. chandata: json.channels[i],
  203. imageurl: tree.imageurl
  204. };
  205. node.children.push( child );
  206. subchan_users += populateNode( child, json.channels[i] );
  207. }
  208. }
  209. var popUsers = function(){
  210. json.users.sort(cmp_names);
  211. for( var i = 0; i < json.users.length; i++ ){
  212. var child = {
  213. text: json.users[i].name,
  214. id: ("user_" + json.users[i].session),
  215. nodeType: 'async',
  216. leaf: true,
  217. allowDrag: true,
  218. draggable: true,
  219. uiProvider: Ext.ux.MumbleUserNodeUI,
  220. userdata: json.users[i],
  221. imageurl: tree.imageurl
  222. };
  223. if( json.users[i].idlesecs <= tree.idleInterval )
  224. child.icon = tree.imageurl+'/talking_on.png';
  225. else
  226. child.icon = tree.imageurl+'/talking_off.png';
  227. node.leaf = false;
  228. node.children.push( child );
  229. }
  230. }
  231. if( tree.usersAboveChannels ){
  232. popUsers();
  233. popChannels();
  234. }
  235. else{
  236. popChannels();
  237. popUsers();
  238. }
  239. if( json.id == 0 || json.users.length > 0 || subchan_users )
  240. node.expanded = true;
  241. return subchan_users + json.users.length;
  242. }
  243. populateNode( root, respdata.root );
  244. this.setRootNode( root );
  245. },
  246. failure: function( resp, opt ){
  247. if( resp.isTimeout === true )
  248. // Ignore, happens from time to time
  249. return;
  250. if( this.refreshInterval > 0 )
  251. this.cbAutoRefresh.setValue(false);
  252. Ext.Msg.show({
  253. title: gettext("Update error"),
  254. msg: gettext("Querying the server failed, so the channel viewer has not been updated."),
  255. icon: Ext.MessageBox.ERROR,
  256. buttons: Ext.MessageBox.OK
  257. });
  258. },
  259. });
  260. },
  261. } );
  262. Ext.reg( 'mumblechannelviewer', Ext.ux.MumbleChannelViewer );