/* * 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. */ WebInspector.CSSStyleDetailsSidebarPanel = class CSSStyleDetailsSidebarPanel extends WebInspector.DOMDetailsSidebarPanel { constructor() { super("css-style", WebInspector.UIString("Styles"), WebInspector.UIString("Style"), null, true); this._selectedPanel = null; this._forcedPseudoClassCheckboxes = {}; if (WebInspector.cssStyleManager.canForcePseudoClasses()) { this._forcedPseudoClassContainer = document.createElement("div"); this._forcedPseudoClassContainer.className = "pseudo-classes"; let groupElement = null; WebInspector.CSSStyleManager.ForceablePseudoClasses.forEach(function(pseudoClass) { // We don't localize the label since it is a CSS pseudo-class from the CSS standard. let label = pseudoClass.capitalize(); let labelElement = document.createElement("label"); let checkboxElement = document.createElement("input"); checkboxElement.addEventListener("change", this._forcedPseudoClassCheckboxChanged.bind(this, pseudoClass)); checkboxElement.type = "checkbox"; this._forcedPseudoClassCheckboxes[pseudoClass] = checkboxElement; labelElement.appendChild(checkboxElement); labelElement.append(label); if (!groupElement || groupElement.children.length === 2) { groupElement = document.createElement("div"); groupElement.className = "group"; this._forcedPseudoClassContainer.appendChild(groupElement); } groupElement.appendChild(labelElement); }, this); this.contentView.element.appendChild(this._forcedPseudoClassContainer); } this._computedStyleDetailsPanel = new WebInspector.ComputedStyleDetailsPanel(this); this._rulesStyleDetailsPanel = new WebInspector.RulesStyleDetailsPanel(this); this._visualStyleDetailsPanel = new WebInspector.VisualStyleDetailsPanel(this); this._computedStyleDetailsPanel.addEventListener(WebInspector.StyleDetailsPanel.Event.Refreshed, this._filterDidChange, this); this._rulesStyleDetailsPanel.addEventListener(WebInspector.StyleDetailsPanel.Event.Refreshed, this._filterDidChange, this); this._panels = [this._computedStyleDetailsPanel, this._rulesStyleDetailsPanel, this._visualStyleDetailsPanel]; this._panelNavigationInfo = [this._computedStyleDetailsPanel.navigationInfo, this._rulesStyleDetailsPanel.navigationInfo, this._visualStyleDetailsPanel.navigationInfo]; this._lastSelectedSectionSetting = new WebInspector.Setting("last-selected-style-details-panel", this._rulesStyleDetailsPanel.navigationInfo.identifier); let selectedPanel = this._panelMatchingIdentifier(this._lastSelectedSectionSetting.value); if (!selectedPanel) selectedPanel = this._rulesStyleDetailsPanel; this._switchPanels(selectedPanel); this._navigationItem = new WebInspector.ScopeRadioButtonNavigationItem(this._identifier, this._displayName, this._panelNavigationInfo, selectedPanel.navigationInfo); this._navigationItem.addEventListener(WebInspector.ScopeRadioButtonNavigationItem.Event.SelectedItemChanged, this._handleSelectedItemChanged, this); let optionsContainer = this.element.createChild("div", "options-container"); let newRuleButton = optionsContainer.createChild("img", "new-rule"); newRuleButton.title = WebInspector.UIString("New Rule"); newRuleButton.addEventListener("click", this._newRuleButtonClicked.bind(this)); this._filterBar = new WebInspector.FilterBar; this._filterBar.placeholder = WebInspector.UIString("Filter Styles"); this._filterBar.addEventListener(WebInspector.FilterBar.Event.FilterDidChange, this._filterDidChange, this); optionsContainer.appendChild(this._filterBar.element); this._classToggleButton = optionsContainer.createChild("button", "toggle-class-toggle"); this._classToggleButton.textContent = WebInspector.UIString("Classes"); this._classToggleButton.title = WebInspector.UIString("Toggle Classes"); this._classToggleButton.addEventListener("click", this._classToggleButtonClicked.bind(this)); this._classListContainer = this.element.createChild("div", "class-list-container"); this._classListContainer.hidden = true; this._addClassContainer = this._classListContainer.createChild("div", "new-class"); this._addClassContainer.title = WebInspector.UIString("Add a Class"); this._addClassContainer.addEventListener("click", this._addClassContainerClicked.bind(this)); let addClassCheckbox = this._addClassContainer.createChild("input"); addClassCheckbox.type = "checkbox"; addClassCheckbox.checked = true; let addClassIcon = useSVGSymbol("Images/Plus13.svg", "add-class-icon"); this._addClassContainer.appendChild(addClassIcon); this._addClassInput = this._addClassContainer.createChild("input", "class-name-input"); this._addClassInput.setAttribute("placeholder", WebInspector.UIString("Enter Class Name")); this._addClassInput.addEventListener("keypress", this._addClassInputKeyPressed.bind(this)); this._addClassInput.addEventListener("blur", this._addClassInputBlur.bind(this)); WebInspector.cssStyleManager.addEventListener(WebInspector.CSSStyleManager.Event.StyleSheetAdded, this.refresh, this); WebInspector.cssStyleManager.addEventListener(WebInspector.CSSStyleManager.Event.StyleSheetRemoved, this.refresh, this); } // Public supportsDOMNode(nodeToInspect) { return nodeToInspect.nodeType() === Node.ELEMENT_NODE; } refresh() { let domNode = this.domNode; if (!domNode) return; this.contentView.element.scrollTop = this._initialScrollOffset; for (let panel of this._panels) { panel.element._savedScrollTop = undefined; panel.markAsNeedsRefresh(domNode); } this._updatePseudoClassCheckboxes(); if (!this._classListContainer.hidden) this._populateClassToggles(); } visibilityDidChange() { super.visibilityDidChange(); if (!this._selectedPanel) return; if (!this.visible) { this._selectedPanel.hidden(); return; } this._updateNoForcedPseudoClassesScrollOffset(); this._selectedPanel.shown(); this._selectedPanel.markAsNeedsRefresh(this.domNode); } widthDidChange() { super.widthDidChange(); this._updateNoForcedPseudoClassesScrollOffset(); if (this._selectedPanel) this._selectedPanel.widthDidChange(); } computedStyleDetailsPanelShowProperty(property) { this._rulesStyleDetailsPanel.scrollToSectionAndHighlightProperty(property); this._switchPanels(this._rulesStyleDetailsPanel); this._navigationItem.selectedItemIdentifier = this._lastSelectedSectionSetting.value; } // Protected addEventListeners() { let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode; if (!effectiveDOMNode) return; effectiveDOMNode.addEventListener(WebInspector.DOMNode.Event.EnabledPseudoClassesChanged, this._updatePseudoClassCheckboxes, this); effectiveDOMNode.addEventListener(WebInspector.DOMNode.Event.AttributeModified, this._handleNodeAttributeModified, this); effectiveDOMNode.addEventListener(WebInspector.DOMNode.Event.AttributeRemoved, this._handleNodeAttributeRemoved, this); } removeEventListeners() { let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode; if (!effectiveDOMNode) return; effectiveDOMNode.removeEventListener(null, null, this); } // Private get _initialScrollOffset() { if (!WebInspector.cssStyleManager.canForcePseudoClasses()) return 0; return this.domNode && this.domNode.enabledPseudoClasses.length ? 0 : WebInspector.CSSStyleDetailsSidebarPanel.NoForcedPseudoClassesScrollOffset; } _updateNoForcedPseudoClassesScrollOffset() { if (this._forcedPseudoClassContainer) WebInspector.CSSStyleDetailsSidebarPanel.NoForcedPseudoClassesScrollOffset = this._forcedPseudoClassContainer.offsetHeight; } _panelMatchingIdentifier(identifier) { let selectedPanel = null; for (let panel of this._panels) { if (panel.navigationInfo.identifier !== identifier) continue; selectedPanel = panel; break; } return selectedPanel; } _handleSelectedItemChanged() { let selectedIdentifier = this._navigationItem.selectedItemIdentifier; let selectedPanel = this._panelMatchingIdentifier(selectedIdentifier); this._switchPanels(selectedPanel); } _switchPanels(selectedPanel) { console.assert(selectedPanel); if (this._selectedPanel) { this._selectedPanel.hidden(); this._selectedPanel.element._savedScrollTop = this.contentView.element.scrollTop; this.contentView.removeSubview(this._selectedPanel); } this._selectedPanel = selectedPanel; if (!this._selectedPanel) return; this.contentView.addSubview(this._selectedPanel); if (typeof this._selectedPanel.element._savedScrollTop === "number") this.contentView.element.scrollTop = this._selectedPanel.element._savedScrollTop; else this.contentView.element.scrollTop = this._initialScrollOffset; let hasFilter = typeof this._selectedPanel.filterDidChange === "function"; this.contentView.element.classList.toggle("has-filter-bar", hasFilter); if (this._filterBar) this.contentView.element.classList.toggle(WebInspector.CSSStyleDetailsSidebarPanel.FilterInProgressClassName, hasFilter && this._filterBar.hasActiveFilters()); this.contentView.element.classList.toggle("supports-new-rule", typeof this._selectedPanel.newRuleButtonClicked === "function"); this._selectedPanel.shown(); this._lastSelectedSectionSetting.value = selectedPanel.navigationInfo.identifier; } _forcedPseudoClassCheckboxChanged(pseudoClass, event) { if (!this.domNode) return; let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode; effectiveDOMNode.setPseudoClassEnabled(pseudoClass, event.target.checked); } _updatePseudoClassCheckboxes() { if (!this.domNode) return; let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode; let enabledPseudoClasses = effectiveDOMNode.enabledPseudoClasses; for (let pseudoClass in this._forcedPseudoClassCheckboxes) { let checkboxElement = this._forcedPseudoClassCheckboxes[pseudoClass]; 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._selectedPanel && typeof this._selectedPanel.newRuleButtonClicked === "function") this._selectedPanel.newRuleButtonClicked(); } _classToggleButtonClicked(event) { this._classToggleButton.classList.toggle("selected"); this._classListContainer.hidden = !this._classListContainer.hidden; if (this._classListContainer.hidden) return; this._populateClassToggles(); } _addClassContainerClicked(event) { this._addClassContainer.classList.add("active"); this._addClassInput.focus(); } _addClassInputKeyPressed(event) { if (event.keyCode !== WebInspector.KeyboardShortcut.Key.Enter.keyCode) return; this._addClassInput.blur(); } _addClassInputBlur(event) { this._toggleClass.call(this, this._addClassInput.value, true); this._addClassContainer.classList.remove("active"); this._addClassInput.value = null; } _populateClassToggles() { // 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[WebInspector.CSSStyleDetailsSidebarPanel.ToggledClassesSymbol]; if (!classToggledMap) classToggledMap = this.domNode[WebInspector.CSSStyleDetailsSidebarPanel.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[WebInspector.CSSStyleDetailsSidebarPanel.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; function classNameToggleChanged(event) { this._toggleClass.call(this, className, classNameToggle.checked); classToggledMap.set(className, classNameToggle.checked); } classNameToggle.addEventListener("click", classNameToggleChanged.bind(this)); classNameTitle.addEventListener("click", (event) => { classNameToggle.checked = !classNameToggle.checked; classNameToggleChanged.call(this); }); this._classListContainer.appendChild(classNameContainer); } _toggleClass(className, flag) { if (!className || !className.length) return; let effectiveNode = this.domNode; if (effectiveNode && effectiveNode.isPseudoElement()) effectiveNode = effectiveNode.parentNode; console.assert(effectiveNode); if (!effectiveNode) return; if (effectiveNode.nodeType() !== Node.ELEMENT_NODE) return; function resolvedNode(object) { if (!object) return; function toggleClass(className, flag) { this.classList.toggle(className, flag); } object.callFunction(toggleClass, [className, flag]); object.release(); } WebInspector.RemoteObject.resolveNode(effectiveNode, "", resolvedNode); } _filterDidChange() { this.contentView.element.classList.toggle(WebInspector.CSSStyleDetailsSidebarPanel.FilterInProgressClassName, this._filterBar.hasActiveFilters()); this._selectedPanel.filterDidChange(this._filterBar); } }; WebInspector.CSSStyleDetailsSidebarPanel.NoForcedPseudoClassesScrollOffset = 30; // Default height of the forced pseudo classes container. Updated in widthDidChange. WebInspector.CSSStyleDetailsSidebarPanel.FilterInProgressClassName = "filter-in-progress"; WebInspector.CSSStyleDetailsSidebarPanel.FilterMatchingSectionHasLabelClassName = "filter-section-has-label"; WebInspector.CSSStyleDetailsSidebarPanel.FilterMatchSectionClassName = "filter-matching"; WebInspector.CSSStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName = "filter-section-non-matching"; WebInspector.CSSStyleDetailsSidebarPanel.NoFilterMatchInPropertyClassName = "filter-property-non-matching"; WebInspector.CSSStyleDetailsSidebarPanel.ToggledClassesSymbol = Symbol("css-style-details-sidebar-panel-toggled-classes-symbol");