1// Copyright (c) 2012 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 * Base item of NavigationListModel. Should not be created directly. 9 * @param {string} label Label. 10 * @constructor 11 */ 12function NavigationModelItem(label) { 13 this.label_ = label; 14} 15 16NavigationModelItem.prototype = { 17 get label() { return this.label_; }, 18}; 19 20/** 21 * Item of NavigationListModel for shortcuts. 22 * 23 * @param {string} label Label. 24 * @param {!DirectoryEntry} entry Entry. Cannot be null. 25 * @constructor 26 * @extends {NavigationModelItem} 27 */ 28function NavigationModelShortcutItem(label, entry) { 29 NavigationModelItem.call(this, label); 30 this.entry_ = entry; 31 Object.freeze(this); 32} 33 34NavigationModelShortcutItem.prototype = { 35 __proto__: NavigationModelItem.prototype, 36 get entry() { return this.entry_; }, 37 get isVolume() { return false; }, 38 get isShortcut() { return true; } 39}; 40 41/** 42 * Item of NavigationListModel for volumes. 43 * 44 * @param {string} label Label. 45 * @param {!VolumeInfo} volumeInfo Volume info for the volume. Cannot be null. 46 * @constructor 47 * @extends {NavigationModelItem} 48 */ 49function NavigationModelVolumeItem(label, volumeInfo) { 50 NavigationModelItem.call(this, label); 51 this.volumeInfo_ = volumeInfo; 52 // Start resolving the display root because it is used 53 // for determining executability of commands. 54 this.volumeInfo_.resolveDisplayRoot( 55 function() {}, function() {}); 56 Object.freeze(this); 57} 58 59NavigationModelVolumeItem.prototype = { 60 __proto__: NavigationModelItem.prototype, 61 get volumeInfo() { return this.volumeInfo_; }, 62 get isVolume() { return true; }, 63 get isShortcut() { return false; } 64}; 65 66/** 67 * A navigation list model. This model combines the 2 lists. 68 * @param {VolumeManagerWrapper} volumeManager VolumeManagerWrapper instance. 69 * @param {cr.ui.ArrayDataModel} shortcutListModel The list of folder shortcut. 70 * @constructor 71 * @extends {cr.EventTarget} 72 */ 73function NavigationListModel(volumeManager, shortcutListModel) { 74 cr.EventTarget.call(this); 75 76 this.volumeManager_ = volumeManager; 77 this.shortcutListModel_ = shortcutListModel; 78 79 var volumeInfoToModelItem = function(volumeInfo) { 80 return new NavigationModelVolumeItem( 81 volumeInfo.label, 82 volumeInfo); 83 }.bind(this); 84 85 var entryToModelItem = function(entry) { 86 var item = new NavigationModelShortcutItem( 87 entry.name, 88 entry); 89 return item; 90 }.bind(this); 91 92 /** 93 * Type of updated list. 94 * @enum {number} 95 * @const 96 */ 97 var ListType = { 98 VOLUME_LIST: 1, 99 SHORTCUT_LIST: 2 100 }; 101 Object.freeze(ListType); 102 103 // Generates this.volumeList_ and this.shortcutList_ from the models. 104 this.volumeList_ = 105 this.volumeManager_.volumeInfoList.slice().map(volumeInfoToModelItem); 106 107 this.shortcutList_ = []; 108 for (var i = 0; i < this.shortcutListModel_.length; i++) { 109 var shortcutEntry = this.shortcutListModel_.item(i); 110 var volumeInfo = this.volumeManager_.getVolumeInfo(shortcutEntry); 111 this.shortcutList_.push(entryToModelItem(shortcutEntry)); 112 } 113 114 // Generates a combined 'permuted' event from an event of either list. 115 var permutedHandler = function(listType, event) { 116 var permutation; 117 118 // Build the volumeList. 119 if (listType == ListType.VOLUME_LIST) { 120 // The volume is mounted or unmounted. 121 var newList = []; 122 123 // Use the old instances if they just move. 124 for (var i = 0; i < event.permutation.length; i++) { 125 if (event.permutation[i] >= 0) 126 newList[event.permutation[i]] = this.volumeList_[i]; 127 } 128 129 // Create missing instances. 130 for (var i = 0; i < event.newLength; i++) { 131 if (!newList[i]) { 132 newList[i] = volumeInfoToModelItem( 133 this.volumeManager_.volumeInfoList.item(i)); 134 } 135 } 136 this.volumeList_ = newList; 137 138 permutation = event.permutation.slice(); 139 140 // shortcutList part has not been changed, so the permutation should be 141 // just identity mapping with a shift. 142 for (var i = 0; i < this.shortcutList_.length; i++) { 143 permutation.push(i + this.volumeList_.length); 144 } 145 } else { 146 // Build the shortcutList. 147 148 // volumeList part has not been changed, so the permutation should be 149 // identity mapping. 150 151 permutation = []; 152 for (var i = 0; i < this.volumeList_.length; i++) { 153 permutation[i] = i; 154 } 155 156 var modelIndex = 0; 157 var oldListIndex = 0; 158 var newList = []; 159 while (modelIndex < this.shortcutListModel_.length && 160 oldListIndex < this.shortcutList_.length) { 161 var shortcutEntry = this.shortcutListModel_.item(modelIndex); 162 var cmp = this.shortcutListModel_.compare( 163 shortcutEntry, this.shortcutList_[oldListIndex].entry); 164 if (cmp > 0) { 165 // The shortcut at shortcutList_[oldListIndex] is removed. 166 permutation.push(-1); 167 oldListIndex++; 168 continue; 169 } 170 171 if (cmp === 0) { 172 // Reuse the old instance. 173 permutation.push(newList.length + this.volumeList_.length); 174 newList.push(this.shortcutList_[oldListIndex]); 175 oldListIndex++; 176 } else { 177 // We needs to create a new instance for the shortcut entry. 178 newList.push(entryToModelItem(shortcutEntry)); 179 } 180 modelIndex++; 181 } 182 183 // Add remaining (new) shortcuts if necessary. 184 for (; modelIndex < this.shortcutListModel_.length; modelIndex++) { 185 var shortcutEntry = this.shortcutListModel_.item(modelIndex); 186 newList.push(entryToModelItem(shortcutEntry)); 187 } 188 189 // Fill remaining permutation if necessary. 190 for (; oldListIndex < this.shortcutList_.length; oldListIndex++) 191 permutation.push(-1); 192 193 this.shortcutList_ = newList; 194 } 195 196 // Dispatch permuted event. 197 var permutedEvent = new Event('permuted'); 198 permutedEvent.newLength = 199 this.volumeList_.length + this.shortcutList_.length; 200 permutedEvent.permutation = permutation; 201 this.dispatchEvent(permutedEvent); 202 }; 203 204 this.volumeManager_.volumeInfoList.addEventListener( 205 'permuted', permutedHandler.bind(this, ListType.VOLUME_LIST)); 206 this.shortcutListModel_.addEventListener( 207 'permuted', permutedHandler.bind(this, ListType.SHORTCUT_LIST)); 208 209 // 'change' event is just ignored, because it is not fired neither in 210 // the folder shortcut list nor in the volume info list. 211 // 'splice' and 'sorted' events are not implemented, since they are not used 212 // in list.js. 213} 214 215/** 216 * NavigationList inherits cr.EventTarget. 217 */ 218NavigationListModel.prototype = { 219 __proto__: cr.EventTarget.prototype, 220 get length() { return this.length_(); }, 221 get folderShortcutList() { return this.shortcutList_; } 222}; 223 224/** 225 * Returns the item at the given index. 226 * @param {number} index The index of the entry to get. 227 * @return {NavigationModelItem} The item at the given index. 228 */ 229NavigationListModel.prototype.item = function(index) { 230 var offset = this.volumeList_.length; 231 if (index < offset) 232 return this.volumeList_[index]; 233 return this.shortcutList_[index - offset]; 234}; 235 236/** 237 * Returns the number of items in the model. 238 * @return {number} The length of the model. 239 * @private 240 */ 241NavigationListModel.prototype.length_ = function() { 242 return this.volumeList_.length + this.shortcutList_.length; 243}; 244 245/** 246 * Returns the first matching item. 247 * @param {NavigationModelItem} modelItem The entry to find. 248 * @param {number=} opt_fromIndex If provided, then the searching start at 249 * the {@code opt_fromIndex}. 250 * @return {number} The index of the first found element or -1 if not found. 251 */ 252NavigationListModel.prototype.indexOf = function(modelItem, opt_fromIndex) { 253 for (var i = opt_fromIndex || 0; i < this.length; i++) { 254 if (modelItem === this.item(i)) 255 return i; 256 } 257 return -1; 258}; 259 260/** 261 * Called externally when one of the items is not found on the filesystem. 262 * @param {NavigationModelItem} modelItem The entry which is not found. 263 */ 264NavigationListModel.prototype.onItemNotFoundError = function(modelItem) { 265 if (modelItem.isVolume) { 266 // TODO(mtomasz, yoshiki): Implement when needed. 267 return; 268 } 269 if (modelItem.isShortcut) { 270 // For shortcuts, lets the shortcut model handle this situation. 271 this.shortcutListModel_.onItemNotFoundError(modelItem.entry); 272 } 273}; 274