| /* |
| * Copyright (C) 2015 Apple Inc. All rights reserved. |
| * Copyright (C) 2016 Devin Rousso <webkit@devinrousso.com>. 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.InlineSwatch = class InlineSwatch extends WI.Object |
| { |
| constructor(type, value, readOnly) |
| { |
| super(); |
| |
| this._type = type; |
| |
| if (this._type === WI.InlineSwatch.Type.Bezier || this._type === WI.InlineSwatch.Type.Spring) |
| this._swatchElement = WI.ImageUtilities.useSVGSymbol("Images/CubicBezier.svg"); |
| else if (this._type === WI.InlineSwatch.Type.Variable) |
| this._swatchElement = WI.ImageUtilities.useSVGSymbol("Images/CSSVariable.svg"); |
| else |
| this._swatchElement = document.createElement("span"); |
| |
| this._swatchElement.classList.add("inline-swatch", this._type.split("-").lastValue); |
| |
| if (readOnly) |
| this._swatchElement.classList.add("read-only"); |
| else { |
| switch (this._type) { |
| case WI.InlineSwatch.Type.Color: |
| this._swatchElement.title = WI.UIString("Click to select a color\nShift-click to switch color formats"); |
| break; |
| case WI.InlineSwatch.Type.Gradient: |
| this._swatchElement.title = WI.UIString("Edit custom gradient"); |
| break; |
| case WI.InlineSwatch.Type.Bezier: |
| this._swatchElement.title = WI.UIString("Edit \u201Ccubic-bezier\u201D function"); |
| break; |
| case WI.InlineSwatch.Type.Spring: |
| this._swatchElement.title = WI.UIString("Edit \u201Cspring\u201D function"); |
| break; |
| case WI.InlineSwatch.Type.Variable: |
| this._swatchElement.title = WI.UIString("Click to view variable value\nShift-click to replace variable with value"); |
| break; |
| case WI.InlineSwatch.Type.Image: |
| this._swatchElement.title = WI.UIString("View Image"); |
| break; |
| default: |
| WI.reportInternalError(`Unknown InlineSwatch type "${type}"`); |
| break; |
| } |
| |
| this._swatchElement.addEventListener("click", this._swatchElementClicked.bind(this)); |
| if (this._type === WI.InlineSwatch.Type.Color) |
| this._swatchElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this)); |
| } |
| |
| this._swatchInnerElement = this._swatchElement.createChild("span"); |
| |
| this._value = value || this._fallbackValue(); |
| this._valueEditor = null; |
| |
| this._updateSwatch(); |
| } |
| |
| // Public |
| |
| get element() |
| { |
| return this._swatchElement; |
| } |
| |
| get value() |
| { |
| if (typeof this._value === "function") |
| return this._value(); |
| return this._value; |
| } |
| |
| set value(value) |
| { |
| this._value = value; |
| this._updateSwatch(true); |
| } |
| |
| // Popover delegate |
| |
| didDismissPopover(popover) |
| { |
| if (!this._valueEditor) |
| return; |
| |
| if (this._valueEditor.removeListeners) |
| this._valueEditor.removeListeners(); |
| |
| if (this._valueEditor instanceof WI.Object) |
| this._valueEditor.removeEventListener(null, null, this); |
| |
| this._valueEditor = null; |
| |
| this.dispatchEventToListeners(WI.InlineSwatch.Event.Deactivated); |
| } |
| |
| // Private |
| |
| _fallbackValue() |
| { |
| switch (this._type) { |
| case WI.InlineSwatch.Type.Bezier: |
| return WI.CubicBezier.fromString("linear"); |
| case WI.InlineSwatch.Type.Gradient: |
| return WI.Gradient.fromString("linear-gradient(transparent, transparent)"); |
| case WI.InlineSwatch.Type.Spring: |
| return WI.Spring.fromString("1 100 10 0"); |
| case WI.InlineSwatch.Type.Color: |
| return WI.Color.fromString("white"); |
| default: |
| return null; |
| } |
| } |
| |
| _updateSwatch(dontFireEvents) |
| { |
| let value = this.value; |
| |
| if (this._type === WI.InlineSwatch.Type.Color || this._type === WI.InlineSwatch.Type.Gradient) |
| this._swatchInnerElement.style.background = value ? value.toString() : null; |
| else if (this._type === WI.InlineSwatch.Type.Image) |
| this._swatchInnerElement.style.setProperty("background-image", `url(${value.src})`); |
| |
| if (!dontFireEvents) |
| this.dispatchEventToListeners(WI.InlineSwatch.Event.ValueChanged, {value}); |
| } |
| |
| _swatchElementClicked(event) |
| { |
| event.stop(); |
| |
| let value = this.value; |
| |
| if (event.shiftKey && value) { |
| if (this._type === WI.InlineSwatch.Type.Color) { |
| let nextFormat = value.nextFormat(); |
| console.assert(nextFormat); |
| if (nextFormat) { |
| value.format = nextFormat; |
| this._updateSwatch(); |
| } |
| return; |
| } |
| |
| if (this._type === WI.InlineSwatch.Type.Variable) { |
| // Force the swatch to replace the displayed text with the variable's value. |
| this._swatchElement.remove(); |
| this._updateSwatch(); |
| return; |
| } |
| } |
| |
| if (this._valueEditor) |
| return; |
| |
| if (!value) |
| value = this._fallbackValue(); |
| |
| let bounds = WI.Rect.rectFromClientRect(this._swatchElement.getBoundingClientRect()); |
| let popover = new WI.Popover(this); |
| |
| popover.windowResizeHandler = () => { |
| let bounds = WI.Rect.rectFromClientRect(this._swatchElement.getBoundingClientRect()); |
| popover.present(bounds.pad(2), [WI.RectEdge.MIN_X]); |
| }; |
| |
| this._valueEditor = null; |
| switch (this._type) { |
| case WI.InlineSwatch.Type.Color: |
| this._valueEditor = new WI.ColorPicker; |
| this._valueEditor.addEventListener(WI.ColorPicker.Event.ColorChanged, this._valueEditorValueDidChange, this); |
| this._valueEditor.addEventListener(WI.ColorPicker.Event.FormatChanged, (event) => popover.update()); |
| break; |
| |
| case WI.InlineSwatch.Type.Gradient: |
| this._valueEditor = new WI.GradientEditor; |
| this._valueEditor.addEventListener(WI.GradientEditor.Event.GradientChanged, this._valueEditorValueDidChange, this); |
| this._valueEditor.addEventListener(WI.GradientEditor.Event.ColorPickerToggled, (event) => popover.update()); |
| break; |
| |
| case WI.InlineSwatch.Type.Bezier: |
| this._valueEditor = new WI.BezierEditor; |
| this._valueEditor.addEventListener(WI.BezierEditor.Event.BezierChanged, this._valueEditorValueDidChange, this); |
| break; |
| |
| case WI.InlineSwatch.Type.Spring: |
| this._valueEditor = new WI.SpringEditor; |
| this._valueEditor.addEventListener(WI.SpringEditor.Event.SpringChanged, this._valueEditorValueDidChange, this); |
| break; |
| |
| case WI.InlineSwatch.Type.Variable: |
| this._valueEditor = {}; |
| |
| this._valueEditor.element = document.createElement("div"); |
| this._valueEditor.element.classList.add("inline-swatch-variable-popover"); |
| |
| this._valueEditor.codeMirror = WI.CodeMirrorEditor.create(this._valueEditor.element, { |
| mode: "css", |
| readOnly: true, |
| }); |
| this._valueEditor.codeMirror.on("update", () => { |
| popover.update(); |
| }); |
| break; |
| |
| case WI.InlineSwatch.Type.Image: |
| if (value.src) { |
| this._valueEditor = {}; |
| this._valueEditor.element = document.createElement("img"); |
| this._valueEditor.element.src = value.src; |
| this._valueEditor.element.classList.add("show-grid"); |
| this._valueEditor.element.style.setProperty("max-width", "50vw"); |
| this._valueEditor.element.style.setProperty("max-height", "50vh"); |
| } |
| break; |
| } |
| |
| if (!this._valueEditor) |
| return; |
| |
| popover.content = this._valueEditor.element; |
| popover.present(bounds.pad(2), [WI.RectEdge.MIN_X]); |
| |
| this.dispatchEventToListeners(WI.InlineSwatch.Event.Activated); |
| |
| switch (this._type) { |
| case WI.InlineSwatch.Type.Color: |
| this._valueEditor.color = value; |
| break; |
| |
| case WI.InlineSwatch.Type.Gradient: |
| this._valueEditor.gradient = value; |
| break; |
| |
| case WI.InlineSwatch.Type.Bezier: |
| this._valueEditor.bezier = value; |
| break; |
| |
| case WI.InlineSwatch.Type.Spring: |
| this._valueEditor.spring = value; |
| break; |
| |
| case WI.InlineSwatch.Type.Variable: { |
| let codeMirror = this._valueEditor.codeMirror; |
| codeMirror.setValue(value); |
| |
| const range = null; |
| function optionsForType(type) { |
| return { |
| allowedTokens: /\btag\b/, |
| callback(marker, valueObject, valueString) { |
| let swatch = new WI.InlineSwatch(type, valueObject, true); |
| codeMirror.setUniqueBookmark({line: 0, ch: 0}, swatch.element); |
| } |
| }; |
| } |
| createCodeMirrorColorTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Color)); |
| createCodeMirrorGradientTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Gradient)); |
| createCodeMirrorCubicBezierTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Bezier)); |
| createCodeMirrorSpringTextMarkers(codeMirror, range, optionsForType(WI.InlineSwatch.Type.Spring)); |
| break; |
| } |
| } |
| } |
| |
| _valueEditorValueDidChange(event) |
| { |
| if (this._type === WI.InlineSwatch.Type.Color) |
| this._value = event.data.color; |
| else if (this._type === WI.InlineSwatch.Type.Gradient) |
| this._value = event.data.gradient; |
| else if (this._type === WI.InlineSwatch.Type.Bezier) |
| this._value = event.data.bezier; |
| else if (this._type === WI.InlineSwatch.Type.Spring) |
| this._value = event.data.spring; |
| |
| this._updateSwatch(); |
| } |
| |
| _handleContextMenuEvent(event) |
| { |
| let value = this.value; |
| if (!value) |
| return; |
| |
| let contextMenu = WI.ContextMenu.createFromEvent(event); |
| |
| if (value.isKeyword() && value.format !== WI.Color.Format.Keyword) { |
| contextMenu.appendItem(WI.UIString("Format: Keyword"), () => { |
| value.format = WI.Color.Format.Keyword; |
| this._updateSwatch(); |
| }); |
| } |
| |
| let hexInfo = this._getNextValidHEXFormat(); |
| if (hexInfo) { |
| contextMenu.appendItem(hexInfo.title, () => { |
| value.format = hexInfo.format; |
| this._updateSwatch(); |
| }); |
| } |
| |
| if (value.simple && value.format !== WI.Color.Format.HSL) { |
| contextMenu.appendItem(WI.UIString("Format: HSL"), () => { |
| value.format = WI.Color.Format.HSL; |
| this._updateSwatch(); |
| }); |
| } else if (value.format !== WI.Color.Format.HSLA) { |
| contextMenu.appendItem(WI.UIString("Format: HSLA"), () => { |
| value.format = WI.Color.Format.HSLA; |
| this._updateSwatch(); |
| }); |
| } |
| |
| if (value.simple && value.format !== WI.Color.Format.RGB) { |
| contextMenu.appendItem(WI.UIString("Format: RGB"), () => { |
| value.format = WI.Color.Format.RGB; |
| this._updateSwatch(); |
| }); |
| } else if (value.format !== WI.Color.Format.RGBA) { |
| contextMenu.appendItem(WI.UIString("Format: RGBA"), () => { |
| value.format = WI.Color.Format.RGBA; |
| this._updateSwatch(); |
| }); |
| } |
| } |
| |
| _getNextValidHEXFormat() |
| { |
| if (this._type !== WI.InlineSwatch.Type.Color) |
| return false; |
| |
| let value = this.value; |
| |
| function hexMatchesCurrentColor(hexInfo) { |
| let nextIsSimple = hexInfo.format === WI.Color.Format.ShortHEX || hexInfo.format === WI.Color.Format.HEX; |
| if (nextIsSimple && !value.simple) |
| return false; |
| |
| let nextIsShort = hexInfo.format === WI.Color.Format.ShortHEX || hexInfo.format === WI.Color.Format.ShortHEXAlpha; |
| if (nextIsShort && !value.canBeSerializedAsShortHEX()) |
| return false; |
| |
| return true; |
| } |
| |
| const hexFormats = [ |
| { |
| format: WI.Color.Format.ShortHEX, |
| title: WI.UIString("Format: Short Hex") |
| }, |
| { |
| format: WI.Color.Format.ShortHEXAlpha, |
| title: WI.UIString("Format: Short Hex with Alpha") |
| }, |
| { |
| format: WI.Color.Format.HEX, |
| title: WI.UIString("Format: Hex") |
| }, |
| { |
| format: WI.Color.Format.HEXAlpha, |
| title: WI.UIString("Format: Hex with Alpha") |
| } |
| ]; |
| |
| let currentColorIsHEX = hexFormats.some((info) => info.format === value.format); |
| |
| for (let i = 0; i < hexFormats.length; ++i) { |
| if (currentColorIsHEX && value.format !== hexFormats[i].format) |
| continue; |
| |
| for (let j = ~~currentColorIsHEX; j < hexFormats.length; ++j) { |
| let nextIndex = (i + j) % hexFormats.length; |
| if (hexMatchesCurrentColor(hexFormats[nextIndex])) |
| return hexFormats[nextIndex]; |
| } |
| return null; |
| } |
| return hexFormats[0]; |
| } |
| }; |
| |
| WI.InlineSwatch.Type = { |
| Color: "inline-swatch-type-color", |
| Gradient: "inline-swatch-type-gradient", |
| Bezier: "inline-swatch-type-bezier", |
| Spring: "inline-swatch-type-spring", |
| Variable: "inline-swatch-type-variable", |
| Image: "inline-swatch-type-image", |
| }; |
| |
| WI.InlineSwatch.Event = { |
| ValueChanged: "inline-swatch-value-changed", |
| Activated: "inline-swatch-activated", |
| Deactivated: "inline-swatch-deactivated", |
| }; |