@@ -2099,6 +2099,24 @@ function RemoteFunctions(config = {}) {
20992099 cursor: pointer !important;
21002100 }
21012101
2102+ .phoenix-select-image-btn {
2103+ background: rgba(255,255,255,0.1) !important;
2104+ border: 1px solid rgba(255,255,255,0.2) !important;
2105+ color: #e8eaf0 !important;
2106+ padding: 6px 12px !important;
2107+ border-radius: 6px !important;
2108+ font-size: 12px !important;
2109+ cursor: pointer !important;
2110+ margin-left: 8px !important;
2111+ white-space: nowrap !important;
2112+ transition: all 0.2s ease !important;
2113+ }
2114+
2115+ .phoenix-select-image-btn:hover {
2116+ background: rgba(255,255,255,0.2) !important;
2117+ border-color: rgba(255,255,255,0.3) !important;
2118+ }
2119+
21022120 .phoenix-ribbon-close {
21032121 background: rgba(0,0,0,0.5) !important;
21042122 border: none !important;
@@ -2192,6 +2210,8 @@ function RemoteFunctions(config = {}) {
21922210 <div class="phoenix-ribbon-search">
21932211 <input type="text" placeholder="Search images..." />
21942212 <button class="phoenix-ribbon-search-btn">Search</button>
2213+ <button class="phoenix-select-image-btn">📁 Select from Computer</button>
2214+ <input type="file" class="phoenix-file-input" accept="image/*" style="display: none;">
21952215 </div>
21962216 <button class="phoenix-ribbon-close">✕</button>
21972217 </div>
@@ -2380,11 +2400,14 @@ function RemoteFunctions(config = {}) {
23802400 } ,
23812401
23822402 _attachEventHandlers : function ( ) {
2403+ const ribbonContainer = this . _shadow . querySelector ( '.phoenix-image-ribbon' ) ;
23832404 const searchInput = this . _shadow . querySelector ( '.phoenix-ribbon-search input' ) ;
23842405 const searchButton = this . _shadow . querySelector ( '.phoenix-ribbon-search-btn' ) ;
23852406 const closeButton = this . _shadow . querySelector ( '.phoenix-ribbon-close' ) ;
23862407 const navLeft = this . _shadow . querySelector ( '.phoenix-ribbon-nav.left' ) ;
23872408 const navRight = this . _shadow . querySelector ( '.phoenix-ribbon-nav.right' ) ;
2409+ const selectImageBtn = this . _shadow . querySelector ( '.phoenix-select-image-btn' ) ;
2410+ const fileInput = this . _shadow . querySelector ( '.phoenix-file-input' ) ;
23882411
23892412 if ( searchInput && searchButton ) {
23902413 const performSearch = ( e ) => {
@@ -2411,6 +2434,22 @@ function RemoteFunctions(config = {}) {
24112434 } ) ;
24122435 }
24132436
2437+ if ( selectImageBtn && fileInput ) {
2438+ selectImageBtn . addEventListener ( 'click' , ( e ) => {
2439+ e . stopPropagation ( ) ;
2440+ fileInput . click ( ) ;
2441+ } ) ;
2442+
2443+ fileInput . addEventListener ( 'change' , ( e ) => {
2444+ e . stopPropagation ( ) ;
2445+ const file = e . target . files [ 0 ] ;
2446+ if ( file ) {
2447+ this . _handleLocalImageSelection ( file ) ;
2448+ fileInput . value = '' ;
2449+ }
2450+ } ) ;
2451+ }
2452+
24142453 if ( closeButton ) {
24152454 closeButton . addEventListener ( 'click' , ( e ) => {
24162455 e . stopPropagation ( ) ;
@@ -2433,7 +2472,6 @@ function RemoteFunctions(config = {}) {
24332472 }
24342473
24352474 // Prevent clicks anywhere inside the ribbon from bubbling up
2436- const ribbonContainer = this . _shadow . querySelector ( '.phoenix-image-ribbon' ) ;
24372475 if ( ribbonContainer ) {
24382476 ribbonContainer . addEventListener ( 'click' , ( e ) => {
24392477 e . stopPropagation ( ) ;
@@ -2521,7 +2559,7 @@ function RemoteFunctions(config = {}) {
25212559 e . preventDefault ( ) ;
25222560 const filename = this . _generateFilename ( image ) ;
25232561 const extnName = ".jpg" ;
2524- this . _useImage ( image . url , filename , extnName ) ;
2562+ this . _useImage ( image . url , filename , extnName , false ) ;
25252563 } ) ;
25262564
25272565 thumbDiv . appendChild ( img ) ;
@@ -2569,21 +2607,67 @@ function RemoteFunctions(config = {}) {
25692607 return `${ cleanSearchTerm } -by-${ cleanPhotographerName } ` ;
25702608 } ,
25712609
2572- _useImage : function ( imageUrl , filename , extnName ) {
2610+ _useImage : function ( imageUrl , filename , extnName , isLocalFile ) {
25732611 // send the message to the editor instance to save the image and update the source code
25742612 const tagId = this . element . getAttribute ( "data-brackets-id" ) ;
25752613
2576- window . _Brackets_MessageBroker . send ( {
2614+ const messageData = {
25772615 livePreviewEditEnabled : true ,
25782616 useImage : true ,
25792617 imageUrl : imageUrl ,
25802618 filename : filename ,
25812619 extnName : extnName ,
25822620 element : this . element ,
25832621 tagId : Number ( tagId )
2584- } ) ;
2622+ } ;
2623+
2624+ // if this is a local file we need some more data before sending it to the editor
2625+ if ( isLocalFile ) {
2626+ messageData . isLocalFile = true ;
2627+ // Convert data URL to binary data array for local files
2628+ const byteCharacters = atob ( imageUrl . split ( ',' ) [ 1 ] ) ;
2629+ const byteNumbers = new Array ( byteCharacters . length ) ;
2630+ for ( let i = 0 ; i < byteCharacters . length ; i ++ ) {
2631+ byteNumbers [ i ] = byteCharacters . charCodeAt ( i ) ;
2632+ }
2633+ messageData . imageData = byteNumbers ;
2634+ }
2635+
2636+ window . _Brackets_MessageBroker . send ( messageData ) ;
25852637 } ,
25862638
2639+ _handleLocalImageSelection : function ( file ) {
2640+ if ( ! file || ! file . type . startsWith ( 'image/' ) ) {
2641+ return ;
2642+ }
2643+
2644+ const reader = new FileReader ( ) ;
2645+ reader . onload = ( e ) => {
2646+ const imageDataUrl = e . target . result ;
2647+
2648+ const originalName = file . name ;
2649+ const nameWithoutExt = originalName . substring ( 0 , originalName . lastIndexOf ( '.' ) ) || originalName ;
2650+ const extension = originalName . substring ( originalName . lastIndexOf ( '.' ) ) || '.jpg' ;
2651+
2652+ // we clean the file name because the file might have some chars which might not be compatible
2653+ const cleanName = nameWithoutExt . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 ] / g, '-' ) . replace ( / - + / g, '-' ) . replace ( / ^ - | - $ / g, '' ) ;
2654+ const filename = cleanName || 'selected-image' ;
2655+
2656+ // Use the unified _useImage method with isLocalFile flag
2657+ this . _useImage ( imageDataUrl , filename , extension , true ) ;
2658+
2659+ // Close the ribbon after successful selection
2660+ this . remove ( ) ;
2661+ } ;
2662+
2663+ reader . onerror = ( error ) => {
2664+ console . error ( 'Something went wrong when reading the image:' , error ) ;
2665+ } ;
2666+
2667+ reader . readAsDataURL ( file ) ;
2668+ } ,
2669+
2670+
25872671 create : function ( ) {
25882672 this . remove ( ) ; // remove existing ribbon if already present
25892673
0 commit comments