Skip to content

Commit 1967c56

Browse files
committed
refactor: move the edit options inside a dropdown
1 parent e93cffb commit 1967c56

File tree

1 file changed

+207
-11
lines changed

1 file changed

+207
-11
lines changed

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 207 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,6 +1495,12 @@ function RemoteFunctions(config = {}) {
14951495
<svg viewBox="0 0 20 16" fill="currentColor" width="17" height="17">
14961496
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
14971497
</svg>
1498+
`,
1499+
1500+
verticalEllipsis: `
1501+
<svg viewBox="0 0 24 24" fill="currentColor">
1502+
<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
1503+
</svg>
14981504
`
14991505
};
15001506

@@ -1606,14 +1612,8 @@ function RemoteFunctions(config = {}) {
16061612
<span data-action="delete" title="${config.strings.delete}">
16071613
${ICONS.trash}
16081614
</span>
1609-
<span data-action="cut" title='Cut'>
1610-
${ICONS.cut}
1611-
</span>
1612-
<span data-action="copy" title='Copy'>
1613-
${ICONS.copy}
1614-
</span>
1615-
<span data-action="paste" title='Paste'>
1616-
${ICONS.paste}
1615+
<span data-action="more-options" title='More Options'>
1616+
${ICONS.verticalEllipsis}
16171617
</span>
16181618
</div>`;
16191619

@@ -1719,9 +1719,20 @@ function RemoteFunctions(config = {}) {
17191719
event.preventDefault();
17201720
// data-action is to differentiate between the buttons (duplicate, delete, select-parent etc)
17211721
const action = event.currentTarget.getAttribute('data-action');
1722-
handleOptionClick(event, action, this.element);
1723-
if (action !== 'duplicate') {
1724-
this.remove();
1722+
1723+
if (action === 'more-options') {
1724+
// to toggle the dropdown on more options button click
1725+
if (_moreOptionsDropdown) {
1726+
_moreOptionsDropdown.remove();
1727+
} else {
1728+
_moreOptionsDropdown = new MoreOptionsDropdown(this.element, event.currentTarget);
1729+
}
1730+
} else {
1731+
handleOptionClick(event, action, this.element);
1732+
// as we don't want to remove the options box on duplicate button click
1733+
if (action !== 'duplicate') {
1734+
this.remove();
1735+
}
17251736
}
17261737
});
17271738
});
@@ -1738,6 +1749,179 @@ function RemoteFunctions(config = {}) {
17381749
}
17391750
};
17401751

1752+
/**
1753+
* the more options dropdown which appears when user clicks on the ellipsis button in the options box
1754+
*/
1755+
function MoreOptionsDropdown(targetElement, ellipsisButton) {
1756+
this.targetElement = targetElement;
1757+
this.ellipsisButton = ellipsisButton;
1758+
this.remove = this.remove.bind(this);
1759+
this.create();
1760+
}
1761+
1762+
MoreOptionsDropdown.prototype = {
1763+
_getDropdownPosition: function(dropdownWidth, dropdownHeight) {
1764+
const buttonBounds = this.ellipsisButton.getBoundingClientRect();
1765+
const viewportHeight = window.innerHeight;
1766+
1767+
let topPos, leftPos;
1768+
1769+
// Check if there's enough space below the button
1770+
const spaceBelow = viewportHeight - buttonBounds.bottom;
1771+
const spaceAbove = buttonBounds.top;
1772+
1773+
if (spaceBelow >= dropdownHeight + 6) {
1774+
// Show below the ellipsis button
1775+
topPos = buttonBounds.bottom + window.pageYOffset + 6;
1776+
} else if (spaceAbove >= dropdownHeight + 6) {
1777+
// Show above the ellipsis button
1778+
topPos = buttonBounds.top + window.pageYOffset - dropdownHeight - 6;
1779+
} else {
1780+
// Not enough space either way, default to below
1781+
topPos = buttonBounds.bottom + window.pageYOffset + 6;
1782+
}
1783+
1784+
// Align dropdown to the right edge of the button
1785+
leftPos = buttonBounds.right + window.pageXOffset - dropdownWidth;
1786+
1787+
// Make sure dropdown doesn't go off the left edge of viewport
1788+
if (leftPos < 0) {
1789+
leftPos = buttonBounds.left + window.pageXOffset;
1790+
}
1791+
1792+
return {topPos: topPos, leftPos: leftPos};
1793+
},
1794+
1795+
_style: function() {
1796+
this.body = window.document.createElement("div");
1797+
this.body.setAttribute("data-phcode-internal-c15r5a9", "true");
1798+
1799+
const shadow = this.body.attachShadow({ mode: "open" });
1800+
1801+
let content = `
1802+
<div class="more-options-dropdown">
1803+
<div class="dropdown-item" data-action="cut">
1804+
<span class="item-icon">${ICONS.cut}</span>
1805+
<span class="item-label">Cut</span>
1806+
</div>
1807+
<div class="dropdown-item" data-action="copy">
1808+
<span class="item-icon">${ICONS.copy}</span>
1809+
<span class="item-label">Copy</span>
1810+
</div>
1811+
<div class="dropdown-item" data-action="paste">
1812+
<span class="item-icon">${ICONS.paste}</span>
1813+
<span class="item-label">Paste</span>
1814+
</div>
1815+
</div>
1816+
`;
1817+
1818+
let styles = `
1819+
:host {
1820+
all: initial !important;
1821+
}
1822+
1823+
.phoenix-dropdown {
1824+
background-color: #ffffff !important;
1825+
color: #1f2933 !important;
1826+
border: 1px solid #1a73e8 !important;
1827+
border-radius: 6px !important;
1828+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25) !important;
1829+
font-size: 13px !important;
1830+
font-family: Arial, sans-serif !important;
1831+
z-index: 2147483647 !important;
1832+
position: absolute !important;
1833+
left: -1000px;
1834+
top: -1000px;
1835+
box-sizing: border-box !important;
1836+
min-width: 150px !important;
1837+
padding: 4px 0 !important;
1838+
overflow: hidden !important;
1839+
}
1840+
1841+
.more-options-dropdown {
1842+
display: flex !important;
1843+
flex-direction: column !important;
1844+
}
1845+
1846+
.dropdown-item {
1847+
padding: 7px 14px !important;
1848+
cursor: pointer !important;
1849+
white-space: nowrap !important;
1850+
user-select: none !important;
1851+
display: flex !important;
1852+
align-items: center !important;
1853+
gap: 8px !important;
1854+
}
1855+
1856+
.dropdown-item:hover {
1857+
background-color: #e8f1ff !important;
1858+
}
1859+
1860+
.item-icon {
1861+
display: flex !important;
1862+
align-items: center !important;
1863+
justify-content: center !important;
1864+
width: 16px !important;
1865+
height: 16px !important;
1866+
flex-shrink: 0 !important;
1867+
}
1868+
1869+
.item-icon svg {
1870+
width: 16px !important;
1871+
height: 16px !important;
1872+
display: block !important;
1873+
}
1874+
1875+
.item-label {
1876+
flex: 1 !important;
1877+
}
1878+
`;
1879+
1880+
shadow.innerHTML = `<style>${styles}</style><div class="phoenix-dropdown">${content}</div>`;
1881+
this._shadow = shadow;
1882+
},
1883+
1884+
create: function() {
1885+
this.remove();
1886+
this._style();
1887+
window.document.body.appendChild(this.body);
1888+
1889+
// to position the dropdown element at the right position
1890+
const dropdownElement = this._shadow.querySelector('.phoenix-dropdown');
1891+
if (dropdownElement) {
1892+
const dropdownRect = dropdownElement.getBoundingClientRect();
1893+
const pos = this._getDropdownPosition(dropdownRect.width, dropdownRect.height);
1894+
1895+
dropdownElement.style.left = pos.leftPos + 'px';
1896+
dropdownElement.style.top = pos.topPos + 'px';
1897+
}
1898+
1899+
// click handlers for the dropdown items
1900+
const items = this._shadow.querySelectorAll('.dropdown-item');
1901+
items.forEach(item => {
1902+
item.addEventListener('click', (event) => {
1903+
event.stopPropagation();
1904+
event.preventDefault();
1905+
const action = event.currentTarget.getAttribute('data-action');
1906+
handleOptionClick(event, action, this.targetElement);
1907+
// when an option is selected we close both the dropdown as well as the options box
1908+
this.remove();
1909+
if (_nodeMoreOptionsBox) {
1910+
_nodeMoreOptionsBox.remove();
1911+
}
1912+
});
1913+
});
1914+
},
1915+
1916+
remove: function() {
1917+
if (this.body && this.body.parentNode && this.body.parentNode === window.document.body) {
1918+
window.document.body.removeChild(this.body);
1919+
this.body = null;
1920+
_moreOptionsDropdown = null;
1921+
}
1922+
}
1923+
};
1924+
17411925
// Node info box to display DOM node ID and classes on hover
17421926
function NodeInfoBox(element) {
17431927
this.element = element;
@@ -3776,6 +3960,7 @@ function RemoteFunctions(config = {}) {
37763960
var _clickHighlight;
37773961
var _nodeInfoBox;
37783962
var _nodeMoreOptionsBox;
3963+
var _moreOptionsDropdown;
37793964
var _aiPromptBox;
37803965
var _imageRibbonGallery;
37813966
var _setup = false;
@@ -4750,6 +4935,16 @@ function RemoteFunctions(config = {}) {
47504935
}
47514936
}
47524937

4938+
/**
4939+
* Helper function to dismiss MoreOptionsDropdown if it exists
4940+
*/
4941+
function dismissMoreOptionsDropdown() {
4942+
if (_moreOptionsDropdown) {
4943+
_moreOptionsDropdown.remove();
4944+
_moreOptionsDropdown = null;
4945+
}
4946+
}
4947+
47534948
/**
47544949
* Helper function to dismiss NodeInfoBox if it exists
47554950
*/
@@ -4785,6 +4980,7 @@ function RemoteFunctions(config = {}) {
47854980
*/
47864981
function dismissAllUIBoxes() {
47874982
dismissNodeMoreOptionsBox();
4983+
dismissMoreOptionsDropdown();
47884984
dismissAIPromptBox();
47894985
dismissNodeInfoBox();
47904986
dismissImageRibbonGallery();

0 commit comments

Comments
 (0)