diff --git a/docs/API-Reference/view/PanelView.md b/docs/API-Reference/view/PanelView.md index 1a7cce00b..5917d6a51 100644 --- a/docs/API-Reference/view/PanelView.md +++ b/docs/API-Reference/view/PanelView.md @@ -171,6 +171,18 @@ The editor holder element, passed from WorkspaceManager ## \_recomputeLayout : function recomputeLayout callback from WorkspaceManager +**Kind**: global variable + + +## \_defaultPanelId : string \| null +The default/quick-access panel ID + +**Kind**: global variable + + +## \_$addBtn : jQueryObject +The "+" button inside the tab overflow area + **Kind**: global variable @@ -205,10 +217,16 @@ during drag without being overly sensitive. Minimum panel height (matches Resizer minSize) used as a floor when computing a sensible restore height. +**Kind**: global constant + + +## PREF\_BOTTOM\_PANEL\_MAXIMIZED +Preference key for persisting the maximize state across reloads. + **Kind**: global constant -## init($container, $tabBar, $tabsOverflow, $editorHolder, recomputeLayoutFn) +## init($container, $tabBar, $tabsOverflow, $editorHolder, recomputeLayoutFn, defaultPanelId) Initializes the PanelView module with references to the bottom panel container DOM elements. Called by WorkspaceManager during htmlReady. @@ -221,6 +239,7 @@ Called by WorkspaceManager during htmlReady. | $tabsOverflow | jQueryObject | The scrollable area holding tab elements. | | $editorHolder | jQueryObject | The editor holder element (for maximize height calculation). | | recomputeLayoutFn | function | Callback to trigger workspace layout recomputation. | +| defaultPanelId | string | The ID of the default/quick-access panel. | diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index cf054caa3..f2fbefc91 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -41,19 +41,12 @@ function RemoteFunctions(config = {}) { // Expose the currently selected element globally for external access window.__current_ph_lp_selected = null; - var req, timeout; - function animateHighlight(time) { - if(req) { - window.cancelAnimationFrame(req); - window.clearTimeout(timeout); - } - req = window.requestAnimationFrame(redrawHighlights); - - timeout = setTimeout(function () { - window.cancelAnimationFrame(req); - req = null; - }, time * 1000); - } + const COLORS = { + highlightPadding: "rgba(147, 196, 125, 0.55)", + highlightMargin: "rgba(246, 178, 107, 0.66)", + outlineEditable: "#4285F4", + outlineNonEditable: "#3C3F41" + }; // the following fucntions can be in the handler and live preview will call those functions when the below // events happen @@ -181,7 +174,8 @@ function RemoteFunctions(config = {}) { handleElementClick: handleElementClick, cleanupPreviousElementState: cleanupPreviousElementState, disableHoverListeners: disableHoverListeners, - enableHoverListeners: enableHoverListeners + enableHoverListeners: enableHoverListeners, + redrawHighlights: redrawHighlights }; /** @@ -265,310 +259,141 @@ function RemoteFunctions(config = {}) { return element.offsetTop + (element.offsetParent ? getDocumentOffsetTop(element.offsetParent) : 0); } - function Highlight(color, trigger) { - this.color = color; + function Highlight(trigger) { this.trigger = !!trigger; this.elements = []; this.selector = ""; + this._divs = []; } Highlight.prototype = { - _elementExists: function (element) { - var i; - for (i in this.elements) { - if (this.elements[i] === element) { - return true; - } - } - return false; - }, - _makeHighlightDiv: function (element, doAnimation) { - const remoteHighlight = { - animateStartValue: { - "background-color": "rgba(0, 162, 255, 0.5)", - "opacity": 0 - }, - animateEndValue: { - "background-color": "rgba(0, 162, 255, 0)", - "opacity": 0.6 - }, - paddingStyling: { - "background-color": "rgba(200, 249, 197, 0.7)" - }, - marginStyling: { - "background-color": "rgba(249, 204, 157, 0.7)" - }, - borderColor: "rgba(200, 249, 197, 0.85)", - showPaddingMargin: true - }; - var elementBounds = element.getBoundingClientRect(), - highlightDiv = window.document.createElement("div"), - elementStyling = window.getComputedStyle(element), - transitionDuration = parseFloat(elementStyling.getPropertyValue('transition-duration')), - animationDuration = parseFloat(elementStyling.getPropertyValue('animation-duration')); - - highlightDiv.trackingElement = element; // save which node are we highlighting - - if (doAnimation) { - if (transitionDuration) { - animateHighlight(transitionDuration); - } - - if (animationDuration) { - animateHighlight(animationDuration); - } - } - - // Don't highlight elements with 0 width & height - if (elementBounds.width === 0 && elementBounds.height === 0) { - return; - } - - var realElBorder = { - right: elementStyling.getPropertyValue('border-right-width'), - left: elementStyling.getPropertyValue('border-left-width'), - top: elementStyling.getPropertyValue('border-top-width'), - bottom: elementStyling.getPropertyValue('border-bottom-width') - }; - - var borderBox = elementStyling.boxSizing === 'border-box'; - - var innerWidth = parseFloat(elementStyling.width), - innerHeight = parseFloat(elementStyling.height), - outerHeight = innerHeight, - outerWidth = innerWidth; - - if (!borderBox) { - innerWidth += parseFloat(elementStyling.paddingLeft) + parseFloat(elementStyling.paddingRight); - innerHeight += parseFloat(elementStyling.paddingTop) + parseFloat(elementStyling.paddingBottom); - outerWidth = innerWidth + parseFloat(realElBorder.right) + - parseFloat(realElBorder.left), - outerHeight = innerHeight + parseFloat(realElBorder.bottom) + parseFloat(realElBorder.top); - } - - - var visualisations = { - horizontal: "left, right", - vertical: "top, bottom" - }; - - var drawPaddingRect = function (side) { - var elStyling = {}; - - if (visualisations.horizontal.indexOf(side) >= 0) { - elStyling["width"] = elementStyling.getPropertyValue("padding-" + side); - elStyling["height"] = innerHeight + "px"; - elStyling["top"] = 0; - - if (borderBox) { - elStyling["height"] = - innerHeight - parseFloat(realElBorder.top) - parseFloat(realElBorder.bottom) + "px"; - } - } else { - elStyling["height"] = elementStyling.getPropertyValue("padding-" + side); - elStyling["width"] = innerWidth + "px"; - elStyling["left"] = 0; - - if (borderBox) { - elStyling["width"] = - innerWidth - parseFloat(realElBorder.left) - parseFloat(realElBorder.right) + "px"; - } - } - - elStyling[side] = 0; - elStyling["position"] = "absolute"; - - return elStyling; - }; - - var drawMarginRect = function (side) { - var elStyling = {}; - - var margin = []; - margin["right"] = parseFloat(elementStyling.getPropertyValue("margin-right")); - margin["top"] = parseFloat(elementStyling.getPropertyValue("margin-top")); - margin["bottom"] = parseFloat(elementStyling.getPropertyValue("margin-bottom")); - margin["left"] = parseFloat(elementStyling.getPropertyValue("margin-left")); - - if (visualisations["horizontal"].indexOf(side) >= 0) { - elStyling["width"] = elementStyling.getPropertyValue("margin-" + side); - elStyling["height"] = outerHeight + margin["top"] + margin["bottom"] + "px"; - elStyling["top"] = "-" + (margin["top"] + parseFloat(realElBorder.top)) + "px"; - } else { - elStyling["height"] = elementStyling.getPropertyValue("margin-" + side); - elStyling["width"] = outerWidth + "px"; - elStyling["left"] = "-" + realElBorder.left; - } - - elStyling[side] = "-" + (margin[side] + parseFloat(realElBorder[side])) + "px"; - elStyling["position"] = "absolute"; - - return elStyling; - }; - - var setVisibility = function (el) { - if ( - !remoteHighlight.showPaddingMargin || - parseInt(el.height, 10) <= 0 || - parseInt(el.width, 10) <= 0 - ) { - el.display = 'none'; - } else { - el.display = 'block'; - } - }; - - var paddingVisualisations = [ - drawPaddingRect("top"), - drawPaddingRect("right"), - drawPaddingRect("bottom"), - drawPaddingRect("left") - ]; - - var marginVisualisations = [ - drawMarginRect("top"), - drawMarginRect("right"), - drawMarginRect("bottom"), - drawMarginRect("left") - ]; - - var setupVisualisations = function (arr, visualConfig) { - var i; - for (i = 0; i < arr.length; i++) { - setVisibility(arr[i]); - - // Applies to every visualisationElement (padding or margin div) - arr[i]["transform"] = "none"; - var el = window.document.createElement("div"), - styles = Object.assign({}, visualConfig, arr[i]); - - _setStyleValues(styles, el.style); - - highlightDiv.appendChild(el); - } - }; - - setupVisualisations( - marginVisualisations, - remoteHighlight.marginStyling - ); - setupVisualisations( - paddingVisualisations, - remoteHighlight.paddingStyling - ); - - highlightDiv.className = GLOBALS.HIGHLIGHT_CLASSNAME; - - var offset = LivePreviewView.screenOffset(element); - - // some code to find element left/top was removed here. This seems to be relevant to box model - // live highlights. firether reading: https://github.com/adobe/brackets/pull/13357/files - // we removed this in phoenix because it was throwing the rendering of live highlight boxes in phonix - // default project at improper places. Some other cases might fail as the above code said they - // introduces that removed computation for fixing some box-model regression. If you are here to fix a - // related bug, check history of this changes in git. - - var stylesToSet = { - "left": offset.left + "px", - "top": offset.top + "px", - "width": elementBounds.width + "px", - "height": elementBounds.height + "px", - "z-index": 2147483645, - "margin": 0, - "padding": 0, - "position": "absolute", - "pointer-events": "none", - "box-shadow": "0 0 1px #fff", - "box-sizing": elementStyling.getPropertyValue('box-sizing'), - "border-right": elementStyling.getPropertyValue('border-right'), - "border-left": elementStyling.getPropertyValue('border-left'), - "border-top": elementStyling.getPropertyValue('border-top'), - "border-bottom": elementStyling.getPropertyValue('border-bottom'), - "border-color": remoteHighlight.borderColor - }; - - var mergedStyles = Object.assign({}, stylesToSet, remoteHighlight.stylesToSet); - - var animateStartValues = remoteHighlight.animateStartValue; - - var animateEndValues = remoteHighlight.animateEndValue; - - var transitionValues = { - "transition-property": "opacity, background-color, transform", - "transition-duration": "300ms, 2.3s" - }; - - function _setStyleValues(styleValues, obj) { - var prop; - - for (prop in styleValues) { - obj.setProperty(prop, styleValues[prop]); - } - } - - _setStyleValues(mergedStyles, highlightDiv.style); - _setStyleValues( - doAnimation ? animateStartValues : animateEndValues, - highlightDiv.style - ); - - - if (doAnimation) { - _setStyleValues(transitionValues, highlightDiv.style); - - window.setTimeout(function () { - _setStyleValues(animateEndValues, highlightDiv.style); - }, 20); - } - - window.document.body.appendChild(highlightDiv); - }, - - add: function (element, doAnimation) { - if (this._elementExists(element) || element === window.document) { + add: function (element) { + if (this.elements.includes(element) || element === window.document) { return; } if (this.trigger) { _trigger(element, "highlight", 1); } - this.elements.push(element); - this._makeHighlightDiv(element, doAnimation); + this._createOverlay(element); }, clear: function () { - var i, highlights = window.document.querySelectorAll("." + GLOBALS.HIGHLIGHT_CLASSNAME), - body = window.document.body; - - for (i = 0; i < highlights.length; i++) { - body.removeChild(highlights[i]); - } - - for (i = 0; i < this.elements.length; i++) { - if (this.trigger) { - _trigger(this.elements[i], "highlight", 0); + this._divs.forEach(function (div) { + if (div.parentNode) { + div.parentNode.removeChild(div); } - clearElementHoverHighlight(this.elements[i]); - } + }); + this._divs = []; + if (this.trigger) { + this.elements.forEach(function (el) { + _trigger(el, "highlight", 0); + }); + } this.elements = []; }, redraw: function () { - var i, highlighted; + const elements = this.selector + ? Array.from(window.document.querySelectorAll(this.selector)) + : this.elements.slice(); + this.clear(); + elements.forEach(function (el) { this.add(el); }, this); + }, - // When redrawing a selector-based highlight, run a new selector - // query to ensure we have the latest set of elements to highlight. - if (this.selector) { - highlighted = window.document.querySelectorAll(this.selector); + _createOverlay: function (element) { + const bounds = element.getBoundingClientRect(); + if (bounds.width === 0 && bounds.height === 0) { return; } + + const cs = window.getComputedStyle(element); + const div = window.document.createElement("div"); + div.className = GLOBALS.HIGHLIGHT_CLASSNAME; + div.trackingElement = element; + + // Parse box model values + const bt = parseFloat(cs.borderTopWidth) || 0, + br = parseFloat(cs.borderRightWidth) || 0, + bb = parseFloat(cs.borderBottomWidth) || 0, + bl = parseFloat(cs.borderLeftWidth) || 0; + const pt = parseFloat(cs.paddingTop) || 0, + pr = parseFloat(cs.paddingRight) || 0, + pb = parseFloat(cs.paddingBottom) || 0, + pl = parseFloat(cs.paddingLeft) || 0; + const mt = parseFloat(cs.marginTop) || 0, + mr = parseFloat(cs.marginRight) || 0, + mb = parseFloat(cs.marginBottom) || 0, + ml = parseFloat(cs.marginLeft) || 0; + + const isBorderBox = cs.boxSizing === "border-box"; + const w = parseFloat(cs.width) || 0; + const h = parseFloat(cs.height) || 0; + + // Dimensions inside border + let innerW, innerH; + if (isBorderBox) { + innerW = w - bl - br; + innerH = h - bt - bb; } else { - highlighted = this.elements.slice(0); + innerW = w + pl + pr; + innerH = h + pt + pb; } - - this.clear(); - for (i = 0; i < highlighted.length; i++) { - this.add(highlighted[i], false); + const contentH = innerH - pt - pb; + const outerW = innerW + bl + br; + const outerH = innerH + bt + bb; + + // Position the overlay to match the element + const offset = LivePreviewView.screenOffset(element); + const divStyle = div.style; + divStyle.position = "absolute"; + divStyle.left = offset.left + "px"; + divStyle.top = offset.top + "px"; + divStyle.width = bounds.width + "px"; + divStyle.height = bounds.height + "px"; + divStyle.zIndex = 2147483645; + divStyle.margin = "0"; + divStyle.padding = "0"; + divStyle.pointerEvents = "none"; + divStyle.boxSizing = cs.boxSizing; + divStyle.borderTopWidth = bt + "px"; + divStyle.borderRightWidth = br + "px"; + divStyle.borderBottomWidth = bb + "px"; + divStyle.borderLeftWidth = bl + "px"; + divStyle.borderStyle = "solid"; + divStyle.borderColor = "transparent"; + + // Helper to create a colored rect + function makeRect(styles, color) { + if (parseFloat(styles.width) <= 0 || parseFloat(styles.height) <= 0) { return; } + const r = window.document.createElement("div"); + r.style.position = "absolute"; + r.style.backgroundColor = color; + r.style.transform = "none"; + for (const prop in styles) { + r.style[prop] = styles[prop]; + } + div.appendChild(r); } + + // Padding rects (top/bottom full width, left/right content height) + const padColor = COLORS.highlightPadding; + makeRect({ top: "0", left: "0", width: innerW + "px", height: pt + "px" }, padColor); + makeRect({ bottom: "0", left: "0", width: innerW + "px", height: pb + "px" }, padColor); + makeRect({ top: pt + "px", left: "0", width: pl + "px", height: contentH + "px" }, padColor); + makeRect({ top: pt + "px", right: "0", width: pr + "px", height: contentH + "px" }, padColor); + + // Margin rects (top/bottom element width, left/right full height) + const margColor = COLORS.highlightMargin; + const mTop = -(mt + bt) + "px"; + const mBot = -(mb + bb) + "px"; + const fullH = (outerH + mt + mb) + "px"; + makeRect({ top: mTop, left: -bl + "px", width: outerW + "px", height: mt + "px" }, margColor); + makeRect({ bottom: mBot, left: -bl + "px", width: outerW + "px", height: mb + "px" }, margColor); + makeRect({ top: mTop, left: -(ml + bl) + "px", width: ml + "px", height: fullH }, margColor); + makeRect({ top: mTop, right: -(mr + br) + "px", width: mr + "px", height: fullH }, margColor); + + window.document.body.appendChild(div); + this._divs.push(div); } }; @@ -608,19 +433,27 @@ function RemoteFunctions(config = {}) { // if _hoverHighlight is uninitialized, initialize it if (!_hoverHighlight && shouldShowHighlightOnHover()) { - _hoverHighlight = new Highlight("#c8f9c5", true); + _hoverHighlight = new Highlight(true); } // this is to check the user's settings, if they want to show the elements highlights on hover or click if (_hoverHighlight && shouldShowHighlightOnHover()) { + _hoverHighlight.elements.forEach(clearElementHoverHighlight); _hoverHighlight.clear(); - // Store original outline to restore on hover out, then apply a blue border - element._originalHoverOutline = element.style.outline; - const outlineColor = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) ? "#4285F4" : "#3C3F41"; - element.style.outline = `1px solid ${outlineColor}`; - - _hoverHighlight.add(element, false); + // Skip hover outline and overlay for the currently click-selected element. + // It already has its own outline and overlay from the click/selection flow. + // Adding hover state on top would corrupt _originalHoverOutline (it would capture + // the click outline instead of the true original) and stack duplicate overlays. + if (element !== previouslySelectedElement) { + // Store original outline to restore on hover out, then apply a border + element._originalHoverOutline = element.style.outline; + const isEditable = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; + element.style.outline = `1px solid ${outlineColor}`; + + _hoverHighlight.add(element); + } // create the info box for the hovered element const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox"); @@ -674,7 +507,7 @@ function RemoteFunctions(config = {}) { // this should also be there when users are in highlight mode scrollElementToViewPort(element); - if(!LivePreviewView.isElementInspectable(element)) { + if(!LivePreviewView.isElementInspectable(element, true)) { return false; } @@ -698,14 +531,15 @@ function RemoteFunctions(config = {}) { } element._originalOutline = element.style.outline; - const outlineColor = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) ? "#4285F4" : "#3C3F41"; + const isEditable = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; element.style.outline = `1px solid ${outlineColor}`; if (!_clickHighlight) { - _clickHighlight = new Highlight("#cfc"); + _clickHighlight = new Highlight(); } _clickHighlight.clear(); - _clickHighlight.add(element, true); + _clickHighlight.add(element); previouslySelectedElement = element; window.__current_ph_lp_selected = element; @@ -812,10 +646,13 @@ function RemoteFunctions(config = {}) { clearCssSelectorHighlight(); // Create new temporary highlight for all matching elements - _cssSelectorHighlight = new Highlight("#cfc"); - for (var i = 0; i < nodes.length; i++) { - if (LivePreviewView.isElementInspectable(nodes[i], true) && nodes[i].nodeType === Node.ELEMENT_NODE) { - _cssSelectorHighlight.add(nodes[i], true); + // Skip the selected element since it already has a click highlight + _cssSelectorHighlight = new Highlight(); + for (let i = 0; i < nodes.length; i++) { + if (nodes[i] !== previouslySelectedElement && + LivePreviewView.isElementInspectable(nodes[i], true) && + nodes[i].nodeType === Node.ELEMENT_NODE) { + _cssSelectorHighlight.add(nodes[i]); } } _cssSelectorHighlight.selector = rule; @@ -831,6 +668,7 @@ function RemoteFunctions(config = {}) { _clickHighlight = null; } if (_hoverHighlight) { + _hoverHighlight.elements.forEach(clearElementHoverHighlight); _hoverHighlight.clear(); _hoverHighlight = null; } @@ -840,13 +678,13 @@ function RemoteFunctions(config = {}) { // highlight an element function highlight(element, clear) { if (!_clickHighlight) { - _clickHighlight = new Highlight("#cfc"); + _clickHighlight = new Highlight(); } if (clear) { _clickHighlight.clear(); } if (LivePreviewView.isElementInspectable(element, true) && element.nodeType === Node.ELEMENT_NODE) { - _clickHighlight.add(element, true); + _clickHighlight.add(element); } } @@ -1263,7 +1101,8 @@ function RemoteFunctions(config = {}) { } else { // Suppression is active - re-apply outline since attrChange may have wiped it if (previouslySelectedElement && previouslySelectedElement.isConnected) { - const outlineColor = previouslySelectedElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) ? "#4285F4" : "#3C3F41"; + const isEditable = previouslySelectedElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; previouslySelectedElement.style.outline = `1px solid ${outlineColor}`; } } @@ -1318,6 +1157,10 @@ function RemoteFunctions(config = {}) { */ function cleanupPreviousElementState() { if (previouslySelectedElement) { + // Safety net: clear any stale hover outline tracking before hideHighlight runs. + // This prevents clearElementHoverHighlight from re-applying a captured click outline + // in edge cases where _originalHoverOutline was set on the selected element. + delete previouslySelectedElement._originalHoverOutline; if (previouslySelectedElement._originalOutline !== undefined) { previouslySelectedElement.style.outline = previouslySelectedElement._originalOutline; } else { @@ -1380,11 +1223,8 @@ function RemoteFunctions(config = {}) { }); if (config.mode === 'edit') { - // Initialize hover highlight with Chrome-like colors - _hoverHighlight = new Highlight("#c8f9c5", true); // Green similar to Chrome's padding color - - // Initialize click highlight with animation - _clickHighlight = new Highlight("#cfc", true); // Light green for click highlight + _hoverHighlight = new Highlight(true); + _clickHighlight = new Highlight(true); // register the event handlers enableHoverListeners(); @@ -1449,7 +1289,16 @@ function RemoteFunctions(config = {}) { "dismissUIAndCleanupState": dismissUIAndCleanupState, "escapeKeyPressInEditor": _handleEscapeKeyPress, "getMode": function() { return config.mode; }, - "suppressDOMEditDismissal": suppressDOMEditDismissal + "suppressDOMEditDismissal": suppressDOMEditDismissal, + "setHotCornerHidden": function(hidden) { + if (SHARED_STATE._hotCorner && SHARED_STATE._hotCorner.hotCorner) { + if (hidden) { + SHARED_STATE._hotCorner.hotCorner.classList.add('hc-hidden'); + } else { + SHARED_STATE._hotCorner.hotCorner.classList.remove('hc-hidden'); + } + } + } }; // the below code comment is replaced by added scripts for extensibility diff --git a/src/LiveDevelopment/LivePreviewConstants.js b/src/LiveDevelopment/LivePreviewConstants.js index ecfc9a95d..928b326b6 100644 --- a/src/LiveDevelopment/LivePreviewConstants.js +++ b/src/LiveDevelopment/LivePreviewConstants.js @@ -41,4 +41,5 @@ define(function main(require, exports, module) { exports.HIGHLIGHT_CLICK = "click"; exports.PREFERENCE_SHOW_RULER_LINES = "livePreviewShowMeasurements"; + exports.PREFERENCE_SHOW_SPACING_HANDLES = "livePreviewShowSpacingHandles"; }); diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js index 60d4caebf..87a3a5c91 100644 --- a/src/LiveDevelopment/main.js +++ b/src/LiveDevelopment/main.js @@ -70,6 +70,7 @@ define(function main(require, exports, module) { mode: LIVE_HIGHLIGHT_MODE, // will be updated when we fetch entitlements elemHighlights: CONSTANTS.HIGHLIGHT_HOVER, // default value, this will get updated when the extension loads showRulerLines: false, // default value, this will get updated when the extension loads + showSpacingHandles: true, // default value, this will get updated when the extension loads isPaidUser: false, // will be updated when we fetch entitlements isLoggedIn: false, // will be updated when we fetch entitlements hasLiveEditCapability: false // handled inside _liveEditCapabilityChanged function @@ -324,6 +325,13 @@ define(function main(require, exports, module) { MultiBrowserLiveDev.updateConfig(config); } + function updateSpacingHandlesConfig() { + const prefValue = PreferencesManager.get(CONSTANTS.PREFERENCE_SHOW_SPACING_HANDLES); + const config = MultiBrowserLiveDev.getConfig(); + config.showSpacingHandles = prefValue !== false; + MultiBrowserLiveDev.updateConfig(config); + } + EventDispatcher.makeEventDispatcher(exports); // private api @@ -347,6 +355,7 @@ define(function main(require, exports, module) { exports.setLivePreviewTransportBridge = setLivePreviewTransportBridge; exports.updateElementHighlightConfig = updateElementHighlightConfig; exports.updateRulerLinesConfig = updateRulerLinesConfig; + exports.updateSpacingHandlesConfig = updateSpacingHandlesConfig; exports.getConnectionIds = MultiBrowserLiveDev.getConnectionIds; exports.getLivePreviewDetails = MultiBrowserLiveDev.getLivePreviewDetails; exports.hideHighlight = MultiBrowserLiveDev.hideHighlight; diff --git a/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css b/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css index 6e3536afd..0bda851c2 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css +++ b/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css @@ -80,13 +80,9 @@ } #live-preview-plugin-toolbar:hover .lp-settings-icon { - display: flex; - align-items: center; - color: #a0a0a0; opacity: 1; visibility: visible; transition: unset; - padding-left: 7.5px; } .live-preview-settings input.error, .live-preview-settings input:focus.error{ @@ -98,25 +94,37 @@ .lp-settings-icon { opacity: 0; color: #a0a0a0; - display: flex; - align-items: center; visibility: hidden; transition: opacity 1s, visibility 0s linear 1s; /* Fade-out effect */ - padding-left: 7.5px; + width: 30px; + height: 22px; + padding: 1px 6px; + flex-shrink: 0; + margin-top: 3.5px; } .lp-device-size-icon { - color: #a0a0a0; + min-width: fit-content; display: flex; align-items: center; - padding-left: 7.5px; - margin-right: 7.5px; + margin: 3.5px 4px 0 3px; + cursor: pointer; + background: #3C3F41; + box-shadow: none; + border: 1px solid #3C3F41; + box-sizing: border-box; + color: #a0a0a0; + padding: 0 0.35em; +} + +.lp-device-size-icon:hover { + border: 1px solid rgba(0, 0, 0, 0.24) !important; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12) !important; } #deviceSizeBtn.btn-dropdown::after { position: static; - margin-top: 2px; - margin-left: 3px; + margin-left: 5px; } .device-size-item-icon { @@ -154,7 +162,7 @@ min-width: fit-content; display: flex; align-items: center; - margin: 3px 4px 0 3px; + margin: 3.5px 4px 0 3px; max-width: 80%; text-overflow: ellipsis; overflow: hidden; diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js index 6b63e3080..c33c959ce 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/main.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js @@ -110,6 +110,12 @@ define(function (require, exports, module) { description: Strings.LIVE_DEV_SETTINGS_SHOW_RULER_LINES_PREFERENCE }); + // live preview spacing handles preference (show/hide spacing handles on element selection) + const PREFERENCE_SHOW_SPACING_HANDLES = CONSTANTS.PREFERENCE_SHOW_SPACING_HANDLES; + PreferencesManager.definePreference(PREFERENCE_SHOW_SPACING_HANDLES, "boolean", true, { + description: Strings.LIVE_DEV_SETTINGS_SHOW_SPACING_HANDLES_PREFERENCE + }); + const LIVE_PREVIEW_PANEL_ID = "live-preview-panel"; const LIVE_PREVIEW_IFRAME_ID = "panel-live-preview-frame"; const LIVE_PREVIEW_IFRAME_HTML = ` @@ -338,6 +344,7 @@ define(function (require, exports, module) { items.push("---"); items.push(Strings.LIVE_PREVIEW_EDIT_HIGHLIGHT_ON); items.push(Strings.LIVE_PREVIEW_SHOW_RULER_LINES); + items.push(Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES); } const currentMode = LiveDevelopment.getCurrentMode(); @@ -372,6 +379,12 @@ define(function (require, exports, module) { return `✓ ${Strings.LIVE_PREVIEW_SHOW_RULER_LINES}`; } return `${'\u00A0'.repeat(4)}${Strings.LIVE_PREVIEW_SHOW_RULER_LINES}`; + } else if (item === Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES) { + const isEnabled = PreferencesManager.get(PREFERENCE_SHOW_SPACING_HANDLES); + if(isEnabled) { + return `✓ ${Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES}`; + } + return `${'\u00A0'.repeat(4)}${Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES}`; } return item; }); @@ -429,6 +442,15 @@ define(function (require, exports, module) { const currentValue = PreferencesManager.get(PREFERENCE_SHOW_RULER_LINES); PreferencesManager.set(PREFERENCE_SHOW_RULER_LINES, !currentValue); return; // Don't dismiss highlights for this option + } else if (item === Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES) { + // Don't allow spacing handles toggle if edit features are not active + if (!isEditFeaturesActive) { + return; + } + // Toggle spacing handles on/off + const currentValue = PreferencesManager.get(PREFERENCE_SHOW_SPACING_HANDLES); + PreferencesManager.set(PREFERENCE_SHOW_SPACING_HANDLES, !currentValue); + return; // Don't dismiss highlights for this option } }); @@ -1227,10 +1249,14 @@ define(function (require, exports, module) { PreferencesManager.on("change", PREFERENCE_SHOW_RULER_LINES, function() { LiveDevelopment.updateRulerLinesConfig(); }); + PreferencesManager.on("change", PREFERENCE_SHOW_SPACING_HANDLES, function() { + LiveDevelopment.updateSpacingHandlesConfig(); + }); - // Initialize element highlight and ruler lines config on startup + // Initialize element highlight, ruler lines, and spacing handles config on startup LiveDevelopment.updateElementHighlightConfig(); LiveDevelopment.updateRulerLinesConfig(); + LiveDevelopment.updateSpacingHandlesConfig(); LiveDevelopment.openLivePreview(); LiveDevelopment.on(LiveDevelopment.EVENT_OPEN_PREVIEW_URL, _openLivePreviewURL); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 34ff0b665..2199d96b3 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -196,6 +196,14 @@ define({ "LIVE_DEV_MORE_OPTIONS_CUT": "Cut", "LIVE_DEV_MORE_OPTIONS_COPY": "Copy", "LIVE_DEV_MORE_OPTIONS_PASTE": "Paste", + "LIVE_DEV_INSERT_ELEMENT": "Insert Element", + "LIVE_DEV_INSERT_BEFORE": "Before", + "LIVE_DEV_INSERT_AFTER": "After", + "LIVE_DEV_INSERT_INSIDE": "Inside", + "LIVE_DEV_INSERT_WRAP": "Wrap", + "LIVE_DEV_INSERT_SEARCH_PLACEHOLDER": "Search elements\u2026", + "LIVE_DEV_INSERT_COMMON": "Common", + "LIVE_DEV_INSERT_NO_RESULTS": "No matching elements", "LIVE_DEV_IMAGE_GALLERY_USE_IMAGE": "Download image", "LIVE_DEV_IMAGE_GALLERY_SELECT_DOWNLOAD_FOLDER": "Choose image download folder", "LIVE_DEV_IMAGE_GALLERY_SEARCH_PLACEHOLDER": "Search images\u2026", @@ -232,6 +240,75 @@ define({ "LIVE_DEV_STYLES_PANEL_NO_STYLES": "No styles found", "LIVE_DEV_STYLES_PANEL_PROPERTY_PLACEHOLDER": "property", "LIVE_DEV_STYLES_PANEL_VALUE_PLACEHOLDER": "value", + "LIVE_DEV_STYLES_QS_COLORS": "Colors", + "LIVE_DEV_STYLES_QS_TEXT_COLOR_LABEL": "Text", + "LIVE_DEV_STYLES_QS_BG_COLOR_LABEL": "Background", + "LIVE_DEV_STYLES_QS_TYPOGRAPHY": "Typography", + "LIVE_DEV_STYLES_QS_SIZE": "Size", + "LIVE_DEV_STYLES_QS_SPACING": "Spacing", + "LIVE_DEV_STYLES_QS_SIZE_AND_SPACING": "Size & Spacing", + "LIVE_DEV_STYLES_QS_BORDER": "Border", + "LIVE_DEV_STYLES_QS_LAYOUT": "Layout", + "LIVE_DEV_STYLES_QS_EFFECTS": "Effects", + "LIVE_DEV_STYLES_QS_FONT_FAMILY": "Font", + "LIVE_DEV_STYLES_QS_FONT_SIZE": "Size", + "LIVE_DEV_STYLES_QS_FONT_WEIGHT": "Weight", + "LIVE_DEV_STYLES_QS_LINE_HEIGHT": "Line Height", + "LIVE_DEV_STYLES_QS_TEXT_COLOR": "Color", + "LIVE_DEV_STYLES_QS_TEXT_ALIGN": "Align", + "LIVE_DEV_STYLES_QS_LETTER_SPACING": "Spacing", + "LIVE_DEV_STYLES_QS_TEXT_DECORATION": "Decoration", + "LIVE_DEV_STYLES_QS_TEXT_TRANSFORM": "Transform", + "LIVE_DEV_STYLES_QS_BG_COLOR": "Color", + "LIVE_DEV_STYLES_QS_BG_IMAGE": "Image URL", + "LIVE_DEV_STYLES_QS_BG_SIZE": "Size", + "LIVE_DEV_STYLES_QS_WIDTH": "Width", + "LIVE_DEV_STYLES_QS_HEIGHT": "Height", + "LIVE_DEV_STYLES_QS_MIN_WIDTH": "Min W", + "LIVE_DEV_STYLES_QS_MAX_WIDTH": "Max W", + "LIVE_DEV_STYLES_QS_MIN_HEIGHT": "Min H", + "LIVE_DEV_STYLES_QS_MAX_HEIGHT": "Max H", + "LIVE_DEV_STYLES_QS_OVERFLOW": "Overflow", + "LIVE_DEV_STYLES_QS_MARGIN": "Margin", + "LIVE_DEV_STYLES_QS_PADDING": "Padding", + "LIVE_DEV_STYLES_QS_BORDER_WIDTH": "Width", + "LIVE_DEV_STYLES_QS_BORDER_STYLE": "Style", + "LIVE_DEV_STYLES_QS_BORDER_COLOR": "Color", + "LIVE_DEV_STYLES_QS_BORDER_RADIUS": "Radius", + "LIVE_DEV_STYLES_QS_DISPLAY": "Display", + "LIVE_DEV_STYLES_QS_POSITION": "Position", + "LIVE_DEV_STYLES_QS_FLEX_DIR": "Direction", + "LIVE_DEV_STYLES_QS_ALIGN_ITEMS": "Align", + "LIVE_DEV_STYLES_QS_JUSTIFY": "Justify", + "LIVE_DEV_STYLES_QS_DISTRIBUTE": "Distribute", + "LIVE_DEV_STYLES_QS_DISTRIBUTE_PACKED": "Packed", + "LIVE_DEV_STYLES_QS_DISTRIBUTE_BETWEEN": "Between", + "LIVE_DEV_STYLES_QS_DISTRIBUTE_AROUND": "Around", + "LIVE_DEV_STYLES_QS_DISTRIBUTE_EVENLY": "Evenly", + "LIVE_DEV_STYLES_QS_GAP": "Gap", + "LIVE_DEV_STYLES_QS_Z_INDEX": "Z-Index", + "LIVE_DEV_STYLES_QS_OPACITY": "Opacity", + "LIVE_DEV_STYLES_QS_BOX_SHADOW": "Box Shadow", + "LIVE_DEV_STYLES_QS_SELECTOR": "Selector", + "LIVE_DEV_STYLES_QS_ELEMENT_STYLE": "element.style", + "LIVE_DEV_STYLES_QS_SHOW_MORE": "Show More", + "LIVE_DEV_STYLES_QS_SHOW_LESS": "Show Less", + "LIVE_DEV_STYLES_TAB_QUICK": "Quick Styles", + "LIVE_DEV_STYLES_TAB_ADVANCED": "Advanced Styles", + "LIVE_DEV_STYLES_TAB_COMPUTED": "Computed", + "LIVE_DEV_STYLES_FILTER_ALL": "All", + "LIVE_DEV_STYLES_FILTER_LAYOUT": "Layout", + "LIVE_DEV_STYLES_FILTER_TYPOGRAPHY": "Typography", + "LIVE_DEV_STYLES_FILTER_COLOR": "Color", + "LIVE_DEV_STYLES_FILTER_EFFECTS": "Effects", + "LIVE_DEV_STYLES_FILTER_BOX_MODEL": "Box Model", + "LIVE_DEV_STYLES_COMPUTED_SEARCH": "Filter properties\u2026", + "LIVE_DEV_STYLES_COMPUTED_NO_RESULTS": "No results found", + "LIVE_DEV_STYLES_COMPUTED_USER_AGENT": "User Agent", + "LIVE_DEV_FORMAT_BOLD": "Bold", + "LIVE_DEV_FORMAT_ITALIC": "Italic", + "LIVE_DEV_FORMAT_UNDERLINE": "Underline", + "LIVE_DEV_FORMAT_STRIKETHROUGH": "Strikethrough", "LIVE_DEV_TOAST_NOT_EDITABLE": "Element not editable - generated by script", "LIVE_DEV_COPY_TOAST_MESSAGE": "Element copied. Use 'Paste' to add it after the selected element", "LIVE_DEV_TOAST_FIXED_ELEMENT_DISMISSED": "Element doesn't scroll with page - edit boxes hidden", @@ -255,6 +332,8 @@ define({ "LIVE_PREVIEW_MODE_EDIT": "Edit Mode", "LIVE_PREVIEW_EDIT_HIGHLIGHT_ON": "Inspect Element on Hover", "LIVE_PREVIEW_SHOW_RULER_LINES": "Show Measurements", + "LIVE_PREVIEW_SHOW_SPACING_HANDLES": "Show Spacing Handles", + "LIVE_DEV_SETTINGS_SHOW_SPACING_HANDLES_PREFERENCE": "Show spacing handles when elements are selected in live preview edit mode. Defaults to 'true'", "LIVE_PREVIEW_MODE_PREFERENCE": "'{0}' shows only the webpage, '{1}' connects the webpage to your code - click on elements to jump to their code and vice versa, '{2}' provides highlighting along with advanced element manipulation", "LIVE_PREVIEW_CONFIGURE_MODES": "Configure Live Preview Modes",