Skip to content

Commit dcf234d

Browse files
committed
feat: add hot corner in the live preview for easy toggle to preview mode and back
1 parent 519cbf4 commit dcf234d

File tree

3 files changed

+215
-1
lines changed

3 files changed

+215
-1
lines changed

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function RemoteFunctions(config = {}) {
6464
let _imageRibbonGallery;
6565
let _hyperlinkEditor;
6666
let _currentRulerLines;
67+
let _hotCorner;
6768
let _setup = false;
6869
let _hoverLockTimer = null;
6970

@@ -3292,6 +3293,76 @@ function RemoteFunctions(config = {}) {
32923293
}
32933294
};
32943295

3296+
/**
3297+
* hot corner class,
3298+
* to switch to preview mode and back
3299+
*/
3300+
class HotCorner {
3301+
constructor() {
3302+
this.element = null;
3303+
this.button = null;
3304+
this.box = null;
3305+
this.wasPreviewMode = false;
3306+
this._setup();
3307+
}
3308+
3309+
_setup() {
3310+
const container = document.createElement("div");
3311+
container.setAttribute(GLOBALS.PHCODE_INTERNAL_ATTR, "true");
3312+
3313+
const shadow = container.attachShadow({ mode: "open" });
3314+
3315+
const content = `
3316+
<div class="phoenix-hot-corner">
3317+
<div class="hot-corner-indicator"></div>
3318+
<div class="hot-corner-box">
3319+
<button class="hot-corner-play-btn"> ${config.icons.playButton} </button>
3320+
</div>
3321+
</div>`;
3322+
3323+
shadow.innerHTML = `<style>${config.styles.hotCorner}</style>${content}`;
3324+
3325+
this.element = container;
3326+
this.button = shadow.querySelector(".hot-corner-play-btn");
3327+
this.box = shadow.querySelector(".hot-corner-box");
3328+
3329+
this.button.addEventListener("click", () => {
3330+
window._Brackets_MessageBroker.send({ livePreviewEditEnabled: true, type: "hotCornerPreviewToggle" });
3331+
});
3332+
document.body.appendChild(this.element);
3333+
}
3334+
3335+
updateState(isPreviewMode, showAnimation = false) {
3336+
3337+
if (isPreviewMode) {
3338+
this.button.classList.add("selected");
3339+
3340+
if (!this.wasPreviewMode && showAnimation && this.box) {
3341+
this.box.classList.add("peek-animation");
3342+
3343+
setTimeout(() => {
3344+
if (this.box) {
3345+
this.box.classList.remove("peek-animation");
3346+
}
3347+
}, 1200);
3348+
}
3349+
} else {
3350+
this.button.classList.remove("selected");
3351+
}
3352+
this.wasPreviewMode = isPreviewMode;
3353+
}
3354+
3355+
remove() {
3356+
if (this.element && this.element.parentNode) {
3357+
this.element.parentNode.removeChild(this.element);
3358+
}
3359+
this.element = null;
3360+
this.button = null;
3361+
this.box = null;
3362+
}
3363+
}
3364+
3365+
32953366
function Highlight(color, trigger) {
32963367
this.color = color;
32973368
this.trigger = !!trigger;
@@ -4706,6 +4777,13 @@ function RemoteFunctions(config = {}) {
47064777
const highlightModeChanged = oldHighlightMode !== newHighlightMode;
47074778
const isModeChanged = oldConfig.mode !== config.mode;
47084779
const highlightSettingChanged = oldConfig.highlight !== config.highlight;
4780+
4781+
// Update hot corner state when mode changes
4782+
// Show animation when mode changes to help users discover the feature
4783+
if (isModeChanged && _hotCorner) {
4784+
_hotCorner.updateState(config.mode === 'preview', true);
4785+
}
4786+
47094787
// Handle configuration changes
47104788
if (highlightModeChanged || isModeChanged || highlightSettingChanged) {
47114789
_handleConfigurationChange();
@@ -5198,6 +5276,26 @@ function RemoteFunctions(config = {}) {
51985276
}
51995277

52005278
registerHandlers();
5279+
5280+
// init the hot corner after the DOM is ready
5281+
if (document.readyState === 'loading') {
5282+
document.addEventListener('DOMContentLoaded', () => {
5283+
// make sure that also the configs are present
5284+
if (!_hotCorner && config.icons && config.styles) {
5285+
_hotCorner = new HotCorner();
5286+
_hotCorner.updateState(config.mode === 'preview');
5287+
}
5288+
});
5289+
} else {
5290+
// or if the DOM is already ready then init directly
5291+
setTimeout(() => {
5292+
if (!_hotCorner && config.icons && config.styles) {
5293+
_hotCorner = new HotCorner();
5294+
_hotCorner.updateState(config.mode === 'preview');
5295+
}
5296+
}, 0);
5297+
}
5298+
52015299
let customReturns = {};
52025300
// the below code comment is replaced by added scripts for extensibility
52035301
// REPLACE_WITH_ADDED_REMOTE_SCRIPTS

src/LiveDevelopment/RemoteHelper.js

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@ define(function (require, exports, module) {
185185
check: `
186186
<svg viewBox="0 0 640 640" fill="currentColor">
187187
<path d="M530.8 134.1C545.1 144.5 548.3 164.5 537.9 178.8L281.9 530.8C276.4 538.4 267.9 543.1 258.5 543.9C249.1 544.7 240 541.2 233.4 534.6L105.4 406.6C92.9 394.1 92.9 373.8 105.4 361.3C117.9 348.8 138.2 348.8 150.7 361.3L252.2 462.8L486.2 141.1C496.6 126.8 516.6 123.6 530.9 134z"/>
188+
</svg>`,
189+
190+
playButton: `
191+
<svg viewBox="0 0 640 640" fill="currentColor">
192+
<path d="M187.2 100.9C174.8 94.1 159.8 94.4 147.6 101.6C135.4 108.8 128 121.9 128 136L128 504C128 518.1 135.5 531.2 147.6 538.4C159.7 545.6 174.8 545.9 187.2 539.1L523.2 355.1C536 348.1 544 334.6 544 320C544 305.4 536 291.9 523.2 284.9L187.2 100.9z"/>
188193
</svg>`
189194
};
190195

@@ -1056,6 +1061,110 @@ define(function (require, exports, module) {
10561061
}
10571062
`;
10581063

1064+
const hotCornerStyles = `
1065+
:host {
1066+
all: initial !important;
1067+
}
1068+
1069+
.phoenix-hot-corner {
1070+
position: fixed !important;
1071+
top: 0 !important;
1072+
left: 50% !important;
1073+
transform: translateX(-50%) !important;
1074+
z-index: 2147483646 !important;
1075+
pointer-events: none !important;
1076+
}
1077+
1078+
.hot-corner-indicator {
1079+
width: 70px !important;
1080+
height: 5px !important;
1081+
background-color: rgba(160, 160, 160, 0.4) !important;
1082+
border-radius: 0 0 3px 3px !important;
1083+
margin: 0 auto !important;
1084+
pointer-events: auto !important;
1085+
}
1086+
1087+
.hot-corner-box {
1088+
position: absolute !important;
1089+
top: 0 !important;
1090+
left: 50% !important;
1091+
transform: translateX(-50%) translateY(-100%) !important;
1092+
width: 36px !important;
1093+
height: 28px !important;
1094+
background-color: rgba(60, 63, 65, 0.95) !important;
1095+
border-radius: 0 0 6px 6px !important;
1096+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
1097+
display: flex !important;
1098+
align-items: center !important;
1099+
justify-content: center !important;
1100+
opacity: 0 !important;
1101+
transition: transform 0.3s ease-out, opacity 0.3s ease-out !important;
1102+
pointer-events: auto !important;
1103+
}
1104+
1105+
.phoenix-hot-corner:hover .hot-corner-box {
1106+
transform: translateX(-50%) translateY(0) !important;
1107+
opacity: 1 !important;
1108+
}
1109+
1110+
.hot-corner-box.peek-animation {
1111+
animation: peekDown 1.2s ease-in-out !important;
1112+
transition: none !important;
1113+
}
1114+
1115+
@keyframes peekDown {
1116+
0% {
1117+
transform: translateX(-50%) translateY(-100%);
1118+
opacity: 0;
1119+
}
1120+
25% {
1121+
transform: translateX(-50%) translateY(0);
1122+
opacity: 1;
1123+
}
1124+
75% {
1125+
transform: translateX(-50%) translateY(0);
1126+
opacity: 1;
1127+
}
1128+
100% {
1129+
transform: translateX(-50%) translateY(-100%);
1130+
opacity: 0;
1131+
}
1132+
}
1133+
1134+
.hot-corner-play-btn {
1135+
background-color: transparent !important;
1136+
border: none !important;
1137+
color: #a0a0a0 !important;
1138+
font-size: 16px !important;
1139+
width: 100% !important;
1140+
height: 100% !important;
1141+
cursor: pointer !important;
1142+
display: flex !important;
1143+
align-items: center !important;
1144+
justify-content: center !important;
1145+
transition: color 0.2s ease !important;
1146+
padding: 0 !important;
1147+
}
1148+
1149+
.hot-corner-play-btn:hover {
1150+
color: #c0c0c0 !important;
1151+
}
1152+
1153+
.hot-corner-play-btn.selected {
1154+
color: #FBB03B !important;
1155+
}
1156+
1157+
.hot-corner-play-btn.selected:hover {
1158+
color: #FCC04B !important;
1159+
}
1160+
1161+
.hot-corner-play-btn svg {
1162+
width: 18px !important;
1163+
height: 18px !important;
1164+
}
1165+
`;
1166+
1167+
10591168
const remoteStyles = {
10601169
optionsBox: optionsBoxStyles,
10611170
optionsBoxImageGallerySelected: optionsBoxImageGallerySelectedStyles,
@@ -1066,7 +1175,8 @@ define(function (require, exports, module) {
10661175
aiPromptBox: aiPromptBoxStyles,
10671176
imageGallery: imageGalleryStyles,
10681177
hyperlinkEditor: hyperlinkEditorStyles,
1069-
ruler: rulerStyles
1178+
ruler: rulerStyles,
1179+
hotCorner: hotCornerStyles
10701180
};
10711181

10721182
exports.remoteStrings = remoteStrings;

src/extensionsIntegrated/phoenix-pro/LivePreviewEdit.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,6 +1746,12 @@ define(function (require, exports, module) {
17461746
return;
17471747
}
17481748

1749+
// toggle live preview mode using hot corner
1750+
if (message.type === "hotCornerPreviewToggle") {
1751+
_handlePreviewModeToggle(true);
1752+
return;
1753+
}
1754+
17491755
// handle reset image folder selection
17501756
if (message.resetImageFolderSelection) {
17511757
_showFolderSelectionDialog(null);

0 commit comments

Comments
 (0)