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