зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1055181 - CSS Filter Tooltip; r=pbrosset
Adds a new devtools tooltip type in the inspector used to edit css filters. The widget displayed in the tooltip allows to add, edit, remove, and re-order filters. Changes made inside the tooltip are applied to the filter property in the rule-view. --HG-- extra : rebase_source : a38ce7108bfa4e035892c3c3d89098dc1b2b0052 extra : histedit_source : ff309c03f920359df61e139731f8f57cf55a1545
This commit is contained in:
Родитель
4aa03199c3
Коммит
43193dfe3b
|
@ -141,6 +141,8 @@ browser.jar:
|
|||
content/browser/devtools/spectrum.css (shared/widgets/spectrum.css)
|
||||
content/browser/devtools/cubic-bezier-frame.xhtml (shared/widgets/cubic-bezier-frame.xhtml)
|
||||
content/browser/devtools/cubic-bezier.css (shared/widgets/cubic-bezier.css)
|
||||
content/browser/devtools/filter-frame.xhtml (shared/widgets/filter-frame.xhtml)
|
||||
content/browser/devtools/filter-widget.css (shared/widgets/filter-widget.css)
|
||||
content/browser/devtools/eyedropper.xul (eyedropper/eyedropper.xul)
|
||||
content/browser/devtools/eyedropper/crosshairs.css (eyedropper/crosshairs.css)
|
||||
content/browser/devtools/eyedropper/nocursor.css (eyedropper/nocursor.css)
|
||||
|
|
|
@ -64,6 +64,7 @@ EXTRA_JS_MODULES.devtools.shared.widgets += [
|
|||
'widgets/CubicBezierPresets.js',
|
||||
'widgets/CubicBezierWidget.js',
|
||||
'widgets/FastListWidget.js',
|
||||
'widgets/FilterWidget.js',
|
||||
'widgets/FlameGraph.js',
|
||||
'widgets/Spectrum.js',
|
||||
'widgets/TableWidget.js',
|
||||
|
|
|
@ -0,0 +1,720 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This is a CSS Filter Editor widget used
|
||||
* for Rule View's filter swatches
|
||||
*/
|
||||
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const { Cu } = require("chrome");
|
||||
const { ViewHelpers } = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
|
||||
const STRINGS_URI = "chrome://browser/locale/devtools/filterwidget.properties";
|
||||
const L10N = new ViewHelpers.L10N(STRINGS_URI);
|
||||
|
||||
const DEFAULT_FILTER_TYPE = "length";
|
||||
const UNIT_MAPPING = {
|
||||
percentage: "%",
|
||||
length: "px",
|
||||
angle: "deg",
|
||||
string: ""
|
||||
};
|
||||
|
||||
const FAST_VALUE_MULTIPLIER = 10;
|
||||
const SLOW_VALUE_MULTIPLIER = 0.1;
|
||||
const DEFAULT_VALUE_MULTIPLIER = 1;
|
||||
|
||||
const LIST_PADDING = 7;
|
||||
const LIST_ITEM_HEIGHT = 32;
|
||||
|
||||
const filterList = [
|
||||
{
|
||||
"name": "blur",
|
||||
"range": [0, Infinity],
|
||||
"type": "length"
|
||||
},
|
||||
{
|
||||
"name": "brightness",
|
||||
"range": [0, Infinity],
|
||||
"type": "percentage"
|
||||
},
|
||||
{
|
||||
"name": "contrast",
|
||||
"range": [0, Infinity],
|
||||
"type": "percentage"
|
||||
},
|
||||
{
|
||||
"name": "drop-shadow",
|
||||
"placeholder": L10N.getStr("dropShadowPlaceholder"),
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "grayscale",
|
||||
"range": [0, 100],
|
||||
"type": "percentage"
|
||||
},
|
||||
{
|
||||
"name": "hue-rotate",
|
||||
"range": [0, 360],
|
||||
"type": "angle"
|
||||
},
|
||||
{
|
||||
"name": "invert",
|
||||
"range": [0, 100],
|
||||
"type": "percentage"
|
||||
},
|
||||
{
|
||||
"name": "opacity",
|
||||
"range": [0, 100],
|
||||
"type": "percentage"
|
||||
},
|
||||
{
|
||||
"name": "saturate",
|
||||
"range": [0, Infinity],
|
||||
"type": "percentage"
|
||||
},
|
||||
{
|
||||
"name": "sepia",
|
||||
"range": [0, 100],
|
||||
"type": "percentage"
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"placeholder": "example.svg#c1",
|
||||
"type": "string"
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* A CSS Filter editor widget used to add/remove/modify
|
||||
* filters.
|
||||
*
|
||||
* Normally, it takes a CSS filter value as input, parses it
|
||||
* and creates the required elements / bindings.
|
||||
*
|
||||
* You can, however, use add/remove/update methods manually.
|
||||
* See each method's comments for more details
|
||||
*
|
||||
* @param {nsIDOMNode} el
|
||||
* The widget container.
|
||||
* @param {String} value
|
||||
* CSS filter value
|
||||
*/
|
||||
function CSSFilterEditorWidget(el, value = "") {
|
||||
this.doc = el.ownerDocument;
|
||||
this.win = this.doc.ownerGlobal;
|
||||
this.el = el;
|
||||
|
||||
this._addButtonClick = this._addButtonClick.bind(this);
|
||||
this._removeButtonClick = this._removeButtonClick.bind(this);
|
||||
this._mouseMove = this._mouseMove.bind(this);
|
||||
this._mouseUp = this._mouseUp.bind(this);
|
||||
this._mouseDown = this._mouseDown.bind(this);
|
||||
this._input = this._input.bind(this);
|
||||
|
||||
this._initMarkup();
|
||||
this._buildFilterItemMarkup();
|
||||
this._addEventListeners();
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.filters = [];
|
||||
this.setCssValue(value);
|
||||
}
|
||||
|
||||
exports.CSSFilterEditorWidget = CSSFilterEditorWidget;
|
||||
|
||||
CSSFilterEditorWidget.prototype = {
|
||||
_initMarkup: function() {
|
||||
const list = this.el.querySelector(".filters");
|
||||
this.el.appendChild(list);
|
||||
this.el.insertBefore(list, this.el.firstChild);
|
||||
this.container = this.el;
|
||||
this.list = list;
|
||||
|
||||
this.filterSelect = this.el.querySelector("select");
|
||||
this._populateFilterSelect();
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates <option> elements for each filter definition
|
||||
* in filterList
|
||||
*/
|
||||
_populateFilterSelect: function() {
|
||||
let select = this.filterSelect;
|
||||
filterList.forEach(filter => {
|
||||
let option = this.doc.createElement("option");
|
||||
option.innerHTML = option.value = filter.name;
|
||||
select.appendChild(option);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a template for filter elements which is cloned and used in render
|
||||
*/
|
||||
_buildFilterItemMarkup: function() {
|
||||
let base = this.doc.createElement("div");
|
||||
base.className = "filter";
|
||||
|
||||
let name = this.doc.createElement("div");
|
||||
name.className = "filter-name";
|
||||
|
||||
let value = this.doc.createElement("div");
|
||||
value.className = "filter-value";
|
||||
|
||||
let drag = this.doc.createElement("i");
|
||||
drag.title = L10N.getStr("dragHandleTooltipText");
|
||||
|
||||
let label = this.doc.createElement("label");
|
||||
|
||||
name.appendChild(drag);
|
||||
name.appendChild(label);
|
||||
|
||||
let unitPreview = this.doc.createElement("span");
|
||||
let input = this.doc.createElement("input");
|
||||
input.classList.add("devtools-textinput");
|
||||
|
||||
value.appendChild(input);
|
||||
value.appendChild(unitPreview);
|
||||
|
||||
let removeButton = this.doc.createElement("button");
|
||||
removeButton.className = "remove-button";
|
||||
value.appendChild(removeButton);
|
||||
|
||||
base.appendChild(name);
|
||||
base.appendChild(value);
|
||||
|
||||
this._filterItemMarkup = base;
|
||||
},
|
||||
|
||||
_addEventListeners: function() {
|
||||
this.addButton = this.el.querySelector("#add-filter");
|
||||
this.addButton.addEventListener("click", this._addButtonClick);
|
||||
this.list.addEventListener("click", this._removeButtonClick);
|
||||
this.list.addEventListener("mousedown", this._mouseDown);
|
||||
|
||||
// These events are event delegators for
|
||||
// drag-drop re-ordering and label-dragging
|
||||
this.win.addEventListener("mousemove", this._mouseMove);
|
||||
this.win.addEventListener("mouseup", this._mouseUp);
|
||||
|
||||
// Used to workaround float-precision problems
|
||||
this.list.addEventListener("input", this._input);
|
||||
},
|
||||
|
||||
_input: function(e) {
|
||||
let filterEl = e.target.closest(".filter"),
|
||||
index = [...this.list.children].indexOf(filterEl),
|
||||
filter = this.filters[index],
|
||||
def = this._definition(filter.name);
|
||||
|
||||
if (def.type !== "string") {
|
||||
e.target.value = fixFloat(e.target.value);
|
||||
}
|
||||
this.updateValueAt(index, e.target.value);
|
||||
},
|
||||
|
||||
_mouseDown: function(e) {
|
||||
let filterEl = e.target.closest(".filter");
|
||||
|
||||
// re-ordering drag handle
|
||||
if (e.target.tagName.toLowerCase() === "i") {
|
||||
this.isReorderingFilter = true;
|
||||
filterEl.startingY = e.pageY;
|
||||
filterEl.classList.add("dragging");
|
||||
|
||||
this.container.classList.add("dragging");
|
||||
// label-dragging
|
||||
} else if (e.target.classList.contains("devtools-draglabel")) {
|
||||
let label = e.target,
|
||||
input = filterEl.querySelector("input"),
|
||||
index = [...this.list.children].indexOf(filterEl);
|
||||
|
||||
this._dragging = {
|
||||
index, label, input,
|
||||
startX: e.pageX
|
||||
};
|
||||
|
||||
this.isDraggingLabel = true;
|
||||
}
|
||||
},
|
||||
|
||||
_addButtonClick: function() {
|
||||
const select = this.filterSelect;
|
||||
if (!select.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = select.value;
|
||||
const def = this._definition(key);
|
||||
// UNIT_MAPPING[string] is an empty string (falsy), so
|
||||
// using || doesn't work here
|
||||
const unitLabel = typeof UNIT_MAPPING[def.type] === "undefined" ?
|
||||
UNIT_MAPPING[DEFAULT_FILTER_TYPE] :
|
||||
UNIT_MAPPING[def.type];
|
||||
|
||||
// string-type filters have no default value but a placeholder instead
|
||||
if (!unitLabel) {
|
||||
this.add(key);
|
||||
} else {
|
||||
this.add(key, def.range[0] + unitLabel);
|
||||
}
|
||||
|
||||
this.render();
|
||||
},
|
||||
|
||||
_removeButtonClick: function(e) {
|
||||
const isRemoveButton = e.target.classList.contains("remove-button");
|
||||
if (!isRemoveButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
let filterEl = e.target.closest(".filter");
|
||||
let index = [...this.list.children].indexOf(filterEl);
|
||||
this.removeAt(index);
|
||||
},
|
||||
|
||||
_mouseMove: function(e) {
|
||||
if (this.isReorderingFilter) {
|
||||
this._dragFilterElement(e);
|
||||
} else if (this.isDraggingLabel) {
|
||||
this._dragLabel(e);
|
||||
}
|
||||
},
|
||||
|
||||
_dragFilterElement: function(e) {
|
||||
const rect = this.list.getBoundingClientRect(),
|
||||
top = e.pageY - LIST_PADDING,
|
||||
bottom = e.pageY + LIST_PADDING;
|
||||
// don't allow dragging over top/bottom of list
|
||||
if (top < rect.top || bottom > rect.bottom) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filterEl = this.list.querySelector(".dragging");
|
||||
|
||||
const delta = e.pageY - filterEl.startingY;
|
||||
filterEl.style.top = delta + "px";
|
||||
|
||||
// change is the number of _steps_ taken from initial position
|
||||
// i.e. how many elements we have passed
|
||||
let change = delta / LIST_ITEM_HEIGHT;
|
||||
change = change > 0 ? Math.floor(change) :
|
||||
change < 0 ? Math.ceil(change) : change;
|
||||
|
||||
const children = this.list.children;
|
||||
const index = [...children].indexOf(filterEl);
|
||||
const destination = index + change;
|
||||
|
||||
// If we're moving out, or there's no change at all, stop and return
|
||||
if (destination >= children.length || destination < 0 || change === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-order filter objects
|
||||
swapArrayIndices(this.filters, index, destination);
|
||||
|
||||
// Re-order the dragging element in markup
|
||||
const target = change > 0 ? children[destination + 1]
|
||||
: children[destination];
|
||||
if (target) {
|
||||
this.list.insertBefore(filterEl, target);
|
||||
} else {
|
||||
this.list.appendChild(filterEl);
|
||||
}
|
||||
|
||||
filterEl.removeAttribute("style");
|
||||
|
||||
const currentPosition = change * LIST_ITEM_HEIGHT;
|
||||
filterEl.startingY = e.pageY + currentPosition - delta;
|
||||
},
|
||||
|
||||
_dragLabel: function(e) {
|
||||
let dragging = this._dragging;
|
||||
|
||||
let input = dragging.input;
|
||||
|
||||
let multiplier = DEFAULT_VALUE_MULTIPLIER;
|
||||
|
||||
if (e.altKey) {
|
||||
multiplier = SLOW_VALUE_MULTIPLIER;
|
||||
} else if (e.shiftKey) {
|
||||
multiplier = FAST_VALUE_MULTIPLIER;
|
||||
}
|
||||
|
||||
dragging.lastX = e.pageX;
|
||||
const delta = e.pageX - dragging.startX;
|
||||
const startValue = parseFloat(input.value);
|
||||
let value = startValue + delta * multiplier;
|
||||
|
||||
const filter = this.filters[dragging.index];
|
||||
const [min, max] = this._definition(filter.name).range;
|
||||
value = value < min ? min :
|
||||
value > max ? max : value;
|
||||
|
||||
input.value = fixFloat(value);
|
||||
|
||||
dragging.startX = e.pageX;
|
||||
|
||||
this.updateValueAt(dragging.index, value);
|
||||
},
|
||||
|
||||
_mouseUp: function() {
|
||||
// Label-dragging is disabled on mouseup
|
||||
this._dragging = null;
|
||||
this.isDraggingLabel = false;
|
||||
|
||||
// Filter drag/drop needs more cleaning
|
||||
if (!this.isReorderingFilter) {
|
||||
return;
|
||||
}
|
||||
let filterEl = this.list.querySelector(".dragging");
|
||||
|
||||
this.isReorderingFilter = false;
|
||||
filterEl.classList.remove("dragging");
|
||||
this.container.classList.remove("dragging");
|
||||
filterEl.removeAttribute("style");
|
||||
|
||||
this.emit("updated", this.getCssValue());
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the list and renders filters, binding required events.
|
||||
* There are some delegated events bound in _addEventListeners method
|
||||
*/
|
||||
render: function() {
|
||||
if (!this.filters.length) {
|
||||
this.list.innerHTML = `<p> ${L10N.getStr("emptyFilterList")} <br />
|
||||
${L10N.getStr("addUsingList")} </p>`;
|
||||
this.emit("render");
|
||||
return;
|
||||
}
|
||||
|
||||
this.list.innerHTML = "";
|
||||
|
||||
let base = this._filterItemMarkup;
|
||||
|
||||
for (let filter of this.filters) {
|
||||
const def = this._definition(filter.name);
|
||||
|
||||
let el = base.cloneNode(true);
|
||||
|
||||
let [name, value] = el.children,
|
||||
label = name.children[1],
|
||||
[input, unitPreview] = value.children;
|
||||
|
||||
let min, max;
|
||||
if (def.range) {
|
||||
[min, max] = def.range;
|
||||
}
|
||||
|
||||
label.textContent = filter.name;
|
||||
input.value = filter.value;
|
||||
|
||||
switch (def.type) {
|
||||
case "percentage":
|
||||
case "angle":
|
||||
case "length":
|
||||
input.type = "number";
|
||||
input.min = min;
|
||||
if (max !== Infinity) {
|
||||
input.max = max;
|
||||
}
|
||||
input.step = "0.1";
|
||||
break;
|
||||
case "string":
|
||||
input.type = "text";
|
||||
input.placeholder = def.placeholder;
|
||||
break;
|
||||
}
|
||||
|
||||
// use photoshop-style label-dragging
|
||||
// and show filters' unit next to their <input>
|
||||
if (def.type !== "string") {
|
||||
unitPreview.textContent = filter.unit;
|
||||
|
||||
label.classList.add("devtools-draglabel");
|
||||
label.title = L10N.getStr("labelDragTooltipText");
|
||||
} else {
|
||||
// string-type filters have no unit
|
||||
unitPreview.remove();
|
||||
}
|
||||
|
||||
this.list.appendChild(el);
|
||||
}
|
||||
|
||||
let el = this.list.querySelector(`.filter:last-of-type input`);
|
||||
if (el) {
|
||||
el.focus();
|
||||
// move cursor to end of input
|
||||
el.setSelectionRange(el.value.length, el.value.length);
|
||||
}
|
||||
|
||||
this.emit("render");
|
||||
},
|
||||
|
||||
/**
|
||||
* returns definition of a filter as defined in filterList
|
||||
*
|
||||
* @param {String} name
|
||||
* filter name (e.g. blur)
|
||||
* @return {Object}
|
||||
* filter's definition
|
||||
*/
|
||||
_definition: function(name) {
|
||||
return filterList.find(a => a.name === name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parses the CSS value specified, updating widget's filters
|
||||
*
|
||||
* @param {String} cssValue
|
||||
* css value to be parsed
|
||||
*/
|
||||
setCssValue: function(cssValue) {
|
||||
if (!cssValue) {
|
||||
throw new Error("Missing CSS filter value in setCssValue");
|
||||
}
|
||||
|
||||
this.filters = [];
|
||||
|
||||
if (cssValue === "none") {
|
||||
this.emit("updated", this.getCssValue());
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply filter to a temporary element
|
||||
// and get the computed value to make parsing
|
||||
// easier
|
||||
let tmp = this.doc.createElement("i");
|
||||
tmp.style.filter = cssValue;
|
||||
const computedValue = this.win.getComputedStyle(tmp).filter;
|
||||
|
||||
for (let {name, value} of tokenizeComputedFilter(computedValue)) {
|
||||
this.add(name, value);
|
||||
}
|
||||
|
||||
this.emit("updated", this.getCssValue());
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new [name] filter record with value
|
||||
*
|
||||
* @param {String} name
|
||||
* filter name (e.g. blur)
|
||||
* @param {String} value
|
||||
* value of the filter (e.g. 30px, 20%)
|
||||
* @return {Number}
|
||||
* The index of the new filter in the current list of filters
|
||||
*/
|
||||
add: function(name, value = "") {
|
||||
const def = this._definition(name);
|
||||
if (!def) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let unit = def.type === "string"
|
||||
? ""
|
||||
: (/[a-zA-Z%]+/.exec(value) || [])[0];
|
||||
|
||||
if (def.type !== "string") {
|
||||
value = parseFloat(value);
|
||||
|
||||
// You can omit percentage values' and use a value between 0..1
|
||||
if (def.type === "percentage" && !unit) {
|
||||
value = value * 100;
|
||||
unit = "%";
|
||||
}
|
||||
|
||||
const [min, max] = def.range;
|
||||
if (value < min) {
|
||||
value = min;
|
||||
} else if (value > max) {
|
||||
value = max;
|
||||
}
|
||||
}
|
||||
|
||||
const index = this.filters.push({value, unit, name: def.name}) - 1;
|
||||
this.emit("updated", this.getCssValue());
|
||||
|
||||
return index;
|
||||
},
|
||||
|
||||
/**
|
||||
* returns value + unit of the specified filter
|
||||
*
|
||||
* @param {Number} index
|
||||
* filter index
|
||||
* @return {String}
|
||||
* css value of filter
|
||||
*/
|
||||
getValueAt: function(index) {
|
||||
let filter = this.filters[index];
|
||||
if (!filter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {value, unit} = filter;
|
||||
|
||||
return value + unit;
|
||||
},
|
||||
|
||||
removeAt: function(index) {
|
||||
if (!this.filters[index]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.filters.splice(index, 1);
|
||||
this.emit("updated", this.getCssValue());
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates CSS filter value for filters of the widget
|
||||
*
|
||||
* @return {String}
|
||||
* css value of filters
|
||||
*/
|
||||
getCssValue: function() {
|
||||
return this.filters.map((filter, i) => {
|
||||
return `${filter.name}(${this.getValueAt(i)})`;
|
||||
}).join(" ") || "none";
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates specified filter's value
|
||||
*
|
||||
* @param {Number} index
|
||||
* The index of the filter in the current list of filters
|
||||
* @param {number/string} value
|
||||
* value to set, string for string-typed filters
|
||||
* number for the rest (unit automatically determined)
|
||||
*/
|
||||
updateValueAt: function(index, value) {
|
||||
let filter = this.filters[index];
|
||||
if (!filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const def = this._definition(filter.name);
|
||||
|
||||
if (def.type !== "string") {
|
||||
const [min, max] = def.range;
|
||||
if (value < min) {
|
||||
value = min;
|
||||
} else if (value > max) {
|
||||
value = max;
|
||||
}
|
||||
}
|
||||
|
||||
filter.value = filter.unit ? fixFloat(value, true) : value;
|
||||
|
||||
this.emit("updated", this.getCssValue());
|
||||
},
|
||||
|
||||
_removeEventListeners: function() {
|
||||
this.addButton.removeEventListener("click", this._addButtonClick);
|
||||
this.list.removeEventListener("click", this._removeButtonClick);
|
||||
this.list.removeEventListener("mousedown", this._mouseDown);
|
||||
|
||||
// These events are used for drag drop re-ordering
|
||||
this.win.removeEventListener("mousemove", this._mouseMove);
|
||||
this.win.removeEventListener("mouseup", this._mouseUp);
|
||||
|
||||
// Used to workaround float-precision problems
|
||||
this.list.removeEventListener("input", this._input);
|
||||
},
|
||||
|
||||
_destroyMarkup: function() {
|
||||
this._filterItemMarkup.remove();
|
||||
this.el.remove();
|
||||
this.el = this.list = this.container = this._filterItemMarkup = null;
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this._removeEventListeners();
|
||||
this._destroyMarkup();
|
||||
}
|
||||
};
|
||||
|
||||
// Fixes JavaScript's float precision
|
||||
function fixFloat(a, number) {
|
||||
let fixed = parseFloat(a).toFixed(1);
|
||||
return number ? parseFloat(fixed) : fixed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to swap two filters' indexes
|
||||
* after drag/drop re-ordering
|
||||
*
|
||||
* @param {Array} array
|
||||
* the array to swap elements of
|
||||
* @param {Number} a
|
||||
* index of first element
|
||||
* @param {Number} b
|
||||
* index of second element
|
||||
*/
|
||||
function swapArrayIndices(array, a, b) {
|
||||
array[a] = array.splice(b, 1, array[a])[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenizes CSS Filter value and returns an array of {name, value} pairs
|
||||
*
|
||||
* This is only a very simple tokenizer that only works its way through
|
||||
* parenthesis in the string to detect function names and values.
|
||||
* It assumes that the string actually is a well-formed filter value
|
||||
* (e.g. "blur(2px) hue-rotate(100deg)").
|
||||
*
|
||||
* @param {String} css
|
||||
* CSS Filter value to be parsed
|
||||
* @return {Array}
|
||||
* An array of {name, value} pairs
|
||||
*/
|
||||
function tokenizeComputedFilter(css) {
|
||||
let filters = [];
|
||||
let current = "";
|
||||
let depth = 0;
|
||||
|
||||
if (css === "none") {
|
||||
return filters;
|
||||
}
|
||||
|
||||
while (css.length) {
|
||||
const char = css[0];
|
||||
|
||||
switch (char) {
|
||||
case "(":
|
||||
depth++;
|
||||
if (depth === 1) {
|
||||
filters.push({name: current.trim()});
|
||||
current = "";
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
break;
|
||||
case ")":
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
filters[filters.length - 1].value = current.trim();
|
||||
current = "";
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
current += char;
|
||||
break;
|
||||
}
|
||||
|
||||
css = css.slice(1);
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
|
@ -10,6 +10,7 @@ const IOService = Cc["@mozilla.org/network/io-service;1"]
|
|||
.getService(Ci.nsIIOService);
|
||||
const {Spectrum} = require("devtools/shared/widgets/Spectrum");
|
||||
const {CubicBezierWidget} = require("devtools/shared/widgets/CubicBezierWidget");
|
||||
const {CSSFilterEditorWidget} = require("devtools/shared/widgets/FilterWidget");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const {colorUtils} = require("devtools/css-color");
|
||||
const Heritage = require("sdk/core/heritage");
|
||||
|
@ -39,6 +40,7 @@ const BORDER_RE = /^border(-(top|bottom|left|right))?$/ig;
|
|||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const SPECTRUM_FRAME = "chrome://browser/content/devtools/spectrum-frame.xhtml";
|
||||
const CUBIC_BEZIER_FRAME = "chrome://browser/content/devtools/cubic-bezier-frame.xhtml";
|
||||
const FILTER_FRAME = "chrome://browser/content/devtools/filter-frame.xhtml";
|
||||
const ESCAPE_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE;
|
||||
const RETURN_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
|
||||
const POPUP_EVENTS = ["shown", "hidden", "showing", "hiding"];
|
||||
|
@ -830,6 +832,56 @@ Tooltip.prototype = {
|
|||
return def.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fill the tooltip with a new instance of the CSSFilterEditorWidget
|
||||
* widget initialized with the given filter value, and return a promise
|
||||
* that resolves to the instance of the widget when ready.
|
||||
*/
|
||||
setFilterContent: function(filter) {
|
||||
let def = promise.defer();
|
||||
|
||||
// Create an iframe to host the filter widget
|
||||
let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
|
||||
iframe.setAttribute("transparent", true);
|
||||
iframe.setAttribute("width", "350");
|
||||
iframe.setAttribute("flex", "1");
|
||||
iframe.setAttribute("class", "devtools-tooltip-iframe");
|
||||
|
||||
let panel = this.panel;
|
||||
|
||||
function onLoad() {
|
||||
iframe.removeEventListener("load", onLoad, true);
|
||||
let win = iframe.contentWindow.wrappedJSObject,
|
||||
doc = win.document.documentElement;
|
||||
|
||||
let container = win.document.getElementById("container");
|
||||
let widget = new CSSFilterEditorWidget(container, filter);
|
||||
|
||||
iframe.height = doc.offsetHeight
|
||||
|
||||
widget.on("render", e => {
|
||||
iframe.height = doc.offsetHeight
|
||||
});
|
||||
|
||||
// Resolve to the widget instance whenever the popup becomes visible
|
||||
if (panel.state == "open") {
|
||||
def.resolve(widget);
|
||||
} else {
|
||||
panel.addEventListener("popupshown", function shown() {
|
||||
panel.removeEventListener("popupshown", shown, true);
|
||||
def.resolve(widget);
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
iframe.addEventListener("load", onLoad, true);
|
||||
iframe.setAttribute("src", FILTER_FRAME);
|
||||
|
||||
// Put the iframe in the tooltip
|
||||
this.content = iframe;
|
||||
|
||||
return def.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the content of the tooltip to display a font family preview.
|
||||
* This is based on Lea Verou's Dablet. See https://github.com/LeaVerou/dabblet
|
||||
|
@ -1446,7 +1498,7 @@ SwatchCubicBezierTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.pr
|
|||
* bezier curve in the widget
|
||||
*/
|
||||
show: function() {
|
||||
// Call then parent class' show function
|
||||
// Call the parent class' show function
|
||||
SwatchBasedEditorTooltip.prototype.show.call(this);
|
||||
// Then set the curve and listen to changes to preview them
|
||||
if (this.activeSwatch) {
|
||||
|
@ -1479,6 +1531,61 @@ SwatchCubicBezierTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.pr
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The swatch-based css filter tooltip class is a specific class meant to be used
|
||||
* along with rule-view's generated css filter swatches.
|
||||
* It extends the parent SwatchBasedEditorTooltip class.
|
||||
* It just wraps a standard Tooltip and sets its content with an instance of a
|
||||
* CSSFilterEditorWidget.
|
||||
*
|
||||
* @param {XULDocument} doc
|
||||
*/
|
||||
function SwatchFilterTooltip(doc) {
|
||||
SwatchBasedEditorTooltip.call(this, doc);
|
||||
|
||||
// Creating a filter editor instance.
|
||||
// this.widget will always be a promise that resolves to the widget instance
|
||||
this.widget = this.tooltip.setFilterContent("none");
|
||||
this._onUpdate = this._onUpdate.bind(this);
|
||||
}
|
||||
|
||||
exports.SwatchFilterTooltip = SwatchFilterTooltip;
|
||||
|
||||
SwatchFilterTooltip.prototype = Heritage.extend(SwatchBasedEditorTooltip.prototype, {
|
||||
show: function() {
|
||||
// Call the parent class' show function
|
||||
SwatchBasedEditorTooltip.prototype.show.call(this);
|
||||
// Then set the filter value and listen to changes to preview them
|
||||
if (this.activeSwatch) {
|
||||
this.currentFilterValue = this.activeSwatch.nextSibling;
|
||||
this.widget.then(widget => {
|
||||
widget.off("updated", this._onUpdate);
|
||||
widget.on("updated", this._onUpdate);
|
||||
widget.setCssValue(this.currentFilterValue.textContent);
|
||||
widget.render();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onUpdate: function(event, filters) {
|
||||
if (!this.activeSwatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentFilterValue.textContent = filters;
|
||||
this.preview();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
SwatchBasedEditorTooltip.prototype.destroy.call(this);
|
||||
this.currentFilterValue = null;
|
||||
this.widget.then(widget => {
|
||||
widget.off("updated", this._onUpdate);
|
||||
widget.destroy();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* L10N utility class
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % filterwidgetDTD SYSTEM "chrome://browser/locale/devtools/filterwidget.dtd" >
|
||||
%filterwidgetDTD;
|
||||
]>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<link rel="stylesheet" href="chrome://browser/content/devtools/filter-widget.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
|
||||
<script type="application/javascript;version=1.8" src="theme-switching.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="container">
|
||||
<div class="filters">
|
||||
</div>
|
||||
<div id="editor-footer">
|
||||
<select value="">
|
||||
<option value="">&filterListSelectPlaceholder;</option>
|
||||
</select>
|
||||
<button id="add-filter">&addNewFilterButton;</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,122 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#container {
|
||||
color: var(--theme-body-color);
|
||||
padding: 5px;
|
||||
font: message-box;
|
||||
}
|
||||
|
||||
#container.dragging {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.theme-light #add-filter,
|
||||
.theme-light .remove-button {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.filter {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.filter-name,
|
||||
.filter-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-name {
|
||||
padding-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-value {
|
||||
min-width: 150px;
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(chrome://browser/skin/devtools/close@2x.png);
|
||||
background-size: 16px;
|
||||
font-size: 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* drag/drop handle */
|
||||
#container i {
|
||||
width: 10px;
|
||||
margin-right: 15px;
|
||||
padding: 10px 0;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
#container i::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 10px;
|
||||
height: 1px;
|
||||
background: currentColor;
|
||||
box-shadow: 0 3px 0 0 currentColor,
|
||||
0 -3px 0 0 currentColor;
|
||||
}
|
||||
|
||||
#container .dragging {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.filter-name label {
|
||||
-moz-user-select: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.filter-name label.devtools-draglabel {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.filter-value input {
|
||||
min-width: 50%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.filter-value span {
|
||||
max-width: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
/* message shown when there's no filter specified */
|
||||
#container p {
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
#editor-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#editor-footer select {
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
font: inherit;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
#add-filter {
|
||||
-moz-appearance: none;
|
||||
background: url(chrome://browser/skin/devtools/add.svg);
|
||||
background-size: 18px;
|
||||
border: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
font-size: 0;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
|
@ -2612,15 +2612,19 @@ TextPropertyEditor.prototype = {
|
|||
this.element.removeAttribute("dirty");
|
||||
}
|
||||
|
||||
let colorSwatchClass = "ruleview-colorswatch";
|
||||
let bezierSwatchClass = "ruleview-bezierswatch";
|
||||
const sharedSwatchClass = "ruleview-swatch ";
|
||||
const colorSwatchClass = "ruleview-colorswatch";
|
||||
const bezierSwatchClass = "ruleview-bezierswatch";
|
||||
const filterSwatchClass = "ruleview-filterswatch";
|
||||
|
||||
let outputParser = this.ruleEditor.ruleView._outputParser;
|
||||
let frag = outputParser.parseCssProperty(name, val, {
|
||||
colorSwatchClass: colorSwatchClass,
|
||||
colorSwatchClass: sharedSwatchClass + colorSwatchClass,
|
||||
colorClass: "ruleview-color",
|
||||
bezierSwatchClass: bezierSwatchClass,
|
||||
bezierSwatchClass: sharedSwatchClass + bezierSwatchClass,
|
||||
bezierClass: "ruleview-bezier",
|
||||
filterSwatchClass: sharedSwatchClass + filterSwatchClass,
|
||||
filterClass: "ruleview-filter",
|
||||
defaultColorType: !propDirty,
|
||||
urlClass: "theme-link",
|
||||
baseURI: this.sheetURI
|
||||
|
@ -2658,6 +2662,20 @@ TextPropertyEditor.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
// Attach the filter editor tooltip to the filter swatch
|
||||
let span = this.valueSpan.querySelector("." + filterSwatchClass);
|
||||
if (this.ruleEditor.isEditable) {
|
||||
if(span) {
|
||||
let originalValue = this.valueSpan.textContent;
|
||||
|
||||
this.ruleEditor.ruleView.tooltips.filterEditor.addSwatch(span, {
|
||||
onPreview: () => this._previewValue(this.valueSpan.textContent),
|
||||
onCommit: () => this._applyNewValue(this.valueSpan.textContent),
|
||||
onRevert: () => this._applyNewValue(originalValue, false)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the computed styles.
|
||||
this._updateComputed();
|
||||
},
|
||||
|
|
|
@ -16,7 +16,8 @@ const {Cc, Ci, Cu} = require("chrome");
|
|||
const {
|
||||
Tooltip,
|
||||
SwatchColorPickerTooltip,
|
||||
SwatchCubicBezierTooltip
|
||||
SwatchCubicBezierTooltip,
|
||||
SwatchFilterTooltip
|
||||
} = require("devtools/shared/widgets/Tooltip");
|
||||
const {CssLogic} = require("devtools/styleinspector/css-logic");
|
||||
const {Promise:promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
|
@ -239,7 +240,8 @@ TooltipsOverlay.prototype = {
|
|||
get isEditing() {
|
||||
return this.colorPicker.tooltip.isShown() ||
|
||||
this.colorPicker.eyedropperOpen ||
|
||||
this.cubicBezier.tooltip.isShown();
|
||||
this.cubicBezier.tooltip.isShown() ||
|
||||
this.filterEditor.tooltip.isShown();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -261,6 +263,8 @@ TooltipsOverlay.prototype = {
|
|||
this.colorPicker = new SwatchColorPickerTooltip(this.view.inspector.panelDoc);
|
||||
// Cubic bezier tooltip
|
||||
this.cubicBezier = new SwatchCubicBezierTooltip(this.view.inspector.panelDoc);
|
||||
// Filter editor tooltip
|
||||
this.filterEditor = new SwatchFilterTooltip(this.view.inspector.panelDoc);
|
||||
}
|
||||
|
||||
this._isStarted = true;
|
||||
|
@ -286,6 +290,10 @@ TooltipsOverlay.prototype = {
|
|||
this.cubicBezier.destroy();
|
||||
}
|
||||
|
||||
if (this.filterEditor) {
|
||||
this.filterEditor.destroy();
|
||||
}
|
||||
|
||||
this._isStarted = false;
|
||||
},
|
||||
|
||||
|
@ -345,6 +353,11 @@ TooltipsOverlay.prototype = {
|
|||
this.cubicBezier.hide();
|
||||
}
|
||||
|
||||
if (this.isRuleView && this.filterEditor.tooltip.isShown()) {
|
||||
this.filterEditor.revert();
|
||||
this.filterEdtior.hide();
|
||||
}
|
||||
|
||||
let inspector = this.view.inspector;
|
||||
|
||||
if (type === TOOLTIP_IMAGE_TYPE) {
|
||||
|
@ -373,6 +386,10 @@ TooltipsOverlay.prototype = {
|
|||
if (this.cubicBezier) {
|
||||
this.cubicBezier.hide();
|
||||
}
|
||||
|
||||
if (this.filterEditor) {
|
||||
this.filterEditor.hide();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<!-- LOCALIZATION NOTE : FILE These strings are used in the CSS Filter Editor Widget
|
||||
- which can be found in a tooltip that appears in the Rule View when clicking
|
||||
- on a filter swatch displayed next to CSS declarations like 'filter: blur(2px)'. -->
|
||||
|
||||
<!-- LOCALIZATION NOTE (filterListSelectPlaceholder): This string is used as
|
||||
- a preview option in the list of possible filters <select> -->
|
||||
<!ENTITY filterListSelectPlaceholder "Select a Filter">
|
||||
|
||||
<!-- LOCALIZATION NOTE (addNewFilterButton): This string is displayed on a button used to add new filters -->
|
||||
<!ENTITY addNewFilterButton "Add">
|
|
@ -0,0 +1,33 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# LOCALIZATION NOTE These strings are used in the CSS Filter Editor Widget
|
||||
# which can be found in a tooltip that appears in the Rule View when clicking
|
||||
# on a filter swatch displayed next to CSS declarations like 'filter: blur(2px)'.
|
||||
|
||||
# LOCALIZATION NOTE (emptyFilterList):
|
||||
# This string is displayed when filter's list is empty
|
||||
# (no filter specified / all removed)
|
||||
emptyFilterList=No filter specified
|
||||
|
||||
# LOCALIZATION NOTE (addUsingList):
|
||||
# This string is displayed under [emptyFilterList] when filter's
|
||||
# list is empty, guiding user to add a filter using the list below it
|
||||
addUsingList=Add a filter using the list below
|
||||
|
||||
# LOCALIZATION NOTE (dropShadowPlaceholder):
|
||||
# This string is used as a placeholder for drop-shadow's input
|
||||
# in the filter list (shown when <input> is empty)
|
||||
dropShadowPlaceholder=x y radius color
|
||||
|
||||
# LOCALIZATION NOTE (dragHandleTooltipText):
|
||||
# This string is used as a tooltip text (shown on mouse hover) on the
|
||||
# drag handles of filters which are used to re-order filters
|
||||
dragHandleTooltipText=Drag up or down to re-order filter
|
||||
|
||||
# LOCALIZATION NOTE (labelDragTooltipText):
|
||||
# This string is used as a tooltip text (shown on mouse hover) on the
|
||||
# filters' labels which can be dragged left/right to increase/decrease
|
||||
# the filter's value (like photoshop)
|
||||
labelDragTooltipText=Drag left or right to decrease or increase the value
|
|
@ -34,6 +34,8 @@
|
|||
locale/browser/devtools/debugger.dtd (%chrome/browser/devtools/debugger.dtd)
|
||||
locale/browser/devtools/debugger.properties (%chrome/browser/devtools/debugger.properties)
|
||||
locale/browser/devtools/device.properties (%chrome/browser/devtools/device.properties)
|
||||
locale/browser/devtools/filterwidget.properties (%chrome/browser/devtools/filterwidget.properties)
|
||||
locale/browser/devtools/filterwidget.dtd (%chrome/browser/devtools/filterwidget.dtd)
|
||||
locale/browser/devtools/netmonitor.dtd (%chrome/browser/devtools/netmonitor.dtd)
|
||||
locale/browser/devtools/netmonitor.properties (%chrome/browser/devtools/netmonitor.properties)
|
||||
locale/browser/devtools/shadereditor.dtd (%chrome/browser/devtools/shadereditor.dtd)
|
||||
|
|
|
@ -226,7 +226,9 @@ browser.jar:
|
|||
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
|
||||
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
|
||||
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
|
||||
skin/classic/browser/devtools/add.svg (../shared/devtools/images/add.svg)
|
||||
skin/classic/browser/devtools/filters.svg (../shared/devtools/filters.svg)
|
||||
skin/classic/browser/devtools/filter-swatch.svg (../shared/devtools/images/filter-swatch.svg)
|
||||
skin/classic/browser/devtools/controls.png (../shared/devtools/images/controls.png)
|
||||
skin/classic/browser/devtools/controls@2x.png (../shared/devtools/images/controls@2x.png)
|
||||
skin/classic/browser/devtools/performance-icons.svg (../shared/devtools/images/performance-icons.svg)
|
||||
|
|
|
@ -356,7 +356,9 @@ browser.jar:
|
|||
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
|
||||
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
|
||||
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
|
||||
skin/classic/browser/devtools/add.svg (../shared/devtools/images/add.svg)
|
||||
skin/classic/browser/devtools/filters.svg (../shared/devtools/filters.svg)
|
||||
skin/classic/browser/devtools/filter-swatch.svg (../shared/devtools/images/filter-swatch.svg)
|
||||
skin/classic/browser/devtools/controls.png (../shared/devtools/images/controls.png)
|
||||
skin/classic/browser/devtools/controls@2x.png (../shared/devtools/images/controls@2x.png)
|
||||
skin/classic/browser/devtools/performance-icons.svg (../shared/devtools/images/performance-icons.svg)
|
||||
|
|
|
@ -204,9 +204,8 @@
|
|||
color: black;
|
||||
}
|
||||
|
||||
.ruleview-colorswatch,
|
||||
.computedview-colorswatch,
|
||||
.ruleview-bezierswatch {
|
||||
.ruleview-swatch,
|
||||
.computedview-colorswatch {
|
||||
box-shadow: 0 0 0 1px #818181;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="18" height="18" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<polygon fill="#EEF0F2" points="4,7 8,7 8,3 10,3 10,7 14,7 14,9 10,9 10,13 8,13 8,9 4,9 4,7"></polygon>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 220 B |
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12" width="12px" height="12px">
|
||||
<defs>
|
||||
<mask id="mask">
|
||||
<rect width="100%" height="100%" fill="white"/>
|
||||
<polygon points="12,0 0,0 0,12"/>
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
<g id="addpage-shape">
|
||||
<circle cx="6" cy="6" r="6" fill="white"/>
|
||||
<circle cx="6" cy="6" r="6" mask="url(#mask)" fill="#AEB0B1" />
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 683 B |
|
@ -202,9 +202,8 @@
|
|||
border-color: var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
.ruleview-colorswatch,
|
||||
.computedview-colorswatch,
|
||||
.ruleview-bezierswatch {
|
||||
.ruleview-swatch,
|
||||
.computedview-colorswatch {
|
||||
box-shadow: 0 0 0 1px #c4c4c4;
|
||||
}
|
||||
|
||||
|
|
|
@ -67,8 +67,7 @@
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
.ruleview-rule[uneditable=true] .ruleview-colorswatch,
|
||||
.ruleview-rule[uneditable=true] .ruleview-bezierswatch {
|
||||
.ruleview-rule[uneditable=true] .ruleview-swatch {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
|
@ -149,8 +148,7 @@
|
|||
-moz-margin-start: 35px;
|
||||
}
|
||||
|
||||
.ruleview-colorswatch,
|
||||
.ruleview-bezierswatch {
|
||||
.ruleview-swatch {
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
width: 1em;
|
||||
|
@ -182,6 +180,11 @@
|
|||
background-size: 1em;
|
||||
}
|
||||
|
||||
.ruleview-filterswatch {
|
||||
background: url("chrome://browser/skin/devtools/filter-swatch.svg");
|
||||
background-size: 1em;
|
||||
}
|
||||
|
||||
@media (min-resolution: 2dppx) {
|
||||
.ruleview-bezierswatch {
|
||||
background: url("chrome://browser/skin/devtools/cubic-bezier-swatch@2x.png");
|
||||
|
|
|
@ -307,7 +307,9 @@ browser.jar:
|
|||
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
|
||||
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
|
||||
* skin/classic/browser/devtools/light-theme.css (../shared/devtools/light-theme.css)
|
||||
skin/classic/browser/devtools/add.svg (../shared/devtools/images/add.svg)
|
||||
skin/classic/browser/devtools/filters.svg (../shared/devtools/filters.svg)
|
||||
skin/classic/browser/devtools/filter-swatch.svg (../shared/devtools/images/filter-swatch.svg)
|
||||
skin/classic/browser/devtools/controls.png (../shared/devtools/images/controls.png)
|
||||
skin/classic/browser/devtools/controls@2x.png (../shared/devtools/images/controls@2x.png)
|
||||
skin/classic/browser/devtools/performance-icons.svg (../shared/devtools/images/performance-icons.svg)
|
||||
|
|
|
@ -103,6 +103,8 @@ OutputParser.prototype = {
|
|||
options.expectCubicBezier = ["transition", "transition-timing-function",
|
||||
"animation", "animation-timing-function"].indexOf(name) !== -1;
|
||||
|
||||
options.expectFilter = name === "filter";
|
||||
|
||||
if (this._cssPropertySupportsValue(name, value)) {
|
||||
return this._parse(value, options);
|
||||
}
|
||||
|
@ -184,6 +186,11 @@ OutputParser.prototype = {
|
|||
break;
|
||||
}
|
||||
|
||||
if (options.expectFilter) {
|
||||
this._appendFilter(text, options);
|
||||
break;
|
||||
}
|
||||
|
||||
matched = text.match(REGEX_QUOTES);
|
||||
if (matched) {
|
||||
let match = matched[0];
|
||||
|
@ -408,6 +415,26 @@ OutputParser.prototype = {
|
|||
return false;
|
||||
},
|
||||
|
||||
_appendFilter: function(filters, options={}) {
|
||||
let container = this._createNode("span", {
|
||||
"data-filters": filters
|
||||
});
|
||||
|
||||
if (options.filterSwatchClass) {
|
||||
let swatch = this._createNode("span", {
|
||||
class: options.filterSwatchClass
|
||||
});
|
||||
container.appendChild(swatch);
|
||||
}
|
||||
|
||||
let value = this._createNode("span", {
|
||||
class: options.filterClass
|
||||
}, filters);
|
||||
|
||||
container.appendChild(value);
|
||||
this.parsed.push(container);
|
||||
},
|
||||
|
||||
/**
|
||||
* Append a URL to the output.
|
||||
*
|
||||
|
|
Загрузка…
Ссылка в новой задаче