Skip to content
Merged
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
10 changes: 8 additions & 2 deletions src/project/FileTreeView.js
Original file line number Diff line number Diff line change
Expand Up @@ -1213,8 +1213,14 @@
},

handleDrop: function(e) {
var data = JSON.parse(e.dataTransfer.getData("text"));
this.props.actions.moveItem(data.path, this.props.parentPath);
try {
var data = JSON.parse(e.dataTransfer.getData("text"));
if (data && data.path) {

Check warning on line 1218 in src/project/FileTreeView.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=AZ1oA-q1yM5yS0P3T3Q6&open=AZ1oA-q1yM5yS0P3T3Q6&pullRequest=2797
this.props.actions.moveItem(data.path, this.props.parentPath);
}
} catch (err) {
console.error("FileTreeView: drop handler error:", err);
}
e.stopPropagation();
},

Expand Down
10 changes: 8 additions & 2 deletions src/project/ProjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -2096,8 +2096,14 @@

// Add support for moving items to root directory
$projectTreeContainer.on("drop", function(e) {
var data = JSON.parse(e.originalEvent.dataTransfer.getData("text"));
actionCreator.moveItem(data.path, getProjectRoot().fullPath);
try {
var data = JSON.parse(e.originalEvent.dataTransfer.getData("text"));
if (data && data.path) {

Check warning on line 2101 in src/project/ProjectManager.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=AZ1oA-neyM5yS0P3T3Q5&open=AZ1oA-neyM5yS0P3T3Q5&pullRequest=2797
actionCreator.moveItem(data.path, getProjectRoot().fullPath);
}
} catch (err) {
console.error("ProjectManager: drop handler error:", err);
}
e.stopPropagation();
});

Expand Down
30 changes: 30 additions & 0 deletions src/styles/Extn-BottomPanelTabs.less
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,35 @@ img.panel-titlebar-icon {
pointer-events: none;
}

/* Overflow button: shown when tabs overflow even after collapsing to icons */
.bottom-panel-overflow-btn {
display: flex;
align-items: center;
justify-content: center;
width: 1.9rem;
height: 2rem;
cursor: pointer;
color: #666;
font-size: 0.9rem;
flex: 0 0 auto;
transition: color 0.12s ease, background-color 0.12s ease;

.dark & {
color: #aaa;
}

&:hover {
background-color: #e0e0e0;
color: #333;

.dark & {
background-color: #333;
color: #eee;
}
}

}

/* Drag-and-drop tab reordering */
.bottom-panel-tab-dragging {
opacity: 0.5;
Expand Down Expand Up @@ -280,6 +309,7 @@ img.panel-titlebar-icon {
color: #eee;
}
}

}

.bottom-panel-tab-bar-actions {
Expand Down
137 changes: 130 additions & 7 deletions src/view/PanelView.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
const EventDispatcher = require("utils/EventDispatcher"),
PreferencesManager = require("preferences/PreferencesManager"),
Resizer = require("utils/Resizer"),
DropdownButton = require("widgets/DropdownButton"),
Strings = require("strings");

/**
Expand Down Expand Up @@ -185,11 +186,11 @@
_$tabsOverflow.append(_buildTab(panel, panelId === _activeId));
});

// Re-append the "+" button at the end (after all tabs)
// Re-append the Tools button at the end
if (_$addBtn) {
_$tabsOverflow.append(_$addBtn);
_updateAddButtonVisibility();
}
_updateAddButtonVisibility();
_checkTabOverflow();
}

Expand Down Expand Up @@ -226,8 +227,7 @@
return;
}
let $tab = _buildTab(panel, panelId === _activeId);

// Insert before the "+" button so it stays at the end
// Insert before the Tools button so it stays at the end
if (_$addBtn && _$addBtn.parent().length) {
_$addBtn.before($tab);
} else {
Expand Down Expand Up @@ -295,7 +295,7 @@
_$tabBar.on("dragstart", ".bottom-panel-tab", function (e) {
draggedTab = this;
e.originalEvent.dataTransfer.effectAllowed = "move";
e.originalEvent.dataTransfer.setData("text/plain", "panel-tab");
e.originalEvent.dataTransfer.setData("application/x-phoenix-panel-tab", "1");
$(this).addClass("bottom-panel-tab-dragging");
});

Expand Down Expand Up @@ -354,6 +354,9 @@
* Only collapses tabs that have an icon available.
* @private
*/
/** @type {jQueryObject} Overflow dropdown button */
let _$overflowBtn = null;

function _checkTabOverflow() {
if (!_$tabBar) {
return;
Expand All @@ -362,6 +365,16 @@
_$tabBar.removeClass("bottom-panel-tabs-collapsed");
const isOverflowing = _$tabsOverflow[0].scrollWidth > _$tabsOverflow[0].clientWidth;
_$tabBar.toggleClass("bottom-panel-tabs-collapsed", isOverflowing);

// Check if still overflowing after collapse
const stillOverflowing = isOverflowing &&
_$tabsOverflow[0].scrollWidth > _$tabsOverflow[0].clientWidth;

// Show/hide overflow button
if (_$overflowBtn) {
_$overflowBtn.toggle(stillOverflowing);
}

// Show tooltip on hover only in collapsed mode (title text is hidden)
_$tabBar.find(".bottom-panel-tab").each(function () {
const $tab = $(this);
Expand All @@ -373,6 +386,105 @@
});
}

/**
* Get the list of hidden (not fully visible) panel tabs.
* @return {Array<{panelId: string, title: string}>}
* @private
*/
function _getHiddenTabs() {
const hidden = [];
const barRect = _$tabsOverflow[0].getBoundingClientRect();
_$tabsOverflow.find(".bottom-panel-tab").each(function () {
const tabRect = this.getBoundingClientRect();
const isVisible = tabRect.left >= barRect.left &&
tabRect.right <= (barRect.right + 2);
if (!isVisible) {
const $tab = $(this);
hidden.push({
panelId: $tab.data("panel-id"),
title: $tab.find(".bottom-panel-tab-title").text()
});
}
});
return hidden;
}

/** @type {DropdownButton.DropdownButton} */
let _overflowDropdown = null;

/**
* Show a dropdown menu listing hidden panel tabs.
* Uses the same DropdownButton widget as the file tab bar overflow.
* @private
*/
function _showOverflowMenu() {
// If dropdown is already open, close it (toggle behavior)
if (_overflowDropdown) {
_overflowDropdown.closeDropdown();
_overflowDropdown = null;
return;
}

const hidden = _getHiddenTabs();
if (!hidden.length) {
return;
}

_overflowDropdown = new DropdownButton.DropdownButton("", hidden, function (item) {
const panel = _panelMap[item.panelId];
let iconHtml = "";
if (panel && panel._options) {

Check warning on line 436 in src/view/PanelView.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=AZ1oA-rWyM5yS0P3T3Q7&open=AZ1oA-rWyM5yS0P3T3Q7&pullRequest=2797
if (panel._options.iconClass) {
iconHtml = '<i class="panel-titlebar-icon ' + panel._options.iconClass
+ '" style="margin-right:6px"></i>';
} else if (panel._options.iconSvg) {
iconHtml = '<img class="panel-titlebar-icon" src="' + panel._options.iconSvg
+ '" style="width:14px;height:14px;margin-right:6px;vertical-align:middle">';
}
}
const activeClass = item.panelId === _activeId ? ' style="font-weight:600"' : '';
return {
html: '<div class="dropdown-tab-item"' + activeClass + '>'
+ iconHtml + '<span>' + item.title + '</span></div>',
enabled: true
};
});

_overflowDropdown.dropdownExtraClasses = "dropdown-overflow-menu";

// Position at the overflow button
const btnRect = _$overflowBtn[0].getBoundingClientRect();
$("body").append(_overflowDropdown.$button);
_overflowDropdown.$button.css({
position: "absolute",
left: btnRect.left + "px",
top: (btnRect.top - 2) + "px",
zIndex: 1000
});

_overflowDropdown.showDropdown();

_overflowDropdown.on("select", function (e, item) {
const panel = _panelMap[item.panelId];
if (panel) {
panel.show();
// Scroll the newly active tab into view
const $tab = _$tabsOverflow.find('.bottom-panel-tab[data-panel-id="' + item.panelId + '"]');
if ($tab.length) {
$tab[0].scrollIntoView({inline: "nearest"});
}
}
});

// Clean up reference when dropdown closes
_overflowDropdown.on(DropdownButton.EVENT_DROPDOWN_CLOSED, function () {
if (_overflowDropdown) {
_overflowDropdown.$button.remove();
_overflowDropdown = null;
}
});
}

/**
* Show or hide the "+" button based on whether the default panel is active.
* The button is hidden when the default panel is the active tab (since
Expand Down Expand Up @@ -664,8 +776,7 @@
_recomputeLayout = recomputeLayoutFn;
_defaultPanelId = defaultPanelId;

// Create the "Tools" button inside the tabs overflow area (after all tabs)
// This opens the default/quick-access panel when clicked.
// Create the "Tools" button inside the scrollable tabs area.
_$addBtn = $('<span class="bottom-panel-add-btn" title="' + Strings.BOTTOM_PANEL_DEFAULT_TITLE + '">'
+ '<img class="app-drawer-tab-icon" src="styles/images/app-drawer.svg"'
+ ' style="width:12px;height:12px;vertical-align:middle;margin-right:4px">'
Expand All @@ -692,10 +803,22 @@
panel.show();
}
}
// Scroll clicked tab into view if partially hidden
this.scrollIntoView({inline: "nearest"});
});

_initDragAndDrop();

// Overflow button for hidden tabs (inserted between tabs and action buttons)
_$overflowBtn = $('<span class="bottom-panel-overflow-btn" title="' + Strings.TABBAR_SHOW_HIDDEN_TABS + '">'
+ '<i class="fa-solid fa-chevron-down"></i></span>');
_$overflowBtn.hide();
_$tabBar.find(".bottom-panel-tab-bar-actions").before(_$overflowBtn);
_$overflowBtn.on("click", function (e) {
e.stopPropagation();
_showOverflowMenu();
});

// "+" button opens the default/quick-access panel
_$addBtn.on("click", function (e) {
e.stopPropagation();
Expand Down
Loading