/**
* @module modules/responsiveOverlayTool
* @description This module adds an overlay to work with responsive interfaces within the Chromium viewer. This can be activated by adding the query string or command line param of responsivetool=true.
*/
define([
],
function () {
var ResonsiveTool = function (){
window.tEvent.eventStr.EVENT_DO_RESPONSIVE = "EVENT_DO_RESPONSIVE";
this.sourceRect = -1;
this.ui = {
"avatarPlaceholder": ".js-avatar-placeholder"
};
this.jConfig = -1;
this.init();
};
ResonsiveTool.prototype = {
// ______________________________________________________________
// init
/**
* init
* @description Initializes class. Sets data-lang attribute of <body>
* @fires onModelReady()
* @memberOf module:modules/responsiveOverlayTool
* @instance
*/
init: function() {
var self = this;
// is model ready?
if (typeof window.oModel === "undefined" || window.oModel.jConfig === -1){
window.tEvent.addListener("EVENT_MODEL_READY", function(evt, data) {
self.onModelReady();
});
} else {
self.onModelReady();
}
},
// ______________________________________________________________
// onModelReady
/**
* onModelReady
* @description After the model is ready, initialize responsive tool.
* @fires activeTool()
* @fires onResponsive()
* @memberOf module:modules/responsiveOverlayTool
* @instance
*/
onModelReady: function() {
this.jConfig = window.oModel.jConfig;
let self = this;
let qs = window.helpers.parseQuerystring();
let activeTool = false;
if (typeof qs.responsivetool !== "undefined" && qs.responsivetool === "true"){
activeTool = true;
}
// check commandline
if (typeof window.oPrsonas !== "undefined" &&
typeof window.oPrsonas.app !== "undefined" &&
window.oPrsonas.app.getCommandLineParameters() !== null){
if (typeof window.oPrsonas.app.getCommandLineParameters().responsivetool !== "undefined"){
if (window.oPrsonas.app.getCommandLineParameters().responsivetool === "true") {
activeTool = true;
}
}
}
if (activeTool) {
$(self.ui.avatarPlaceholder).css("border", "1px solid red");
if (window.oPrsonas.avatar.isConnected()) {
window.oPrsonas.app.getWindowRect(function(res) {
self.sourceRect = res;
self.activeTool();
} );
} else {
self.sourceRect = {
left: "0",
top: "0"
};
self.activeTool();
self.onResponsive();
}
}
// set up responsive avater handling.
if (this.jConfig.avatar.isResponsive) {
window.addEventListener('resize', () => {
self.onResponsive();
});
window.tEvent.addListener(window.tEvent.eventStr.EVENT_TEMPLATE_LOADED_DETAILS, function(evt, eventId){
self.onResponsive();
});
}
},
// ______________________________________________________________
// activeTool
/**
* activeTool
* @description Applies responsive tool.
* @memberOf module:modules/responsiveOverlayTool
* @instance
*/
activeTool: function() {
// append tool
$("body").append(window.oDxpTemplates["partial_responsive_tool"]());
console.log(" * <ResonsiveTool> Activated");
const viewport = document.getElementById('viewport');
const overlay = document.getElementById('resize-overlay');
const miniGhost = document.getElementById('mini-ghost');
const widthSlider = document.getElementById('width-slider');
const heightSlider = document.getElementById('height-slider');
const widthInput = document.getElementById('width-input');
const heightInput = document.getElementById('height-input');
const widthValue = document.getElementById('width-value');
const heightValue = document.getElementById('height-value');
const submitBtn = document.getElementById('submit-btn');
const panel = document.getElementById('control-panel');
const toggleBtn = document.getElementById('toggle-minimize');
const showBtn = document.getElementById('show-controls-btn');
const hHandle = document.getElementById('resize-horizontal');
const vHandle = document.getElementById('resize-vertical');
const cHandle = document.getElementById('resize-corner');
let isResizing = false;
let isSliderActive = false;
let resizeType = '';
let startX = 0, startY = 0;
let startW = 0, startH = 0;
let ghost = null;
let pendingW = 0;
let pendingH = 0;
const SPEED_MULT = 5;
const MINI_BASE_W = 180;
// ──────────────────────────────
// SINGLE PLACE for commit / resize trigger
// ──────────────────────────────
function triggerCommit(w, h) {
const finalW = Math.round(w);
const finalH = Math.round(h);
console.log(` * <ResonsiveTool> COMMIT SIZE → ${finalW} × ${finalH} px`);
window.oPrsonas.app.resize(Number(oResponsiveTool.sourceRect.top), Number(oResponsiveTool.sourceRect.left), finalW, finalH);
}
// Silent update – only changes display, no commit
function silentUpdateDisplay(w, h) {
w = Math.round(w);
h = Math.round(h);
overlay.textContent = `Width: ${w} px | Height: ${h} px`;
widthValue.textContent = w;
heightValue.textContent = h;
widthSlider.value = w;
heightSlider.value = h;
widthInput.value = w;
heightInput.value = h;
const scale = MINI_BASE_W / Math.max(w, 1);
miniGhost.style.width = `${MINI_BASE_W}px`;
miniGhost.style.height = `${h * scale}px`;
miniGhost.style.display = 'block';
}
// Update show button text with current real dimensions
function updateShowButtonText() {
const w = viewport.clientWidth;
const h = viewport.clientHeight;
showBtn.textContent = `Show Controls ${w} × ${h}`;
}
// Live preview during interaction
function setLiveSize(w, h) {
pendingW = w;
pendingH = h;
silentUpdateDisplay(w, h);
}
// Commit → log → reset to real size
function commitAndReset() {
triggerCommit(pendingW, pendingH);
// Reset controllers to real viewport size (silent)
const realW = viewport.clientWidth;
const realH = viewport.clientHeight;
silentUpdateDisplay(realW, realH);
updateShowButtonText(); // keep button up-to-date
}
// ──────────────────────────────
// Slider + text input sync + ghost preview
// ──────────────────────────────
let sliderGhost = null;
function createSliderGhost() {
if (sliderGhost) return;
const r = viewport.getBoundingClientRect();
sliderGhost = document.createElement('div');
sliderGhost.id = 'slider-ghost';
sliderGhost.style.left = `${r.left}px`;
sliderGhost.style.top = `${r.top}px`;
sliderGhost.style.width = `${viewport.clientWidth}px`;
sliderGhost.style.height = `${viewport.clientHeight}px`;
document.body.appendChild(sliderGhost);
}
function updateSliderGhost(w, h) {
if (!sliderGhost) return;
sliderGhost.style.width = `${Math.round(w)}px`;
sliderGhost.style.height = `${Math.round(h)}px`;
sliderGhost.style.display = 'block';
}
function removeSliderGhost() {
if (sliderGhost) {
document.body.removeChild(sliderGhost);
sliderGhost = null;
}
}
// Live sync for sliders
function syncInputsFromSlider() {
if (!isSliderActive) return;
const w = parseInt(widthSlider.value, 10) || viewport.clientWidth;
const h = parseInt(heightSlider.value, 10) || viewport.clientHeight;
setLiveSize(w, h);
updateSliderGhost(w, h);
widthInput.value = w;
heightInput.value = h;
}
// Live sync for number inputs
function syncSlidersFromInput() {
const w = parseInt(widthInput.value, 10) || viewport.clientWidth;
const h = parseInt(heightInput.value, 10) || viewport.clientHeight;
widthSlider.value = w;
heightSlider.value = h;
setLiveSize(w, h);
updateSliderGhost(w, h);
}
// Attach listeners
widthSlider.addEventListener('input', syncInputsFromSlider);
heightSlider.addEventListener('input', syncInputsFromSlider);
widthInput.addEventListener('input', syncSlidersFromInput);
heightInput.addEventListener('input', syncSlidersFromInput);
// Slider drag lifecycle: create ghost on mousedown, update on input, remove + commit on mouseup
function startSlider() {
isSliderActive = true;
createSliderGhost();
// Listen for mouseup anywhere to detect release
document.addEventListener('mouseup', onSliderMouseUp, { once: true });
}
function onSliderMouseUp() {
isSliderActive = false;
removeSliderGhost();
commitAndReset();
}
widthSlider.addEventListener('mousedown', startSlider);
heightSlider.addEventListener('mousedown', startSlider);
// Submit button – manual commit (no ghost needed here)
submitBtn.addEventListener('click', () => {
const w = parseInt(widthInput.value, 10) || viewport.clientWidth;
const h = parseInt(heightInput.value, 10) || viewport.clientHeight;
setLiveSize(w, h);
commitAndReset();
});
// ──────────────────────────────
// Drag handles
// ──────────────────────────────
function createGhost() {
const r = viewport.getBoundingClientRect();
ghost = document.createElement('div');
ghost.style.cssText = `
position: fixed;
border: 2px dashed #e74c3c;
pointer-events: none;
z-index: 100000;
left: ${r.left}px;
top: ${r.top}px;
width: ${viewport.clientWidth}px;
height: ${viewport.clientHeight}px;
`;
document.body.appendChild(ghost);
}
function startResize(e, type) {
e.preventDefault();
isResizing = true;
resizeType = type;
startX = e.clientX;
startY = e.clientY;
startW = viewport.clientWidth;
startH = viewport.clientHeight;
createGhost();
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', stopResize);
}
function onDrag(e) {
if (!isResizing) return;
let dx = e.clientX - startX;
let dy = e.clientY - startY;
if (e.shiftKey) {
dx *= SPEED_MULT;
dy *= SPEED_MULT;
}
let newW = startW;
let newH = startH;
if (resizeType === 'vertical' || resizeType === 'both') newW = Math.max(320, startW + dx);
if (resizeType === 'horizontal' || resizeType === 'both') newH = Math.max(240, startH + dy);
if (ghost) {
ghost.style.width = `${newW}px`;
ghost.style.height = `${newH}px`;
}
setLiveSize(newW, newH);
}
function stopResize() {
if (!isResizing) return;
isResizing = false;
if (ghost) {
document.body.removeChild(ghost);
ghost = null;
}
commitAndReset();
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', stopResize);
}
hHandle.addEventListener('mousedown', e => startResize(e, 'horizontal'));
vHandle.addEventListener('mousedown', e => startResize(e, 'vertical'));
cHandle.addEventListener('mousedown', e => startResize(e, 'both'));
// ──────────────────────────────
// Minimize / Show Controls + Viewport toggle
// ──────────────────────────────
toggleBtn.addEventListener('click', () => {
panel.classList.add('hidden');
viewport.classList.add('hidden');
$(".resp-tool").addClass("minimized");
});
showBtn.addEventListener('click', () => {
panel.classList.remove('hidden');
viewport.classList.remove('hidden');
$(".resp-tool").removeClass("minimized");
});
// Initialize + update show button text
function init() {
const w = Math.max(320, viewport.clientWidth);
const h = Math.max(240, viewport.clientHeight);
silentUpdateDisplay(w, h);
updateShowButtonText();
}
function updateShowButtonText() {
const w = viewport.clientWidth;
const h = viewport.clientHeight;
showBtn.textContent = `Show Controls ${w} × ${h}`;
}
init();
window.addEventListener('resize', () => {
init();
updateShowButtonText();
});
},
// ______________________________________________________________
// onResponsive
/**
* onResponsive
* @description Fires command to avatar to resize the avatar within the bounds of the avatar placeholder element.
* @memberOf module:modules/responsiveOverlayTool
* @instance
*/
onResponsive: function() {
let self = this;
let rect = $(this.ui.avatarPlaceholder)[0].getBoundingClientRect();
let vw = window.innerWidth;
let vh = window.innerHeight;
console.table({
"pixel (left)": rect.left,
"pixel (top)": rect.top,
"pixel (w)": rect.width,
"pixel (h)": rect.height,
"viewport w": vw,
"viewport h": vh,
"norm top": rect.top / vh,
"norm left": rect.left / vw,
"norm width": rect.width / vw,
"norm height": rect.height / vh
});
window.oPrsonas.avatar.setCameraAvatarBox(
rect.top / vh,
rect.left / vw,
rect.width / vw,
rect.height / vh
);
window.tEvent.fire(window.tEvent.eventStr.EVENT_DO_RESPONSIVE);
}
};
window.oResponsiveTool = new ResonsiveTool();
return (window.oResponsiveTool);
});