Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 * Use of this source code is governed by a BSD-style license that can be | 3 * Use of this source code is governed by a BSD-style license that can be |
| 4 * found in the LICENSE file. | 4 * found in the LICENSE file. |
| 5 */ | 5 */ |
| 6 /** | 6 /** |
| 7 * @unrestricted | 7 * @unrestricted |
| 8 * @implements {UI.ListDelegate} | 8 * @implements {UI.ListDelegate} |
| 9 */ | 9 */ |
| 10 QuickOpen.FilteredListWidget = class extends UI.VBox { | 10 QuickOpen.FilteredListWidget = class extends UI.VBox { |
| 11 /** | 11 /** |
| 12 * @param {!QuickOpen.FilteredListWidget.Delegate} delegate | 12 * @param {?QuickOpen.FilteredListWidget.Delegate} delegate |
| 13 * @param {!Array<string>=} promptHistory | 13 * @param {!Array<string>=} promptHistory |
| 14 */ | 14 */ |
| 15 constructor(delegate, promptHistory) { | 15 constructor(delegate, promptHistory) { |
| 16 super(true); | 16 super(true); |
| 17 this._promptHistory = promptHistory || []; | 17 this._promptHistory = promptHistory || []; |
| 18 this._renderAsTwoRows = delegate.renderAsTwoRows(); | |
| 19 | 18 |
| 20 this.contentElement.classList.add('filtered-list-widget'); | 19 this.contentElement.classList.add('filtered-list-widget'); |
| 21 this.contentElement.addEventListener('keydown', this._onKeyDown.bind(this), true); | 20 this.contentElement.addEventListener('keydown', this._onKeyDown.bind(this), true); |
| 22 this.registerRequiredCSS('quick_open/filteredListWidget.css'); | 21 this.registerRequiredCSS('quick_open/filteredListWidget.css'); |
| 23 | 22 |
| 24 this._promptElement = this.contentElement.createChild('div', 'filtered-list- widget-input'); | 23 this._promptElement = this.contentElement.createChild('div', 'filtered-list- widget-input'); |
| 25 this._promptElement.setAttribute('spellcheck', 'false'); | 24 this._promptElement.setAttribute('spellcheck', 'false'); |
| 26 this._promptElement.setAttribute('contenteditable', 'plaintext-only'); | 25 this._promptElement.setAttribute('contenteditable', 'plaintext-only'); |
| 27 this._prompt = new UI.TextPrompt(); | 26 this._prompt = new UI.TextPrompt(); |
| 28 this._prompt.initialize(() => Promise.resolve([])); | 27 this._prompt.initialize(() => Promise.resolve([])); |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 39 this._list = new UI.ListControl(this, UI.ListMode.EqualHeightItems); | 38 this._list = new UI.ListControl(this, UI.ListMode.EqualHeightItems); |
| 40 this._itemElementsContainer = this._list.element; | 39 this._itemElementsContainer = this._list.element; |
| 41 this._itemElementsContainer.classList.add('container'); | 40 this._itemElementsContainer.classList.add('container'); |
| 42 this._bottomElementsContainer.appendChild(this._itemElementsContainer); | 41 this._bottomElementsContainer.appendChild(this._itemElementsContainer); |
| 43 | 42 |
| 44 this._notFoundElement = this._bottomElementsContainer.createChild('div', 'no t-found-text'); | 43 this._notFoundElement = this._bottomElementsContainer.createChild('div', 'no t-found-text'); |
| 45 this._notFoundElement.classList.add('hidden'); | 44 this._notFoundElement.classList.add('hidden'); |
| 46 | 45 |
| 47 this.setDefaultFocusedElement(this._promptElement); | 46 this.setDefaultFocusedElement(this._promptElement); |
| 48 | 47 |
| 49 this._delegate = delegate; | 48 this._prefix = ''; |
| 50 this._delegate.setRefreshCallback(this._itemsLoaded.bind(this)); | 49 this._delegate = null; |
|
pfeldman
2017/03/06 18:37:18
Could you extract swappable delegate-related chang
einbinder
2017/03/08 22:39:56
Done.
| |
| 50 this.setDelegate(delegate); | |
| 51 this._itemsLoaded(); | 51 this._itemsLoaded(); |
| 52 this._updateShowMatchingItems(); | |
| 53 } | 52 } |
| 54 | 53 |
| 55 /** | 54 /** |
| 56 * @param {string} query | 55 * @param {string} query |
| 57 * @return {!RegExp} | 56 * @return {!RegExp} |
| 58 */ | 57 */ |
| 59 static filterRegex(query) { | 58 static filterRegex(query) { |
| 60 const toEscape = String.regexSpecialCharacters(); | 59 const toEscape = String.regexSpecialCharacters(); |
| 61 var regexString = ''; | 60 var regexString = ''; |
| 62 for (var i = 0; i < query.length; ++i) { | 61 for (var i = 0; i < query.length; ++i) { |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 111 return false; | 110 return false; |
| 112 } | 111 } |
| 113 | 112 |
| 114 showAsDialog() { | 113 showAsDialog() { |
| 115 this._dialog = new UI.Dialog(); | 114 this._dialog = new UI.Dialog(); |
| 116 this._dialog.setMaxContentSize(new UI.Size(504, 340)); | 115 this._dialog.setMaxContentSize(new UI.Size(504, 340)); |
| 117 this._dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.SetMaxHeight); | 116 this._dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.SetMaxHeight); |
| 118 this._dialog.setContentPosition(null, 22); | 117 this._dialog.setContentPosition(null, 22); |
| 119 this.show(this._dialog.contentElement); | 118 this.show(this._dialog.contentElement); |
| 120 this._dialog.show(); | 119 this._dialog.show(); |
| 121 this._updateShowMatchingItems(); | |
| 122 } | 120 } |
| 123 | 121 |
| 124 /** | 122 /** |
| 123 * @param {?QuickOpen.FilteredListWidget.Delegate} delegate | |
| 124 */ | |
| 125 setDelegate(delegate) { | |
| 126 if (this._delegate === delegate) | |
| 127 return; | |
| 128 this._delegate = delegate; | |
| 129 if (!delegate) | |
| 130 return; | |
| 131 this._delegate.setRefreshCallback(this._itemsLoaded.bind(this)); | |
| 132 this._delegate.queryChanged(this._cleanValue()); | |
| 133 this._itemsLoaded(); | |
| 134 } | |
| 135 | |
| 136 /** | |
| 137 * @param {string} prefix | |
| 138 */ | |
| 139 setPrefix(prefix) { | |
| 140 this._prefix = prefix; | |
| 141 } | |
| 142 | |
| 143 /** | |
| 125 * @return {string} | 144 * @return {string} |
| 126 */ | 145 */ |
| 127 _value() { | 146 _value() { |
| 128 return this._prompt.text().trim(); | 147 return this._prompt.text().trim(); |
| 129 } | 148 } |
| 130 | 149 |
| 150 _cleanValue() { | |
| 151 return this._value().substring(this._prefix.length); | |
| 152 } | |
| 153 | |
| 131 /** | 154 /** |
| 132 * @override | 155 * @override |
| 133 */ | 156 */ |
| 134 wasShown() { | 157 wasShown() { |
| 135 this._list.invalidateItemHeight(); | 158 this._list.invalidateItemHeight(); |
| 136 } | 159 } |
| 137 | 160 |
| 138 /** | 161 /** |
| 139 * @override | 162 * @override |
| 140 */ | 163 */ |
| 141 willHide() { | 164 willHide() { |
| 142 this._delegate.dispose(); | 165 this.emit(new QuickOpen.FilteredListWidget.DisposeEvent()); |
| 166 if (this._delegate) | |
| 167 this._delegate.dispose(); | |
| 143 if (this._filterTimer) | 168 if (this._filterTimer) |
| 144 clearTimeout(this._filterTimer); | 169 clearTimeout(this._filterTimer); |
| 145 } | 170 } |
| 146 | 171 |
| 147 /** | 172 /** |
| 148 * @param {!Event} event | 173 * @param {!Event} event |
| 149 */ | 174 */ |
| 150 _onEnter(event) { | 175 _onEnter(event) { |
| 151 event.preventDefault(); | 176 event.preventDefault(); |
| 152 if (!this._delegate.itemCount()) | 177 var selectedIndexInDelegate = this._delegate.itemCount() ? this._list.select edItem() : null; |
| 153 return; | |
| 154 var selectedIndexInDelegate = this._shouldShowMatchingItems() ? this._list.s electedItem() : null; | |
| 155 | 178 |
| 156 // Detach dialog before allowing delegate to override focus. | 179 // Detach dialog before allowing delegate to override focus. |
| 157 if (this._dialog) | 180 if (this._dialog) |
| 158 this._dialog.hide(); | 181 this._dialog.hide(); |
| 159 this._selectItemWithQuery(selectedIndexInDelegate, this._value()); | 182 this._selectItem(selectedIndexInDelegate); |
| 160 } | 183 } |
| 161 | 184 |
| 162 _itemsLoaded() { | 185 _itemsLoaded() { |
| 163 if (this._loadTimeout) | 186 if (this._loadTimeout) |
| 164 return; | 187 return; |
| 165 this._loadTimeout = setTimeout(this._updateAfterItemsLoaded.bind(this), 0); | 188 this._loadTimeout = setTimeout(this._updateAfterItemsLoaded.bind(this), 0); |
| 166 } | 189 } |
| 167 | 190 |
| 168 _updateAfterItemsLoaded() { | 191 _updateAfterItemsLoaded() { |
| 169 delete this._loadTimeout; | 192 delete this._loadTimeout; |
| 170 this._filterItems(); | 193 this._filterItems(); |
| 171 } | 194 } |
| 172 | 195 |
| 173 /** | 196 /** |
| 174 * @override | 197 * @override |
| 175 * @param {number} item | 198 * @param {number} item |
| 176 * @return {!Element} | 199 * @return {!Element} |
| 177 */ | 200 */ |
| 178 createElementForItem(item) { | 201 createElementForItem(item) { |
| 179 var itemElement = createElement('div'); | 202 var itemElement = createElement('div'); |
| 180 itemElement.className = 'filtered-list-widget-item ' + (this._renderAsTwoRow s ? 'two-rows' : 'one-row'); | 203 itemElement.className = 'filtered-list-widget-item ' + (this._delegate.rende rAsTwoRows() ? 'two-rows' : 'one-row'); |
| 181 var titleElement = itemElement.createChild('div', 'filtered-list-widget-titl e'); | 204 var titleElement = itemElement.createChild('div', 'filtered-list-widget-titl e'); |
| 182 var subtitleElement = itemElement.createChild('div', 'filtered-list-widget-s ubtitle'); | 205 var subtitleElement = itemElement.createChild('div', 'filtered-list-widget-s ubtitle'); |
| 183 subtitleElement.textContent = '\u200B'; | 206 subtitleElement.textContent = '\u200B'; |
| 184 this._delegate.renderItem(item, this._value(), titleElement, subtitleElement ); | 207 this._delegate.renderItem(item, this._cleanValue(), titleElement, subtitleEl ement); |
| 185 itemElement.addEventListener('click', event => { | 208 itemElement.addEventListener('click', event => { |
| 186 event.consume(true); | 209 event.consume(true); |
| 187 // Detach dialog before allowing delegate to override focus. | 210 // Detach dialog before allowing delegate to override focus. |
| 188 if (this._dialog) | 211 if (this._dialog) |
| 189 this._dialog.hide(); | 212 this._dialog.hide(); |
| 190 this._selectItemWithQuery(item, this._value()); | 213 this._selectItem(item); |
| 191 }, false); | 214 }, false); |
| 192 return itemElement; | 215 return itemElement; |
| 193 } | 216 } |
| 194 | 217 |
| 195 /** | 218 /** |
| 196 * @override | 219 * @override |
| 197 * @param {number} item | 220 * @param {number} item |
| 198 * @return {number} | 221 * @return {number} |
| 199 */ | 222 */ |
| 200 heightForItem(item) { | 223 heightForItem(item) { |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 223 fromElement.classList.remove('selected'); | 246 fromElement.classList.remove('selected'); |
| 224 if (toElement) | 247 if (toElement) |
| 225 toElement.classList.add('selected'); | 248 toElement.classList.add('selected'); |
| 226 } | 249 } |
| 227 | 250 |
| 228 /** | 251 /** |
| 229 * @param {string} query | 252 * @param {string} query |
| 230 */ | 253 */ |
| 231 setQuery(query) { | 254 setQuery(query) { |
| 232 this._prompt.setText(query); | 255 this._prompt.setText(query); |
| 256 this._queryChanged(); | |
| 233 this._prompt.autoCompleteSoon(true); | 257 this._prompt.autoCompleteSoon(true); |
| 234 this._scheduleFilter(); | 258 this._scheduleFilter(); |
| 235 this._updateShowMatchingItems(); | |
| 236 } | 259 } |
| 237 | 260 |
| 238 _tabKeyPressed() { | 261 _tabKeyPressed() { |
| 239 var userEnteredText = this._prompt.text(); | 262 var userEnteredText = this._prompt.text(); |
| 240 var completion; | 263 var completion; |
| 241 for (var i = this._promptHistory.length - 1; i >= 0; i--) { | 264 for (var i = this._promptHistory.length - 1; i >= 0; i--) { |
| 242 if (this._promptHistory[i] !== userEnteredText && this._promptHistory[i].s tartsWith(userEnteredText)) { | 265 if (this._promptHistory[i] !== userEnteredText && this._promptHistory[i].s tartsWith(userEnteredText)) { |
| 243 completion = this._promptHistory[i]; | 266 completion = this._promptHistory[i]; |
| 244 break; | 267 break; |
| 245 } | 268 } |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 262 delete this._scoringTimer; | 285 delete this._scoringTimer; |
| 263 | 286 |
| 264 if (this._refreshListWithCurrentResult) | 287 if (this._refreshListWithCurrentResult) |
| 265 this._refreshListWithCurrentResult(); | 288 this._refreshListWithCurrentResult(); |
| 266 } | 289 } |
| 267 | 290 |
| 268 this._progressBarElement.style.transform = 'scaleX(0)'; | 291 this._progressBarElement.style.transform = 'scaleX(0)'; |
| 269 this._progressBarElement.classList.remove('filtered-widget-progress-fade'); | 292 this._progressBarElement.classList.remove('filtered-widget-progress-fade'); |
| 270 this._progressBarElement.classList.remove('hidden'); | 293 this._progressBarElement.classList.remove('hidden'); |
| 271 | 294 |
| 272 var query = this._delegate.rewriteQuery(this._value()); | 295 var query = this._delegate.rewriteQuery(this._cleanValue()); |
| 273 this._query = query; | 296 this._query = query; |
| 274 | 297 |
| 275 var filterRegex = query ? QuickOpen.FilteredListWidget.filterRegex(query) : null; | 298 var filterRegex = query ? QuickOpen.FilteredListWidget.filterRegex(query) : null; |
| 276 | 299 |
| 277 var filteredItems = []; | 300 var filteredItems = []; |
| 278 | 301 |
| 279 var bestScores = []; | 302 var bestScores = []; |
| 280 var bestItems = []; | 303 var bestItems = []; |
| 281 var bestItemsToCollect = 100; | 304 var bestItemsToCollect = 100; |
| 282 var minBestScore = 0; | 305 var minBestScore = 0; |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 368 this._itemsFilteredForTest(); | 391 this._itemsFilteredForTest(); |
| 369 } | 392 } |
| 370 | 393 |
| 371 /** | 394 /** |
| 372 * @param {boolean} hasItems | 395 * @param {boolean} hasItems |
| 373 */ | 396 */ |
| 374 _updateNotFoundMessage(hasItems) { | 397 _updateNotFoundMessage(hasItems) { |
| 375 this._list.element.classList.toggle('hidden', !hasItems); | 398 this._list.element.classList.toggle('hidden', !hasItems); |
| 376 this._notFoundElement.classList.toggle('hidden', hasItems); | 399 this._notFoundElement.classList.toggle('hidden', hasItems); |
| 377 if (!hasItems) | 400 if (!hasItems) |
| 378 this._notFoundElement.textContent = this._delegate.notFoundText(); | 401 this._notFoundElement.textContent = this._delegate.notFoundText(this._clea nValue()); |
| 402 } | |
| 403 | |
| 404 _onInput() { | |
| 405 this._queryChanged(); | |
| 406 this._scheduleFilter(); | |
| 407 } | |
| 408 | |
| 409 _queryChanged() { | |
| 410 this.emit(new QuickOpen.FilteredListWidget.QueryChangedEvent(this._value())) ; | |
| 411 this._delegate.queryChanged(this._cleanValue()); | |
| 379 } | 412 } |
| 380 | 413 |
| 381 /** | 414 /** |
| 382 * @return {boolean} | |
| 383 */ | |
| 384 _shouldShowMatchingItems() { | |
| 385 return this._delegate.shouldShowMatchingItems(this._value()); | |
| 386 } | |
| 387 | |
| 388 _onInput() { | |
| 389 this._updateShowMatchingItems(); | |
| 390 this._scheduleFilter(); | |
| 391 } | |
| 392 | |
| 393 _updateShowMatchingItems() { | |
| 394 var shouldShowMatchingItems = this._shouldShowMatchingItems(); | |
| 395 this._bottomElementsContainer.classList.toggle('hidden', !shouldShowMatching Items); | |
| 396 } | |
| 397 | |
| 398 /** | |
| 399 * @param {!Event} event | 415 * @param {!Event} event |
| 400 */ | 416 */ |
| 401 _onKeyDown(event) { | 417 _onKeyDown(event) { |
| 402 var handled = false; | 418 var handled = false; |
| 403 switch (event.key) { | 419 switch (event.key) { |
| 404 case 'Enter': | 420 case 'Enter': |
| 405 this._onEnter(event); | 421 this._onEnter(event); |
| 406 return; | 422 return; |
| 407 case 'Tab': | 423 case 'Tab': |
| 408 this._tabKeyPressed(); | 424 this._tabKeyPressed(); |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 425 } | 441 } |
| 426 | 442 |
| 427 _scheduleFilter() { | 443 _scheduleFilter() { |
| 428 if (this._filterTimer) | 444 if (this._filterTimer) |
| 429 return; | 445 return; |
| 430 this._filterTimer = setTimeout(this._filterItems.bind(this), 0); | 446 this._filterTimer = setTimeout(this._filterItems.bind(this), 0); |
| 431 } | 447 } |
| 432 | 448 |
| 433 /** | 449 /** |
| 434 * @param {?number} itemIndex | 450 * @param {?number} itemIndex |
| 435 * @param {string} promptValue | |
| 436 */ | 451 */ |
| 437 _selectItemWithQuery(itemIndex, promptValue) { | 452 _selectItem(itemIndex) { |
|
einbinder
2017/03/03 23:07:14
This was always called with promptValue = this._va
| |
| 438 this._promptHistory.push(promptValue); | 453 this._promptHistory.push(this._value()); |
| 439 if (this._promptHistory.length > 100) | 454 if (this._promptHistory.length > 100) |
| 440 this._promptHistory.shift(); | 455 this._promptHistory.shift(); |
| 441 this._delegate.selectItem(itemIndex, promptValue); | 456 this._delegate.selectItem(itemIndex, this._cleanValue()); |
| 442 } | 457 } |
| 443 }; | 458 }; |
| 444 | 459 |
| 445 | 460 |
| 446 /** | 461 /** |
| 447 * @unrestricted | 462 * @unrestricted |
| 448 */ | 463 */ |
| 449 QuickOpen.FilteredListWidget.Delegate = class { | 464 QuickOpen.FilteredListWidget.Delegate = class { |
| 450 /** | 465 /** |
| 451 * @param {function():void} refreshCallback | 466 * @param {function():void} refreshCallback |
| 452 */ | 467 */ |
| 453 setRefreshCallback(refreshCallback) { | 468 setRefreshCallback(refreshCallback) { |
| 454 this._refreshCallback = refreshCallback; | 469 this._refreshCallback = refreshCallback; |
| 455 } | 470 } |
| 456 | 471 |
| 457 /** | 472 /** |
| 458 * @param {string} query | |
| 459 * @return {boolean} | |
| 460 */ | |
| 461 shouldShowMatchingItems(query) { | |
| 462 return true; | |
| 463 } | |
| 464 | |
| 465 /** | |
| 466 * @return {number} | 473 * @return {number} |
| 467 */ | 474 */ |
| 468 itemCount() { | 475 itemCount() { |
| 469 return 0; | 476 return 0; |
| 470 } | 477 } |
| 471 | 478 |
| 472 /** | 479 /** |
| 473 * @param {number} itemIndex | 480 * @param {number} itemIndex |
| 474 * @return {string} | 481 * @return {string} |
| 475 */ | 482 */ |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 515 | 522 |
| 516 /** | 523 /** |
| 517 * @param {string} query | 524 * @param {string} query |
| 518 * @return {string} | 525 * @return {string} |
| 519 */ | 526 */ |
| 520 rewriteQuery(query) { | 527 rewriteQuery(query) { |
| 521 return query; | 528 return query; |
| 522 } | 529 } |
| 523 | 530 |
| 524 /** | 531 /** |
| 532 * @param {string} query | |
| 533 */ | |
| 534 queryChanged(query) { | |
| 535 } | |
| 536 | |
| 537 /** | |
| 538 * @param {string} query | |
| 525 * @return {string} | 539 * @return {string} |
| 526 */ | 540 */ |
| 527 notFoundText() { | 541 notFoundText(query) { |
| 528 return Common.UIString('No results found'); | 542 return Common.UIString('No results found'); |
| 529 } | 543 } |
| 530 | 544 |
| 531 dispose() { | 545 dispose() { |
| 532 } | 546 } |
| 533 }; | 547 }; |
| 548 | |
| 549 /** @implements {Common.Emittable} */ | |
| 550 QuickOpen.FilteredListWidget.QueryChangedEvent = class { | |
| 551 /** | |
| 552 * @param {string} query | |
| 553 */ | |
| 554 constructor(query) { | |
| 555 this.query = query; | |
| 556 } | |
| 557 }; | |
| 558 | |
| 559 /** @implements {Common.Emittable} */ | |
| 560 QuickOpen.FilteredListWidget.DisposeEvent = class {}; | |
| OLD | NEW |