Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / file_manager / foreground / js / progress_center_item_group.js
1 // Copyright 2014 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  * Group of progress item in the progress center panels.
9  *
10  * This is responsible for generating the summarized item and managing lifetime
11  * of error items.
12  * @param {string} name Name of the group.
13  * @param {boolean} quiet Whether the group is for quiet items or not.
14  * @constructor
15  */
16 function ProgressCenterItemGroup(name, quiet) {
17   /**
18    * Name of the group.
19    * @type {string}
20    */
21   this.name = name;
22
23   /**
24    * Whether the group is for quiet items or not.
25    * @type {boolean}
26    * @private
27    */
28   this.quiet_ = quiet;
29
30   /**
31    * State of the group.
32    * @type {ProgressCenterItemGroup.State}
33    * @private
34    */
35   this.state_ = ProgressCenterItemGroup.State.EMPTY;
36
37   /**
38    * Items that are progressing, or completed but still animated.
39    * Key is item ID.
40    * @type {Object.<string, ProgressCenterItem>}
41    * @private
42    */
43   this.items_ = {};
44
45   /**
46    * Set of animated state of items. Key is item ID and value is whether the
47    * item is animated or not.
48    * @type {Object.<string, boolean>}
49    * @private
50    */
51   this.animated_ = {};
52
53   /**
54    * Last summarized item.
55    * @type {ProgressCenterItem}
56    * @private
57    */
58   this.summarizedItem_ = null;
59
60   /**
61    * Whether the summarized item is animated or not.
62    * @type {boolean}
63    * @private
64    */
65   this.summarizedItemAnimated_ = false;
66
67   /**
68    * Total maximum progress value of items already completed and removed from
69    * this.items_.
70    * @type {number}
71    * @private
72    */
73   this.totalProgressMax_ = 0;
74
75   /**
76    * Total progress value of items already completed and removed from
77    * this.items_.
78    * @type {number}
79    * @private
80    */
81   this.totalProgressValue_ = 0;
82
83   Object.seal(this);
84 }
85
86 /**
87  * State of ProgressCenterItemGroup.
88  * @enum {string}
89  * @const
90  */
91 ProgressCenterItemGroup.State = Object.freeze({
92   // Group has no items.
93   EMPTY: 'empty',
94   // Group has at least 1 progressing item.
95   ACTIVE: 'active',
96   // Group has no progressing items but still shows error items.
97   INACTIVE: 'inactive'
98 });
99
100 /**
101  * Makes the summarized item for the groups.
102  *
103  * When a group has only error items, getSummarizedItem of the item returns
104  * null. Basically the first result of the groups that the progress center panel
105  * contains is used as a summarized item. But If all the group returns null, the
106  * progress center panel generates the summarized item by using the method.
107  *
108  * @param {Array.<ProgressCenterItemGroup>} var_groups List of groups.
109  * @return {ProgressCenterItem} Summarized item.
110  */
111 ProgressCenterItemGroup.getSummarizedErrorItem = function(var_groups) {
112   var groups = Array.prototype.slice.call(arguments);
113   var errorItems = [];
114   for (var i = 0; i < groups.length; i++) {
115     for (var id in groups[i].items_) {
116       var item = groups[i].items_[id];
117       if (item.state === ProgressItemState.ERROR)
118         errorItems.push(item);
119     }
120   }
121   if (errorItems.length === 0)
122     return null;
123
124   if (errorItems.length === 1)
125     return errorItems[0].clone();
126
127   var item = new ProgressCenterItem();
128   item.state = ProgressItemState.ERROR;
129   item.message = strf('ERROR_PROGRESS_SUMMARY_PLURAL',
130                       errorItems.length);
131   item.single = false;
132   return item;
133 };
134
135 /**
136  * Obtains Whether the item should be animated or not.
137  * @param {boolean} previousAnimated Whether the item is previously animated or
138  *     not.
139  * @param {ProgressCenterItem} previousItem Item before updating.
140  * @param {ProgressCenterItem} item New item.
141  * @return {boolean} Whether the item should be animated or not.
142  * @private
143  */
144 ProgressCenterItemGroup.shouldAnimate_ = function(
145     previousAnimated, previousItem, item) {
146   if (!previousItem || !item || previousItem.quiet || item.quiet)
147     return false;
148   if (previousItem.progressRateInPercent < item.progressRateInPercent)
149     return true;
150   if (previousAnimated &&
151       previousItem.progressRateInPercent === item.progressRateInPercent)
152     return true;
153   return false;
154 };
155
156 ProgressCenterItemGroup.prototype = {
157   /**
158    * @return {ProgressCenterItemGroup.State} State of the group.
159    */
160   get state() {
161     return this.state_;
162   },
163
164   /**
165    * @return {number} Number of error items that the group contains.
166    */
167   get numErrors() {
168     var result = 0;
169     for (var id in this.items_) {
170       if (this.items_[id].state === ProgressItemState.ERROR)
171         result++;
172     }
173     return result;
174   }
175 };
176
177 /**
178  * Obtains the progressing (or completed but animated) item.
179  *
180  * @param {string} id Item ID.
181  * @return {ProgressCenterItem} Item having the ID.
182  */
183 ProgressCenterItemGroup.prototype.getItem = function(id) {
184   return this.items_[id] || null;
185 };
186
187 /**
188  * Obtains whether the item should be animated or not.
189  * @param {string} id Item ID.
190  * @return {boolean} Whether the item should be animated or not.
191  */
192 ProgressCenterItemGroup.prototype.isAnimated = function(id) {
193   return !!this.animated_[id];
194 };
195
196 /**
197  * Obtains whether the summarized item should be animated or not.
198  * @return {boolean} Whether the summarized item should be animated or not.
199  */
200 ProgressCenterItemGroup.prototype.isSummarizedAnimated = function() {
201   return this.summarizedItemAnimated_;
202 };
203
204 /**
205  * Starts item update.
206  * Marks the given item as updating.
207  * @param {ProgressCenterItem} item Item containing updated information.
208  */
209 ProgressCenterItemGroup.prototype.update = function(item) {
210   // If the group is inactive, go back to the empty state.
211   this.endInactive();
212
213   // Compares the current state and the new state to check if the update is
214   // valid or not.
215   var previousItem = this.items_[item.id];
216   switch (item.state) {
217     case ProgressItemState.ERROR:
218       if (previousItem && previousItem.state !== ProgressItemState.PROGRESSING)
219         return;
220       if (this.state_ === ProgressCenterItemGroup.State.EMPTY)
221         this.state_ = ProgressCenterItemGroup.State.INACTIVE;
222       this.items_[item.id] = item.clone();
223       this.animated_[item.id] = false;
224       this.summarizedItem_ = null;
225       break;
226
227     case ProgressItemState.PROGRESSING:
228     case ProgressItemState.COMPLETED:
229       if ((!previousItem && item.state === ProgressItemState.COMPLETED) ||
230           (previousItem &&
231            previousItem.state !== ProgressItemState.PROGRESSING))
232         return;
233       if (this.state_ === ProgressCenterItemGroup.State.EMPTY)
234         this.state_ = ProgressCenterItemGroup.State.ACTIVE;
235       this.items_[item.id] = item.clone();
236       this.animated_[item.id] = ProgressCenterItemGroup.shouldAnimate_(
237           !!this.animated_[item.id],
238           previousItem,
239           item);
240       if (!this.animated_[item.id])
241         this.completeItemAnimation(item.id);
242       break;
243
244     case ProgressItemState.CANCELED:
245       if (!previousItem ||
246           previousItem.state !== ProgressItemState.PROGRESSING)
247         return;
248       delete this.items_[item.id];
249       this.animated_[item.id] = false;
250       this.summarizedItem_ = null;
251   }
252
253   // Update the internal summarized item cache.
254   var previousSummarizedItem = this.summarizedItem_;
255   this.summarizedItem_ = this.getSummarizedItem(0);
256   this.summarizedItemAnimated_ = ProgressCenterItemGroup.shouldAnimate_(
257       !!this.summarizedItemAnimated_,
258       previousSummarizedItem,
259       this.summarizedItem_);
260   if (!this.summarizedItemAnimated_)
261     this.completeSummarizedItemAnimation();
262 };
263
264 /**
265  * Notifies the end of the item's animation to the group.
266  * If all the items except error items completes, the group enter the inactive
267  * state.
268  * @param {string} id Item ID.
269  */
270 ProgressCenterItemGroup.prototype.completeItemAnimation = function(id) {
271   if (this.state_ !== ProgressCenterItemGroup.State.ACTIVE)
272     return;
273
274   this.animated_[id] = false;
275   if (this.items_[id].state === ProgressItemState.COMPLETED) {
276     this.totalProgressValue_ += (this.items_[id].progressValue || 0.0);
277     this.totalProgressMax_ += (this.items_[id].progressMax || 0.0);
278     delete this.items_[id];
279     this.tryEndActive_();
280   }
281 };
282
283 /**
284  * Notifies the end of the summarized item's animation.
285  * This may update summarized view. (1 progressing + 1 error -> 1 error)
286  */
287 ProgressCenterItemGroup.prototype.completeSummarizedItemAnimation = function() {
288   this.summarizedItemAnimated_ = false;
289   this.tryEndActive_();
290 };
291
292 /**
293  * Obtains the summary of the set.
294  * @param {number} numOtherErrors Number of errors contained by other groups.
295  * @return {ProgressCenterItem} Item.
296  */
297 ProgressCenterItemGroup.prototype.getSummarizedItem =
298     function(numOtherErrors) {
299   if (this.state_ === ProgressCenterItemGroup.State.EMPTY ||
300       this.state_ === ProgressCenterItemGroup.State.INACTIVE)
301     return null;
302
303   var summarizedItem = new ProgressCenterItem();
304   summarizedItem.quiet = this.quiet_;
305   summarizedItem.progressMax += this.totalProgressMax_;
306   summarizedItem.progressValue += this.totalProgressValue_;
307   var progressingItems = [];
308   var errorItems = [];
309   var numItems = 0;
310
311   for (var id in this.items_) {
312     var item = this.items_[id];
313     numItems++;
314
315     // Count states.
316     switch (item.state) {
317       case ProgressItemState.PROGRESSING:
318       case ProgressItemState.COMPLETED:
319         progressingItems.push(item);
320         break;
321       case ProgressItemState.ERROR:
322         errorItems.push(item);
323         continue;
324     }
325
326     // If all of the progressing items have the same type, then use
327     // it. Otherwise use TRANSFER, since it is the most generic.
328     if (summarizedItem.type === null)
329       summarizedItem.type = item.type;
330     else if (summarizedItem.type !== item.type)
331       summarizedItem.type = ProgressItemType.TRANSFER;
332
333     // Sum up the progress values.
334     summarizedItem.progressMax += item.progressMax;
335     summarizedItem.progressValue += item.progressValue;
336   }
337
338   // Returns 1 item.
339   if (progressingItems.length === 1 &&
340       errorItems.length + numOtherErrors === 0) {
341     summarizedItem.id = progressingItems[0].id;
342     summarizedItem.cancelCallback = progressingItems[0].cancelCallback;
343     summarizedItem.message = progressingItems[0].message;
344     summarizedItem.state = progressingItems[0].state;
345     return summarizedItem;
346   }
347
348   // Returns integrated items.
349   if (progressingItems.length > 0) {
350     var numErrors = errorItems.length + numOtherErrors;
351     var messages = [];
352     switch (summarizedItem.type) {
353       case ProgressItemType.COPY:
354         messages.push(str('COPY_PROGRESS_SUMMARY'));
355         break;
356       case ProgressItemType.MOVE:
357         messages.push(str('MOVE_PROGRESS_SUMMARY'));
358         break;
359       case ProgressItemType.DELETE:
360         messages.push(str('DELETE_PROGRESS_SUMMARY'));
361         break;
362       case ProgressItemType.ZIP:
363         messages.push(str('ZIP_PROGRESS_SUMMARY'));
364         break;
365       case ProgressItemType.SYNC:
366         messages.push(str('SYNC_PROGRESS_SUMMARY'));
367         break;
368       case ProgressItemType.TRANSFER:
369         messages.push(str('TRANSFER_PROGRESS_SUMMARY'));
370         break;
371     }
372     if (numErrors === 1)
373       messages.push(str('ERROR_PROGRESS_SUMMARY'));
374     else if (numErrors > 1)
375       messages.push(strf('ERROR_PROGRESS_SUMMARY_PLURAL', numErrors));
376     summarizedItem.single = false;
377     summarizedItem.message = messages.join(' ');
378     summarizedItem.state = ProgressItemState.PROGRESSING;
379     return summarizedItem;
380   }
381
382   // Returns complete items.
383   summarizedItem.state = ProgressItemState.COMPLETED;
384   return summarizedItem;
385 };
386
387 /**
388  * Goes back to the EMPTY state from the INACTIVE state. Removes all the items.
389  * If the current state is not the INACTIVE, nothing happens.
390  */
391 ProgressCenterItemGroup.prototype.endInactive = function() {
392   if (this.state_ !== ProgressCenterItemGroup.State.INACTIVE)
393     return;
394   this.items_ = {};
395   this.animated_ = {};
396   this.summarizedItem_ = null;
397   this.summarizedItemAnimated_ = false;
398   this.totalProgressValue_ = 0.0;
399   this.totalProgressMax_ = 0.0;
400   this.state_ = ProgressCenterItemGroup.State.EMPTY;
401 };
402
403 /**
404  * Ends active state if there is no progressing and animated items.
405  * @private
406  */
407 ProgressCenterItemGroup.prototype.tryEndActive_ = function() {
408   if (this.state_ !== ProgressCenterItemGroup.State.ACTIVE ||
409       this.summarizedItemAnimated_)
410     return;
411   var hasError = false;
412   for (var id in this.items_) {
413     // If there is non-error item (progressing, or completed but still
414     // animated), we should stay the active state.
415     if (this.items_[id].state !== ProgressItemState.ERROR)
416       return;
417     hasError = true;
418   }
419   this.state_ = ProgressCenterItemGroup.State.INACTIVE;
420   if (!hasError)
421     this.endInactive();
422 };