@@ -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
0 commit comments