From 7f403b103b83a835d30f901344b4e4d674c19a8e Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 7 Jan 2025 16:09:34 +0530 Subject: [PATCH 1/3] chore: simplilfy recent projects dropdown impl --- .../htmlContent/projects-menu.html | 1 + .../RecentProjects/main.js | 79 +++---------------- src/styles/Extn-RecentProjects.less | 10 +++ 3 files changed, 23 insertions(+), 67 deletions(-) diff --git a/src/extensionsIntegrated/RecentProjects/htmlContent/projects-menu.html b/src/extensionsIntegrated/RecentProjects/htmlContent/projects-menu.html index cf84bd6fa6..8055627445 100644 --- a/src/extensionsIntegrated/RecentProjects/htmlContent/projects-menu.html +++ b/src/extensionsIntegrated/RecentProjects/htmlContent/projects-menu.html @@ -13,6 +13,7 @@ {{folder}} {{rest}} +
×
{{/projectList}} diff --git a/src/extensionsIntegrated/RecentProjects/main.js b/src/extensionsIntegrated/RecentProjects/main.js index ee5da356ee..3b03323aa7 100644 --- a/src/extensionsIntegrated/RecentProjects/main.js +++ b/src/extensionsIntegrated/RecentProjects/main.js @@ -95,24 +95,6 @@ define(function (require, exports, module) { PreferencesManager.setViewState(RECENT_PROJECT_STATE, recentProjects); } - /** - * Check the list of items to see if any of them are hovered, and if so trigger a mouseenter. - * Normally the mouseenter event handles this, but when a previous item is deleted and the next - * item moves up to be underneath the mouse, we don't get a mouseenter event for that item. - */ - function checkHovers(pageX, pageY) { - $dropdown.children().each(function () { - var offset = $(this).offset(), - width = $(this).outerWidth(), - height = $(this).outerHeight(); - - if (pageX >= offset.left && pageX <= offset.left + width && - pageY >= offset.top && pageY <= offset.top + height) { - $(".recent-folder-link", this).triggerHandler("mouseenter"); - } - }); - } - function removeFromRecentProject(fullPath) { fullPath = FileUtils.stripTrailingSlash(fullPath); let recentProjects = getRecentProjects(), @@ -127,44 +109,6 @@ define(function (require, exports, module) { PreferencesManager.setViewState(RECENT_PROJECT_STATE, newProjects); } - /** - * Create the "delete" button that shows up when you hover over a project. - */ - function renderDelete() { - return $("
×
") - .mouseup(function (e) { - // Don't let the click bubble upward. - e.stopPropagation(); - - // Remove the project from the preferences. - removeFromRecentProject($(this).parent().data("path")); - $(this).closest("li").remove(); - checkHovers(e.pageX, e.pageY); - - if (getRecentProjects().length === 1) { - $dropdown.find(".divider").remove(); - } - }); - } - - /** - * Hide the delete button. - */ - function removeDeleteButton() { - $("#recent-folder-delete").remove(); - } - - /** - * Show the delete button over a given target. - */ - function addDeleteButton($target) { - removeDeleteButton(); - renderDelete() - .css("top", $target.position().top + 6) - .appendTo($target); - } - - /** * Selects the next or previous item in the list * @param {number} direction +1 for next, -1 for prev @@ -190,7 +134,6 @@ define(function (require, exports, module) { $newItem.addClass("selected"); $dropdownItem = $newItem; - removeDeleteButton(); } let searchStr =""; @@ -247,7 +190,6 @@ define(function (require, exports, module) { // remove project recentProjects.splice(index, 1); PreferencesManager.setViewState(RECENT_PROJECT_STATE, recentProjects); - checkHovers(e.pageX, e.pageY); if (recentProjects.length === 1) { $dropdown.find(".divider").remove(); @@ -374,6 +316,18 @@ define(function (require, exports, module) { */ function _handleListEvents() { $dropdown + .on("click", ".recent-project-delete", function (e) { + // Don't let the click bubble upward. + e.stopPropagation(); + + // Remove the project from the preferences. + removeFromRecentProject($(this).parent().data("path")); + $(this).closest("li").remove(); + + if (getRecentProjects().length === 1) { + $dropdown.find(".divider").remove(); + } + }) .on("click", "a", function () { var $link = $(this), id = $link.attr("id"), @@ -397,12 +351,6 @@ define(function (require, exports, module) { $dropdownItem.removeClass("selected"); } $dropdownItem = $(this).addClass("selected"); - - if ($dropdownItem.hasClass("recent-folder-link")) { - // Note: we can't depend on the event here because this can be triggered - // manually from checkHovers(). - addDeleteButton($(this)); - } }) .on("mouseleave", "a", function () { var $link = $(this).removeClass("selected"); @@ -410,9 +358,6 @@ define(function (require, exports, module) { if ($link.get(0) === $dropdownItem.get(0)) { $dropdownItem = null; } - if ($link.hasClass("recent-folder-link")) { - removeDeleteButton(); - } }); } diff --git a/src/styles/Extn-RecentProjects.less b/src/styles/Extn-RecentProjects.less index 2465b24770..c77967714a 100644 --- a/src/styles/Extn-RecentProjects.less +++ b/src/styles/Extn-RecentProjects.less @@ -99,6 +99,16 @@ #project-dropdown.dropdown-menu li a { padding: 5px 15px; + position: relative; +} + +.recent-project-delete { + visibility: hidden; +} + +.recent-folder-link:hover > .recent-project-delete { + top: 26%; + visibility: visible; } #project-dropdown.dropdown-menu .recent-folder-link, #project-dropdown.dropdown-menu #open-folder-link #new-project-link #download-project-link{ From d2961afbb3aca9f062b5ad1a46d0c84e14f8d85c Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 7 Jan 2025 17:19:13 +0530 Subject: [PATCH 2/3] chore: simplilfy recent projects dropdown by using new popupmanager APIs --- src/assets/new-project/code-editor.html | 2 +- .../RecentProjects/main.js | 217 ++---------------- src/nls/root/strings.js | 2 +- src/widgets/PopUpManager.js | 18 +- 4 files changed, 38 insertions(+), 201 deletions(-) diff --git a/src/assets/new-project/code-editor.html b/src/assets/new-project/code-editor.html index 88a4fa7830..594ff0c001 100644 --- a/src/assets/new-project/code-editor.html +++ b/src/assets/new-project/code-editor.html @@ -70,7 +70,7 @@

{{APP_TITLE}}

-
{{CMD_TOGGLE_RECENT_PROJECTS}}
+
{{CMD_RECENT_PROJECTS}}
    diff --git a/src/extensionsIntegrated/RecentProjects/main.js b/src/extensionsIntegrated/RecentProjects/main.js index 3b03323aa7..34b1ea25f1 100644 --- a/src/extensionsIntegrated/RecentProjects/main.js +++ b/src/extensionsIntegrated/RecentProjects/main.js @@ -31,7 +31,6 @@ define(function (require, exports, module) { Commands = require("command/Commands"), CommandManager = require("command/CommandManager"), Menus = require("command/Menus"), - MainViewManager = require("view/MainViewManager"), FileSystem = require("filesystem/FileSystem"), AppInit = require("utils/AppInit"), KeyEvent = require("utils/KeyEvent"), @@ -47,16 +46,11 @@ define(function (require, exports, module) { ExtensionInterface.registerExtensionInterface(RECENT_PROJECTS_INTERFACE, exports); const RECENT_PROJECT_STATE = "recentProjects"; - /** @const {string} Recent Projects commands ID */ - let TOGGLE_DROPDOWN = "recentProjects.toggle"; - /** @const {number} Maximum number of displayed recent projects */ var MAX_PROJECTS = 20; /** @type {$.Element} jQuery elements used for the dropdown menu */ - var $dropdownItem, - $dropdown, - $links; + let $dropdown; /** * Get the stored list of recent projects, fixing up paths as appropriate. @@ -109,156 +103,28 @@ define(function (require, exports, module) { PreferencesManager.setViewState(RECENT_PROJECT_STATE, newProjects); } - /** - * Selects the next or previous item in the list - * @param {number} direction +1 for next, -1 for prev - */ - function selectNextItem(direction) { - let $links = $dropdown.find("a:visible"), - index = $dropdownItem ? $links.index($dropdownItem) : (direction > 0 ? -1 : 0), - $newItem = $links.eq((index + direction) % $links.length); - - if(searchStr && $links.length === 1){ - // no search result, only the top search field visible - return; - } - if($newItem.parent().hasClass("sticky-li-top")) { - if(index === -1){ - index = 0; - } - $newItem = $links.eq((index + direction) % $links.length); - } - if ($dropdownItem) { - $dropdownItem.removeClass("selected"); - } - $newItem.addClass("selected"); - - $dropdownItem = $newItem; - } - - let searchStr =""; - /** - * hides all elements in popup that doesn't match the given search string, also shows the search bar in popup - * @param searchString - */ - function filterDropdown(searchString) { - searchStr = searchString; - const $stickyLi = $dropdown.find('li.sticky-li-top'); - if(searchString){ - $stickyLi.removeClass("forced-hidden"); - } else { - $stickyLi.addClass("forced-hidden"); - } - - $dropdown.find('li').each(function(index, li) { - if(index === 0){ - // this is the top search box itself - return; - } - const $li = $(li); - if(!$li.text().toLowerCase().includes(searchString.toLowerCase())){ - $li.addClass("forced-hidden"); - } else { - $li.removeClass("forced-hidden"); - } - }); - - if(searchString) { - $stickyLi.removeClass('forced-hidden'); - $stickyLi.find('.searchTextSpan').text(searchString); - } else { - $stickyLi.addClass('forced-hidden'); - } - } - - /** - * Deletes the selected item and - * move the focus to next item in list. - * - * @return {boolean} TRUE if project is removed - */ - function removeSelectedItem(e) { - var recentProjects = getRecentProjects(), - $cacheItem = $dropdownItem, - index = recentProjects.indexOf($cacheItem.data("path")); - - // When focus is not on project item - if (index === -1) { - return false; - } - - // remove project - recentProjects.splice(index, 1); - PreferencesManager.setViewState(RECENT_PROJECT_STATE, recentProjects); - - if (recentProjects.length === 1) { - $dropdown.find(".divider").remove(); - } - selectNextItem(+1); - $cacheItem.closest("li").remove(); - return true; - } - /** * Handles the Key Down events * @param {KeyboardEvent} event + * @param $popUp * @return {boolean} True if the key was handled */ - function keydownHook(event) { - var keyHandled = false; - - switch (event.keyCode) { - case KeyEvent.DOM_VK_UP: - selectNextItem(-1); - keyHandled = true; - break; - case KeyEvent.DOM_VK_DOWN: - selectNextItem(+1); - keyHandled = true; - break; - case KeyEvent.DOM_VK_ENTER: - case KeyEvent.DOM_VK_RETURN: - if ($dropdownItem) { - $dropdownItem.trigger("click"); - } - keyHandled = true; - break; - case KeyEvent.DOM_VK_DELETE: - if ($dropdownItem) { - removeSelectedItem(event); - } - keyHandled = true; - break; - } - - if(keyHandled){ - event.stopImmediatePropagation(); - event.preventDefault(); - return keyHandled; - } else if((event.ctrlKey || event.metaKey) && event.key === 'v') { - Phoenix.app.clipboardReadText().then(text=>{ - searchStr += text; - filterDropdown(searchStr); - }); - keyHandled = true; - } else if (event.key.length === 1) { - searchStr += event.key; - keyHandled = true; - } else if (event.key === 'Backspace') { - // Remove the last character when Backspace is pressed - searchStr = searchStr.slice(0, -1); - keyHandled = true; - } else { - // bubble up, not for us to handle - return false; - } - filterDropdown(searchStr); + function _handlePopupKeyEvents(event, $popUp) { + if(event.keyCode === KeyEvent.DOM_VK_DELETE){ + event.stopPropagation(); + const $selectedItem = $popUp.find(".selected"); + if ($selectedItem.length && $selectedItem.data("path")) { + // Remove the project from the preferences. + removeFromRecentProject($selectedItem.data("path")); + PopUpManager.selectNextItem(+1, $popUp); + $selectedItem.closest("li").remove(); - if (keyHandled) { - event.stopImmediatePropagation(); - event.preventDefault(); + if (getRecentProjects().length === 1) { + $dropdown.find(".divider").remove(); + } + } + return true; } - return keyHandled; } @@ -272,7 +138,6 @@ define(function (require, exports, module) { if ($dropdown) { PopUpManager.removePopUp($dropdown); } - searchStr = ""; } /** @@ -284,9 +149,6 @@ define(function (require, exports, module) { $("#project-files-container").off("scroll", closeDropdown); $("#titlebar .nav").off("click", closeDropdown); $dropdown = null; - - $(window).off("keydown", keydownHook); - searchStr = ""; } function openProjectWithPath(fullPath) { @@ -345,19 +207,6 @@ define(function (require, exports, module) { CommandManager.execute(Commands.FILE_DOWNLOAD_PROJECT); } - }) - .on("mouseenter", "a", function () { - if ($dropdownItem) { - $dropdownItem.removeClass("selected"); - } - $dropdownItem = $(this).addClass("selected"); - }) - .on("mouseleave", "a", function () { - var $link = $(this).removeClass("selected"); - - if ($link.get(0) === $dropdownItem.get(0)) { - $dropdownItem = null; - } }); } @@ -428,6 +277,10 @@ define(function (require, exports, module) { .appendTo($("body")); PopUpManager.addPopUp($dropdown, cleanupDropdown, true, {closeCurrentPopups: true}); + PopUpManager.handleSelectionEvents($dropdown, { + enableSearchFilter: true, + keyboardEventHandler: _handlePopupKeyEvents + }); // TODO: should use capture, otherwise clicking on the menus doesn't close it. More fallout // from the fact that we can't use the Boostrap (1.4) dropdowns. @@ -448,38 +301,8 @@ define(function (require, exports, module) { $("#titlebar .nav").on("click", closeDropdown); _handleListEvents(); - $(window).on("keydown", keydownHook); - } - - - /** - * Show or hide the recent projects dropdown from the toogle command. - */ - function handleKeyEvent() { - if (!$dropdown) { - if (!SidebarView.isVisible()) { - SidebarView.show(); - } - - $("#project-dropdown-toggle").trigger("click"); - - $dropdown.focus(); - $links = $dropdown.find("a"); - // By default, select the most recent project (which is at the top of the list underneath Open Folder), - // but if there are none, select Open Folder instead. - $dropdownItem = $links.eq($links.length > 1 ? 1 : 0); - $dropdownItem.addClass("selected"); - - // If focusing the dropdown caused a modal bar to close, we need to refocus the dropdown - window.setTimeout(function () { - $dropdown.focus(); - }, 0); - } } - // Register command handlers - CommandManager.register(Strings.CMD_TOGGLE_RECENT_PROJECTS, TOGGLE_DROPDOWN, handleKeyEvent); - // Initialize extension AppInit.appReady(function () { PreferencesManager.stateManager.definePreference(RECENT_PROJECT_STATE, 'array', []) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 6289789240..803168b3da 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -954,7 +954,7 @@ define({ "TOOLTIP_CLICK_TO_EDIT_COLOR": "Click here to edit color", // extensions/default/RecentProjects - "CMD_TOGGLE_RECENT_PROJECTS": "Recent Projects", + "CMD_RECENT_PROJECTS": "Recent Projects", "REMOVE_FROM_RECENT_PROJECTS": "Remove from Recent Projects", // extensions/default/MDNDocs diff --git a/src/widgets/PopUpManager.js b/src/widgets/PopUpManager.js index 094f09be57..99481f03b6 100644 --- a/src/widgets/PopUpManager.js +++ b/src/widgets/PopUpManager.js @@ -88,6 +88,19 @@ define(function (require, exports, module) { $popUp.off("keydown", _processSelectionEvent); $popUp.on("keydown", _processSelectionEvent); $popUp.focus(); + function _selectItem() { + $popUp.find(".selected").removeClass("selected"); + $(this).addClass("selected"); + } + function _unselectItem() { + $(this).removeClass("selected"); + } + $popUp + .off("mouseenter", "a", _selectItem) + .off("mouseleave", "a", _unselectItem); + $popUp + .on("mouseenter", "a", _selectItem) + .on("mouseleave", "a", _unselectItem); } /** @@ -207,7 +220,7 @@ define(function (require, exports, module) { /** - * Selects the next or previous item in the list + * Selects the next or previous item in the popup. * @param {number} direction +1 for next, -1 for prev * @param $popUp */ @@ -246,7 +259,7 @@ define(function (require, exports, module) { return false; } if(keyboardEventHandler) { - const processed = keyboardEventHandler(event); + const processed = keyboardEventHandler(event, $popUp); if(processed){ return true; } @@ -367,6 +380,7 @@ define(function (require, exports, module) { exports.addPopUp = addPopUp; exports.handleSelectionEvents = handleSelectionEvents; + exports.selectNextItem = selectNextItem; exports.removePopUp = removePopUp; exports.closeAllPopups = closeAllPopups; exports.listenToContextMenu = listenToContextMenu; From 4ee6f9d48ccb141e168ec2adf4ec8824d559e91d Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 7 Jan 2025 19:57:05 +0530 Subject: [PATCH 3/3] fix: integ test failures on recent projects refactor --- src/base-config/keyboard.json | 5 ----- test/spec/Extn-RecentProjects-integ-test.js | 19 +++++++------------ 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/base-config/keyboard.json b/src/base-config/keyboard.json index cd3b643e96..8674ac0619 100644 --- a/src/base-config/keyboard.json +++ b/src/base-config/keyboard.json @@ -338,10 +338,5 @@ ], "help.docs": [ "Shift-F1" - ], - "recentProjects.toggle": [ - { - "key": "Ctrl-Alt-R" - } ] } diff --git a/test/spec/Extn-RecentProjects-integ-test.js b/test/spec/Extn-RecentProjects-integ-test.js index 357bfe8e9c..9ae0de9d1f 100644 --- a/test/spec/Extn-RecentProjects-integ-test.js +++ b/test/spec/Extn-RecentProjects-integ-test.js @@ -24,10 +24,8 @@ define(function (require, exports, module) { - var SpecRunnerUtils = require("spec/SpecRunnerUtils"), - FileUtils = require("file/FileUtils"), - KeyEvent = require("utils/KeyEvent"), - _ = require("thirdparty/lodash"); + const SpecRunnerUtils = require("spec/SpecRunnerUtils"), + KeyEvent = require("utils/KeyEvent"); describe("integration:Recent Projects", function () { const testFolder = SpecRunnerUtils.getTestPath("/spec/LiveDevelopment-MultiBrowser-test-files"), @@ -36,17 +34,12 @@ define(function (require, exports, module) { const testFolderProjectName = "LiveDevelopment-MultiBrowser-test-files", prettierTestFolderProjectName = "prettier-test-files", jsUtilsTestFolderProjectName = "JSUtils-test-files"; - let extensionPath = FileUtils.getNativeModuleDirectoryPath(module), - testWindow, - $, - CommandManager, - PreferencesManager; + let testWindow, + $; beforeAll(async function () { testWindow = await SpecRunnerUtils.createTestWindowAndRun(); $ = testWindow.$; - CommandManager = testWindow.brackets.test.CommandManager; - PreferencesManager = testWindow.brackets.test.PreferencesManager; }, 30000); afterAll(async function () { @@ -55,7 +48,9 @@ define(function (require, exports, module) { }, 30000); async function openRecentProjectDropDown() { - CommandManager.execute("recentProjects.toggle"); + if(!$("#project-dropdown").is(":visible")){ + $("#project-dropdown-toggle").click(); + } await awaitsFor(function () { return $("#project-dropdown").is(":visible"); });