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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Thumbs.db
/phoenix-builder-mcp/chrome_extension/build/
/phoenix-builder-mcp/chrome_extension/*.zip

# claude
.claude/**/*.lock

# ignore node_modules inside src
/src/node_modules
/src-node/node_modules
Expand Down
3 changes: 2 additions & 1 deletion src/document/DocumentCommandHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ define(function (require, exports, module) {
NodeUtils = require("utils/NodeUtils"),
ChangeHelper = require("editor/EditorHelper/ChangeHelper"),
SidebarTabs = require("view/SidebarTabs"),
SidebarView = require("project/SidebarView"),
_ = require("thirdparty/lodash");

const KernalModeTrust = window.KernalModeTrust;
Expand Down Expand Up @@ -1954,7 +1955,7 @@ define(function (require, exports, module) {
function handleShowInTree() {
let activeFile = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE);
if(activeFile){
if (!$("#sidebar").is(":visible")) {
if (!SidebarView.isVisible()) {
CommandManager.execute(Commands.VIEW_HIDE_SIDEBAR);
}
SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES);
Expand Down
36 changes: 11 additions & 25 deletions src/extensionsIntegrated/CollapseFolders/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,14 @@
*
*/

/* Displays sidebar-hover action buttons: "show in file tree" (binoculars) and
* "collapse all folders" (stacked chevrons). Both appear on sidebar hover so the
* sidebar stays visually quiet when the user isn't interacting with it. */
/* Styling for both buttons is done in `../../styles/Extn-CollapseFolders.less` */
/* Displays a Collapse button in the sidebar area */
/* when the button gets clicked, it closes all the directories recursively that are opened */
/* Styling for the button is done in `../../styles/Extn-CollapseFolders.less` */
define(function (require, exports, module) {
const AppInit = require("utils/AppInit");
const CommandManager = require("command/CommandManager");
const Commands = require("command/Commands");
const ProjectManager = require("project/ProjectManager");
const Strings = require("strings");

const SHOW_IN_TREE_SVG = '<svg class="show-in-tree-icon" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
'<path fill="currentColor" d="M4.5 1A1.5 1.5 0 0 0 3 2.5V3h4v-.5A1.5 1.5 0 0 0 5.5 1h-1zM7 4v1h2V4h4v.882a.5.5 0 0 0 .276.447l.895.447A1.5 1.5 0 0 1 15 7.118V13H9v-1.5a.5.5 0 0 1 .146-.354l.854-.853V9.5a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v.793l.854.853A.5.5 0 0 1 7 11.5V13H1V7.118a1.5 1.5 0 0 1 .83-1.342l.894-.447A.5.5 0 0 0 3 4.882V4h4zM1 14v.5A1.5 1.5 0 0 0 2.5 16h3A1.5 1.5 0 0 0 7 14.5V14H1zm8 0v.5a1.5 1.5 0 0 0 1.5 1.5h3a1.5 1.5 0 0 0 1.5-1.5V14H9zm4-11H9v-.5A1.5 1.5 0 0 1 10.5 1h1A1.5 1.5 0 0 1 13 2.5V3z"/>' +
'</svg>';

/**
* This is the main function that handles the closing of all the directories
*/
Expand All @@ -59,37 +52,30 @@ define(function (require, exports, module) {
}
}

function _handleShowInTreeClick() {
CommandManager.execute(Commands.NAVIGATE_SHOW_IN_FILE_TREE);
}

/**
* Append the sidebar hover actions: a "Show in File Tree" binoculars button
* followed by the "Collapse All" chevron button. Both live in
* #project-files-header and become visible only on #sidebar:hover.
* This function is responsible to create the 'Collapse All' button
* and append it to the sidebar area on the project-files-header
*/
function createSidebarHoverButtons() {
function createCollapseButton() {
const $projectFilesHeader = $("#project-files-header");
// make sure that we were able to get the project-files-header DOM element
if ($projectFilesHeader.length === 0) {
return;
}

const $showInTreeBtn = $('<div id="show-in-file-tree" class="btn-alt-quiet" title="' +
Strings.CMD_SHOW_IN_TREE + '">' + SHOW_IN_TREE_SVG + '</div>');
$showInTreeBtn.on("click", _handleShowInTreeClick);
$projectFilesHeader.append($showInTreeBtn);

// create the collapse btn
const $collapseBtn = $(`
<div id="collapse-folders" class="btn-alt-quiet" title="${Strings.COLLAPSE_ALL_FOLDERS}">
<i class="fa-solid fa-chevron-down collapse-icon" aria-hidden="true"></i>
<i class="fa-solid fa-chevron-up collapse-icon" aria-hidden="true"></i>
</div>
`);

$collapseBtn.on("click", handleCollapseBtnClick);
$projectFilesHeader.append($collapseBtn);
$projectFilesHeader.append($collapseBtn); // append the btn to the project-files-header
}

AppInit.appReady(function () {
createSidebarHoverButtons();
createCollapseButton();
});
});
12 changes: 5 additions & 7 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,7 @@
<div class="main-view forced-hidden">
<div id="notificationUIDefaultAnchor" href="#">
</div>
<div id="sidebar" class="sidebar panel quiet-scrollbars horz-resizable right-resizer collapsible" data-minsize="0" data-maxsize="80%" data-forceleft=".content">
<div id="sidebar" class="sidebar panel quiet-scrollbars horz-resizable right-resizer collapsible layout-preserve-hide" data-minsize="0" data-maxsize="80%" data-forceleft=".content">
<div id="mainNavBar">
<div id="mainNavBarLeft">
<div id="newProject" class="new-project-btn btn-alt-quiet"></div>
Expand All @@ -914,6 +914,10 @@
<a id="phcode-io-main-nav" href="https://phcode.io" target="_blank" rel="noopener">phcode.io</a>
</div>
<div id="mainNavBarRight">
<div id="navBackButton" class="nav-back-btn btn-alt-quiet"></div>
<div id="navForwardButton" class="nav-forward-btn btn-alt-quiet"></div>
<div id="showInfileTree" class="show-in-file-tree-btn btn-alt-quiet"></div>
<div id="searchNav" class="search-nav-btn btn-alt-quiet"></div>
<div class="working-set-splitview-btn btn-alt-quiet"></div>
</div>
</div>
Expand Down Expand Up @@ -965,12 +969,6 @@
<i class="fa-solid fa-floppy-disk"></i>
</a>
</div>
<div class="ccb-divider"></div>
<div class="ccb-group ccb-group-nav">
<a href="#" id="searchNav" class="ccb-btn"><i class="fa-solid fa-magnifying-glass"></i></a>
<a href="#" id="navBackButton" class="ccb-btn"><i class="fa-solid fa-arrow-left"></i></a>
<a href="#" id="navForwardButton" class="ccb-btn"><i class="fa-solid fa-arrow-right"></i></a>
</div>
</div>

<!--
Expand Down
2 changes: 1 addition & 1 deletion src/project/SidebarView.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ define(function (require, exports, module) {

// AppInit.htmlReady in utils/Resizer executes before, so it's possible that the sidebar
// is collapsed before we add the event. Check here initially
if (!$sidebar.is(":visible")) {
if (!isVisible()) {
$sidebar.trigger("panelCollapsed");
}

Expand Down
19 changes: 0 additions & 19 deletions src/styles/CentralControlBar.less
Original file line number Diff line number Diff line change
Expand Up @@ -107,25 +107,6 @@
}
}

/* Nav buttons: suppress legacy sprite styles (NavigationProvider toggles enabled/disabled class) */
#searchNav,
#navBackButton,
#navForwardButton {
background: transparent !important;
top: auto !important;
padding: 0 !important;
width: auto !important;
height: 28px !important;
content: normal !important;
opacity: 1;
}

#navBackButton.nav-back-btn-disabled,
#navForwardButton.nav-forward-btn-disabled {
opacity: 0.35;
pointer-events: none;
}

}

/* Editor collapse: actual layout handled in JS via .content width/visibility */
Expand Down
5 changes: 5 additions & 0 deletions src/styles/Extn-AIChatPanel.less
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@
.ai-msg {
margin-bottom: 12px;
max-width: 100%;
// Skip layout / paint for messages that are far off-screen. For a large
// chat history (>1k messages, >10k DOM nodes), showing the sidebar after
// it was `display: none` otherwise costs ~750ms of synchronous reflow.
content-visibility: auto;
contain-intrinsic-size: auto 300px;

.ai-msg-label {
font-size: @sidebar-xs-font-size;
Expand Down
25 changes: 4 additions & 21 deletions src/styles/Extn-CollapseFolders.less
Original file line number Diff line number Diff line change
@@ -1,42 +1,25 @@
#collapse-folders,
#show-in-file-tree {
#collapse-folders {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0.2em 0.65em;
margin-top: 0.1em;
position: absolute !important;
right: 0;
opacity: 0;
visibility: hidden;
transition:
opacity 0.2s ease-in-out,
visibility 0.2s ease-in-out;
}

#collapse-folders {
flex-direction: column;
right: 0;

.collapse-icon {
font-size: 0.5em;
line-height: 1;
}
}

#show-in-file-tree {
// Sit to the left of #collapse-folders.
right: 24px;

.show-in-tree-icon {
// Sized to match the combined stacked-chevron glyph of #collapse-folders.
width: 11px;
height: 11px;
pointer-events: none;
}
}

#sidebar:hover #collapse-folders,
#sidebar:hover #show-in-file-tree {
#sidebar:hover #collapse-folders {
opacity: 1;
visibility: visible;
}
9 changes: 9 additions & 0 deletions src/styles/brackets.less
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,15 @@ a, img {
cursor: col-resize;
}

/* Resizer.js visibility-preserving hide: elements with .layout-preserve-hide
get this class toggled instead of `display: none`, so their descendants
keep their layout objects (and scrollTop / content-visibility caches)
across collapse/expand. */
.layout-preserve-hide.layout-hidden {
visibility: hidden !important;
pointer-events: none !important;
}

.working-set-header-title{
font-size: 15px;
}
Expand Down
35 changes: 27 additions & 8 deletions src/utils/Resizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
* @param {DOMNode} element Html element to toggle
*/
function toggle(element) {
if ($(element).is(":visible")) {
if (isVisible(element)) {
hide(element);
} else {
show(element);
Expand Down Expand Up @@ -217,7 +217,14 @@
* @return {boolean} true if element is visible, false if it is not visible
*/
function isVisible(element) {
return $(element).is(":visible");
var $el = $(element);

Check failure on line 220 in src/utils/Resizer.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected var, use let or const instead.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ206VczJQi_iJBa2Xaw&open=AZ206VczJQi_iJBa2Xaw&pullRequest=2834
if ($el.hasClass("layout-preserve-hide")) {
// Element opts out of `display: none` on hide (to keep layout alive
// so scrollTop / content-visibility cache survive the toggle). jQuery's
// `:visible` still returns true in that case, so consult our own flag.
return !$el.hasClass("layout-hidden");
}
return $el.is(":visible");
}

function _isPercentage(value) {
Expand Down Expand Up @@ -422,7 +429,13 @@
// the resizer, the size of the element should be 0, so we restore size in preferences
resizeElement(elementSize, contentSize);

$element.show();
if ($element.hasClass("layout-preserve-hide")) {
// Keep display unchanged — just un-hide via class. The element's
// layout tree was preserved, so no reflow avalanche here.
$element.removeClass("layout-hidden");
} else {
$element.show();
}
elementPrefs.visible = true;

if (collapsible) {
Expand Down Expand Up @@ -451,7 +464,13 @@
elementSize = elementSizeFunction.apply($element),
resizerSize = elementSizeFunction.apply($resizer);

$element.hide();
if ($element.hasClass("layout-preserve-hide")) {
// Visually hide but keep the layout object alive so descendants
// retain scrollTop and content-visibility caches across toggles.
$element.addClass("layout-hidden");
} else {
$element.hide();
}
elementPrefs.visible = false;
if (collapsible) {
$resizer.insertBefore($element);
Expand All @@ -472,7 +491,7 @@
$resizer.on("mousedown.resizer", function (e) {
var $resizeShield = $("<div class='resizing-container " + direction + "-resizing' />"),
startPosition = e[directionProperty],
startSize = $element.is(":visible") ? elementSizeFunction.apply($element) : 0,
startSize = isVisible($element) ? elementSizeFunction.apply($element) : 0,
newSize = startSize,
previousSize = startSize,
baseSize = 0,
Expand Down Expand Up @@ -502,7 +521,7 @@
if (newSize !== previousSize) {
previousSize = newSize;

if ($element.is(":visible")) {
if (isVisible($element)) {
if (collapsible && newSize < 10) {
toggle($element);
elementSizeFunction.apply($element, [0]);
Expand Down Expand Up @@ -579,7 +598,7 @@
if (isResizing) {

var elementSize = elementSizeFunction.apply($element);
if ($element.is(":visible")) {
if (isVisible($element)) {
elementPrefs.size = elementSize;
if ($resizableElement.length) {
elementPrefs.contentSize = contentSizeFunction.apply($resizableElement);
Expand Down Expand Up @@ -651,7 +670,7 @@
}

function onWindowResize(e) {
if ($sideBar.css("display") === "none") {
if (!isVisible($sideBar)) {
return;
}

Expand Down
5 changes: 3 additions & 2 deletions src/view/CentralControlBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ define(function (require, exports, module) {
const Commands = require("command/Commands");
const Strings = require("strings");
const WorkspaceManager = require("view/WorkspaceManager");
const SidebarView = require("project/SidebarView");

const BAR_WIDTH = 30;

Expand All @@ -44,7 +45,7 @@ define(function (require, exports, module) {
// and rendered width can diverge mid-drag, and outerWidth has returned the
// uncapped style value in some frames which left CCB / main-toolbar stuck
// at stale offsets.
if (!$sidebar || !$sidebar.is(":visible")) {
if (!$sidebar || !SidebarView.isVisible()) {
return 0;
}
return $sidebar[0].offsetWidth || 0;
Expand Down Expand Up @@ -248,7 +249,7 @@ define(function (require, exports, module) {
if (!$btn.length) {
return;
}
const isVisible = $("#sidebar").is(":visible");
const isVisible = SidebarView.isVisible();
$btn.find("i").attr("class", isVisible ? "fa-solid fa-angles-left" : "fa-solid fa-angles-right");
}

Expand Down
27 changes: 0 additions & 27 deletions test/spec/CentralControlBar-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,14 +246,6 @@ define(function (require, exports, module) {
expect(executed).toContain(Commands.FILE_SAVE);
});

it("should trigger CMD_FIND_IN_FILES when the search button is clicked", function () {
const executed = recordCommands(function () {
_$("#searchNav").trigger("click");
});
// searchNav routes through NavigationProvider._findInFiles which dispatches CMD_FIND_IN_FILES.
expect(executed).toContain(Commands.CMD_FIND_IN_FILES);
});

it("should execute VIEW_HIDE_SIDEBAR when #ccbSidebarToggleBtn is clicked, and the sidebar's visibility actually flips", async function () {
// Measure the effect: sidebar is visible → click → sidebar hides.
expect(SidebarView.isVisible()).toBe(true);
Expand Down Expand Up @@ -300,25 +292,6 @@ define(function (require, exports, module) {
});
});

describe("2a. #show-in-file-tree button in sidebar", function () {

it("should render #show-in-file-tree inside #project-files-header, before #collapse-folders, with the localized title", function () {
const $btn = _$("#show-in-file-tree");
expect($btn.length).toBe(1);
expect($btn.parent().attr("id")).toBe("project-files-header");
// #collapse-folders sits after #show-in-file-tree in DOM order.
expect($btn.nextAll("#collapse-folders").length).toBe(1);
expect($btn.attr("title")).toBe(Strings.CMD_SHOW_IN_TREE);
});

it("should execute NAVIGATE_SHOW_IN_FILE_TREE when #show-in-file-tree is clicked", function () {
const executed = recordCommands(function () {
_$("#show-in-file-tree").trigger("click");
});
expect(executed).toContain(Commands.NAVIGATE_SHOW_IN_FILE_TREE);
});
});

describe("3. Toggle Design Mode command", function () {

it("should execute VIEW_TOGGLE_DESIGN_MODE and flip isInDesignMode() from false to true and back", async function () {
Expand Down
Loading
Loading