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

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

Issue 2646283002: ClassesPaneWidget - Add ability to quickly preview autocompleted CSS classes. (Closed)
Patch Set: Fixes after code review. 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
« no previous file with comments | « no previous file | no next file » | 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 * @unrestricted 5 * @unrestricted
6 */ 6 */
7 Elements.ClassesPaneWidget = class extends UI.Widget { 7 Elements.ClassesPaneWidget = class extends UI.Widget {
8 constructor() { 8 constructor() {
9 super(); 9 super();
10 this.element.className = 'styles-element-classes-pane'; 10 this.element.className = 'styles-element-classes-pane';
11 var container = this.element.createChild('div', 'title-container'); 11 var container = this.element.createChild('div', 'title-container');
12 this._input = container.createChild('div', 'new-class-input monospace'); 12 this._input = container.createChild('div', 'new-class-input monospace');
13 this.setDefaultFocusedElement(this._input); 13 this.setDefaultFocusedElement(this._input);
14 this._classesContainer = this.element.createChild('div', 'source-code'); 14 this._classesContainer = this.element.createChild('div', 'source-code');
15 this._classesContainer.classList.add('styles-element-classes-container'); 15 this._classesContainer.classList.add('styles-element-classes-container');
16 this._prompt = new Elements.ClassesPaneWidget.ClassNamePrompt(); 16 this._prompt = new Elements.ClassesPaneWidget.ClassNamePrompt();
17 this._prompt.setAutocompletionTimeout(0); 17 this._prompt.setAutocompletionTimeout(0);
18 this._prompt.renderAsBlock(); 18 this._prompt.renderAsBlock();
19 19
20 var proxyElement = this._prompt.attach(this._input); 20 var proxyElement = this._prompt.attach(this._input);
21 this._prompt.setPlaceholder(Common.UIString('Add new class')); 21 this._prompt.setPlaceholder(Common.UIString('Add new class'));
22 this._prompt.on(UI.TextPrompt.TextChangedEvent, this._onTextChanged, this);
22 proxyElement.addEventListener('keydown', this._onKeyDown.bind(this), false); 23 proxyElement.addEventListener('keydown', this._onKeyDown.bind(this), false);
23 24
24 SDK.targetManager.addModelListener(SDK.DOMModel, SDK.DOMModel.Events.DOMMuta ted, this._onDOMMutated, this); 25 SDK.targetManager.addModelListener(SDK.DOMModel, SDK.DOMModel.Events.DOMMuta ted, this._onDOMMutated, this);
25 /** @type {!Set<!SDK.DOMNode>} */ 26 /** @type {!Set<!SDK.DOMNode>} */
26 this._mutatingNodes = new Set(); 27 this._mutatingNodes = new Set();
27 UI.context.addFlavorChangeListener(SDK.DOMNode, this._update, this); 28 this._updateNodeThrottler = new Common.Throttler(0);
29 /** @type {?SDK.DOMNode} */
30 this._previousTarget = null;
31 UI.context.addFlavorChangeListener(SDK.DOMNode, this._onFlavorChange, this);
32 }
33
34 /**
35 * @param {string} text
36 * @return {!Array.<string>}
37 */
38 _splitTextIntoClasses(text) {
39 return text.split(/[.,\s]/)
40 .map(className => className.trim())
41 .filter(className => className.length);
28 } 42 }
29 43
30 /** 44 /**
31 * @param {!Event} event 45 * @param {!Event} event
32 */ 46 */
33 _onKeyDown(event) { 47 _onKeyDown(event) {
48 if (!isEnterKey(event) && !isEscKey(event))
49 return;
50
34 var text = event.target.textContent; 51 var text = event.target.textContent;
35 if (isEscKey(event)) { 52 if (isEscKey(event)) {
36 event.target.textContent = '';
37 if (!text.isWhitespace()) 53 if (!text.isWhitespace())
38 event.consume(true); 54 event.consume(true);
39 return; 55 text = '';
40 } 56 }
41 57
42 if (!isEnterKey(event)) 58 if (isEnterKey(event))
43 return; 59 event.consume(true);
60
61 this._prompt.clearAutocomplete();
62 event.target.textContent = '';
63
44 var node = UI.context.flavor(SDK.DOMNode); 64 var node = UI.context.flavor(SDK.DOMNode);
45 if (!node) 65 if (!node)
46 return; 66 return;
47 67
48 this._prompt.clearAutocomplete(); 68 var classNames = this._splitTextIntoClasses(text);
49 event.target.textContent = ''; 69 for (var className of classNames)
50 var classNames = text.split(/[.,\s]/);
51 for (var className of classNames) {
52 var className = className.trim();
53 if (!className.length)
54 continue;
55 this._toggleClass(node, className, true); 70 this._toggleClass(node, className, true);
56 }
57 this._installNodeClasses(node); 71 this._installNodeClasses(node);
58 this._update(); 72 this._update();
59 event.consume(true); 73 }
74
75 _onTextChanged() {
76 var node = UI.context.flavor(SDK.DOMNode);
77 if (!node)
78 return;
79 var text = this._prompt.textWithCurrentSuggestion();
80 var classes = this._splitTextIntoClasses(text);
81
82 this._installNodeClasses(node, classes);
83 this._update();
lushnikov 2017/03/22 01:37:33 you don't need _update() here, do you?
60 } 84 }
61 85
62 /** 86 /**
63 * @param {!Common.Event} event 87 * @param {!Common.Event} event
64 */ 88 */
65 _onDOMMutated(event) { 89 _onDOMMutated(event) {
66 var node = /** @type {!SDK.DOMNode} */ (event.data); 90 var node = /** @type {!SDK.DOMNode} */ (event.data);
67 if (this._mutatingNodes.has(node)) 91 if (this._mutatingNodes.has(node))
68 return; 92 return;
69 delete node[Elements.ClassesPaneWidget._classesSymbol]; 93 delete node[Elements.ClassesPaneWidget._classesSymbol];
70 this._update(); 94 this._update();
71 } 95 }
72 96
73 /** 97 /**
98 * @param {!Common.Event} event
99 */
100 _onFlavorChange(event) {
lushnikov 2017/03/22 01:37:33 let's name this _onSelectedNodeChanged
101 if (this._previousTarget && this._prompt.text()) {
102 this._input.textContent = '';
103 this._installNodeClasses(this._previousTarget, [], true);
104 }
105 this._previousTarget = /** @type {?SDK.DOMNode} */ (event.data);
106 this._update();
107 }
108
109 /**
74 * @override 110 * @override
75 */ 111 */
76 wasShown() { 112 wasShown() {
77 this._update(); 113 this._update();
78 } 114 }
79 115
80 _update() { 116 _update() {
81 if (!this.isShowing()) 117 if (!this.isShowing())
82 return; 118 return;
83 119
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
143 * @param {string} className 179 * @param {string} className
144 * @param {boolean} enabled 180 * @param {boolean} enabled
145 */ 181 */
146 _toggleClass(node, className, enabled) { 182 _toggleClass(node, className, enabled) {
147 var classes = this._nodeClasses(node); 183 var classes = this._nodeClasses(node);
148 classes.set(className, enabled); 184 classes.set(className, enabled);
149 } 185 }
150 186
151 /** 187 /**
152 * @param {!SDK.DOMNode} node 188 * @param {!SDK.DOMNode} node
189 * @param {!Array.<string>=} additionalClasses
190 * @param {boolean=} immediately
153 */ 191 */
154 _installNodeClasses(node) { 192 _installNodeClasses(node, additionalClasses, immediately) {
155 var classes = this._nodeClasses(node); 193 var classes = this._nodeClasses(node);
156 var activeClasses = new Set(); 194 var activeClasses = new Set();
157 for (var className of classes.keys()) { 195 for (var className of classes.keys()) {
158 if (classes.get(className)) 196 if (classes.get(className))
159 activeClasses.add(className); 197 activeClasses.add(className);
160 } 198 }
161 199
200 if (additionalClasses) {
lushnikov 2017/03/22 01:37:33 it looks like you never want to call "_installNode
201 for (className of additionalClasses)
202 activeClasses.add(className);
203 }
162 var newClasses = activeClasses.valuesArray(); 204 var newClasses = activeClasses.valuesArray();
163 newClasses.sort(); 205 newClasses.sort();
206
207 if (immediately)
208 this._updateNodeThrottler.flush();
lushnikov 2017/03/22 01:37:33 i looked closer into the flush() method and it tur
209 this._updateNodeThrottler.schedule(this._setClassValue.bind(this, node, newC lasses.join(' ')));
210 }
211
212 /**
213 * @param {!SDK.DOMNode} node
214 * @param {string} value
215 * @return {!Promise}
216 */
217 _setClassValue(node, value) {
218 var fulfill;
219 var promise = new Promise(f => fulfill = f);
220
164 this._mutatingNodes.add(node); 221 this._mutatingNodes.add(node);
165 node.setAttributeValue('class', newClasses.join(' '), onClassNameUpdated.bin d(this)); 222 node.setAttributeValue('class', value, onClassValueUpdated.bind(this));
166 223
167 /** 224 /**
168 * @this {Elements.ClassesPaneWidget} 225 * @this {Elements.ClassesPaneWidget}
169 */ 226 */
170 function onClassNameUpdated() { 227 function onClassValueUpdated() {
171 this._mutatingNodes.delete(node); 228 this._mutatingNodes.delete(node);
229 fulfill();
172 } 230 }
231
232 return promise;
173 } 233 }
174 }; 234 };
175 235
176 Elements.ClassesPaneWidget._classesSymbol = Symbol('Elements.ClassesPaneWidget._ classesSymbol'); 236 Elements.ClassesPaneWidget._classesSymbol = Symbol('Elements.ClassesPaneWidget._ classesSymbol');
177 237
178 /** 238 /**
179 * @implements {UI.ToolbarItem.Provider} 239 * @implements {UI.ToolbarItem.Provider}
180 * @unrestricted 240 * @unrestricted
181 */ 241 */
182 Elements.ClassesPaneWidget.ButtonProvider = class { 242 Elements.ClassesPaneWidget.ButtonProvider = class {
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
255 if (!this._classNamesPromise || this._selectedFrameId !== selectedNode.frame Id()) 315 if (!this._classNamesPromise || this._selectedFrameId !== selectedNode.frame Id())
256 this._classNamesPromise = this._getClassNames(selectedNode); 316 this._classNamesPromise = this._getClassNames(selectedNode);
257 317
258 return this._classNamesPromise.then(completions => { 318 return this._classNamesPromise.then(completions => {
259 if (prefix[0] === '.') 319 if (prefix[0] === '.')
260 completions = completions.map(value => '.' + value); 320 completions = completions.map(value => '.' + value);
261 return completions.filter(value => value.startsWith(prefix)).map(completio n => ({text: completion})); 321 return completions.filter(value => value.startsWith(prefix)).map(completio n => ({text: completion}));
262 }); 322 });
263 } 323 }
264 }; 324 };
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698