Skip to content

Commit

Permalink
DevTools: ClassesPaneWidget - Add ability to quickly preview autocomp…
Browse files Browse the repository at this point in the history
…leted CSS classes.

In action - https://i.imgur.com/yucn4TI.gif 📽

BUG=683623

Review-Url: https://codereview.chromium.org/2646283002
Cr-Commit-Position: refs/heads/master@{#463104}
  • Loading branch information
kdzwinel authored and Commit bot committed Apr 8, 2017
1 parent 6a2cc09 commit 56d5720
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,69 @@ Elements.ClassesPaneWidget = class extends UI.Widget {

var proxyElement = this._prompt.attach(this._input);
this._prompt.setPlaceholder(Common.UIString('Add new class'));
this._prompt.addEventListener(UI.TextPrompt.Events.TextChanged, this._onTextChanged, this);
proxyElement.addEventListener('keydown', this._onKeyDown.bind(this), false);

SDK.targetManager.addModelListener(SDK.DOMModel, SDK.DOMModel.Events.DOMMutated, this._onDOMMutated, this);
/** @type {!Set<!SDK.DOMNode>} */
this._mutatingNodes = new Set();
UI.context.addFlavorChangeListener(SDK.DOMNode, this._update, this);
/** @type {!Map<!SDK.DOMNode, string>} */
this._pendingNodeClasses = new Map();
this._updateNodeThrottler = new Common.Throttler(0);
/** @type {?SDK.DOMNode} */
this._previousTarget = null;
UI.context.addFlavorChangeListener(SDK.DOMNode, this._onSelectedNodeChanged, this);
}

/**
* @param {string} text
* @return {!Array.<string>}
*/
_splitTextIntoClasses(text) {
return text.split(/[.,\s]/)
.map(className => className.trim())
.filter(className => className.length);
}

/**
* @param {!Event} event
*/
_onKeyDown(event) {
if (!isEnterKey(event) && !isEscKey(event))
return;

if (isEnterKey(event)) {
event.consume(true);
if (this._prompt.acceptAutoComplete())
return;
}

var text = event.target.textContent;
if (isEscKey(event)) {
event.target.textContent = '';
if (!text.isWhitespace())
event.consume(true);
return;
text = '';
}

if (!isEnterKey(event))
return;
if (this._prompt.acceptAutoComplete()) {
event.consume(true);
return;
}
this._prompt.clearAutocomplete();
event.target.textContent = '';

var node = UI.context.flavor(SDK.DOMNode);
if (!node)
return;

this._prompt.clearAutocomplete();
event.target.textContent = '';
var classNames = text.split(/[.,\s]/);
for (var className of classNames) {
var className = className.trim();
if (!className.length)
continue;
var classNames = this._splitTextIntoClasses(text);
for (var className of classNames)
this._toggleClass(node, className, true);
}
this._installNodeClasses(node);
this._update();
event.consume(true);
}

_onTextChanged() {
var node = UI.context.flavor(SDK.DOMNode);
if (!node)
return;
this._installNodeClasses(node);
}

/**
Expand All @@ -74,6 +95,18 @@ Elements.ClassesPaneWidget = class extends UI.Widget {
this._update();
}

/**
* @param {!Common.Event} event
*/
_onSelectedNodeChanged(event) {
if (this._previousTarget && this._prompt.text()) {
this._input.textContent = '';
this._installNodeClasses(this._previousTarget);
}
this._previousTarget = /** @type {?SDK.DOMNode} */ (event.data);
this._update();
}

/**
* @override
*/
Expand Down Expand Up @@ -162,15 +195,35 @@ Elements.ClassesPaneWidget = class extends UI.Widget {
activeClasses.add(className);
}

var additionalClasses = this._splitTextIntoClasses(this._prompt.textWithCurrentSuggestion());
for (className of additionalClasses)
activeClasses.add(className);

var newClasses = activeClasses.valuesArray();
newClasses.sort();
this._mutatingNodes.add(node);
node.setAttributeValue('class', newClasses.join(' '), onClassNameUpdated.bind(this));

this._pendingNodeClasses.set(node, newClasses.join(' '));
this._updateNodeThrottler.schedule(this._flushPendingClasses.bind(this));
}

/**
* @return {!Promise}
*/
_flushPendingClasses() {
var promises = [];
for (var node of this._pendingNodeClasses.keys()) {
this._mutatingNodes.add(node);
var promise = node.setAttributeValuePromise('class', this._pendingNodeClasses.get(node)).then(onClassValueUpdated.bind(this, node));
promises.push(promise);
}
this._pendingNodeClasses.clear();
return Promise.all(promises);

/**
* @param {!SDK.DOMNode} node
* @this {Elements.ClassesPaneWidget}
*/
function onClassNameUpdated() {
function onClassValueUpdated(node) {
this._mutatingNodes.delete(node);
}
}
Expand Down
9 changes: 9 additions & 0 deletions third_party/WebKit/Source/devtools/front_end/sdk/DOMModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,15 @@ SDK.DOMNode = class {
this._agent.setAttributeValue(this.id, name, value, this._domModel._markRevision(this, callback));
}

/**
* @param {string} name
* @param {string} value
* @return {!Promise<?Protocol.Error>}
*/
setAttributeValuePromise(name, value) {
return new Promise(fulfill => this.setAttributeValue(name, value, fulfill));
}

/**
* @return {!Array<!SDK.DOMNode.Attribute>}
*/
Expand Down

0 comments on commit 56d5720

Please sign in to comment.