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 |