diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/VisualStylePropertyEditor.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/VisualStylePropertyEditor.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/VisualStylePropertyEditor.js | 617 |
1 files changed, 617 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/VisualStylePropertyEditor.js b/Source/WebInspectorUI/UserInterface/Views/VisualStylePropertyEditor.js new file mode 100644 index 000000000..85276dd24 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/VisualStylePropertyEditor.js @@ -0,0 +1,617 @@ +/* + * Copyright (C) 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.VisualStylePropertyEditor = class VisualStylePropertyEditor extends WebInspector.Object +{ + constructor(propertyNames, label, possibleValues, possibleUnits, className, layoutReversed) + { + super(); + + this._propertyInfoList = []; + this._style = null; + + function canonicalizeValues(values) + { + if (!values) + return; + + let canonicalizedValues = {}; + for (let value of values) + canonicalizedValues[value.toLowerCase().replace(/\s/g, "-")] = value; + + return canonicalizedValues; + } + + this._possibleValues = null; + if (possibleValues) { + this._possibleValues = {}; + if (Array.isArray(possibleValues)) + this._possibleValues.basic = canonicalizeValues(possibleValues); + else { + this._possibleValues.basic = canonicalizeValues(possibleValues.basic); + this._possibleValues.advanced = canonicalizeValues(possibleValues.advanced); + } + } + this._possibleUnits = null; + if (possibleUnits) { + this._possibleUnits = {}; + if (Array.isArray(possibleUnits)) + this._possibleUnits.basic = possibleUnits; + else + this._possibleUnits = possibleUnits; + } + this._dependencies = new Map; + + this._element = document.createElement("div"); + this._element.classList.add("visual-style-property-container", className); + this._element.classList.toggle("layout-reversed", !!layoutReversed); + + if (label && label.length) { + let titleContainer = this._element.createChild("div", "visual-style-property-title"); + + this._titleElement = titleContainer.createChild("span"); + this._titleElement.append(label); + this._titleElement.title = label; + this._titleElement.addEventListener("mouseover", this._titleElementMouseOver.bind(this)); + this._titleElement.addEventListener("mouseout", this._titleElementMouseOut.bind(this)); + this._titleElement.addEventListener("click", this._titleElementClick.bind(this)); + + this._boundTitleElementPrepareForClick = this._titleElementPrepareForClick.bind(this); + } + + this._contentElement = this._element.createChild("div", "visual-style-property-value-container"); + + this._specialPropertyPlaceholderElement = this._contentElement.createChild("span", "visual-style-special-property-placeholder"); + this._specialPropertyPlaceholderElement.hidden = true; + + this._warningElement = this._element.createChild("div", "visual-style-property-editor-warning"); + + this._updatedValues = {}; + this._lastValue = null; + this._propertyMissing = false; + + if (typeof propertyNames === "string") + propertyNames = [propertyNames]; + else { + this._hasMultipleProperties = true; + this._element.classList.add("multiple"); + } + + for (let name of propertyNames) { + this._element.classList.add(name); + this._propertyInfoList.push({ + name, + textContainsNameRegExp: new RegExp("(?:(?:^|;)\\s*" + name + "\\s*:)"), + replacementRegExp: new RegExp("((?:^|;)\\s*)(" + name + ")(.+?(?:;|$))") + }); + } + + this._propertyReferenceName = propertyNames[0]; + this._propertyReferenceText = WebInspector.VisualStyleDetailsPanel.propertyReferenceInfo[this._propertyReferenceName]; + this._hasPropertyReference = this._propertyReferenceText && !!this._propertyReferenceText.trim().length; + this._representedProperty = null; + } + + // Static + + static generateFormattedTextForNewProperty(styleText, propertyName, propertyValue) { + if (!propertyName || !propertyValue) + return ""; + + styleText = styleText || ""; + + let linePrefixText = WebInspector.indentString(); + let lineSuffixWhitespace = "\n"; + let trimmedText = styleText.trimRight(); + let textHasNewlines = trimmedText.includes("\n"); + + if (trimmedText.trimLeft().length) { + let styleTextPrefixWhitespace = trimmedText.match(/^\s*/); + if (styleTextPrefixWhitespace) { + let linePrefixWhitespaceMatch = styleTextPrefixWhitespace[0].match(/[^\S\n]+$/); + if (linePrefixWhitespaceMatch && textHasNewlines) + linePrefixText = linePrefixWhitespaceMatch[0]; + else { + linePrefixText = ""; + lineSuffixWhitespace = styleTextPrefixWhitespace[0]; + } + } + + if (!trimmedText.endsWith(";")) + linePrefixText = ";" + linePrefixText; + } else + linePrefixText = "\n" + linePrefixText; + + return linePrefixText + propertyName + ": " + propertyValue + ";" + lineSuffixWhitespace; + } + + // Public + + get element() + { + return this._element; + } + + get style() + { + return this._style; + } + + get value() + { + // Implemented by subclass. + } + + set value(value) + { + // Implemented by subclass. + } + + get units() + { + // Implemented by subclass. + } + + set units(unit) + { + // Implemented by subclass. + } + + get placeholder() + { + // Implemented by subclass. + } + + set placeholder(text) + { + // Implemented by subclass. + } + + get synthesizedValue() + { + // Implemented by subclass. + } + + set suppressStyleTextUpdate(flag) + { + this._suppressStyleTextUpdate = flag; + } + + set masterProperty(flag) + { + this._masterProperty = flag; + } + + get masterProperty() + { + return this._masterProperty; + } + + set optionalProperty(flag) + { + this._optionalProperty = flag; + } + + get optionalProperty() + { + return this._optionalProperty; + } + + set colorProperty(flag) + { + this._colorProperty = flag; + } + + get colorProperty() + { + return this._colorProperty; + } + + get propertyReferenceName() + { + return this._propertyReferenceName; + } + + set propertyReferenceName(name) + { + if (!name || !name.length) + return; + + this._propertyReferenceName = name; + } + + set disabled(flag) + { + this._disabled = flag; + this._element.classList.toggle("disabled", this._disabled); + this._toggleTabbingOfSelectableElements(this._disabled); + } + + get disabled() + { + return this._disabled; + } + + update(style) + { + if (style) + this._style = style; + else if (this._ignoreNextUpdate) { + this._ignoreNextUpdate = false; + return; + } + + if (!this._style) + return; + + this._updatedValues = {}; + let propertyValuesConflict = false; + let propertyMissing = false; + for (let propertyInfo of this._propertyInfoList) { + let property = this._style.propertyForName(propertyInfo.name, true); + propertyMissing = !property; + if (propertyMissing && this._style.nodeStyles) + property = this._style.nodeStyles.computedStyle.propertyForName(propertyInfo.name); + + let longhandPropertyValue = null; + if (typeof this._generateTextFromLonghandProperties === "function") + longhandPropertyValue = this._generateTextFromLonghandProperties(); + + if (longhandPropertyValue) + propertyMissing = false; + + let propertyText = (property && property.value) || longhandPropertyValue; + if (!propertyText || !propertyText.length) + continue; + + if (!propertyMissing && property && property.anonymous) + this._representedProperty = property; + + if (!propertyMissing && property && !property.valid) { + this._element.classList.add("invalid-value"); + this._warningElement.title = WebInspector.UIString("The value ā%sā is not supported for this property.").format(propertyText); + this.specialPropertyPlaceholderElementText = propertyText; + return; + } + + let newValues = this.getValuesFromText(propertyText, propertyMissing); + if (this._updatedValues.placeholder && this._updatedValues.placeholder !== newValues.placeholder) + propertyValuesConflict = true; + + if (!this._updatedValues.placeholder) + this._updatedValues = newValues; + + if (propertyValuesConflict) { + this._updatedValues.conflictingValues = true; + this.specialPropertyPlaceholderElementText = WebInspector.UIString("(multiple)"); + break; + } + } + + if (this._hasMultipleProperties) + this._specialPropertyPlaceholderElement.hidden = !propertyValuesConflict; + + this.updateEditorValues(this._updatedValues); + } + + updateEditorValues(updatedValues) + { + this.value = updatedValues.value; + this.units = updatedValues.units; + this.placeholder = updatedValues.placeholder; + + this._lastValue = this.synthesizedValue; + this.disabled = false; + + this._element.classList.remove("invalid-value"); + this._checkDependencies(); + } + + resetEditorValues(value) + { + this._ignoreNextUpdate = false; + if (!value || !value.length) { + this.value = null; + this._specialPropertyPlaceholderElement.hidden = false; + return; + } + + let updatedValues = this.getValuesFromText(value); + this.updateEditorValues(updatedValues); + } + + modifyPropertyText(text, value) + { + for (let property of this._propertyInfoList) { + if (property.textContainsNameRegExp.test(text)) + text = text.replace(property.replacementRegExp, value !== null ? "$1$2: " + value + ";" : "$1"); + else if (value !== null) + text += WebInspector.VisualStylePropertyEditor.generateFormattedTextForNewProperty(text, property.name, value); + } + return text; + } + + getValuesFromText(text, propertyMissing) + { + let match = this.parseValue(text); + let placeholder = match ? match[1] : text; + let units = match ? match[2] : null; + let value = placeholder; + if (propertyMissing) + value = this.valueIsSupportedKeyword(text) ? text : null; + + this._propertyMissing = propertyMissing || false; + return {value, units, placeholder}; + } + + get propertyMissing() + { + return this._updatedValues && this._propertyMissing; + } + + valueIsCompatible(value) + { + if (!value || !value.length) + return false; + + return this.valueIsSupportedKeyword(value) || !!this.parseValue(value); + } + + valueIsSupportedKeyword(value) { + if (!this._possibleValues) + return false; + + if (Object.keys(this._possibleValues.basic).includes(value)) + return true; + + return this._valueIsSupportedAdvancedKeyword(value); + } + + valueIsSupportedUnit(unit) + { + if (!this._possibleUnits) + return false; + + if (this._possibleUnits.basic.includes(unit)) + return true; + + return this._valueIsSupportedAdvancedUnit(unit); + } + + addDependency(propertyNames, propertyValues) + { + if (!propertyNames || !propertyNames.length || !propertyValues || !propertyValues.length) + return; + + if (!Array.isArray(propertyNames)) + propertyNames = [propertyNames]; + + for (let property of propertyNames) + this._dependencies.set(property, propertyValues); + } + + // Protected + + get contentElement() + { + return this._contentElement; + } + + get specialPropertyPlaceholderElement() + { + return this._specialPropertyPlaceholderElement; + } + + set specialPropertyPlaceholderElementText(text) + { + if (!text || !text.length) + return; + + this._specialPropertyPlaceholderElement.hidden = false; + this._specialPropertyPlaceholderElement.textContent = text; + } + + parseValue(text) + { + return /^([^;]+)\s*;?$/.exec(text); + } + + // Private + + _valueIsSupportedAdvancedKeyword(value) + { + return this._possibleValues.advanced && Object.keys(this._possibleValues.advanced).includes(value); + } + + _valueIsSupportedAdvancedUnit(unit) + { + return this._possibleUnits.advanced && this._possibleUnits.advanced.includes(unit); + } + + _canonicalizedKeywordForKey(value) + { + if (!value || !this._possibleValues) + return null; + + return this._possibleValues.basic[value] || (this._possibleValues.advanced && this._possibleValues.advanced[value]) || null; + } + + _keyForKeyword(keyword) + { + if (!keyword || !keyword.length || !this._possibleValues) + return null; + + for (let basicKey in this._possibleValues.basic) { + if (this._possibleValues.basic[basicKey] === keyword) + return basicKey; + } + + if (!this._possibleValues.advanced) + return null; + + for (let advancedKey in this._possibleValues.advanced) { + if (this._possibleValues.advanced[advancedKey] === keyword) + return advancedKey; + } + + return null; + } + + _valueDidChange() + { + let value = this.synthesizedValue; + if (value === this._lastValue) + return false; + + if (this._style && !this._suppressStyleTextUpdate) { + let newText = this._style.text; + newText = this._replaceShorthandPropertyWithLonghandProperties(newText); + newText = this.modifyPropertyText(newText, value); + this._style.text = newText; + if (!newText.length) + this._style.update(null, null, this._style.styleSheetTextRange); + } + + this._lastValue = value; + this._propertyMissing = !value; + this._ignoreNextUpdate = true; + this._specialPropertyPlaceholderElement.hidden = true; + + this._checkDependencies(); + this._element.classList.remove("invalid-value"); + + this.dispatchEventToListeners(WebInspector.VisualStylePropertyEditor.Event.ValueDidChange); + return true; + } + + _replaceShorthandPropertyWithLonghandProperties(text) + { + if (!this._representedProperty) + return text; + + let shorthand = this._representedProperty.relatedShorthandProperty; + if (!shorthand) + return text; + + let longhandText = ""; + for (let longhandProperty of shorthand.relatedLonghandProperties) { + if (longhandProperty.anonymous) + longhandText += longhandProperty.synthesizedText; + } + return longhandText ? text.replace(shorthand.text, longhandText) : text; + } + + _hasMultipleConflictingValues() + { + return this._hasMultipleProperties && !this._specialPropertyPlaceholderElement.hidden; + } + + _checkDependencies() + { + if (!this._dependencies.size || !this._style || !this.synthesizedValue) { + this._element.classList.remove("missing-dependency"); + return; + } + + let title = ""; + + let dependencies = this._style.nodeStyles.computedStyle.properties.filter((property) => { + return this._dependencies.has(property.name) || this._dependencies.has(property.canonicalName); + }); + + for (let property of dependencies) { + let dependencyValues = this._dependencies.get(property.name); + if (!dependencyValues.includes(property.value)) + title += "\n " + property.name + ": " + dependencyValues.join("/"); + } + + this._element.classList.toggle("missing-dependency", !!title.length); + this._warningElement.title = title.length ? WebInspector.UIString("Missing Dependencies:%s").format(title) : null; + } + + _titleElementPrepareForClick(event) + { + this._titleElement.classList.toggle("property-reference-info", event.type === "keydown" && event.altKey); + } + + _titleElementMouseOver(event) + { + if (!this._hasPropertyReference) + return; + + this._titleElement.classList.toggle("property-reference-info", event.altKey); + document.addEventListener("keydown", this._boundTitleElementPrepareForClick); + document.addEventListener("keyup", this._boundTitleElementPrepareForClick); + } + + _titleElementMouseOut() + { + if (!this._hasPropertyReference) + return; + + this._titleElement.classList.remove("property-reference-info"); + document.removeEventListener("keydown", this._boundTitleElementPrepareForClick); + document.removeEventListener("keyup", this._boundTitleElementPrepareForClick); + } + + _titleElementClick(event) + { + if (event.altKey) + this._showPropertyInfoPopover(); + } + + _showPropertyInfoPopover() + { + if (!this._hasPropertyReference) + return; + + let propertyInfoElement = document.createElement("p"); + propertyInfoElement.classList.add("visual-style-property-info-popover"); + + let propertyInfoTitleElement = document.createElement("h3"); + propertyInfoTitleElement.appendChild(document.createTextNode(this._propertyReferenceName)); + propertyInfoElement.appendChild(propertyInfoTitleElement); + + propertyInfoElement.appendChild(document.createTextNode(this._propertyReferenceText)); + + let bounds = WebInspector.Rect.rectFromClientRect(this._titleElement.getBoundingClientRect()); + let popover = new WebInspector.Popover(this); + popover.content = propertyInfoElement; + popover.present(bounds.pad(2), [WebInspector.RectEdge.MIN_Y]); + popover.windowResizeHandler = () => { + let bounds = WebInspector.Rect.rectFromClientRect(this._titleElement.getBoundingClientRect()); + popover.present(bounds.pad(2), [WebInspector.RectEdge.MIN_Y]); + }; + } + + _toggleTabbingOfSelectableElements(disabled) + { + // Implemented by subclass. + } +}; + +WebInspector.VisualStylePropertyEditor.Event = { + ValueDidChange: "visual-style-property-editor-value-changed" +}; |