Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(269)

Side by Side Diff: chrome/browser/resources/md_bookmarks/store.js

Issue 2752173002: MD Bookmarks: Remove old bookmark data store (Closed)
Patch Set: Rebase Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 var BookmarksStore = Polymer({ 5 /**
6 is: 'bookmarks-store', 6 * @fileoverview A singleton datastore for the Bookmarks page. Page state is
7 * publicly readable, but can only be modified by dispatching an Action to
8 * the store.
9 */
7 10
8 properties: { 11 cr.define('bookmarks', function() {
9 /** @type {BookmarkTreeNode} */ 12 /** @constructor */
10 rootNode: { 13 function Store() {
11 type: Object, 14 /** @type {!BookmarksPageState} */
12 notify: true, 15 this.data_ = bookmarks.util.createEmptyState();
16 /** @type {boolean} */
17 this.initialized_ = false;
18 /** @type {!Array<!Action>} */
19 this.queuedActions_ = [];
20 /** @type {!Array<!StoreObserver>} */
21 this.observers_ = [];
22 }
23
24 Store.prototype = {
25 /**
26 * @param {!BookmarksPageState} initialState
27 */
28 init: function(initialState) {
29 this.data_ = initialState;
30
31 this.queuedActions_.forEach(function(action) {
32 this.reduce_(action);
33 }.bind(this));
34
35 this.initialized_ = true;
36 this.notifyObservers_(this.data_);
13 }, 37 },
14 38
15 /** @type {?string} */ 39 /** @type {!BookmarksPageState} */
16 selectedId: { 40 get data() {
17 type: String, 41 return this.data_;
18 observer: 'updateSelectedDisplay_',
19 notify: true,
20 }, 42 },
21 43
22 searchTerm: { 44 /** @return {boolean} */
23 type: String, 45 isInitialized: function() {
24 value: '', 46 return this.initialized_;
25 observer: 'updateSearchDisplay_', 47 },
26 notify: true, 48
49 /** @param {!StoreObserver} observer */
50 addObserver: function(observer) {
51 this.observers_.push(observer);
52 },
53
54 /** @param {!StoreObserver} observer */
55 removeObserver: function(observer) {
56 var index = this.observers_.indexOf(observer);
57 this.observers_.splice(index, 1);
27 }, 58 },
28 59
29 /** 60 /**
30 * This updates to either the result of a search or the contents of the 61 * Transition to a new UI state based on the supplied |action|, and notify
31 * selected folder. 62 * observers of the change. If the Store has not yet been initialized, the
32 * @type {Array<BookmarkTreeNode>} 63 * action will be queued and performed upon initialization.
64 * @param {Action} action
33 */ 65 */
34 displayedList: { 66 handleAction: function(action) {
35 type: Array, 67 if (!this.initialized_) {
36 notify: true, 68 this.queuedActions_.push(action);
37 readOnly: true, 69 return;
70 }
71
72 this.reduce_(action);
73 this.notifyObservers_(this.data_);
38 }, 74 },
39 75
40 /** @type {Object<?string, !BookmarkTreeNode>} */ 76 /**
41 idToNodeMap_: Object, 77 * @param {Action} action
78 * @private
79 */
80 reduce_: function(action) {
81 this.data_ = bookmarks.reduceAction(this.data_, action);
82 },
42 83
43 /** @type {?number} */ 84 /**
44 anchorIndex_: Number, 85 * @param {!BookmarksPageState} state
86 * @private
87 */
88 notifyObservers_: function(state) {
89 this.observers_.forEach(function(o) {
90 o.onStateChanged(state);
91 });
92 },
93 };
45 94
46 /** @type {Set<string>} */ 95 cr.addSingletonGetter(Store);
47 searchResultSet_: Object,
48 },
49 96
50 /** @private {Object} */ 97 return {
51 documentListeners_: null, 98 Store: Store,
52 99 };
53 /** @override */
54 attached: function() {
55 this.documentListeners_ = {
56 'folder-open-changed': this.onFolderOpenChanged_.bind(this),
57 'search-term-changed': this.onSearchTermChanged_.bind(this),
58 'select-item': this.onItemSelected_.bind(this),
59 'selected-folder-changed': this.onSelectedFolderChanged_.bind(this),
60 };
61 for (var event in this.documentListeners_)
62 document.addEventListener(event, this.documentListeners_[event]);
63 },
64
65 /** @override */
66 detached: function() {
67 for (var event in this.documentListeners_)
68 document.removeEventListener(event, this.documentListeners_[event]);
69 },
70
71 /**
72 * Initializes the store with data from the bookmarks API.
73 * Called by app on attached.
74 */
75 initializeStore: function() {
76 chrome.bookmarks.getTree(function(results) {
77 this.setupStore_(results[0]);
78 }.bind(this));
79 // Attach bookmarks API listeners.
80 chrome.bookmarks.onRemoved.addListener(this.onBookmarkRemoved_.bind(this));
81 chrome.bookmarks.onChanged.addListener(this.onBookmarkChanged_.bind(this));
82 chrome.bookmarks.onImportBegan.addListener(this.onImportBegan_.bind(this));
83 chrome.bookmarks.onImportEnded.addListener(this.onImportEnded_.bind(this));
84 },
85
86 //////////////////////////////////////////////////////////////////////////////
87 // bookmarks-store, private:
88
89 /**
90 * @param {BookmarkTreeNode} rootNode
91 * @private
92 */
93 setupStore_: function(rootNode) {
94 this.rootNode = rootNode;
95 this.idToNodeMap_ = {};
96 this.rootNode.path = 'rootNode';
97 BookmarksStore.generatePaths(rootNode, 0);
98 BookmarksStore.initNodes(this.rootNode, this.idToNodeMap_);
99
100 // Initialize the store's fields from the router.
101 if (this.$.router.searchTerm)
102 this.searchTerm = this.$.router.searchTerm;
103 else
104 this.fire('selected-folder-changed', this.$.router.selectedId);
105 },
106
107 /** @private */
108 deselectFolders_: function() {
109 this.unlinkPaths('displayedList');
110 this.set(
111 this.idToNodeMap_[this.selectedId].path + '.isSelectedFolder', false);
112 this.selectedId = null;
113 },
114
115 /**
116 * @param {BookmarkTreeNode} folder
117 * @private
118 * @return {boolean}
119 */
120 isAncestorOfSelected_: function(folder) {
121 if (!this.selectedId)
122 return false;
123
124 var selectedNode = this.idToNodeMap_[this.selectedId];
125 return selectedNode.path.startsWith(folder.path);
126 },
127
128 /** @private */
129 updateSearchDisplay_: function() {
130 if (!this.rootNode)
131 return;
132
133 if (!this.searchTerm) {
134 this.fire('selected-folder-changed', this.rootNode.children[0].id);
135 } else {
136 chrome.bookmarks.search(this.searchTerm, function(results) {
137 this.anchorIndex_ = null;
138 this.clearSelectedItems_();
139 this.searchResultSet_ = new Set();
140
141 if (this.selectedId)
142 this.deselectFolders_();
143
144 this.setupSearchResults_(results);
145 }.bind(this));
146 }
147 },
148
149 /** @private */
150 updateSelectedDisplay_: function() {
151 // Don't change to the selected display if ID was cleared.
152 if (!this.selectedId)
153 return;
154
155 this.clearSelectedItems_();
156 this.anchorIndex_ = null;
157
158 var selectedNode = this.idToNodeMap_[this.selectedId];
159 this.linkPaths('displayedList', selectedNode.path + '.children');
160 this._setDisplayedList(
161 /** @type {Array<BookmarkTreeNode>} */ (selectedNode.children));
162 },
163
164 /**
165 * Remove all descendants of a given node from the map.
166 * @param {string} id
167 * @private
168 */
169 removeDescendantsFromMap_: function(id) {
170 var node = this.idToNodeMap_[id];
171 if (!node)
172 return;
173
174 if (node.children) {
175 for (var i = 0; i < node.children.length; i++)
176 this.removeDescendantsFromMap_(node.children[i].id);
177 }
178 delete this.idToNodeMap_[id];
179 },
180
181 /**
182 * Remove all selected items in the list.
183 * @private
184 */
185 clearSelectedItems_: function() {
186 if (!this.displayedList)
187 return;
188
189 for (var i = 0; i < this.displayedList.length; i++) {
190 if (!this.displayedList[i].isSelectedItem)
191 continue;
192
193 this.set('displayedList.#' + i + '.isSelectedItem', false);
194 }
195 },
196
197 /**
198 * Return the index in the search result of an item.
199 * @param {BookmarkTreeNode} item
200 * @return {number}
201 * @private
202 */
203 getIndexInList_: function(item) {
204 return this.searchTerm ? item.searchResultIndex : item.index;
205 },
206
207 /**
208 * @param {string} id
209 * @return {boolean}
210 * @private
211 */
212 isInDisplayedList_: function(id) {
213 return this.searchTerm ? this.searchResultSet_.has(id) :
214 this.idToNodeMap_[id].parentId == this.selectedId;
215 },
216
217 /**
218 * Initializes the search results returned by the API as follows:
219 * - Populates |searchResultSet_| with a mapping of all result ids to
220 * their corresponding result.
221 * - Sets up the |searchResultIndex|.
222 * @param {Array<BookmarkTreeNode>} results
223 * @private
224 */
225 setupSearchResults_: function(results) {
226 for (var i = 0; i < results.length; i++) {
227 results[i].searchResultIndex = i;
228 results[i].isSelectedItem = false;
229 this.searchResultSet_.add(results[i].id);
230 }
231
232 this._setDisplayedList(results);
233 },
234
235 /**
236 * Select multiple items based on |anchorIndex_| and the selected
237 * item. If |anchorIndex_| is not set, single select the item.
238 * @param {BookmarkTreeNode} item
239 * @private
240 */
241 selectRange_: function(item) {
242 var startIndex, endIndex;
243 if (this.anchorIndex_ == null) {
244 this.anchorIndex_ = this.getIndexInList_(item);
245 startIndex = this.anchorIndex_;
246 endIndex = this.anchorIndex_;
247 } else {
248 var selectedIndex = this.getIndexInList_(item);
249 startIndex = Math.min(this.anchorIndex_, selectedIndex);
250 endIndex = Math.max(this.anchorIndex_, selectedIndex);
251 }
252 for (var i = startIndex; i <= endIndex; i++)
253 this.set('displayedList.#' + i + '.isSelectedItem', true);
254 },
255
256 /**
257 * Selects a single item in the displayedList.
258 * @param {BookmarkTreeNode} item
259 * @private
260 */
261 selectItem_: function(item) {
262 this.anchorIndex_ = this.getIndexInList_(item);
263 this.set('displayedList.#' + this.anchorIndex_ + '.isSelectedItem', true);
264 },
265
266 //////////////////////////////////////////////////////////////////////////////
267 // bookmarks-store, bookmarks API event listeners:
268
269 /**
270 * Callback for when a bookmark node is removed.
271 * If a folder is selected or is an ancestor of a selected folder, the parent
272 * of the removed folder will be selected.
273 * @param {string} id The id of the removed bookmark node.
274 * @param {!{index: number,
275 * parentId: string,
276 * node: BookmarkTreeNode}} removeInfo
277 */
278 onBookmarkRemoved_: function(id, removeInfo) {
279 chrome.bookmarks.getSubTree(removeInfo.parentId, function(parentNodes) {
280 var parentNode = parentNodes[0];
281 var isAncestor = this.isAncestorOfSelected_(this.idToNodeMap_[id]);
282 var wasInDisplayedList = this.isInDisplayedList_(id);
283
284 // Refresh the parent node's data from the backend as its children's
285 // indexes will have changed and Polymer doesn't update them.
286 this.removeDescendantsFromMap_(id);
287 parentNode.path = this.idToNodeMap_[parentNode.id].path;
288 BookmarksStore.generatePaths(parentNode, 0);
289 BookmarksStore.initNodes(parentNode, this.idToNodeMap_);
290 this.set(parentNode.path, parentNode);
291
292 // Updates selectedId if the removed node is an ancestor of the current
293 // selected node.
294 if (isAncestor)
295 this.fire('selected-folder-changed', removeInfo.parentId);
296
297 // Only update the displayedList if the removed node is in the
298 // displayedList.
299 if (!wasInDisplayedList)
300 return;
301
302 this.anchorIndex_ = null;
303
304 // Update the currently displayed list.
305 if (this.searchTerm) {
306 this.updateSearchDisplay_();
307 } else {
308 if (!isAncestor)
309 this.fire('selected-folder-changed', this.selectedId);
310
311 this._setDisplayedList(parentNode.children);
312 }
313 }.bind(this));
314 },
315
316 /**
317 * Called when the title of a bookmark changes.
318 * @param {string} id The id of changed bookmark node.
319 * @param {!Object} changeInfo
320 */
321 onBookmarkChanged_: function(id, changeInfo) {
322 if (changeInfo.title)
323 this.set(this.idToNodeMap_[id].path + '.title', changeInfo.title);
324 if (changeInfo.url)
325 this.set(this.idToNodeMap_[id].path + '.url', changeInfo.url);
326
327 if (this.searchTerm)
328 this.updateSearchDisplay_();
329 },
330
331 /**
332 * Called when importing bookmark is started.
333 */
334 onImportBegan_: function() {
335 // TODO(rongjie): pause onCreated once this event is used.
336 },
337
338 /**
339 * Called when importing bookmark node is finished.
340 */
341 onImportEnded_: function() {
342 chrome.bookmarks.getTree(function(results) {
343 this.setupStore_(results[0]);
344 this.updateSelectedDisplay_();
345 }.bind(this));
346 },
347
348 //////////////////////////////////////////////////////////////////////////////
349 // bookmarks-store, bookmarks app event listeners:
350
351 /**
352 * @param {Event} e
353 * @private
354 */
355 onSearchTermChanged_: function(e) {
356 this.searchTerm = /** @type {string} */ (e.detail);
357 },
358
359 /**
360 * Selects the folder specified by the event and deselects the previously
361 * selected folder.
362 * @param {CustomEvent} e
363 * @private
364 */
365 onSelectedFolderChanged_: function(e) {
366 if (this.searchTerm)
367 this.searchTerm = '';
368
369 // Deselect the old folder if defined.
370 if (this.selectedId && this.idToNodeMap_[this.selectedId])
371 this.set(
372 this.idToNodeMap_[this.selectedId].path + '.isSelectedFolder', false);
373
374 // Check if the selected id is that of a defined folder.
375 var id = /** @type {string} */ (e.detail);
376 if (!this.idToNodeMap_[id] || this.idToNodeMap_[id].url)
377 id = this.rootNode.children[0].id;
378
379 var folder = this.idToNodeMap_[id];
380 this.set(folder.path + '.isSelectedFolder', true);
381 this.selectedId = id;
382
383 if (folder.id == this.rootNode.id)
384 return;
385
386 var parent = this.idToNodeMap_[/** @type {?string} */ (folder.parentId)];
387 while (parent) {
388 if (!parent.isOpen) {
389 this.fire('folder-open-changed', {
390 id: parent.id,
391 open: true,
392 });
393 }
394
395 parent = this.idToNodeMap_[/** @type {?string} */ (parent.parentId)];
396 }
397 },
398
399 /**
400 * Handles events that open and close folders.
401 * @param {CustomEvent} e
402 * @private
403 */
404 onFolderOpenChanged_: function(e) {
405 var folder = this.idToNodeMap_[e.detail.id];
406 this.set(folder.path + '.isOpen', e.detail.open);
407 if (!folder.isOpen && this.isAncestorOfSelected_(folder))
408 this.fire('selected-folder-changed', folder.id);
409 },
410
411 /**
412 * Selects items according to keyboard behaviours.
413 * @param {CustomEvent} e
414 * @private
415 */
416 onItemSelected_: function(e) {
417 if (!e.detail.add)
418 this.clearSelectedItems_();
419
420 if (e.detail.range)
421 this.selectRange_(e.detail.item);
422 else
423 this.selectItem_(e.detail.item);
424 },
425 }); 100 });
426
427 ////////////////////////////////////////////////////////////////////////////////
428 // bookmarks-store, static methods:
429
430 /**
431 * Stores the path from the store to a node inside the node.
432 * @param {BookmarkTreeNode} bookmarkNode
433 * @param {number} startIndex
434 */
435 BookmarksStore.generatePaths = function(bookmarkNode, startIndex) {
436 if (!bookmarkNode.children)
437 return;
438
439 for (var i = startIndex; i < bookmarkNode.children.length; i++) {
440 bookmarkNode.children[i].path = bookmarkNode.path + '.children.#' + i;
441 BookmarksStore.generatePaths(bookmarkNode.children[i], 0);
442 }
443 };
444
445 /**
446 * Initializes the nodes in the bookmarks tree as follows:
447 * - Populates |idToNodeMap_| with a mapping of all node ids to their
448 * corresponding BookmarkTreeNode.
449 * - Sets all the nodes to not selected and open by default.
450 * @param {BookmarkTreeNode} bookmarkNode
451 * @param {Object=} idToNodeMap
452 */
453 BookmarksStore.initNodes = function(bookmarkNode, idToNodeMap) {
454 bookmarkNode.isSelectedItem = false;
455 if (idToNodeMap)
456 idToNodeMap[bookmarkNode.id] = bookmarkNode;
457
458 if (bookmarkNode.url)
459 return;
460
461 bookmarkNode.isSelectedFolder = false;
462 bookmarkNode.isOpen = true;
463 for (var i = 0; i < bookmarkNode.children.length; i++)
464 BookmarksStore.initNodes(bookmarkNode.children[i], idToNodeMap);
465 };
OLDNEW
« no previous file with comments | « chrome/browser/resources/md_bookmarks/store.html ('k') | chrome/browser/ui/webui/md_bookmarks/md_bookmarks_ui.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698