Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 78 additions & 17 deletions src/LiveDevelopment/BrowserScripts/RemoteFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
const SHARED_STATE = {
__description: "Use this to keep shared state for Live Preview Edit instead of window.*",
_suppressDOMEditDismissal: false,
_suppressDOMEditDismissalTimeout: null
_suppressDOMEditDismissalTimeout: null,
_boxModelHighlightHidden: false
};

let _hoverHighlight;
Expand Down Expand Up @@ -190,7 +191,10 @@
disableHoverListeners: disableHoverListeners,
enableHoverListeners: enableHoverListeners,
redrawHighlights: redrawHighlights,
redrawEverything: redrawEverything
redrawEverything: redrawEverything,
getTreePath: _getTreePath,
getElementByTreePath: _getElementByTreePath,
getSourceChildren: _instrumentedChildren
};

/**
Expand Down Expand Up @@ -446,15 +450,17 @@
s.backgroundColor = color;
}

// Padding region
const padColor = COLORS.highlightPadding;
// Padding region. Rects stay in place when hidden, only their fill goes away,
// so nothing has to be rebuilt when they come back.
const boxModelHidden = SHARED_STATE._boxModelHighlightHidden;
const padColor = boxModelHidden ? "transparent" : COLORS.highlightPadding;
setRect(refs.padTop, paddingBox.left, paddingBox.top, paddingBox.width, pt, padColor);
setRect(refs.padBottom, paddingBox.left, contentBox.top + contentBox.height, paddingBox.width, pb, padColor);
setRect(refs.padLeft, paddingBox.left, contentBox.top, pl, contentBox.height, padColor);
setRect(refs.padRight, contentBox.left + contentBox.width, contentBox.top, pr, contentBox.height, padColor);

// Margin region
const margColor = COLORS.highlightMargin;
const margColor = boxModelHidden ? "transparent" : COLORS.highlightMargin;
setRect(refs.marTop, marginBox.left, marginBox.top, marginBox.width, mt, margColor);
setRect(refs.marBottom, marginBox.left, borderBox.top + borderBox.height, marginBox.width, mb, margColor);
setRect(refs.marLeft, marginBox.left, borderBox.top, ml, borderBox.height, margColor);
Expand Down Expand Up @@ -1054,6 +1060,25 @@
return results && results[0];
};

// True for elements Phoenix adds to the page itself, like the tool boxes and
// the highlight overlays. They are not part of the user's source file.
function _isPhoenixInternalNode(node) {
return !!node && node.nodeType === Node.ELEMENT_NODE &&
(node.hasAttribute(GLOBALS.PHCODE_INTERNAL_ATTR) ||
node.className === GLOBALS.HIGHLIGHT_CLASSNAME);
}

/** The first of the Phoenix elements sitting at the end of `parent`, else null. */
function _firstTrailingInternalNode(parent) {
let node = parent.lastChild;
let first = null;
while (_isPhoenixInternalNode(node)) {
first = node;
node = node.previousSibling;
}
return first;
}

/**
* @private
* Insert a new child element
Expand All @@ -1068,7 +1093,12 @@
if (edit.firstChild) {
before = targetElement.firstChild;
} else if (edit.lastChild) {
after = targetElement.lastChild;
// Phoenix's tool boxes are the last children of <body>, so appending
// here would put the new element after them, in the wrong place.
before = _firstTrailingInternalNode(targetElement);
if (!before) {
after = targetElement.lastChild;
}
}

if (before) {
Expand Down Expand Up @@ -1479,6 +1509,9 @@
_pendingHoverRAF = null;
}

// the selection is gone, so a popover can no longer turn the fills back on
SHARED_STATE._boxModelHighlightHidden = false;

// Highlight.clear() removes all overlay divs (outline + margin/padding rects)
hideHighlight();

Expand All @@ -1492,27 +1525,38 @@
}
}

// Only the children that came from the source file. Phoenix's own elements have
// no data-brackets-id, and counting them would shift the tree path indexes.
function _instrumentedChildren(parent) {
const result = [];
const children = (parent && parent.children) || [];

Check warning on line 1532 in src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ-uCfPwIE5X13WW_RCr&open=AZ-uCfPwIE5X13WW_RCr&pullRequest=3055
for (let i = 0; i < children.length; i++) {
if (children[i].hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR)) {
result.push(children[i]);
}
}

Check warning on line 1537 in src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Expected a `for-of` loop instead of a `for` loop with this simple iteration.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ-uCfPwIE5X13WW_RCs&open=AZ-uCfPwIE5X13WW_RCs&pullRequest=3055
return result;
}

/**
* Compute the tree path of an element as an array of child indices
* from <html> down. Used to re-locate the element after re-instrumentation
* when data-brackets-id changes and text matching is ambiguous.
* E.g. [1, 0, 0, 1] means html > 2nd child > 1st child > 1st child > 2nd child.
* @return {?Array.<number>} null if the element did not come from the source file.
*/
function _getTreePath(element) {
const path = [];
let el = element;
while (el && el.parentElement) {
const parent = el.parentElement;
const children = parent.children;
for (let i = 0; i < children.length; i++) {
if (children[i] === el) {
path.unshift(i);
break;
}
const index = _instrumentedChildren(el.parentElement).indexOf(el);
if (index === -1) {
return null;
}
el = parent;
path.unshift(index);
el = el.parentElement;
}
return path;
return path.length ? path : null;
}

/**
Expand All @@ -1521,10 +1565,11 @@
function _getElementByTreePath(path) {
let el = document.documentElement;
for (let i = 0; i < path.length; i++) {
if (!el || !el.children || !el.children[path[i]]) {
const siblings = _instrumentedChildren(el);
if (!siblings[path[i]]) {
return null;
}
el = el.children[path[i]];
el = siblings[path[i]];
}
return el;
}
Expand Down Expand Up @@ -1727,6 +1772,21 @@
}
}

/**
* Hide just the margin/padding fills of the selected element highlight, keeping
* the outline and the selection itself. Used while editing paint properties like
* background color, where the fills sit on top of what the user is changing.
* @param {Boolean} hidden
*/
function setBoxModelHighlightHidden(hidden) {
hidden = !!hidden;
if (SHARED_STATE._boxModelHighlightHidden === hidden) {
return;
}
SHARED_STATE._boxModelHighlightHidden = hidden;
redrawHighlights();
}

let customReturns = {};
// only apis that needs to be called from phoenix js layer should be customReturns. APis that are shared within
// the remote function context only should not be in customReturns and should be in
Expand All @@ -1750,6 +1810,7 @@
"getHighlightTrackingElement": getHighlightTrackingElement,
"getHighlightStyle": getHighlightStyle,
"setHotCornerHidden": setHotCornerHidden,
"setBoxModelHighlightHidden": setBoxModelHighlightHidden,
"clearHoverState": _clearHoverState
};

Expand Down
1 change: 1 addition & 0 deletions src/nls/root/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ define({
"LIVE_DEV_STYLER_BACKGROUND": "Background color",
"LIVE_DEV_STYLER_TEXT_COLOR": "Text color",
"LIVE_DEV_STYLER_FONT": "Font family",
"LIVE_DEV_STYLER_FONT_DEFAULT": "Default",
"LIVE_DEV_STYLER_SIZE": "Font size",
"LIVE_DEV_STYLER_WEIGHT": "Font weight",
"LIVE_DEV_STYLER_ALIGN": "Text alignment",
Expand Down
Loading