Fade out display on error.
[guacamole.git] / src / main / webapp / scripts / interface.js
1
2 // UI Definition
3 var GuacamoleUI = {
4
5     "viewport"    : document.getElementById("viewportClone"),
6     "display"     : document.getElementById("display"),
7     "menu"        : document.getElementById("menu"),
8     "menuControl" : document.getElementById("menuControl"),
9     "touchMenu"   : document.getElementById("touchMenu"),
10     "logo"        : document.getElementById("status-logo"),
11
12     "buttons": {
13
14         "showClipboard": document.getElementById("showClipboard"),
15         "showKeyboard" : document.getElementById("showKeyboard"),
16         "ctrlAltDelete": document.getElementById("ctrlAltDelete"),
17         "reconnect"    : document.getElementById("reconnect"),
18         "logout"       : document.getElementById("logout")
19
20     },
21
22     "containers": {
23         "state"    : document.getElementById("statusDialog"),
24         "clipboard": document.getElementById("clipboardDiv"),
25         "keyboard" : document.getElementById("keyboardContainer")
26     },
27     
28     "state"     : document.getElementById("statusText"),
29     "clipboard" : document.getElementById("clipboard")
30
31 };
32
33 // Constant UI initialization and behavior
34 (function() {
35
36     var menu_shaded = false;
37
38     var shade_interval = null;
39     var show_interval = null;
40
41     // Cache error image (might not be available when error occurs)
42     var guacErrorImage = new Image();
43     guacErrorImage.src = "images/noguacamole-logo-24.png";
44
45     // Function for adding a class to an element
46     var addClass;
47
48     // Function for removing a class from an element
49     var removeClass;
50
51     // If Node.classList is supported, implement addClass/removeClass using that
52     if (Node.classList) {
53
54         addClass = function(element, classname) {
55             element.classList.add(classname);
56         };
57         
58         removeClass = function(element, classname) {
59             element.classList.remove(classname);
60         };
61         
62     }
63
64     // Otherwise, implement own
65     else {
66
67         addClass = function(element, classname) {
68
69             // Simply add new class
70             element.className += " " + classname;
71
72         };
73         
74         removeClass = function(element, classname) {
75
76             // Filter out classes with given name
77             element.className = element.className.replace(/([^ ]+)[ ]*/g,
78                 function(match, testClassname, spaces, offset, string) {
79
80                     // If same class, remove
81                     if (testClassname == classname)
82                         return "";
83
84                     // Otherwise, allow
85                     return match;
86                     
87                 }
88             );
89
90         };
91         
92     }
93
94
95     GuacamoleUI.hideStatus = function() {
96         removeClass(document.body, "guac-error");
97         GuacamoleUI.containers.state.style.visibility = "hidden";
98         GuacamoleUI.display.style.opacity = "1";
99     };
100     
101     GuacamoleUI.showStatus = function(text) {
102         removeClass(document.body, "guac-error");
103         GuacamoleUI.containers.state.style.visibility = "visible";
104         GuacamoleUI.state.textContent = text;
105         GuacamoleUI.display.style.opacity = "1";
106     };
107     
108     GuacamoleUI.showError = function(error) {
109         addClass(document.body, "guac-error");
110         GuacamoleUI.state.textContent = error;
111         GuacamoleUI.display.style.opacity = "0.1";
112     };
113
114     GuacamoleUI.shadeMenu = function() {
115
116         if (!menu_shaded) {
117
118             var step = Math.floor(GuacamoleUI.menu.offsetHeight / 10) + 1;
119             var offset = 0;
120             menu_shaded = true;
121
122             window.clearInterval(show_interval);
123             shade_interval = window.setInterval(function() {
124
125                 offset -= step;
126                 GuacamoleUI.menu.style.top = offset + "px";
127
128                 if (offset <= -GuacamoleUI.menu.offsetHeight) {
129                     window.clearInterval(shade_interval);
130                     GuacamoleUI.menu.style.visiblity = "hidden";
131                 }
132
133             }, 30);
134         }
135
136     };
137
138     GuacamoleUI.showMenu = function() {
139
140         if (menu_shaded) {
141
142             var step = Math.floor(GuacamoleUI.menu.offsetHeight / 5) + 1;
143             var offset = -GuacamoleUI.menu.offsetHeight;
144             menu_shaded = false;
145             GuacamoleUI.menu.style.visiblity = "";
146
147             window.clearInterval(shade_interval);
148             show_interval = window.setInterval(function() {
149
150                 offset += step;
151
152                 if (offset >= 0) {
153                     offset = 0;
154                     window.clearInterval(show_interval);
155                 }
156
157                 GuacamoleUI.menu.style.top = offset + "px";
158
159             }, 30);
160         }
161
162     };
163
164     // Show/Hide clipboard
165     GuacamoleUI.buttons.showClipboard.onclick = function() {
166
167         var displayed = GuacamoleUI.containers.clipboard.style.display;
168         if (displayed != "block") {
169             GuacamoleUI.containers.clipboard.style.display = "block";
170             GuacamoleUI.buttons.showClipboard.innerHTML = "Hide Clipboard";
171         }
172         else {
173             GuacamoleUI.containers.clipboard.style.display = "none";
174             GuacamoleUI.buttons.showClipboard.innerHTML = "Show Clipboard";
175             GuacamoleUI.clipboard.onchange();
176         }
177
178     };
179
180     // Show/Hide keyboard
181     var keyboardResizeInterval = null;
182     GuacamoleUI.buttons.showKeyboard.onclick = function() {
183
184         var displayed = GuacamoleUI.containers.keyboard.style.display;
185         if (displayed != "block") {
186             GuacamoleUI.containers.keyboard.style.display = "block";
187             GuacamoleUI.buttons.showKeyboard.textContent = "Hide Keyboard";
188
189             // Automatically update size
190             window.onresize = updateKeyboardSize;
191             keyboardResizeInterval = window.setInterval(updateKeyboardSize, 30);
192
193             updateKeyboardSize();
194         }
195         else {
196             GuacamoleUI.containers.keyboard.style.display = "none";
197             GuacamoleUI.buttons.showKeyboard.textContent = "Show Keyboard";
198
199             window.onresize = null;
200             window.clearInterval(keyboardResizeInterval);
201         }
202
203     };
204
205     // Logout
206     GuacamoleUI.buttons.logout.onclick = function() {
207         window.location.href = "logout";
208     };
209
210     // Timeouts for detecting if users wants menu to open or close
211     var detectMenuOpenTimeout = null;
212     var detectMenuCloseTimeout = null;
213
214     // Clear detection timeouts
215     GuacamoleUI.resetMenuDetect = function() {
216
217         if (detectMenuOpenTimeout != null) {
218             window.clearTimeout(detectMenuOpenTimeout);
219             detectMenuOpenTimeout = null;
220         }
221
222         if (detectMenuCloseTimeout != null) {
223             window.clearTimeout(detectMenuCloseTimeout);
224             detectMenuCloseTimeout = null;
225         }
226
227     };
228
229     // Initiate detection of menu open action. If not canceled through some
230     // user event, menu will open.
231     GuacamoleUI.startMenuOpenDetect = function() {
232
233         if (!detectMenuOpenTimeout) {
234
235             // Clear detection state
236             GuacamoleUI.resetMenuDetect();
237
238             // Wait and then show menu
239             detectMenuOpenTimeout = window.setTimeout(function() {
240                 GuacamoleUI.showMenu();
241                 detectMenuOpenTimeout = null;
242             }, 325);
243
244         }
245
246     };
247
248     // Initiate detection of menu close action. If not canceled through some
249     // user event, menu will close.
250     GuacamoleUI.startMenuCloseDetect = function() {
251
252         if (!detectMenuCloseTimeout) {
253
254             // Clear detection state
255             GuacamoleUI.resetMenuDetect();
256
257             // Wait and then shade menu
258             detectMenuCloseTimeout = window.setTimeout(function() {
259                 GuacamoleUI.shadeMenu();
260                 detectMenuCloseTimeout = null;
261             }, 500);
262
263         }
264
265     };
266
267     // Show menu if mouseover any part of menu
268     GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.showMenu, true);
269
270     // Stop detecting menu state change intents if mouse is over menu
271     GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.resetMenuDetect, true);
272
273     // When mouse hovers over top of screen, start detection of intent to open menu
274     GuacamoleUI.menuControl.addEventListener('mousemove', GuacamoleUI.startMenuOpenDetect, true);
275
276     var menuShowLongPressTimeout = null;
277
278     GuacamoleUI.startLongPressDetect = function() {
279
280         if (!menuShowLongPressTimeout) {
281
282             menuShowLongPressTimeout = window.setTimeout(function() {
283                 
284                 menuShowLongPressTimeout = null;
285                 GuacamoleUI.showMenu();
286
287             }, 800);
288
289         }
290     };
291
292     GuacamoleUI.stopLongPressDetect = function() {
293         window.clearTimeout(menuShowLongPressTimeout);
294         menuShowLongPressTimeout = null;
295     };
296
297     // Detect long-press at bottom of screen
298     document.body.addEventListener('touchstart', GuacamoleUI.startLongPressDetect, true);
299
300     // Reconnect button
301     GuacamoleUI.buttons.reconnect.onclick = function() {
302         window.location.reload();
303     };
304
305     // On-screen keyboard
306     GuacamoleUI.keyboard = new Guacamole.OnScreenKeyboard("layouts/en-us-qwerty-mobile.xml");
307     GuacamoleUI.containers.keyboard.appendChild(GuacamoleUI.keyboard.getElement());
308
309     // Function for automatically updating keyboard size
310     var lastKeyboardWidth;
311     function updateKeyboardSize() {
312         var currentSize = GuacamoleUI.keyboard.getElement().offsetWidth;
313         if (lastKeyboardWidth != currentSize) {
314             GuacamoleUI.keyboard.resize(currentSize);
315             lastKeyboardWidth = currentSize;
316         }
317     };
318
319 })();
320
321 // Tie UI events / behavior to a specific Guacamole client
322 GuacamoleUI.attach = function(guac) {
323
324     var guac_display = guac.getDisplay();
325
326     // When mouse enters display, start detection of intent to close menu
327     guac_display.addEventListener('mouseover', GuacamoleUI.startMenuCloseDetect, true);
328
329     guac_display.onclick = function(e) {
330         e.preventDefault();
331         return false;
332     };
333
334     // Mouse
335     var mouse = new Guacamole.Mouse(guac_display);
336     mouse.onmousedown = mouse.onmouseup = mouse.onmousemove =
337         function(mouseState) {
338        
339             // Determine mouse position within view
340             var mouse_view_x = mouseState.x + guac_display.offsetLeft - window.pageXOffset;
341             var mouse_view_y = mouseState.y + guac_display.offsetTop  - window.pageYOffset;
342
343             // Determine viewport dimensioins
344             var view_width  = GuacamoleUI.viewport.offsetWidth;
345             var view_height = GuacamoleUI.viewport.offsetHeight;
346
347             // Determine scroll amounts based on mouse position relative to document
348
349             var scroll_amount_x;
350             if (mouse_view_x > view_width)
351                 scroll_amount_x = mouse_view_x - view_width;
352             else if (mouse_view_x < 0)
353                 scroll_amount_x = mouse_view_x;
354             else
355                 scroll_amount_x = 0;
356
357             var scroll_amount_y;
358             if (mouse_view_y > view_height)
359                 scroll_amount_y = mouse_view_y - view_height;
360             else if (mouse_view_y < 0)
361                 scroll_amount_y = mouse_view_y;
362             else
363                 scroll_amount_y = 0;
364
365             // Scroll (if necessary) to keep mouse on screen.
366             window.scrollBy(scroll_amount_x, scroll_amount_y);
367        
368             // Hide menu on movement
369             GuacamoleUI.startMenuCloseDetect();
370
371             // Stop detecting long presses if mouse is being used
372             GuacamoleUI.stopLongPressDetect();
373
374             // Send mouse event
375             guac.sendMouseState(mouseState);
376             
377         };
378
379     // Keyboard
380     var keyboard = new Guacamole.Keyboard(document);
381
382     function disableKeyboard() {
383         keyboard.onkeydown = null;
384         keyboard.onkeyup = null;
385     }
386
387     function enableKeyboard() {
388         keyboard.onkeydown = 
389             function (keysym) {
390                 guac.sendKeyEvent(1, keysym);
391             };
392
393         keyboard.onkeyup = 
394             function (keysym) {
395                 guac.sendKeyEvent(0, keysym);
396             };
397     }
398
399     // Enable keyboard by default
400     enableKeyboard();
401
402     // Handle client state change
403     guac.onstatechange = function(clientState) {
404         switch (clientState) {
405
406             // Idle
407             case 0:
408                 GuacamoleUI.showStatus("Idle.");
409                 break;
410
411             // Connecting
412             case 1:
413                 GuacamoleUI.shadeMenu();
414                 GuacamoleUI.showStatus("Connecting...");
415                 break;
416
417             // Connected + waiting
418             case 2:
419                 GuacamoleUI.showStatus("Connected, waiting for first update...");
420                 break;
421
422             // Connected
423             case 3:
424                 
425                 GuacamoleUI.hideStatus();
426                 GuacamoleUI.display.className =
427                     GuacamoleUI.display.className.replace(/guac-loading/, '');
428
429                 GuacamoleUI.menu.className = "connected";
430                 break;
431
432             // Disconnecting
433             case 4:
434                 GuacamoleUI.showStatus("Disconnecting...");
435                 break;
436
437             // Disconnected
438             case 5:
439                 GuacamoleUI.showStatus("Disconnected.");
440                 break;
441
442             // Unknown status code
443             default:
444                 GuacamoleUI.showStatus("[UNKNOWN STATUS]");
445
446         }
447     };
448
449     // Name instruction handler
450     guac.onname = function(name) {
451         document.title = name;
452     };
453
454     // Error handler
455     guac.onerror = function(error) {
456
457         // Disconnect, if connected
458         guac.disconnect();
459
460         // Display error message
461         GuacamoleUI.showError(error);
462         
463     };
464
465     // Disconnect on close
466     window.onunload = function() {
467         guac.disconnect();
468     };
469
470     // Handle clipboard events
471     GuacamoleUI.clipboard.onchange = function() {
472
473         var text = GuacamoleUI.clipboard.value;
474         guac.setClipboard(text);
475
476     };
477
478     // Ignore keypresses when clipboard is focused
479     GuacamoleUI.clipboard.onfocus = function() {
480         disableKeyboard();
481     };
482
483     // Capture keypresses when clipboard is not focused
484     GuacamoleUI.clipboard.onblur = function() {
485         enableKeyboard();
486     };
487
488     // Server copy handler
489     guac.onclipboard = function(data) {
490         GuacamoleUI.clipboard.value = data;
491     };
492
493     GuacamoleUI.keyboard.onkeydown = function(keysym) {
494         guac.sendKeyEvent(1, keysym);
495     };
496
497     GuacamoleUI.keyboard.onkeyup = function(keysym) {
498         guac.sendKeyEvent(0, keysym);
499     };
500
501     // Send Ctrl-Alt-Delete
502     GuacamoleUI.buttons.ctrlAltDelete.onclick = function() {
503
504         var KEYSYM_CTRL   = 0xFFE3;
505         var KEYSYM_ALT    = 0xFFE9;
506         var KEYSYM_DELETE = 0xFFFF;
507
508         guac.sendKeyEvent(1, KEYSYM_CTRL);
509         guac.sendKeyEvent(1, KEYSYM_ALT);
510         guac.sendKeyEvent(1, KEYSYM_DELETE);
511         guac.sendKeyEvent(0, KEYSYM_DELETE);
512         guac.sendKeyEvent(0, KEYSYM_ALT);
513         guac.sendKeyEvent(0, KEYSYM_CTRL);
514     };
515
516 };