Skip to content

Commit a0e4ae8

Browse files
committed
feat: show folder suggestions when typing a folder name in the dialog
1 parent a0634ef commit a0e4ae8

File tree

3 files changed

+231
-13
lines changed

3 files changed

+231
-13
lines changed

src/LiveDevelopment/LivePreviewEdit.js

Lines changed: 136 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ define(function (require, exports, module) {
3131
const ProjectManager = require("project/ProjectManager");
3232
const FileSystem = require("filesystem/FileSystem");
3333
const PathUtils = require("thirdparty/path-utils/path-utils");
34+
const StringMatch = require("utils/StringMatch");
3435
const Dialogs = require("widgets/Dialogs");
3536
const ProDialogs = require("services/pro-dialogs");
3637
const ImageFolderDialogTemplate = require("text!htmlContent/image-folder-dialog.html");
@@ -799,6 +800,120 @@ define(function (require, exports, module) {
799800
});
800801
}
801802

803+
// these folders are generally very large, and we don't scan them otherwise it might freeze the UI
804+
const EXCLUDED_FOLDERS = ['node_modules', 'bower_components', '.git', '.npm', '.yarn'];
805+
806+
/**
807+
* this function scans all the directories recursively
808+
* and then add the relative paths of the directories to the folderList array
809+
*
810+
* @param {Directory} directory - The parent directory to scan
811+
* @param {string} relativePath - The relative path from project root
812+
* @param {Array<string>} folderList - Array to store all discovered folder paths
813+
* @return {Promise} Resolves when scanning is complete
814+
*/
815+
function _scanDirectories(directory, relativePath, folderList) {
816+
return new Promise((resolve) => {
817+
directory.getContents((err, contents) => {
818+
if (err) {
819+
resolve();
820+
return;
821+
}
822+
823+
const directories = contents.filter(entry => entry.isDirectory);
824+
const scanPromises = [];
825+
826+
directories.forEach(dir => {
827+
// if its an excluded folder we ignore it
828+
if (EXCLUDED_FOLDERS.includes(dir.name)) {
829+
return;
830+
}
831+
832+
const dirRelativePath = relativePath ? `${relativePath}${dir.name}/` : `${dir.name}/`;
833+
folderList.push(dirRelativePath);
834+
835+
// also check subdirectories for this dir
836+
scanPromises.push(_scanDirectories(dir, dirRelativePath, folderList));
837+
});
838+
839+
Promise.all(scanPromises).then(() => resolve());
840+
});
841+
});
842+
}
843+
844+
/**
845+
* Renders folder suggestions as a dropdown in the UI with fuzzy match highlighting
846+
*
847+
* @param {Array<string|Object>} matches - Array of folder paths (strings) or fuzzy match objects with stringRanges
848+
* @param {JQuery} $suggestions - jQuery element for the suggestions container
849+
* @param {JQuery} $input - jQuery element for the input field
850+
*/
851+
function _renderFolderSuggestions(matches, $suggestions, $input) {
852+
if (matches.length === 0) {
853+
$suggestions.empty();
854+
return;
855+
}
856+
857+
let html = '<ul class="folder-suggestions-list">';
858+
matches.forEach((match) => {
859+
let displayHTML = '';
860+
let folderPath = '';
861+
862+
// Check if match is a string or an object
863+
if (typeof match === 'string') {
864+
// Simple string (from empty query showing folders)
865+
displayHTML = match;
866+
folderPath = match;
867+
} else if (match && match.stringRanges) {
868+
// fuzzy match, highlight matched chars
869+
match.stringRanges.forEach(range => {
870+
if (range.matched) {
871+
displayHTML += `<span class="folder-match-highlight">${range.text}</span>`;
872+
} else {
873+
displayHTML += range.text;
874+
}
875+
});
876+
folderPath = match.label || '';
877+
}
878+
879+
html += `<li class="folder-suggestion-item" data-path="${folderPath}">${displayHTML}</li>`;
880+
});
881+
html += '</ul>';
882+
883+
$suggestions.html(html);
884+
885+
// when a suggestion is clicked we add the folder path in the input box
886+
$suggestions.find('.folder-suggestion-item').on('click', function() {
887+
const folderPath = $(this).data('path');
888+
$input.val(folderPath);
889+
$suggestions.empty();
890+
});
891+
}
892+
893+
/**
894+
* This function is responsible to update the folder suggestion everytime a new char is inserted in the input field
895+
*
896+
* @param {string} query - The search query from the input field
897+
* @param {Array<string>} folderList - List of all available folder paths
898+
* @param {StringMatch.StringMatcher} stringMatcher - StringMatcher instance for fuzzy matching
899+
* @param {JQuery} $suggestions - jQuery element for the suggestions container
900+
* @param {JQuery} $input - jQuery element for the input field
901+
*/
902+
function _updateFolderSuggestions(query, folderList, stringMatcher, $suggestions, $input) {
903+
if (!query || query.trim() === '') {
904+
return;
905+
}
906+
907+
// filter folders using fuzzy matching
908+
const matches = folderList
909+
.map(folder => stringMatcher.match(folder, query))
910+
.filter(result => result !== null && result !== undefined)
911+
.sort((a, b) => b.matchGoodness - a.matchGoodness)
912+
.slice(0, 5);
913+
914+
_renderFolderSuggestions(matches, $suggestions, $input);
915+
}
916+
802917
/**
803918
* This function is called when 'use this image' button is clicked in the image ribbon gallery
804919
* or user loads an image file from the computer
@@ -808,33 +923,43 @@ define(function (require, exports, module) {
808923
* @param {Object} message - the message object which stores all the required data for this operation
809924
*/
810925
function _handleUseThisImage(message) {
926+
const projectRoot = ProjectManager.getProjectRoot();
927+
if (!projectRoot) { return; }
928+
811929
// show the dialog with a text box to select a folder
812930
// dialog html is written in 'image-folder-dialog.html'
813931
const dialog = Dialogs.showModalDialogUsingTemplate(ImageFolderDialogTemplate, false);
814932
const $dlg = dialog.getElement();
815933
const $input = $dlg.find("#folder-path-input");
934+
const $suggestions = $dlg.find("#folder-suggestions");
935+
936+
let folderList = [];
937+
let stringMatcher = null;
938+
939+
// Scan project directories and setup event handlers
940+
_scanDirectories(projectRoot, '', folderList).then(() => {
941+
stringMatcher = new StringMatch.StringMatcher({ segmentedSearch: true });
942+
943+
// input event handler
944+
$input.on('input', function() {
945+
_updateFolderSuggestions($input.val(), folderList, stringMatcher, $suggestions, $input);
946+
});
947+
});
816948

817949
// focus the input box
818950
setTimeout(function() {
819951
$input.focus();
820952
}, 100);
821953

822954
// handle dialog button clicks
955+
// so the logic is either its an ok button click or cancel button click, so if its ok click
956+
// then we download image in that folder and close the dialog, in close btn click we directly close the dialog
823957
$dlg.one("buttonClick", function(e, buttonId) {
824958
if (buttonId === Dialogs.DIALOG_BTN_OK) {
825959
const folderPath = $input.val().trim();
826-
dialog.close();
827-
// if folder path is specified we download in that folder
828-
// else we download in the project root
829-
if (folderPath) {
830-
_downloadToFolder(message, folderPath);
831-
} else {
832-
_downloadToFolder(message, '');
833-
}
834-
} else if (buttonId === Dialogs.DIALOG_BTN_CANCEL) {
835-
// if cancel is clicked, we abort the download
836-
dialog.close();
960+
_downloadToFolder(message, folderPath);
837961
}
962+
dialog.close();
838963
});
839964
}
840965

src/htmlContent/image-folder-dialog.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ <h1 class="dialog-title">Select Folder to Save Image</h1>
99
placeholder="Type folder path (e.g., assets/images/)"
1010
value=""
1111
autocomplete="off"
12-
spellcheck="false"
13-
style="width: 100%; height: 30px; padding: 5px; box-sizing: border-box;">
12+
spellcheck="false">
13+
14+
<!-- the folder suggestions will come here dynamically -->
15+
<div id="folder-suggestions"></div>
16+
17+
<p class="folder-help-text">
18+
💡 Tip: Type to filter folders or create a new path
19+
</p>
1420
</div>
1521
<div class="modal-footer">
1622
<button class="dialog-button btn" data-button-id="cancel">Cancel</button>

src/styles/brackets_patterns_override.less

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2518,3 +2518,90 @@ code {
25182518
}
25192519
}
25202520
}
2521+
2522+
// image folder selection dialog
2523+
.image-folder-dialog {
2524+
#folder-path-input {
2525+
width: 100%;
2526+
height: 30px;
2527+
padding: 5px;
2528+
box-sizing: border-box;
2529+
margin-bottom: 8px;
2530+
}
2531+
2532+
#folder-suggestions {
2533+
max-height: 180px;
2534+
overflow-y: auto;
2535+
overflow-x: hidden;
2536+
border: 1px solid @bc-btn-border;
2537+
border-radius: @bc-border-radius;
2538+
background-color: @bc-panel-bg-alt;
2539+
2540+
.dark & {
2541+
border: 1px solid @dark-bc-btn-border;
2542+
background-color: @dark-bc-panel-bg-alt;
2543+
}
2544+
2545+
&:empty {
2546+
display: none;
2547+
}
2548+
2549+
.folder-suggestions-list {
2550+
margin: 0;
2551+
padding: 0;
2552+
list-style: none;
2553+
}
2554+
2555+
.folder-suggestion-item {
2556+
padding: 6px 10px;
2557+
cursor: pointer;
2558+
font-size: 12px;
2559+
color: @bc-text;
2560+
transition: background-color 0.1s;
2561+
border-left: 3px solid transparent;
2562+
2563+
.dark & {
2564+
color: @dark-bc-text;
2565+
}
2566+
2567+
&:hover {
2568+
background-color: @bc-panel-bg-hover-alt;
2569+
2570+
.dark & {
2571+
background-color: @dark-bc-panel-bg-hover-alt;
2572+
}
2573+
}
2574+
2575+
&.highlight {
2576+
background-color: @bc-bg-highlight;
2577+
border-left-color: @bc-primary-btn-bg;
2578+
2579+
.dark & {
2580+
background-color: @dark-bc-bg-highlight;
2581+
border-left-color: @dark-bc-primary-btn-bg;
2582+
}
2583+
}
2584+
}
2585+
2586+
.folder-match-highlight {
2587+
font-weight: @font-weight-semibold;
2588+
color: @bc-primary-btn-bg;
2589+
2590+
.dark & {
2591+
color: @dark-bc-primary-btn-bg;
2592+
}
2593+
}
2594+
}
2595+
2596+
.folder-help-text {
2597+
margin-top: 8px;
2598+
margin-bottom: 0;
2599+
font-size: 11px;
2600+
color: @bc-text-quiet;
2601+
user-select: none;
2602+
2603+
.dark & {
2604+
color: @dark-bc-text-quiet;
2605+
}
2606+
}
2607+
}

0 commit comments

Comments
 (0)