Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4146e44
refactor: improve image gallery UI
devvaannsh Oct 25, 2025
190f63a
fix: add missing titles to buttons
devvaannsh Oct 25, 2025
6b49fe1
refactor: use ellipsis char instead of three dots
devvaannsh Oct 25, 2025
81eb0f5
fix: image gallery nav buttons styling
devvaannsh Oct 25, 2025
15d209b
fix: image flicker issue
devvaannsh Oct 25, 2025
c4e7553
refactor: modern image gallery UI
devvaannsh Nov 2, 2025
bab982a
refactor: update classnames when selecting elements in DOM
devvaannsh Nov 2, 2025
664e4db
fix: redundant margin properties pushing down images
devvaannsh Nov 2, 2025
cc40092
fix: image gallery moving when live preview is scrolled due to absolu…
devvaannsh Nov 2, 2025
b2100ca
fix: give some space to allow elements to breathe
devvaannsh Nov 2, 2025
0cf38e7
feat: localize missing strings for button title
devvaannsh Nov 2, 2025
897453e
fix: hide nav buttons and keep image gallery height consistent when s…
devvaannsh Nov 2, 2025
fef347b
feat: hide image gallery title when live preview width is reduced too…
devvaannsh Nov 2, 2025
4ec8b76
feat: show image gallery by default, saved its state in state manager
devvaannsh Nov 3, 2025
f17e309
fix: add missing !important in styles
devvaannsh Nov 3, 2025
b17963d
fix: some image thumbnails always gets cut because of incorrect image…
devvaannsh Nov 3, 2025
a63ca5c
refactor: reduce hover lock timer to 800ms
devvaannsh Nov 3, 2025
f5f27bc
fix: element highlighting overlapping image gallery
devvaannsh Nov 4, 2025
200f411
fix: alignment incorrect on different machines
devvaannsh Nov 4, 2025
543aea2
refactor: use a slight dimmer color for search input as fff felt too …
devvaannsh Nov 4, 2025
fa2e25f
fix: nav buttons not disappearing when on error or loading state
devvaannsh Nov 4, 2025
4eadbd4
refactor: improve styles of folder suggestions dialog
devvaannsh Nov 5, 2025
c146078
fix: multiple API calls being made to unsplash on initial load
devvaannsh Nov 5, 2025
c4111ae
fix: store first 50 images in cache instead of last 50 to always show…
devvaannsh Nov 5, 2025
d140da7
fix: download call not authorized error fix
devvaannsh Nov 6, 2025
c5c83ba
fix: architectural issue with code flow on image downloads
devvaannsh Nov 6, 2025
8abd172
fix: folder save dialog checkbox doesn't remember previous state
devvaannsh Nov 6, 2025
e74e07c
fix: add max-width to image gallery so that on larger screens there's…
devvaannsh Nov 6, 2025
90a34c2
feat: also show elements dimensions in info box
devvaannsh Nov 7, 2025
1373bca
feat: show info box even for JS rendered elements
devvaannsh Nov 8, 2025
d289dbc
fix: info box for non-editable elements not appearing when highlight …
devvaannsh Nov 8, 2025
60ca314
feat: show toast notification when clicked on non-editable elements
devvaannsh Nov 8, 2025
300f86c
fix: info box not getting out of bounds for non-editable elements whe…
devvaannsh Nov 8, 2025
92ae1fb
feat: allow closing the toast notification with a dont show again button
devvaannsh Nov 9, 2025
2bce9c5
feat: localize strings used in toast notification
devvaannsh Nov 9, 2025
8987188
fix: apostrophe in strings breaks the config json string
devvaannsh Nov 9, 2025
6d41401
fix: live preview edit working unexpectedly on popped out live previews
devvaannsh Nov 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
991 changes: 653 additions & 338 deletions src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion src/LiveDevelopment/LiveDevMultiBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ define(function (require, exports, module) {
const urlString = `${url.origin}${url.pathname}`;
if (_liveDocument && urlString === _resolveUrl(_liveDocument.doc.file.fullPath)) {
_setStatus(STATUS_ACTIVE);
resetLPEditState();
}
}
Metrics.countEvent(Metrics.EVENT_TYPE.LIVE_PREVIEW, "connect",
Expand Down Expand Up @@ -733,7 +734,18 @@ define(function (require, exports, module) {
*/
function updateConfig(configJSON) {
if (_protocol) {
_protocol.evaluate("_LD.updateConfig('" + configJSON + "')");
_protocol.evaluate("_LD.updateConfig(" + JSON.stringify(configJSON) + ")");
}
}

/**
* this function is to completely reset the live preview edit
* its done so that when live preview is opened/popped out, we can re-update the config so that
* there are no stale markers and edit works perfectly
*/
function resetLPEditState() {
if (_protocol) {
_protocol.evaluate("_LD.resetState()");
}
}

Expand Down
85 changes: 76 additions & 9 deletions src/LiveDevelopment/LivePreviewEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
define(function (require, exports, module) {
const HTMLInstrumentation = require("LiveDevelopment/MultiBrowserImpl/language/HTMLInstrumentation");
const LiveDevMultiBrowser = require("LiveDevelopment/LiveDevMultiBrowser");
const LiveDevelopment = require("LiveDevelopment/main");
const CodeMirror = require("thirdparty/CodeMirror/lib/codemirror");
const ProjectManager = require("project/ProjectManager");
const FileSystem = require("filesystem/FileSystem");
Expand All @@ -41,6 +42,14 @@ define(function (require, exports, module) {

// state manager key, to save the download location of the image
const IMAGE_DOWNLOAD_FOLDER_KEY = "imageGallery.downloadFolder";
const IMAGE_DOWNLOAD_PERSIST_FOLDER_KEY = "imageGallery.persistFolder";

const DOWNLOAD_EVENTS = {
STARTED: 'downloadStarted',
COMPLETED: 'downloadCompleted',
CANCELLED: 'downloadCancelled',
ERROR: 'downloadError'
};

const KernalModeTrust = window.KernalModeTrust;
if(!KernalModeTrust){
Expand Down Expand Up @@ -685,6 +694,32 @@ define(function (require, exports, module) {
}
}

function _sendDownloadStatusToBrowser(eventType, data) {
const currLiveDoc = LiveDevMultiBrowser.getCurrentLiveDoc();
if (currLiveDoc && currLiveDoc.protocol && currLiveDoc.protocol.evaluate) {
const dataJson = JSON.stringify(data || {});
const evalString = `_LD.handleDownloadEvent('${eventType}', ${dataJson})`;
currLiveDoc.protocol.evaluate(evalString);
}
}

function _handleDownloadError(error, downloadId) {
console.error('something went wrong while download the image. error:', error);
if (downloadId) {
_sendDownloadStatusToBrowser(DOWNLOAD_EVENTS.ERROR, { downloadId: downloadId });
}
}

function _trackDownload(downloadLocation) {
if (!downloadLocation) {
return;
}
fetch(`https://images.phcode.dev/api/images/download?download_location=${encodeURIComponent(downloadLocation)}`)
.catch(error => {
console.error('download tracking failed:', error);
});
}

/**
* Helper function to update image src attribute and dismiss ribbon gallery
*
Expand Down Expand Up @@ -716,16 +751,18 @@ define(function (require, exports, module) {
* @param {Directory} projectRoot - the project root in which the image is to be saved
*/
function _handleUseThisImageLocalFiles(message, filename, projectRoot) {
const { tagId, imageData } = message;
const { tagId, imageData, downloadLocation, downloadId } = message;

const uint8Array = new Uint8Array(imageData);
const targetPath = projectRoot.fullPath + filename;

window.fs.writeFile(targetPath, window.Filer.Buffer.from(uint8Array),
{ encoding: window.fs.BYTE_ARRAY_ENCODING }, (err) => {
if (err) {
console.error('Failed to save image:', err);
_handleDownloadError(err, downloadId);
} else {
_trackDownload(downloadLocation);
_sendDownloadStatusToBrowser(DOWNLOAD_EVENTS.COMPLETED, { downloadId });
_updateImageAndDismissRibbon(tagId, targetPath, filename);
}
});
Expand All @@ -738,7 +775,7 @@ define(function (require, exports, module) {
* @param {Directory} projectRoot - the project root in which the image is to be saved
*/
function _handleUseThisImageRemote(message, filename, projectRoot) {
const { imageUrl, tagId } = message;
const { imageUrl, tagId, downloadLocation, downloadId } = message;

fetch(imageUrl)
.then(response => {
Expand All @@ -754,14 +791,16 @@ define(function (require, exports, module) {
window.fs.writeFile(targetPath, window.Filer.Buffer.from(uint8Array),
{ encoding: window.fs.BYTE_ARRAY_ENCODING }, (err) => {
if (err) {
console.error('Failed to save image:', err);
_handleDownloadError(err, downloadId);
} else {
_trackDownload(downloadLocation);
_sendDownloadStatusToBrowser(DOWNLOAD_EVENTS.COMPLETED, { downloadId });
_updateImageAndDismissRibbon(tagId, targetPath, filename);
}
});
})
.catch(error => {
console.error('Failed to fetch image:', error);
_handleDownloadError(error, downloadId);
});
}

Expand All @@ -778,6 +817,10 @@ define(function (require, exports, module) {
return;
}

if (message.downloadId) {
_sendDownloadStatusToBrowser(DOWNLOAD_EVENTS.STARTED, { downloadId: message.downloadId });
}

const filename = message.filename;
const extnName = message.extnName || "jpg";

Expand All @@ -792,11 +835,17 @@ define(function (require, exports, module) {
// the directory name that user wrote, first check if it exists or not
// if it doesn't exist we create it and then download the image inside it
targetDir.exists((err, exists) => {
if (err) { return; }
if (err) {
_handleDownloadError(err, message.downloadId);
return;
}

if (!exists) {
targetDir.create((err) => {
if (err) { return; }
if (err) {
_handleDownloadError(err, message.downloadId);
return;
}
_downloadImageToDirectory(message, filename, extnName, targetDir);
});
} else {
Expand Down Expand Up @@ -1109,6 +1158,10 @@ define(function (require, exports, module) {
let rootFolders = [];
let stringMatcher = null;

const persistFolder = StateManager.get(IMAGE_DOWNLOAD_PERSIST_FOLDER_KEY, StateManager.PROJECT_CONTEXT);
const shouldBeChecked = persistFolder !== false;
$rememberCheckbox.prop('checked', shouldBeChecked);

_scanRootDirectoriesOnly(projectRoot, rootFolders).then(() => {
stringMatcher = new StringMatch.StringMatcher({ segmentedSearch: true });
_renderFolderSuggestions(rootFolders.slice(0, 15), $suggestions, $input);
Expand All @@ -1134,14 +1187,22 @@ define(function (require, exports, module) {
const folderPath = $input.val().trim();

// if the checkbox is checked, we save the folder preference for this project
if ($rememberCheckbox.is(':checked')) {
const isChecked = $rememberCheckbox.is(':checked');
StateManager.set(IMAGE_DOWNLOAD_PERSIST_FOLDER_KEY, isChecked, StateManager.PROJECT_CONTEXT);
if (isChecked) {
StateManager.set(IMAGE_DOWNLOAD_FOLDER_KEY, folderPath, StateManager.PROJECT_CONTEXT);
} else {
StateManager.set(IMAGE_DOWNLOAD_FOLDER_KEY, undefined, StateManager.PROJECT_CONTEXT);
}

// if message is provided, download the image
if (message) {
_downloadToFolder(message, folderPath);
}
} else {
if (message && message.downloadId) {
_sendDownloadStatusToBrowser(DOWNLOAD_EVENTS.CANCELLED, { downloadId: message.downloadId });
}
}
dialog.close();
});
Expand Down Expand Up @@ -1187,7 +1248,7 @@ define(function (require, exports, module) {
_handleUseThisImageRemote(message, uniqueFilename, targetDir);
}
}).catch(error => {
console.error('Something went wrong when trying to use this image', error);
_handleDownloadError(error, message.downloadId);
});
}

Expand Down Expand Up @@ -1234,6 +1295,12 @@ define(function (require, exports, module) {
return;
}

// handle image gallery state change message
if (message.type === "imageGalleryStateChange") {
LiveDevelopment.setImageGalleryState(message.selected);
return;
}

// handle move(drag & drop)
if (message.move && message.sourceId && message.targetId) {
_moveElementInSource(message.sourceId, message.targetId, message.insertAfter, message.insertInside);
Expand Down
36 changes: 35 additions & 1 deletion src/LiveDevelopment/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ define(function main(require, exports, module) {
LivePreviewTransport = require("LiveDevelopment/MultiBrowserImpl/transports/LivePreviewTransport"),
CommandManager = require("command/CommandManager"),
PreferencesManager = require("preferences/PreferencesManager"),
StateManager = require("preferences/StateManager"),
UrlParams = require("utils/UrlParams").UrlParams,
Strings = require("strings"),
ExtensionUtils = require("utils/ExtensionUtils"),
Expand All @@ -56,6 +57,33 @@ define(function main(require, exports, module) {

const EVENT_LIVE_HIGHLIGHT_PREF_CHANGED = "liveHighlightPrefChange";

// state manager key to track image gallery selected state, by default we keep this as selected
// if this is true we show the image gallery when an image element is clicked
const IMAGE_GALLERY_STATE = "livePreview.imageGallery.state";

/**
* get the image gallery state from StateManager
* @returns {boolean} true (default)
*/
function _getImageGalleryState() {
const savedState = StateManager.get(IMAGE_GALLERY_STATE);
return savedState !== undefined && savedState !== null ? savedState : true;
}

/**
* sets the image gallery state
* @param {Boolean} the state that we need to set
*/
function setImageGalleryState(state) {
StateManager.set(IMAGE_GALLERY_STATE, state);

// update the config with the new state
config.imageGalleryState = state;
if (MultiBrowserLiveDev && MultiBrowserLiveDev.status >= MultiBrowserLiveDev.STATUS_ACTIVE) {
MultiBrowserLiveDev.updateConfig(JSON.stringify(config));
}
}

var params = new UrlParams();
var config = {
experimental: false, // enable experimental features
Expand All @@ -70,6 +98,7 @@ define(function main(require, exports, module) {
},
isProUser: isProUser,
elemHighlights: "hover", // default value, this will get updated when the extension loads
imageGalleryState: _getImageGalleryState(), // image gallery selected state
// this strings are used in RemoteFunctions.js
// we need to pass this through config as remoteFunctions runs in browser context and cannot
// directly reference Strings file
Expand All @@ -89,7 +118,11 @@ define(function main(require, exports, module) {
imageGalleryLoadingInitial: Strings.LIVE_DEV_IMAGE_GALLERY_LOADING_INITIAL,
imageGalleryLoadingMore: Strings.LIVE_DEV_IMAGE_GALLERY_LOADING_MORE,
imageGalleryNoImages: Strings.LIVE_DEV_IMAGE_GALLERY_NO_IMAGES,
imageGalleryLoadError: Strings.LIVE_DEV_IMAGE_GALLERY_LOAD_ERROR
imageGalleryLoadError: Strings.LIVE_DEV_IMAGE_GALLERY_LOAD_ERROR,
imageGalleryClose: Strings.LIVE_DEV_IMAGE_GALLERY_CLOSE,
imageGalleryUpload: Strings.LIVE_DEV_IMAGE_GALLERY_UPLOAD,
toastNotEditable: Strings.LIVE_DEV_TOAST_NOT_EDITABLE,
toastDontShowAgain: Strings.LIVE_DEV_TOAST_DONT_SHOW_AGAIN
}
};
// Status labels/styles are ordered: error, not connected, progress1, progress2, connected.
Expand Down Expand Up @@ -402,6 +435,7 @@ define(function main(require, exports, module) {
exports.setLivePreviewTransportBridge = setLivePreviewTransportBridge;
exports.togglePreviewHighlight = togglePreviewHighlight;
exports.setLivePreviewEditFeaturesActive = setLivePreviewEditFeaturesActive;
exports.setImageGalleryState = setImageGalleryState;
exports.updateElementHighlightConfig = updateElementHighlightConfig;
exports.getConnectionIds = MultiBrowserLiveDev.getConnectionIds;
exports.getLivePreviewDetails = MultiBrowserLiveDev.getLivePreviewDetails;
Expand Down
2 changes: 1 addition & 1 deletion src/htmlContent/image-folder-dialog.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ <h1 class="dialog-title">{{Strings.LIVE_DEV_IMAGE_FOLDER_DIALOG_TITLE}}</h1>

<div class="remember-folder-container">
<label>
<input type="checkbox" id="remember-folder-checkbox" checked>
<input type="checkbox" id="remember-folder-checkbox">
<span>{{Strings.LIVE_DEV_IMAGE_FOLDER_DIALOG_REMEMBER}}</span>
</label>
</div>
Expand Down
12 changes: 8 additions & 4 deletions src/nls/root/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,18 @@ define({
"LIVE_DEV_MORE_OPTIONS_AI": "Edit with AI",
"LIVE_DEV_MORE_OPTIONS_IMAGE_GALLERY": "Image Gallery",
"LIVE_DEV_IMAGE_GALLERY_USE_IMAGE": "Use this image",
"LIVE_DEV_IMAGE_GALLERY_SELECT_FROM_COMPUTER": "Select image from computer",
"LIVE_DEV_IMAGE_GALLERY_SELECT_FROM_COMPUTER": "Upload image from computer",
"LIVE_DEV_IMAGE_GALLERY_SELECT_DOWNLOAD_FOLDER": "Choose image download folder",
"LIVE_DEV_IMAGE_GALLERY_SEARCH_PLACEHOLDER": "Search images...",
"LIVE_DEV_IMAGE_GALLERY_SEARCH_PLACEHOLDER": "Search images\u2026",
"LIVE_DEV_IMAGE_GALLERY_SEARCH_BUTTON": "Search",
"LIVE_DEV_IMAGE_GALLERY_LOADING_INITIAL": "Loading images...",
"LIVE_DEV_IMAGE_GALLERY_LOADING_MORE": "Loading...",
"LIVE_DEV_IMAGE_GALLERY_LOADING_INITIAL": "Loading images\u2026",
"LIVE_DEV_IMAGE_GALLERY_LOADING_MORE": "Loading\u2026",
"LIVE_DEV_IMAGE_GALLERY_NO_IMAGES": "No images found",
"LIVE_DEV_IMAGE_GALLERY_LOAD_ERROR": "Failed to load images",
"LIVE_DEV_IMAGE_GALLERY_CLOSE": "Close",
"LIVE_DEV_IMAGE_GALLERY_UPLOAD": "Upload",
"LIVE_DEV_TOAST_NOT_EDITABLE": "Element not editable - generated by script.",
"LIVE_DEV_TOAST_DONT_SHOW_AGAIN": "Don't show again",
"LIVE_DEV_IMAGE_FOLDER_DIALOG_TITLE": "Select Folder to Save Image",
"LIVE_DEV_IMAGE_FOLDER_DIALOG_DESCRIPTION": "Choose where to download the image:",
"LIVE_DEV_IMAGE_FOLDER_DIALOG_PLACEHOLDER": "Type folder path (e.g., assets/images/)",
Expand Down
25 changes: 16 additions & 9 deletions src/styles/brackets_patterns_override.less
Original file line number Diff line number Diff line change
Expand Up @@ -2560,30 +2560,37 @@ code {
}

&:hover {
background-color: @bc-panel-bg-hover-alt;
background-color: rgba(0, 0, 0, 0.03);

.dark & {
background-color: @dark-bc-panel-bg-hover-alt;
background-color: rgba(255, 255, 255, 0.05);
}
}

&.selected {
background-color: @bc-bg-highlight;
border-left-color: @bc-primary-btn-bg;
background-color: rgba(40, 142, 223, 0.08);
border-left-color: #288edf;

.dark & {
background-color: @dark-bc-bg-highlight;
border-left-color: @dark-bc-primary-btn-bg;
background-color: rgba(40, 142, 223, 0.15);
border-left-color: #3da3ff;
}

&:hover {
background-color: rgba(40, 142, 223, 0.12);

.dark & {
background-color: rgba(40, 142, 223, 0.2);
}
}
}
}

.folder-match-highlight {
font-weight: @font-weight-semibold;
color: @bc-primary-btn-bg;
color: #288edf;

.dark & {
color: @dark-bc-primary-btn-bg;
color: #3da3ff;
}
}
}
Expand Down
Loading