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

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

Issue 2639453002: [MD Bookmarks] Add Select for Bookmarks. (Closed)
Patch Set: selection after deletion Created 3 years, 11 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 2016 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 var BookmarksStore = Polymer({
6 is: 'bookmarks-store', 6 is: 'bookmarks-store',
7 7
8 properties: { 8 properties: {
9 /** @type {BookmarkTreeNode} */ 9 /** @type {BookmarkTreeNode} */
10 rootNode: { 10 rootNode: {
(...skipping 19 matching lines...) Expand all
30 * selected folder. 30 * selected folder.
31 * @type {Array<BookmarkTreeNode>} 31 * @type {Array<BookmarkTreeNode>}
32 */ 32 */
33 displayedList: { 33 displayedList: {
34 type: Array, 34 type: Array,
35 notify: true, 35 notify: true,
36 readOnly: true, 36 readOnly: true,
37 }, 37 },
38 38
39 idToNodeMap_: Object, 39 idToNodeMap_: Object,
40
41 prevSelectedItemIndex_: Number,
40 }, 42 },
41 43
42 /** @private {Object} */ 44 /** @private {Object} */
43 documentListeners_: null, 45 documentListeners_: null,
44 46
45 /** @override */ 47 /** @override */
46 attached: function() { 48 attached: function() {
47 this.documentListeners_ = { 49 this.documentListeners_ = {
48 'selected-folder-changed': this.onSelectedFolderChanged_.bind(this),
49 'folder-open-changed': this.onFolderOpenChanged_.bind(this), 50 'folder-open-changed': this.onFolderOpenChanged_.bind(this),
50 'search-term-changed': this.onSearchTermChanged_.bind(this), 51 'search-term-changed': this.onSearchTermChanged_.bind(this),
52 'select-item': this.onItemSelected_.bind(this),
53 'selected-folder-changed': this.onSelectedFolderChanged_.bind(this),
51 }; 54 };
52 for (var event in this.documentListeners_) 55 for (var event in this.documentListeners_)
53 document.addEventListener(event, this.documentListeners_[event]); 56 document.addEventListener(event, this.documentListeners_[event]);
54 }, 57 },
55 58
56 /** @override */ 59 /** @override */
57 detached: function() { 60 detached: function() {
58 for (var event in this.documentListeners_) 61 for (var event in this.documentListeners_)
59 document.removeEventListener(event, this.documentListeners_[event]); 62 document.removeEventListener(event, this.documentListeners_[event]);
60 }, 63 },
(...skipping 23 matching lines...) Expand all
84 this.idToNodeMap_ = {}; 87 this.idToNodeMap_ = {};
85 this.rootNode.path = 'rootNode'; 88 this.rootNode.path = 'rootNode';
86 BookmarksStore.generatePaths(rootNode, 0); 89 BookmarksStore.generatePaths(rootNode, 0);
87 BookmarksStore.initNodes(this.rootNode, this.idToNodeMap_); 90 BookmarksStore.initNodes(this.rootNode, this.idToNodeMap_);
88 this.fire('selected-folder-changed', this.rootNode.children[0].id); 91 this.fire('selected-folder-changed', this.rootNode.children[0].id);
89 }, 92 },
90 93
91 /** @private */ 94 /** @private */
92 deselectFolders_: function() { 95 deselectFolders_: function() {
93 this.unlinkPaths('displayedList'); 96 this.unlinkPaths('displayedList');
94 this.set(this.idToNodeMap_[this.selectedId].path + '.isSelected', false); 97 this.set(
98 this.idToNodeMap_[this.selectedId].path + '.isSelectedFolder', false);
95 this.selectedId = null; 99 this.selectedId = null;
96 }, 100 },
97 101
98 /** 102 /**
99 * @param {BookmarkTreeNode} folder 103 * @param {BookmarkTreeNode} folder
100 * @private 104 * @private
101 * @return {boolean} 105 * @return {boolean}
102 */ 106 */
103 isAncestorOfSelected_: function(folder) { 107 isAncestorOfSelected_: function(folder) {
104 if (!this.selectedId) 108 if (!this.selectedId)
105 return false; 109 return false;
106 110
107 var selectedNode = this.idToNodeMap_[this.selectedId]; 111 var selectedNode = this.idToNodeMap_[this.selectedId];
108 return selectedNode.path.startsWith(folder.path); 112 return selectedNode.path.startsWith(folder.path);
109 }, 113 },
110 114
111 /** @private */ 115 /** @private */
112 updateSearchDisplay_: function() { 116 updateSearchDisplay_: function() {
113 if (this.searchTerm == '') { 117 if (this.searchTerm == '') {
114 this.fire('selected-folder-changed', this.rootNode.children[0].id); 118 this.fire('selected-folder-changed', this.rootNode.children[0].id);
115 } else { 119 } else {
116 chrome.bookmarks.search(this.searchTerm, function(results) { 120 chrome.bookmarks.search(this.searchTerm, function(results) {
121 this.clearSelectedItems_();
122 this.prevSelectedItemIndex_ = undefined;
123
117 if (this.selectedId) 124 if (this.selectedId)
118 this.deselectFolders_(); 125 this.deselectFolders_();
119 126
127 for (var i = 0; i < results.length; i++)
128 this.idToNodeMap_[results[i].id].searchResultIndex = i;
calamity 2017/01/23 00:44:27 Why do we have to update the nodes inside the tree
jiaxi 2017/01/23 06:14:46 Done.
129
120 this._setDisplayedList(results); 130 this._setDisplayedList(results);
121 }.bind(this)); 131 }.bind(this));
122 } 132 }
123 }, 133 },
124 134
125 /** @private */ 135 /** @private */
126 updateSelectedDisplay_: function() { 136 updateSelectedDisplay_: function() {
127 // Don't change to the selected display if ID was cleared. 137 // Don't change to the selected display if ID was cleared.
128 if (!this.selectedId) 138 if (!this.selectedId)
129 return; 139 return;
130 140
141 this.clearSelectedItems_();
142 this.prevSelectedItemIndex_ = undefined;
143
131 var selectedNode = this.idToNodeMap_[this.selectedId]; 144 var selectedNode = this.idToNodeMap_[this.selectedId];
132 this.linkPaths('displayedList', selectedNode.path + '.children'); 145 this.linkPaths('displayedList', selectedNode.path + '.children');
133 this._setDisplayedList(selectedNode.children); 146 this._setDisplayedList(selectedNode.children);
134 }, 147 },
135 148
136 /** 149 /**
137 * Remove all descendants of a given node from the map. 150 * Remove all descendants of a given node from the map.
138 * @param {string} id 151 * @param {string} id
139 * @private 152 * @private
140 */ 153 */
141 removeDescendantsFromMap_: function(id) { 154 removeDescendantsFromMap_: function(id) {
142 var node = this.idToNodeMap_[id]; 155 var node = this.idToNodeMap_[id];
143 if (!node) 156 if (!node)
144 return; 157 return;
145 158
146 if (node.children) { 159 if (node.children) {
147 for (var i = 0; i < node.children.length; i++) 160 for (var i = 0; i < node.children.length; i++)
148 this.removeDescendantsFromMap_(node.children[i].id); 161 this.removeDescendantsFromMap_(node.children[i].id);
149 } 162 }
150 delete this.idToNodeMap_[id]; 163 delete this.idToNodeMap_[id];
151 }, 164 },
152 165
166 /**
167 * Remove all selected items in the list.
168 * @private
169 */
170 clearSelectedItems_: function() {
171 if (!this.displayedList)
172 return;
173
174 for (var i = 0; i < this.displayedList.length; i++) {
175 if (!this.displayedList[i].isSelectedItem)
176 continue;
177
178 this.set('displayedList.#' + i + '.isSelectedItem', false);
179 }
180 },
181
182 /**
183 * Return the index in the search result of an item.
184 * @param {string} id
185 * @return {number}
186 * @private
187 */
188 getIndexInList_: function(id) {
189 if (this.searchTerm)
190 return this.idToNodeMap_[id].searchResultIndex;
191
192 return this.idToNodeMap_[id].index;
193 },
194
153 //////////////////////////////////////////////////////////////////////////////// 195 ////////////////////////////////////////////////////////////////////////////////
154 // bookmarks-store, bookmarks API event listeners: 196 // bookmarks-store, bookmarks API event listeners:
155 197
156 /** 198 /**
157 * Callback for when a bookmark node is removed. 199 * Callback for when a bookmark node is removed.
158 * If a folder is selected or is an ancestor of a selected folder, the parent 200 * If a folder is selected or is an ancestor of a selected folder, the parent
159 * of the removed folder will be selected. 201 * of the removed folder will be selected.
160 * @param {string} id The id of the removed bookmark node. 202 * @param {string} id The id of the removed bookmark node.
161 * @param {!{index: number, 203 * @param {!{index: number,
162 * parentId: string, 204 * parentId: string,
163 * node: BookmarkTreeNode}} removeInfo 205 * node: BookmarkTreeNode}} removeInfo
164 */ 206 */
165 onBookmarkRemoved_: function(id, removeInfo) { 207 onBookmarkRemoved_: function(id, removeInfo) {
166 if (this.isAncestorOfSelected_(this.idToNodeMap_[id])) { 208 chrome.bookmarks.getSubTree(removeInfo.parentId, function(parentNode) {
calamity 2017/01/23 00:44:27 Does this cancel all existing selections?
jiaxi 2017/01/23 06:14:46 All existing selections will be canceled when the
167 this.fire('selected-folder-changed', removeInfo.parentId); 209 var isSelected = this.isAncestorOfSelected_(this.idToNodeMap_[id]);
168 } 210 // Check if the removed item is from the list.
calamity 2017/01/23 00:44:27 This only checks if the removed item is the previo
jiaxi 2017/01/23 06:14:46 Now it checks if the removed item is in the displa
211 var isRemovedFromList = false;
212 if (this.prevSelectedItemIndex_ != undefined) {
213 if (id == this.displayedList[this.prevSelectedItemIndex_].id) {
calamity 2017/01/23 00:44:27 Use && for these 2 conditions?
jiaxi 2017/01/23 06:14:46 This part has been changed a lot so...
214 if (this.prevSelectedItemIndex_ == this.displayedList.length - 1)
215 this.prevSelectedItemIndex_--;
216 isRemovedFromList = true;
217 }
218 }
169 219
170 var parentNode = this.idToNodeMap_[removeInfo.parentId]; 220 // Update the tree.
171 this.splice(parentNode.path + '.children', removeInfo.index, 1); 221 this.removeDescendantsFromMap_(id);
172 this.removeDescendantsFromMap_(id); 222 parentNode[0].path = this.idToNodeMap_[parentNode[0].id].path;
calamity 2017/01/23 00:44:27 Change the arg to the function to parentNodes and
jiaxi 2017/01/23 06:14:46 Done.
173 BookmarksStore.generatePaths(parentNode, removeInfo.index); 223 BookmarksStore.generatePaths(parentNode[0], 0);
224 BookmarksStore.initNodes(parentNode[0], this.idToNodeMap_);
225 this.set(parentNode[0].path, parentNode[0])
174 226
175 // Regenerate the search list if its displayed. 227 // Update selectedId.
176 if (this.searchTerm) 228 if (isSelected)
177 this.updateSearchDisplay_(); 229 this.fire('selected-folder-changed', removeInfo.parentId);
230
231 // Update the displayedList.
232 if (this.searchTerm) {
233 // Regenerate the search list if its displayed.
234 chrome.bookmarks.search(this.searchTerm, function(results) {
235 for (var i = 0; i < results.length; i++)
236 this.idToNodeMap_[results[i].id].searchResultIndex = i;
237
238 this._setDisplayedList(results);
239 if (!isRemovedFromList)
240 return
241
242 this.set(
243 'displayedList.#' + this.prevSelectedItemIndex_ +
244 '.isSelectedItem',
245 true);
246 }.bind(this));
247 } else {
248 this._setDisplayedList(parentNode[0].children);
calamity 2017/01/23 00:44:27 This assumes that removed bookmarks are always bei
jiaxi 2017/01/23 06:14:46 Done.
249 if (!isRemovedFromList)
250 return
251
252 this.set(
253 'displayedList.#' + this.prevSelectedItemIndex_ + '.isSelectedItem',
254 true);
calamity 2017/01/23 00:44:27 I'm not sure what this does?
jiaxi 2017/01/23 06:14:46 This selects the item in the displayedList since w
255 }
256 }.bind(this));
178 }, 257 },
179 258
180 /** 259 /**
181 * Called when the title of a bookmark changes. 260 * Called when the title of a bookmark changes.
182 * @param {string} id The id of changed bookmark node. 261 * @param {string} id The id of changed bookmark node.
183 * @param {!Object} changeInfo 262 * @param {!Object} changeInfo
184 */ 263 */
185 onBookmarkChanged_: function(id, changeInfo) { 264 onBookmarkChanged_: function(id, changeInfo) {
186 if (changeInfo.title) 265 if (changeInfo.title)
187 this.set(this.idToNodeMap_[id].path + '.title', changeInfo.title); 266 this.set(this.idToNodeMap_[id].path + '.title', changeInfo.title);
(...skipping 19 matching lines...) Expand all
207 * Selects the folder specified by the event and deselects the previously 286 * Selects the folder specified by the event and deselects the previously
208 * selected folder. 287 * selected folder.
209 * @param {CustomEvent} e 288 * @param {CustomEvent} e
210 * @private 289 * @private
211 */ 290 */
212 onSelectedFolderChanged_: function(e) { 291 onSelectedFolderChanged_: function(e) {
213 if (this.searchTerm) 292 if (this.searchTerm)
214 this.searchTerm = ''; 293 this.searchTerm = '';
215 294
216 // Deselect the old folder if defined. 295 // Deselect the old folder if defined.
217 if (this.selectedId) 296 if (this.selectedId && this.idToNodeMap_[this.selectedId])
218 this.set(this.idToNodeMap_[this.selectedId].path + '.isSelected', false); 297 this.set(
298 this.idToNodeMap_[this.selectedId].path + '.isSelectedFolder', false);
219 299
220 var selectedId = /** @type {string} */ (e.detail); 300 var selectedId = /** @type {string} */ (e.detail);
221 var newFolder = this.idToNodeMap_[selectedId]; 301 var newFolder = this.idToNodeMap_[selectedId];
222 this.set(newFolder.path + '.isSelected', true); 302 this.set(newFolder.path + '.isSelectedFolder', true);
223 this.selectedId = selectedId; 303 this.selectedId = selectedId;
224 }, 304 },
225 305
226 /** 306 /**
227 * Handles events that open and close folders. 307 * Handles events that open and close folders.
228 * @param {CustomEvent} e 308 * @param {CustomEvent} e
229 * @private 309 * @private
230 */ 310 */
231 onFolderOpenChanged_: function(e) { 311 onFolderOpenChanged_: function(e) {
232 var folder = this.idToNodeMap_[e.detail.id]; 312 var folder = this.idToNodeMap_[e.detail.id];
233 this.set(folder.path + '.isOpen', e.detail.open); 313 this.set(folder.path + '.isOpen', e.detail.open);
234 if (!folder.isOpen && this.isAncestorOfSelected_(folder)) 314 if (!folder.isOpen && this.isAncestorOfSelected_(folder))
235 this.fire('selected-folder-changed', folder.id); 315 this.fire('selected-folder-changed', folder.id);
236 }, 316 },
317
318 /**
319 * Select a single item in the list.
320 * @param {string} id
321 * @private
322 */
323 onSingleItemSelected_: function(id) {
324 this.clearSelectedItems_();
325 this.prevSelectedItemIndex_ = this.getIndexInList_(id);
326 this.set(
327 'displayedList.#' + this.prevSelectedItemIndex_ + '.isSelectedItem',
328 true);
329 },
330
331 /**
332 * Select multiple items based on |prevSelectedItemIndex_| and the selected
333 * item. If |prevSelectedItemIndex_| is not set, single select the item.
334 * @param {string} id
335 * @private
336 */
337 onMultipleItemsShiftSelected_: function(id) {
338 this.clearSelectedItems_();
339 var startIndex, endIndex;
340 if (this.prevSelectedItemIndex_ == undefined) {
341 this.prevSelectedItemIndex_ = this.getIndexInList_(id);
342 startIndex = this.prevSelectedItemIndex_;
343 endIndex = this.prevSelectedItemIndex_;
344 } else {
345 var currIndex = this.getIndexInList_(id);
346 startIndex = Math.min(this.prevSelectedItemIndex_, currIndex);
347 endIndex = Math.max(this.prevSelectedItemIndex_, currIndex);
348 }
349 for (var i = startIndex; i <= endIndex; i++)
350 this.set('displayedList.#' + i + '.isSelectedItem', true);
351 },
352
353 /**
354 * Select multiple items with the index of the last elected item as
355 * |prevSelectedItemIndex_|.
356 * @param {string} id
357 * @private
358 */
359 onMultipleItemsCtrlSelected_: function(id) {
360 this.prevSelectedItemIndex_ = this.getIndexInList_(id);
361 this.set(
362 'displayedList.#' + this.prevSelectedItemIndex_ + '.isSelectedItem',
363 true);
364 },
365
366 /**
367 * Select item according to keyboard behaviours.
368 * @param {CustomEvent} e
369 * @private
370 */
371 onItemSelected_: function(e) {
372 if (e.detail.shiftKey)
373 this.onMultipleItemsShiftSelected_(e.detail.id);
374 else if (e.detail.ctrlKey)
375 this.onMultipleItemsCtrlSelected_(e.detail.id);
376 else
377 this.onSingleItemSelected_(e.detail.id);
378 },
237 }); 379 });
238 380
239 //////////////////////////////////////////////////////////////////////////////// 381 ////////////////////////////////////////////////////////////////////////////////
240 // bookmarks-store, static methods: 382 // bookmarks-store, static methods:
241 383
242 /** 384 /**
243 * Stores the path from the store to a node inside the node. 385 * Stores the path from the store to a node inside the node.
244 * @param {BookmarkTreeNode} bookmarkNode 386 * @param {BookmarkTreeNode} bookmarkNode
245 * @param {number} startIndex 387 * @param {number} startIndex
246 */ 388 */
247 BookmarksStore.generatePaths = function(bookmarkNode, startIndex) { 389 BookmarksStore.generatePaths = function(bookmarkNode, startIndex) {
248 if (!bookmarkNode.children) 390 if (!bookmarkNode.children)
249 return; 391 return;
250 392
251 for (var i = startIndex; i < bookmarkNode.children.length; i++) { 393 for (var i = startIndex; i < bookmarkNode.children.length; i++) {
252 bookmarkNode.children[i].path = bookmarkNode.path + '.children.#' + i; 394 bookmarkNode.children[i].path = bookmarkNode.path + '.children.#' + i;
253 BookmarksStore.generatePaths(bookmarkNode.children[i], 0); 395 BookmarksStore.generatePaths(bookmarkNode.children[i], 0);
254 } 396 }
255 }; 397 };
256 398
257 /** 399 /**
258 * Initializes the nodes in the bookmarks tree as follows: 400 * Initializes the nodes in the bookmarks tree as follows:
259 * - Populates |idToNodeMap_| with a mapping of all node ids to their 401 * - Populates |idToNodeMap_| with a mapping of all node ids to their
260 * corresponding BookmarkTreeNode. 402 * corresponding BookmarkTreeNode.
261 * - Sets all the nodes to not selected and open by default. 403 * - Sets all the nodes to not selected and open by default.
262 * @param {BookmarkTreeNode} bookmarkNode 404 * @param {BookmarkTreeNode} bookmarkNode
263 * @param {Object=} idToNodeMap 405 * @param {Object=} idToNodeMap
264 */ 406 */
265 BookmarksStore.initNodes = function(bookmarkNode, idToNodeMap) { 407 BookmarksStore.initNodes = function(bookmarkNode, idToNodeMap) {
408 bookmarkNode.isSelectedItem = false;
266 if (idToNodeMap) 409 if (idToNodeMap)
267 idToNodeMap[bookmarkNode.id] = bookmarkNode; 410 idToNodeMap[bookmarkNode.id] = bookmarkNode;
268 411
269 if (bookmarkNode.url) 412 if (bookmarkNode.url)
270 return; 413 return;
271 414
272 bookmarkNode.isSelected = false; 415 bookmarkNode.isSelectedFolder = false;
273 bookmarkNode.isOpen = true; 416 bookmarkNode.isOpen = true;
274 for (var i = 0; i < bookmarkNode.children.length; i++) 417 for (var i = 0; i < bookmarkNode.children.length; i++)
275 BookmarksStore.initNodes(bookmarkNode.children[i], idToNodeMap); 418 BookmarksStore.initNodes(bookmarkNode.children[i], idToNodeMap);
276 }; 419 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698