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

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/elements/ClassesPaneWidget.js

Issue 2343773002: DevTools: Autocomplete class names in ClassesPaneWidget (Closed)
Patch Set: Removed caching Created 4 years, 3 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
« no previous file with comments | « no previous file | third_party/WebKit/Source/devtools/front_end/elements/elementsPanel.css » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2015 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 /** 5 /**
6 * @constructor 6 * @constructor
7 * @extends {WebInspector.Widget} 7 * @extends {WebInspector.Widget}
8 */ 8 */
9 WebInspector.ClassesPaneWidget = function() 9 WebInspector.ClassesPaneWidget = function()
10 { 10 {
11 WebInspector.Widget.call(this); 11 WebInspector.Widget.call(this);
12 this.element.className = "styles-element-classes-pane"; 12 this.element.className = "styles-element-classes-pane";
13 var container = this.element.createChild("div", "title-container"); 13 var container = this.element.createChild("div", "title-container");
14 this._input = container.createChild("input", "new-class-input monospace"); 14 this._input = container.createChild("div", "new-class-input monospace");
15 this._input.placeholder = WebInspector.UIString("Add new class"); 15 this._input.setAttribute("placeholder" , WebInspector.UIString("Add new clas s"));
16 this._input.addEventListener("keydown", this._onKeyDown.bind(this), false);
17 this.setDefaultFocusedElement(this._input); 16 this.setDefaultFocusedElement(this._input);
17
18 this._classesContainer = this.element.createChild("div", "source-code"); 18 this._classesContainer = this.element.createChild("div", "source-code");
19 this._classesContainer.classList.add("styles-element-classes-container"); 19 this._classesContainer.classList.add("styles-element-classes-container");
20 this._prompt = new WebInspector.ClassesPaneWidget.ClassNamePrompt();
21 this._prompt.setAutocompletionTimeout(0);
22 this._prompt.renderAsBlock();
23
24 this._frameClasses = new Map();
25 this._selectedNode = null;
26 this._requestedFrames = new Set();
27 var proxyElement = this._prompt.attach(this._input);
28 proxyElement.addEventListener("keydown", this._onKeyDown.bind(this), false);
20 29
21 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspec tor.DOMModel.Events.DOMMutated, this._onDOMMutated, this); 30 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspec tor.DOMModel.Events.DOMMutated, this._onDOMMutated, this);
31 WebInspector.targetManager.addModelListener(WebInspector.CSSModel, WebInspec tor.CSSModel.Events.StyleSheetAdded, this._styleSheetAdded, this);
32 WebInspector.targetManager.addModelListener(WebInspector.CSSModel, WebInspec tor.CSSModel.Events.StyleSheetChanged, this._styleSheetChanged, this);
33 WebInspector.targetManager.addModelListener(WebInspector.CSSModel, WebInspec tor.CSSModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this);
34 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspec tor.DOMModel.Events.DocumentUpdated, this._documentUpdated, this);
35 WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Event s.MainFrameNavigated, this._mainFrameNavigated, this);
36
22 /** @type {!Set<!WebInspector.DOMNode>} */ 37 /** @type {!Set<!WebInspector.DOMNode>} */
23 this._mutatingNodes = new Set(); 38 this._mutatingNodes = new Set();
24 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this._upd ate, this); 39 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this._upd ate, this);
25 } 40 }
26 41
27 WebInspector.ClassesPaneWidget._classesSymbol = Symbol("WebInspector.ClassesPane Widget._classesSymbol"); 42 WebInspector.ClassesPaneWidget._classesSymbol = Symbol("WebInspector.ClassesPane Widget._classesSymbol");
28 43
29 WebInspector.ClassesPaneWidget.prototype = { 44 WebInspector.ClassesPaneWidget.prototype = {
30 /** 45 /**
31 * @param {!Event} event 46 * @param {!Event} event
32 */ 47 */
33 _onKeyDown: function(event) 48 _onKeyDown: function(event)
34 { 49 {
35 var text = event.target.value; 50 var text = event.target.innerText;
36 if (isEscKey(event)) { 51 if (isEscKey(event)) {
37 event.target.value = ""; 52 this._hidePrompt();
53 event.target.innerText = "";
38 if (!text.isWhitespace()) 54 if (!text.isWhitespace())
39 event.consume(true); 55 event.consume(true);
40 return; 56 return;
41 } 57 }
42 58
43 if (!isEnterKey(event)) 59 if (!isEnterKey(event))
44 return; 60 return;
45 var node = WebInspector.context.flavor(WebInspector.DOMNode); 61 var node = WebInspector.context.flavor(WebInspector.DOMNode);
46 if (!node) 62 if (!node)
47 return; 63 return;
48 64 this._hidePrompt();
49 event.target.value = ""; 65 event.target.innerText = "";
50 var classNames = text.split(/[.,\s]/); 66 var classNames = text.split(/[.,\s]/);
51 for (var className of classNames) { 67 for (var className of classNames) {
52 var className = className.trim(); 68 var className = className.trim();
53 if (!className.length) 69 if (!className.length)
54 continue; 70 continue;
55 this._toggleClass(node, className, true); 71 this._toggleClass(node, className, true);
56 } 72 }
57 this._installNodeClasses(node); 73 this._installNodeClasses(node);
58 this._update(); 74 this._update();
59 event.consume(true); 75 event.consume(true);
60 }, 76 },
61 77
62 /** 78 /**
63 * @param {!WebInspector.Event} event 79 * @param {!WebInspector.Event} event
64 */ 80 */
81 _documentUpdated: function(event)
lushnikov 2016/09/16 16:40:01 let's pull the classnames whenever the text prompt
ahmetemirercin 2016/09/16 16:51:14 But what if these events occurs when the text prom
lushnikov 2016/09/16 17:09:17 If the completions change while the suggest box is
ahmetemirercin 2016/09/16 19:23:00 Done.
82 {
83 if (event.data && this.isShowing()) {
84 var updatedDocument = /** @type {!WebInspector.DOMDocument} */ (even t.data);
85 this._getDomClasses(updatedDocument);
86 }
87 },
88
89 /**
90 * @param {!WebInspector.Event} event
91 */
92 _styleSheetAdded: function(event)
93 {
94 var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.dat a);
95 this._getStylesheetClasses(header);
96 },
97
98 /**
99 * @param {!WebInspector.Event} event
100 */
101 _styleSheetChanged: function(event)
102 {
103 var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.dat a);
104
105 this._collectFrameClasses(header.frameId);
106 },
107
108 /**
109 * @param {!WebInspector.Event} event
110 */
111 _styleSheetRemoved: function(event)
112 {
113 var header = /** @type {!WebInspector.CSSStyleSheetHeader} */ (event.dat a);
114 this._collectFrameClasses(header.frameId);
115 },
116
117 /**
118 * @param {!WebInspector.Event} event
119 */
120 _mainFrameNavigated: function(event)
121 {
122 this._selectedNode = null;
123 this._requestedFrames = new Set();
124 this._frameClasses = new Map();
125 this._prompt.clearCompletions();
126 },
127
128 _hidePrompt: function()
129 {
130 this._prompt.clearAutoComplete();
131 },
132
133 /**
134 * @param {?WebInspector.DOMDocument=} inspectedDocument
135 */
136 _getDomClasses: function(inspectedDocument)
137 {
138 if (!inspectedDocument && this._selectedNode)
139 inspectedDocument = this._selectedNode.ownerDocument;
140
141 if (!inspectedDocument)
142 return;
143
144 var frameId = inspectedDocument.frameId() || WebInspector.ResourceTreeMo del.fromTarget(inspectedDocument.target()).mainFrame.id;
145 /**
146 * @param {!Array.<string>} classNames
147 * @this {!WebInspector.ClassesPaneWidget}
148 */
149 function classNamesCallback(classNames)
150 {
151 this._addFrameClasses(frameId, classNames);
152 }
153
154 inspectedDocument.domModel().classNamesPromise(inspectedDocument.id).the n(classNamesCallback.bind(this));
155 },
156
157
158 _getCssClasses: function()
159 {
160 if (!this._selectedNode)
161 return;
162
163 var frameId = this._selectedNode.frameId();
164 var cssModel = WebInspector.CSSModel.fromTarget(this._selectedNode.targe t());
165 var headers = cssModel.allStyleSheets();
166 for (var stylesheet of cssModel.allStyleSheets())
167 if (stylesheet.frameId === frameId)
168 this._getStylesheetClasses(stylesheet);
169 },
170
171 /**
172 * @param {?PageAgent.FrameId} frameId
173 * @return {!Set<string>}
174 */
175 _getFrameClasses: function(frameId)
176 {
177 if (!this._frameClasses.get(frameId))
178 this._frameClasses.set(frameId, new Set());
179
180 return this._frameClasses.get(frameId);
181 },
182
183 _selectedFrameChanged: function()
184 {
185 var newFrameId = this._selectedFrameId();
186 this._prompt.sourceFrameId = newFrameId;
187 this._updateCompletions(newFrameId);
188
189 if (this.isShowing() && !this._requestedFrames.has(newFrameId))
190 this._collectFrameClasses(newFrameId);
191 },
192
193 /**
194 * @return {?PageAgent.FrameId}
195 */
196 _selectedFrameId: function()
197 {
198 return this._selectedNode ? this._selectedNode.frameId() || WebInspector .ResourceTreeModel.fromTarget(this._selectedNode.target()).mainFrame.id : 0;
199 },
200
201 /**
202 * @param {!WebInspector.CSSStyleSheetHeader} stylesheet
203 */
204 _getStylesheetClasses: function(stylesheet)
205 {
206 if (!this.isShowing())
207 return;
208
209 /**
210 * @param {!WebInspector.CSSStyleSheetHeader} stylsheet
211 * @param {!Array.<string>} classNames
212 * @this {!WebInspector.ClassesPaneWidget}
213 */
214 function classNamesCallback(stylsheet, classNames)
215 {
216 this._addFrameClasses(stylsheet.frameId, classNames);
217 }
218
219 var cssModel = stylesheet.cssModel();
220 cssModel.classNamesPromise(stylesheet.id).then(classNamesCallback.bind(t his, stylesheet));
221 },
222
223 /**
224 * @param {?PageAgent.FrameId} frameId
225 * @param {!Array.<string>} classNames
226 */
227 _addFrameClasses: function(frameId, classNames)
228 {
229 if (!classNames)
230 return;
231 this._getFrameClasses(frameId).addAll(classNames);
232 this._updateCompletions(frameId);
233 },
234
235 /**
236 * @param {?PageAgent.FrameId} frameId
237 */
238 _updateCompletions: function(frameId)
239 {
240 if (frameId === this._prompt.sourceFrameId)
241 this._prompt.updateCompletions(this._getFrameClasses(frameId).values Array());
242 },
243
244 /**
245 * @param {?PageAgent.FrameId} frameId
246 */
247 _collectFrameClasses: function(frameId)
248 {
249 this._requestedFrames.add(frameId);
250 this._getDomClasses();
251 this._getCssClasses();
252 },
253
254 /**
255 * @param {!WebInspector.Event} event
256 */
65 _onDOMMutated: function(event) 257 _onDOMMutated: function(event)
66 { 258 {
67 var node = /** @type {!WebInspector.DOMNode} */(event.data); 259 var node = /** @type {!WebInspector.DOMNode} */(event.data);
68 if (this._mutatingNodes.has(node)) 260 if (this._mutatingNodes.has(node))
69 return; 261 return;
70 delete node[WebInspector.ClassesPaneWidget._classesSymbol]; 262 delete node[WebInspector.ClassesPaneWidget._classesSymbol];
71 this._update(); 263 this._update();
72 }, 264 },
73 265
74 /** 266 /**
75 * @override 267 * @override
76 */ 268 */
77 wasShown: function() 269 wasShown: function()
78 { 270 {
79 this._update(); 271 this._update();
80 }, 272 },
81 273
82 _update: function() 274 _update: function()
83 { 275 {
84 if (!this.isShowing()) 276 if (!this.isShowing())
85 return; 277 return;
86
87 var node = WebInspector.context.flavor(WebInspector.DOMNode); 278 var node = WebInspector.context.flavor(WebInspector.DOMNode);
88 if (node) 279 if (node)
89 node = node.enclosingElementOrSelf(); 280 node = node.enclosingElementOrSelf();
90 281
91 this._classesContainer.removeChildren(); 282 this._classesContainer.removeChildren();
92 this._input.disabled = !node; 283 this._input.disabled = !node;
93 284
94 if (!node) 285 if (!node)
95 return; 286 return;
96 287
288 var oldFrameId = this._selectedFrameId();
289 this._selectedNode = node;
290 if (this._selectedFrameId() !== oldFrameId)
291 this._selectedFrameChanged();
97 var classes = this._nodeClasses(node); 292 var classes = this._nodeClasses(node);
98 var keys = classes.keysArray(); 293 var keys = classes.keysArray();
99 keys.sort(String.caseInsensetiveComparator); 294 keys.sort(String.caseInsensetiveComparator);
100 for (var i = 0; i < keys.length; ++i) { 295 for (var i = 0; i < keys.length; ++i) {
101 var className = keys[i]; 296 var className = keys[i];
102 var label = createCheckboxLabel(className, classes.get(className)); 297 var label = createCheckboxLabel(className, classes.get(className));
103 label.visualizeFocus = true; 298 label.visualizeFocus = true;
104 label.classList.add("monospace"); 299 label.classList.add("monospace");
105 label.checkboxElement.addEventListener("click", this._onClick.bind(t his, className), false); 300 label.checkboxElement.addEventListener("click", this._onClick.bind(t his, className), false);
106 this._classesContainer.appendChild(label); 301 this._classesContainer.appendChild(label);
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
204 399
205 /** 400 /**
206 * @override 401 * @override
207 * @return {!WebInspector.ToolbarItem} 402 * @return {!WebInspector.ToolbarItem}
208 */ 403 */
209 item: function() 404 item: function()
210 { 405 {
211 return this._button; 406 return this._button;
212 } 407 }
213 } 408 }
409
410 /**
411 * @constructor
412 * @extends {WebInspector.TextPrompt}
413 */
414 WebInspector.ClassesPaneWidget.ClassNamePrompt = function()
415 {
416 WebInspector.TextPrompt.call(this, this._buildClassNameCompletions.bind(this ), " ");
417 this.setSuggestBoxEnabled(true);
418 this.disableDefaultSuggestionForEmptyInput();
419 this._classNameCompletions = [];
420 this.sourceFrameId = 0;
421 }
422
423 WebInspector.ClassesPaneWidget.ClassNamePrompt.prototype = {
424 /**
425 * @override
426 * @param {!Event} event
427 */
428 onKeyDown: function(event)
429 {
430 switch (event.key) {
431 case "Enter":
432 // Accept any available autocompletions and advance to the next fiel d.
433 if (this.autoCompleteElement && this.autoCompleteElement.textContent .length) {
434 this.acceptAutoComplete();
435 return;
436 }
437 break;
438 }
439
440 WebInspector.TextPrompt.prototype.onKeyDown.call(this, event);
441 },
442
443 /**
444 * @param {!Array.<string>} classNameList
445 */
446 updateCompletions: function(classNameList)
447 {
448 this._classNameCompletions = classNameList;
449 },
450
451 clearCompletions: function()
452 {
453 this._classNameCompletions = [];
454 },
455
456 /**
457 * @param {!Element} proxyElement
458 * @param {!Range} wordRange
459 * @param {boolean} force
460 * @param {function(!Array.<string>, number=)} completionsReadyCallback
461 */
462 _buildClassNameCompletions: function(proxyElement, wordRange, force, complet ionsReadyCallback)
463 {
464 var prefix = wordRange.toString();
465
466 if (!prefix && !force && !proxyElement.textContent.length) {
467 completionsReadyCallback([]);
468 return;
469 }
470
471 var results = this._classNameCompletions.filter((value) => value.startsW ith(prefix));
472 var selectedIndex = 0;
473 completionsReadyCallback(results, selectedIndex);
474 },
475
476 __proto__: WebInspector.TextPrompt.prototype
477 }
OLDNEW
« no previous file with comments | « no previous file | third_party/WebKit/Source/devtools/front_end/elements/elementsPanel.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698