Home | History | Annotate | Download | only in 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 };
    423