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
31 changes: 18 additions & 13 deletions src/document/DocumentCommandHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,19 +347,24 @@ define(function (require, exports, module) {
_$dirtydot.css("visibility", "hidden");
}

// Set _$titleWrapper to a fixed width just large enough to accommodate _$title. This seems equivalent to what
// the browser would do automatically, but the CSS trick we use for layout requires _$titleWrapper to have a
// fixed width set on it (see the "#titlebar" CSS rule for details).
_$titleWrapper.css("width", "");
var newWidth = _$title.width();
_$titleWrapper.css("width", newWidth);

// Changing the width of the title may cause the toolbar layout to change height, which needs to resize the
// editor beneath it (toolbar changing height due to window resize is already caught by EditorManager).
var newToolbarHeight = _$titleContainerToolbar.height();
if (_lastToolbarHeight !== newToolbarHeight) {
_lastToolbarHeight = newToolbarHeight;
WorkspaceManager.recomputeLayout();
if (!Phoenix.isNativeApp) {
// In the native app the title-wrapper is display:none (the OS window titlebar
// shows the file name), so none of this layout work applies there.

// Set _$titleWrapper to a fixed width just large enough to accommodate _$title. This seems equivalent
// to what the browser would do automatically, but the CSS trick we use for layout requires
// _$titleWrapper to have a fixed width set on it (see the "#titlebar" CSS rule for details).
_$titleWrapper.css("width", "");
const newWidth = _$title.width();
_$titleWrapper.css("width", newWidth);

// Changing the width of the title may cause the toolbar layout to change height, which needs to resize
// the editor beneath it (toolbar changing height due to window resize is already caught by EditorManager).
const newToolbarHeight = _$titleContainerToolbar.height();
if (_lastToolbarHeight !== newToolbarHeight) {
_lastToolbarHeight = newToolbarHeight;
WorkspaceManager.recomputeLayout();
}
}


Expand Down
4 changes: 2 additions & 2 deletions src/styles/brackets_patterns_override.less
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ a:focus {
}

body.tauri & {
.title, .dirty-dot {
// In tauri, the window title bar shows the file name fully. so this isn't required.
display: flow-root;
.title-wrapper {
display: none;
}
}
Expand Down
56 changes: 45 additions & 11 deletions src/utils/Resizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@
*/
var PREFS_PURE_CODE = "noDistractions";

// to ignore the small jitters that might happen during click
const DRAG_START_THRESHOLD = 3;

/**
* Event triggered when a panel is collapsed.
*
Expand Down Expand Up @@ -496,6 +499,7 @@
previousSize = startSize,
baseSize = 0,
resizeStarted = false;
let dragThresholdCrossed = false;

isResizing = true;
$body.append($resizeShield);
Expand Down Expand Up @@ -559,6 +563,15 @@
}

function onMouseMove(e) {
e.preventDefault();

if (!dragThresholdCrossed) {
if (Math.abs(e[directionProperty] - startPosition) < DRAG_START_THRESHOLD) {
return;
}
dragThresholdCrossed = true;
}

// calculate newSize adding to startSize the difference
// between starting and current position, capped at minSize
newSize = Math.max(startSize + directionIncrement * (startPosition - e[directionProperty]), minSize);
Expand All @@ -580,8 +593,6 @@
}
}

e.preventDefault();

if (animationRequest === null) {
animationRequest = window.requestAnimationFrame(doRedraw);
}
Expand All @@ -597,7 +608,23 @@
$resizeShield.off("mousedown");
$resizeShield.remove();
animationRequest = null;
toggle($element);

// The shield covers the whole window, so only treat this press as
// the second click of a double click if it landed on/near the
// resizer; stray clicks elsewhere just dismiss the shield.
const buffer = 8;
const resizerOffset = $resizer.offset();
let nearResizer;
if (direction === DIRECTION_HORIZONTAL) {
nearResizer = e.pageX >= resizerOffset.left - buffer &&
e.pageX <= resizerOffset.left + $resizer.outerWidth() + buffer;
} else {
nearResizer = e.pageY >= resizerOffset.top - buffer &&
e.pageY <= resizerOffset.top + $resizer.outerHeight() + buffer;
}
if (nearResizer) {
toggle($element);
}
});
}

Expand All @@ -616,18 +643,25 @@

isResizing = false;

if (resizeStarted) {
$element.trigger(EVENT_PANEL_RESIZE_END, [elementSize]);
}

// We wait 300ms to remove the resizer container to capture a mousedown
// on the container that would account for double click
window.setTimeout(function () {
function removeShield() {

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

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest functions more than 4 levels deep.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ9SOxZG-c_STaj1-5tq&open=AZ9SOxZG-c_STaj1-5tq&pullRequest=3021
$(window.document).off("mousemove", onMouseMove);
$resizeShield.off("mousedown");
$resizeShield.remove();
animationRequest = null;
}, 300);
}

if (resizeStarted) {
$element.trigger(EVENT_PANEL_RESIZE_END, [elementSize]);
// A double click never includes a drag between its two presses,
// so after a real resize the shield isn't needed to catch a
// second click — remove it right away so it doesn't swallow
// clicks elsewhere in the window.
removeShield();
} else {
// We wait 300ms to remove the resizer container to capture a mousedown
// on the container that would account for double click
window.setTimeout(removeShield, 300);
}
}
}

Expand Down
12 changes: 7 additions & 5 deletions test/spec/DragTestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,13 @@ define(function (require, exports, module) {

_fireMouse(doc, "mouseup", endX, endY, testWindow, 0);
await _awaitFrames(testWindow, 2);
// Resizer leaves a full-viewport `.resizing-container` shield in the DOM
// for 300ms after mouseup (so a trailing mousedown still registers as a
// double-click). If the next test fires its mousedown before that shield
// is removed, it lands on the shield rather than the handle and the drag
// silently no-ops. Waiting the shield out makes consecutive drags reliable.
// After a real drag Resizer removes its full-viewport `.resizing-container`
// shield immediately on mouseup, but a press+release with no effective
// movement leaves it in the DOM for 300ms (so a trailing mousedown still
// registers as a double-click). If a drag degenerates to no movement and
// the next test fires its mousedown before the shield is removed, it lands
// on the shield rather than the handle and the drag silently no-ops.
// Waiting the window out keeps consecutive drags reliable either way.
await new Promise(function (resolve) { testWindow.setTimeout(resolve, 320); });
}

Expand Down
29 changes: 29 additions & 0 deletions test/spec/MainViewManager-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,35 @@ define(function (require, exports, module) {
expect(isDisplayed).toBeFalse();

});

it("should set a fixed title wrapper width in browser windows", async function () {
if(Phoenix.isNativeApp) {
return;
}
const openPromise = MainViewManager._open(MainViewManager.FIRST_PANE,
FileSystem.getFileForPath(testPath + "/test.js"));
await awaitsForDone(openPromise, "MainViewManager.doOpen");

const wrapper = testWindow.$('.title-wrapper')[0];
await awaitsFor(function () {
return parseFloat(wrapper.style.width) > 0;
}, "title wrapper to get a fixed width", 2000);
});

it("should not set a title wrapper width in tauri windows", async function () {
if(!Phoenix.isNativeApp) {
return;
}
const openPromise = MainViewManager._open(MainViewManager.FIRST_PANE,
FileSystem.getFileForPath(testPath + "/test.js"));
await awaitsForDone(openPromise, "MainViewManager.doOpen");

// In native apps, the file name is shown in the OS window title bar
// the in-app wrapper for file name stays hidden and _updateTitle skips its layout work...
const $wrapper = testWindow.$('.title-wrapper');
expect($wrapper.css("display")).toBe("none");
expect($wrapper[0].style.width).toBe("");
});
});

describe("opening and closing files", function () {
Expand Down
108 changes: 107 additions & 1 deletion test/spec/SidebarTabs-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*
*/

/*global describe, it, expect, beforeAll, afterAll, beforeEach, awaitsFor, awaitsForDone, jsPromise */
/*global describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, awaitsFor, awaitsForDone, jsPromise */

define(function (require, exports, module) {

Expand Down Expand Up @@ -586,5 +586,111 @@ define(function (require, exports, module) {
expect(prefs.size).toBe(380);
});
});

describe("Sidebar resizer double-click and drag shield", function () {

function fireMouse(target, type, x, y) {
const ev = new testWindow.MouseEvent(type, {
bubbles: true,
cancelable: true,
view: testWindow,
clientX: x,
clientY: y,
button: 0,
buttons: type === "mouseup" ? 0 : 1
});
target.dispatchEvent(ev);
}

function awaitFrames(n) {
return new Promise(function (resolve) {
let remaining = n;
function tick() {
remaining -= 1;
if (remaining <= 0) {
resolve();
return;
}
testWindow.requestAnimationFrame(tick);
}
testWindow.requestAnimationFrame(tick);
});
}

function resizerCenter() {
const rect = _$("#sidebar > .horz-resizer")[0].getBoundingClientRect();
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
}

beforeEach(async function () {
SidebarView.show();
_$(".resizing-container").remove();
SidebarView.resize(300);
await awaitsFor(function () {
return _$("#sidebar")[0].offsetWidth === 300;
}, "sidebar to settle at baseline 300px", 2000);
});

afterEach(function () {
_$(".resizing-container").remove();
SidebarView.show();
});

it("should collapse the sidebar on resizer double click", function () {
const center = resizerCenter();
const $resizer = _$("#sidebar > .horz-resizer");

fireMouse($resizer[0], "mousedown", center.x, center.y);
fireMouse(testWindow.document, "mouseup", center.x, center.y);
const $shield = _$(".resizing-container");
expect($shield.length).toBe(1);

fireMouse($shield[0], "mousedown", center.x, center.y);
expect(SidebarView.isVisible()).toBe(false);
});

it("should not collapse the sidebar when the second press lands away from the resizer", function () {
const center = resizerCenter();
const $resizer = _$("#sidebar > .horz-resizer");

fireMouse($resizer[0], "mousedown", center.x, center.y);
fireMouse(testWindow.document, "mouseup", center.x, center.y);
const $shield = _$(".resizing-container");
expect($shield.length).toBe(1);

fireMouse($shield[0], "mousedown", center.x + 200, center.y);
expect(SidebarView.isVisible()).toBe(true);
expect(_$(".resizing-container").length).toBe(0);
});

it("should remove the shield right after a drag so a quick editor click cannot collapse", async function () {
const center = resizerCenter();
const $resizer = _$("#sidebar > .horz-resizer");

fireMouse($resizer[0], "mousedown", center.x, center.y);
await awaitFrames(1);
for (let i = 1; i <= 5; i++) {
fireMouse(testWindow.document, "mousemove", center.x + i * 10, center.y);
await awaitFrames(1);
}
fireMouse(testWindow.document, "mouseup", center.x + 50, center.y);
await awaitFrames(1);

const widthAfterDrag = _$("#sidebar")[0].offsetWidth;
expect(widthAfterDrag).toBeGreaterThan(300);

expect(_$(".resizing-container").length).toBe(0);

const edRect = _$("#editor-holder")[0].getBoundingClientRect();
const clickX = edRect.left + edRect.width / 2;
const clickY = edRect.top + edRect.height / 2;
const target = testWindow.document.elementFromPoint(clickX, clickY) || _$("#editor-holder")[0];
fireMouse(target, "mousedown", clickX, clickY);
fireMouse(target, "mouseup", clickX, clickY);

expect(SidebarView.isVisible()).toBe(true);
expect(_$("#sidebar")[0].offsetWidth).toBe(widthAfterDrag);
});
});
});
});
Loading