| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 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 /** | |
| 6 * Model for the folder shortcuts. This object is cr.ui.ArrayDataModel-like | |
| 7 * object with additional methods for the folder shortcut feature. | |
| 8 * This uses chrome.storage as backend. Items are always sorted by file path. | |
| 9 * | |
| 10 * @constructor | |
| 11 * @extends {cr.EventTarget} | |
| 12 */ | |
| 13 function FolderShortcutsDataModel() { | |
| 14 this.array_ = []; | |
| 15 | |
| 16 /** | |
| 17 * Eliminate unsupported folders from the list. | |
| 18 * | |
| 19 * @param {Array.<string>} array Folder array which may contain the | |
| 20 * unsupported folders. | |
| 21 * @return {Array.<string>} Folder list without unsupported folder. | |
| 22 */ | |
| 23 var filter = function(array) { | |
| 24 return array.filter(PathUtil.isEligibleForFolderShortcut); | |
| 25 }; | |
| 26 | |
| 27 // Loads the contents from the storage to initialize the array. | |
| 28 chrome.storage.sync.get(FolderShortcutsDataModel.NAME, function(value) { | |
| 29 if (!(FolderShortcutsDataModel.NAME in value)) | |
| 30 return; | |
| 31 | |
| 32 // Since the value comes from outer resource, we have to check it just in | |
| 33 // case. | |
| 34 var list = value[FolderShortcutsDataModel.NAME]; | |
| 35 if (list instanceof Array) { | |
| 36 list = filter(list); | |
| 37 | |
| 38 // Record metrics. | |
| 39 metrics.recordSmallCount('FolderShortcut.Count', list.length); | |
| 40 | |
| 41 var permutation = this.calculatePermutation_(this.array_, list); | |
| 42 this.array_ = list; | |
| 43 this.firePermutedEvent_(permutation); | |
| 44 } | |
| 45 }.bind(this)); | |
| 46 | |
| 47 // Listening for changes in the storage. | |
| 48 chrome.storage.onChanged.addListener(function(changes, namespace) { | |
| 49 if (!(FolderShortcutsDataModel.NAME in changes) || namespace != 'sync') | |
| 50 return; | |
| 51 | |
| 52 var list = changes[FolderShortcutsDataModel.NAME].newValue; | |
| 53 // Since the value comes from outer resource, we have to check it just in | |
| 54 // case. | |
| 55 if (list instanceof Array) { | |
| 56 list = filter(list); | |
| 57 | |
| 58 // If the list is not changed, do nothing and just return. | |
| 59 if (this.array_.length == list.length) { | |
| 60 var changed = false; | |
| 61 for (var i = 0; i < this.array_.length; i++) { | |
| 62 // Same item check: must be exact match. | |
| 63 if (this.array_[i] != list[i]) { | |
| 64 changed = true; | |
| 65 break; | |
| 66 } | |
| 67 } | |
| 68 if (!changed) | |
| 69 return; | |
| 70 } | |
| 71 | |
| 72 var permutation = this.calculatePermutation_(this.array_, list); | |
| 73 this.array_ = list; | |
| 74 this.firePermutedEvent_(permutation); | |
| 75 } | |
| 76 }.bind(this)); | |
| 77 } | |
| 78 | |
| 79 /** | |
| 80 * Key name in chrome.storage. The array are stored with this name. | |
| 81 * @type {string} | |
| 82 * @const | |
| 83 */ | |
| 84 FolderShortcutsDataModel.NAME = 'folder-shortcuts-list'; | |
| 85 | |
| 86 FolderShortcutsDataModel.prototype = { | |
| 87 __proto__: cr.EventTarget.prototype, | |
| 88 | |
| 89 /** | |
| 90 * @return {number} Number of elements in the array. | |
| 91 */ | |
| 92 get length() { | |
| 93 return this.array_.length; | |
| 94 }, | |
| 95 | |
| 96 /** | |
| 97 * Returns the paths in the given range as a new array instance. The | |
| 98 * arguments and return value are compatible with Array.slice(). | |
| 99 * | |
| 100 * @param {number} start Where to start the selection. | |
| 101 * @param {number=} opt_end Where to end the selection. | |
| 102 * @return {Array.<string>} Paths in the selected range. | |
| 103 */ | |
| 104 slice: function(begin, opt_end) { | |
| 105 return this.array_.slice(begin, opt_end); | |
| 106 }, | |
| 107 | |
| 108 /** | |
| 109 * @param {number} index Index of the element to be retrieved. | |
| 110 * @return {string} The value of the |index|-th element. | |
| 111 */ | |
| 112 item: function(index) { | |
| 113 return this.array_[index]; | |
| 114 }, | |
| 115 | |
| 116 /** | |
| 117 * @param {string} value Value of the element to be retrieved. | |
| 118 * @return {number} Index of the element with the specified |value|. | |
| 119 */ | |
| 120 getIndex: function(value) { | |
| 121 for (var i = 0; i < this.length; i++) { | |
| 122 // Same item check: must be exact match. | |
| 123 if (this.array_[i] == value) { | |
| 124 return i; | |
| 125 } | |
| 126 } | |
| 127 return -1; | |
| 128 }, | |
| 129 | |
| 130 /** | |
| 131 * Compares 2 strings and returns a number indicating one string comes before | |
| 132 * or after or is the same as the other string in sort order. | |
| 133 * | |
| 134 * @param {string} a String1. | |
| 135 * @param {string} b String2. | |
| 136 * @return {boolean} Return -1, if String1 < String2. Return 0, if String1 == | |
| 137 * String2. Otherwise, return 1. | |
| 138 */ | |
| 139 compare: function(a, b) { | |
| 140 return a.localeCompare(b, | |
| 141 undefined, // locale parameter, use default locale. | |
| 142 {usage: 'sort', numeric: true}); | |
| 143 }, | |
| 144 | |
| 145 /** | |
| 146 * Adds the given item to the array. If there were already same item in the | |
| 147 * list, return the index of the existing item without adding a duplicate | |
| 148 * item. | |
| 149 * | |
| 150 * @param {string} value Value to be added into the array. | |
| 151 * @return {number} Index in the list which the element added to. | |
| 152 */ | |
| 153 add: function(value) { | |
| 154 var oldArray = this.array_.slice(0); // Shallow copy. | |
| 155 var addedIndex = -1; | |
| 156 for (var i = 0; i < this.length; i++) { | |
| 157 // Same item check: must be exact match. | |
| 158 if (this.array_[i] == value) | |
| 159 return i; | |
| 160 | |
| 161 // Since the array is sorted, new item will be added just before the first | |
| 162 // larger item. | |
| 163 if (this.compare(this.array_[i], value) >= 0) { | |
| 164 this.array_.splice(i, 0, value); | |
| 165 addedIndex = i; | |
| 166 break; | |
| 167 } | |
| 168 } | |
| 169 // If value is not added yet, add it at the last. | |
| 170 if (addedIndex == -1) { | |
| 171 this.array_.push(value); | |
| 172 addedIndex = this.length; | |
| 173 } | |
| 174 | |
| 175 this.firePermutedEvent_( | |
| 176 this.calculatePermutation_(oldArray, this.array_)); | |
| 177 this.save_(); | |
| 178 metrics.recordUserAction('FolderShortcut.Add'); | |
| 179 return addedIndex; | |
| 180 }, | |
| 181 | |
| 182 /** | |
| 183 * Removes the given item from the array. | |
| 184 * @param {string} value Value to be removed from the array. | |
| 185 * @return {number} Index in the list which the element removed from. | |
| 186 */ | |
| 187 remove: function(value) { | |
| 188 var removedIndex = -1; | |
| 189 var oldArray = this.array_.slice(0); // Shallow copy. | |
| 190 for (var i = 0; i < this.length; i++) { | |
| 191 // Same item check: must be exact match. | |
| 192 if (this.array_[i] == value) { | |
| 193 this.array_.splice(i, 1); | |
| 194 removedIndex = i; | |
| 195 break; | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 if (removedIndex != -1) { | |
| 200 this.firePermutedEvent_( | |
| 201 this.calculatePermutation_(oldArray, this.array_)); | |
| 202 this.save_(); | |
| 203 metrics.recordUserAction('FolderShortcut.Remove'); | |
| 204 return removedIndex; | |
| 205 } | |
| 206 | |
| 207 // No item is removed. | |
| 208 return -1; | |
| 209 }, | |
| 210 | |
| 211 /** | |
| 212 * @param {string} path Path to be checked. | |
| 213 * @return {boolean} True if the given |path| exists in the array. False | |
| 214 * otherwise. | |
| 215 */ | |
| 216 exists: function(path) { | |
| 217 var index = this.getIndex(path); | |
| 218 return (index >= 0); | |
| 219 }, | |
| 220 | |
| 221 /** | |
| 222 * Saves the current array to chrome.storage. | |
| 223 * @private | |
| 224 */ | |
| 225 save_: function() { | |
| 226 var obj = {}; | |
| 227 obj[FolderShortcutsDataModel.NAME] = this.array_; | |
| 228 chrome.storage.sync.set(obj, function() {}); | |
| 229 }, | |
| 230 | |
| 231 /** | |
| 232 * Creates a permutation array for 'permuted' event, which is compatible with | |
| 233 * a permutation array used in cr/ui/array_data_model.js. | |
| 234 * | |
| 235 * @param {array} oldArray Previous array before changing. | |
| 236 * @param {array} newArray New array after changing. | |
| 237 * @return {Array.<number>} Created permutation array. | |
| 238 * @private | |
| 239 */ | |
| 240 calculatePermutation_: function(oldArray, newArray) { | |
| 241 var oldIndex = 0; // Index of oldArray. | |
| 242 var newIndex = 0; // Index of newArray. | |
| 243 | |
| 244 // Note that both new and old arrays are sorted. | |
| 245 var permutation = []; | |
| 246 for (; oldIndex < oldArray.length; oldIndex++) { | |
| 247 if (newIndex >= newArray.length) { | |
| 248 // oldArray[oldIndex] is deleted, which is not in the new array. | |
| 249 permutation[oldIndex] = -1; | |
| 250 continue; | |
| 251 } | |
| 252 | |
| 253 while (newIndex < newArray.length) { | |
| 254 // Unchanged item, which exists in both new and old array. But the | |
| 255 // index may be changed. | |
| 256 if (oldArray[oldIndex] == newArray[newIndex]) { | |
| 257 permutation[oldIndex] = newIndex; | |
| 258 newIndex++; | |
| 259 break; | |
| 260 } | |
| 261 | |
| 262 // oldArray[oldIndex] is deleted, which is not in the new array. | |
| 263 if (this.compare(oldArray[oldIndex], newArray[newIndex]) < 0) { | |
| 264 permutation[oldIndex] = -1; | |
| 265 break; | |
| 266 } | |
| 267 | |
| 268 // In the case of this.compare(oldArray[oldIndex]) > 0: | |
| 269 // newArray[newIndex] is added, which is not in the old array. | |
| 270 newIndex++; | |
| 271 } | |
| 272 } | |
| 273 return permutation; | |
| 274 }, | |
| 275 | |
| 276 /** | |
| 277 * Fires a 'permuted' event, which is compatible with cr.ui.ArrayDataModel. | |
| 278 * @param {Array.<number>} Permutation array. | |
| 279 */ | |
| 280 firePermutedEvent_: function(permutation) { | |
| 281 var permutedEvent = new Event('permuted'); | |
| 282 permutedEvent.newLength = this.length; | |
| 283 permutedEvent.permutation = permutation; | |
| 284 this.dispatchEvent(permutedEvent); | |
| 285 | |
| 286 // Note: This model only fires 'permuted' event, because: | |
| 287 // 1) 'change' event is not necessary to fire since it is covered by | |
| 288 // 'permuted' event. | |
| 289 // 2) 'splice' and 'sorted' events are not implemented. These events are | |
| 290 // not used in NavigationListModel. We have to implement them when | |
| 291 // necessary. | |
| 292 } | |
| 293 }; | |
| OLD | NEW |