@@ -28,6 +28,9 @@ define(function (require, exports, module) {
2828 const HTMLInstrumentation = require ( "LiveDevelopment/MultiBrowserImpl/language/HTMLInstrumentation" ) ;
2929 const LiveDevMultiBrowser = require ( "LiveDevelopment/LiveDevMultiBrowser" ) ;
3030 const CodeMirror = require ( "thirdparty/CodeMirror/lib/codemirror" ) ;
31+ const ProjectManager = require ( "project/ProjectManager" ) ;
32+ const FileSystem = require ( "filesystem/FileSystem" ) ;
33+ const PathUtils = require ( "thirdparty/path-utils/path-utils" ) ;
3134
3235 /**
3336 * This function syncs text content changes between the original source code
@@ -593,6 +596,204 @@ define(function (require, exports, module) {
593596 // write the AI implementation here...@abose
594597 }
595598
599+ /**
600+ * this is a helper function to make sure that when saving a new image, there's no existing file with the same name
601+ * @param {String } basePath - this is the base path where the image will be saved
602+ * @param {String } filename - the name of the image file
603+ * @param {String } extnName - the name of the image extension. (defaults to "jpg")
604+ * @returns {String } - the new file name
605+ */
606+ function getUniqueFilename ( basePath , filename , extnName ) {
607+ let counter = 0 ;
608+ let uniqueFilename = filename + extnName ;
609+
610+ function checkAndIncrement ( ) {
611+ const filePath = basePath + uniqueFilename ;
612+ const file = FileSystem . getFileForPath ( filePath ) ;
613+
614+ return new Promise ( ( resolve ) => {
615+ file . exists ( ( err , exists ) => {
616+ if ( exists ) {
617+ counter ++ ;
618+ uniqueFilename = `${ filename } -${ counter } ${ extnName } ` ;
619+ checkAndIncrement ( ) . then ( resolve ) ;
620+ } else {
621+ resolve ( uniqueFilename ) ;
622+ }
623+ } ) ;
624+ } ) ;
625+ }
626+
627+ return checkAndIncrement ( ) ;
628+ }
629+
630+ /**
631+ * This function updates the src attribute of an image element in the source code
632+ * @param {Number } tagId - the data-brackets-id of the image element
633+ * @param {String } newSrcValue - the new src value to set
634+ */
635+ function _updateImageSrcAttribute ( tagId , newSrcValue ) {
636+ const editor = _getEditorAndValidate ( tagId ) ;
637+ if ( ! editor ) {
638+ return ;
639+ }
640+
641+ const range = _getElementRange ( editor , tagId ) ;
642+ if ( ! range ) {
643+ return ;
644+ }
645+
646+ const { startPos, endPos } = range ;
647+ const elementText = editor . getTextBetween ( startPos , endPos ) ;
648+
649+ // parse it using DOM parser so that we can update the src attribute
650+ const parser = new DOMParser ( ) ;
651+ const doc = parser . parseFromString ( elementText , "text/html" ) ;
652+ const imgElement = doc . querySelector ( 'img' ) ;
653+
654+ if ( imgElement ) {
655+ imgElement . setAttribute ( 'src' , newSrcValue ) ;
656+ const updatedElementText = imgElement . outerHTML ;
657+
658+ editor . document . batchOperation ( function ( ) {
659+ editor . replaceRange ( updatedElementText , startPos , endPos ) ;
660+ } ) ;
661+ }
662+ }
663+
664+ /**
665+ * Helper function to update image src attribute and dismiss ribbon gallery
666+ *
667+ * @param {Number } tagId - the data-brackets-id of the image element
668+ * @param {String } targetPath - the full path where the image was saved
669+ * @param {String } filename - the filename of the saved image
670+ */
671+ function _updateImageAndDismissRibbon ( tagId , targetPath , filename ) {
672+ const editor = _getEditorAndValidate ( tagId ) ;
673+ if ( editor ) {
674+ const htmlFilePath = editor . document . file . fullPath ;
675+ const relativePath = PathUtils . makePathRelative ( targetPath , htmlFilePath ) ;
676+ _updateImageSrcAttribute ( tagId , relativePath ) ;
677+ } else {
678+ _updateImageSrcAttribute ( tagId , filename ) ;
679+ }
680+
681+ // dismiss the image ribbon gallery
682+ const currLiveDoc = LiveDevMultiBrowser . getCurrentLiveDoc ( ) ;
683+ if ( currLiveDoc && currLiveDoc . protocol && currLiveDoc . protocol . evaluate ) {
684+ currLiveDoc . protocol . evaluate ( "_LD.dismissImageRibbonGallery()" ) ;
685+ }
686+ }
687+
688+ /**
689+ * helper function to handle 'upload from computer'
690+ * @param {Object } message - the message object
691+ * @param {String } filename - the file name with which we need to save the image
692+ * @param {Directory } projectRoot - the project root in which the image is to be saved
693+ */
694+ function _handleUseThisImageLocalFiles ( message , filename , projectRoot ) {
695+ const { tagId, imageData } = message ;
696+
697+ const uint8Array = new Uint8Array ( imageData ) ;
698+ const targetPath = projectRoot . fullPath + filename ;
699+
700+ window . fs . writeFile ( targetPath , window . Filer . Buffer . from ( uint8Array ) ,
701+ { encoding : window . fs . BYTE_ARRAY_ENCODING } , ( err ) => {
702+ if ( err ) {
703+ console . error ( 'Failed to save image:' , err ) ;
704+ } else {
705+ _updateImageAndDismissRibbon ( tagId , targetPath , filename ) ;
706+ }
707+ } ) ;
708+ }
709+
710+ /**
711+ * helper function to handle 'use this image' button click on remote images
712+ * @param {Object } message - the message object
713+ * @param {String } filename - the file name with which we need to save the image
714+ * @param {Directory } projectRoot - the project root in which the image is to be saved
715+ */
716+ function _handleUseThisImageRemote ( message , filename , projectRoot ) {
717+ const { imageUrl, tagId } = message ;
718+
719+ fetch ( imageUrl )
720+ . then ( response => {
721+ if ( ! response . ok ) {
722+ throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
723+ }
724+ return response . arrayBuffer ( ) ;
725+ } )
726+ . then ( arrayBuffer => {
727+ const uint8Array = new Uint8Array ( arrayBuffer ) ;
728+ const targetPath = projectRoot . fullPath + filename ;
729+
730+ window . fs . writeFile ( targetPath , window . Filer . Buffer . from ( uint8Array ) ,
731+ { encoding : window . fs . BYTE_ARRAY_ENCODING } , ( err ) => {
732+ if ( err ) {
733+ console . error ( 'Failed to save image:' , err ) ;
734+ } else {
735+ _updateImageAndDismissRibbon ( tagId , targetPath , filename ) ;
736+ }
737+ } ) ;
738+ } )
739+ . catch ( error => {
740+ console . error ( 'Failed to fetch image:' , error ) ;
741+ } ) ;
742+ }
743+
744+ /**
745+ * This function is called when 'use this image' button is clicked in the image ribbon gallery
746+ * or user loads an image file from the computer
747+ * this is responsible to download the image in the appropriate place
748+ * and also change the src attribute of the element (by calling appropriate helper functions)
749+ * @param {Object } message - the message object which stores all the required data for this operation
750+ */
751+ function _handleUseThisImage ( message ) {
752+ const filename = message . filename ;
753+ const extnName = message . extnName || "jpg" ;
754+
755+ const projectRoot = ProjectManager . getProjectRoot ( ) ;
756+ if ( ! projectRoot ) { return ; }
757+
758+ // phoenix-assets folder, all the images will be stored inside this
759+ const phoenixAssetsPath = projectRoot . fullPath + "phoenix-code-assets/" ;
760+ const phoenixAssetsDir = FileSystem . getDirectoryForPath ( phoenixAssetsPath ) ;
761+
762+ // check if the phoenix-assets dir exists
763+ // if present, download the image inside it, if not create the dir and then download the image inside it
764+ phoenixAssetsDir . exists ( ( err , exists ) => {
765+ if ( err ) { return ; }
766+
767+ if ( ! exists ) {
768+ phoenixAssetsDir . create ( ( err ) => {
769+ if ( err ) {
770+ console . error ( 'Error creating phoenix-code-assets directory:' , err ) ;
771+ return ;
772+ }
773+ _downloadImageToPhoenixAssets ( message , filename , extnName , phoenixAssetsDir ) ;
774+ } ) ;
775+ } else {
776+ _downloadImageToPhoenixAssets ( message , filename , extnName , phoenixAssetsDir ) ;
777+ }
778+ } ) ;
779+ }
780+
781+ /**
782+ * Helper function to download image to phoenix-assets folder
783+ */
784+ function _downloadImageToPhoenixAssets ( message , filename , extnName , phoenixAssetsDir ) {
785+ getUniqueFilename ( phoenixAssetsDir . fullPath , filename , extnName ) . then ( ( uniqueFilename ) => {
786+ // check if the image is loaded from computer or from remote
787+ if ( message . isLocalFile && message . imageData ) {
788+ _handleUseThisImageLocalFiles ( message , uniqueFilename , phoenixAssetsDir ) ;
789+ } else {
790+ _handleUseThisImageRemote ( message , uniqueFilename , phoenixAssetsDir ) ;
791+ }
792+ } ) . catch ( error => {
793+ console . error ( 'Something went wrong when trying to use this image' , error ) ;
794+ } ) ;
795+ }
796+
596797 /**
597798 * This is the main function that is exported.
598799 * it will be called by LiveDevProtocol when it receives a message from RemoteFunctions.js
@@ -623,6 +824,12 @@ define(function (require, exports, module) {
623824 return ;
624825 }
625826
827+ // use this image
828+ if ( message . useImage && message . imageUrl && message . filename ) {
829+ _handleUseThisImage ( message ) ;
830+ return ;
831+ }
832+
626833 if ( ! message . element || ! message . tagId ) {
627834 // check for undo
628835 if ( message . undoLivePreviewOperation || message . redoLivePreviewOperation ) {
0 commit comments