Files
zoneminder/web/js/panzoom.js
2024-09-18 12:30:04 +03:00

331 lines
13 KiB
JavaScript

/*
* Copyright (C) 2024 ZoneMinder
* This file is for managing jquery.panzoom.js
*/
var zmPanZoom = {
panZoomMaxScale: 10,
panZoomStep: 0.3,
panZoom: [],
shifted: null,
ctrled: null,
alted: null,
panOnlyWhenZoomed: true,
//canvas: true,
touchAction: 'manipulation',
/*
* param.objString - class or id
*/
init: function(params={}) {
if (!panZoomEnabled) return;
const _this = this;
const object = (params.objString) ? $j(params.objString) : $j('.zoompan');
object.each( function() {
params.obj = this;
params.handleStartEvent = () => {}; //Prevents the progress bar generated by video.js from breaking because it disables preventDefault(), which is enabled by default.
_this.action('enable', params);
});
},
/*
* params['obj'] : DOM object
* params['id'] : monitor id
* params['contain'] : "inside" | "outside", default="outside"
* params['disablePan'] : true || false
* & etc
*/
action: function(action, params) {
const _this = this;
const objString = params['objString'];
const contain = (params['contain']) ? params['contain'] : "outside";
const minScale = (contain != "outside") ? 0.1 : 1.0;
if (action == "enable") {
var id;
if (typeof eventData != 'undefined') {
id = eventData.MonitorId; //Event page
} else {
const obj = this.getStream(params['obj']);
if (obj.length > 0) {
id = stringToNumber(obj[0].id); //Montage & Watch page
}
}
if (!id) {
console.log("The for panZoom action object was not found.", params);
return;
}
$j('.btn-zoom-in').removeClass('hidden');
$j('.btn-zoom-out').removeClass('hidden');
const objPanZoom = (params['additional'] && objString) ? id+objString : id;
// default value for ZM, if not explicitly specified in the parameters
if (!('contain' in params)) params.contain = contain;
if (!('minScale' in params)) params.minScale = minScale;
if (!('maxScale' in params)) params.maxScale = params['additional'] ? 1 : this.panZoomMaxScale;
if (!('step' in params)) params.step = this.panZoomStep;
if (!('cursor' in params)) params.cursor = 'inherit';
if (!('disablePan' in params)) params.disablePan = false;
if (!('roundPixels' in params)) params.roundPixels = false;
if (!('panOnlyWhenZoomed' in params)) params.panOnlyWhenZoomed = this.panOnlyWhenZoomed;
//if (!('canvas' in params)) params.canvas = this.canvas;
if (!('touchAction' in params)) params.touchAction = this.touchAction;
//Direct initialization Panzoom
this.panZoom[objPanZoom] = Panzoom(params['obj'], params);
this.panZoom[objPanZoom].target = params['obj'];
this.panZoom[objPanZoom].additional = params['additional'];
//panZoom[id].pan(10, 10);
//panZoom[id].zoom(1, {animate: true});
// Binds to shift || alt + wheel
params['obj'].parentElement.addEventListener('wheel', function(event) {
if (!_this.shifted && !_this.alted) {
return;
}
if (_this.shifted && _this.alted) {
event.preventDefault(); //Avoid page scrolling
if (!_this.panZoom[objPanZoom].additional) return;
const obj = (event.target.closest('#monitor'+id)) ? event.target.closest('#monitor'+id)/*Watch & Montage*/ : event.target.closest('#eventVideo')/*Event*/;
const objDim = obj.getBoundingClientRect();
//Get the coordinates (x - the middle of the image, y - the top edge) of the point relative to which we will scale the image
const x = objDim.x + objDim.width/2;
const y = objDim.y;
const scale = (event.deltaY < 0) ? _this.panZoom[objPanZoom].getScale() * Math.exp(_this.panZoomStep)/*scrolling up*/ : _this.panZoom[objPanZoom].getScale() / Math.exp(_this.panZoomStep)/*scrolling down*/;
_this.panZoom[objPanZoom].zoomToPoint(scale, {clientX: x, clientY: y});
} else if (_this.shifted && !_this.alted) {
if (!_this.panZoom[id]) return;
_this.panZoom[id].zoomWithWheel(event);
} else {
return;
}
_this.setTriggerChangedMonitors(id);
});
$j(document).on('keyup.panzoom keydown.panzoom', function(e) {
_this.shifted = e.shiftKey ? e.shiftKey : e.shift;
_this.ctrled = e.ctrlKey;
_this.alted = e.altKey;
_this.manageCursor(id);
});
params['obj'].addEventListener('mousemove', handlePanZoomEventMousemove);
params['obj'].addEventListener('panzoomchange', handlePanZoomEventPanzoomchange);
params['obj'].addEventListener('panzoomzoom', handlePanZoomEventPanzoomzoom);
params['obj'].addEventListener('panzoomstart', handlePanZoomEventPanzoomstart);
params['obj'].addEventListener('panzoompan', handlePanzoompan);
params['obj'].addEventListener('panzoomend', handlePanzoomend);
params['obj'].addEventListener('panzoomreset', handlePanzoomreset);
} else if (action == "disable") { //Disable a specific object
if (!this.panZoom[params['id']]) {
console.log(`PanZoom for monitor "${params['id']}" was not initialized.`);
return;
}
//Disables for the entire document!//$j(document).off('keyup.panzoom keydown.panzoom');
const obj = this.panZoom[params['id']].target;
// #videoFeed - Event page, #monitorX - Montage & Watch page
const el = document.getElementById('videoFeed');
const wrapper = el ? el : document.getElementById('monitor'+params['id']);
$j(wrapper).find('.btn-zoom-in, .btn-zoom-out').addClass('hidden');
this.panZoom[params['id']].reset();
this.panZoom[params['id']].resetStyle();
this.panZoom[params['id']].setOptions({disablePan: true, disableZoom: true});
this.panZoom[params['id']].destroy();
obj.removeEventListener('panzoomzoom', handlePanZoomEventPanzoomzoom);
obj.removeEventListener('mousemove', handlePanZoomEventMousemove);
obj.removeEventListener('panzoomchange', handlePanZoomEventPanzoomchange);
obj.removeEventListener('panzoomzoom', handlePanZoomEventPanzoomzoom);
obj.removeEventListener('panzoomstart', handlePanZoomEventPanzoomstart);
obj.removeEventListener('panzoompan', handlePanzoompan);
obj.removeEventListener('panzoomend', handlePanzoomend);
obj.removeEventListener('panzoomreset', handlePanzoomreset);
}
},
zoomIn: function(clickedElement) {
if (clickedElement.target.id) {
var id = stringToNumber(clickedElement.target.id);
} else { //There may be an element without ID inside the button
var id = stringToNumber(clickedElement.target.parentElement.id);
}
if (clickedElement.ctrlKey) {
// Double the zoom step.
this.panZoom[id].zoom(this.panZoom[id].getScale() * Math.exp(this.panZoomStep*2), {animate: true});
} else {
this.panZoom[id].zoomIn();
}
this.setTriggerChangedMonitors(id);
this.setTouchAction(this.panZoom[id]);
this.manageCursor(id);
},
zoomOut: function(clickedElement) {
const id = stringToNumber(clickedElement.target.id ? clickedElement.target.id : clickedElement.target.parentElement.id);
if (clickedElement.ctrlKey) {
// Reset zoom
this.panZoom[id].zoom(1, {animate: true});
} else {
this.panZoom[id].zoomOut();
}
this.setTriggerChangedMonitors(id);
this.setTouchAction(this.panZoom[id]);
this.manageCursor(id);
},
setTouchAction: function(el) {
if (!el) return;
const currentScale = el.getScale().toFixed(1);
if (currentScale == 1) {
el.setOptions({touchAction: 'manipulation'});
} else {
el.setOptions({touchAction: 'none'});
}
},
/*
* id - Monitor ID
* !!! On Montage & Watch page, when you hover over a block of buttons (in the empty space between the buttons themselves), the cursor changes, but no action occurs, you need to review "monitors[i]||monitorStream.setup_onclick(handleClick)"
*/
manageCursor: function(id) {
if (!this.panZoom[id]) {
console.log(`PanZoom for monitor ID=${id} was not initialized.`);
return;
}
var obj;
var obj_btn;
const disablePan = this.panZoom[id].getOptions().disablePan;
const disableZoom = this.panZoom[id].getOptions().disableZoom;
obj = this.getStream(id);
if (obj) { //Montage & Watch page
obj_btn = document.getElementById('button_zoom'+id); //Change the cursor when you hover over the block of buttons at the top of the image. Not required on Event page
} else { //Event page
obj = document.getElementById('videoFeedStream'+id);
}
if (!obj) {
console.log(`Stream with id=${id} not found.`);
return;
}
const currentScale = this.panZoom[id].getScale().toFixed(1);
if (this.shifted && this.ctrled) {
const cursor = (disableZoom) ? 'auto' : 'zoom-out';
obj.style['cursor'] = cursor;
if (obj_btn) {
obj_btn.style['cursor'] = cursor;
}
} else if (this.shifted) {
const cursor = (disableZoom) ? 'auto' : 'zoom-in';
obj.style['cursor'] = cursor;
if (obj_btn) {
obj_btn.style['cursor'] = cursor;
}
} else if (this.ctrled) {
if (currentScale == 1.0) {
obj.style['cursor'] = 'auto';
if (obj_btn) {
obj_btn.style['cursor'] = 'auto';
}
} else {
const cursor = (disableZoom) ? 'auto' : 'zoom-out';
obj.style['cursor'] = cursor;
if (obj_btn) {
obj_btn.style['cursor'] = cursor;
}
}
} else { //No ctrled & no shifted
if (currentScale == 1.0) {
obj.style['cursor'] = 'auto';
if (obj_btn) {
obj_btn.style['cursor'] = 'auto';
}
} else {
const cursor = (disablePan) ? 'auto' : 'move';
obj.style['cursor'] = cursor;
if (obj_btn) {
obj_btn.style['cursor'] = cursor;
}
}
}
if (this.panZoom[id].getScale().toFixed(1) > 1) {
this.panZoom[id].setOptions({handleStartEvent: (event) => {
event.preventDefault();
}});
} else {
this.panZoom[id].setOptions({handleStartEvent: (event) => {}});
}
},
click: function(id) {
if (this.ctrled && this.shifted) {
this.panZoom[id].zoom(1, {animate: true});
} else if (this.ctrled) {
this.panZoom[id].zoomOut();
} else if (this.shifted) {
const scale = this.panZoom[id].getScale() * Math.exp(this.panZoomStep);
const point = {clientX: event.clientX, clientY: event.clientY};
this.panZoom[id].zoomToPoint(scale, point, {focal: {x: event.clientX, y: event.clientY}});
}
if (this.ctrled || this.shifted) {
this.setTriggerChangedMonitors(id);
}
this.setTouchAction(this.panZoom[id]);
},
getStream: function(id) {
if (isNaN(id)) {
const liveStream = $j(id).find('[id ^= "liveStream"]');
const evtStream = $j(id).find('[id ^= "evtStream"]');
return (liveStream.length > 0) ? liveStream : evtStream;
} else {
const liveStream = document.getElementById('liveStream'+id);
const evtStream = document.getElementById('evtStream'+id);
return (liveStream) ? liveStream : evtStream;
}
},
setTriggerChangedMonitors: function(id) {
if (typeof setTriggerChangedMonitors !== 'undefined' && $j.isFunction(setTriggerChangedMonitors)) {
//Montage page
setTriggerChangedMonitors(id);
} else {
// Event page
updateScale = true;
}
}
};
function handlePanZoomEventMousemove(event) {
if (typeof panZoomEventMousemove !== 'undefined' && $j.isFunction(panZoomEventMousemove)) panZoomEventMousemove(event);
}
function handlePanZoomEventPanzoomchange(event) {
if (typeof panZoomEventPanzoomchange !== 'undefined' && $j.isFunction(panZoomEventPanzoomchange)) panZoomEventPanzoomchange(event);
//console.log('panzoomchange', event.detail) // => { x: 0, y: 0, scale: 1 }
}
function handlePanZoomEventPanzoomzoom(event) {
if (typeof panZoomEventPanzoomzoom !== 'undefined' && $j.isFunction(panZoomEventPanzoomzoom)) panZoomEventPanzoomzoom(event);
}
function handlePanZoomEventPanzoomstart(event) {
if (typeof panZoomEventPanzoomstart !== 'undefined' && $j.isFunction(panZoomEventPanzoomstart)) panZoomEventPanzoomstart(event);
}
function handlePanzoompan(event) {
if (typeof panZoomEventPanzoompan !== 'undefined' && $j.isFunction(panZoomEventPanzoompan)) panZoomEventPanzoompan(event);
}
function handlePanzoomend(event) {
if (typeof panZoomEventPanzoomend !== 'undefined' && $j.isFunction(panZoomEventPanzoomend)) panZoomEventPanzoomend(event);
}
function handlePanzoomreset(event) {
if (typeof panZoomEventPanzoomreset !== 'undefined' && $j.isFunction(panZoomEventPanzoomreset)) panZoomEventPanzoomreset(event);
}