Skip to content

Commit 1681baa

Browse files
committed
deploy: 606b7d0
1 parent b31d56d commit 1681baa

File tree

77 files changed

+6800
-540
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+6800
-540
lines changed

LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 1130 additions & 109 deletions
Large diffs are not rendered by default.

LiveDevelopment/LiveDevMultiBrowser.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,16 @@ define(function (require, exports, module) {
715715
function dismissLivePreviewBoxes() {
716716
if (_protocol) {
717717
_protocol.evaluate("_LD.dismissUIAndCleanupState()");
718+
_protocol.evaluate("_LD.dismissImageRibbonGallery()");
719+
}
720+
}
721+
722+
/**
723+
* Dismiss image ribbon gallery if it's open
724+
*/
725+
function dismissImageRibbonGallery() {
726+
if (_protocol) {
727+
_protocol.evaluate("_LD.dismissImageRibbonGallery()");
718728
}
719729
}
720730

@@ -804,6 +814,7 @@ define(function (require, exports, module) {
804814
exports.redrawHighlight = redrawHighlight;
805815
exports.hasVisibleLivePreviewBoxes = hasVisibleLivePreviewBoxes;
806816
exports.dismissLivePreviewBoxes = dismissLivePreviewBoxes;
817+
exports.dismissImageRibbonGallery = dismissImageRibbonGallery;
807818
exports.registerHandlers = registerHandlers;
808819
exports.updateConfig = updateConfig;
809820
exports.init = init;

LiveDevelopment/LivePreviewEdit.js

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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) {

LiveDevelopment/main.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ define(function main(require, exports, module) {
7070
},
7171
isProUser: isProUser,
7272
elemHighlights: "hover", // default value, this will get updated when the extension loads
73+
imageRibbon: true, // default value, this will get updated when the extension loads
7374
// this strings are used in RemoteFunctions.js
7475
// we need to pass this through config as remoteFunctions runs in browser context and cannot
7576
// directly reference Strings file
@@ -79,7 +80,11 @@ define(function main(require, exports, module) {
7980
duplicate: Strings.LIVE_DEV_MORE_OPTIONS_DUPLICATE,
8081
delete: Strings.LIVE_DEV_MORE_OPTIONS_DELETE,
8182
ai: Strings.LIVE_DEV_MORE_OPTIONS_AI,
82-
aiPromptPlaceholder: Strings.LIVE_DEV_AI_PROMPT_PLACEHOLDER
83+
aiPromptPlaceholder: Strings.LIVE_DEV_AI_PROMPT_PLACEHOLDER,
84+
imageGalleryUseImage: Strings.LIVE_DEV_IMAGE_GALLERY_USE_IMAGE,
85+
imageGallerySelectFromComputer: Strings.LIVE_DEV_IMAGE_GALLERY_SELECT_FROM_COMPUTER,
86+
imageGalleryChooseFolder: Strings.LIVE_DEV_IMAGE_GALLERY_CHOOSE_FOLDER,
87+
imageGallerySearchPlaceholder: Strings.LIVE_DEV_IMAGE_GALLERY_SEARCH_PLACEHOLDER
8388
}
8489
};
8590
// Status labels/styles are ordered: error, not connected, progress1, progress2, connected.
@@ -365,6 +370,20 @@ define(function main(require, exports, module) {
365370
}
366371
}
367372

373+
// this function is responsible to update image picker config
374+
// called from live preview extension when preference changes
375+
function updateImageRibbonConfig() {
376+
const prefValue = PreferencesManager.get("livePreviewImagePicker");
377+
config.imageRibbon = prefValue !== false; // default to true if undefined
378+
379+
if (MultiBrowserLiveDev && MultiBrowserLiveDev.status >= MultiBrowserLiveDev.STATUS_ACTIVE) {
380+
if (!prefValue) { MultiBrowserLiveDev.dismissImageRibbonGallery(); } // to remove any existing image ribbons
381+
382+
MultiBrowserLiveDev.updateConfig(JSON.stringify(config));
383+
MultiBrowserLiveDev.registerHandlers();
384+
}
385+
}
386+
368387
// init commands
369388
CommandManager.register(Strings.CMD_LIVE_HIGHLIGHT, Commands.FILE_LIVE_HIGHLIGHT, togglePreviewHighlight);
370389
CommandManager.register(Strings.CMD_RELOAD_LIVE_PREVIEW, Commands.CMD_RELOAD_LIVE_PREVIEW, _handleReloadLivePreviewCommand);
@@ -393,6 +412,7 @@ define(function main(require, exports, module) {
393412
exports.togglePreviewHighlight = togglePreviewHighlight;
394413
exports.setLivePreviewEditFeaturesActive = setLivePreviewEditFeaturesActive;
395414
exports.updateElementHighlightConfig = updateElementHighlightConfig;
415+
exports.updateImageRibbonConfig = updateImageRibbonConfig;
396416
exports.getConnectionIds = MultiBrowserLiveDev.getConnectionIds;
397417
exports.getLivePreviewDetails = MultiBrowserLiveDev.getLivePreviewDetails;
398418
exports.hideHighlight = MultiBrowserLiveDev.hideHighlight;

appConfig.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ window.AppConfig = {
3131
"app_notification_url": "assets/notifications/dev/",
3232
"app_update_url": "https://updates.phcode.io/tauri/update-latest-experimental-build.json",
3333
"linting.enabled_by_default": true,
34-
"build_timestamp": "2025-09-24T13:41:33.530Z",
34+
"build_timestamp": "2025-09-25T02:25:16.972Z",
3535
"googleAnalyticsID": "G-P4HJFPDB76",
3636
"googleAnalyticsIDDesktop": "G-VE5BXWJ0HF",
3737
"mixPanelID": "49c4d164b592be2350fc7af06a259bf3",
@@ -43,7 +43,7 @@ window.AppConfig = {
4343
"bugsnagEnv": "development"
4444
},
4545
"name": "Phoenix Code",
46-
"version": "4.1.2-21493",
46+
"version": "4.1.2-21534",
4747
"apiVersion": "4.1.2",
4848
"homepage": "https://core.ai",
4949
"issues": {

assets/default-project/en.zip

0 Bytes
Binary file not shown.

assets/sample-projects/HTML5.zip

0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

assets/sample-projects/explore.zip

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)