OLD | NEW |
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 | |
5 /** | 4 /** |
6 * @constructor | 5 * @unrestricted |
7 * @extends {WebInspector.Widget} | |
8 */ | 6 */ |
9 WebInspector.ClassesPaneWidget = function() | 7 WebInspector.ClassesPaneWidget = class extends WebInspector.Widget { |
10 { | 8 constructor() { |
11 WebInspector.Widget.call(this); | 9 super(); |
12 this.element.className = "styles-element-classes-pane"; | 10 this.element.className = 'styles-element-classes-pane'; |
13 var container = this.element.createChild("div", "title-container"); | 11 var container = this.element.createChild('div', 'title-container'); |
14 this._input = container.createChild("div", "new-class-input monospace"); | 12 this._input = container.createChild('div', 'new-class-input monospace'); |
15 this._input.setAttribute("placeholder", WebInspector.UIString("Add new class
")); | 13 this._input.setAttribute('placeholder', WebInspector.UIString('Add new class
')); |
16 this.setDefaultFocusedElement(this._input); | 14 this.setDefaultFocusedElement(this._input); |
17 this._classesContainer = this.element.createChild("div", "source-code"); | 15 this._classesContainer = this.element.createChild('div', 'source-code'); |
18 this._classesContainer.classList.add("styles-element-classes-container"); | 16 this._classesContainer.classList.add('styles-element-classes-container'); |
19 this._prompt = new WebInspector.ClassesPaneWidget.ClassNamePrompt(); | 17 this._prompt = new WebInspector.ClassesPaneWidget.ClassNamePrompt(); |
20 this._prompt.setAutocompletionTimeout(0); | 18 this._prompt.setAutocompletionTimeout(0); |
21 this._prompt.renderAsBlock(); | 19 this._prompt.renderAsBlock(); |
22 | 20 |
23 var proxyElement = this._prompt.attach(this._input); | 21 var proxyElement = this._prompt.attach(this._input); |
24 proxyElement.addEventListener("keydown", this._onKeyDown.bind(this), false); | 22 proxyElement.addEventListener('keydown', this._onKeyDown.bind(this), false); |
25 | 23 |
26 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspec
tor.DOMModel.Events.DOMMutated, this._onDOMMutated, this); | 24 WebInspector.targetManager.addModelListener( |
| 25 WebInspector.DOMModel, WebInspector.DOMModel.Events.DOMMutated, this._on
DOMMutated, this); |
27 /** @type {!Set<!WebInspector.DOMNode>} */ | 26 /** @type {!Set<!WebInspector.DOMNode>} */ |
28 this._mutatingNodes = new Set(); | 27 this._mutatingNodes = new Set(); |
29 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this._upd
ate, this); | 28 WebInspector.context.addFlavorChangeListener(WebInspector.DOMNode, this._upd
ate, this); |
| 29 } |
| 30 |
| 31 /** |
| 32 * @param {!Event} event |
| 33 */ |
| 34 _onKeyDown(event) { |
| 35 var text = event.target.textContent; |
| 36 if (isEscKey(event)) { |
| 37 event.target.textContent = ''; |
| 38 if (!text.isWhitespace()) |
| 39 event.consume(true); |
| 40 return; |
| 41 } |
| 42 |
| 43 if (!isEnterKey(event)) |
| 44 return; |
| 45 var node = WebInspector.context.flavor(WebInspector.DOMNode); |
| 46 if (!node) |
| 47 return; |
| 48 |
| 49 this._prompt.clearAutocomplete(); |
| 50 event.target.textContent = ''; |
| 51 var classNames = text.split(/[.,\s]/); |
| 52 for (var className of classNames) { |
| 53 var className = className.trim(); |
| 54 if (!className.length) |
| 55 continue; |
| 56 this._toggleClass(node, className, true); |
| 57 } |
| 58 this._installNodeClasses(node); |
| 59 this._update(); |
| 60 event.consume(true); |
| 61 } |
| 62 |
| 63 /** |
| 64 * @param {!WebInspector.Event} event |
| 65 */ |
| 66 _onDOMMutated(event) { |
| 67 var node = /** @type {!WebInspector.DOMNode} */ (event.data); |
| 68 if (this._mutatingNodes.has(node)) |
| 69 return; |
| 70 delete node[WebInspector.ClassesPaneWidget._classesSymbol]; |
| 71 this._update(); |
| 72 } |
| 73 |
| 74 /** |
| 75 * @override |
| 76 */ |
| 77 wasShown() { |
| 78 this._update(); |
| 79 } |
| 80 |
| 81 _update() { |
| 82 if (!this.isShowing()) |
| 83 return; |
| 84 |
| 85 var node = WebInspector.context.flavor(WebInspector.DOMNode); |
| 86 if (node) |
| 87 node = node.enclosingElementOrSelf(); |
| 88 |
| 89 this._classesContainer.removeChildren(); |
| 90 this._input.disabled = !node; |
| 91 |
| 92 if (!node) |
| 93 return; |
| 94 |
| 95 var classes = this._nodeClasses(node); |
| 96 var keys = classes.keysArray(); |
| 97 keys.sort(String.caseInsensetiveComparator); |
| 98 for (var i = 0; i < keys.length; ++i) { |
| 99 var className = keys[i]; |
| 100 var label = createCheckboxLabel(className, classes.get(className)); |
| 101 label.visualizeFocus = true; |
| 102 label.classList.add('monospace'); |
| 103 label.checkboxElement.addEventListener('click', this._onClick.bind(this, c
lassName), false); |
| 104 this._classesContainer.appendChild(label); |
| 105 } |
| 106 } |
| 107 |
| 108 /** |
| 109 * @param {string} className |
| 110 * @param {!Event} event |
| 111 */ |
| 112 _onClick(className, event) { |
| 113 var node = WebInspector.context.flavor(WebInspector.DOMNode); |
| 114 if (!node) |
| 115 return; |
| 116 var enabled = event.target.checked; |
| 117 this._toggleClass(node, className, enabled); |
| 118 this._installNodeClasses(node); |
| 119 } |
| 120 |
| 121 /** |
| 122 * @param {!WebInspector.DOMNode} node |
| 123 * @return {!Map<string, boolean>} |
| 124 */ |
| 125 _nodeClasses(node) { |
| 126 var result = node[WebInspector.ClassesPaneWidget._classesSymbol]; |
| 127 if (!result) { |
| 128 var classAttribute = node.getAttribute('class') || ''; |
| 129 var classes = classAttribute.split(/\s/); |
| 130 result = new Map(); |
| 131 for (var i = 0; i < classes.length; ++i) { |
| 132 var className = classes[i].trim(); |
| 133 if (!className.length) |
| 134 continue; |
| 135 result.set(className, true); |
| 136 } |
| 137 node[WebInspector.ClassesPaneWidget._classesSymbol] = result; |
| 138 } |
| 139 return result; |
| 140 } |
| 141 |
| 142 /** |
| 143 * @param {!WebInspector.DOMNode} node |
| 144 * @param {string} className |
| 145 * @param {boolean} enabled |
| 146 */ |
| 147 _toggleClass(node, className, enabled) { |
| 148 var classes = this._nodeClasses(node); |
| 149 classes.set(className, enabled); |
| 150 } |
| 151 |
| 152 /** |
| 153 * @param {!WebInspector.DOMNode} node |
| 154 */ |
| 155 _installNodeClasses(node) { |
| 156 var classes = this._nodeClasses(node); |
| 157 var activeClasses = new Set(); |
| 158 for (var className of classes.keys()) { |
| 159 if (classes.get(className)) |
| 160 activeClasses.add(className); |
| 161 } |
| 162 |
| 163 var newClasses = activeClasses.valuesArray(); |
| 164 newClasses.sort(); |
| 165 this._mutatingNodes.add(node); |
| 166 node.setAttributeValue('class', newClasses.join(' '), onClassNameUpdated.bin
d(this)); |
| 167 |
| 168 /** |
| 169 * @this {WebInspector.ClassesPaneWidget} |
| 170 */ |
| 171 function onClassNameUpdated() { |
| 172 this._mutatingNodes.delete(node); |
| 173 } |
| 174 } |
30 }; | 175 }; |
31 | 176 |
32 WebInspector.ClassesPaneWidget._classesSymbol = Symbol("WebInspector.ClassesPane
Widget._classesSymbol"); | 177 WebInspector.ClassesPaneWidget._classesSymbol = Symbol('WebInspector.ClassesPane
Widget._classesSymbol'); |
33 | 178 |
34 WebInspector.ClassesPaneWidget.prototype = { | 179 /** |
35 /** | 180 * @implements {WebInspector.ToolbarItem.Provider} |
36 * @param {!Event} event | 181 * @unrestricted |
37 */ | 182 */ |
38 _onKeyDown: function(event) | 183 WebInspector.ClassesPaneWidget.ButtonProvider = class { |
39 { | 184 constructor() { |
40 var text = event.target.textContent; | 185 this._button = new WebInspector.ToolbarToggle(WebInspector.UIString('Element
Classes'), ''); |
41 if (isEscKey(event)) { | 186 this._button.setText('.cls'); |
42 event.target.textContent = ""; | 187 this._button.element.classList.add('monospace'); |
43 if (!text.isWhitespace()) | 188 this._button.addEventListener('click', this._clicked, this); |
44 event.consume(true); | 189 this._view = new WebInspector.ClassesPaneWidget(); |
45 return; | 190 } |
46 } | 191 |
47 | 192 _clicked() { |
48 if (!isEnterKey(event)) | 193 WebInspector.ElementsPanel.instance().showToolbarPane(!this._view.isShowing(
) ? this._view : null, this._button); |
49 return; | 194 } |
50 var node = WebInspector.context.flavor(WebInspector.DOMNode); | 195 |
51 if (!node) | 196 /** |
52 return; | 197 * @override |
53 | 198 * @return {!WebInspector.ToolbarItem} |
54 this._prompt.clearAutocomplete(); | 199 */ |
55 event.target.textContent = ""; | 200 item() { |
56 var classNames = text.split(/[.,\s]/); | 201 return this._button; |
57 for (var className of classNames) { | 202 } |
58 var className = className.trim(); | |
59 if (!className.length) | |
60 continue; | |
61 this._toggleClass(node, className, true); | |
62 } | |
63 this._installNodeClasses(node); | |
64 this._update(); | |
65 event.consume(true); | |
66 }, | |
67 | |
68 /** | |
69 * @param {!WebInspector.Event} event | |
70 */ | |
71 _onDOMMutated: function(event) | |
72 { | |
73 var node = /** @type {!WebInspector.DOMNode} */(event.data); | |
74 if (this._mutatingNodes.has(node)) | |
75 return; | |
76 delete node[WebInspector.ClassesPaneWidget._classesSymbol]; | |
77 this._update(); | |
78 }, | |
79 | |
80 /** | |
81 * @override | |
82 */ | |
83 wasShown: function() | |
84 { | |
85 this._update(); | |
86 }, | |
87 | |
88 _update: function() | |
89 { | |
90 if (!this.isShowing()) | |
91 return; | |
92 | |
93 var node = WebInspector.context.flavor(WebInspector.DOMNode); | |
94 if (node) | |
95 node = node.enclosingElementOrSelf(); | |
96 | |
97 this._classesContainer.removeChildren(); | |
98 this._input.disabled = !node; | |
99 | |
100 if (!node) | |
101 return; | |
102 | |
103 var classes = this._nodeClasses(node); | |
104 var keys = classes.keysArray(); | |
105 keys.sort(String.caseInsensetiveComparator); | |
106 for (var i = 0; i < keys.length; ++i) { | |
107 var className = keys[i]; | |
108 var label = createCheckboxLabel(className, classes.get(className)); | |
109 label.visualizeFocus = true; | |
110 label.classList.add("monospace"); | |
111 label.checkboxElement.addEventListener("click", this._onClick.bind(t
his, className), false); | |
112 this._classesContainer.appendChild(label); | |
113 } | |
114 }, | |
115 | |
116 /** | |
117 * @param {string} className | |
118 * @param {!Event} event | |
119 */ | |
120 _onClick: function(className, event) | |
121 { | |
122 var node = WebInspector.context.flavor(WebInspector.DOMNode); | |
123 if (!node) | |
124 return; | |
125 var enabled = event.target.checked; | |
126 this._toggleClass(node, className, enabled); | |
127 this._installNodeClasses(node); | |
128 }, | |
129 | |
130 /** | |
131 * @param {!WebInspector.DOMNode} node | |
132 * @return {!Map<string, boolean>} | |
133 */ | |
134 _nodeClasses: function(node) | |
135 { | |
136 var result = node[WebInspector.ClassesPaneWidget._classesSymbol]; | |
137 if (!result) { | |
138 var classAttribute = node.getAttribute("class") || ""; | |
139 var classes = classAttribute.split(/\s/); | |
140 result = new Map(); | |
141 for (var i = 0; i < classes.length; ++i) { | |
142 var className = classes[i].trim(); | |
143 if (!className.length) | |
144 continue; | |
145 result.set(className, true); | |
146 } | |
147 node[WebInspector.ClassesPaneWidget._classesSymbol] = result; | |
148 } | |
149 return result; | |
150 }, | |
151 | |
152 /** | |
153 * @param {!WebInspector.DOMNode} node | |
154 * @param {string} className | |
155 * @param {boolean} enabled | |
156 */ | |
157 _toggleClass: function(node, className, enabled) | |
158 { | |
159 var classes = this._nodeClasses(node); | |
160 classes.set(className, enabled); | |
161 }, | |
162 | |
163 /** | |
164 * @param {!WebInspector.DOMNode} node | |
165 */ | |
166 _installNodeClasses: function(node) | |
167 { | |
168 var classes = this._nodeClasses(node); | |
169 var activeClasses = new Set(); | |
170 for (var className of classes.keys()) { | |
171 if (classes.get(className)) | |
172 activeClasses.add(className); | |
173 } | |
174 | |
175 var newClasses = activeClasses.valuesArray(); | |
176 newClasses.sort(); | |
177 this._mutatingNodes.add(node); | |
178 node.setAttributeValue("class", newClasses.join(" "), onClassNameUpdated
.bind(this)); | |
179 | |
180 /** | |
181 * @this {WebInspector.ClassesPaneWidget} | |
182 */ | |
183 function onClassNameUpdated() | |
184 { | |
185 this._mutatingNodes.delete(node); | |
186 } | |
187 }, | |
188 | |
189 __proto__: WebInspector.Widget.prototype | |
190 }; | 203 }; |
191 | 204 |
192 /** | 205 /** |
193 * @constructor | 206 * @unrestricted |
194 * @implements {WebInspector.ToolbarItem.Provider} | |
195 */ | 207 */ |
196 WebInspector.ClassesPaneWidget.ButtonProvider = function() | 208 WebInspector.ClassesPaneWidget.ClassNamePrompt = class extends WebInspector.Text
Prompt { |
197 { | 209 constructor() { |
198 this._button = new WebInspector.ToolbarToggle(WebInspector.UIString("Element
Classes"), ""); | 210 super(); |
199 this._button.setText(".cls"); | 211 this.initialize(this._buildClassNameCompletions.bind(this), ' '); |
200 this._button.element.classList.add("monospace"); | |
201 this._button.addEventListener("click", this._clicked, this); | |
202 this._view = new WebInspector.ClassesPaneWidget(); | |
203 }; | |
204 | |
205 WebInspector.ClassesPaneWidget.ButtonProvider.prototype = { | |
206 _clicked: function() | |
207 { | |
208 WebInspector.ElementsPanel.instance().showToolbarPane(!this._view.isShow
ing() ? this._view : null, this._button); | |
209 }, | |
210 | |
211 /** | |
212 * @override | |
213 * @return {!WebInspector.ToolbarItem} | |
214 */ | |
215 item: function() | |
216 { | |
217 return this._button; | |
218 } | |
219 }; | |
220 | |
221 /** | |
222 * @constructor | |
223 * @extends {WebInspector.TextPrompt} | |
224 */ | |
225 WebInspector.ClassesPaneWidget.ClassNamePrompt = function() | |
226 { | |
227 WebInspector.TextPrompt.call(this); | |
228 this.initialize(this._buildClassNameCompletions.bind(this), " "); | |
229 this.setSuggestBoxEnabled(true); | 212 this.setSuggestBoxEnabled(true); |
230 this.disableDefaultSuggestionForEmptyInput(); | 213 this.disableDefaultSuggestionForEmptyInput(); |
231 this._selectedFrameId = ""; | 214 this._selectedFrameId = ''; |
232 this._classNamesPromise = null; | 215 this._classNamesPromise = null; |
| 216 } |
| 217 |
| 218 /** |
| 219 * @param {!WebInspector.DOMNode} selectedNode |
| 220 * @return {!Promise.<!Array.<string>>} |
| 221 */ |
| 222 _getClassNames(selectedNode) { |
| 223 var promises = []; |
| 224 var completions = new Set(); |
| 225 this._selectedFrameId = selectedNode.frameId(); |
| 226 |
| 227 var cssModel = WebInspector.CSSModel.fromTarget(selectedNode.target()); |
| 228 var allStyleSheets = cssModel.allStyleSheets(); |
| 229 for (var stylesheet of allStyleSheets) { |
| 230 if (stylesheet.frameId !== this._selectedFrameId) |
| 231 continue; |
| 232 var cssPromise = cssModel.classNamesPromise(stylesheet.id).then(classes =>
completions.addAll(classes)); |
| 233 promises.push(cssPromise); |
| 234 } |
| 235 |
| 236 var domPromise = selectedNode.domModel() |
| 237 .classNamesPromise(selectedNode.ownerDocument.id) |
| 238 .then(classes => completions.addAll(classes)); |
| 239 promises.push(domPromise); |
| 240 return Promise.all(promises).then(() => completions.valuesArray()); |
| 241 } |
| 242 |
| 243 /** |
| 244 * @param {!Element} proxyElement |
| 245 * @param {!Range} wordRange |
| 246 * @param {boolean} force |
| 247 * @param {function(!Array.<string>, number=)} completionsReadyCallback |
| 248 */ |
| 249 _buildClassNameCompletions(proxyElement, wordRange, force, completionsReadyCal
lback) { |
| 250 var prefix = wordRange.toString(); |
| 251 if (!prefix || force) |
| 252 this._classNamesPromise = null; |
| 253 |
| 254 var selectedNode = WebInspector.context.flavor(WebInspector.DOMNode); |
| 255 if (!selectedNode || (!prefix && !force && !proxyElement.textContent.length)
) { |
| 256 completionsReadyCallback([]); |
| 257 return; |
| 258 } |
| 259 |
| 260 if (!this._classNamesPromise || this._selectedFrameId !== selectedNode.frame
Id()) |
| 261 this._classNamesPromise = this._getClassNames(selectedNode); |
| 262 |
| 263 this._classNamesPromise.then(completions => { |
| 264 if (prefix[0] === '.') |
| 265 completions = completions.map(value => '.' + value); |
| 266 var results = completions.filter(value => value.startsWith(prefix)); |
| 267 completionsReadyCallback(results, 0); |
| 268 }); |
| 269 } |
233 }; | 270 }; |
234 | |
235 WebInspector.ClassesPaneWidget.ClassNamePrompt.prototype = { | |
236 /** | |
237 * @param {!WebInspector.DOMNode} selectedNode | |
238 * @return {!Promise.<!Array.<string>>} | |
239 */ | |
240 _getClassNames: function(selectedNode) | |
241 { | |
242 var promises = []; | |
243 var completions = new Set(); | |
244 this._selectedFrameId = selectedNode.frameId(); | |
245 | |
246 var cssModel = WebInspector.CSSModel.fromTarget(selectedNode.target()); | |
247 var allStyleSheets = cssModel.allStyleSheets(); | |
248 for (var stylesheet of allStyleSheets) { | |
249 if (stylesheet.frameId !== this._selectedFrameId) | |
250 continue; | |
251 var cssPromise = cssModel.classNamesPromise(stylesheet.id).then(clas
ses => completions.addAll(classes)); | |
252 promises.push(cssPromise); | |
253 } | |
254 | |
255 var domPromise = selectedNode.domModel().classNamesPromise(selectedNode.
ownerDocument.id).then(classes => completions.addAll(classes)); | |
256 promises.push(domPromise); | |
257 return Promise.all(promises).then(() => completions.valuesArray()); | |
258 }, | |
259 | |
260 /** | |
261 * @param {!Element} proxyElement | |
262 * @param {!Range} wordRange | |
263 * @param {boolean} force | |
264 * @param {function(!Array.<string>, number=)} completionsReadyCallback | |
265 */ | |
266 _buildClassNameCompletions: function(proxyElement, wordRange, force, complet
ionsReadyCallback) | |
267 { | |
268 var prefix = wordRange.toString(); | |
269 if (!prefix || force) | |
270 this._classNamesPromise = null; | |
271 | |
272 var selectedNode = WebInspector.context.flavor(WebInspector.DOMNode); | |
273 if (!selectedNode || (!prefix && !force && !proxyElement.textContent.len
gth)) { | |
274 completionsReadyCallback([]); | |
275 return; | |
276 } | |
277 | |
278 if (!this._classNamesPromise || this._selectedFrameId !== selectedNode.f
rameId()) | |
279 this._classNamesPromise = this._getClassNames(selectedNode); | |
280 | |
281 this._classNamesPromise.then(completions => { | |
282 if (prefix[0] === ".") | |
283 completions = completions.map(value => "." + value); | |
284 var results = completions.filter(value => value.startsWith(prefix)); | |
285 completionsReadyCallback(results, 0); | |
286 }); | |
287 }, | |
288 | |
289 __proto__: WebInspector.TextPrompt.prototype | |
290 }; | |
OLD | NEW |