Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / file_manager / foreground / js / ui / progress_center_panel.js
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 'use strict';
6
7 /**
8  * Item element of the progress center.
9  * @param {HTMLDocument} document Document which the new item belongs to.
10  * @constructor
11  */
12 function ProgressCenterItemElement(document) {
13   var label = document.createElement('label');
14   label.className = 'label';
15
16   var progressBarIndicator = document.createElement('div');
17   progressBarIndicator.className = 'progress-track';
18
19   var progressBar = document.createElement('div');
20   progressBar.className = 'progress-bar';
21   progressBar.appendChild(progressBarIndicator);
22
23   var progressFrame = document.createElement('div');
24   progressFrame.className = 'progress-frame';
25   progressFrame.appendChild(label);
26   progressFrame.appendChild(progressBar);
27
28   var cancelButton = document.createElement('button');
29   cancelButton.className = 'cancel';
30   cancelButton.setAttribute('tabindex', '-1');
31
32   var buttonFrame = document.createElement('div');
33   buttonFrame.className = 'button-frame';
34   buttonFrame.appendChild(cancelButton);
35
36   var itemElement = document.createElement('li');
37   itemElement.appendChild(progressFrame);
38   itemElement.appendChild(buttonFrame);
39
40   return ProgressCenterItemElement.decorate(itemElement);
41 }
42
43 /**
44  * Ensures the animation triggers.
45  *
46  * @param {function()} callback Function to set the transition end properties.
47  * @return {function()} Function to cancel the request.
48  * @private
49  */
50 ProgressCenterItemElement.safelySetAnimation_ = function(callback) {
51   var requestId = requestAnimationFrame(function() {
52     // The transitoin start properties currently set are rendered at this frame.
53     // And the transition end properties set by the callback is rendered at the
54     // next frame.
55     requestId = requestAnimationFrame(callback);
56   });
57   return function() {
58     cancelAnimationFrame(requestId);
59   };
60 };
61
62 /**
63  * Event triggered when the item should be dismissed.
64  * @type {string}
65  * @const
66  */
67 ProgressCenterItemElement.PROGRESS_ANIMATION_END_EVENT = 'progressAnimationEnd';
68
69 /**
70  * Decoreates the given element as a progress item.
71  * @param {HTMLElement} element Item to be decoreated.
72  * @return {ProgressCenterItemElement} Decoreated item.
73  */
74 ProgressCenterItemElement.decorate = function(element) {
75   element.__proto__ = ProgressCenterItemElement.prototype;
76   element.state_ = ProgressItemState.PROGRESSING;
77   element.track_ = element.querySelector('.progress-track');
78   element.track_.addEventListener('webkitTransitionEnd',
79                                   element.onTransitionEnd_.bind(element));
80   element.cancelTransition_ = null;
81   return element;
82 };
83
84 ProgressCenterItemElement.prototype = {
85   __proto__: HTMLDivElement.prototype,
86   get quiet() {
87     return this.classList.contains('quiet');
88   }
89 };
90
91 /**
92  * Updates the element view according to the item.
93  * @param {ProgressCenterItem} item Item to be referred for the update.
94  * @param {boolean} animated Whether the progress width is applied as animated
95  *     or not.
96  */
97 ProgressCenterItemElement.prototype.update = function(item, animated) {
98   // Set element attributes.
99   this.state_ = item.state;
100   this.setAttribute('data-progress-id', item.id);
101   this.classList.toggle('error', item.state === ProgressItemState.ERROR);
102   this.classList.toggle('cancelable', item.cancelable);
103   this.classList.toggle('single', item.single);
104   this.classList.toggle('quiet', item.quiet);
105
106   // Set label.
107   if (this.state_ === ProgressItemState.PROGRESSING ||
108       this.state_ === ProgressItemState.ERROR) {
109     this.querySelector('label').textContent = item.message;
110   } else if (this.state_ === ProgressItemState.CANCELED) {
111     this.querySelector('label').textContent = '';
112   }
113
114   // Cancel the previous property set.
115   if (this.cancelTransition_) {
116     this.cancelTransition_();
117     this.cancelTransition_ = null;
118   }
119
120   // Set track width.
121   var setWidth = function(nextWidthFrame) {
122     var currentWidthRate = parseInt(this.track_.style.width);
123     // Prevent assigning the same width to avoid stopping the animation.
124     // animated == false may be intended to cancel the animation, so in that
125     // case, the assignment should be done.
126     if (currentWidthRate === nextWidthFrame && animated)
127       return;
128     this.track_.hidden = false;
129     this.track_.style.width = nextWidthFrame + '%';
130     this.track_.classList.toggle('animated', animated);
131   }.bind(this, item.progressRateInPercent);
132
133   if (animated) {
134     this.cancelTransition_ =
135         ProgressCenterItemElement.safelySetAnimation_(setWidth);
136   } else {
137     // For animated === false, we should call setWidth immediately to cancel the
138     // animation, otherwise the animation may complete before canceling it.
139     setWidth();
140   }
141 };
142
143 /**
144  * Resets the item.
145  */
146 ProgressCenterItemElement.prototype.reset = function() {
147   this.track_.hidden = true;
148   this.track_.width = '';
149   this.state_ = ProgressItemState.PROGRESSING;
150 };
151
152 /**
153  * Handles transition end events.
154  * @param {Event} event Transition end event.
155  * @private
156  */
157 ProgressCenterItemElement.prototype.onTransitionEnd_ = function(event) {
158   if (event.propertyName !== 'width')
159     return;
160   this.track_.classList.remove('animated');
161   this.dispatchEvent(new Event(
162       ProgressCenterItemElement.PROGRESS_ANIMATION_END_EVENT,
163       {bubbles: true}));
164 };
165
166 /**
167  * Progress center panel.
168  *
169  * @param {HTMLElement} element DOM Element of the process center panel.
170  * @constructor
171  */
172 function ProgressCenterPanel(element) {
173   /**
174    * Root element of the progress center.
175    * @type {HTMLElement}
176    * @private
177    */
178   this.element_ = element;
179
180   /**
181    * Open view containing multiple progress items.
182    * @type {HTMLElement}
183    * @private
184    */
185   this.openView_ = this.element_.querySelector('#progress-center-open-view');
186
187   /**
188    * Close view that is a summarized progress item.
189    * @type {HTMLElement}
190    * @private
191    */
192   this.closeView_ = ProgressCenterItemElement.decorate(
193       this.element_.querySelector('#progress-center-close-view'));
194
195   /**
196    * Toggle animation rule of the progress center.
197    * @type {CSSKeyFrameRule}
198    * @private
199    */
200   this.toggleAnimation_ = ProgressCenterPanel.getToggleAnimation_(
201       element.ownerDocument);
202
203   /**
204    * Item group for normal priority items.
205    * @type {ProgressCenterItemGroup}
206    * @private
207    */
208   this.normalItemGroup_ = new ProgressCenterItemGroup('normal', false);
209
210   /**
211    * Item group for low priority items.
212    * @type {ProgressCenterItemGroup}
213    * @private
214    */
215   this.quietItemGroup_ = new ProgressCenterItemGroup('quiet', true);
216
217   /**
218    * Queries to obtains items for each group.
219    * @type {Object.<string, string>}
220    * @private
221    */
222   this.itemQuery_ = Object.seal({
223     normal: 'li:not(.quiet)',
224     quiet: 'li.quiet'
225   });
226
227   /**
228    * Timeout IDs of the inactive state of each group.
229    * @type {Object.<string, number?>}
230    * @private
231    */
232   this.timeoutId_ = Object.seal({
233     normal: null,
234     quiet: null
235   });
236
237   /**
238    * Callback to becalled with the ID of the progress item when the cancel
239    * button is clicked.
240    */
241   this.cancelCallback = null;
242
243   Object.seal(this);
244
245   // Register event handlers.
246   element.addEventListener('click', this.onClick_.bind(this));
247   element.addEventListener(
248       'webkitAnimationEnd', this.onToggleAnimationEnd_.bind(this));
249   element.addEventListener(
250       ProgressCenterItemElement.PROGRESS_ANIMATION_END_EVENT,
251       this.onItemAnimationEnd_.bind(this));
252 }
253
254 /**
255  * Obtains the toggle animation keyframes rule from the document.
256  * @param {HTMLDocument} document Document containing the rule.
257  * @return {CSSKeyFrameRules} Animation rule.
258  * @private
259  */
260 ProgressCenterPanel.getToggleAnimation_ = function(document) {
261   for (var i = 0; i < document.styleSheets.length; i++) {
262     var styleSheet = document.styleSheets[i];
263     for (var j = 0; j < styleSheet.cssRules.length; j++) {
264       var rule = styleSheet.cssRules[j];
265       if (rule.type === CSSRule.WEBKIT_KEYFRAMES_RULE &&
266           rule.name === 'progress-center-toggle') {
267         return rule;
268       }
269     }
270   }
271   throw new Error('The progress-center-toggle rules is not found.');
272 };
273
274 /**
275  * The default amount of milliseconds time, before a progress item will reset
276  * after the last complete.
277  * @type {number}
278  * @private
279  * @const
280  */
281 ProgressCenterPanel.RESET_DELAY_TIME_MS_ = 5000;
282
283 /**
284  * Updates an item to the progress center panel.
285  * @param {!ProgressCenterItem} item Item including new contents.
286  */
287 ProgressCenterPanel.prototype.updateItem = function(item) {
288   var targetGroup = this.getGroupForItem_(item);
289
290   // Update the item.
291   var oldState = targetGroup.state;
292   targetGroup.update(item);
293   this.handleGroupStateChange_(targetGroup, oldState, targetGroup.state);
294
295   // Update an open view item.
296   var newItem = targetGroup.getItem(item.id);
297   var itemElement = this.getItemElement_(item.id);
298   if (newItem) {
299     if (!itemElement) {
300       itemElement = new ProgressCenterItemElement(this.element_.ownerDocument);
301       this.openView_.insertBefore(itemElement, this.openView_.firstNode);
302     }
303     itemElement.update(newItem, targetGroup.isAnimated(item.id));
304   } else {
305     if (itemElement)
306       itemElement.parentNode.removeChild(itemElement);
307   }
308
309   // Update the close view.
310   this.updateCloseView_();
311 };
312
313 /**
314  * Handles the item animation end.
315  * @param {Event} event Item animation end event.
316  * @private
317  */
318 ProgressCenterPanel.prototype.onItemAnimationEnd_ = function(event) {
319   var targetGroup = event.target.classList.contains('quiet') ?
320       this.quietItemGroup_ : this.normalItemGroup_;
321   var oldState = targetGroup.state;
322   if (event.target === this.closeView_) {
323     targetGroup.completeSummarizedItemAnimation();
324   } else {
325     var itemId = event.target.getAttribute('data-progress-id');
326     targetGroup.completeItemAnimation(itemId);
327     var newItem = targetGroup.getItem(itemId);
328     var itemElement = this.getItemElement_(itemId);
329     if (!newItem && itemElement)
330       itemElement.parentNode.removeChild(itemElement);
331   }
332   this.handleGroupStateChange_(targetGroup, oldState, targetGroup.state);
333   this.updateCloseView_();
334 };
335
336 /**
337  * Handles the state change of group.
338  * @param {ProgressCenterItemGroup} group Item group.
339  * @param {ProgressCenterItemGroup.State} oldState Old state of the group.
340  * @param {ProgressCenterItemGroup.State} newState New state of the group.
341  * @private
342  */
343 ProgressCenterPanel.prototype.handleGroupStateChange_ =
344     function(group, oldState, newState) {
345   if (oldState === ProgressCenterItemGroup.State.INACTIVE) {
346     clearTimeout(this.timeoutId_[group.name]);
347     this.timeoutId_[group.name] = null;
348     var elements =
349         this.openView_.querySelectorAll(this.itemQuery_[group.name]);
350     for (var i = 0; i < elements.length; i++) {
351       elements[i].parentNode.removeChild(elements[i]);
352     }
353   }
354   if (newState === ProgressCenterItemGroup.State.INACTIVE) {
355     this.timeoutId_[group.name] = setTimeout(function() {
356       var inOldState = group.state;
357       group.endInactive();
358       this.handleGroupStateChange_(group, inOldState, group.state);
359       this.updateCloseView_();
360     }.bind(this), ProgressCenterPanel.RESET_DELAY_TIME_MS_);
361   }
362 };
363
364 /**
365  * Updates the close view.
366  * @private
367  */
368 ProgressCenterPanel.prototype.updateCloseView_ = function() {
369   // Try to use the normal summarized item.
370   var normalSummarizedItem =
371       this.normalItemGroup_.getSummarizedItem(this.quietItemGroup_.numErrors);
372   if (normalSummarizedItem) {
373     // If the quiet animation is overrided by normal summarized item, discard
374     // the quiet animation.
375     if (this.quietItemGroup_.isSummarizedAnimated()) {
376       var oldState = this.quietItemGroup_.state;
377       this.quietItemGroup_.completeSummarizedItemAnimation();
378       this.handleGroupStateChange_(this.quietItemGroup_,
379                                    oldState,
380                                    this.quietItemGroup_.state);
381     }
382
383     // Update the view state.
384     this.closeView_.update(normalSummarizedItem,
385                            this.normalItemGroup_.isSummarizedAnimated());
386     this.element_.hidden = false;
387     return;
388   }
389
390   // Try to use the quiet summarized item.
391   var quietSummarizedItem =
392       this.quietItemGroup_.getSummarizedItem(this.normalItemGroup_.numErrors);
393   if (quietSummarizedItem) {
394     this.closeView_.update(quietSummarizedItem,
395                            this.quietItemGroup_.isSummarizedAnimated());
396     this.element_.hidden = false;
397     return;
398   }
399
400   // Try to use the error summarized item.
401   var errorSummarizedItem = ProgressCenterItemGroup.getSummarizedErrorItem(
402       this.normalItemGroup_, this.quietItemGroup_);
403   if (errorSummarizedItem) {
404     this.closeView_.update(errorSummarizedItem, false);
405     this.element_.hidden = false;
406     return;
407   }
408
409   // Hide the progress center because there is no items to show.
410   this.closeView_.reset();
411   this.element_.hidden = true;
412   this.element_.classList.remove('opened');
413 };
414
415 /**
416  * Gets an item element having the specified ID.
417  * @param {string} id progress item ID.
418  * @return {HTMLElement} Item element having the ID.
419  * @private
420  */
421 ProgressCenterPanel.prototype.getItemElement_ = function(id) {
422   var query = 'li[data-progress-id="' + id + '"]';
423   return this.openView_.querySelector(query);
424 };
425
426 /**
427  * Obtains the group for the item.
428  * @param {ProgressCenterItem} item Progress item.
429  * @return {ProgressCenterItemGroup} Item group that should contain the item.
430  * @private
431  */
432 ProgressCenterPanel.prototype.getGroupForItem_ = function(item) {
433   return item.quiet ? this.quietItemGroup_ : this.normalItemGroup_;
434 };
435
436 /**
437  * Handles the animation end event of the progress center.
438  * @param {Event} event Animation end event.
439  * @private
440  */
441 ProgressCenterPanel.prototype.onToggleAnimationEnd_ = function(event) {
442   // Transition end of the root element's height.
443   if (event.target === this.element_ &&
444       event.animationName === 'progress-center-toggle') {
445     this.element_.classList.remove('animated');
446     return;
447   }
448 };
449
450 /**
451  * Handles the click event.
452  * @param {Event} event Click event.
453  * @private
454  */
455 ProgressCenterPanel.prototype.onClick_ = function(event) {
456   // Toggle button.
457   if (event.target.classList.contains('open') ||
458       event.target.classList.contains('close')) {
459     // If the progress center has already animated, just return.
460     if (this.element_.classList.contains('animated'))
461       return;
462
463     // Obtains current and target height.
464     var currentHeight;
465     var targetHeight;
466     if (this.element_.classList.contains('opened')) {
467       currentHeight = this.openView_.getBoundingClientRect().height;
468       targetHeight = this.closeView_.getBoundingClientRect().height;
469     } else {
470       currentHeight = this.closeView_.getBoundingClientRect().height;
471       targetHeight = this.openView_.getBoundingClientRect().height;
472     }
473
474     // Set styles for animation.
475     this.toggleAnimation_.cssRules[0].style.height = currentHeight + 'px';
476     this.toggleAnimation_.cssRules[1].style.height = targetHeight + 'px';
477     this.element_.classList.add('animated');
478     this.element_.classList.toggle('opened');
479     return;
480   }
481
482   // Cancel button.
483   if (event.target.classList.contains('cancel')) {
484     var itemElement = event.target.parentNode.parentNode;
485     if (this.cancelCallback) {
486       var id = itemElement.getAttribute('data-progress-id');
487       this.cancelCallback(id);
488     }
489   }
490 };