451 lines
17 KiB
JavaScript
451 lines
17 KiB
JavaScript
/*
|
|
* Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
WI.GeneralStyleDetailsSidebarPanel = class GeneralStyleDetailsSidebarPanel extends WI.DOMDetailsSidebarPanel
|
|
{
|
|
constructor(identifier, displayName, panelConstructor)
|
|
{
|
|
super(identifier, displayName);
|
|
|
|
this.element.classList.add("css-style");
|
|
|
|
console.assert(panelConstructor.prototype instanceof WI.StyleDetailsPanel);
|
|
this._panel = new panelConstructor(this);
|
|
this._panel.addEventListener(WI.StyleDetailsPanel.Event.NodeChanged, this._handleNodeChanged, this);
|
|
|
|
if (this._panel.supportsToggleCSSClassList && InspectorBackend.hasCommand("DOM.resolveNode"))
|
|
this._classListContainerToggledSetting = new WI.Setting(identifier + "-class-list-container-toggled", !!WI.Setting.migrateValue("class-list-container-toggled"));
|
|
|
|
if (this._panel.supportsToggleCSSForcedPseudoClass && WI.cssManager.canForcePseudoClass()) {
|
|
this._forcedPseudoClassContainerToggledSetting = new WI.Setting(identifier + "-forced-pseudo-class-container-toggled", this._panel.initialToggleCSSForcedPseudoClassState);
|
|
this._checkboxForForcedPseudoClass = new Map;
|
|
}
|
|
}
|
|
|
|
// Public
|
|
|
|
get panel() { return this._panel; }
|
|
|
|
get minimumWidth()
|
|
{
|
|
return Math.max(super.minimumWidth, this._panel.minimumWidth || 0);
|
|
}
|
|
|
|
supportsDOMNode(nodeToInspect)
|
|
{
|
|
return nodeToInspect.nodeType() === Node.ELEMENT_NODE;
|
|
}
|
|
|
|
attached()
|
|
{
|
|
super.attached();
|
|
|
|
if (!this._panel)
|
|
return;
|
|
|
|
console.assert(this.visible, `Shown panel ${this._identifier} must be visible.`);
|
|
|
|
this._panel.markAsNeedsRefresh(this.domNode);
|
|
}
|
|
|
|
// StyleDetailsPanel delegate
|
|
|
|
styleDetailsPanelFocusFilterBar(styleDetailsPanel)
|
|
{
|
|
if (this._filterBar)
|
|
this._filterBar.inputField.focus();
|
|
}
|
|
|
|
// Protected
|
|
|
|
layout()
|
|
{
|
|
let domNode = this.domNode;
|
|
if (!domNode || domNode.destroyed)
|
|
return;
|
|
|
|
this.contentView.element.scrollTop = 0;
|
|
this._panel.markAsNeedsRefresh(domNode);
|
|
|
|
if (this._forcedPseudoClassContainerToggledSetting)
|
|
this._updatePseudoClassCheckboxes();
|
|
|
|
if (this._classListContainerToggledSetting)
|
|
this._populateClassToggles();
|
|
}
|
|
|
|
addEventListeners()
|
|
{
|
|
let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode;
|
|
if (!effectiveDOMNode)
|
|
return;
|
|
|
|
if (this._forcedPseudoClassContainerToggledSetting)
|
|
effectiveDOMNode.addEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._updatePseudoClassCheckboxes, this);
|
|
|
|
if (this._classListContainerToggledSetting) {
|
|
effectiveDOMNode.addEventListener(WI.DOMNode.Event.AttributeModified, this._handleNodeAttributeModified, this);
|
|
effectiveDOMNode.addEventListener(WI.DOMNode.Event.AttributeRemoved, this._handleNodeAttributeRemoved, this);
|
|
}
|
|
}
|
|
|
|
removeEventListeners()
|
|
{
|
|
let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode;
|
|
if (!effectiveDOMNode)
|
|
return;
|
|
|
|
if (this._forcedPseudoClassContainerToggledSetting)
|
|
effectiveDOMNode.removeEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._updatePseudoClassCheckboxes, this);
|
|
|
|
if (this._classListContainerToggledSetting) {
|
|
effectiveDOMNode.removeEventListener(WI.DOMNode.Event.AttributeModified, this._handleNodeAttributeModified, this);
|
|
effectiveDOMNode.removeEventListener(WI.DOMNode.Event.AttributeRemoved, this._handleNodeAttributeRemoved, this);
|
|
}
|
|
}
|
|
|
|
initialLayout()
|
|
{
|
|
this.contentView.addSubview(this._panel);
|
|
|
|
if (this._classListContainerToggledSetting) {
|
|
this._classListContainer = this.element.createChild("div", "class-list-container");
|
|
|
|
this._addClassContainer = this._classListContainer.createChild("div", "new-class");
|
|
this._addClassContainer.title = WI.UIString("Add a Class");
|
|
this._addClassContainer.addEventListener("click", this._addClassContainerClicked.bind(this));
|
|
|
|
this._addClassInput = this._addClassContainer.createChild("input", "class-name-input");
|
|
this._addClassInput.spellcheck = false;
|
|
this._addClassInput.setAttribute("placeholder", WI.UIString("Add New Class"));
|
|
this._addClassInput.addEventListener("keypress", this._addClassInputKeyPressed.bind(this));
|
|
this._addClassInput.addEventListener("blur", this._addClassInputBlur.bind(this));
|
|
}
|
|
|
|
if (this._forcedPseudoClassContainerToggledSetting) {
|
|
this._forcedPseudoClassContainer = this.element.appendChild(document.createElement("div"));
|
|
this._forcedPseudoClassContainer.className = "forced-pseudo-class-container";
|
|
|
|
for (let pseudoClass of Object.values(WI.CSSManager.ForceablePseudoClass)) {
|
|
if (!WI.cssManager.canForcePseudoClass(pseudoClass))
|
|
continue;
|
|
|
|
let labelElement = this._forcedPseudoClassContainer.appendChild(document.createElement("label"));
|
|
|
|
let checkboxElement = labelElement.appendChild(document.createElement("input"));
|
|
checkboxElement.addEventListener("change", this._forcedPseudoClassCheckboxChanged.bind(this, pseudoClass));
|
|
checkboxElement.type = "checkbox";
|
|
this._checkboxForForcedPseudoClass.set(pseudoClass, checkboxElement);
|
|
|
|
labelElement.append(WI.CSSManager.displayNameForForceablePseudoClass(pseudoClass));
|
|
}
|
|
}
|
|
|
|
let optionsContainer = this.element.createChild("div", "options-container");
|
|
|
|
let newRuleButton = optionsContainer.createChild("img", "new-rule");
|
|
newRuleButton.title = WI.UIString("Add new rule");
|
|
newRuleButton.addEventListener("click", this._newRuleButtonClicked.bind(this));
|
|
newRuleButton.addEventListener("contextmenu", this._newRuleButtonContextMenu.bind(this));
|
|
|
|
if (typeof this._panel.filterDidChange === "function") {
|
|
this._filterBar = new WI.FilterBar;
|
|
this._filterBar.addEventListener(WI.FilterBar.Event.FilterDidChange, this._filterDidChange, this);
|
|
this._filterBar.inputField.addEventListener("keydown", this._handleFilterBarInputFieldKeyDown.bind(this));
|
|
this.contentView.element.classList.add("has-filter-bar");
|
|
|
|
optionsContainer.appendChild(this._filterBar.element);
|
|
}
|
|
|
|
if (this._classListContainerToggledSetting) {
|
|
this._classListToggleButton = optionsContainer.createChild("button", "toggle class-list");
|
|
this._classListToggleButton.textContent = WI.UIString("Classes");
|
|
this._classListToggleButton.title = WI.UIString("Toggle Classes");
|
|
this._classListToggleButton.addEventListener("click", this._classListToggleButtonClicked.bind(this));
|
|
|
|
this._updateClassListContainer();
|
|
}
|
|
|
|
if (this._forcedPseudoClassContainerToggledSetting) {
|
|
this._forcedPseudoClassToggleButton = optionsContainer.appendChild(document.createElement("button"));
|
|
this._forcedPseudoClassToggleButton.className = "toggle forced-pseudo-class";
|
|
this._forcedPseudoClassToggleButton.textContent = WI.UIString("Pseudo", "Pseudo @ Styles details sidebar panel", "Label for button that shows controls for toggling CSS pseudo-classes on the selected element.");
|
|
this._forcedPseudoClassToggleButton.title = WI.UIString("Toggle Pseudo Classes");
|
|
this._forcedPseudoClassToggleButton.addEventListener("click", this._forcedPseudoClassToggleButtonClicked.bind(this));
|
|
|
|
this._updateForcedPseudoClassContainer();
|
|
}
|
|
|
|
WI.cssManager.addEventListener(WI.CSSManager.Event.StyleSheetAdded, this._styleSheetAddedOrRemoved, this);
|
|
WI.cssManager.addEventListener(WI.CSSManager.Event.StyleSheetRemoved, this._styleSheetAddedOrRemoved, this);
|
|
}
|
|
|
|
sizeDidChange()
|
|
{
|
|
super.sizeDidChange();
|
|
|
|
if (this._panel)
|
|
this._panel.sizeDidChange();
|
|
}
|
|
|
|
// Private
|
|
|
|
_updateClassListContainer()
|
|
{
|
|
let hidden = !this._classListContainerToggledSetting.value;
|
|
this._classListToggleButton.classList.toggle("selected", !hidden);
|
|
this._classListContainer.hidden = hidden;
|
|
|
|
this._populateClassToggles();
|
|
}
|
|
|
|
_updateForcedPseudoClassContainer()
|
|
{
|
|
let hidden = !this._forcedPseudoClassContainerToggledSetting.value;
|
|
this._forcedPseudoClassToggleButton.classList.toggle("selected", !hidden);
|
|
this._forcedPseudoClassContainer.hidden = hidden;
|
|
}
|
|
|
|
_handleNodeChanged(event)
|
|
{
|
|
this.contentView.element.classList.toggle("supports-new-rule", this._panel.supportsNewRule);
|
|
this.contentView.element.classList.toggle("supports-toggle-class-list", this._panel.supportsToggleCSSClassList);
|
|
this.contentView.element.classList.toggle("supports-toggle-forced-pseudo-class", this._panel.supportsToggleCSSForcedPseudoClass);
|
|
}
|
|
|
|
_forcedPseudoClassCheckboxChanged(pseudoClass, event)
|
|
{
|
|
if (!this.domNode)
|
|
return;
|
|
|
|
let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode;
|
|
if (!effectiveDOMNode)
|
|
return;
|
|
|
|
effectiveDOMNode.setPseudoClassEnabled(pseudoClass, event.target.checked);
|
|
|
|
this._checkboxForForcedPseudoClass.get(pseudoClass).focus();
|
|
}
|
|
|
|
_updatePseudoClassCheckboxes()
|
|
{
|
|
if (!this.domNode)
|
|
return;
|
|
|
|
let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode;
|
|
if (!effectiveDOMNode)
|
|
return;
|
|
|
|
let enabledPseudoClasses = effectiveDOMNode.enabledPseudoClasses;
|
|
|
|
for (let [pseudoClass, checkboxElement] of this._checkboxForForcedPseudoClass)
|
|
checkboxElement.checked = enabledPseudoClasses.includes(pseudoClass);
|
|
}
|
|
|
|
_handleNodeAttributeModified(event)
|
|
{
|
|
if (event && event.data && event.data.name === "class")
|
|
this._populateClassToggles();
|
|
}
|
|
|
|
_handleNodeAttributeRemoved(event)
|
|
{
|
|
if (event && event.data && event.data.name === "class")
|
|
this._populateClassToggles();
|
|
}
|
|
|
|
_newRuleButtonClicked()
|
|
{
|
|
if (this._panel && typeof this._panel.newRuleButtonClicked === "function")
|
|
this._panel.newRuleButtonClicked();
|
|
}
|
|
|
|
_newRuleButtonContextMenu(event)
|
|
{
|
|
if (this._panel && typeof this._panel.newRuleButtonContextMenu === "function")
|
|
this._panel.newRuleButtonContextMenu(event);
|
|
}
|
|
|
|
_classListToggleButtonClicked(event)
|
|
{
|
|
if (this._forcedPseudoClassContainerToggledSetting) {
|
|
this._forcedPseudoClassContainerToggledSetting.value = false;
|
|
this._updateForcedPseudoClassContainer();
|
|
}
|
|
|
|
this._classListContainerToggledSetting.value = !this._classListContainerToggledSetting.value;
|
|
this._updateClassListContainer();
|
|
}
|
|
|
|
_forcedPseudoClassToggleButtonClicked(event)
|
|
{
|
|
if (this._classListContainerToggledSetting) {
|
|
this._classListContainerToggledSetting.value = false;
|
|
this._updateClassListContainer();
|
|
}
|
|
|
|
this._forcedPseudoClassContainerToggledSetting.value = !this._forcedPseudoClassContainerToggledSetting.value;
|
|
this._updateForcedPseudoClassContainer();
|
|
}
|
|
|
|
_addClassContainerClicked(event)
|
|
{
|
|
this._addClassContainer.classList.add("active");
|
|
this._addClassInput.focus();
|
|
}
|
|
|
|
_addClassInputKeyPressed(event)
|
|
{
|
|
if (event.keyCode !== WI.KeyboardShortcut.Key.Enter.keyCode)
|
|
return;
|
|
|
|
this._addClassFromInput();
|
|
}
|
|
|
|
_addClassInputBlur(event)
|
|
{
|
|
this._addClassFromInput();
|
|
|
|
this._addClassContainer.classList.remove("active");
|
|
}
|
|
|
|
_addClassFromInput()
|
|
{
|
|
this.domNode.toggleClass(this._addClassInput.value, true);
|
|
this._addClassInput.value = null;
|
|
}
|
|
|
|
_populateClassToggles()
|
|
{
|
|
if (!this._classListContainer || this._classListContainer.hidden)
|
|
return;
|
|
|
|
// Ensure that _addClassContainer is the first child of _classListContainer.
|
|
while (this._classListContainer.children.length > 1)
|
|
this._classListContainer.children[1].remove();
|
|
|
|
let classes = this.domNode.getAttribute("class") || [];
|
|
let classToggledMap = this.domNode[WI.GeneralStyleDetailsSidebarPanel.ToggledClassesSymbol];
|
|
if (!classToggledMap)
|
|
classToggledMap = this.domNode[WI.GeneralStyleDetailsSidebarPanel.ToggledClassesSymbol] = new Map;
|
|
|
|
if (classes && classes.length) {
|
|
for (let className of classes.split(/\s+/))
|
|
classToggledMap.set(className, true);
|
|
}
|
|
|
|
for (let [className, toggled] of classToggledMap) {
|
|
if ((toggled && !classes.includes(className)) || (!toggled && classes.includes(className))) {
|
|
toggled = !toggled;
|
|
classToggledMap.set(className, toggled);
|
|
}
|
|
|
|
this._createToggleForClassName(className);
|
|
}
|
|
}
|
|
|
|
_createToggleForClassName(className)
|
|
{
|
|
if (!className || !className.length)
|
|
return;
|
|
|
|
let classToggledMap = this.domNode[WI.GeneralStyleDetailsSidebarPanel.ToggledClassesSymbol];
|
|
if (!classToggledMap)
|
|
return;
|
|
|
|
if (!classToggledMap.has(className))
|
|
classToggledMap.set(className, true);
|
|
|
|
let toggled = classToggledMap.get(className);
|
|
|
|
let classNameContainer = document.createElement("div");
|
|
classNameContainer.classList.add("class-toggle");
|
|
|
|
let classNameToggle = classNameContainer.createChild("input");
|
|
classNameToggle.type = "checkbox";
|
|
classNameToggle.checked = toggled;
|
|
|
|
let classNameTitle = classNameContainer.createChild("span");
|
|
classNameTitle.textContent = className;
|
|
classNameTitle.draggable = true;
|
|
classNameTitle.addEventListener("dragstart", (event) => {
|
|
event.dataTransfer.setData(WI.GeneralStyleDetailsSidebarPanel.ToggledClassesDragType, className);
|
|
event.dataTransfer.effectAllowed = "copy";
|
|
});
|
|
|
|
let classNameToggleChanged = (event) => {
|
|
this.domNode.toggleClass(className, classNameToggle.checked);
|
|
classToggledMap.set(className, classNameToggle.checked);
|
|
};
|
|
|
|
classNameToggle.addEventListener("click", classNameToggleChanged);
|
|
classNameTitle.addEventListener("click", (event) => {
|
|
classNameToggle.checked = !classNameToggle.checked;
|
|
classNameToggleChanged();
|
|
});
|
|
|
|
this._classListContainer.appendChild(classNameContainer);
|
|
}
|
|
|
|
_filterDidChange()
|
|
{
|
|
if (!this._filterBar)
|
|
return;
|
|
|
|
this.contentView.element.classList.toggle(WI.GeneralStyleDetailsSidebarPanel.FilterInProgressClassName, this._filterBar.hasActiveFilters());
|
|
|
|
this._panel.filterDidChange(this._filterBar);
|
|
}
|
|
|
|
_handleFilterBarInputFieldKeyDown(event)
|
|
{
|
|
if (event.key !== "Tab" || !event.shiftKey)
|
|
return;
|
|
|
|
if (this._panel.focusLastSection) {
|
|
this._panel.focusLastSection();
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
|
|
_styleSheetAddedOrRemoved()
|
|
{
|
|
let domNode = this.domNode;
|
|
if (!domNode || domNode.destroyed)
|
|
return;
|
|
|
|
this._panel.markAsNeedsRefresh(domNode);
|
|
}
|
|
};
|
|
|
|
WI.GeneralStyleDetailsSidebarPanel.FilterInProgressClassName = "filter-in-progress";
|
|
WI.GeneralStyleDetailsSidebarPanel.FilterMatchingSectionHasLabelClassName = "filter-section-has-label";
|
|
WI.GeneralStyleDetailsSidebarPanel.FilterMatchSectionClassName = "filter-matching";
|
|
WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName = "filter-section-non-matching";
|
|
WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInPropertyClassName = "filter-property-non-matching";
|
|
|
|
WI.GeneralStyleDetailsSidebarPanel.ToggledClassesSymbol = Symbol("css-style-details-sidebar-panel-toggled-classes-symbol");
|
|
WI.GeneralStyleDetailsSidebarPanel.ToggledClassesDragType = "web-inspector/css-class";
|